Browse Source

no message

master
xpz2018 10 months ago
parent
commit
5f685326c5
12 changed files with 1166 additions and 499 deletions
  1. 2
      package.json
  2. 6
      src/api/clue/index.ts
  3. 2
      src/api/sys/user.ts
  4. 28
      src/enums/customerEnum.ts
  5. 4
      src/layouts/default/header/index.vue
  6. 400
      src/views/clue/clueList/data.ts
  7. 70
      src/views/clue/clueList/index.vue
  8. 76
      src/views/clue/clueList/modal.vue
  9. 659
      src/views/clue/cluePool/data.ts
  10. 187
      src/views/clue/cluePool/index.vue
  11. 196
      src/views/clue/cluePool/modal.vue
  12. 35
      src/views/clue/customer/index.vue

2
package.json

@ -68,7 +68,7 @@
"vue-router": "^4.0.14", "vue-router": "^4.0.14",
"vue-types": "^4.1.1", "vue-types": "^4.1.1",
"webpack-aliyun-oss": "^0.5.9", "webpack-aliyun-oss": "^0.5.9",
"xlsx": "^0.18.5"
"xlsx": "^0.17.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.2.3", "@commitlint/cli": "^16.2.3",

6
src/api/clue/index.ts

@ -7,6 +7,12 @@ export const getCluePage = (params: any) =>
params, params,
}) })
export const getClueList = (params: any) =>
defHttp.get<PageResultModel<any>>({
url: '/dating-clue-service/user/page/own-dating-clue',
params,
})
export const createClueRecord = (params: any) => defHttp.post({ url: '/dating-clue-service/user/create/dating-clue', params }) export const createClueRecord = (params: any) => defHttp.post({ url: '/dating-clue-service/user/create/dating-clue', params })
export const editClueRecord = (params: any) => defHttp.post({ url: '/dating-clue-service/user/edit/dating-clue', params }) export const editClueRecord = (params: any) => defHttp.post({ url: '/dating-clue-service/user/edit/dating-clue', params })

2
src/api/sys/user.ts

@ -20,7 +20,7 @@ enum Api {
TestRetry = '/testRetry', TestRetry = '/testRetry',
AuthorizeByCaptcha = '/dating-agency-uec/authorize/by-captcha', AuthorizeByCaptcha = '/dating-agency-uec/authorize/by-captcha',
GetAuthCaptcha = '/dating-agency-uec/authorize/get/auth-captcha', GetAuthCaptcha = '/dating-agency-uec/authorize/get/auth-captcha',
GetOwnOrganizationList = '/dating-agency-uec/user/list/own-organization',
GetOwnOrganizationList = '/dating-agency-uec/user/list/own-organization/by-dating-clue',
GetBaseInfo = '/dating-agency-uec/user/get/base-info', GetBaseInfo = '/dating-agency-uec/user/get/base-info',
} }

28
src/enums/customerEnum.ts

@ -13,3 +13,31 @@ export const sourceList = [
{ label: '红娘录入', value: 2 }, { label: '红娘录入', value: 2 },
{ label: '红娘资料', value: 3 }, { label: '红娘资料', value: 3 },
] ]
export const channelList = [
{ label: '线上录入', value: 1 },
{ label: '抖音', value: 2 },
{ label: '小红书', value: 3 },
]
export const educationList = [
{ label: '高中及以下', value: 0 },
{ label: '中专', value: 1 },
{ label: '大专', value: 2 },
{ label: '大学本科', value: 3 },
{ label: '硕士', value: 4 },
{ label: '博士', value: 5 },
]
export const maritalList = [
{ label: '未婚', value: 0 },
{ label: '离异', value: 1 },
{ label: '丧偶的', value: 2 },
]
export const clueStatusList = [
{ label: '待分配', value: 1 },
{ label: '跟进中', value: 2 },
{ label: '无效线索', value: 3 },
{ label: '有效线索', value: 4 },
]

4
src/layouts/default/header/index.vue

