11 changed files with 1008 additions and 13 deletions
Split 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