20 changed files with 1774 additions and 10 deletions
Split View
Diff Options
-
50src/api/clue/index.ts
-
22src/enums/customerEnum.ts
-
5src/locales/lang/zh-CN/routes/invite.ts
-
26src/router/menu.ts
-
2src/views/clue/customer/modal.vue
-
2src/views/invite/myList/data.ts
-
17src/views/invite/myList/index.vue
-
77src/views/market/appointment/data.ts
-
163src/views/market/appointment/index.vue
-
68src/views/market/appointment/modal.vue
-
90src/views/market/client/contract.vue
-
376src/views/market/client/data.ts
-
171src/views/market/client/index.vue
-
203src/views/market/client/modal.vue
-
74src/views/market/components/data.ts
-
95src/views/market/components/modal.vue
-
5src/views/market/contract/index.vue
-
78src/views/market/task/data.ts
-
171src/views/market/task/index.vue
-
89src/views/market/task/modal.vue
@ -0,0 +1,77 @@ |
|||
import { BasicColumn, FormSchema } from '/@/components/Table' |
|||
import { genderList, incomeList, maritalList, appointmentStatusList } from '/@/enums/customerEnum' |
|||
|
|||
export const tableColumns: BasicColumn[] = [ |
|||
{ width: 120, title: '用户名称', dataIndex: 'nickName' }, |
|||
{ width: 90, title: '用户性别', dataIndex: 'genderCode', customRender: ({ text }) => { return genderList.find((find) => find.value === text)?.label} }, |
|||
{ width: 120, title: '手机号码', dataIndex: 'phone' }, |
|||
{ width: 80, title: '年龄', dataIndex: 'age' }, |
|||
{ width: 90, title: '婚姻状态', dataIndex: 'maritalStatusCode', |
|||
customRender: ({ text }) => { |
|||
return maritalList.find((find) => find.value === text)?.label |
|||
}, |
|||
}, |
|||
{ width: 120, title: '月收入', dataIndex: 'incomeCode', |
|||
customRender: ({ text }) => { |
|||
return incomeList.find((find) => find.value === text)?.label |
|||
}, |
|||
}, |
|||
{ width: 120, title: '面谈销售', dataIndex: 'salesConsultantName' }, |
|||
{ width: 160, title: '预约时间', dataIndex: 'appointmentTime' }, |
|||
{ title: '跟进进度', dataIndex: 'datingStoreAppointmentStatus', slots: { customRender: 'datingStoreAppointmentStatus' } }, |
|||
] |
|||
|
|||
export const tableFormSchema: FormSchema[] = [ |
|||
{ |
|||
field: 'phone', |
|||
label: ' 用户电话号码', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'salesConsultantName', |
|||
label: '面谈销售', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'createTime', |
|||
label: '预约时间', |
|||
component: 'RangePicker', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
] |
|||
|
|||
export const appointmentFormSchema: FormSchema[] = [ |
|||
{ |
|||
label: '跟进阶段', |
|||
component: 'Select', |
|||
field: 'datingStoreAppointmentFollowStage', |
|||
required: true, |
|||
componentProps: { |
|||
options: appointmentStatusList, |
|||
style: { width: '100%' }, |
|||
}, |
|||
}, |
|||
{ |
|||
label: '约见时间', |
|||
component: 'DatePicker', |
|||
field: 'appointmentTime', |
|||
required: true, |
|||
componentProps: { |
|||
showTime: true, |
|||
style: { width: '100%' }, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'remark', |
|||
label: '备注', |
|||
required: false, |
|||
component: 'InputTextArea', |
|||
componentProps: { |
|||
autosize: { |
|||
minRows: 4, |
|||
}, |
|||
}, |
|||
}, |
|||
] |
|||
@ -0,0 +1,163 @@ |
|||
<template> |
|||
<div class="order-list"> |
|||
<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 followStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton> |
|||
</RadioGroup> |
|||
</div> |
|||
</template> |
|||
<template #datingStoreAppointmentStatus="{ text, record }"> |
|||
<Tag :color="appointmentStatusList.find((find) => find.value === text)?.color">{{appointmentStatusList.find((find) => find.value === text)?.label}}</Tag> |
|||
</template> |
|||
<template #action="{ record }"> |
|||
<div class="flex-row-center-start" v-if="record.datingStoreAppointmentStatus != 5"> |
|||
<Popconfirm placement="topRight" :icon="'分配'" @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 style="width: 88px;margin-right: 12px;" type="primary" danger>分配销售</Button> |
|||
</Popconfirm> |
|||
<Button style="width: 72px;margin-right: 12px;" @click="handleAppointment(record)">约见</Button> |
|||
<Button style="width: 72px;margin-right: 12px;" danger @click="handleReport(record)">跟进</Button> |
|||
<Button type="primary" style="width: 88px;margin-right: 4px;" @click="handleSign(record)">签约登记</Button> |
|||
</div> |
|||
</template> |
|||
</BasicTable> |
|||
<SignModal @register="signModal" @success="handleSuccess" /> |
|||
<AppointmentModal @register="appointmentModal" @success="handleSuccess" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Radio } from 'ant-design-vue' |
|||
|
|||
export default { |
|||
name: 'MyList', |
|||
components: { RadioGroup: Radio.Group, RadioButton: Radio.Button }, |
|||
} |
|||
</script> |
|||
<script setup lang="ts"> |
|||
import moment from 'moment/moment' |
|||
import { computed, ref, onMounted } from 'vue' |
|||
import { Avatar, Card, Button, Popconfirm, Result, Tag, Checkbox } from 'ant-design-vue' |
|||
import { useRouter } from 'vue-router' |
|||
import { useMessage } from '/@/hooks/web/useMessage' |
|||
import { tableColumns, tableFormSchema } from './data' |
|||
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
|||
import { useModal } from '/@/components/Modal' |
|||
import FollowModal from '../components/modal.vue' |
|||
import AppointmentModal from './modal.vue' |
|||
import SignModal from '/@/views/market/components/modal.vue' |
|||
import { getStoreAppointmentList, allocationSaler } from '/@/api/clue' |
|||
import { appointmentStatusList } from '/@/enums/customerEnum' |
|||
import { pageOrganizationMember } from '/@/api/staff/staff' |
|||
|
|||
const memberList = ref<any[]>([]); |
|||
const verifier = ref<any>('') |
|||
onMounted(async () => { |
|||
const result = await pageOrganizationMember({}) |
|||
memberList.value = result.items |
|||
}) |
|||
|
|||
const [registerTable, { reload, setPagination }] = useTable({ |
|||
bordered: false, |
|||
useSearchForm: true, |
|||
columns: tableColumns, |
|||
showIndexColumn: false, |
|||
showTableSetting: false, |
|||
api: getStoreAppointmentList, |
|||
formConfig: { |
|||
labelWidth: 120, |
|||
schemas: tableFormSchema, |
|||
}, |
|||
beforeFetch: (arg) => { |
|||
const { createTime } = arg |
|||
if (createTime) { |
|||
arg.appointmentTimeFrom = moment(createTime[0]).format('YYYY-MM-DD 00:00:00') |
|||
arg.appointmentTimeTo = moment(createTime[1]).format('YYYY-MM-DD 23:59:59') |
|||
delete arg.createTime |
|||
} |
|||
arg.datingStoreAppointmentStatus = radioVal.value |
|||
}, |
|||
actionColumn: { |
|||
width: 360, |
|||
title: '操作', |
|||
fixed: 'right', |
|||
dataIndex: 'action', |
|||
slots: { customRender: 'action' }, |
|||
}, |
|||
}) |
|||
const followStatusList = [ |
|||
{ label: '待到店', value: 1 }, |
|||
{ label: '已到店', value: 2 }, |
|||
{ label: '未到店', value: 3 }, |
|||
{ label: '全部', value: '' }, |
|||
] |
|||
const radioVal = ref<any>('') |
|||
function handleRadioChange() { |
|||
setPagination({ current: 1 }) |
|||
reload() |
|||
} |
|||
|
|||
function onCheckChange(e, item){ |
|||
if(e.target.checked){ |
|||
verifier.value = item.userId |
|||
} |
|||
} |
|||
const { createMessage } = useMessage() |
|||
async function allocateList(id = null){ |
|||
if(!verifier.value){ |
|||
createMessage.warning('请选择分配人') |
|||
return |
|||
} |
|||
try { |
|||
await allocationSaler({salesConsultant: verifier.value, id}) |
|||
createMessage.success(`分配成功`) |
|||
} finally { |
|||
reload() |
|||
} |
|||
} |
|||
|
|||
const router = useRouter() |
|||
function toDetail(record: any) { |
|||
const { id } = record |
|||
router.push({ |
|||
query: { id }, |
|||
path: '/clue/customer', |
|||
}) |
|||
} |
|||
|
|||
// 上传modal |
|||
const [appointmentModal, { openModal: openAppointmentModal, closeModal: closeAppointmentModal }] = useModal() |
|||
const [signModal, { openModal: openSignModal, closeModal: closeSignModal }] = useModal() |
|||
|
|||
function handleReport(record: any) { |
|||
openAppointmentModal(true, {record, ifUpdate: true}) |
|||
} |
|||
function handleAppointment(record: any) { |
|||
openAppointmentModal(true, {record}) |
|||
} |
|||
function handleSign(record: any) { |
|||
openSignModal(true, {record}) |
|||
} |
|||
// 操作成功 |
|||
function handleSuccess() { |
|||
closeAppointmentModal() |
|||
closeSignModal() |
|||
reload() |
|||
} |
|||
</script> |
|||
<style scoped lang="less"> |
|||
.single-line { |
|||
overflow: hidden; /* 超出部分隐藏 */ |
|||
white-space: nowrap; /* 文本不换行 */ |
|||
text-overflow: ellipsis; /* 超出部分显示省略号 */ |
|||
} |
|||
</style> |
|||
@ -0,0 +1,68 @@ |
|||
<template> |
|||
<BasicModal v-bind="$attrs" @ok="handleOk" @register="registerModal"> |
|||
<BasicForm @register="registerForm" /> |
|||
</BasicModal> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue' |
|||
import { appointmentFormSchema } from './data' |
|||
import { useMessage } from '/@/hooks/web/useMessage' |
|||
import { BasicForm, useForm } from '/@/components/Form' |
|||
import { BasicModal, useModalInner } from '/@/components/Modal' |
|||
import { appointmentCustomer, remarkCustomer } from '/@/api/clue' |
|||
import { formatToDateTime } from '/@/utils/dateUtil' |
|||
|
|||
const [registerForm, { resetFields, updateSchema, validate }] = useForm({ |
|||
labelWidth: 100, |
|||
schemas: appointmentFormSchema, |
|||
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: ifUpdate.value ? `预约跟进` : '预约客户', |
|||
confirmLoading: false, |
|||
}) |
|||
if (!!ifUpdate.value) { |
|||
await updateSchema({ field: 'datingStoreAppointmentFollowStage', ifShow: true }) |
|||
await updateSchema({ field: 'remark', ifShow: true }) |
|||
await updateSchema({ field: 'appointmentTime', ifShow: false }) |
|||
} else { |
|||
await updateSchema({ field: 'datingStoreAppointmentFollowStage', ifShow: false }) |
|||
await updateSchema({ field: 'remark', ifShow: false }) |
|||
await updateSchema({ field: 'appointmentTime', ifShow: true }) |
|||
} |
|||
}) |
|||
|
|||
const { createMessage } = useMessage() |
|||
const emits = defineEmits(['success']) |
|||
async function handleOk() { |
|||
try { |
|||
const values = await validate() |
|||
setModalProps({ confirmLoading: true }) |
|||
if (!!ifUpdate.value) { |
|||
values.datingStoreAppointmentId = id.value |
|||
await remarkCustomer(values) |
|||
createMessage.success(`跟进成功!`) |
|||
} else { |
|||
values.id = id.value |
|||
const { appointmentTime } = values |
|||
values.appointmentTime = formatToDateTime(appointmentTime) |
|||
await appointmentCustomer(values) |
|||
createMessage.success(`预约成功!`) |
|||
} |
|||
emits('success') |
|||
} finally { |
|||
setModalProps({ confirmLoading: false }) |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,90 @@ |
|||
<template> |
|||
<BasicModal v-bind="$attrs" @ok="handleOk" @register="registerModal"> |
|||
<div class="flex-col" v-if="detail"> |
|||
<div class="flex-col" style="font-size: 14px;color: #333;padding-left: 12px;" v-if="detail"> |
|||
<span>合同编号:{{detail.id}}</span> |
|||
<span style="margin-top: 16rpx;">合同金额:{{detail.amount}}元</span> |
|||
<span style="margin-top: 16rpx;">合同状态:{{contractStatusList.find((find) => find.value === detail.status)?.label}}</span> |
|||
<span style="margin-top: 16rpx;">服务次数:{{detail.contractTermList[0].name}}次</span> |
|||
<span style="margin-top: 16rpx;">甲方:{{detail.firstPartyName}}</span> |
|||
<span style="margin-top: 16rpx;">甲方身份编号:{{detail.firstPartyIdentityNo}}</span> |
|||
<span style="margin-top: 16rpx;">乙方:{{detail.secondPartyName}}</span> |
|||
<span style="margin-top: 16rpx;">乙方身份编号:{{detail.secondPartyIdentityNo}}</span> |
|||
<span style="margin-top: 16rpx;">签约日期:{{detail.signDate}}</span> |
|||
<span style="margin-top: 16rpx;">服务开始日期:{{detail.startDate}}</span> |
|||
<span style="margin-top: 16rpx;">服务结束日期:{{detail.endDate}}</span> |
|||
</div> |
|||
|
|||
<div class="flex-col" style="padding-left: 12px;" v-if="detail.contractImageList && detail.contractImageList.length"> |
|||
<span style="font-size: 14px;color: #333;padding: 12px 12px 12px 0rpx;">合同凭证:</span> |
|||
<!-- <image-upload :file-list="imageList" :album="true" :enable="false" size="200rpx"/> --> |
|||
<div class="flex-row"> |
|||
<ImagePreviewGroup> |
|||
<Image |
|||
v-for="(item, index) in detail.contractImageList" |
|||
style="width: 120px; height: 120px; border-radius: 5px; margin: 0 5px 5px 0" |
|||
:key="index" |
|||
:src="item.url" |
|||
/> |
|||
</ImagePreviewGroup> |
|||
</div> |
|||
</div> |
|||
<view class="flex-col" style="font-size: 28rpx;color: #333;padding-left: 24rpx;margin-top: 24rpx;" v-if="detail.remark"> |
|||
<text>合同备注:</text> |
|||
<text style="margin-top: 8rpx;">{{detail.remark}}</text> |
|||
</view> |
|||
|
|||
<view class="flex-col" style="font-size: 28rpx;color: #333;padding: 24rpx;margin-top: 24rpx;" v-if="paymentInfo"> |
|||
<text style="font-size: 28rpx;color: #333;font-weight: bold;">支付信息:</text> |
|||
<text style="margin-top: 16rpx;">订单标识:{{paymentInfo.orderId}}</text> |
|||
<text style="margin-top: 16rpx;">应付金额:{{paymentInfo.payableAmount}}元</text> |
|||
<text style="margin-top: 16rpx;">实付金额:{{paymentInfo.paidAmount}}元</text> |
|||
<text style="margin-top: 16rpx;">未付金额:{{paymentInfo.unpaidAmount}}元</text> |
|||
<text style="margin-top: 16rpx;">付款时间:{{paymentInfo.paidTime}}</text> |
|||
<text style="margin-top: 16rpx;">付款方式:{{paymentInfoList.find((find) => find.value === paymentInfo.paymentMethod)?.label}}</text> |
|||
</view> |
|||
<view class="flex-col" style="padding-left: 24rpx;" v-if="paymentInfo && paymentInfo.voucherImageList && paymentInfo.voucherImageList.length"> |
|||
<text style="font-size: 28rpx;color: #333;padding: 24rpx 24rpx 24rpx 0rpx;">支付凭证:</text> |
|||
<ImagePreviewGroup> |
|||
<Image |
|||
v-for="(item, index) in paymentInfo.voucherImageList" |
|||
style="width: 120px; height: 120px; border-radius: 5px; margin: 0 5px 5px 0" |
|||
:key="index" |
|||
:src="item" |
|||
/> |
|||
</ImagePreviewGroup> |
|||
</view> |
|||
</div> |
|||
</BasicModal> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue' |
|||
import { ImagePreviewGroup, Image } from 'ant-design-vue' |
|||
import { BasicModal, useModalInner } from '/@/components/Modal' |
|||
import { getContractInfo, getPaymentInfo } from '/@/api/clue' |
|||
import { contractStatusList, paymentInfoList } from '/@/enums/customerEnum' |
|||
|
|||
const detail = ref<any>(null) |
|||
const paymentInfo = ref<any>(null) |
|||
const [registerModal, { setModalProps }] = useModalInner(async (data) => { |
|||
const { record } = data |
|||
// id.value = record?.id |
|||
setModalProps({ |
|||
minHeight: 50, |
|||
title: '合同信息', |
|||
confirmLoading: false, |
|||
}) |
|||
detail.value = await getContractInfo({datingStoreCustomerId: record?.id}) |
|||
paymentInfo.value = await getPaymentInfo({datingStoreCustomerId: record?.id}) |
|||
}) |
|||
// const { createMessage } = useMessage() |
|||
const emits = defineEmits(['success']) |
|||
async function handleOk() { |
|||
try { |
|||
emits('success') |
|||
} finally { |
|||
setModalProps({ confirmLoading: false }) |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,376 @@ |
|||
import { BasicColumn, FormSchema } from '/@/components/Table' |
|||
|
|||
export const contactStatusList = [ |
|||
{ label: '进行中', value: 1 }, |
|||
{ label: '生效中', value: 2 }, |
|||
{ label: '已终止', value: 3 }, |
|||
] |
|||
|
|||
export const tableColumns: BasicColumn[] = [ |
|||
{ title: '用户信息', dataIndex: 'userInfo', slots: { customRender: 'userInfo' } }, |
|||
{ width: 120, title: '手机号码', dataIndex: 'phone' }, |
|||
{ width: 240, title: '服务任务', dataIndex: 'inProgressTaskId', slots: { customRender: 'inProgressTaskId' } }, |
|||
{ width: 120, title: '服务红娘', dataIndex: 'serviceMatchmakerName' }, |
|||
{ width: 160, title: '面谈销售', dataIndex: 'salesConsultantName' }, |
|||
{ width: 160, title: '创建时间', dataIndex: 'createTime' }, |
|||
] |
|||
|
|||
export const tableFormSchema: FormSchema[] = [ |
|||
{ |
|||
field: 'name', |
|||
label: '客户姓名', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'phone', |
|||
label: '电话', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'salesConsultantName', |
|||
label: '面谈销售', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'serviceMatchmakerName', |
|||
label: '服务红娘', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
] |
|||
|
|||
|
|||
import { ref } from 'vue' |
|||
import dayjs, { Dayjs } from 'dayjs' |
|||
import { genderList, incomeList } from '/@/enums/customerEnum' |
|||
import { useAddressData } from '/@/hooks/common' |
|||
import { |
|||
getNationList, |
|||
getEducationList, |
|||
getOccupationList, |
|||
getPropertyPermits, |
|||
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: 'name', label: '客户姓名', component: 'Input', required: true, colProps: { span: 12 },}, |
|||
{ |
|||
field: 'genderCode', |
|||
label: '性别', |
|||
colProps: { span: 12 }, |
|||
component: 'Select', |
|||
required: true, |
|||
componentProps: { |
|||
options: genderList, |
|||
}, |
|||
}, |
|||
{ field: 'phone', label: '电话号码', component: 'Input', required: true, colProps: { span: 12 },}, |
|||
{ |
|||
label: '出生日期', |
|||
component: 'DatePicker', |
|||
field: 'birthDate', |
|||
required: true, |
|||
colProps: { span: 12 }, |
|||
componentProps: { |
|||
style: { width: '100%' }, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'maritalStatusCode', |
|||
label: '婚姻状况', |
|||
colProps: { span: 12 }, |
|||
component: 'ApiSelect', |
|||
componentProps: { |
|||
labelField: 'desc', |
|||
api: getMaritalStatusList, |
|||
getPopupContainer: () => document.body, |
|||
onChange: (_: any, v: any) => { |
|||
basicInfoData.value.maritalStatusName = v?.label |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'educationCode', |
|||
label: '学历', |
|||
colProps: { span: 12 }, |
|||
component: 'ApiSelect', |
|||
componentProps: { |
|||
labelField: 'desc', |
|||
api: getEducationList, |
|||
getPopupContainer: () => document.body, |
|||
onChange: (_: any, v: any) => { |
|||
basicInfoData.value.education = v?.label |
|||
}, |
|||
}, |
|||
}, |
|||
{ field: 'height', label: '身高(cm)', component: 'InputNumber', colProps: { span: 12 }, }, |
|||
{ field: 'weight', label: '体重(kg)', component: 'InputNumber', colProps: { span: 12 }, }, |
|||
{ field: 'annualIncome', label: '年收入(万元)', component: 'InputNumber', colProps: { span: 12 }, }, |
|||
{ field: 'netAsset', label: '净资产(万元)', component: 'InputNumber', colProps: { span: 12 }, }, |
|||
{ field: 'graduateSchool', label: '毕业高校', component: 'InputNumber', colProps: { span: 12 }, }, |
|||
{ |
|||
field: 'nationCode', |
|||
label: '民族', |
|||
colProps: { span: 12 }, |
|||
component: 'ApiSelect', |
|||
componentProps: { |
|||
labelField: 'cn', |
|||
valueField: 'id', |
|||
api: getNationList, |
|||
getPopupContainer: () => document.body, |
|||
onChange: (_: any, v: any) => { |
|||
demandMarriageMoreData.value.nation = v?.label |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'homeAddress', |
|||
label: '现居住地', |
|||
colProps: { span: 12 }, |
|||
component: 'Cascader', |
|||
componentProps: { |
|||
options: addressList, |
|||
onChange: (_: any, v: any) => { |
|||
basicInfoData.value.homeProvinceName = v?.[0]?.label |
|||
basicInfoData.value.homeCityName = v?.[1]?.label |
|||
basicInfoData.value.homeDistrictName = v?.[2]?.label |
|||
}, |
|||
}, |
|||
}, |
|||
{ field: 'homeDetailAddress', label: '详细地址', component: 'Input', colProps: { span: 12 }, }, |
|||
{ |
|||
label: '职业', |
|||
field: 'occupationList', |
|||
colProps: { span: 12 }, |
|||
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: 'childrenNum', label: '孩子数量', component: 'InputNumber', colProps: { span: 12 }, }, |
|||
{ |
|||
field: 'housePurchase', |
|||
label: '购房情况', |
|||
colProps: { span: 12 }, |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: [ |
|||
{ label: '已购房', value: 1 }, |
|||
{ label: '未购房', value: 0 }, |
|||
], |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'carPurchase', |
|||
label: '购车情况', |
|||
colProps: { span: 12 }, |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: [ |
|||
{ label: '已购车', value: 1 }, |
|||
{ label: '未购车', value: 0 }, |
|||
], |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'profilePhoto', |
|||
component: 'Upload', |
|||
label: '资料头像', |
|||
colProps: { span: 8 }, |
|||
slot: 'profilePhoto', |
|||
}, |
|||
{ |
|||
field: 'remark', |
|||
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: 'educationCode', |
|||
label: '学历要求', |
|||
colProps: { span: 12 }, |
|||
component: 'ApiSelect', |
|||
componentProps: { |
|||
labelField: 'desc', |
|||
api: getEducationList, |
|||
getPopupContainer: () => document.body, |
|||
onChange: (_: any, v: any) => { |
|||
demandMarriageMoreData.value.education = v?.label |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'minimumAnnualIncome', |
|||
label: '年收入(万元)', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
slot: 'income', |
|||
}, |
|||
{ |
|||
field: 'maximumAnnualIncome', |
|||
label: '年收入', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
ifShow: false, |
|||
}, |
|||
{ |
|||
field: 'cityList', |
|||
label: '所在城市', |
|||
colProps: { span: 24 }, |
|||
component: 'Cascader', |
|||
componentProps: { |
|||
options: domicilePlaceList, |
|||
onChange: (_: any, v: any) => { |
|||
demandMarriageMoreData.value.provinceName = v?.[0]?.label |
|||
demandMarriageMoreData.value.cityName = v?.[1]?.label |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'minAge', |
|||
label: '年龄范围', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
slot: 'age', |
|||
}, |
|||
{ |
|||
field: 'maxAge', |
|||
label: '年龄', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
ifShow: false, |
|||
}, |
|||
{ |
|||
field: 'minHeight', |
|||
label: '身高(cm)', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
slot: 'height', |
|||
}, |
|||
{ |
|||
field: 'maxHeight', |
|||
label: '身高', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
ifShow: false, |
|||
}, |
|||
{ |
|||
field: 'minWeight', |
|||
label: '体重(kg)', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
slot: 'weight', |
|||
}, |
|||
{ |
|||
field: 'maxWeight', |
|||
label: '体重', |
|||
colProps: { span: 12 }, |
|||
component: 'InputNumber', |
|||
ifShow: false, |
|||
}, |
|||
|
|||
{ |
|||
field: 'isDivorceAccepted', |
|||
label: '是否接受离异', |
|||
colProps: { span: 12 }, |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: [ |
|||
{ label: '不接受', value: 0 }, |
|||
{ label: '接受', value: 1 }, |
|||
], |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'isChildAccepted', |
|||
label: '是否跟老人一起生活', |
|||
colProps: { span: 12 }, |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: [ |
|||
{ label: '不接受', value: 0 }, |
|||
{ label: '接受', value: 1 }, |
|||
], |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'isLivingWithElderly', |
|||
label: '是否跟老人一起生活', |
|||
colProps: { span: 12 }, |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: [ |
|||
{ label: '跟男方', value: 1 }, |
|||
{ label: '跟女方', value: 2 }, |
|||
{ label: '不跟老人居住', value: 3 }, |
|||
], |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'otherRequirements', |
|||
label: '其他需求', |
|||
component: 'InputTextArea', |
|||
colProps: { span: 24 }, |
|||
componentProps: ({ formModel }) => { |
|||
return { |
|||
autoSize: { |
|||
minRows: 4, |
|||
}, |
|||
disabled: formModel?.describeAudit, |
|||
} |
|||
}, |
|||
}, |
|||
] |
|||
@ -0,0 +1,171 @@ |
|||
<template> |
|||
<div class="order-list"> |
|||
<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 followStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton> |
|||
</RadioGroup> |
|||
</div> |
|||
</template> --> |
|||
<template #userInfo="{ text, record }"> |
|||
<div class="flex-row-center-start" style="padding-left: 12px;"> |
|||
<Avatar :src="record.profilePhoto || 'https://dating-agency-prod.oss-cn-shenzhen.aliyuncs.com/827036501B11.png'" :size="36" /> |
|||
<div class="flex-col" style="margin-left: 12px;"> |
|||
<div class="flex-row"> |
|||
<span class="single-line" style="font-size: 14px;color: #333;font-weight: bold;max-width: 160px;">{{record.name}}</span> |
|||
</div> |
|||
<div class="flex-row"> |
|||
<span class="single-line" style="font-size: 13px;color: #333;">{{record.genderCode == 1 ? '女' : '男'}}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<template #inProgressTaskId="{ text }"> |
|||
<Tag color="pink" v-if="text">任务进行中</Tag> |
|||
</template> |
|||
<template #action="{ record }"> |
|||
<div class="flex-row-center-start"> |
|||
<Popconfirm placement="topRight" :icon="'分配'" @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 style="width: 88px;margin-right: 12px;" type="primary" danger>分配红娘</Button> |
|||
</Popconfirm> |
|||
<Button style="width: 88px;margin-right: 12px;" @click.stop="openContract(record)">查看合同</Button> |
|||
<Button style="width: 88px;margin-right: 12px;" danger @click.stop="handleSign(record)" v-if="!record.inProgressTaskId">续签</Button> |
|||
<Button style="width: 88px;" type="primary" @click.stop="handleCustomer(record)">编辑信息</Button> |
|||
</div> |
|||
</template> |
|||
</BasicTable> |
|||
|
|||
<SignModal @register="signModal" @success="handleSuccess" /> |
|||
<ContractModal @register="contractModal" @success="handleSuccess" /> |
|||
<CustomerModal @register="customerModal" @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, onMounted } from 'vue' |
|||
import { Avatar, Card, Button, ImagePreviewGroup, Image, Tag, Popconfirm, Checkbox} from 'ant-design-vue' |
|||
import { useRouter } from 'vue-router' |
|||
import { useMessage } from '/@/hooks/web/useMessage' |
|||
import { tableColumns, tableFormSchema } from './data' |
|||
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
|||
import { useModal } from '/@/components/Modal' |
|||
import SignModal from '/@/views/market/components/modal.vue' |
|||
import ContractModal from './contract.vue' |
|||
import CustomerModal from './modal.vue' |
|||
|
|||
import { getClientList, allocationTask } from '/@/api/clue' |
|||
import { pageOrganizationMember } from '/@/api/staff/staff' |
|||
|
|||
const memberList = ref<any[]>([]); |
|||
const verifier = ref<any>('') |
|||
onMounted(async () => { |
|||
const result = await pageOrganizationMember({}) |
|||
memberList.value = result.items |
|||
}) |
|||
|
|||
const [registerTable, { reload, setPagination }] = useTable({ |
|||
bordered: false, |
|||
useSearchForm: true, |
|||
columns: tableColumns, |
|||
showIndexColumn: false, |
|||
showTableSetting: false, |
|||
api: getClientList, |
|||
formConfig: { |
|||
labelWidth: 120, |
|||
schemas: tableFormSchema, |
|||
}, |
|||
beforeFetch: (arg) => { |
|||
const { createTime } = arg |
|||
if (createTime) { |
|||
arg.signDateFrom = moment(createTime[0]).format('YYYY-MM-DD 00:00:00') |
|||
arg.signDateTo = moment(createTime[1]).format('YYYY-MM-DD 23:59:59') |
|||
delete arg.createTime |
|||
} |
|||
arg.status = radioVal.value |
|||
}, |
|||
actionColumn: { |
|||
width: 400, |
|||
title: '操作', |
|||
fixed: 'right', |
|||
dataIndex: 'action', |
|||
slots: { customRender: 'action' }, |
|||
}, |
|||
}) |
|||
|
|||
const followStatusList = [ |
|||
{ label: '进行中', value: 1 }, |
|||
{ label: '生效中', value: 2 }, |
|||
{ label: '已终止', value: 3 }, |
|||
{ label: '全部', value: '' }, |
|||
] |
|||
const radioVal = ref<any>('') |
|||
function handleRadioChange() { |
|||
setPagination({ current: 1 }) |
|||
reload() |
|||
} |
|||
|
|||
function onCheckChange(e, item){ |
|||
if(e.target.checked){ |
|||
verifier.value = item.userId |
|||
} |
|||
} |
|||
const { createMessage } = useMessage() |
|||
async function allocateList(id = null){ |
|||
if(!verifier.value){ |
|||
createMessage.warning('请选择分配人') |
|||
return |
|||
} |
|||
try { |
|||
await allocationTask({serviceMatchmaker: verifier.value, id}) |
|||
createMessage.success(`分配成功`) |
|||
} finally { |
|||
reload() |
|||
} |
|||
} |
|||
|
|||
const [signModal, { openModal: openSignModal, closeModal: closeSignModal }] = useModal() |
|||
const [contractModal, { openModal: openContractModal, closeModal: closeContractModal }] = useModal() |
|||
const [customerModal, { openModal: openCustomerModal, closeModal: closeCustomerModal }] = useModal() |
|||
|
|||
function handleSign(record: any) { |
|||
openSignModal(true, {record, ifUpdate: true}) |
|||
} |
|||
function openContract(record: any) { |
|||
openContractModal(true, {record}) |
|||
} |
|||
function handleCustomer(record: any) { |
|||
openCustomerModal(true, {record}) |
|||
} |
|||
// 操作成功 |
|||
function handleSuccess() { |
|||
closeSignModal() |
|||
closeContractModal() |
|||
closeCustomerModal() |
|||
reload() |
|||
} |
|||
</script> |
|||
<style scoped lang="less"> |
|||
.single-line { |
|||
overflow: hidden; /* 超出部分隐藏 */ |
|||
white-space: nowrap; /* 文本不换行 */ |
|||
text-overflow: ellipsis; /* 超出部分显示省略号 */ |
|||
} |
|||
</style> |
|||
@ -0,0 +1,203 @@ |
|||
<template> |
|||
<BasicModal v-bind="$attrs" :width="960" @ok="handleOk" @register="registerModal"> |
|||
<Tabs tab-position="left" v-model:activeKey="activeKey2" :animated="false" @change="onInfoChange"> |
|||
<TabPane key="1" tab="个人资料"> |
|||
<BasicForm @register="registerForm" style="padding: 16px 48px 0px 0px;" > |
|||
<!-- <template #birthYear="{ model, field }"> |
|||
<DatePicker v-model:value="model[field]" mode="year" :open="open" format="YYYY" @openChange="openChange" @panelChange="panelChange"/> |
|||
</template> --> |
|||
<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 #weight="{ model }"> |
|||
<div class="flex-row-center-start"> |
|||
<InputNumber placeholder="请输入" :min="0" v-model:value="model['minWeight']" /> |
|||
<span style="margin: 0 5px">-</span> |
|||
<InputNumber placeholder="请输入" :min="0" v-model:value="model['maxWeight']" /> |
|||
</div> |
|||
</template> |
|||
<template #income="{ model }"> |
|||
<div class="flex-row-center-start"> |
|||
<InputNumber placeholder="请输入" :min="0" v-model:value="model['minimumAnnualIncome']" /> |
|||
<span style="margin: 0 5px">-</span> |
|||
<InputNumber placeholder="请输入" :min="0" v-model:value="model['maximumAnnualIncome']" /> |
|||
</div> |
|||
</template> |
|||
</BasicForm> |
|||
</TabPane> |
|||
</Tabs> |
|||
</BasicModal> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import moment from 'moment' |
|||
import { ref, unref, nextTick } from 'vue' |
|||
import dayjs, { Dayjs } from 'dayjs' |
|||
import { Tabs, TabPane, Button, Result, InputNumber, DatePicker } from 'ant-design-vue' |
|||
import Icon from '/@/components/Icon' |
|||
import { modalFormSchema, basicInfoData, demandMarriageSchema, demandMarriageMoreData } 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 { getDemandInfo, getCustomerInfo, editCustomerInfo, editDemandInfo } from '/@/api/clue' |
|||
import { formatToDate } from '/@/utils/dateUtil' |
|||
import { isEmpty } from '/@/utils/is' |
|||
|
|||
|
|||
const activeKey2 = ref<string>('1') |
|||
|
|||
const [registerForm, { setFieldsValue, resetFields, validate, getFieldsValue }] = |
|||
useForm({ |
|||
labelWidth: 108, |
|||
schemas: modalFormSchema, |
|||
baseColProps: { span: 22 }, |
|||
showActionButtonGroup: false, |
|||
}) |
|||
|
|||
const id = ref<string>('') |
|||
const details = ref<any>() |
|||
const demand = ref<any>() |
|||
const [registerModal, { setModalProps }] = useModalInner(async (data) => { |
|||
const { record } = data |
|||
var title :string = '' |
|||
id.value = record?.id |
|||
await resetFields() |
|||
|
|||
if(record && record.id){ |
|||
title = '编辑客户信息' |
|||
details.value = await getCustomerInfo({id: record.id}) |
|||
const { homeProvinceCode, homeCityCode, homeDistrictCode, birthYear, birthMonth, birthDay, nationCode } = details.value || {} |
|||
const homeAddress = homeDistrictCode ? [homeProvinceCode, homeCityCode, homeDistrictCode] : [] |
|||
const birthDate = moment(`${birthYear}-${birthMonth}-${birthDay}`) |
|||
|
|||
const { homeProvinceName, homeCityName, homeDistrictName } = details.value || {} |
|||
if(homeDistrictName){ |
|||
basicInfoData.value.homeProvinceName = homeProvinceName |
|||
basicInfoData.value.homeCityName = homeCityName |
|||
basicInfoData.value.homeDistrictName =homeDistrictName |
|||
} |
|||
|
|||
await nextTick() |
|||
await setFieldsValue({ ...details.value, homeAddress, birthDate, nationCode: `${nationCode}` }) |
|||
activeKey2.value = '1' |
|||
} |
|||
setModalProps({ minHeight: 50, confirmLoading: false, title }) |
|||
}) |
|||
|
|||
// 择偶标准表单配置 |
|||
const [registerForm3, { getFieldsValue: getFieldsValue3, setFieldsValue: setFieldsValue3, resetFields: resetFields3 }] = |
|||
useForm({ |
|||
labelWidth: 160, |
|||
schemas: demandMarriageSchema, |
|||
baseColProps: { span: 22 }, |
|||
showActionButtonGroup: false, |
|||
}) |
|||
|
|||
function uploadAvatarAfter(value) { |
|||
//修改表单的值 |
|||
setFieldsValue({ profilePhoto: value }); |
|||
} |
|||
|
|||
async function onInfoChange(){ |
|||
if(activeKey2.value == '2' && (!demand.value || !demand.value.id)){ |
|||
await nextTick() |
|||
await resetFields3() |
|||
demand.value = await getDemandInfo({datingStoreCustomerId: id.value}) |
|||
const { isDivorceAccepted, isChildAccepted, customerAreaDemandList } = demand.value || {} |
|||
var cityList: any = [] |
|||
if(customerAreaDemandList && customerAreaDemandList.length){ |
|||
const provinceCode = customerAreaDemandList[0].provinceCode |
|||
const provinceName = customerAreaDemandList[0].provinceName |
|||
const cityCode = customerAreaDemandList[0].cityCode |
|||
const cityName = customerAreaDemandList[0].cityName |
|||
cityList = [provinceCode, cityCode] |
|||
demandMarriageMoreData.value.provinceName = provinceName |
|||
demandMarriageMoreData.value.cityName = cityName |
|||
} |
|||
if(!isEmpty(isDivorceAccepted)){ |
|||
demand.value.isDivorceAccepted = isDivorceAccepted ? 1 : 0 |
|||
} |
|||
if(!isEmpty(isChildAccepted)){ |
|||
demand.value.isChildAccepted = isChildAccepted ? 1 : 0 |
|||
} |
|||
|
|||
await setFieldsValue3({ ...demand.value, cityList}) |
|||
} |
|||
} |
|||
|
|||
const { createMessage } = useMessage() |
|||
const emits = defineEmits(['success']) |
|||
async function handleOk() { |
|||
try { |
|||
await validate() |
|||
const values: any = getFieldsValue() |
|||
// console.log({...values, ...basicInfoData.value}) |
|||
const { birthDate, homeAddress } = values |
|||
setModalProps({ confirmLoading: true }) |
|||
const birth = dayjs(birthDate) |
|||
values.birthYear = birth.year() |
|||
values.birthMonth = birth.month() + 1 |
|||
values.birthDay = birth.date() |
|||
const param = { |
|||
...values, |
|||
homeCityCode: homeAddress?.[1], |
|||
homeCityName: basicInfoData.value.homeCityName, |
|||
homeProvinceCode: homeAddress?.[0], |
|||
homeProvinceName: basicInfoData.value.homeProvinceName, |
|||
homeDistrictCode: homeAddress?.[2], |
|||
homeDistrictName: basicInfoData.value.homeDistrictName, |
|||
} |
|||
param.id = details.value.id |
|||
console.log(param) |
|||
|
|||
const demandInfo: any = getFieldsValue3() |
|||
if(demandInfo){ |
|||
demandInfo.datingStoreCustomerId = details.value.id |
|||
const { cityList } = demandInfo |
|||
if(cityList && cityList.length){ |
|||
const provinceName = demandMarriageMoreData.value.provinceName |
|||
const cityName = demandMarriageMoreData.value.cityName |
|||
demandInfo.customerAreaDemandList = [{provinceCode: cityList[0], provinceName, cityCode: cityList[1], cityName}] |
|||
} |
|||
} |
|||
if(details.value && details.value.id){ |
|||
await editCustomerInfo(param) |
|||
await editDemandInfo(demandInfo) |
|||
createMessage.success(`编辑成功!`) |
|||
} else { |
|||
// await createClueRecord(param) |
|||
createMessage.success(`编辑成功!`) |
|||
} |
|||
emits('success') |
|||
} finally { |
|||
setModalProps({ confirmLoading: false }) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="less"> |
|||
::v-deep .ant-input-number { |
|||
min-width: 60px; |
|||
width: 100% !important; |
|||
max-width: 100%; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,74 @@ |
|||
import { FormSchema } from '/@/components/Form' |
|||
import { genderList } from '/@/enums/customerEnum' |
|||
|
|||
export const modalFormSchema: FormSchema[] = [ |
|||
{ |
|||
field: 'phone', |
|||
label: '客户电话', |
|||
colProps: { span: 12 }, |
|||
component: 'Input', |
|||
required: true, |
|||
}, |
|||
{ |
|||
field: 'genderCode', |
|||
label: '客户性别', |
|||
required: true, |
|||
component: 'Select', |
|||
colProps: { span: 12 }, |
|||
componentProps: { |
|||
options: genderList, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'name', |
|||
label: '客户姓名', |
|||
component: 'Input', |
|||
required: true, |
|||
colProps: { span: 12 }, |
|||
}, |
|||
{ |
|||
field: 'identityCard', |
|||
label: '身份证号', |
|||
component: 'Input', |
|||
required: true, |
|||
colProps: { span: 12 }, |
|||
}, |
|||
{ |
|||
field: 'amount', |
|||
label: '服务金额', |
|||
component: 'Input', |
|||
required: true, |
|||
colProps: { span: 12 }, |
|||
}, |
|||
{ |
|||
field: 'numberOfServices', |
|||
label: '服务次数', |
|||
component: 'Input', |
|||
required: true, |
|||
colProps: { span: 12 }, |
|||
}, |
|||
{ |
|||
label: '结束时间', |
|||
component: 'DatePicker', |
|||
field: 'serviceEndDate', |
|||
required: true, |
|||
colProps: { span: 12 }, |
|||
componentProps: { |
|||
style: { width: '100%' }, |
|||
}, |
|||
}, |
|||
{ |
|||
label: '合同凭证', |
|||
required: true, |
|||
field: 'imageList1', |
|||
component: 'Upload', |
|||
slot: 'imageList1', |
|||
}, |
|||
{ |
|||
label: '付款凭证', |
|||
required: true, |
|||
field: 'imageList2', |
|||
component: 'Upload', |
|||
slot: 'imageList2', |
|||
}, |
|||
] |
|||
@ -0,0 +1,95 @@ |
|||
<template> |
|||
<BasicModal v-bind="$attrs" :width="720" @ok="handleOk" @register="registerModal"> |
|||
<BasicForm @register="registerForm"> |
|||
<template #imageList1="{ model, field }"> |
|||
<OssUpload v-model="model[field]" /> |
|||
</template> |
|||
<template #imageList2="{ model, field }"> |
|||
<OssUpload v-model="model[field]" /> |
|||
</template> |
|||
</BasicForm> |
|||
</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 { OssUpload } from '/@/components/OssUpload' |
|||
import { signCustomer, resignCustomer } from '/@/api/clue' |
|||
import { formatToDate } from '/@/utils/dateUtil' |
|||
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: ifUpdate.value ? `续签` : '签约', |
|||
confirmLoading: false, |
|||
}) |
|||
if (!!ifUpdate.value) { |
|||
await setFieldsValue({ ...record }) |
|||
} |
|||
}) |
|||
|
|||
const { createMessage } = useMessage() |
|||
const emits = defineEmits(['success']) |
|||
async function handleOk() { |
|||
try { |
|||
const values = await validate() |
|||
const {name, phone, genderCode, identityCard, amount, numberOfServices, serviceEndDate} = values |
|||
var data: any = {name, phone, genderCode, identityCard, amount, numberOfServices} |
|||
var imageList1: any = [] |
|||
var imageList2: any = [] |
|||
Object.keys(values).forEach((key) => { |
|||
const val = values[key] |
|||
switch (key) { |
|||
case 'imageList1': |
|||
imageList1 = val?.map?.((item: any) => { |
|||
const { url, response, originFileObj } = item |
|||
const path = typeof item === 'string' ? item : url || item?.photoUrl || response?.url || originFileObj?.url |
|||
return {url: path, type: 1} |
|||
}) |
|||
break |
|||
case 'imageList2': |
|||
imageList2 = val?.map?.((item: any) => { |
|||
const { url, response, originFileObj } = item |
|||
return typeof item === 'string' ? item : url || item?.photoUrl || response?.url || originFileObj?.url |
|||
}) |
|||
break |
|||
} |
|||
}) |
|||
data.contractImageList = imageList1 |
|||
data.paymentVoucherImageList = imageList2 |
|||
|
|||
data.serviceEndDate = formatToDate(serviceEndDate) |
|||
|
|||
// console.log('values.............', values) |
|||
setModalProps({ confirmLoading: true }) |
|||
if (!!ifUpdate.value) { |
|||
data.datingStoreCustomerId = id.value |
|||
await resignCustomer(data) |
|||
createMessage.success(`续签成功!`) |
|||
} else { |
|||
data.datingStoreAppointmentId = id.value |
|||
await signCustomer(data) |
|||
createMessage.success(`签约成功!`) |
|||
} |
|||
emits('success') |
|||
} finally { |
|||
setModalProps({ confirmLoading: false }) |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,78 @@ |
|||
import { BasicColumn, FormSchema } from '/@/components/Table' |
|||
|
|||
export const contactStatusList = [ |
|||
{ label: '进行中', value: 1 }, |
|||
{ label: '已交付', value: 2 }, |
|||
{ label: '已终止', value: 3 }, |
|||
] |
|||
|
|||
export const tableColumns: BasicColumn[] = [ |
|||
{ title: '用户信息', dataIndex: 'userInfo', slots: { customRender: 'userInfo' } }, |
|||
{ width: 120, title: '手机号码', dataIndex: 'phone' }, |
|||
{ width: 120, title: '合同开始日期', dataIndex: 'contractStartDate' }, |
|||
{ width: 120, title: '合同结束日期', dataIndex: 'contractEndDate' }, |
|||
{ width: 120, title: '任务状态', dataIndex: 'status', |
|||
customRender: ({ text }) => { |
|||
return contactStatusList.find((find) => find.value === text)?.label |
|||
}, |
|||
}, |
|||
{ width: 120, title: '服务红娘', dataIndex: 'serviceMatchmakerName' }, |
|||
{ width: 100, title: '服务次数', dataIndex: 'numberOfServices' }, |
|||
{ width: 100, title: '剩余次数', dataIndex: 'numberOfRemaining' }, |
|||
{ title: '任务交付凭证', dataIndex: 'deliveryVoucherList', slots: { customRender: 'deliveryVoucherList' } }, |
|||
|
|||
] |
|||
|
|||
export const tableFormSchema: FormSchema[] = [ |
|||
{ |
|||
field: 'name', |
|||
label: '客户姓名', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'phone', |
|||
label: '电话', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'serviceMatchmakerName', |
|||
label: '服务红娘', |
|||
component: 'Input', |
|||
colProps: { span: 6 }, |
|||
}, |
|||
] |
|||
|
|||
export const modalFormSchema: FormSchema[] = [ |
|||
{ |
|||
field: 'status', |
|||
label: '上报结果', |
|||
required: true, |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: [ |
|||
{ label: '已交付', value: 2 }, |
|||
{ label: '已终止', value: 3 }, |
|||
], |
|||
}, |
|||
}, |
|||
{ |
|||
label: '处理凭证', |
|||
required: true, |
|||
field: 'imageList', |
|||
component: 'Upload', |
|||
slot: 'imageList', |
|||
}, |
|||
{ |
|||
field: 'remark', |
|||
label: '备注', |
|||
required: false, |
|||
component: 'InputTextArea', |
|||
componentProps: { |
|||
autosize: { |
|||
minRows: 4, |
|||
}, |
|||
}, |
|||
}, |
|||
] |
|||
@ -0,0 +1,171 @@ |
|||
<template> |
|||
<div class="order-list"> |
|||
<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 followStatusList" :key="item.value" :value="item.value">{{item.label}}</RadioButton> |
|||
</RadioGroup> |
|||
</div> |
|||
</template> |
|||
<template #userInfo="{ text, record }"> |
|||
<div class="flex-row-center-start" style="padding-left: 12px;"> |
|||
<Avatar :src="record.profilePhoto || 'https://dating-agency-prod.oss-cn-shenzhen.aliyuncs.com/827036501B11.png'" :size="36" /> |
|||
<div class="flex-col" style="margin-left: 12px;"> |
|||
<div class="flex-row"> |
|||
<span class="single-line" style="font-size: 14px;color: #333;font-weight: bold;max-width: 160px;">{{record.name}}</span> |
|||
</div> |
|||
<div class="flex-row"> |
|||
<span class="single-line" style="font-size: 13px;color: #333;">{{record.genderCode == 1 ? '女' : '男'}}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<template #deliveryVoucherList="{ text }"> |
|||
<ImagePreviewGroup v-if="text && text.length"> |
|||
<Image |
|||
v-for="(item, index) in text" |
|||
style="width: 40px; height: 40px; border-radius: 5px; margin: 0 5px 5px 0" |
|||
:key="index" |
|||
:src="item" |
|||
/> |
|||
</ImagePreviewGroup> |
|||
</template> |
|||
<template #action="{ record }"> |
|||
<div class="flex-row-center-start"> |
|||
<Popconfirm placement="topRight" :icon="'分配'" @confirm="allocateList(record.datingStoreCustomerId)" v-if="record.status == 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 style="width: 72px;margin-right: 12px;" type="primary" danger>分配</Button> |
|||
</Popconfirm> |
|||
<Button style="width: 96px;margin-right: 12px;" @click.stop="handleReport(record, 1)" v-if="record.status == 1 && record.numberOfRemaining > 0">上报任务</Button> |
|||
<Button style="width: 96px;" type="primary" @click.stop="handleReport(record, 2)" v-if="record.status == 1">结束任务</Button> |
|||
</div> |
|||
</template> |
|||
</BasicTable> |
|||
|
|||
<ReportModal @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, onMounted } from 'vue' |
|||
import { Avatar, Card, Button, ImagePreviewGroup, Image, Popconfirm, Checkbox } from 'ant-design-vue' |
|||
import { useRouter } from 'vue-router' |
|||
import { useMessage } from '/@/hooks/web/useMessage' |
|||
import { useModal } from '/@/components/Modal' |
|||
import { tableColumns, tableFormSchema } from './data' |
|||
import ReportModal from './modal.vue' |
|||
import { BasicTable, useTable, TableAction } from '/@/components/Table' |
|||
import { getTaskList, allocationTask } from '/@/api/clue' |
|||
import { pageOrganizationMember } from '/@/api/staff/staff' |
|||
|
|||
const memberList = ref<any[]>([]); |
|||
const verifier = ref<any>('') |
|||
onMounted(async () => { |
|||
const result = await pageOrganizationMember({}) |
|||
memberList.value = result.items |
|||
}) |
|||
|
|||
const [registerTable, { reload, setPagination }] = useTable({ |
|||
bordered: false, |
|||
useSearchForm: true, |
|||
columns: tableColumns, |
|||
showIndexColumn: false, |
|||
showTableSetting: false, |
|||
api: getTaskList, |
|||
formConfig: { |
|||
labelWidth: 120, |
|||
schemas: tableFormSchema, |
|||
}, |
|||
beforeFetch: (arg) => { |
|||
const { createTime } = arg |
|||
if (createTime) { |
|||
arg.signDateFrom = moment(createTime[0]).format('YYYY-MM-DD 00:00:00') |
|||
arg.signDateTo = moment(createTime[1]).format('YYYY-MM-DD 23:59:59') |
|||
delete arg.createTime |
|||
} |
|||
arg.status = radioVal.value |
|||
}, |
|||
actionColumn: { |
|||
width: 300, |
|||
title: '操作', |
|||
fixed: 'right', |
|||
dataIndex: 'action', |
|||
slots: { customRender: 'action' }, |
|||
}, |
|||
}) |
|||
|
|||
const followStatusList = [ |
|||
{ label: '进行中', value: 1 }, |
|||
{ label: '生效中', value: 2 }, |
|||
{ label: '已终止', value: 3 }, |
|||
{ label: '全部', value: '' }, |
|||
] |
|||
const radioVal = ref<any>('') |
|||
function handleRadioChange() { |
|||
setPagination({ current: 1 }) |
|||
reload() |
|||
} |
|||
|
|||
function onCheckChange(e, item){ |
|||
if(e.target.checked){ |
|||
verifier.value = item.userId |
|||
} |
|||
} |
|||
const { createMessage } = useMessage() |
|||
async function allocateList(id = null){ |
|||
if(!verifier.value){ |
|||
createMessage.warning('请选择分配人') |
|||
return |
|||
} |
|||
try { |
|||
await allocationTask({serviceMatchmaker: verifier.value, id}) |
|||
createMessage.success(`分配成功`) |
|||
} finally { |
|||
reload() |
|||
} |
|||
} |
|||
|
|||
const [registerModal, { openModal, closeModal }] = useModal() |
|||
function handleReport(record: any, type = 1) { |
|||
record.type = type |
|||
openModal(true, {record}) |
|||
} |
|||
// 操作成功 |
|||
function handleSuccess() { |
|||
closeModal() |
|||
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,89 @@ |
|||
<template> |
|||
<BasicModal v-bind="$attrs" @ok="handleOk" @register="registerModal"> |
|||
<BasicForm @register="registerForm"> |
|||
<template #imageList="{ model, field }"> |
|||
<OssUpload v-model="model[field]" /> |
|||
</template> |
|||
</BasicForm> |
|||
</BasicModal> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue' |
|||
import { modalFormSchema } from './data' |
|||
import { useMessage } from '/@/hooks/web/useMessage' |
|||
import { OssUpload } from '/@/components/OssUpload' |
|||
import { BasicForm, useForm } from '/@/components/Form' |
|||
import { BasicModal, useModalInner } from '/@/components/Modal' |
|||
import { reportTask, finishTask, getTaskItems } from '/@/api/clue' |
|||
|
|||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({ |
|||
labelWidth: 100, |
|||
schemas: modalFormSchema, |
|||
baseColProps: { span: 22 }, |
|||
showActionButtonGroup: false, |
|||
}) |
|||
|
|||
const id = ref<string>('') |
|||
const taskId = ref<string>('') |
|||
const type = ref<any>(1) |
|||
// const ifUpdate = ref<boolean>(false) |
|||
const [registerModal, { setModalProps }] = useModalInner(async (data) => { |
|||
const { record } = data |
|||
await resetFields() |
|||
id.value = record?.id |
|||
type.value = record?.type |
|||
// ifUpdate.value = !!data.ifUpdate |
|||
setModalProps({ |
|||
minHeight: 50, |
|||
title: type.value === 1 ? `上报任务` : '结束任务', |
|||
confirmLoading: false, |
|||
}) |
|||
|
|||
if (type.value === 1) { |
|||
const taskList = await getTaskItems({datingStoreCustomerTaskId: record?.id}) |
|||
taskId.value = taskList.find((item) => !item.isDelivered).id |
|||
console.log(taskId.value) |
|||
await updateSchema({ field: 'status', ifShow: false }) |
|||
await updateSchema({ field: 'remark', ifShow: true }) |
|||
} else { |
|||
await updateSchema({ field: 'status', ifShow: true }) |
|||
await updateSchema({ field: 'remark', ifShow: false }) |
|||
} |
|||
}) |
|||
|
|||
const { createMessage } = useMessage() |
|||
const emits = defineEmits(['success']) |
|||
async function handleOk() { |
|||
try { |
|||
const values = await validate() |
|||
values.id = type.value === 1 ? taskId.value : id.value |
|||
var imageList: any = [] |
|||
Object.keys(values).forEach((key) => { |
|||
const val = values[key] |
|||
switch (key) { |
|||
case 'imageList': |
|||
imageList = val?.map?.((item: any) => { |
|||
const { url, response, originFileObj } = item |
|||
return typeof item === 'string' ? item : url || item?.photoUrl || response?.url || originFileObj?.url |
|||
}) |
|||
break |
|||
} |
|||
}) |
|||
values.deliveryVoucherList = imageList |
|||
|
|||
// console.log('values.............', values) |
|||
setModalProps({ confirmLoading: true }) |
|||
if (type.value === 1) { |
|||
await reportTask(values) |
|||
createMessage.success(`上报成功!`) |
|||
} else { |
|||
await finishTask(values) |
|||
createMessage.success(`上报已结束!`) |
|||
} |
|||
emits('success') |
|||
} finally { |
|||
setModalProps({ confirmLoading: false }) |
|||
} |
|||
} |
|||
</script> |
|||
Write
Preview
Loading…
Cancel
Save