@ -47,7 +47,7 @@
/> --> /> -->
<div class="flex-row-center-start" style="padding-right: 8px;"> <div class="flex-row-center-start" style="padding-right: 8px;">
<span style="color: #333;font-size: 20px;font-weight: bold;">{{orgName}}</span> <span style="color: #333;font-size: 20px;font-weight: bold;">{{orgName}}</span>
<span style="background: #333;height: 24px;width: 2px;margin-top: 6px;margin-left: 18px;"></span>
<span style="background: #999;height: 24px;width: 2px;margin-top: 6px;margin-left: 18px;"></span>
</div> </div>
<UserDropDown :theme="getHeaderTheme" /> <UserDropDown :theme="getHeaderTheme" />
@ -173,11 +173,9 @@
const orgName = computed(() => { const orgName = computed(() => {
var orgId = localStorage.getItem(ORG_ID_KEY) + '' var orgId = localStorage.getItem(ORG_ID_KEY) + ''
console.log(orgId)
var orgString = localStorage.getItem(ORG_LIST_KEY) + '' var orgString = localStorage.getItem(ORG_LIST_KEY) + ''
var orgList = JSON.parse(orgString) var orgList = JSON.parse(orgString)
var org = orgList.find(item => item.orgId === orgId) var org = orgList.find(item => item.orgId === orgId)
console.log(org)
return org.fullName return org.fullName
}) })

400
src/views/clue/clueList/data.ts

