初始化

This commit is contained in:
qsh
2024-04-28 16:20:45 +08:00
parent 3f2749b6c4
commit 58929c05ef
687 changed files with 90151 additions and 13 deletions

View File

@@ -0,0 +1,246 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-tabs v-model="tabName">
<el-tab-pane label="线索信息" name="info">
<Form
ref="formRef"
v-loading="formLoading"
:rules="rules"
isCol
:schema="allSchemas.formSchema"
/>
</el-tab-pane>
<el-tab-pane label="跟进信息" name="follow">
<el-button type="primary" @click="handleAppendFollow">新增跟进人</el-button>
<el-table :data="followList">
<el-table-column label="跟进人">
<template #default="{ row }">
<el-select v-model="row.followUser" placeholder="选择跟进人" filterable>
<el-option
v-for="item in userOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="nextFollowTime" label="下次跟进时间">
<template #default="{ row }">
<el-date-picker v-model="row.nextFollowTime" type="date" placeholder="选择日期时间" />
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ $index }">
<Icon icon="ep:remove-filled" class="text-red-500" @click="handleRemove($index)" />
<!-- <el-button @click="handleRemove(row, $index)">
</el-button> -->
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="位置信息" name="map">
<div class="flex justify-between">
<el-select
v-model="areaValue"
filterable
clearable
remote
style="width: 350px"
reserve-keyword
placeholder="请输入关键词"
:remote-method="remoteMethod"
@change="currentSelect"
>
<el-option
v-for="item in areaList"
:key="item.id"
:label="item.name"
:value="item.name"
class="one-text"
>
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.district }}</span>
</el-option>
</el-select>
<el-checkbox v-model="showSchool" :label="true" @change="handleShowSchool"
>展示场地</el-checkbox
>
</div>
<div id="dialogMap" class="mt-20px" style="height: 400px; width: 100%"></div>
</el-tab-pane>
</el-tabs>
<template #footer>
<span>
<el-button @click="dialogVisible = false"> </el-button>
<el-button :disabled="formLoading" type="primary" @click="handleSave"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { allSchemas, rules } from '../cluePool.data'
import { formatDate } from '@/utils/formatTime'
import AMapLoader from '@amap/amap-jsapi-loader'
import ImgPostion from '@/assets/imgs/flag/flag_red.png'
import ImgFlag from '@/assets/imgs/flag/position_blue.png'
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formRef = ref() // 表单 Ref
const tabName = ref('info')
const followList = ref([])
const userOptions = ref([])
const areaValue = ref('新天地国际')
const areaList = ref([])
const open = async (type, info) => {
dialogVisible.value = true
dialogTitle.value = type == 'create' ? '新增线索' : '修改线索'
formType.value = type
// 修改时,设置数据
if (info) {
formLoading.value = true
try {
const data = { ...info }
nextTick(() => {
formRef.value.setValues(data)
})
} finally {
formLoading.value = false
}
}
if (!dialogMap.value) {
nextTick(() => {
initMap()
remoteMethod('新天地国际')
})
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
function handleSave() {
console.log('保存')
}
function handleAppendFollow() {
followList.value.push({
followUser: undefined,
nextFollowTime: formatDate(new Date())
})
}
function handleRemove(index) {
followList.value.splice(index, 1)
}
// 地图相关
const dialogMap = ref(null)
const aMap = ref(null)
let AutoComplete = ref(null)
let geoCoder = ref(null)
function initMap() {
AMapLoader.load({
key: '2ffb0e2ea90b1df0b8be48ed66e18fc8', //设置您的key
version: '2.0',
plugins: ['AMap.Geocoder', 'AMap.AutoComplete']
}).then((AMap) => {
aMap.value = AMap
dialogMap.value = new AMap.Map('dialogMap', {
zoom: 12,
zooms: [2, 22],
center: [117.283042, 31.86119]
})
AutoComplete.value = new AMap.AutoComplete({
city: '全国'
})
geoCoder.value = new AMap.Geocoder()
dialogMap.value.on('click', (e) => {
addmark(e.lnglat.getLng(), e.lnglat.getLat(), AMap)
})
})
}
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
})
dialogMap.value.add(marker.value)
dialogMap.value.setCenter([lat, lng], '', 500)
}
function removeMarker() {
dialogMap.value.remove(marker.value)
}
const showSchool = ref(false)
const schoolMarkers = ref([])
function handleShowSchool() {
if (showSchool.value) {
let marker1 = new aMap.value.Marker({
map: dialogMap.value,
position: [117.258001, 31.895216],
label: {
content: '慧安驾校桃花社区训练基地',
direction: 'left'
},
icon: ImgFlag,
// extData: element,
clickable: true
})
let marker2 = new aMap.value.Marker({
map: dialogMap.value,
position: [117.286731, 31.902396],
label: {
content: '皖西瑞星驾校总校D',
direction: 'left'
},
icon: ImgFlag,
// extData: element,
clickable: true
})
schoolMarkers.value = [marker1, marker2]
} else {
dialogMap.value.remove(schoolMarkers.value)
}
}
function remoteMethod(searchValue) {
if (searchValue !== '') {
setTimeout(() => {
AutoComplete.value.search(searchValue, (status, result) => {
if (result.tips?.length) {
areaList.value = result?.tips
}
})
}, 200)
}
}
function currentSelect(val) {
const area = areaList.value.find((it) => it.name == val)
if (area) {
addmark(area.location?.lng, area.location?.lat, aMap.value)
dialogMap.value.setCenter([area.location?.lng, area.location?.lat], '', 500)
}
}
</script>
<style scoped>
:deep() .amap-logo {
display: none !important;
}
:deep() .amap-copyright {
display: none !important;
}
</style>

