40 changed files with 2471 additions and 2295 deletions
Unified View
Diff Options
-
4mock/sys/user.ts
-
44src/api/clue/index.ts
-
2src/enums/pageEnum.ts
-
1src/locales/lang/zh-CN/routes/basic.ts
-
8src/locales/lang/zh-CN/routes/clue.ts
-
6src/locales/lang/zh-CN/routes/invite.ts
-
4src/router/helper/menuHelper.ts
-
56src/router/menu.ts
-
8src/router/menus/modules/clue.ts
-
8src/router/menus/modules/invite.ts
-
17src/router/menus/modules/workbench.ts
-
16src/router/routes/modules/clue.ts
-
16src/router/routes/modules/invite.ts
-
31src/router/routes/workbench.ts
-
8src/store/modules/permission.ts
-
60src/views/clue/clueList/data.ts
-
48src/views/clue/clueList/index.vue
-
574src/views/clue/cluePool/data.ts
-
32src/views/clue/cluePool/index.vue
-
571src/views/clue/components/data.ts
-
14src/views/clue/components/modal.vue
-
2src/views/clue/customer/index.vue
-
124src/views/clue/followlist/data.ts
-
171src/views/clue/followlist/index.vue
-
124src/views/clue/poolist/data.ts
-
254src/views/clue/poolist/index.vue
-
42src/views/invite/components/data.ts
-
54src/views/invite/components/modal.vue
-
627src/views/invite/index/data.ts
-
22src/views/invite/index/index.vue
-
211src/views/invite/index/modal.vue
-
627src/views/invite/list/data.ts
-
24src/views/invite/list/index.vue
-
211src/views/invite/list/modal.vue
-
124src/views/invite/myList/data.ts
-
188src/views/invite/myList/index.vue
-
124src/views/invite/seasList/data.ts
-
191src/views/invite/seasList/index.vue
-
109src/views/sys/workbench/data.ts
-
9src/views/sys/workbench/index.vue
@ -1,4 +1,5 @@ |
|||||
export default { |
export default { |
||||
login: '登录', |
login: '登录', |
||||
|
workbench: '工作台', |
||||
errorLogList: '错误日志列表', |
errorLogList: '错误日志列表', |
||||
} |
} |
||||
@ -1,6 +1,8 @@ |
|||||
export default { |
export default { |
||||
clue: '线索管理', |
clue: '线索管理', |
||||
cluePool: '线索池', |
|
||||
clueList: '我的线索', |
|
||||
customer: '客户信息' |
|
||||
|
cluePool: '待分配线索', |
||||
|
clueList: '已分配线索', |
||||
|
customer: '客户信息', |
||||
|
poolist: '我的线索', |
||||
|
followlist: '跟进记录' |
||||
} |
} |
||||
@ -1,5 +1,7 @@ |
|||||
export default { |
export default { |
||||
invite: '电邀管理', |
invite: '电邀管理', |
||||
index: '线索分配', |
|
||||
list: '我的线索' |
|
||||
|
index: '待分配线索', |
||||
|
list: '已分配线索', |
||||
|
myList: '我的线索', |
||||
|
seasList: '公海线索' |
||||
} |
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
import type { MenuModule } from '/@/router/types' |
||||
|
import { t } from '/@/hooks/web/useI18n' |
||||
|
|
||||
|
const workbenchMenu: MenuModule = { |
||||
|
orderNo: 90001, |
||||
|
menu: { |
||||
|
path: '/workbench', |
||||
|
name: t('routes.basic.workbench'), |
||||
|
children: [ |
||||
|
{ |
||||
|
path: 'index', |
||||
|
name: t('routes.basic.workbench'), |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
} |
||||
|
export default workbenchMenu |
||||
@ -0,0 +1,31 @@ |
|||||
|
import type { AppRouteModule } from '/@/router/types' |
||||
|
|
||||
|
import { LAYOUT } from '/@/router/constant' |
||||
|
import { t } from '/@/hooks/web/useI18n' |
||||
|
|
||||
|
const workbenchRoute: AppRouteModule = { |
||||
|
path: '/workbench', |
||||
|
name: 'Workbench', |
||||
|
component: LAYOUT, |
||||
|
redirect: '/workbench/index', |
||||
|
meta: { |
||||
|
hideChildrenInMenu: true, |
||||
|
icon: 'ant-design:pay-circle-outlined', |
||||
|
title: t('routes.basic.workbench'), |
||||
|
orderNo: 90001, |
||||
|
}, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: 'index', |
||||
|
name: 'WorkbenchPage', |
||||
|
component: () => import('@/views/sys/workbench/index.vue'), |
||||
|
meta: { |
||||
|
title: t('routes.basic.workbench'), |
||||
|
icon: 'ant-design:pay-circle-outlined', |
||||
|
hideMenu: true, |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
} |
||||
|
|
||||
|
export default workbenchRoute; |
||||
@ -0,0 +1,571 @@ |
|||||
|
import { FormSchema } from '/@/components/Table' |
||||
|
import { ref } from 'vue' |
||||
|
import dayjs, { Dayjs } from 'dayjs' |
||||
|
import { genderList, channelList, clueStatusList, followStatusList, validStatusList } from '/@/enums/customerEnum' |
||||
|
import { useAddressData } from '/@/hooks/common' |
||||
|
import { |
||||
|
getIncomeList, |
||||
|
getNationList, |
||||
|
getEducationList, |
||||
|
getFamilyTiesList, |
||||
|
getOccupationList, |
||||
|
getBodilyFormList, |
||||
|
getPropertyPermits, |
||||
|
getAccountTypeList, |
||||
|
getIdentityTypeList, |
||||
|
getMaritalStatusList, |
||||
|
getConstellationList, |
||||
|
getCarPurchaseSituation, |
||||
|
} from '/@/api/essentialData' |
||||
|
|
||||
|
// 基础信息的额外数据
|
||||
|
export const basicInfoData = 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 addressName = ref<string[]>([]) |
||||
|
// 基本信息
|
||||
|
export const modalFormSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'genderCode', |
||||
|
label: '性别', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Select', |
||||
|
required: true, |
||||
|
componentProps: ({ formModel }) => { |
||||
|
return { |
||||
|
options: genderList, |
||||
|
disabled: !!formModel.name && formModel.name !== -1, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.genderValue = v?.label |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
{ field: 'age', label: '年龄', component: 'InputNumber', colProps: { span: 8 },}, |
||||
|
{ field: 'nickName', label: '昵称', component: 'Input', colProps: { span: 8 },}, |
||||
|
{ field: 'phone', label: '电话号码', component: 'Input', colProps: { span: 8 },}, |
||||
|
{ field: 'weChatId', label: '微信号', component: 'Input', colProps: { span: 8 }, }, |
||||
|
{ field: 'height', label: '身高(cm)', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
{ field: 'weight', label: '体重(kg)', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
// {
|
||||
|
// field: 'birthDate',
|
||||
|
// label: '出生日期',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// component: 'DatePicker',
|
||||
|
// defaultValue: moment('2001-06-05').format('YYYY-MM-DD 00:00:00'),
|
||||
|
// componentProps: ({ formModel }) => {
|
||||
|
// return {
|
||||
|
// style: { width: '100%' },
|
||||
|
// disabled: !!formModel.name && formModel.name !== -1,
|
||||
|
// disabledDate: (current: Dayjs) => {
|
||||
|
// const date: Dayjs = dayjs().subtract(18, 'year')
|
||||
|
// return current && current > date.endOf('year')
|
||||
|
// },
|
||||
|
// }
|
||||
|
// },
|
||||
|
// },
|
||||
|
|
||||
|
{ |
||||
|
field: 'educationCode', |
||||
|
label: '学历', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getEducationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.education = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'incomeCode', |
||||
|
label: '月收入', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getIncomeList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.income = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maritalStatusCode', |
||||
|
label: '婚姻状况', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getMaritalStatusList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.maritalStatusName = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'address', |
||||
|
label: '现居住地', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: { |
||||
|
options: addressList, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.provinceName = v?.[0]?.label |
||||
|
basicInfoData.value.cityName = v?.[1]?.label |
||||
|
basicInfoData.value.districtName = v?.[2]?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'domicilePlace', |
||||
|
label: '户口所在地', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: () => { |
||||
|
return { |
||||
|
options: domicilePlaceList.value, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.domicilePlaceProvinceName = v?.[0]?.label |
||||
|
basicInfoData.value.domicilePlaceCityName = v?.[1]?.label |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'hometown', |
||||
|
label: '家乡', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: () => { |
||||
|
return { |
||||
|
options: domicilePlaceList.value, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.hometownProvinceName = v?.[0]?.label |
||||
|
basicInfoData.value.hometownCityName = v?.[1]?.label |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
{ field: 'childrenNum', label: '孩子数量', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
{ |
||||
|
field: 'nationCode', |
||||
|
label: '民族', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'cn', |
||||
|
valueField: 'id', |
||||
|
api: getNationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.nation = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'bodilyFormCode', |
||||
|
label: '体型', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getBodilyFormList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.bodilyForm = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'accountTypeCode',
|
||||
|
// label: '户口',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// component: 'ApiSelect',
|
||||
|
// componentProps: {
|
||||
|
// labelField: 'desc',
|
||||
|
// api: getAccountTypeList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// basicInfoData.value.accountTypeName = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
// {
|
||||
|
// field: 'nativePlaceCode',
|
||||
|
// label: '籍贯',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// component: 'Select',
|
||||
|
// componentProps: {
|
||||
|
// options: domicilePlaceList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// basicInfoData.value.nativePlaceName = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ |
||||
|
label: '职业', |
||||
|
field: 'occupationList', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: { |
||||
|
options: occupationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
const industry = v?.[0] || {} |
||||
|
const occupation = v?.[1] || {} |
||||
|
basicInfoData.value.industry = industry.label |
||||
|
basicInfoData.value.industryCode = industry.value |
||||
|
basicInfoData.value.occupation = occupation.label |
||||
|
basicInfoData.value.occupationCode = occupation.value |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'onlyChild', |
||||
|
label: '是否独生子女', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: [ |
||||
|
{ label: '是', value: 1 }, |
||||
|
{ label: '否', value: 0 }, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'propertyPermitsCode', |
||||
|
label: '购房情况', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getPropertyPermits, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.propertyPermits = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'carPurchaseSituationCode', |
||||
|
label: '购车情况', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getCarPurchaseSituation, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.carPurchaseSituation = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'constellationCode', |
||||
|
label: '星座', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getConstellationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
basicInfoData.value.constellation = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'profilePhoto', |
||||
|
component: 'Upload', |
||||
|
label: '资料头像', |
||||
|
colProps: { span: 8 }, |
||||
|
slot: 'profilePhoto', |
||||
|
}, |
||||
|
{ |
||||
|
field: 'describeInfo', |
||||
|
label: '个人描述', |
||||
|
component: 'InputTextArea', |
||||
|
colProps: { span: 16 }, |
||||
|
componentProps: ({ formModel }) => { |
||||
|
return { |
||||
|
autoSize: { |
||||
|
minRows: 4, |
||||
|
}, |
||||
|
disabled: formModel?.describeAudit, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
// 择偶标准的额外数据
|
||||
|
export const demandMarriageMoreData = ref<any>({}) |
||||
|
// 择偶标准
|
||||
|
export const demandMarriageSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'maritalStatusCode', |
||||
|
label: '婚姻状况', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getMaritalStatusList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.maritalStatusName = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'minAge', |
||||
|
label: '年龄', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'InputNumber', |
||||
|
slot: 'age', |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maxAge', |
||||
|
label: '年龄', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'InputNumber', |
||||
|
ifShow: false, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'minHeight', |
||||
|
label: '身高', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'InputNumber', |
||||
|
slot: 'height', |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maxHeight', |
||||
|
label: '身高', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'InputNumber', |
||||
|
ifShow: false, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'educationCode', |
||||
|
label: '学历', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getEducationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.education = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'minimumIncome', |
||||
|
label: '月收入', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'InputNumber', |
||||
|
slot: 'income', |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maximumIncome', |
||||
|
label: '月收入', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'InputNumber', |
||||
|
ifShow: false, |
||||
|
}, |
||||
|
{ field: 'childrenNum', label: '孩子数量', component: 'InputNumber', colProps: { span: 8 }, }, |
||||
|
{ |
||||
|
field: 'address', |
||||
|
label: '现居住地', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: { |
||||
|
options: addressList, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.provinceName = v?.[0]?.label |
||||
|
demandMarriageMoreData.value.cityName = v?.[1]?.label |
||||
|
demandMarriageMoreData.value.districtName = v?.[2]?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'domicilePlace', |
||||
|
label: '户口所在地', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: { |
||||
|
options: domicilePlaceList, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.domicilePlaceProvinceName = v?.[0]?.label |
||||
|
demandMarriageMoreData.value.domicilePlaceCityName = v?.[1]?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'hometown', |
||||
|
label: '家乡', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: () => { |
||||
|
return { |
||||
|
options: domicilePlaceList.value, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.hometownProvinceName = v?.[0]?.label |
||||
|
demandMarriageMoreData.value.hometownCityName = v?.[1]?.label |
||||
|
}, |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'nationCode', |
||||
|
label: '民族', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'cn', |
||||
|
valueField: 'id', |
||||
|
api: getNationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.nation = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'bodilyFormCode',
|
||||
|
// label: '体型',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// component: 'ApiSelect',
|
||||
|
// componentProps: {
|
||||
|
// labelField: 'desc',
|
||||
|
// api: getBodilyFormList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// demandMarriageMoreData.value.bodilyForm = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
// {
|
||||
|
// field: 'accountTypeCode',
|
||||
|
// label: '户口',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// component: 'ApiSelect',
|
||||
|
// componentProps: {
|
||||
|
// labelField: 'desc',
|
||||
|
// api: getAccountTypeList,
|
||||
|
// getPopupContainer: () => document.body,
|
||||
|
// onChange: (_: any, v: any) => {
|
||||
|
// demandMarriageMoreData.value.accountTypeName = v?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'nativePlaceCode', |
||||
|
label: '籍贯', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: domicilePlaceList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.nativePlaceName = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: '职业', |
||||
|
field: 'occupationList', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Cascader', |
||||
|
componentProps: { |
||||
|
options: occupationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
const industry = v?.[0] || {} |
||||
|
const occupation = v?.[1] || {} |
||||
|
demandMarriageMoreData.value.industry = industry.label |
||||
|
demandMarriageMoreData.value.industryCode = industry.value |
||||
|
demandMarriageMoreData.value.occupation = occupation.label |
||||
|
demandMarriageMoreData.value.occupationCode = occupation.value |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'onlyChild', |
||||
|
label: '是否独生子女', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: [ |
||||
|
{ label: '是', value: 1 }, |
||||
|
{ label: '否', value: 0 }, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'propertyPermitsCode', |
||||
|
label: '购房情况', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getPropertyPermits, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.propertyPermits = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'carPurchaseSituationCode', |
||||
|
label: '购车情况', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getCarPurchaseSituation, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.carPurchaseSituation = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
field: 'constellationCode', |
||||
|
label: '星座', |
||||
|
colProps: { span: 8 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getConstellationList, |
||||
|
getPopupContainer: () => document.body, |
||||
|
onChange: (_: any, v: any) => { |
||||
|
demandMarriageMoreData.value.constellation = v?.label |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
@ -0,0 +1,124 @@ |
|||||
|
import { BasicColumn, FormSchema } from '/@/components/Table' |
||||
|
import { genderList, channelList, clueStatusList, followStatusList, validStatusList } from '/@/enums/customerEnum' |
||||
|
import { getEducationList, getMaritalStatusList } from '/@/api/essentialData' |
||||
|
|
||||
|
export const tableColumns: BasicColumn[] = [ |
||||
|
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } }, |
||||
|
// { width: 70, title: '性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} },
|
||||
|
// { width: 110, title: '手机号码', dataIndex: 'phone' },
|
||||
|
// { width: 100, title: '渠道来源', dataIndex: 'channelType',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return channelList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ title: '分配信息', dataIndex: 'allocateInfo', slots: { customRender: 'allocateInfo' } }, |
||||
|
// { width: 80, title: '状态', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return clueStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 100, title: '创建人', dataIndex: 'creatorName' },
|
||||
|
// { width: 100, title: '核验人', dataIndex: 'verifierName' },
|
||||
|
// { width: 100, title: '跟进状态', dataIndex: 'followStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return followStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 160, title: '最后跟进时间', dataIndex: 'finalFollowTime' },
|
||||
|
// { width: 100, title: '跟进结果', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return validStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 160, title: '录入时间', dataIndex: 'creatTime' },
|
||||
|
] |
||||
|
|
||||
|
export const tableFormSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'genderCode', |
||||
|
label: '用户性别', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: genderList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'channelType', |
||||
|
label: '渠道来源', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: channelList, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'status',
|
||||
|
// label: '状态',
|
||||
|
// component: 'Input',
|
||||
|
// colProps: { span: 6 },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'nickName', |
||||
|
label: '用户昵称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'phone', |
||||
|
label: '电话号码', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maritalStatusCode', |
||||
|
label: '婚姻状况', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getMaritalStatusList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'age', |
||||
|
label: '年龄', |
||||
|
slot: 'age', |
||||
|
component: 'InputNumber', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'educationCode', |
||||
|
label: '学历', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getEducationList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'createTime', |
||||
|
label: '创建时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'creatorName', |
||||
|
label: '创建人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'followTime', |
||||
|
label: '最后跟进时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'verifierName', |
||||
|
label: '核验人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
] |
||||
@ -0,0 +1,171 @@ |
|||||
|
<template> |
||||
|
<div class="order-list"> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #form-age> |
||||
|
<div class="flex-row"> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.minAge" /> |
||||
|
<div style="width: 8%" class="flex-row-center-center">-</div> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.maxAge" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<!-- <template #toolbar> |
||||
|
<div style="width: 100%; padding: 2px" class="flex-row-center-space"> |
||||
|
<RadioGroup button-style="solid" v-model:value="radioVal" @change="handleRadioChange"> |
||||
|
<RadioButton v-for="item in followStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton> |
||||
|
</RadioGroup> |
||||
|
</div> |
||||
|
</template> --> |
||||
|
<template #userinfo="{ text, record }"> |
||||
|
<div class="flex-row" style="padding-left: 12px;"> |
||||
|
<Image style="width: 40px;height: 40px;" :src="record.profilePhoto || 'https://dating-agency-prod.oss-cn-shenzhen.aliyuncs.com/827036501B11.png'" /> |
||||
|
<div class="flex-col" style="margin-left: 8px;"> |
||||
|
<div class="flex-row"> |
||||
|
<span class="single-line" style="font-size: 14px;color: #333;font-weight: bold;max-width: 160px;">{{record.nickName}}</span> |
||||
|
<!-- <span class="single-line" style="font-size: 13px;color: #666;margin: 0 12px;">(ID:{{record.id}})</span> --> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{genderList.find((find) => find.value === record.genderCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;" v-if="record.phone">{{record.phone}}</span> |
||||
|
</div> |
||||
|
<div class="flex-row"> |
||||
|
<span style="font-size: 13px;color: #666;">{{record.age}}岁</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{educationList.find((find) => find.value === record.educationCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{maritalList.find((find) => find.value === record.maritalStatusCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;" v-if="record.occupation">{{record.occupation}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #allocateInfo="{ text, record }"> |
||||
|
<ul class="ant-timeline" style="padding-top: 10px;" v-if="record.datingClueFollowRecordList && record.datingClueFollowRecordList.length"> |
||||
|
<li class="ant-timeline-item" style="padding: 0 0 10px;"> |
||||
|
<div class="ant-timeline-item-tail"></div> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
<li class="ant-timeline-item" style="padding: 0 0 10px;"> |
||||
|
<div class="ant-timeline-item-tail"></div> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;">果然非-飞鹅,趣招亲-电话线索</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
<li class="ant-timeline-item" style="padding: 0px;"> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;">果然非-飞鹅,趣招亲-电话线索</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<div class="flex-row-center-center" style="padding: 8px;" v-else> |
||||
|
<span style="color: #999;">暂无数据</span> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #action="{ record }"> |
||||
|
<TableAction |
||||
|
:actions="[ |
||||
|
{ |
||||
|
label: '详情', |
||||
|
onClick: toDetail.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { Radio } from 'ant-design-vue' |
||||
|
|
||||
|
export default { |
||||
|
name: 'ClueList', |
||||
|
components: { RadioGroup: Radio.Group, RadioButton: Radio.Button }, |
||||
|
} |
||||
|
</script> |
||||
|
<script setup lang="ts"> |
||||
|
import moment from 'moment/moment' |
||||
|
import { computed, ref, reactive } from 'vue' |
||||
|
import { Card, Image, InputNumber, Timeline, TimelineItem, Result } from 'ant-design-vue' |
||||
|
import { useRouter } from 'vue-router' |
||||
|
import { tableColumns, tableFormSchema } from './data' |
||||
|
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
||||
|
import { getClueFollow } from '/@/api/clue' |
||||
|
import { educationList, maritalList, genderList, followStageList } from '/@/enums/customerEnum' |
||||
|
|
||||
|
const [registerTable, { reload, setPagination }] = useTable({ |
||||
|
bordered: false, |
||||
|
useSearchForm: true, |
||||
|
columns: tableColumns, |
||||
|
showIndexColumn: false, |
||||
|
showTableSetting: false, |
||||
|
api: getClueFollow, |
||||
|
formConfig: { |
||||
|
labelWidth: 120, |
||||
|
schemas: tableFormSchema, |
||||
|
}, |
||||
|
beforeFetch: (arg) => { |
||||
|
const { createTime, followTime } = arg |
||||
|
if (createTime) { |
||||
|
arg.creatTimeFrom = moment(createTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.creatTimeTo = moment(createTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.createTime |
||||
|
} |
||||
|
if (followTime) { |
||||
|
arg.finalFollowTimeFrom = moment(followTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.finalFollowTimeTo = moment(followTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.followTime |
||||
|
} |
||||
|
arg.minimumAge = ageModel.minAge |
||||
|
arg.maximumAge = ageModel.maxAge |
||||
|
// arg.followStatus = radioVal.value |
||||
|
}, |
||||
|
actionColumn: { |
||||
|
width: 160, |
||||
|
title: '操作', |
||||
|
fixed: 'right', |
||||
|
dataIndex: 'action', |
||||
|
slots: { customRender: 'action' }, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const followStatusList = [ |
||||
|
{ label: '待跟进', value: 1 }, |
||||
|
{ label: '跟进中', value: 2 }, |
||||
|
{ label: '全部', value: '' }, |
||||
|
] |
||||
|
const radioVal = ref<any>('') |
||||
|
const ageModel = reactive({ |
||||
|
minAge: '', |
||||
|
maxAge: '', |
||||
|
}) |
||||
|
function handleRadioChange() { |
||||
|
setPagination({ current: 1 }) |
||||
|
reload() |
||||
|
} |
||||
|
|
||||
|
const router = useRouter() |
||||
|
function toDetail(record: any) { |
||||
|
const { id } = record |
||||
|
router.push({ |
||||
|
query: { id }, |
||||
|
path: '/clue/customer', |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped lang="less"> |
||||
|
.single-line { |
||||
|
overflow: hidden; /* 超出部分隐藏 */ |
||||
|
white-space: nowrap; /* 文本不换行 */ |
||||
|
text-overflow: ellipsis; /* 超出部分显示省略号 */ |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,124 @@ |
|||||
|
import { BasicColumn, FormSchema } from '/@/components/Table' |
||||
|
import { genderList, channelList, clueStatusList, followStatusList, validStatusList } from '/@/enums/customerEnum' |
||||
|
import { getEducationList, getMaritalStatusList,} from '/@/api/essentialData' |
||||
|
|
||||
|
export const tableColumns: BasicColumn[] = [ |
||||
|
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } }, |
||||
|
// { width: 60, title: '性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} },
|
||||
|
// { width: 120, title: '手机号码', dataIndex: 'phone' },
|
||||
|
// { width: 90, title: '渠道来源', dataIndex: 'channelType',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return channelList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ title: '录入信息', dataIndex: 'recordInfo', slots: { customRender: 'recordInfo' } }, |
||||
|
// { width: 80, title: '状态', dataIndex: 'allocationStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return clueStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 100, title: '创建人', dataIndex: 'creatorName' },
|
||||
|
// { width: 100, title: '核验人', dataIndex: 'verifierName' },
|
||||
|
// { width: 90, title: '跟进状态', dataIndex: 'followStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return followStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 150, title: '最后跟进时间', dataIndex: 'finalFollowTime' },
|
||||
|
// { width: 90, title: '跟进结果', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return validStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 150, title: '录入时间', dataIndex: 'createTime' },
|
||||
|
] |
||||
|
|
||||
|
export const tableFormSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'genderCode', |
||||
|
label: '用户性别', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: genderList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'channelType', |
||||
|
label: '渠道来源', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: channelList, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'status',
|
||||
|
// label: '状态',
|
||||
|
// component: 'Input',
|
||||
|
// colProps: { span: 6 },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'nickName', |
||||
|
label: '用户昵称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'phone', |
||||
|
label: '电话号码', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maritalStatusCode', |
||||
|
label: '婚姻状况', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getMaritalStatusList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'age', |
||||
|
label: '年龄', |
||||
|
slot: 'age', |
||||
|
component: 'InputNumber', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'educationCode', |
||||
|
label: '学历', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getEducationList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'createTime', |
||||
|
label: '创建时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'creatorName', |
||||
|
label: '创建人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'followTime', |
||||
|
label: '最后跟进时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'verifierName', |
||||
|
label: '核验人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
] |
||||
@ -0,0 +1,254 @@ |
|||||
|
<template> |
||||
|
<div class="order-list"> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #form-age> |
||||
|
<div class="flex-row"> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.minAge" /> |
||||
|
<div style="width: 8%" class="flex-row-center-center">-</div> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.maxAge" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #toolbar> |
||||
|
<div style="width: 100%; padding: 2px" class="flex-row-center-end"> |
||||
|
<!-- <RadioGroup button-style="solid" v-model:value="radioVal" @change="handleRadioChange"> |
||||
|
<RadioButton v-for="item in clueStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton> |
||||
|
</RadioGroup> --> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<Button type="primary" danger :disabled="datingClueIdList.length == 0" @click="handleDelete(null)">删除</Button> |
||||
|
<!-- <Popconfirm placement="bottom" icon="分配线索" :disabled="datingClueIdList.length == 0" @confirm="allocateList(null)"> |
||||
|
<template #title> |
||||
|
<div class="flex-row-center-start" style="padding: 8px 8px 8px 0px;border-bottom: 1px solid #ddd;" v-for="item in memberList" :key="item.userId"> |
||||
|
<Checkbox :checked="verifier == item.userId" @change="e => onCheckChange(e, item)"> |
||||
|
<span style="color: #333;font-weight: bold;margin-left: 4px;">{{item.realName}}</span> |
||||
|
<span style="color: #666;margin-left: 4px;">({{item.phone}})</span> |
||||
|
</Checkbox> |
||||
|
</div> |
||||
|
</template> |
||||
|
<Button type="primary" danger style="margin-left: 16px;" :disabled="datingClueIdList.length == 0">分配</Button> |
||||
|
</Popconfirm> --> |
||||
|
<Button style="margin-left: 16px;" @click="handleAdd(true)">批量导入</Button> |
||||
|
<Button :type="'primary'" style="margin-left: 16px;" @click="handleAdd(false)">新增</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #userinfo="{ text, record }"> |
||||
|
<div class="flex-row" style="padding-left: 12px;"> |
||||
|
<Image style="width: 40px;height: 40px;" :src="record.profilePhoto || 'https://dating-agency-prod.oss-cn-shenzhen.aliyuncs.com/827036501B11.png'" /> |
||||
|
<div class="flex-col" style="margin-left: 8px;"> |
||||
|
<div class="flex-row"> |
||||
|
<span class="single-line" style="font-size: 14px;color: #333;font-weight: bold;max-width: 160px;">{{record.nickName}}</span> |
||||
|
<!-- <span class="single-line" style="font-size: 13px;color: #666;margin: 0 12px;">(ID:{{record.id}})</span> --> |
||||
|
</div> |
||||
|
<div class="flex-row"> |
||||
|
<span style="font-size: 13px;color: #666;" v-if="record.age">{{record.age}}岁</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{educationList.find((find) => find.value === record.educationCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{maritalList.find((find) => find.value === record.maritalStatusCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;" v-if="record.occupation">{{record.occupation}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #recordInfo="{ text, record }"> |
||||
|
<div class="flex-col"> |
||||
|
<div class="flex-row"> |
||||
|
<span class="single-line" style="font-size: 14px;color: #333;font-weight: bold;max-width: 160px;">{{record.creatorName}}</span> |
||||
|
<!-- <span style="font-size: 13px;color: #666;margin-left: 12px;">{{record.orgName}}</span> --> |
||||
|
</div> |
||||
|
<div class="flex-row"> |
||||
|
<span style="font-size: 13px;color: #666;">{{record.createTime}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #action="{ record }"> |
||||
|
<div class="flex-col-center-start"> |
||||
|
<Button style="width: 100px;" @click.stop="toDetail(record)">编辑</Button> |
||||
|
<!-- <Popconfirm placement="topRight" :icon="record.allocationStatus == 1 ? '分配' : '重新分配'" @confirm="allocateList(record.id)"> |
||||
|
<template #title> |
||||
|
<div class="flex-row-center-start" style="padding: 8px 8px 8px 0px;border-bottom: 1px solid #ddd;" v-for="item in memberList" :key="item.userId"> |
||||
|
<Checkbox :checked="verifier == item.userId" @change="e => onCheckChange(e, item)"> |
||||
|
<span style="color: #333;font-weight: bold;margin-left: 4px;">{{item.realName}}</span> |
||||
|
<span style="color: #666;margin-left: 4px;">({{item.phone}})</span> |
||||
|
</Checkbox> |
||||
|
</div> |
||||
|
</template> |
||||
|
<Button danger style="width: 100px;margin-top: 8px;">{{record.allocationStatus == 1 ? '分配' : '重新分配'}}</Button> |
||||
|
</Popconfirm> --> |
||||
|
<Button danger style="width: 100px;margin-top: 8px;" @click.stop="handleDelete(record.id)">删除</Button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
<ClueModal @register="registerModal" @success="handleSuccess" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { Radio } from 'ant-design-vue' |
||||
|
|
||||
|
export default { |
||||
|
name: 'CluePool', |
||||
|
components: { RadioGroup: Radio.Group, RadioButton: Radio.Button }, |
||||
|
} |
||||
|
</script> |
||||
|
<script setup lang="ts"> |
||||
|
import moment from 'moment/moment' |
||||
|
import { onMounted, reactive, ref } from 'vue' |
||||
|
import { Card, Image, Button, InputNumber, Popconfirm, Checkbox } from 'ant-design-vue' |
||||
|
import { useRouter } from 'vue-router' |
||||
|
import { useMessage } from '/@/hooks/web/useMessage' |
||||
|
import { tableColumns, tableFormSchema } from './data' |
||||
|
import { useModal } from '/@/components/Modal' |
||||
|
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
||||
|
import ClueModal from '../components/modal.vue' |
||||
|
import { getCluePool, allocateCluing, deleteCluingList } from '/@/api/clue' |
||||
|
import { pageOrganizationMember } from '/@/api/staff/staff' |
||||
|
import { educationList, maritalList } from '/@/enums/customerEnum' |
||||
|
|
||||
|
const radioVal = ref<any>('') |
||||
|
const verifier = ref<any>('') |
||||
|
|
||||
|
const clueStatusList = [ |
||||
|
{ label: '待分配', value: 1 }, |
||||
|
{ label: '跟进中', value: 2 }, |
||||
|
{ label: '公海线索', value: 3 }, |
||||
|
{ label: '全部', value: '' }, |
||||
|
] |
||||
|
|
||||
|
const ageModel = reactive({ |
||||
|
minAge: '', |
||||
|
maxAge: '', |
||||
|
}) |
||||
|
|
||||
|
const memberList = ref<any[]>([]); |
||||
|
|
||||
|
onMounted(async () => { |
||||
|
const result = await pageOrganizationMember({}) |
||||
|
memberList.value = result.items |
||||
|
}) |
||||
|
|
||||
|
const [registerTable, { reload, setPagination, getSelectRowKeys, clearSelectedRowKeys }] = useTable({ |
||||
|
bordered: false, |
||||
|
useSearchForm: true, |
||||
|
columns: tableColumns, |
||||
|
showIndexColumn: false, |
||||
|
showTableSetting: false, |
||||
|
api: getCluePool, |
||||
|
rowKey: 'id', |
||||
|
rowSelection: { |
||||
|
type: 'checkbox', |
||||
|
onChange, |
||||
|
// getCheckboxProps(record: Recordable) { |
||||
|
// // Demo: 第一行(id为0)的选择框禁用 |
||||
|
// return { disabled: record.allocationStatus !== 1 }; |
||||
|
// }, |
||||
|
}, |
||||
|
formConfig: { |
||||
|
labelWidth: 120, |
||||
|
schemas: tableFormSchema, |
||||
|
}, |
||||
|
beforeFetch: (arg) => { |
||||
|
const { createTime, followTime } = arg |
||||
|
if (createTime) { |
||||
|
arg.creatTimeFrom = moment(createTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.creatTimeTo = moment(createTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.createTime |
||||
|
} |
||||
|
if (followTime) { |
||||
|
arg.finalFollowTimeFrom = moment(followTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.finalFollowTimeTo = moment(followTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.followTime |
||||
|
} |
||||
|
arg.minimumAge = ageModel.minAge |
||||
|
arg.maximumAge = ageModel.maxAge |
||||
|
arg.allocationStatus = radioVal.value |
||||
|
}, |
||||
|
actionColumn: { |
||||
|
width: 160, |
||||
|
title: '操作', |
||||
|
fixed: 'right', |
||||
|
dataIndex: 'action', |
||||
|
slots: { customRender: 'action' }, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
function handleRadioChange() { |
||||
|
setPagination({ current: 1 }) |
||||
|
datingClueIdList.value = [] |
||||
|
reload() |
||||
|
} |
||||
|
|
||||
|
const [registerModal, { openModal, closeModal }] = useModal() |
||||
|
function handleAdd(excel) { |
||||
|
openModal(true, {ifUpdate: excel,}) |
||||
|
} |
||||
|
// 操作成功 |
||||
|
function handleSuccess() { |
||||
|
closeModal() |
||||
|
reload() |
||||
|
} |
||||
|
const { createMessage } = useMessage() |
||||
|
const router = useRouter() |
||||
|
function toDetail(record: any) { |
||||
|
openModal(true, {record, ifUpdate: false}) |
||||
|
// const { id } = record |
||||
|
// router.push({ |
||||
|
// query: { id }, |
||||
|
// path: '/clue/customer', |
||||
|
// }) |
||||
|
} |
||||
|
async function handleDelete(id){ |
||||
|
const { createConfirm } = useMessage() |
||||
|
createConfirm({ |
||||
|
title: '提示', |
||||
|
iconType: 'warning', |
||||
|
content: `是否确定删除?`, |
||||
|
onOk: () => { |
||||
|
var datingClueIdList: string[] = [] |
||||
|
if(id){ |
||||
|
datingClueIdList = [id] |
||||
|
} else { |
||||
|
datingClueIdList = getSelectRowKeys() |
||||
|
} |
||||
|
deleteCluingList({datingClueIdList}).then(() => { |
||||
|
createMessage.success('删除成功') |
||||
|
clearSelectedRowKeys() |
||||
|
reload() |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
const datingClueIdList = ref<string[]>([]); |
||||
|
function onChange(selectedRowKeys) { |
||||
|
datingClueIdList.value = selectedRowKeys |
||||
|
} |
||||
|
function onCheckChange(e, item){ |
||||
|
if(e.target.checked){ |
||||
|
verifier.value = item.userId |
||||
|
} |
||||
|
} |
||||
|
async function allocateList(id = null){ |
||||
|
if(!verifier.value){ |
||||
|
createMessage.warning('请选择分配人') |
||||
|
return |
||||
|
} |
||||
|
var datingClueIdList: string[] = [] |
||||
|
if(id){ |
||||
|
datingClueIdList = [id] |
||||
|
} else { |
||||
|
datingClueIdList = getSelectRowKeys() |
||||
|
} |
||||
|
try { |
||||
|
await allocateCluing({verifier: verifier.value, datingClueIdList}) |
||||
|
createMessage.success(`分配成功`) |
||||
|
clearSelectedRowKeys() |
||||
|
} finally { |
||||
|
reload() |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped lang="less"> |
||||
|
.single-line { |
||||
|
overflow: hidden; /* 超出部分隐藏 */ |
||||
|
white-space: nowrap; /* 文本不换行 */ |
||||
|
text-overflow: ellipsis; /* 超出部分显示省略号 */ |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,42 @@ |
|||||
|
import { FormSchema } from '/@/components/Form' |
||||
|
import { followTypeList, followStageList } from '/@/enums/customerEnum' |
||||
|
|
||||
|
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, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
@ -0,0 +1,54 @@ |
|||||
|
<template> |
||||
|
<BasicModal v-bind="$attrs" @ok="handleOk" @register="registerModal"> |
||||
|
<BasicForm @register="registerForm" /> |
||||
|
</BasicModal> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref } from 'vue' |
||||
|
import { modalFormSchema } from './data' |
||||
|
import { useMessage } from '/@/hooks/web/useMessage' |
||||
|
import { BasicForm, useForm } from '/@/components/Form' |
||||
|
import { BasicModal, useModalInner } from '/@/components/Modal' |
||||
|
import { recordClueing } from '/@/api/clue' |
||||
|
|
||||
|
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({ |
||||
|
labelWidth: 100, |
||||
|
schemas: modalFormSchema, |
||||
|
baseColProps: { span: 22 }, |
||||
|
showActionButtonGroup: false, |
||||
|
}) |
||||
|
|
||||
|
const id = ref<string>('') |
||||
|
const ifUpdate = ref<boolean>(false) |
||||
|
const [registerModal, { setModalProps }] = useModalInner(async (data) => { |
||||
|
const { record } = data |
||||
|
await resetFields() |
||||
|
id.value = record?.id |
||||
|
ifUpdate.value = !!data.ifUpdate |
||||
|
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() |
||||
|
values.datingClueId = id.value |
||||
|
// console.log('values.............', values) |
||||
|
setModalProps({ confirmLoading: true }) |
||||
|
await recordClueing(values) |
||||
|
createMessage.success(`上报成功!`) |
||||
|
emits('success') |
||||
|
} finally { |
||||
|
setModalProps({ confirmLoading: false }) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
@ -1,211 +0,0 @@ |
|||||
<template> |
|
||||
<BasicModal v-bind="$attrs" :width="960" @ok="handleOk" @register="registerModal"> |
|
||||
|
|
||||
<template v-if="!ifUpdate" #title> |
|
||||
<span>新增线索</span> |
|
||||
</template> |
|
||||
|
|
||||
<div v-if="ifUpdate"> |
|
||||
<Result title="请将模板Excel文件导入" v-if="tableListRef.length == 0"> |
|
||||
<template #icon> |
|
||||
<Icon icon="ant-design:cloud-upload-outlined" size="150px" color="#d6d6d6"/> |
|
||||
</template> |
|
||||
<template #extra> |
|
||||
<ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD"> |
|
||||
<Button>导入Excel</Button> |
|
||||
</ImpExcel> |
|
||||
</template> |
|
||||
</Result> |
|
||||
<BasicTable v-for="(table, index) in tableListRef" |
|
||||
:key="index" |
|
||||
:columns="table.columns" |
|
||||
:canResize="false" |
|
||||
:dataSource="table.dataSource" |
|
||||
:pagination="{ pageSize: 10 }" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div class="flex-col" v-else> |
|
||||
<Tabs tab-position="left" v-model:activeKey="activeKey2" :animated="false"> |
|
||||
<TabPane key="1" tab="个人资料"> |
|
||||
<BasicForm @register="registerForm" style="padding: 16px 48px 0px 0px;" > |
|
||||
<template #profilePhoto="{ model, field }"> |
|
||||
<CropperAvatar :value="model[field]" @change="uploadAvatarAfter"/> |
|
||||
</template> |
|
||||
</BasicForm> |
|
||||
</TabPane> |
|
||||
<TabPane key="2" tab="择偶要求"> |
|
||||
<BasicForm @register="registerForm3" style="padding: 16px 48px 0px 0px;"> |
|
||||
<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> |
|
||||
</Tabs> |
|
||||
</div> |
|
||||
|
|
||||
<template v-if="ifUpdate" #footer> |
|
||||
<div class="flex-row-center-space"> |
|
||||
<ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD"> |
|
||||
<Button type="primary">导入Excel</Button> |
|
||||
</ImpExcel> |
|
||||
<div class="flex-row-center-start"> |
|
||||
<Button @click="close">取消</Button> |
|
||||
<Button type="primary" @click="handleOk">确定</Button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
</BasicModal> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { ref, unref } from 'vue' |
|
||||
import { Tabs, TabPane, Button, Result, InputNumber } from 'ant-design-vue' |
|
||||
import Icon from '/@/components/Icon' |
|
||||
import { modalFormSchema, basicInfoData, demandMarriageSchema, demandMarriageMoreData } from './data' |
|
||||
import { ImpExcel, ExcelData } from '/@/components/Excel'; |
|
||||
import { BasicTable, BasicColumn } from '/@/components/Table' |
|
||||
import { useMessage } from '/@/hooks/web/useMessage' |
|
||||
import { BasicForm, useForm } from '/@/components/Form' |
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal' |
|
||||
import { CropperAvatar } from '/@/components/Cropper'; |
|
||||
import { createClueRecord, importClueRecord } from '/@/api/clue' |
|
||||
|
|
||||
const activeKey2 = ref<string>('1') |
|
||||
|
|
||||
const [registerForm, { setFieldsValue, resetFields, validate, getFieldsValue }] = |
|
||||
useForm({ |
|
||||
labelWidth: 108, |
|
||||
schemas: modalFormSchema, |
|
||||
baseColProps: { span: 22 }, |
|
||||
showActionButtonGroup: false, |
|
||||
}) |
|
||||
|
|
||||
const ifUpdate = ref<boolean>(false) |
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => { |
|
||||
ifUpdate.value = !!data.ifUpdate |
|
||||
if(ifUpdate.value){ |
|
||||
tableListRef.value = []; |
|
||||
} else { |
|
||||
demandMarriageMoreData.value = {} |
|
||||
basicInfoData.value = {} |
|
||||
activeKey2.value = '1' |
|
||||
await resetFields() |
|
||||
await resetFields3() |
|
||||
} |
|
||||
setModalProps({ |
|
||||
minHeight: 50, |
|
||||
confirmLoading: false, |
|
||||
title: ifUpdate.value ? '导入线索' : '新增线索', |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
// 择偶标准表单配置 |
|
||||
const [registerForm3, { getFieldsValue: getFieldsValue3, resetFields: resetFields3 }] = |
|
||||
useForm({ |
|
||||
labelWidth: 100, |
|
||||
schemas: demandMarriageSchema, |
|
||||
baseColProps: { span: 22 }, |
|
||||
showActionButtonGroup: false, |
|
||||
}) |
|
||||
|
|
||||
function close(){ |
|
||||
closeModal() |
|
||||
} |
|
||||
|
|
||||
function uploadAvatarAfter(value) { |
|
||||
//修改表单的值 |
|
||||
setFieldsValue({ |
|
||||
profilePhoto: value, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
const { createMessage } = useMessage() |
|
||||
const emits = defineEmits(['success']) |
|
||||
async function handleOk() { |
|
||||
try { |
|
||||
if(ifUpdate.value){ |
|
||||
await importClueRecord({keyList: tableListRef.value[0].header, dataList: tableListRef.value[0].dataSource}) |
|
||||
createMessage.success(`导入成功!`) |
|
||||
emits('success') |
|
||||
return |
|
||||
} |
|
||||
await validate() |
|
||||
const values: any = getFieldsValue() |
|
||||
// console.log({...values, ...basicInfoData.value}) |
|
||||
|
|
||||
setModalProps({ confirmLoading: true }) |
|
||||
const { domicilePlace, address } = values || {} |
|
||||
|
|
||||
const demandInfo: any = getFieldsValue3() |
|
||||
if(demandInfo){ |
|
||||
const address1 = demandInfo.address |
|
||||
demandInfo.cityCode = address1?.[1] |
|
||||
demandInfo.provinceCode = address1?.[0] |
|
||||
demandInfo.districtCode = address1?.[2] |
|
||||
const domicilePlace1 = demandInfo.domicilePlace |
|
||||
demandInfo.domicilePlaceCityCode = domicilePlace1?.[1] |
|
||||
demandInfo.domicilePlaceProvinceCode = domicilePlace1?.[0] |
|
||||
} |
|
||||
|
|
||||
const datingClueDemand = { ...demandInfo, ...demandMarriageMoreData.value } |
|
||||
|
|
||||
const param = { |
|
||||
...values, |
|
||||
channelType: 1, |
|
||||
datingClueDemand, |
|
||||
cityCode: address?.[1], |
|
||||
provinceCode: address?.[0], |
|
||||
districtCode: address?.[2], |
|
||||
domicilePlaceCityCode: domicilePlace?.[1], |
|
||||
domicilePlaceProvinceCode: domicilePlace?.[0] |
|
||||
} |
|
||||
await createClueRecord(param) |
|
||||
createMessage.success(`新增成功!`) |
|
||||
emits('success') |
|
||||
} finally { |
|
||||
setModalProps({ confirmLoading: false }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const tableListRef = ref<{ title: string; columns?: any[]; dataSource?: any[]; header:any[];}[]>([]); |
|
||||
|
|
||||
function loadDataSuccess(excelDataList: ExcelData[]) { |
|
||||
tableListRef.value = []; |
|
||||
console.log(excelDataList); |
|
||||
for (const excelData of excelDataList) { |
|
||||
const { header, results, meta: { sheetName }, } = excelData; |
|
||||
const columns: BasicColumn[] = []; |
|
||||
for (const title of header) { |
|
||||
columns.push({ title, dataIndex: title }); |
|
||||
} |
|
||||
tableListRef.value.push({ title: sheetName, dataSource: results, columns, header }); |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped lang="less"> |
|
||||
::v-deep .ant-input-number { |
|
||||
min-width: 60px; |
|
||||
width: 100% !important; |
|
||||
max-width: 100%; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,211 +0,0 @@ |
|||||
<template> |
|
||||
<BasicModal v-bind="$attrs" :width="960" @ok="handleOk" @register="registerModal"> |
|
||||
|
|
||||
<template v-if="!ifUpdate" #title> |
|
||||
<span>新增线索</span> |
|
||||
</template> |
|
||||
|
|
||||
<div v-if="ifUpdate"> |
|
||||
<Result title="请将模板Excel文件导入" v-if="tableListRef.length == 0"> |
|
||||
<template #icon> |
|
||||
<Icon icon="ant-design:cloud-upload-outlined" size="150px" color="#d6d6d6"/> |
|
||||
</template> |
|
||||
<template #extra> |
|
||||
<ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD"> |
|
||||
<Button>导入Excel</Button> |
|
||||
</ImpExcel> |
|
||||
</template> |
|
||||
</Result> |
|
||||
<BasicTable v-for="(table, index) in tableListRef" |
|
||||
:key="index" |
|
||||
:columns="table.columns" |
|
||||
:canResize="false" |
|
||||
:dataSource="table.dataSource" |
|
||||
:pagination="{ pageSize: 10 }" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div class="flex-col" v-else> |
|
||||
<Tabs tab-position="left" v-model:activeKey="activeKey2" :animated="false"> |
|
||||
<TabPane key="1" tab="个人资料"> |
|
||||
<BasicForm @register="registerForm" style="padding: 16px 48px 0px 0px;" > |
|
||||
<template #profilePhoto="{ model, field }"> |
|
||||
<CropperAvatar :value="model[field]" @change="uploadAvatarAfter"/> |
|
||||
</template> |
|
||||
</BasicForm> |
|
||||
</TabPane> |
|
||||
<TabPane key="2" tab="择偶要求"> |
|
||||
<BasicForm @register="registerForm3" style="padding: 16px 48px 0px 0px;"> |
|
||||
<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> |
|
||||
</Tabs> |
|
||||
</div> |
|
||||
|
|
||||
<template v-if="ifUpdate" #footer> |
|
||||
<div class="flex-row-center-space"> |
|
||||
<ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD"> |
|
||||
<Button type="primary">导入Excel</Button> |
|
||||
</ImpExcel> |
|
||||
<div class="flex-row-center-start"> |
|
||||
<Button @click="close">取消</Button> |
|
||||
<Button type="primary" @click="handleOk">确定</Button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
</BasicModal> |
|
||||
</template> |
|
||||
|
|
||||
<script setup lang="ts"> |
|
||||
import { ref, unref } from 'vue' |
|
||||
import { Tabs, TabPane, Button, Result, InputNumber } from 'ant-design-vue' |
|
||||
import Icon from '/@/components/Icon' |
|
||||
import { modalFormSchema, basicInfoData, demandMarriageSchema, demandMarriageMoreData } from './data' |
|
||||
import { ImpExcel, ExcelData } from '/@/components/Excel'; |
|
||||
import { BasicTable, BasicColumn } from '/@/components/Table' |
|
||||
import { useMessage } from '/@/hooks/web/useMessage' |
|
||||
import { BasicForm, useForm } from '/@/components/Form' |
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal' |
|
||||
import { CropperAvatar } from '/@/components/Cropper'; |
|
||||
import { createClueRecord, importClueRecord } from '/@/api/clue' |
|
||||
|
|
||||
const activeKey2 = ref<string>('1') |
|
||||
|
|
||||
const [registerForm, { setFieldsValue, resetFields, validate, getFieldsValue }] = |
|
||||
useForm({ |
|
||||
labelWidth: 108, |
|
||||
schemas: modalFormSchema, |
|
||||
baseColProps: { span: 22 }, |
|
||||
showActionButtonGroup: false, |
|
||||
}) |
|
||||
|
|
||||
const ifUpdate = ref<boolean>(false) |
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => { |
|
||||
ifUpdate.value = !!data.ifUpdate |
|
||||
if(ifUpdate.value){ |
|
||||
tableListRef.value = []; |
|
||||
} else { |
|
||||
demandMarriageMoreData.value = {} |
|
||||
basicInfoData.value = {} |
|
||||
activeKey2.value = '1' |
|
||||
await resetFields() |
|
||||
await resetFields3() |
|
||||
} |
|
||||
setModalProps({ |
|
||||
minHeight: 50, |
|
||||
confirmLoading: false, |
|
||||
title: ifUpdate.value ? '导入线索' : '新增线索', |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
// 择偶标准表单配置 |
|
||||
const [registerForm3, { getFieldsValue: getFieldsValue3, resetFields: resetFields3 }] = |
|
||||
useForm({ |
|
||||
labelWidth: 100, |
|
||||
schemas: demandMarriageSchema, |
|
||||
baseColProps: { span: 22 }, |
|
||||
showActionButtonGroup: false, |
|
||||
}) |
|
||||
|
|
||||
function close(){ |
|
||||
closeModal() |
|
||||
} |
|
||||
|
|
||||
function uploadAvatarAfter(value) { |
|
||||
//修改表单的值 |
|
||||
setFieldsValue({ |
|
||||
profilePhoto: value, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
const { createMessage } = useMessage() |
|
||||
const emits = defineEmits(['success']) |
|
||||
async function handleOk() { |
|
||||
try { |
|
||||
if(ifUpdate.value){ |
|
||||
await importClueRecord({keyList: tableListRef.value[0].header, dataList: tableListRef.value[0].dataSource}) |
|
||||
createMessage.success(`导入成功!`) |
|
||||
emits('success') |
|
||||
return |
|
||||
} |
|
||||
await validate() |
|
||||
const values: any = getFieldsValue() |
|
||||
// console.log({...values, ...basicInfoData.value}) |
|
||||
|
|
||||
setModalProps({ confirmLoading: true }) |
|
||||
const { domicilePlace, address } = values || {} |
|
||||
|
|
||||
const demandInfo: any = getFieldsValue3() |
|
||||
if(demandInfo){ |
|
||||
const address1 = demandInfo.address |
|
||||
demandInfo.cityCode = address1?.[1] |
|
||||
demandInfo.provinceCode = address1?.[0] |
|
||||
demandInfo.districtCode = address1?.[2] |
|
||||
const domicilePlace1 = demandInfo.domicilePlace |
|
||||
demandInfo.domicilePlaceCityCode = domicilePlace1?.[1] |
|
||||
demandInfo.domicilePlaceProvinceCode = domicilePlace1?.[0] |
|
||||
} |
|
||||
|
|
||||
const datingClueDemand = { ...demandInfo, ...demandMarriageMoreData.value } |
|
||||
|
|
||||
const param = { |
|
||||
...values, |
|
||||
channelType: 1, |
|
||||
datingClueDemand, |
|
||||
cityCode: address?.[1], |
|
||||
provinceCode: address?.[0], |
|
||||
districtCode: address?.[2], |
|
||||
domicilePlaceCityCode: domicilePlace?.[1], |
|
||||
domicilePlaceProvinceCode: domicilePlace?.[0] |
|
||||
} |
|
||||
await createClueRecord(param) |
|
||||
createMessage.success(`新增成功!`) |
|
||||
emits('success') |
|
||||
} finally { |
|
||||
setModalProps({ confirmLoading: false }) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const tableListRef = ref<{ title: string; columns?: any[]; dataSource?: any[]; header:any[];}[]>([]); |
|
||||
|
|
||||
function loadDataSuccess(excelDataList: ExcelData[]) { |
|
||||
tableListRef.value = []; |
|
||||
console.log(excelDataList); |
|
||||
for (const excelData of excelDataList) { |
|
||||
const { header, results, meta: { sheetName }, } = excelData; |
|
||||
const columns: BasicColumn[] = []; |
|
||||
for (const title of header) { |
|
||||
columns.push({ title, dataIndex: title }); |
|
||||
} |
|
||||
tableListRef.value.push({ title: sheetName, dataSource: results, columns, header }); |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped lang="less"> |
|
||||
::v-deep .ant-input-number { |
|
||||
min-width: 60px; |
|
||||
width: 100% !important; |
|
||||
max-width: 100%; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,124 @@ |
|||||
|
import { BasicColumn, FormSchema } from '/@/components/Table' |
||||
|
import { genderList, channelList, clueStatusList, followStatusList, validStatusList } from '/@/enums/customerEnum' |
||||
|
import { getEducationList, getMaritalStatusList } from '/@/api/essentialData' |
||||
|
|
||||
|
export const tableColumns: BasicColumn[] = [ |
||||
|
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } }, |
||||
|
// { width: 70, title: '性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} },
|
||||
|
// { width: 110, title: '手机号码', dataIndex: 'phone' },
|
||||
|
// { width: 100, title: '渠道来源', dataIndex: 'channelType',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return channelList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ title: '分配信息', dataIndex: 'allocateInfo', slots: { customRender: 'allocateInfo' } }, |
||||
|
// { width: 80, title: '状态', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return clueStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 100, title: '创建人', dataIndex: 'creatorName' },
|
||||
|
// { width: 100, title: '核验人', dataIndex: 'verifierName' },
|
||||
|
// { width: 100, title: '跟进状态', dataIndex: 'followStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return followStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 160, title: '最后跟进时间', dataIndex: 'finalFollowTime' },
|
||||
|
// { width: 100, title: '跟进结果', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return validStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 160, title: '录入时间', dataIndex: 'creatTime' },
|
||||
|
] |
||||
|
|
||||
|
export const tableFormSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'genderCode', |
||||
|
label: '用户性别', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: genderList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'channelType', |
||||
|
label: '渠道来源', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: channelList, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'status',
|
||||
|
// label: '状态',
|
||||
|
// component: 'Input',
|
||||
|
// colProps: { span: 6 },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'nickName', |
||||
|
label: '用户昵称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'phone', |
||||
|
label: '电话号码', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maritalStatusCode', |
||||
|
label: '婚姻状况', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getMaritalStatusList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'age', |
||||
|
label: '年龄', |
||||
|
slot: 'age', |
||||
|
component: 'InputNumber', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'educationCode', |
||||
|
label: '学历', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getEducationList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'createTime', |
||||
|
label: '创建时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'creatorName', |
||||
|
label: '创建人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'followTime', |
||||
|
label: '最后跟进时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'verifierName', |
||||
|
label: '核验人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
] |
||||
@ -0,0 +1,188 @@ |
|||||
|
<template> |
||||
|
<div class="order-list"> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #form-age> |
||||
|
<div class="flex-row"> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.minAge" /> |
||||
|
<div style="width: 8%" class="flex-row-center-center">-</div> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.maxAge" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #toolbar> |
||||
|
<div style="width: 100%; padding: 2px" class="flex-row-center-space"> |
||||
|
<RadioGroup button-style="solid" v-model:value="radioVal" @change="handleRadioChange"> |
||||
|
<RadioButton v-for="item in followStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton> |
||||
|
</RadioGroup> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #userinfo="{ text, record }"> |
||||
|
<div class="flex-row" style="padding-left: 12px;"> |
||||
|
<Image style="width: 40px;height: 40px;" :src="record.profilePhoto || 'https://dating-agency-prod.oss-cn-shenzhen.aliyuncs.com/827036501B11.png'" /> |
||||
|
<div class="flex-col" style="margin-left: 8px;"> |
||||
|
<div class="flex-row"> |
||||
|
<span class="single-line" style="font-size: 14px;color: #333;font-weight: bold;max-width: 160px;">{{record.nickName}}</span> |
||||
|
<!-- <span class="single-line" style="font-size: 13px;color: #666;margin: 0 12px;">(ID:{{record.id}})</span> --> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{genderList.find((find) => find.value === record.genderCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;" v-if="record.phone">{{record.phone}}</span> |
||||
|
</div> |
||||
|
<div class="flex-row"> |
||||
|
<span style="font-size: 13px;color: #666;">{{record.age}}岁</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{educationList.find((find) => find.value === record.educationCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{maritalList.find((find) => find.value === record.maritalStatusCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;" v-if="record.occupation">{{record.occupation}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #allocateInfo="{ text, record }"> |
||||
|
<ul class="ant-timeline" style="padding-top: 10px;" v-if="record.datingClueFollowRecordList && record.datingClueFollowRecordList.length"> |
||||
|
<li class="ant-timeline-item" style="padding: 0 0 10px;"> |
||||
|
<div class="ant-timeline-item-tail"></div> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
<li class="ant-timeline-item" style="padding: 0 0 10px;"> |
||||
|
<div class="ant-timeline-item-tail"></div> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;">果然非-飞鹅,趣招亲-电话线索</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
<li class="ant-timeline-item" style="padding: 0px;"> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;">果然非-飞鹅,趣招亲-电话线索</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<div class="flex-row-center-center" style="padding: 8px;" v-else> |
||||
|
<span style="color: #999;">暂无数据</span> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #action="{ record }"> |
||||
|
<TableAction |
||||
|
:actions="[ |
||||
|
{ |
||||
|
label: '详情', |
||||
|
onClick: toDetail.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
label: '跟进', |
||||
|
onClick: handleReport.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
<FollowModal @register="registerModal" @success="handleSuccess" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { Radio } from 'ant-design-vue' |
||||
|
|
||||
|
export default { |
||||
|
name: 'ClueList', |
||||
|
components: { RadioGroup: Radio.Group, RadioButton: Radio.Button }, |
||||
|
} |
||||
|
</script> |
||||
|
<script setup lang="ts"> |
||||
|
import moment from 'moment/moment' |
||||
|
import { computed, ref, reactive } from 'vue' |
||||
|
import { Card, Image, InputNumber, Timeline, TimelineItem, Result } from 'ant-design-vue' |
||||
|
import { useRouter } from 'vue-router' |
||||
|
import { tableColumns, tableFormSchema } from './data' |
||||
|
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
||||
|
import { useModal } from '/@/components/Modal' |
||||
|
import FollowModal from '../components/modal.vue' |
||||
|
import { getMyInvitationList } from '/@/api/clue' |
||||
|
import { educationList, maritalList, genderList, followStageList } from '/@/enums/customerEnum' |
||||
|
|
||||
|
const [registerTable, { reload, setPagination }] = useTable({ |
||||
|
bordered: false, |
||||
|
useSearchForm: true, |
||||
|
columns: tableColumns, |
||||
|
showIndexColumn: false, |
||||
|
showTableSetting: false, |
||||
|
api: getMyInvitationList, |
||||
|
formConfig: { |
||||
|
labelWidth: 120, |
||||
|
schemas: tableFormSchema, |
||||
|
}, |
||||
|
beforeFetch: (arg) => { |
||||
|
const { createTime, followTime } = arg |
||||
|
if (createTime) { |
||||
|
arg.creatTimeFrom = moment(createTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.creatTimeTo = moment(createTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.createTime |
||||
|
} |
||||
|
if (followTime) { |
||||
|
arg.finalFollowTimeFrom = moment(followTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.finalFollowTimeTo = moment(followTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.followTime |
||||
|
} |
||||
|
arg.minimumAge = ageModel.minAge |
||||
|
arg.maximumAge = ageModel.maxAge |
||||
|
arg.followStatus = radioVal.value |
||||
|
}, |
||||
|
actionColumn: { |
||||
|
width: 160, |
||||
|
title: '操作', |
||||
|
fixed: 'right', |
||||
|
dataIndex: 'action', |
||||
|
slots: { customRender: 'action' }, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const followStatusList = [ |
||||
|
{ label: '待跟进', value: 1 }, |
||||
|
{ label: '跟进中', value: 2 }, |
||||
|
{ label: '全部', value: '' }, |
||||
|
] |
||||
|
const radioVal = ref<any>('') |
||||
|
const ageModel = reactive({ |
||||
|
minAge: '', |
||||
|
maxAge: '', |
||||
|
}) |
||||
|
function handleRadioChange() { |
||||
|
setPagination({ current: 1 }) |
||||
|
reload() |
||||
|
} |
||||
|
|
||||
|
const router = useRouter() |
||||
|
function toDetail(record: any) { |
||||
|
const { id } = record |
||||
|
router.push({ |
||||
|
query: { id }, |
||||
|
path: '/clue/customer', |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const [registerModal, { openModal, closeModal }] = useModal() |
||||
|
function handleReport(record: any) { |
||||
|
openModal(true, {record}) |
||||
|
} |
||||
|
// 操作成功 |
||||
|
function handleSuccess() { |
||||
|
closeModal() |
||||
|
reload() |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped lang="less"> |
||||
|
.single-line { |
||||
|
overflow: hidden; /* 超出部分隐藏 */ |
||||
|
white-space: nowrap; /* 文本不换行 */ |
||||
|
text-overflow: ellipsis; /* 超出部分显示省略号 */ |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,124 @@ |
|||||
|
import { BasicColumn, FormSchema } from '/@/components/Table' |
||||
|
import { genderList, channelList, clueStatusList, followStatusList, validStatusList } from '/@/enums/customerEnum' |
||||
|
import { getEducationList, getMaritalStatusList } from '/@/api/essentialData' |
||||
|
|
||||
|
export const tableColumns: BasicColumn[] = [ |
||||
|
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } }, |
||||
|
// { width: 70, title: '性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} },
|
||||
|
// { width: 110, title: '手机号码', dataIndex: 'phone' },
|
||||
|
// { width: 100, title: '渠道来源', dataIndex: 'channelType',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return channelList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
{ title: '分配信息', dataIndex: 'allocateInfo', slots: { customRender: 'allocateInfo' } }, |
||||
|
// { width: 80, title: '状态', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return clueStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 100, title: '创建人', dataIndex: 'creatorName' },
|
||||
|
// { width: 100, title: '核验人', dataIndex: 'verifierName' },
|
||||
|
// { width: 100, title: '跟进状态', dataIndex: 'followStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return followStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 160, title: '最后跟进时间', dataIndex: 'finalFollowTime' },
|
||||
|
// { width: 100, title: '跟进结果', dataIndex: 'validStatus',
|
||||
|
// customRender: ({ text }) => {
|
||||
|
// return validStatusList.find((find) => find.value === text)?.label
|
||||
|
// },
|
||||
|
// },
|
||||
|
// { width: 160, title: '录入时间', dataIndex: 'creatTime' },
|
||||
|
] |
||||
|
|
||||
|
export const tableFormSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'genderCode', |
||||
|
label: '用户性别', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: genderList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'channelType', |
||||
|
label: '渠道来源', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: channelList, |
||||
|
}, |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'status',
|
||||
|
// label: '状态',
|
||||
|
// component: 'Input',
|
||||
|
// colProps: { span: 6 },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'nickName', |
||||
|
label: '用户昵称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'phone', |
||||
|
label: '电话号码', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'maritalStatusCode', |
||||
|
label: '婚姻状况', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getMaritalStatusList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'age', |
||||
|
label: '年龄', |
||||
|
slot: 'age', |
||||
|
component: 'InputNumber', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'educationCode', |
||||
|
label: '学历', |
||||
|
colProps: { span: 6 }, |
||||
|
component: 'ApiSelect', |
||||
|
componentProps: { |
||||
|
labelField: 'desc', |
||||
|
api: getEducationList, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'createTime', |
||||
|
label: '创建时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'creatorName', |
||||
|
label: '创建人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'followTime', |
||||
|
label: '最后跟进时间', |
||||
|
component: 'RangePicker', |
||||
|
colProps: { span: 6 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'verifierName', |
||||
|
label: '核验人名称', |
||||
|
component: 'Input', |
||||
|
colProps: { span: 6}, |
||||
|
}, |
||||
|
] |
||||
@ -0,0 +1,191 @@ |
|||||
|
<template> |
||||
|
<div class="order-list"> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #form-age> |
||||
|
<div class="flex-row"> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.minAge" /> |
||||
|
<div style="width: 8%" class="flex-row-center-center">-</div> |
||||
|
<InputNumber placeholder="请输入" style="width: 46%" v-model:value="ageModel.maxAge" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<!-- <template #toolbar> |
||||
|
<div style="width: 100%; padding: 2px" class="flex-row-center-space"> |
||||
|
<RadioGroup button-style="solid" v-model:value="radioVal" @change="handleRadioChange"> |
||||
|
<RadioButton v-for="item in followStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton> |
||||
|
</RadioGroup> |
||||
|
</div> |
||||
|
</template> --> |
||||
|
<template #userinfo="{ text, record }"> |
||||
|
<div class="flex-row" style="padding-left: 12px;"> |
||||
|
<Image style="width: 40px;height: 40px;" :src="record.profilePhoto || 'https://dating-agency-prod.oss-cn-shenzhen.aliyuncs.com/827036501B11.png'" /> |
||||
|
<div class="flex-col" style="margin-left: 8px;"> |
||||
|
<div class="flex-row"> |
||||
|
<span class="single-line" style="font-size: 14px;color: #333;font-weight: bold;max-width: 160px;">{{record.nickName}}</span> |
||||
|
<!-- <span class="single-line" style="font-size: 13px;color: #666;margin: 0 12px;">(ID:{{record.id}})</span> --> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{genderList.find((find) => find.value === record.genderCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;" v-if="record.phone">{{record.phone}}</span> |
||||
|
</div> |
||||
|
<div class="flex-row"> |
||||
|
<span style="font-size: 13px;color: #666;">{{record.age}}岁</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{educationList.find((find) => find.value === record.educationCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;">{{maritalList.find((find) => find.value === record.maritalStatusCode)?.label}}</span> |
||||
|
<span style="font-size: 13px;color: #666;margin-left: 12px;" v-if="record.occupation">{{record.occupation}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #allocateInfo="{ text, record }"> |
||||
|
<ul class="ant-timeline" style="padding-top: 10px;" v-if="record.datingClueFollowRecordList && record.datingClueFollowRecordList.length"> |
||||
|
<li class="ant-timeline-item" style="padding: 0 0 10px;"> |
||||
|
<div class="ant-timeline-item-tail"></div> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;"></span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
<li class="ant-timeline-item" style="padding: 0 0 10px;"> |
||||
|
<div class="ant-timeline-item-tail"></div> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;">果然非-飞鹅,趣招亲-电话线索</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
<li class="ant-timeline-item" style="padding: 0px;"> |
||||
|
<div class="ant-timeline-item-head ant-timeline-item-head-blue"><!----></div> |
||||
|
<div class="ant-timeline-item-content"> |
||||
|
<div class="flex-row-center-start"> |
||||
|
<span style="color: rgb(153, 153, 153);">2025年6月26日 14:38:26</span> |
||||
|
<span style="color: rgb(51, 51, 51); margin-left: 10px;">果然非-飞鹅,趣招亲-电话线索</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<div class="flex-row-center-center" style="padding: 8px;" v-else> |
||||
|
<span style="color: #999;">暂无数据</span> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #action="{ record }"> |
||||
|
<TableAction |
||||
|
:actions="[ |
||||
|
{ |
||||
|
label: '详情', |
||||
|
onClick: toDetail.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
label: '领取', |
||||
|
onClick: handleReceive.bind(null, record.id), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { Radio } from 'ant-design-vue' |
||||
|
|
||||
|
export default { |
||||
|
name: 'ClueList', |
||||
|
components: { RadioGroup: Radio.Group, RadioButton: Radio.Button }, |
||||
|
} |
||||
|
</script> |
||||
|
<script setup lang="ts"> |
||||
|
import moment from 'moment/moment' |
||||
|
import { computed, ref, reactive } from 'vue' |
||||
|
import { Card, Image, InputNumber, Timeline, TimelineItem, Result } from 'ant-design-vue' |
||||
|
import { useRouter } from 'vue-router' |
||||
|
import { tableColumns, tableFormSchema } from './data' |
||||
|
import { useMessage } from '/@/hooks/web/useMessage' |
||||
|
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
||||
|
import { getSeasList, receiveCluing } from '/@/api/clue' |
||||
|
import { educationList, maritalList, genderList, followStageList } from '/@/enums/customerEnum' |
||||
|
|
||||
|
const [registerTable, { reload, setPagination }] = useTable({ |
||||
|
bordered: false, |
||||
|
useSearchForm: true, |
||||
|
columns: tableColumns, |
||||
|
showIndexColumn: false, |
||||
|
showTableSetting: false, |
||||
|
api: getSeasList, |
||||
|
formConfig: { |
||||
|
labelWidth: 120, |
||||
|
schemas: tableFormSchema, |
||||
|
}, |
||||
|
beforeFetch: (arg) => { |
||||
|
const { createTime, followTime } = arg |
||||
|
if (createTime) { |
||||
|
arg.creatTimeFrom = moment(createTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.creatTimeTo = moment(createTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.createTime |
||||
|
} |
||||
|
if (followTime) { |
||||
|
arg.finalFollowTimeFrom = moment(followTime[0]).format('YYYY-MM-DD 00:00:00') |
||||
|
arg.finalFollowTimeTo = moment(followTime[1]).format('YYYY-MM-DD 23:59:59') |
||||
|
delete arg.followTime |
||||
|
} |
||||
|
arg.minimumAge = ageModel.minAge |
||||
|
arg.maximumAge = ageModel.maxAge |
||||
|
arg.followStatus = radioVal.value |
||||
|
}, |
||||
|
actionColumn: { |
||||
|
width: 160, |
||||
|
title: '操作', |
||||
|
fixed: 'right', |
||||
|
dataIndex: 'action', |
||||
|
slots: { customRender: 'action' }, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const followStatusList = [ |
||||
|
{ label: '待跟进', value: 1 }, |
||||
|
{ label: '跟进中', value: 2 }, |
||||
|
{ label: '全部', value: '' }, |
||||
|
] |
||||
|
const radioVal = ref<any>('') |
||||
|
const ageModel = reactive({ |
||||
|
minAge: '', |
||||
|
maxAge: '', |
||||
|
}) |
||||
|
function handleRadioChange() { |
||||
|
setPagination({ current: 1 }) |
||||
|
reload() |
||||
|
} |
||||
|
|
||||
|
const router = useRouter() |
||||
|
function toDetail(record: any) { |
||||
|
const { id } = record |
||||
|
router.push({ |
||||
|
query: { id }, |
||||
|
path: '/clue/customer', |
||||
|
}) |
||||
|
} |
||||
|
const { createMessage } = useMessage() |
||||
|
async function handleReceive(id){ |
||||
|
const { createConfirm } = useMessage() |
||||
|
createConfirm({ |
||||
|
title: '提示', |
||||
|
iconType: 'warning', |
||||
|
content: `确定领取该线索?`, |
||||
|
onOk: () => { |
||||
|
receiveCluing({id}).then(() => { |
||||
|
createMessage.success('领取成功') |
||||
|
reload() |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
<style scoped lang="less"> |
||||
|
.single-line { |
||||
|
overflow: hidden; /* 超出部分隐藏 */ |
||||
|
white-space: nowrap; /* 文本不换行 */ |
||||
|
text-overflow: ellipsis; /* 超出部分显示省略号 */ |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,109 @@ |
|||||
|
import { BasicColumn, FormSchema } from '/@/components/Table'; |
||||
|
|
||||
|
export const columns: BasicColumn[] = [ |
||||
|
{ |
||||
|
title: '菜单名称', |
||||
|
dataIndex: 'name', |
||||
|
width: 300, |
||||
|
align: 'left', |
||||
|
}, |
||||
|
{ |
||||
|
title: '权限标识', |
||||
|
dataIndex: 'permission', |
||||
|
width: 180, |
||||
|
}, |
||||
|
{ |
||||
|
title: '路由地址', |
||||
|
dataIndex: 'path', |
||||
|
}, |
||||
|
{ |
||||
|
title: '组件', |
||||
|
dataIndex: 'component', |
||||
|
}, |
||||
|
{ |
||||
|
title: '创建时间', |
||||
|
dataIndex: 'createTime', |
||||
|
width: 240, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const isDir = (type: string) => type === '1'; |
||||
|
const isMenu = (type: string) => type === '2'; |
||||
|
const isButton = (type: number) => type === 3; |
||||
|
|
||||
|
export const searchFormSchema: FormSchema[] = [ |
||||
|
// {
|
||||
|
// field: 'menuName',
|
||||
|
// label: '菜单名称',
|
||||
|
// component: 'Input',
|
||||
|
// colProps: { span: 8 },
|
||||
|
// },
|
||||
|
{ |
||||
|
field: 'status', |
||||
|
label: '状态', |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: [ |
||||
|
{ label: '启用', value: '0' }, |
||||
|
{ label: '停用', value: '1' }, |
||||
|
], |
||||
|
}, |
||||
|
colProps: { span: 8 }, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
export const formSchema: FormSchema[] = [ |
||||
|
{ |
||||
|
field: 'type', |
||||
|
label: '菜单类型', |
||||
|
component: 'RadioButtonGroup', |
||||
|
defaultValue: 1, |
||||
|
componentProps: { |
||||
|
options: [ |
||||
|
{ label: '目录', value: 1 }, |
||||
|
{ label: '菜单', value: 2 }, |
||||
|
// { label: '按钮', value: '3' },
|
||||
|
], |
||||
|
}, |
||||
|
colProps: { lg: 24, md: 24 }, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'name', |
||||
|
label: '菜单名称', |
||||
|
component: 'Input', |
||||
|
required: true, |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
field: 'parentId', |
||||
|
label: '上级菜单', |
||||
|
component: 'TreeSelect', |
||||
|
componentProps: { |
||||
|
replaceFields: { |
||||
|
title: 'name', |
||||
|
value: 'id', |
||||
|
key: 'id', |
||||
|
}, |
||||
|
getPopupContainer: () => document.body, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'path', |
||||
|
label: '路由地址', |
||||
|
component: 'Input', |
||||
|
required: true, |
||||
|
ifShow: ({ values }) => !isButton(values.type), |
||||
|
}, |
||||
|
// {
|
||||
|
// field: 'component',
|
||||
|
// label: '组件路径',
|
||||
|
// component: 'Input',
|
||||
|
// ifShow: ({ values }) => isMenu(values.type),
|
||||
|
// },
|
||||
|
// {
|
||||
|
// field: 'permission',
|
||||
|
// label: '权限标识',
|
||||
|
// component: 'Input',
|
||||
|
// ifShow: ({ values }) => !isDir(values.type),
|
||||
|
// },
|
||||
|
]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
<template> |
||||
|
<div style="padding: 16px;"> |
||||
|
|
||||
|
</div> |
||||
|
</template> |
||||
|
<script lang="ts" setup> |
||||
|
import { nextTick } from 'vue'; |
||||
|
|
||||
|
</script> |
||||
Write
Preview
Loading…
Cancel
Save