@ -1,69 +1,5 @@
import { BasicColumn, FormSchema } from '/@/components/Table' import { BasicColumn, FormSchema } from '/@/components/Table'
const userTypeList = [
{ label: '正常用户', value: 1 },
{ label: '录入用户', value: 2},
{ label: '红娘', value: 3 },
]
export const tableColumns: BasicColumn[] = [
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } },
{ width: 80, title: '性别', dataIndex: 'genderCode' },
{ width: 160, title: '渠道来源', dataIndex: 'channel' },
{ width: 120, title: '状态', dataIndex: 'status' },
{ width: 160, title: '手机号码', dataIndex: 'userPhone' },
{ width: 120, title: '核验人', dataIndex: 'nickName' },
{ width: 120, title: '跟进状态', dataIndex: 'userMarriageInformationType',
customRender: ({ text }) => {
return userTypeList.find((find) => find.value === text)?.label
},
},
{ width: 200, title: '最后跟进时间', dataIndex: 'contentTime' },
{ width: 200, title: '录入时间', dataIndex: 'contentTime' },
]
export const tableFormSchema: FormSchema[] = [
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
]
import { ref } from 'vue'
import dayjs, { Dayjs } from 'dayjs'
import { genderList } from '/@/enums/customerEnum'
import { useAddressData } from '/@/hooks/common'
import { genderList, channelList, clueStatusList } from '/@/enums/customerEnum'
import { import {
getIncomeList, getIncomeList,
getNationList, getNationList,
@ -79,301 +15,123 @@ import {
getCarPurchaseSituation, getCarPurchaseSituation,
} from '/@/api/essentialData' } 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)
}
})
}
const userTypeList = [
{ label: '正常用户', value: 1 },
{ label: '录入用户', value: 2},
{ label: '红娘', value: 3 },
]
export const addressName = ref<string[]>([])
// 基本信息
export const modalFormSchema: FormSchema[] = [
{
field: 'genderCode',
label: '性别',
colProps: { span: 8 },
component: 'Select',
componentProps: ({ formModel }) => {
return {
options: genderList,
disabled: !!formModel.name && formModel.name !== -1,
onChange: (_: any, v: any) => {
basicInfoData.value.genderValue = v?.label
},
}
},
},
{ 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: '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
},
export const tableColumns: BasicColumn[] = [
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } },
{ width: 80, title: '性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} },
{ width: 120, title: '手机号码', dataIndex: 'phone' },
{ width: 120, title: '渠道来源', dataIndex: 'channelType',
customRender: ({ text }) => {
return channelList.find((find) => find.value === text)?.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
},
}
},
{ width: 100, title: '状态', dataIndex: 'validStatus',
customRender: ({ text }) => {
return clueStatusList.find((find) => find.value === text)?.label
},
}, },
{
field: 'birthDate',
label: '出生日期',
colProps: { span: 8 },
component: 'DatePicker',
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')
},
}
{ width: 120, title: '创建人', dataIndex: 'creatorName' },
{ width: 120, title: '核验人', dataIndex: 'verifierName' },
{ width: 120, title: '跟进状态', dataIndex: 'userMarriageInformationType',
customRender: ({ text }) => {
return userTypeList.find((find) => find.value === text)?.label
}, },
}, },
{ field: 'height', label: '身高(cm)', component: 'InputNumber', colProps: { span: 8 }, },
{ width: 160, title: '最后跟进时间', dataIndex: 'finalFollowTime' },
{ width: 160, title: '录入时间', dataIndex: 'creatTime' },
]
export const tableFormSchema: FormSchema[] = [
{ {
field: 'educationCode',
label: '学历',
colProps: { span: 8 },
component: 'ApiSelect',
field: 'genderCode',
label: '用户性别',
colProps: { span: 6 },
component: 'Select',
componentProps: { componentProps: {
labelField: 'desc',
api: getEducationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.education = v?.label
},
options: genderList,
}, },
}, },
{ {
field: 'incomeCode',
label: '月收入',
colProps: { span: 8 },
component: 'ApiSelect',
field: 'channelType',
label: '渠道来源',
colProps: { span: 6 },
component: 'Select',
componentProps: { componentProps: {
labelField: 'desc',
api: getIncomeList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.income = v?.label
},
options: channelList,
}, },
}, },
// { // {
// 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: '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', field: 'maritalStatusCode',
label: '婚姻状况', label: '婚姻状况',
colProps: { span: 8 },
colProps: { span: 6 },
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
labelField: 'desc', labelField: 'desc',
api: getMaritalStatusList, api: getMaritalStatusList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.maritalStatusName = v?.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: 'age',
label: '年龄',
slot: 'age',
component: 'InputNumber',
colProps: { span: 6 },
}, },
{ {
field: 'propertyPermitsCode',
label: '购房情况',
colProps: { span: 8 },
field: 'educationCode',
label: '学历',
colProps: { span: 6 },
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
labelField: 'desc', labelField: 'desc',
api: getPropertyPermits,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.propertyPermits = v?.label
},
api: getEducationList,
}, },
}, },
{ {
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: 'createTime',
label: '创建时间',
component: 'RangePicker',
colProps: { span: 6 },
}, },
{ {
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: 'creatorName',
label: '创建人名称',
component: 'Input',
colProps: { span: 6},
}, },
{ {
field: 'profilePhoto',
component: 'Upload',
label: '资料头像',
colProps: { span: 8 },
slot: 'profilePhoto',
field: 'followTime',
label: '最后跟进时间',
component: 'RangePicker',
colProps: { span: 6 },
}, },
{ {
field: 'describeInfo',
label: '个人描述',
component: 'InputTextArea',
colProps: { span: 16 },
componentProps: ({ formModel }) => {
return {
autoSize: {
minRows: 4,
},
disabled: formModel?.describeAudit,
}
},
field: 'verifierName',
label: '核验人名称',
component: 'Input',
colProps: { span: 6},
}, },
] ]

70
src/views/clue/clueList/index.vue

