Files
ss-crm-manage-web/src/views/Clue/Pool/Comp/DrawerClue.vue
2024-07-31 18:35:36 +08:00

473 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-drawer
v-model="show"
direction="rtl"
size="60%"
style="min-width: 700px"
:with-header="false"
:destroy-on-close="true"
:show-close="true"
:wrapperClosable="true"
@close="destroyMap"
>
<!-- header -->
<el-skeleton :loading="loading" animated>
<div class="flex justify-between" style="height: 32px">
<div class="flex" style="align-items: center">
<b class="mr-5px text-24px">{{ info.name }}</b>
<div class="mr-5px text-16px">{{ info.phone }}</div>
<el-tag type="success">{{ info.intentionStateName }}</el-tag>
</div>
<div>
<el-button type="primary" v-hasPermi="['clue:pool:update']" plain @click="handleUpdate">
修改
</el-button>
<el-button type="danger" v-hasPermi="['clue:pool:delete']" plain @click="handleRemove">
删除
</el-button>
</div>
</div>
</el-skeleton>
<!-- 基础信息 -->
<el-skeleton :loading="loading" animated>
<el-table :data="followList" size="small" border class="mt-10px">
<el-table-column prop="userName" label="跟进人" />
<el-table-column prop="followTime" label="最新跟进时间" :formatter="dateFormatter" />
<el-table-column prop="nextFollowTime" label="下次跟进时间" />
<el-table-column prop="signSate" label="成交状态" />
</el-table>
</el-skeleton>
<el-divider direction="horizontal" />
<!-- 详细信息 -->
<el-tabs v-model="infoIndex" type="border-card">
<el-tab-pane label="跟进记录" name="followRecord">
<el-button
v-if="followList.length"
v-hasPermi="['clue:pool:update']"
class="mb-10px"
type="primary"
@click="addFollow"
>添加跟进记录</el-button
>
<el-timeline>
<el-timeline-item
v-for="item in followRecordList"
:key="item.recordId"
:timestamp="item.operateDate"
placement="top"
>
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div>
<div>
<b class="text-18px" style="line-height: 36px">{{ item.operateUserName }}</b>
</div>
<div>{{ item.content }}</div>
<div class="flex mt-10px" style="align-items: center">
<div class="flex" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>本次跟进时间{{ item.followTime }}</span>
</div>
<div class="flex ml-50px" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>下次跟进时间{{ item.nextFollowTime }}</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</el-tab-pane>
<el-tab-pane label="详细信息" name="infoDetail">
<Descriptions :data="info" :schema="showSchema" :columns="2" />
<div v-if="appStore.getAppInfo?.instanceType == 1">
<el-checkbox v-model="showSchool" :label="true" @change="handleShowSchool">
展示场地
</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">
<el-timeline>
<el-timeline-item
v-for="item in operateRecordList"
:key="item.recordId"
:timestamp="item.operateDate"
placement="top"
>
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人{{ item.operateUserName }}</span>
</div>
<div class="pt-5px pb-5px">
<!-- <span>{{ item.content }}</span> -->
<span v-dompurify-html="item.content"></span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间{{ item.followTime }}</span>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</el-tab-pane>
</el-tabs>
<!-- 新建编辑跟进信息 -->
<DialogFollow ref="followRef" @success="followSuccess" />
<DialogSchoolInfo ref="schoolInfoDialog" />
</el-drawer>
</template>
<script setup name="DrawerClue">
import { useAppStore } from '@/store/modules/app'
import * as ClueApi from '@/api/clue'
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'
import ImgPostion from '@/assets/imgs/flag/position_black.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'
import FlagGreen from '@/assets/imgs/flag/flag_green.png'
import FlagBlue from '@/assets/imgs/flag/flag_blue.png'
import FlagBlack from '@/assets/imgs/flag/flag_black.png'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const appStore = useAppStore()
const show = ref(false)
const info = ref(null)
const loading = ref(false)
const props = defineProps({
schema: {
type: Array
}
})
const showSchema = computed(() => {
const arr1 = [...props.schema]
arr1.forEach((it) => {
if (it.label.includes('日期')) {
it.dateFormat = 'YYYY-MM-DD'
}
})
if (arr1.length % 2 != 0) {
arr1.push({})
}
const arr2 = [
{
field: 'requirement',
label: '诉求',
span: 2
},
{
field: 'remark',
label: '备注',
span: 2,
isEditor: true
}
]
return [...arr1, ...arr2]
})
const followList = ref([])
const followRecordList = ref([])
const operateRecordList = ref([])
// 地图相关
const dialogMap = ref(null)
const aMap = ref(null)
const clueId = ref('')
function getFollowList() {
FollowApi.getFollowList({ clueId: clueId.value }).then((data) => {
followRecordList.value = data.map((item) => ({
operateUserName: item.operateUserName,
content: item.content,
operateDate: formatDate(item.operateTime),
followTime: formatDate(item.operateTime, 'YYYY-MM-DD HH:mm'),
nextFollowTime: formatDate(item.nextFollowTime)
}))
})
}
function getFollowUsers(id) {
ClueApi.getFollowUserList({ id }).then((data) => {
followList.value = data
})
}
async function open(id) {
clueId.value = id
try {
getFollowUsers(id)
getFollowList()
ClueApi.getOpearateRecord({ clueId: id }).then((data) => {
operateRecordList.value = data.map((item) => ({
operateUserName: item.operateUserName,
content: item.content,
operateDate: formatDate(item.operateTime),
followTime: formatDate(item.operateTime, 'YYYY-MM-DD HH:mm')
}))
})
const data = await ClueApi.getClue(id)
info.value = { ...data, ...data.diyParams }
show.value = true
infoIndex.value = 'followRecord'
if (appStore.getAppInfo?.instanceType == 1 && !dialogMap.value) {
nextTick(async () => {
await getSchoolPlace()
initMap(info.value)
})
}
} catch (error) {
console.log(error)
}
}
function followSuccess() {
ClueApi.getClue(clueId.value).then((data) => {
info.value = { ...data, ...data.diyParams }
})
getFollowList()
emit('getList')
}
const placeList = ref([])
async function getSchoolPlace() {
const data = await getPlaceList({ placeStatus: 0 })
placeList.value = data.placeList
}
const defaultLatLng = ref({
lat: 31.86119,
lng: 117.283042
})
function initMap(data) {
AMapLoader.load({
key: '713d839ff505943b0f18e6df45f3b0dc', //设置您的key
version: '2.0'
}).then((AMap) => {
aMap.value = AMap
if (data.lng || data.lat) {
defaultLatLng.value = {
lng: data.lng,
lat: data.lat
}
}
dialogMap.value = new AMap.Map('dialogMap', {
zoom: 14,
zooms: [2, 22],
center: [defaultLatLng.value.lng, defaultLatLng.value.lat]
})
if (data.lng || data.lat) {
addmark(data.lng, data.lat, AMap)
}
})
}
const showSchool = ref(false)
const schoolMarkers = ref([])
function handleShowSchool() {
if (showSchool.value) {
const flagMap = {
red: FlagRed,
yellow: FlagYellow,
purple: FlagPurple,
green: FlagGreen,
blue: FlagBlue,
black: FlagBlack
}
schoolMarkers.value = []
for (let i = 0; i < placeList.value.length; i++) {
const place = placeList.value[i]
const marker = new aMap.value.Marker({
map: dialogMap.value,
position: [place.lng, place.lat],
label: {
content: place.name,
direction: 'left'
},
icon: flagMap[place.flagColor || 'red'],
extData: place,
clickable: true
})
marker.on('click', (ev) => showSchoolInfo(ev.target.getExtData()))
schoolMarkers.value.push(marker)
}
} else {
dialogMap.value.remove(schoolMarkers.value)
}
}
let marker = ref(null)
function addmark(lat, lng, AMap) {
marker.value && removeMarker()
marker.value = new AMap.Marker({
position: new AMap.LngLat(lat, lng),
zoom: 13,
icon: ImgPostion,
offset: [-16, -32]
})
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)
}
const infoIndex = ref('followRecord')
defineExpose({
open
})
const followRef = ref()
function addFollow() {
followRef.value.open(info.value.clueId, info.value.intentionState)
}
function destroyMap() {
dialogMap.value = null
aMap.value = null
}
const emit = defineEmits(['update', 'getList'])
// 修改
function handleUpdate() {
emit('update', info.value)
show.value = false
}
// 删除
async function handleRemove() {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ClueApi.deleteClue(info.value.clueId)
message.success(t('common.delSuccess'))
// 刷新列表
emit('getList')
show.value = false
} catch (err) {
console.log(err)
}
}
</script>
<style lang="scss" scoped></style>