diff --git a/src/views/Clue/Order/Comp/OrderList.vue b/src/views/Clue/Order/Comp/OrderList.vue index 58ab559..fbb4e93 100644 --- a/src/views/Clue/Order/Comp/OrderList.vue +++ b/src/views/Clue/Order/Comp/OrderList.vue @@ -87,7 +87,22 @@ :prop="item.field" :label="item.label" min-width="120px" - /> + > + <template #default="{ row }"> + <el-popover + placement="top" + width="500px" + trigger="click" + v-if="item.form?.component == 'Editor' && row[item.field]" + > + <template #reference> + <el-button type="primary" style="padding: 0" text>点击查看</el-button> + </template> + <div v-dompurify-html="row.remark"></div> + </el-popover> + <span v-else>{{ row[item.field] }}</span> + </template> + </el-table-column> <el-table-column label="操作" width="240px" fixed="right"> <template #default="scope"> <el-button diff --git a/src/views/Clue/Pool/Comp/DialogClue.vue b/src/views/Clue/Pool/Comp/DialogClue.vue index 4ace2a1..803a903 100644 --- a/src/views/Clue/Pool/Comp/DialogClue.vue +++ b/src/views/Clue/Pool/Comp/DialogClue.vue @@ -47,15 +47,15 @@ </el-table> </el-tab-pane> <el-tab-pane v-if="appStore.getAppInfo?.instanceType == 1" label="位置信息" name="map"> - <div class="flex justify-between"> + <div class="flex justify-between items-center"> <el-select v-model="areaValue" filterable clearable remote - style="width: 350px" + style="width: 250px" reserve-keyword - placeholder="请输入关键词" + placeholder="输入并搜索位置" :remote-method="remoteMethod" @change="currentSelect" > @@ -70,17 +70,44 @@ <span style="float: right; color: #8492a6; font-size: 13px">{{ item.district }}</span> </el-option> </el-select> + <div class="flex-1 flex items-center ml-10px mr-10px"> + <div class="w-100px">线索位置:</div> + <el-input v-model="address" disabled placeholder="请输入线索位置" clearable /> + </div> <el-checkbox v-model="showSchool" :label="true" @change="handleShowSchool"> 展示场地 </el-checkbox> </div> <div id="dialogMap" class="mt-20px" style="height: 400px; width: 100%"></div> - <div class="flex items-center mt-10px mb-10px"> - <div class="w-100px">线索位置:</div> - <el-input v-model="address" placeholder="请输入线索位置" clearable /> - </div> + <el-collapse v-model="collaspeKey" class="box-card"> + <el-collapse-item title="附近驾校" name="nearbySchool"> + <template #header>附近驾校</template> + <div style="padding: 10px"> + <div v-if="nearbySchoolSearching">正在搜索中...</div> + <template v-else> + <div v-for="p in nearbySchoolList" :key="p.index"> + <div + class="hover-pointer" + style="font-size: 14px; color: blue" + @click="getClassType(p)" + > + <i v-if="p.recommend" class="el-icon-star-off"></i> + 驾校: {{ p.deptName }}-{{ p.name }} + </div> + <div class="mt5">地址:{{ p.address }}</div> + <div class="mt5"> + 直线距离: {{ p.distance }} 公里; + <span class="ml0">步行距离:{{ p.walkdistance }}</span> + </div> + <el-divider style="margin: 6px 0 !important" /> + </div> + </template> + </div> + </el-collapse-item> + </el-collapse> </el-tab-pane> </el-tabs> + <DialogSchoolInfo ref="schoolInfoDialog" /> <template #footer> <span> <el-button @click="dialogVisible = false">取 消</el-button> @@ -97,7 +124,10 @@ import * as ClueApi from '@/api/clue' import { getDiyFieldList } from '@/api/clue/clueField' import { formatDate } from '@/utils/formatTime' import AMapLoader from '@amap/amap-jsapi-loader' -import ImgPostion from '@/assets/imgs/flag/flag_red.png' + +import DialogSchoolInfo from './DialogSchoolInfo.vue' + +import ImgPostion from '@/assets/imgs/flag/position_blue.png' import FlagRed from '@/assets/imgs/flag/flag_red.png' import FlagYellow from '@/assets/imgs/flag/flag_yellow.png' import FlagPurple from '@/assets/imgs/flag/flag_purple.png' @@ -201,8 +231,8 @@ const open = async (type, id) => { } } if (appStore.getAppInfo?.instanceType == 1 && !dialogMap.value) { - nextTick(() => { - getSchoolPlace() + nextTick(async () => { + await getSchoolPlace() initMap(info.value) remoteMethod(address.value) }) @@ -220,10 +250,9 @@ function resetForm() { } const placeList = ref([]) -function getSchoolPlace() { - getPlaceList().then((data) => { - placeList.value = data.placeList - }) +async function getSchoolPlace() { + const data = await getPlaceList() + placeList.value = data.placeList } const emit = defineEmits(['success']) @@ -237,6 +266,10 @@ async function handleSave() { message.info('请将跟进人填写完整!') return } + if (!address.value) { + message.info('请选择学员位置!') + return + } // 提交请求 formLoading.value = true try { @@ -299,7 +332,7 @@ function initMap(data) { } } dialogMap.value = new AMap.Map('dialogMap', { - zoom: 12, + zoom: 14, zooms: [2, 22], center: [defaultLatLng.value.lng, defaultLatLng.value.lat] }) @@ -321,6 +354,74 @@ function initMap(data) { }) } +const collaspeKey = ref('nearbySchool') +const nearbySchoolSearching = ref(false) +const nearbySchoolList = ref([]) +function getNearbySchool(info) { + if (info.lng && info.lat) { + nearbySchoolList.value = [] + nearbySchoolSearching.value = true + // 推荐的场地 + let places1 = [] + // 普通的场地 + let places2 = [] + + const p2 = [info.lng, info.lat] + for (let i = 0; i < placeList.value.length; i++) { + const element = placeList.value[i] + const p1 = [element.lng, element.lat] + // 计算直线距离 + element.distance = (aMap.value.GeometryUtil.distance(p1, p2) / 1000).toFixed(2) + element.recommend ? places1.push(element) : places2.push(element) + } + // 按直线距离排序 + // 排序 + if (places1.length > 1) { + places1 = places1.sort((a, b) => a.distance - b.distance) + } + // 排序 + if (places2.length > 1) { + places2 = places2.sort((a, b) => a.distance - b.distance) + } + // 取普通场地和推荐场地,组合, 取四个 + nearbySchoolList.value = [] + for (let i = 0; i < 4; i++) { + places1.length > i && nearbySchoolList.value.push(places1[i]) + places2.length > i && nearbySchoolList.value.push(places2[i]) + if (nearbySchoolList.value.length === 4) { + break + } + } + // 计算步行距离 + nearbySchoolList.value.map(async (item) => { + const p1 = [item.lng, item.lat] + const resp = await getWalkingDistance(p1, p2) + item.walkdistance = resp + }) + nearbySchoolSearching.value = false + } +} + +// 获取两点之间的步行距离 +async function getWalkingDistance(start, end) { + return new Promise((resolve) => { + aMap.value.plugin('AMap.Walking', () => { + const walking = new aMap.value.Walking() + let num = 0 + walking.search(start, end, (status, result) => { + if (status === 'complete') { + result.routes.forEach((item) => { + num += item.distance + }) + resolve(num > 1000 ? `${(num / 1000).toFixed(2)} 公里` : `${num} 米`) + } else { + resolve('步行数据无法确定') + } + }) + }) + }) +} + function regeoCode(lng, lat) { try { geoCoder.value.getAddress([lng, lat], (status, result) => { @@ -345,6 +446,7 @@ function addmark(lat, lng, AMap) { }) dialogMap.value.add(marker.value) dialogMap.value.setCenter([lat, lng], '', 500) + getNearbySchool({ lat: lng, lng: lat }) } function removeMarker() { dialogMap.value.remove(marker.value) @@ -376,6 +478,7 @@ function handleShowSchool() { extData: place, clickable: true }) + marker.on('click', (ev) => showSchoolInfo(ev.target.getExtData())) schoolMarkers.value.push(marker) } } else { @@ -383,6 +486,11 @@ function handleShowSchool() { } } +const schoolInfoDialog = ref() +function showSchoolInfo(val) { + schoolInfoDialog.value.open(val) +} + function remoteMethod(searchValue) { if (searchValue !== '') { setTimeout(() => { diff --git a/src/views/Clue/Pool/Comp/DialogSchoolInfo.vue b/src/views/Clue/Pool/Comp/DialogSchoolInfo.vue new file mode 100644 index 0000000..8b0f4ed --- /dev/null +++ b/src/views/Clue/Pool/Comp/DialogSchoolInfo.vue @@ -0,0 +1,23 @@ +<template> + <el-dialog width="800px" :title="title" v-model="show" append-to-body> + <div v-dompurify-html="detail" class="mb-20px"></div> + </el-dialog> +</template> + +<script setup name="DialogSchoolInfo"> +const title = ref('') +const show = ref(false) +const detail = ref('') + +function open(info) { + title.value = `【${info.schoolName}】详细信息` + show.value = true + detail.value = info.schoolRemark || '该驾校暂未配置详细信息' +} + +defineExpose({ + open +}) +</script> + +<style lang="scss" scoped></style> diff --git a/src/views/Clue/Pool/Comp/DrawerClue.vue b/src/views/Clue/Pool/Comp/DrawerClue.vue index 9bbff93..145b661 100644 --- a/src/views/Clue/Pool/Comp/DrawerClue.vue +++ b/src/views/Clue/Pool/Comp/DrawerClue.vue @@ -83,6 +83,32 @@ 展示场地 </el-checkbox> <div id="dialogMap" class="mt-20px" style="height: 400px; width: 100%"></div> + <el-collapse v-model="collaspeKey" class="box-card"> + <el-collapse-item title="附近驾校" name="nearbySchool"> + <template #header>附近驾校</template> + <div style="padding: 10px"> + <div v-if="nearbySchoolSearching">正在搜索中...</div> + <template v-else> + <div v-for="p in nearbySchoolList" :key="p.index"> + <div + class="hover-pointer" + style="font-size: 14px; color: blue" + @click="getClassType(p)" + > + <i v-if="p.recommend" class="el-icon-star-off"></i> + 驾校: {{ p.deptName }}-{{ p.name }} + </div> + <div class="mt5">地址:{{ p.address }}</div> + <div class="mt5"> + 直线距离: {{ p.distance }} 公里; + <span class="ml0">步行距离:{{ p.walkdistance }}</span> + </div> + <el-divider style="margin: 6px 0 !important" /> + </div> + </template> + </div> + </el-collapse-item> + </el-collapse> </div> </el-tab-pane> <el-tab-pane label="操作记录" name="operateRecord"> @@ -114,6 +140,7 @@ </el-tabs> <!-- 新建编辑跟进信息 --> <DialogFollow ref="followRef" @success="getFollowList" /> + <DialogSchoolInfo ref="schoolInfoDialog" /> </el-drawer> </template> @@ -124,6 +151,7 @@ import * as FollowApi from '@/api/clue/followRecord' import { getPlaceList } from '@/api/school/place' import DialogFollow from './DialogFollow.vue' +import DialogSchoolInfo from './DialogSchoolInfo.vue' import AMapLoader from '@amap/amap-jsapi-loader' import { formatDate, dateFormatter } from '@/utils/formatTime' @@ -214,8 +242,8 @@ async function open(id) { infoIndex.value = 'followRecord' if (appStore.getAppInfo?.instanceType == 1 && !dialogMap.value) { - nextTick(() => { - getSchoolPlace() + nextTick(async () => { + await getSchoolPlace() initMap(info.value) }) } @@ -225,10 +253,9 @@ async function open(id) { } const placeList = ref([]) -function getSchoolPlace() { - getPlaceList().then((data) => { - placeList.value = data.placeList - }) +async function getSchoolPlace() { + const data = await getPlaceList() + placeList.value = data.placeList } const defaultLatLng = ref({ @@ -249,7 +276,7 @@ function initMap(data) { } } dialogMap.value = new AMap.Map('dialogMap', { - zoom: 12, + zoom: 14, zooms: [2, 22], center: [defaultLatLng.value.lng, defaultLatLng.value.lat] }) @@ -285,6 +312,7 @@ function handleShowSchool() { extData: place, clickable: true }) + marker.on('click', (ev) => showSchoolInfo(ev.target.getExtData())) schoolMarkers.value.push(marker) } } else { @@ -302,7 +330,82 @@ function addmark(lat, lng, AMap) { }) dialogMap.value.add(marker.value) dialogMap.value.setCenter([lat, lng], '', 500) + getNearbySchool({ lat: lng, lng: lat }) +} + +const collaspeKey = ref('nearbySchool') +const nearbySchoolSearching = ref(false) +const nearbySchoolList = ref([]) +function getNearbySchool(info) { + if (info.lng && info.lat) { + nearbySchoolList.value = [] + nearbySchoolSearching.value = true + // 推荐的场地 + let places1 = [] + // 普通的场地 + let places2 = [] + + const p2 = [info.lng, info.lat] + for (let i = 0; i < placeList.value.length; i++) { + const element = placeList.value[i] + const p1 = [element.lng, element.lat] + // 计算直线距离 + element.distance = (aMap.value.GeometryUtil.distance(p1, p2) / 1000).toFixed(2) + element.recommend ? places1.push(element) : places2.push(element) + } + // 按直线距离排序 + // 排序 + if (places1.length > 1) { + places1 = places1.sort((a, b) => a.distance - b.distance) + } + // 排序 + if (places2.length > 1) { + places2 = places2.sort((a, b) => a.distance - b.distance) + } + // 取普通场地和推荐场地,组合, 取四个 + nearbySchoolList.value = [] + for (let i = 0; i < 4; i++) { + places1.length > i && nearbySchoolList.value.push(places1[i]) + places2.length > i && nearbySchoolList.value.push(places2[i]) + if (nearbySchoolList.value.length === 4) { + break + } + } + // 计算步行距离 + nearbySchoolList.value.map(async (item) => { + const p1 = [item.lng, item.lat] + const resp = await getWalkingDistance(p1, p2) + item.walkdistance = resp + }) + nearbySchoolSearching.value = false + } } + +// 获取两点之间的步行距离 +async function getWalkingDistance(start, end) { + return new Promise((resolve) => { + aMap.value.plugin('AMap.Walking', () => { + const walking = new aMap.value.Walking() + let num = 0 + walking.search(start, end, (status, result) => { + if (status === 'complete') { + result.routes.forEach((item) => { + num += item.distance + }) + resolve(num > 1000 ? `${(num / 1000).toFixed(2)} 公里` : `${num} 米`) + } else { + resolve('步行数据无法确定') + } + }) + }) + }) +} + +const schoolInfoDialog = ref() +function showSchoolInfo(val) { + schoolInfoDialog.value.open(val) +} + function removeMarker() { dialogMap.value.remove(marker.value) } diff --git a/src/views/SchoolManagement/Place/index.vue b/src/views/SchoolManagement/Place/index.vue index 660cb57..520789a 100644 --- a/src/views/SchoolManagement/Place/index.vue +++ b/src/views/SchoolManagement/Place/index.vue @@ -539,7 +539,7 @@ async function changePlaceStatus(row) { const text = row.status == 0 ? '启用' : '停用' await message.confirm('确认要"' + text + '""' + row.name + '"场地吗?') // 发起修改状态 - await PlaceApi.updatePlaceStatus({ placeId: row.placeId, status: row.status }) + await PlaceApi.updatePlaceStatus(row.placeId, row.status) message.success('修改成功') // 刷新列表 // await getList()