@ -1,36 +1,6 @@
<template> <template>
<div class="order-list"> <div class="order-list">
<BasicTable @register="registerTable"> <BasicTable @register="registerTable">
<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 activityStatusList" :key="item.value" :value="item.value">{{
item.label
}}</RadioButton>
</RadioGroup>
<div class="flex-row">
<Button :type="'primary'" @click="handleAdd">导入</Button>
<Button :type="'primary'" style="margin-left: 16px;" @click="handleAdd">新增</Button>
</div>
</div>
</template>
<template #userinfo="{ text, record }">
<div class="flex-row">
<Image style="width: 40px;height: 40px;" src="https://dating-agency-test.oss-accelerate.aliyuncs.com/B96BAC02B145C2D.png" />
<div class="flex-col" style="margin-left: 8px;">
<div class="flex-row">
<span style="font-size: 14px;color: #333;">用户昵称</span>
<span style="font-size: 13px;color: #666;margin: 0 12px;">(ID23762736)</span>
</div>
<div class="flex-row">
<span style="font-size: 13px;color: #666;">30</span>
<span style="font-size: 13px;color: #666;margin-left: 12px;">大专</span>
<span style="font-size: 13px;color: #666;margin-left: 12px;">离异</span>
<span style="font-size: 13px;color: #666;margin-left: 12px;">销售</span>
</div>
</div>
</div>
</template>
<template #action="{ record }"> <template #action="{ record }">
<TableAction <TableAction
:actions="[ :actions="[
@ -42,44 +12,30 @@
/> />
</template> </template>
</BasicTable> </BasicTable>
<ClueModal @register="registerModal" @success="handleSuccess" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Radio } from 'ant-design-vue'
export default { export default {
name: 'ClueList', name: 'ClueList',
components: { RadioGroup: Radio.Group, RadioButton: Radio.Button },
} }
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import moment from 'moment/moment' import moment from 'moment/moment'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { Card, Image, Button } from 'ant-design-vue'
import { Card } from 'ant-design-vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { tableColumns, tableFormSchema } from './data' import { tableColumns, tableFormSchema } from './data'
import { useModal } from '/@/components/Modal'
import { BasicTable, useTable, TableAction } from '/@/components/Table' import { BasicTable, useTable, TableAction } from '/@/components/Table'
import ClueModal from './modal.vue'
import { getCluePage } from '/@/api/clue'
const radioVal = ref<any>('')
const activityStatusList = [
{ label: '未开始', value: 1, color: 'orange' },
{ label: '报名中', value: 2, color: 'blue' },
{ label: '全部', value: '' },
]
import { getClueList } from '/@/api/clue'
const [registerTable, { reload, setPagination }] = useTable({
const [registerTable] = useTable({
bordered: true, bordered: true,
useSearchForm: true, useSearchForm: true,
columns: tableColumns, columns: tableColumns,
showIndexColumn: true, showIndexColumn: true,
showTableSetting: false, showTableSetting: false,
api: getCluePage,
api: getClueList,
formConfig: { formConfig: {
labelWidth: 120, labelWidth: 120,
schemas: tableFormSchema, schemas: tableFormSchema,
@ -101,23 +57,6 @@
}, },
}) })
function handleRadioChange() {
setPagination({ current: 1 })
reload()
}
const [registerModal, { openModal, closeModal }] = useModal()
function handleAdd() {
openModal(true, {
ifUpdate: false,
})
}
//
function handleSuccess() {
closeModal()
reload()
}
const router = useRouter() const router = useRouter()
function toDetail(record: any) { function toDetail(record: any) {
const { miId } = record const { miId } = record
@ -128,4 +67,5 @@
} }
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
</style> </style>

76
src/views/clue/clueList/modal.vue