View File

@@ -0,0 +1,261 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
<div class="flex">
<Form
style="flex: 1"
ref="formRef"
v-loading="formLoading"
isCol
:rules="rules"
:schema="allSchemas.formSchema"
/>
<div class="ml-20px" style="width: 300px">
<el-input
v-model="keyword"
placeholder="请输入关键字查询关键话术"
clearable
@keyup.enter="filterList"
/>
<el-collapse v-model="activeQues" :accordion="false">
<el-collapse-item
v-for="item in showList"
:key="item.skillId"
:title="item.question"
:name="item.skillId"
>
<div v-dompurify-html="item.content"></div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { allSchemas, rules } from './follow.data'
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formRef = ref() // 表单 Ref
const open = async (type, info) => {
dialogVisible.value = true
dialogTitle.value = type == 'create' ? '新增跟进记录' : '修改跟进记录'
formType.value = type
// 修改时,设置数据
if (info) {
formLoading.value = true
try {
const data = { ...info }
formRef.value.setValues(data)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
const resultList = [
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 1,
deptId: 167,
question: '暂时先看题的',
skillKey: '先看科目一',
keyList: ['先看科目一'],
content:
'<p>科目一的题目不难的,等会可以加个微信给您发一些做题技巧,我们驾校离你也很近,后面您计划找驾校报名了可以接您来我们驾校实地看看的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 2,
deptId: 167,
question: '你们怎么练车的',
skillKey: '练车人数',
keyList: ['练车人数'],
content:
'<p>我们驾校这边练车是自己提前在微信上跟教练约时间来练车的,科二练车的时候到你练车了是你一个人一辆车,教练一对一教学的,也可以一对二,不用排队等的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 3,
deptId: 167,
question: '教练怎么样 凶嘛',
skillKey: '教练凶',
keyList: ['教练凶'],
content:
'<p>我们驾校教练都是驾校本部教练,也都是经过正规培训上岗的,脾气比较温和幽默的,不存在凶学员的情况,学车氛围也比较好;学员也不需要给教练送礼的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 4,
deptId: 167,
question: '驾校通过率咋样',
skillKey: '通过率',
keyList: ['通过率'],
content:
'<p>我们驾校在合肥也是老牌驾校的了,在合肥干了十几年了,并且拥有自家考场,科二科三考试都在我们自家考场考试,所以通过率一直非常高</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 5,
deptId: 167,
question: '可以晚上练车吗?',
skillKey: '晚上练车',
keyList: ['晚上练车'],
content:
'<p>我们驾校练车时间是根据学员的时间来安排的,但是晚上练车视线不太好,因为考试的时候也是白天嘛,建议最好是白天过来练车,然后等熟练了后期可以让教练加加班晚上</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 6,
deptId: 167,
question: '大学生有优惠嘛',
skillKey: '学生优惠',
keyList: ['学生优惠'],
content: '<p>学生我们驾校是有优惠的,而且我们驾校有专设学生班,练车不用排队,随到随练的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 7,
deptId: 167,
question: '拿证时间多久',
skillKey: '拿证时间',
keyList: ['拿证时间'],
content: '<p>C1手动挡正常拿证时间45天左右拿证C2自动挡正常35天左右拿证</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 8,
deptId: 167,
question: '驾校有熟人',
skillKey: '熟人',
keyList: ['熟人'],
content:
'<p>你朋友认识的人是教练还是驾校里面工作人员呢,驾校场地在哪呢 学车肯定是要对比的 要个近的合适的,我们驾校离你很近,而且学费目前也在做活动比较优惠,您可以对比一下的,找驾校也是可以自己来现场看过后再决定的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 9,
deptId: 167,
question: '考试地点在哪',
skillKey: '考试地点',
keyList: ['考试地点'],
content:
'<p>科一和科四是统一要去滨湖车管所考试的,科二科三合肥市总共六个考场,科二和科三考试是就近安排考场考试的,这样考试也方便些</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 10,
deptId: 167,
question: '没时间练车',
skillKey: '没时间学车',
keyList: ['没时间学车'],
content:
'<p>我们驾校练车时间根据学员时间来安排,并且也不用每天都来的,一周抽时间来练个三四次就可以的,每次来一个小时这样,科二来个十来次 科三来个十来次就行,耽误不了你太多时间的</p>',
status: 2
}
]
const showList = ref(resultList)
const keyword = ref('')
const activeQues = ref('')
function filterList() {
showList.value = resultList.filter((it) => it.question.includes(keyword.value))
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,92 @@
<template>
<el-dialog title="成交登记" v-model="show" width="800px">
<Descriptions :data="info" :schema="schema" :columns="2" />
<el-form :model="form" ref="formRef" :rules="rules" label-width="80px" class="mt-20px">
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="成交日期">
<el-date-picker
v-model="form.date"
type="date"
placeholder="选择日期时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="是否全款">
<el-radio-group v-model="form.isFull">
<el-radio :label="1"> 全款 </el-radio>
<el-radio :label="2"> 非全款 </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="支付金额">
<el-input-number v-model="form.pay" :min="1" :step="1" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" :offset="0">
<el-form-item label="备注">
<Editor v-model:modelValue="form.remark" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span>
<el-button @click="show = false"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
const show = ref(false)
const form = ref({})
const rules = ref({})
const info = ref({})
const schema = ref([
{
field: 'name',
label: '线索名称'
},
{
field: 'specsName',
label: '联系方式'
},
{
field: 'supplier',
label: '意向状态'
},
{
field: 'supplier',
label: '创建时间'
},
{
field: 'purchaseCount',
label: '诉求',
span: 2
},
{
field: 'remark',
label: '备注',
isEditor: true,
span: 2
}
])
function open(val) {
show.value = true
info.value = { ...val }
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
function handleSave() {
console.log('保存成功')
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,234 @@
<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"
>
<!-- 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">1888888888</div>
<el-tag type="success">A高意向</el-tag>
</div>
<div>
<el-button type="primary" plain>修改</el-button>
<el-button type="danger" plain>删除</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="name" label="跟进人" />
<el-table-column prop="latestFollowTime" label="最新跟进时间" />
<el-table-column prop="nextFollowTime" label="下次跟进时间" />
<el-table-column prop="saleStatus" 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 class="mb-10px" type="primary" @click="addFollow">添加跟进记录</el-button>
<el-timeline>
<el-timeline-item timestamp="2024-04-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div>
<div class="flex justify-between" style="align-items: center">
<div class="flex align-baseline">
<b class="text-18px">张三</b>
<span class="text-14 ml-10px">2024-04-01 09:00:00</span>
</div>
<div>
<el-button type="primary" plain @click="updateFollow()">修改</el-button>
<el-button type="danger" plain>删除</el-button>
</div>
</div>
<div v-dompurify-html="followContent"></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>本次跟进时间2024-04-05 10:57</span>
</div>
<div class="flex ml-50px" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>下次跟进时间2024-04-10 10:00</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div>
<div class="flex justify-between" style="align-items: center">
<div class="flex align-baseline">
<b class="text-18px">李四</b>
<span class="text-14 ml-10px">2024-02-01 09:00:00</span>
</div>
<div>
<el-button type="primary" plain>修改</el-button>
<el-button type="danger" plain>删除</el-button>
</div>
</div>
<div key="followContent" v-dompurify-html="followContent2"></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>本次跟进时间2024-02-05 10:57</span>
</div>
<div class="flex ml-50px" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>下次跟进时间2024-02-10 10:00</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</el-tab-pane>
<el-tab-pane label="详细信息" name="infoDetail">
<el-descriptions :column="2" border>
<el-descriptions-item min-width="150px" label="线索名称">{{
info.name
}}</el-descriptions-item>
<el-descriptions-item min-width="150px" label="联系方式"
>18888888888</el-descriptions-item
>
<el-descriptions-item min-width="150px" label="线索来源">驾考宝典</el-descriptions-item>
<el-descriptions-item min-width="150px" label="意向状态">高意向</el-descriptions-item>
<el-descriptions-item min-width="150px" :span="2" label="诉求"
>这是诉求内容这是诉求内容这是诉求内容这是诉求内容这是诉求内容这是诉求内容这是诉求内容</el-descriptions-item
>
<el-descriptions-item min-width="150px" :span="2" label="备注"
>这是备注内容</el-descriptions-item
>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="操作记录" name="operateRecord">
<el-timeline>
<el-timeline-item timestamp="2024-04-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人1号</span>
</div>
<div class="pt-5px pb-5px">
<span>成交线索</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-05 10:57</span>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人2号</span>
</div>
<div class="pt-5px pb-5px">
<span>修改意向状态为高意向</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-1 10:57</span>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人1号</span>
</div>
<div class="pt-5px pb-5px">
<span>跟进线索</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-1 10:57</span>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人2号</span>
</div>
<div class="pt-5px pb-5px">
<span>创建线索</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-1 10:57</span>
</div>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</el-tab-pane>
</el-tabs>
<!-- 新建编辑跟进信息 -->
<DialogFollow ref="followRef" />
</el-drawer>
</template>
<script setup>
import DialogFollow from './DialogFollow.vue'
const show = ref(false)
const info = ref(null)
const loading = ref(false)
const followContent = `<p style="color: red;">这是本次跟进的内容。</p><br/><p>我还能放图片,但需要你自己排版:</p><br/><img style="width: 200px;" src="https://q6.itc.cn/images01/20240407/0e6be21aebc847648109304f20370790.jpeg">`
const followContent2 = `<p style="color: red;">这是本次跟进的内容。</p>`
const followList = ref([
{
name: '李四',
latestFollowTime: '2024-02-01',
nextFollowTime: '2024-04-01',
saleStatus: '未成交'
},
{
name: '王二',
latestFollowTime: '2024-03-01',
nextFollowTime: '2024-04-11',
saleStatus: '已成交'
}
])
function open(row) {
info.value = row
show.value = true
}
const infoIndex = ref('followRecord')
defineExpose({
open
})
const followRef = ref()
function addFollow() {
followRef.value.open('create', null)
}
function updateFollow() {
followRef.value.open('update', { nextFollowTime: '2024-04-01 12:12' })
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,55 @@
import { dateFormatter } from '@/utils/formatTime'
// 表单校验
export const rules = reactive({
username: [required],
password: [required]
})
// CrudSchemahttps://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive([
{
label: '本次跟进时间',
field: 'createTime',
formatter: dateFormatter,
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm',
placeholder: '创建时间'
}
}
},
{
label: '下次跟进时间',
field: 'nextTime',
isForm: true,
formatter: dateFormatter,
detail: {
dateFormat: 'YYYY-MM-DD HH:mm'
},
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm',
placeholder: '下次跟进时间'
}
}
},
{
label: '跟进内容',
field: 'remark',
isTable: true,
form: {
component: 'Editor',
colProps: {
span: 24
}
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)