11 changed files with 1008 additions and 13 deletions
Unified View
Diff Options
-
10src/router/menu.ts
-
10src/router/routes/modules/invite.ts
-
2src/views/components/Profile.vue
-
2src/views/invite/index/index.vue
-
443src/views/invite/inviteInfo/data.ts
-
468src/views/invite/inviteInfo/index.vue
-
64src/views/invite/inviteInfo/modal.vue
-
2src/views/invite/list/index.vue
-
2src/views/invite/myList/index.vue
-
2src/views/invite/seasList/index.vue
-
16src/views/market/appointment/index.vue
@ -0,0 +1,443 @@ |
|||||
|
import { useAddressData } from '/@/hooks/common' |
||||
|
import { ref } from 'vue' |
||||
|
import dayjs, { Dayjs } from 'dayjs' |
||||
|
import { FormSchema } from '/@/components/Form' |
||||
|
import { genderList, channelList, followStageList, paymentList, marriageList, storeAppointmentStatus } from '/@/enums/customerEnum' |
||||
|
import { |
||||
|
getIncomeList, |
||||
|
getNationList, |
||||
|
getEducationList, |
||||
|
getFamilyTiesList, |
||||
|
getOccupationList, |
||||
|
getBodilyFormList, |
||||
|
getPropertyPermits, |
||||
|
getAccountTypeList, |
||||
|
getIdentityTypeList, |
||||
|
getMaritalStatusList, |
||||
|
getConstellationList, |
||||
|
getCarPurchaseSituation, |
||||
|
} from '/@/api/essentialData' |
||||
|
|
||||
|
import { incomeList } from '/@/enums/customerEnum' |
||||
|
|
||||
|
import { getStoreTeam } from '/@/api/clue' |
||||
|
|
||||
|
// 基础信息的额外数据
|
||||
|
export const basicInfoMoreData = ref<any>({}) |
||||
|
// 获取地区数据
|
||||
|
const { addressList, domicilePlaceList } = useAddressData() |
||||
|
|
||||
|
// 获取职业列表
|
||||
|
export const occupationList = ref<any>([]) |
||||
|
getOccupationList().then((res) => { |
||||
|
handleOccupationList(res) |
||||
|
occupationList.value = res || [] |
||||
|
}) |
||||
|
function handleOccupationList(data: any, ifFirst = true) { |
||||
|
data?.forEach?.((item: any) => { |
||||
|
const { industry, industryCode, occupation, occupationCode, occupationList } = item |
||||
|
item.label = ifFirst ? industry : occupation |
||||
|
item.value = ifFirst ? industryCode : occupationCode |
||||
|
if (occupationList?.length) { |
||||
|
item.children = occupationList |
||||
|
handleOccupationList(occupationList, false) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 基本信息
|
||||
|
export const basicSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'genderCode', |
||||
|
label: '性别', |
||||
|
component: 'Select', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: ({ formModel }) => { |
||||
|
return { |
||||
|
options: genderList, |
||||
|
disabled: !!formModel.name && formModel.name !== -1, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.genderValue = v?.label |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'channelType', |
||||
|
label: '渠道来源', |
||||
|
defaultValue: 1, |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: channelList, |
||||
|
}, |
||||
|
}, |
||||
|
{ field: 'age', label: '年龄', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
{ field: 'height', label: '身高(cm)', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
{ field: 'weight', label: '体重(kg)', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
{ |
||||
|
field: 'educationCode', |
||||
|
label: '学历', |
||||
|
component: 'ApiSelect', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getEducationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.education = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'incomeCode', |
||||
|
label: '月收入', |
||||
|
component: 'ApiSelect', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getIncomeList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.income = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maritalStatusCode', |
||||
|
label: '婚姻状况', |
||||
|
component: 'ApiSelect', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getMaritalStatusList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.maritalStatusName = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
// { field: 'name', label: '姓名', component: 'Input', componentProps: { disabled: true }, colProps: { span: 8 }, },
|
||||
|
{ |
||||
|
field: 'constellationCode', |
||||
|
label: '星座', |
||||
|
component: 'ApiSelect', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getConstellationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.constellation = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ field: 'childrenNum', label: '孩子数量', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
{ |
||||
|
field: 'address', |
||||
|
label: '居住地', |
||||
|
component: 'Cascader', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
options: addressList, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.provinceName = v?.[0]?.label |
||||
|
basicInfoMoreData.value.cityName = v?.[1]?.label |
||||
|
basicInfoMoreData.value.districtName = v?.[2]?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'domicilePlace',
|
||||
|
// label: '户口所在地',
|
||||
|
// component: 'Cascader',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// componentProps: () => {
|
||||
|
// return {
|
||||
|
// options: domicilePlaceList.value,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// basicInfoMoreData.value.domicilePlaceProvinceName = v?.[0]?.label
|
||||
|
// basicInfoMoreData.value.domicilePlaceCityName = v?.[1]?.label
|
||||
|
// },
|
||||
|
// }
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'hometown', |
||||
|
label: '家乡', |
||||
|
component: 'Cascader', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: () => { |
||||
|
return { |
||||
|
options: domicilePlaceList.value, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.hometownProvinceName = v?.[0]?.label |
||||
|
basicInfoMoreData.value.hometownCityName = v?.[1]?.label |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'identityType',
|
||||
|
// label: '身份',
|
||||
|
// component: 'ApiSelect',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// componentProps: {
|
||||
|
// labelField: 'desc',
|
||||
|
// api: getIdentityTypeList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// basicInfoMoreData.value.identityTypeName = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
// {
|
||||
|
// field: 'nationCode',
|
||||
|
// label: '民族',
|
||||
|
// component: 'ApiSelect',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// componentProps: {
|
||||
|
// labelField: 'cn',
|
||||
|
// valueField: 'id',
|
||||
|
// api: getNationList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// basicInfoMoreData.value.nation = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
// {
|
||||
|
// field: 'bodilyFormCode',
|
||||
|
// label: '体型',
|
||||
|
// component: 'ApiSelect',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// componentProps: {
|
||||
|
// labelField: 'desc',
|
||||
|
// api: getBodilyFormList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// basicInfoMoreData.value.bodilyForm = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
// {
|
||||
|
// field: 'accountTypeCode',
|
||||
|
// label: '户口',
|
||||
|
// component: 'ApiSelect',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// componentProps: {
|
||||
|
// labelField: 'desc',
|
||||
|
// api: getAccountTypeList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// basicInfoMoreData.value.accountTypeName = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'nativePlaceCode', |
||||
|
label: '籍贯', |
||||
|
component: 'Select', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
options: domicilePlaceList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoMoreData.value.nativePlaceName = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: '职业', |
||||
|
field: 'occupationList', |
||||
|
component: 'Cascader', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
options: occupationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
const industry = v?.[0] || {} |
||||
|
const occupation = v?.[1] || {} |
||||
|
basicInfoMoreData.value.industry = industry.label |
||||
|
basicInfoMoreData.value.industryCode = industry.value |
||||
|
basicInfoMoreData.value.occupation = occupation.label |
||||
|
basicInfoMoreData.value.occupationCode = occupation.value |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'onlyChild',
|
||||
|
// label: '是否独生子女',
|
||||
|
// component: 'Select',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// componentProps: {
|
||||
|
// options: [
|
||||
|
// { label: '是', value: 1 },
|
||||
|
// { label: '否', value: 0 },
|
||||
|
// ],
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'housePurchase', |
||||
|
label: '购房情况', |
||||
|
component: 'Select', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
options: [ |
||||
|
{ label: '已购房', value: 1 }, |
||||
|
{ label: '未购房', value: 0 }, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'carPurchase', |
||||
|
label: '购车情况', |
||||
|
component: 'Select', |
||||
|
colProps: { span: 8 }, |
||||
|
componentProps: { |
||||
|
options: [ |
||||
|
{ label: '已购车', value: 1 }, |
||||
|
{ label: '未购车', value: 0 }, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'paymentIntention', |
||||
|
label: '付费意愿', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: paymentList |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'marriageIntention', |
||||
|
label: '结婚意愿', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: marriageList |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'remark', |
||||
|
label: '个人描述', |
||||
|
component: 'InputTextArea', |
||||
|
colProps: { span: 24 }, |
||||
|
componentProps: ({ formModel }) => { |
||||
|
return { |
||||
|
autoSize: { |
||||
|
minRows: 4, |
||||
|
}, |
||||
|
disabled: formModel?.describeAudit, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
{ field: 'empty1', label: '', slot: 'empty1', component: 'Input' }, |
||||
|
] |
||||
|
|
||||
|
export const modalFormSchema: FormSchema[] = [ |
||||
|
// {
|
||||
|
// field: 'datingClueFollowType',
|
||||
|
// label: '线索跟进方式',
|
||||
|
// component: 'Select',
|
||||
|
// required: true,
|
||||
|
// componentProps: {
|
||||
|
// options: followTypeList,
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'datingClueFollowStage', |
||||
|
label: '线索跟进阶段', |
||||
|
required: true, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: followStageList, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// label: '下次跟进时间',
|
||||
|
// component: 'DatePicker',
|
||||
|
// field: 'nextFollowTime',
|
||||
|
// componentProps: {
|
||||
|
// ...dateComProps,
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'remark', |
||||
|
label: '备注', |
||||
|
required: false, |
||||
|
component: 'InputTextArea', |
||||
|
componentProps: { |
||||
|
autosize: { |
||||
|
minRows: 4, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
export const appointmentFormSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'datingStoreOrgId', |
||||
|
label: '预约门店', |
||||
|
component: 'ApiSelect', |
||||
|
required: true, |
||||
|
componentProps: { |
||||
|
valueField: 'orgId', |
||||
|
labelField: 'orgName', |
||||
|
api: getStoreTeam, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: '预约到店时间', |
||||
|
component: 'DatePicker', |
||||
|
field: 'appointmentTime', |
||||
|
required: true, |
||||
|
componentProps: { |
||||
|
showTime: true, |
||||
|
style: { width: '100%' }, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'remark', |
||||
|
label: '备注', |
||||
|
required: false, |
||||
|
component: 'InputTextArea', |
||||
|
componentProps: { |
||||
|
autosize: { |
||||
|
minRows: 4, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
import { BasicColumn } from '/@/components/Table' |
||||
|
export const tableColumns: BasicColumn[] = [ |
||||
|
// { width: 70, title: '性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} },
|
||||
|
{ title: '门店', dataIndex: 'datingStoreOrgName' }, |
||||
|
// { width: 100, title: '渠道来源', dataIndex: 'channelType',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return channelList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ title: '预约到店时间', dataIndex: 'appointmentTime' }, |
||||
|
{ title: '到店状态', dataIndex: 'datingStoreAppointmentStatus', |
||||
|
customRender: ({ text }) => { |
||||
|
return storeAppointmentStatus.find((find) => find.value === text)?.label |
||||
|
}, |
||||
|
}, |
||||
|
{ title: '销售', dataIndex: 'salesConsultantName' }, |
||||
|
{ title: '电邀人', dataIndex: 'telemarketerName' }, |
||||
|
// { width: 100, title: '跟进状态', dataIndex: 'followStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return followStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ title: '电邀团队', dataIndex: 'teleInvitationOrgName' }, |
||||
|
// { width: 100, title: '跟进结果', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return validStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 160, title: '录入时间', dataIndex: 'creatTime' },
|
||||
|
] |
||||
@ -0,0 +1,468 @@ |
|||||
|
<template> |
||||
|
<div class="flex-col" style="height:100%;padding: 16px;" v-if="details"> |
||||
|
<div class="flex-row" style="background: white;width: 100%;padding: 10px;"> |
||||
|
<CropperAvatar :value="avatar" width="90px" @change="uploadAvatarAfter"/> |
||||
|
<div class="flex-col" style="flex: 1;margin-left: 10px;"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="font-size: 18px;font-weight: bold;">{{details.nickName}}</span> |
||||
|
<!-- <Icon icon="ant-design:edit-twotone" size="18px" style="margin-left: 12px;color:#0960bd;"/> --> |
||||
|
<Popconfirm placement="bottom" icon="修改昵称" @confirm="handleNickname"> |
||||
|
<template #title> |
||||
|
<Input placeholder="请输入昵称" v-model:value="nickName" style="margin-top: 12px;"/> |
||||
|
</template> |
||||
|
<Button type="link"><template #icon><Icon icon="ant-design:edit-twotone" size="16px" style="margin-left: 12px;color:#0960bd;"/></template></Button> |
||||
|
</Popconfirm> |
||||
|
|
||||
|
<span style="color: #666;margin: 0 12px;">(ID:{{details.id}} | 渠道来源:{{channelList.find((find) => find.value === details.channelType)?.label}})</span> |
||||
|
<Tag color="pink">#{{followStageList.find((find) => find.value === details.followStatus)?.label}}</Tag> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="margin-top: 5px;"> |
||||
|
<div class="flex-row-center-start" style="width: 240px;"> |
||||
|
<span style="color: #666;">电话:</span> |
||||
|
<span style="color: #333;font-weight: bold;">{{details.phone}}</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="width: 240px;"> |
||||
|
<span style="color: #666;">微信:</span> |
||||
|
<span style="color: #333;font-weight: bold;">{{details.weChatId || '--'}}</span> |
||||
|
</div> |
||||
|
<!-- <div class="flex-row-center-start" style="width: 240px;"> |
||||
|
<span style="color: #666;">客户标签:</span> |
||||
|
<Tag color="#f50" closable @close="onLabelClose">无效</Tag> |
||||
|
<Popconfirm placement="bottom" icon="添加标签" @confirm="handleLabel"> |
||||
|
<template #title> |
||||
|
<Input placeholder="请输入标签" v-model:value="labelName" style="margin-top: 12px;"/> |
||||
|
</template> |
||||
|
<Button style="height: 22px;line-height: 22px;width: 24px;border: 1px solid #0960bd;"> |
||||
|
<template #icon><Icon icon="ant-design:plus-outlined" size="14px" style="color:#0960bd;display: block;"/></template> |
||||
|
</Button> |
||||
|
</Popconfirm> |
||||
|
</div> --> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="margin-top: 5px;"> |
||||
|
<div class="flex-row-center-start" style="width: 240px;"> |
||||
|
<span style="color: #666;">下次跟进时间:</span> |
||||
|
<span style="color: #333;">{{details.nextFollowTime || '--'}}</span> |
||||
|
</div> |
||||
|
<!-- <div class="flex-row-center-start" style="width: 240px;"> |
||||
|
<span style="color: #666;">创建人:</span> |
||||
|
<span style="color: #333;">{{details.creatorName || '--'}}</span> |
||||
|
</div> --> |
||||
|
<div class="flex-row-center-start" style="width: 240px;"> |
||||
|
<span style="color: #666;">跟进人:</span> |
||||
|
<span style="color: #333;">{{details.followName || '--'}}</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-end" style="flex:1;padding-right: 32px;"> |
||||
|
<Button size="small" type="primary">查看下一个客户</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="flex-col" style="background: white;width: 100%;margin-top: 16px;padding: 10px 40px;"> |
||||
|
<div class="flex-row-center-space"> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">签约销售:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">服务红娘:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">约见红娘:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">店邀人:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">核验人:</span> |
||||
|
<span style="color: #333;font-weight: bold;">{{details.verifierName || '--'}}</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">录入人:</span> |
||||
|
<span style="color: #333;font-weight: bold;">{{details.creatorName || '--'}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="flex-row-center-space" style="margin-top: 4px;"> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">签约时间:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">服务次数:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">约见次数:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">店邀时间:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">核验时间:</span> |
||||
|
<span style="color: #333;font-weight: bold;">--</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" style="flex: 1;"> |
||||
|
<span style="color: #666;">录入时间:</span> |
||||
|
<span style="color: #333;font-weight: bold;">{{details.createTime || '--'}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="flex-row" style="width: 100%;flex: 1;margin-top: 16px;"> |
||||
|
<div style="flex: 1;background: white;padding: 0px 10px 10px 10px;"> |
||||
|
<Tabs v-model:activeKey="activeKey" :animated="false" @change="onTabChange"> |
||||
|
<TabPane key="1" tab="客户资料"> |
||||
|
<div class="flex-col" style="flex:1;padding-top: 16px;"> |
||||
|
<Tabs tab-position="left" v-model:activeKey="activeKey3" :animated="false" @change="onInfoChange"> |
||||
|
<TabPane key="1" tab="个人资料"> |
||||
|
<BasicForm @register="registerForm1" style="padding-right: 48px;"></BasicForm> |
||||
|
</TabPane> |
||||
|
<TabPane key="2" tab="择偶要求"> |
||||
|
<BasicForm @register="registerForm3" style="padding-right: 48px;"> |
||||
|
<template #age="{ model }"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<InputNumber placeholder="请输入" :min="0" v-model:value="model['minAge']" /> |
||||
|
<span style="margin: 0 5px">-</span> |
||||
|
<InputNumber placeholder="请输入" :min="0" v-model:value="model['maxAge']" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #height="{ model }"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<InputNumber placeholder="请输入" :min="0" v-model:value="model['minHeight']" /> |
||||
|
<span style="margin: 0 5px">-</span> |
||||
|
<InputNumber placeholder="请输入" :min="0" v-model:value="model['maxHeight']" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #income="{ model }"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<InputNumber placeholder="请输入" :min="0" v-model:value="model['minimumIncome']" /> |
||||
|
<span style="margin: 0 5px">-</span> |
||||
|
<InputNumber placeholder="请输入" :min="0" v-model:value="model['maximumIncome']" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
</BasicForm> |
||||
|
</TabPane> |
||||
|
<TabPane key="3" tab="线索影像"> |
||||
|
<div style="padding: 16px;"> |
||||
|
<OssUpload v-model="uploadList" @remove="onUploadRemove" :multiple="true" :if-custom-render="true" /> |
||||
|
</div> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</div> |
||||
|
</TabPane> |
||||
|
<TabPane key="2" tab="邀约进店"> |
||||
|
<div style="height: 100%;width: 100%;position: relative;"> |
||||
|
<!-- <Button shape="circle" type="primary" ghost style="position: absolute;right: 24rpx;bottom: 24rpx;"> |
||||
|
<template #icon><Icon icon="ant-design:plus-outlined" size="16px" style="color: #0960bd;"/></template> |
||||
|
</Button> --> |
||||
|
<!-- <Table bordered :pagination="false" :columns="tableColumns" :dataSource="appointmentList"> |
||||
|
|
||||
|
</Table> --> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #action="{ record }"> |
||||
|
<TableAction |
||||
|
:actions="[ |
||||
|
{ |
||||
|
label: '取消', |
||||
|
color: 'error', |
||||
|
// onClick: handleCancel.bind(null, record['id']), |
||||
|
}, |
||||
|
{ |
||||
|
label: '编辑', |
||||
|
onClick: handleAppointment.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
label: '结算', |
||||
|
color: 'error', |
||||
|
ifShow: record['status'] === 5, |
||||
|
// onClick: handleSettle.bind(null, record['id']), |
||||
|
} |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
<!-- <Result subTitle="暂无数据"></Result> --> |
||||
|
</div> |
||||
|
</TabPane> |
||||
|
<TabPane key="3" tab="合同"> |
||||
|
<Result subTitle="暂无数据"></Result> |
||||
|
</TabPane> |
||||
|
<TabPane key="4" tab="约见记录"> |
||||
|
<Result subTitle="暂无数据"></Result> |
||||
|
</TabPane> |
||||
|
<template #tabBarExtraContent> |
||||
|
<Button type="link" @click="handleSubmit" v-if="activeKey == '1'"> |
||||
|
<template #icon><Icon icon="ant-design:edit-twotone" size="16px" style="margin-right: -4px;"/></template>保存资料 |
||||
|
</Button> |
||||
|
<Button type="link" @click="handleAppointment({datingClueId: details.id})" v-else-if="activeKey == '2'"> |
||||
|
<template #icon> |
||||
|
<div style="display: inline;"><Icon icon="ant-design:plus-outlined" size="16px" style="margin-right: 2px;"/></div> |
||||
|
</template> |
||||
|
<span>邀约进店</span> |
||||
|
</Button> |
||||
|
</template> |
||||
|
</Tabs> |
||||
|
</div> |
||||
|
<div style="width: 400px;background: white;margin-left: 16px;padding: 0px 10px 10px 10px;" v-if="details.allocationStatus !== 1"> |
||||
|
<Tabs v-model:activeKey="activeKey2" :animated="false" @change="onReportChange"> |
||||
|
<TabPane key="1" tab="跟进小计"> |
||||
|
<div class="flex-col-center-start" style="flex:1;padding-top: 16px;"> |
||||
|
<BasicForm @register="registerForm"></BasicForm> |
||||
|
<div class="flex-row-center-end" style="width: 100%;padding-right: 40px;"> |
||||
|
<Button type="primary" style="width: 120px;margin-top: 36px;" @click="handleReport">保存</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</TabPane> |
||||
|
<TabPane key="2" tab="跟进记录" style="flex:1;padding: 16px 16px 0px 16px;"> |
||||
|
<Spin :spinning="spinning"> |
||||
|
<Timeline v-if="reportList.length"> |
||||
|
<TimelineItem :color="followStageList.find((find) => find.value === item.datingClueFollowStage)?.color" v-for="(item, index) in reportList" :key="index"> |
||||
|
<div class="flex-row-center-space"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: #333;">{{item.operatorName || item.operator}}</span> |
||||
|
<span style="color: #999;margin-left: 8px;">{{item.createTime}}</span> |
||||
|
</div> |
||||
|
<span style="color: #333;">[{{followStageList.find((find) => find.value === item.datingClueFollowStage)?.label}}]</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" v-if="item.nextFollowTime"> |
||||
|
<span style="color: #0960bd;">下次跟进时间:{{item.nextFollowTime}}</span> |
||||
|
</div> |
||||
|
<div class="flex-row-center-start" v-if="item.remark"> |
||||
|
<span style="color: #333;">备注:{{item.remark || "--"}}</span> |
||||
|
</div> |
||||
|
</TimelineItem> |
||||
|
</Timeline> |
||||
|
<Result subTitle="暂无数据" v-else></Result> |
||||
|
</Spin> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</div> |
||||
|
</div> |
||||
|
<AppointmentModal @register="registerModal" @success="handleSuccess" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { useRoute } from 'vue-router' |
||||
|
import { onMounted, ref, nextTick } from 'vue' |
||||
|
import { Tabs, Input, Popconfirm, Button, TabPane, Tag, Timeline, TimelineItem, Result, Spin, InputNumber, Table } from 'ant-design-vue' |
||||
|
import { Icon } from '/@/components/Icon' |
||||
|
import { useMessage } from '/@/hooks/web/useMessage' |
||||
|
import { CropperAvatar } from '/@/components/Cropper'; |
||||
|
import { OssUpload } from '/@/components/OssUpload' |
||||
|
import BasicForm from '/@/components/Form/src/BasicForm.vue' |
||||
|
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
||||
|
import { useForm } from '/@/components/Form' |
||||
|
import { formatToDateTime } from '/@/utils/dateUtil' |
||||
|
import { basicSchema, modalFormSchema, tableColumns } from './data' |
||||
|
import { useModal } from '/@/components/Modal' |
||||
|
import AppointmentModal from './modal.vue' |
||||
|
import { demandMarriageMoreData, demandMarriageSchema } from '/@/views/clue/components/data' |
||||
|
import { getClueInfo, editClueRecord, recordClueing, getClueRecord, getAppointmentList } from '/@/api/clue' |
||||
|
import { genderList, channelList, clueStatusList, followTypeList, followStageList } from '/@/enums/customerEnum' |
||||
|
|
||||
|
const details = ref<any>() |
||||
|
const uploadList = ref<any>([]) |
||||
|
onMounted(() => { |
||||
|
initFetch() |
||||
|
}) |
||||
|
|
||||
|
async function initFetch() { |
||||
|
demandMarriageMoreData.value = {} |
||||
|
const { query } = useRoute() |
||||
|
const { id } = query || {} |
||||
|
if (!id) return |
||||
|
details.value = await getClueInfo(`${id}`) |
||||
|
|
||||
|
const { domicilePlaceProvinceCode, domicilePlaceCityCode } = details.value || {} |
||||
|
const domicilePlace = domicilePlaceCityCode && domicilePlaceProvinceCode ? [domicilePlaceProvinceCode, domicilePlaceCityCode] : [] |
||||
|
const { provinceCode, cityCode, districtCode } = details.value || {} |
||||
|
const address = provinceCode && cityCode && districtCode ? [provinceCode, cityCode, districtCode] : [] |
||||
|
avatar.value = details.value.profilePhoto |
||||
|
await nextTick() |
||||
|
await resetFields1() |
||||
|
await setFieldsValue1({ ...details.value, domicilePlace, address }) |
||||
|
|
||||
|
if(details.value.datingClueImageList && details.value.datingClueImageList.length){ |
||||
|
uploadList.value = details.value.datingClueImageList.map((item: string) => { |
||||
|
return {url: item} |
||||
|
}) |
||||
|
} |
||||
|
activeKey.value = '1' |
||||
|
} |
||||
|
|
||||
|
async function onInfoChange(){ |
||||
|
if(activeKey3.value == '2' && details.value.datingClueDemand){ |
||||
|
await nextTick() |
||||
|
const { domicilePlaceProvinceCode, domicilePlaceCityCode } = details.value.datingClueDemand || {} |
||||
|
const domicilePlace = domicilePlaceCityCode && domicilePlaceProvinceCode ? [domicilePlaceProvinceCode, domicilePlaceCityCode] : [] |
||||
|
const { provinceCode, cityCode, districtCode } = details.value.datingClueDemand || {} |
||||
|
const address = provinceCode && cityCode && districtCode ? [provinceCode, cityCode, districtCode] : [] |
||||
|
await setFieldsValue3({ ...details.value.datingClueDemand, domicilePlace, address }) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onTabChange(){ |
||||
|
if(activeKey.value == '2'){ |
||||
|
reloadTable() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function reloadTable(){ |
||||
|
const dataSource= await getAppointmentList({datingClueId: details.value.id}) |
||||
|
setTableData(dataSource) |
||||
|
} |
||||
|
|
||||
|
const activeKey = ref<string>('1') |
||||
|
const activeKey2 = ref<string>('1') |
||||
|
const activeKey3 = ref<string>('1') |
||||
|
const avatar = ref<string>('') |
||||
|
const nickName = ref<string>('') |
||||
|
const labelName = ref<string>('') |
||||
|
async function handleNickname(){ |
||||
|
if(!nickName.value){ |
||||
|
createMessage.warning('请输入昵称') |
||||
|
return |
||||
|
} |
||||
|
await editClueRecord({id: details.value.id, nickName: nickName.value, phone: details.value.phone, weChatId: details.value.weChatId}) |
||||
|
createMessage.success('修改昵称成功') |
||||
|
details.value.nickName = nickName.value |
||||
|
nickName.value = '' |
||||
|
} |
||||
|
async function onLabelClose(id){ |
||||
|
|
||||
|
} |
||||
|
async function handleLabel(){ |
||||
|
if(!labelName.value){ |
||||
|
createMessage.warning('请输入标签') |
||||
|
return |
||||
|
} |
||||
|
// await editClueRecord({id: details.value.id, nickName: nickName.value}) |
||||
|
// createMessage.success('修改昵称成功') |
||||
|
// details.value.nickName = nickName.value |
||||
|
labelName.value = '' |
||||
|
} |
||||
|
// 基础信息表单配置 |
||||
|
const [registerForm1, { validate: validate1, resetFields: resetFields1, getFieldsValue: getFieldsValue1, setFieldsValue: setFieldsValue1 }] = useForm({ |
||||
|
labelWidth: 120, |
||||
|
schemas: basicSchema, |
||||
|
baseColProps: { span: 22 }, |
||||
|
showActionButtonGroup: false, |
||||
|
}) |
||||
|
//这里是上传头像成功后的回调 |
||||
|
async function uploadAvatarAfter(value) { |
||||
|
await editClueRecord({id: details.value.id, profilePhoto: value, phone: details.value.phone, weChatId: details.value.weChatId}) |
||||
|
details.value.profilePhoto = value |
||||
|
} |
||||
|
|
||||
|
function onUploadRemove({file, index}){ |
||||
|
uploadList.value.splice(index, 1) |
||||
|
} |
||||
|
// 择偶标准表单配置 |
||||
|
const [registerForm3, { getFieldsValue: getFieldsValue3, setFieldsValue: setFieldsValue3 }] = |
||||
|
useForm({ |
||||
|
labelWidth: 120, |
||||
|
schemas: demandMarriageSchema, |
||||
|
baseColProps: { span: 22 }, |
||||
|
showActionButtonGroup: false, |
||||
|
}) |
||||
|
|
||||
|
async function handleSubmit() { |
||||
|
await validate1() |
||||
|
const baseInfo: any = getFieldsValue1() |
||||
|
const address1 = baseInfo.address |
||||
|
baseInfo.provinceCode = address1?.[0] |
||||
|
baseInfo.cityCode = address1?.[1] |
||||
|
baseInfo.districtCode = address1?.[2] |
||||
|
const domicilePlace1 = baseInfo.domicilePlace |
||||
|
baseInfo.domicilePlaceProvinceCode = domicilePlace1?.[0] |
||||
|
baseInfo.phone = details.value.phone || '' |
||||
|
baseInfo.weChatId = details.value.weChatId || '' |
||||
|
|
||||
|
const demandInfo: any = getFieldsValue3() |
||||
|
const { domicilePlace, address } = demandInfo || {} |
||||
|
baseInfo.datingClueDemand = { |
||||
|
...demandInfo, |
||||
|
...demandMarriageMoreData.value, |
||||
|
cityCode: address?.[1], |
||||
|
provinceCode: address?.[0], |
||||
|
districtCode: address?.[2], |
||||
|
domicilePlaceCityCode: domicilePlace?.[1], |
||||
|
domicilePlaceProvinceCode: domicilePlace?.[0] |
||||
|
} |
||||
|
const datingClueImageList = uploadList.value.map((item: any) => { |
||||
|
if(item.response && item.response.url){ |
||||
|
return item.response.url |
||||
|
} |
||||
|
return item.url |
||||
|
}) |
||||
|
await editClueRecord({id: details.value.id, ...baseInfo, datingClueImageList}) |
||||
|
createMessage.success('保存成功') |
||||
|
} |
||||
|
const { createMessage } = useMessage() |
||||
|
const [registerForm, { resetFields, validate, getFieldsValue }] = |
||||
|
useForm({ |
||||
|
labelWidth: 120, |
||||
|
schemas: modalFormSchema, |
||||
|
baseColProps: { span: 22 }, |
||||
|
showActionButtonGroup: false, |
||||
|
}) |
||||
|
async function handleReport() { |
||||
|
await validate() |
||||
|
const values: any = getFieldsValue() |
||||
|
const { nextFollowTime } = values |
||||
|
values.nextFollowTime = formatToDateTime(nextFollowTime) |
||||
|
values.datingClueId = details.value.id |
||||
|
await recordClueing(values) |
||||
|
createMessage.success('提交成功') |
||||
|
resetFields() |
||||
|
} |
||||
|
const spinning = ref<boolean>(false) |
||||
|
const reportList = ref<any[]>([]); |
||||
|
async function onReportChange(){ |
||||
|
if(activeKey2.value == '2'){ |
||||
|
spinning.value = true |
||||
|
reportList.value = await getClueRecord(details.value.id) |
||||
|
spinning.value = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// const appointmentList = ref<any[]>([]); |
||||
|
const [registerTable, { setTableData }] = useTable({ |
||||
|
bordered: true, |
||||
|
immediate: false, |
||||
|
canResize: false, |
||||
|
pagination: false, |
||||
|
useSearchForm: false, |
||||
|
showIndexColumn: false, |
||||
|
showTableSetting: false, |
||||
|
columns: tableColumns, |
||||
|
actionColumn: { |
||||
|
width: 120, |
||||
|
title: '操作', |
||||
|
fixed: 'right', |
||||
|
dataIndex: 'action', |
||||
|
slots: { customRender: 'action' }, |
||||
|
}, |
||||
|
}) |
||||
|
const [registerModal, { openModal, closeModal }] = useModal() |
||||
|
function handleAppointment(record) { |
||||
|
openModal(true, {record}) |
||||
|
} |
||||
|
// 操作成功 |
||||
|
function handleSuccess() { |
||||
|
closeModal() |
||||
|
reloadTable() |
||||
|
initFetch() |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped lang="less"> |
||||
|
::v-deep .ant-input-number { |
||||
|
min-width: 90px; |
||||
|
width: 100% !important; |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,64 @@ |
|||||
|
<template> |
||||
|
<BasicModal v-bind="$attrs" @ok="handleOk" @register="registerModal"> |
||||
|
<BasicForm @register="registerForm" /> |
||||
|
</BasicModal> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref } from 'vue' |
||||
|
import { appointmentFormSchema } from './data' |
||||
|
import { useMessage } from '/@/hooks/web/useMessage' |
||||
|
import { BasicForm, useForm } from '/@/components/Form' |
||||
|
import { BasicModal, useModalInner } from '/@/components/Modal' |
||||
|
import { createAppointment, editAppointment } from '/@/api/clue' |
||||
|
import { formatToDateTime } from '/@/utils/dateUtil' |
||||
|
|
||||
|
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({ |
||||
|
labelWidth: 100, |
||||
|
schemas: appointmentFormSchema, |
||||
|
baseColProps: { span: 22 }, |
||||
|
showActionButtonGroup: false, |
||||
|
}) |
||||
|
|
||||
|
const id = ref<string>('') |
||||
|
const datingClueId = ref<string>('') |
||||
|
const ifUpdate = ref<boolean>(false) |
||||
|
const [registerModal, { setModalProps }] = useModalInner(async (data) => { |
||||
|
const { record } = data |
||||
|
await resetFields() |
||||
|
id.value = record?.id |
||||
|
datingClueId.value = record?.datingClueId |
||||
|
ifUpdate.value = !!id.value |
||||
|
setModalProps({ |
||||
|
minHeight: 50, |
||||
|
title: `预约到店`, |
||||
|
confirmLoading: false, |
||||
|
}) |
||||
|
if (!!ifUpdate.value) { |
||||
|
await setFieldsValue({ ...record }) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
const { createMessage } = useMessage() |
||||
|
const emits = defineEmits(['success']) |
||||
|
async function handleOk() { |
||||
|
try { |
||||
|
const values = await validate() |
||||
|
setModalProps({ confirmLoading: true }) |
||||
|
const { appointmentTime } = values |
||||
|
values.appointmentTime = formatToDateTime(appointmentTime) |
||||
|
if(id.value){ |
||||
|
values.id = id.value |
||||
|
await editAppointment(values) |
||||
|
createMessage.success(`编辑成功!`) |
||||
|
} else { |
||||
|
values.datingClueId = datingClueId.value |
||||
|
await createAppointment(values) |
||||
|
createMessage.success(`邀约成功!`) |
||||
|
} |
||||
|
emits('success') |
||||
|
} finally { |
||||
|
setModalProps({ confirmLoading: false }) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
Write
Preview
Loading…
Cancel
Save