@ -1,76 +0,0 @@
<template>
<BasicModal v-bind="$attrs" :width="960" @ok="handleOk" @register="registerModal">
<BasicForm @register="registerForm" style="padding: 16px 48px 0px 0px;">
<template #profilePhoto="{ model, field }">
<CropperAvatar :value="model[field]" @change="uploadAvatarAfter"/>
</template>
</BasicForm>
</BasicModal>
</template>
<script setup lang="ts">
import { ref, unref } from 'vue'
import { modalFormSchema, basicInfoData } from './data'
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, editClueRecord } from '/@/api/clue'
const [registerForm, { setFieldsValue, resetFields, validate, getFieldsValue, updateSchema }] =
useForm({
labelWidth: 120,
schemas: modalFormSchema,
baseColProps: { span: 22 },
showActionButtonGroup: false,
})
const ifUpdate = ref<boolean>(false)
const [registerModal, { setModalProps }] = useModalInner(async (data) => {
await resetFields()
ifUpdate.value = !!data.ifUpdate
setModalProps({
minHeight: 50,
confirmLoading: false,
title: ifUpdate.value ? '编辑' : '新增线索',
})
await updateSchema({
field: 'franchiseFee',
componentProps: {
disabled: ifUpdate.value,
},
})
if (unref(ifUpdate)) {
await setFieldsValue({
...data.record,
})
}
})
function uploadAvatarAfter(value) {
//
setFieldsValue({
profilePhoto: value,
});
}
const { createMessage } = useMessage()
const emits = defineEmits(['success'])
async function handleOk() {
try {
await validate()
const values: any = getFieldsValue()
console.log({...values, ...basicInfoData.value})
setModalProps({ confirmLoading: true })
const fun = unref(ifUpdate) ? editClueRecord : createClueRecord
await fun({...values, ...basicInfoData.value})
createMessage.success(`${unref(ifUpdate) ? '编辑' : '新增'}成功!`)
emits('success')
} finally {
setModalProps({ confirmLoading: false })
}
}
</script>
<style scoped lang="less"></style>

659
src/views/clue/cluePool/data.ts

@ -1,4 +1,6 @@
import { BasicColumn, FormSchema } from '/@/components/Table' import { BasicColumn, FormSchema } from '/@/components/Table'
import moment from 'moment';
const userTypeList = [ const userTypeList = [
{ label: '正常用户', value: 1 }, { label: '正常用户', value: 1 },
{ label: '录入用户', value: 2}, { label: '录入用户', value: 2},
@ -6,21 +8,668 @@ const userTypeList = [
] ]
export const tableColumns: BasicColumn[] = [ export const tableColumns: BasicColumn[] = [
{ title: '用户昵称', dataIndex: 'nickName' },
{ title: '用户身份', dataIndex: 'userMarriageInformationType',
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } },
{ width: 80, title: '性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} },
{ width: 120, title: '手机号码', dataIndex: 'phone' },
{ width: 100, title: '渠道来源', dataIndex: 'channelType',
customRender: ({ text }) => {
return channelList.find((find) => find.value === text)?.label
},
},
{ width: 100, title: '状态', dataIndex: 'allocationStatus',
customRender: ({ text }) => {
return clueStatusList.find((find) => find.value === text)?.label
},
},
{ width: 120, title: '创建人', dataIndex: 'creatorName' },
{ width: 120, title: '核验人', dataIndex: 'verifierName' },
{ width: 100, title: '跟进状态', dataIndex: 'followStatus',
customRender: ({ text }) => { customRender: ({ text }) => {
return userTypeList.find((find) => find.value === text)?.label return userTypeList.find((find) => find.value === text)?.label
}, },
}, },
// { title: '客户手机号', dataIndex: 'userPhone' },
{ title: '最新消息时间', dataIndex: 'contentTime' },
{ width: 160, title: '最后跟进时间', dataIndex: 'finalFollowTime' },
{ width: 160, title: '录入时间', dataIndex: 'creatTime' },
] ]
export const tableFormSchema: FormSchema[] = [ 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', field: 'nickName',
label: '用户昵称', label: '用户昵称',
component: 'Input', component: 'Input',
colProps: { span: 8},
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},
},
]
import { ref } from 'vue'
import dayjs, { Dayjs } from 'dayjs'
import { genderList, channelList, clueStatusList } 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',
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: '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: '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: 'height', label: '身高(cm)', component: 'InputNumber', colProps: { span: 8 }, },
{
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: '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: '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: '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: '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: '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: 'minIncome',
label: '月收入',
colProps: { span: 8 },
component: 'InputNumber',
slot: 'income',
},
{
field: 'maxIncome',
label: '月收入',
colProps: { span: 8 },
component: 'InputNumber',
ifShow: false,
},
{
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: '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: '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: 'constellationCode',
label: '星座',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getConstellationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
demandMarriageMoreData.value.constellation = v?.label
},
},
}, },
] ]

187
src/views/clue/cluePool/index.vue

@ -1,53 +1,162 @@
<template> <template>
<div class="order-list"> <div class="order-list">
<BasicTable @register="registerTable"> <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 clueStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton>
</RadioGroup>
<div class="flex-row-center-start">
<Popconfirm placement="bottom" icon="分配线索" :disabled="datingClueIdList.length == 0" @confirm="allocateList">
<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 :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">
<Image style="width: 40px;height: 40px;" :src="record.profilePhoto || 'https://dating-agency-test.oss-accelerate.aliyuncs.com/B96BAC02B145C2D.png'" />
<div class="flex-col" style="margin-left: 8px;">
<div class="flex-row">
<span style="font-size: 14px;color: #333;font-weight: bold;">{{record.nickName}}</span>
<span 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;">{{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 #action="{ record }"> <template #action="{ record }">
<TableAction
<!-- <TableAction
:actions="[ :actions="[
{ {
label: '详情', label: '详情',
onClick: toDetail.bind(null, record), onClick: toDetail.bind(null, record),
}, },
]" ]"
/>
/> -->
<div class="flex-row-center-start">
<Button type="link" @click="toDetail(record)">详情</Button>
<span style="color: #999;" v-if="record.allocationStatus !== 1">|</span>
<Popconfirm placement="topRight" icon="重新分配" @confirm="allocateList(record.id)" v-if="record.allocationStatus !== 1">
<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="link" danger v-if="record.allocationStatus !== 1">重新分配</Button>
</Popconfirm>
</div>
</template> </template>
</BasicTable> </BasicTable>
<ClueModal @register="registerModal" @success="handleSuccess" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Radio } from 'ant-design-vue'
export default { export default {
name: 'OrderList',
name: 'CluePool',
components: { RadioGroup: Radio.Group, RadioButton: Radio.Button },
} }
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import moment from 'moment/moment' import moment from 'moment/moment'
import { computed, ref } from 'vue'
import { Card } from 'ant-design-vue'
import { onMounted, reactive, ref } from 'vue'
import { Card, Image, Button, InputNumber, Popconfirm, Checkbox } from 'ant-design-vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useMessage } from '/@/hooks/web/useMessage'
import { tableColumns, tableFormSchema } from './data' import { tableColumns, tableFormSchema } from './data'
import { useModal } from '/@/components/Modal'
import { BasicTable, useTable, TableAction } from '/@/components/Table' import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { getCluePage } from '/@/api/clue'
import ClueModal from './modal.vue'
import { getCluePage, allocateCluing } 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: 4 },
{ label: '全部', value: '' },
]
const ageModel = reactive({
minAge: '',
maxAge: '',
})
const memberList = ref<any[]>([]);
onMounted(async () => {
const result = await pageOrganizationMember({})
memberList.value = result.items
})
const [registerTable] = useTable({
const [registerTable, { reload, setPagination, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
bordered: true, bordered: true,
useSearchForm: true, useSearchForm: true,
columns: tableColumns, columns: tableColumns,
showIndexColumn: true, showIndexColumn: true,
showTableSetting: false, showTableSetting: false,
api: getCluePage, api: getCluePage,
rowKey: 'id',
rowSelection: {
type: 'checkbox',
onChange,
getCheckboxProps(record: Recordable) {
// Demo: id0
return { disabled: record.allocationStatus !== 1 };
},
},
formConfig: { formConfig: {
labelWidth: 120, labelWidth: 120,
schemas: tableFormSchema, schemas: tableFormSchema,
}, },
// beforeFetch: (arg) => {
// const { orderTime } = arg
// if (orderTime) {
// arg.orderTimeFrom = moment(orderTime[0]).format('YYYY-MM-DD 00:00:00')
// arg.orderTimeTo = moment(orderTime[1]).format('YYYY-MM-DD 23:59:59')
// delete arg.orderTime
// }
// },
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
},
actionColumn: { actionColumn: {
width: 160, width: 160,
title: '操作', title: '操作',
@ -57,15 +166,57 @@
}, },
}) })
function handleRadioChange() {
setPagination({ current: 1 })
reload()
}
const [registerModal, { openModal, closeModal }] = useModal()
function handleAdd(excel) {
openModal(true, {ifUpdate: excel,})
}
//
function handleSuccess() {
closeModal()
reload()
}
const { createMessage } = useMessage()
const router = useRouter() const router = useRouter()
function toDetail(record: any) { function toDetail(record: any) {
const { miId } = record
const { id } = record
router.push({ router.push({
query: { miId },
query: { id },
path: '/clue/customer', path: '/clue/customer',
}) })
} }
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> </script>
<style scoped lang="less"> <style scoped lang="less">
</style> </style>

196
src/views/clue/cluePool/modal.vue

@ -0,0 +1,196 @@
<template>
<BasicModal v-bind="$attrs" :width="960" @ok="handleOk" @register="registerModal">
<template v-if="!ifUpdate" #title>
<span style="margin: 0 5px">-</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 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['minIncome']" />
<span style="margin: 0 5px">-</span>
<InputNumber placeholder="请输入" :min="0" v-model:value="model['maxIncome']" />
</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, editClueRecord } from '/@/api/clue'
const activeKey2 = ref<string>('1')
const [registerForm, { setFieldsValue, resetFields, validate, getFieldsValue, updateSchema }] =
useForm({
labelWidth: 120,
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 setFieldsValue3()
}
setModalProps({
minHeight: 50,
confirmLoading: false,
title: ifUpdate.value ? '导入线索' : '新增线索',
})
await updateSchema({
field: 'franchiseFee',
componentProps: {
disabled: ifUpdate.value,
},
})
if (unref(ifUpdate)) {
await setFieldsValue({
...data.record,
})
}
})
//
const [registerForm3, { getFieldsValue: getFieldsValue3, setFieldsValue: setFieldsValue3 }] =
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 {
await validate()
const values: any = getFieldsValue()
console.log({...values, ...basicInfoData.value})
setModalProps({ confirmLoading: true })
const fun = unref(ifUpdate) ? editClueRecord : createClueRecord
await fun({...values, ...basicInfoData.value, channelType: 1})
createMessage.success(`${unref(ifUpdate) ? '编辑' : '新增'}成功!`)
emits('success')
} finally {
setModalProps({ confirmLoading: false })
}
}
const tableListRef = ref<{ title: string; columns?: any[]; dataSource?: 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 });
}
}
</script>
<style scoped lang="less">
::v-deep .ant-input-number {
min-width: 80px;
width: 100% !important;
max-width: 100%;
}
</style>

35
src/views/clue/customer/index.vue

@ -1,23 +1,23 @@
<template> <template>
<div class="flex-col" style="height:100%;padding: 16px;">
<div class="flex-col" style="height:100%;padding: 16px;" v-if="details">
<div class="flex-row" style="background: white;width: 100%;padding: 10px;"> <div class="flex-row" style="background: white;width: 100%;padding: 10px;">
<CropperAvatar :value="avatar" width="90px" @change="uploadAvatarAfter"/> <CropperAvatar :value="avatar" width="90px" @change="uploadAvatarAfter"/>
<div class="flex-col" style="flex: 1;margin-left: 10px;"> <div class="flex-col" style="flex: 1;margin-left: 10px;">
<div class="flex-row-center-start"> <div class="flex-row-center-start">
<span style="font-size: 18px;font-weight: bold;">用户昵称</span>
<span style="font-size: 18px;font-weight: bold;">{{details.nickName}}</span>
<!-- <Icon icon="ant-design:edit-twotone" size="18px" style="margin-left: 12px;color:#0960bd;"/> --> <!-- <Icon icon="ant-design:edit-twotone" size="18px" style="margin-left: 12px;color:#0960bd;"/> -->
<Button type="link"><template #icon><Icon icon="ant-design:edit-twotone" size="16px" style="margin-left: 12px;color:#0960bd;"/></template></Button> <Button type="link"><template #icon><Icon icon="ant-design:edit-twotone" size="16px" style="margin-left: 12px;color:#0960bd;"/></template></Button>
<span style="color: #666;margin: 0 12px;">(ID23762736 | 渠道来源抖音)</span>
<span style="color: #666;margin: 0 12px;">(ID{{details.id}} | 渠道来源{{channelList.find((find) => find.value === details.channelType)?.label}})</span>
<Tag color="#d5d5d5">#线索阶段-0</Tag> <Tag color="#d5d5d5">#线索阶段-0</Tag>
</div> </div>
<div class="flex-row-center-start" style="margin-top: 8px;"> <div class="flex-row-center-start" style="margin-top: 8px;">
<div class="flex-row-center-start" style="width: 240px;"> <div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">电话</span> <span style="color: #666;">电话</span>
<span style="color: #333;font-weight: bold;">18565126880</span>
<span style="color: #333;font-weight: bold;">{{details.phone}}</span>
</div> </div>
<div class="flex-row-center-start" style="width: 240px;"> <div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">微信</span> <span style="color: #666;">微信</span>
<span style="color: #333;font-weight: bold;">--</span>
<span style="color: #333;font-weight: bold;">{{details.weChatId || '--'}}</span>
</div> </div>
<div class="flex-row-center-start" style="width: 240px;"> <div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">客户标签</span> <span style="color: #666;">客户标签</span>
@ -28,11 +28,15 @@
<div class="flex-row-center-start" style="margin-top: 8px;"> <div class="flex-row-center-start" style="margin-top: 8px;">
<div class="flex-row-center-start" style="width: 240px;"> <div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">下次跟进时间</span> <span style="color: #666;">下次跟进时间</span>
<span style="color: #333;">--</span>
<span style="color: #333;">{{details.nextFollowTime || '--'}}</span>
</div> </div>
<!-- <div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">创建人</span>
<span style="color: #333;">{{details.creatorName || '--'}}</span>
</div> -->
<div class="flex-row-center-start" style="width: 240px;"> <div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">跟进人</span> <span style="color: #666;">跟进人</span>
<span style="color: #333;">彭杰</span>
<span style="color: #333;">{{details.followName || '--'}}</span>
</div> </div>
<div class="flex-row-center-end" style="flex:1;padding-right: 32px;"> <div class="flex-row-center-end" style="flex:1;padding-right: 32px;">
<Button size="small" type="primary">查看下一个客户</Button> <Button size="small" type="primary">查看下一个客户</Button>
@ -140,8 +144,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onActivated, ref } from 'vue'
// import { Spin, Result } from 'ant-design-vue'
import { useRoute } from 'vue-router'
import { onMounted, ref } from 'vue'
import { Tabs, Card, Table, Button, TabPane, Tag, Timeline, TimelineItem, Result, Spin } from 'ant-design-vue' import { Tabs, Card, Table, Button, TabPane, Tag, Timeline, TimelineItem, Result, Spin } from 'ant-design-vue'
import { Icon } from '/@/components/Icon' import { Icon } from '/@/components/Icon'
import { CropperAvatar } from '/@/components/Cropper'; import { CropperAvatar } from '/@/components/Cropper';
@ -149,6 +153,19 @@
import { useForm } from '/@/components/Form' import { useForm } from '/@/components/Form'
import { basicSchema, modalFormSchema } from './data' import { basicSchema, modalFormSchema } from './data'
import { getClueInfo } from '/@/api/clue' import { getClueInfo } from '/@/api/clue'
import { genderList, channelList, clueStatusList } from '/@/enums/customerEnum'
const details = ref<any>()
onMounted(() => {
initFetch()
})
async function initFetch() {
const { query } = useRoute()
const { id } = query || {}
if (!id) return
details.value = await getClueInfo(`${id}`)
}
const activeKey = ref<string>('1') const activeKey = ref<string>('1')
const activeKey2 = ref<string>('1') const activeKey2 = ref<string>('1')

Loading…
Cancel
Save