Browse Source

no message

master
xpz2018 8 months ago
parent
commit
7a9ea254d2
20 changed files with 1774 additions and 10 deletions
  1. 50
      src/api/clue/index.ts
  2. 22
      src/enums/customerEnum.ts
  3. 5
      src/locales/lang/zh-CN/routes/invite.ts
  4. 26
      src/router/menu.ts
  5. 2
      src/views/clue/customer/modal.vue
  6. 2
      src/views/invite/myList/data.ts
  7. 17
      src/views/invite/myList/index.vue
  8. 77
      src/views/market/appointment/data.ts
  9. 163
      src/views/market/appointment/index.vue
  10. 68
      src/views/market/appointment/modal.vue
  11. 90
      src/views/market/client/contract.vue
  12. 376
      src/views/market/client/data.ts
  13. 171
      src/views/market/client/index.vue
  14. 203
      src/views/market/client/modal.vue
  15. 74
      src/views/market/components/data.ts
  16. 95
      src/views/market/components/modal.vue
  17. 5
      src/views/market/contract/index.vue
  18. 78
      src/views/market/task/data.ts
  19. 171
      src/views/market/task/index.vue
  20. 89
      src/views/market/task/modal.vue

50
src/api/clue/index.ts

@ -87,4 +87,52 @@ export const getContractList = (params: any) =>
defHttp.get<PageResultModel<any>>({
url: '/dating-agency-mall/user/page/customized-service-contract/by/dating-store-team',
params,
})
})
export const getTaskList = (params: any) =>
defHttp.get<PageResultModel<any>>({
url: '/dating-clue-service/user/page/dating-store-customer-task/by/dating-store',
params,
})
export const getClientList = (params: any) =>
defHttp.get<PageResultModel<any>>({
url: '/dating-clue-service/user/page/dating-store-customer/by/dating-store',
params,
})
export const allocationTask = (params: any) => defHttp.post({ url: '/dating-clue-service/user/allocate/dating-store-customer/service-matchmaker', params })
export const reportTask = (params: any) => defHttp.post({ url: '/dating-clue-service/user/delivery/dating-store-customer-task-item', params })
export const finishTask = (params: any) => defHttp.post({ url: '/dating-clue-service/user/operate/dating-store-customer-task', params })
export const getTaskItems = (params: any) => defHttp.get<any>({ url: '/dating-clue-service/user/list/dating-store-customer-task-item', params})
export const signCustomer = (params: any) => defHttp.post({ url: '/dating-clue-service/user/sign/dating-store-customer', params })
export const resignCustomer = (params: any) => defHttp.post({ url: '/dating-clue-service/user/renew/dating-store-customer', params })
export const getStoreAppointmentList = (params: any) =>
defHttp.get<PageResultModel<any>>({
url: '/dating-clue-service/user/page/dating-store-appointment/by/dating-store-team',
params,
})
export const allocationSaler = (params: any) => defHttp.post({ url: '/dating-clue-service/user/allocate/dating-store-appointment/by-dating-store', params })
export const appointmentCustomer = (params: any) => defHttp.post({ url: '/dating-clue-service/user/change/dating-store-appointment-time/by-dating-store', params })
export const remarkCustomer = (params: any) => defHttp.post({ url: '/dating-clue-service/user/create/dating-store-appointment-record', params })
export const getContractInfo = (params: any) => defHttp.get<any>({ url: '/dating-agency-mall/user/get/customized-service-contract/by/dating-store-customer', params})
export const getPaymentInfo = (params: any) => defHttp.get<any>({ url: '/dating-agency-mall/user/get/payment-order/by/dating-store-customer', params})
export const getCustomerInfo = (params: any) => defHttp.get<any>({ url: '/dating-clue-service/user/get/dating-store-customer/detail', params})
export const editCustomerInfo = (params: any) => defHttp.post({ url: '/dating-clue-service/user/edit/dating-store-customer', params })
export const getDemandInfo = (params: any) => defHttp.get<any>({ url: '/dating-clue-service/user/get/dating-store-customer-demand/detail', params})
export const editDemandInfo = (params: any) => defHttp.post({ url: '/dating-clue-service/user/edit/dating-store-customer-demand', params })

22
src/enums/customerEnum.ts

@ -105,6 +105,15 @@ export const storeAppointmentStatus = [
{ label: '未到店', value: 3 },
]
export const appointmentStatusList = [
{ label: '待到店', value: 1, color: 'gray' },
{ label: '已到店', value: 2, color: 'green' },
{ label: '未到店', value: 3, color: 'gray' },
{ label: '复邀', value: 4, color: 'green' },
{ label: '已签约', value: 5, color: 'red' },
{ label: '未签约', value: 6, color: 'gray' },
]
export const femaleAgeList = [
{ label: '20-28岁未婚', value: 1 },
@ -185,4 +194,17 @@ export const maleHeightList = [
{ label: '180cm-184cm', value: 4 },
{ label: '185cm-194cm', value: 5 },
{ label: '低于160cm,或高于195cm,或者有肥胖症的,或对生理、身体相关有特殊要求的', value: 6 },
]
export const contractStatusList = [
{ label: '未开始', value: 1 },
{ label: '生效中', value: 2 },
{ label: '已结束', value: 3 },
{ label: '已废弃', value: 4 },
]
export const paymentInfoList = [
{ label: '微信小程序付款', value: 1 },
{ label: '微信小程序H5', value: 2 },
{ label: '线下付款', value: 3 },
]

5
src/locales/lang/zh-CN/routes/invite.ts

@ -5,5 +5,8 @@ export default {
myList: '我的线索',
seasList: '公海线索',
market: '运营管理',
contract: '合同列表'
appointment: '预约列表',
contract: '合同列表',
task: '任务列表',
client: '客户列表'
}

26
src/router/menu.ts

@ -130,7 +130,7 @@ const marketMenu: Menu = {
path: '/market',
name: 'Market',
component: 'LAYOUT',
redirect: '/market/contract',
redirect: '/market/appointment',
meta: {
orderNo: 90005,
hideChildrenInMenu: false,
@ -138,6 +138,22 @@ const marketMenu: Menu = {
title: 'routes.invite.market',
},
children: [
{
path: 'appointment',
name: 'Appointment',
component: '/market/appointment/index.vue',
meta: {
title: 'routes.invite.appointment',
},
},
{
path: 'task',
name: 'Task',
component: '/market/task/index.vue',
meta: {
title: 'routes.invite.task',
},
},
{
path: 'contract',
name: 'Contract',
@ -146,6 +162,14 @@ const marketMenu: Menu = {
title: 'routes.invite.contract',
},
},
{
path: 'client',
name: 'Client',
component: '/market/client/index.vue',
meta: {
title: 'routes.invite.client',
},
},
],
}

2
src/views/clue/customer/modal.vue

@ -54,7 +54,7 @@
} else {
values.datingClueId = datingClueId.value
await createAppointment(values)
createMessage.success(`新增成功!`)
createMessage.success(`邀约成功!`)
}
emits('success')
} finally {

2
src/views/invite/myList/data.ts

@ -12,6 +12,8 @@ export const tableColumns: BasicColumn[] = [
// },
// },
{ title: '分配信息', dataIndex: 'allocateInfo', slots: { customRender: 'allocateInfo' } },
{ title: '跟进进度', dataIndex: 'datingStoreAppointmentStatus', slots: { customRender: 'datingStoreAppointmentStatus' } },
// { width: 80, title: '状态', dataIndex: 'validStatus',
// customRender: ({ text }) => {
// return clueStatusList.find((find) => find.value === text)?.label

17
src/views/invite/myList/index.vue

@ -21,14 +21,20 @@
<template #allocateInfo="{ text, record }">
<Timeline :list="record.datingClueFollowRecordList"/>
</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-col-center-start">
<Button type="primary" style="width: 100px;" @click="toDetail(record)">详情</Button>
<Button style="width: 100px;margin-top: 8px;" danger @click="handleReport(record)">跟进</Button>
<Button type="primary" style="width: 100px;margin-top: 8px;" danger @click="handleAppointment({datingClueId: record.id})">邀约进店</Button>
</div>
</template>
</BasicTable>
<FollowModal @register="registerModal" @success="handleSuccess" />
<!-- <SignModal @register="signModal" @success="handleSuccess" /> -->
<AppointmentModal @register="appointmentModal" @success="handleSuccess" />
</div>
</template>
@ -43,16 +49,17 @@
<script setup lang="ts">
import moment from 'moment/moment'
import { computed, ref, reactive } from 'vue'
import { Avatar, Card, Button, InputNumber, Result } from 'ant-design-vue'
import { Avatar, Card, Button, InputNumber, Result, Tag } from 'ant-design-vue'
import { useRouter } from 'vue-router'
import { tableColumns, tableFormSchema } from './data'
import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { useModal } from '/@/components/Modal'
import FollowModal from '../components/modal.vue'
import AppointmentModal from '/@/views/clue/customer/modal.vue'
import Timeline from '../../components/Timeline.vue'
import Profile from '../../components/Profile.vue'
import { getMyInvitationList } from '/@/api/clue'
import { educationList, maritalList, genderList, followStageList } from '/@/enums/customerEnum'
import { appointmentStatusList } from '/@/enums/customerEnum'
const [registerTable, { reload, setPagination }] = useTable({
bordered: false,
@ -115,12 +122,18 @@
}
const [registerModal, { openModal, closeModal }] = useModal()
// modal
const [appointmentModal, { openModal: openAppointmentModal, closeModal: closeAppointmentModal }] = useModal()
function handleReport(record: any) {
openModal(true, {record})
}
function handleAppointment(record: any) {
openAppointmentModal(true, {record})
}
//
function handleSuccess() {
closeModal()
closeAppointmentModal()
reload()
}
</script>

77
src/views/market/appointment/data.ts

@ -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,
},
},
},
]

163
src/views/market/appointment/index.vue

@ -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>

68
src/views/market/appointment/modal.vue

@ -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>

90
src/views/market/client/contract.vue

@ -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>

376
src/views/market/client/data.ts

@ -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,
}
},
},
]

171
src/views/market/client/index.vue

@ -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>

203
src/views/market/client/modal.vue

@ -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>

74
src/views/market/components/data.ts

@ -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',
},
]

95
src/views/market/components/modal.vue

@ -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>

5
src/views/market/contract/index.vue

@ -38,14 +38,11 @@
<script setup lang="ts">
import moment from 'moment/moment'
import { computed, ref, reactive } from 'vue'
import { Avatar, Card, Button, InputNumber, Result } from 'ant-design-vue'
import { Avatar, Card, Button, ImagePreviewGroup, Image } from 'ant-design-vue'
import { useRouter } from 'vue-router'
import { tableColumns, tableFormSchema } from './data'
import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { getContractList } from '/@/api/clue'
import Timeline from '../../components/Timeline.vue'
import Profile from '../../components/Profile.vue'
import { educationList, maritalList, genderList, followStageList } from '/@/enums/customerEnum'
const [registerTable, { reload, setPagination }] = useTable({
bordered: false,

78
src/views/market/task/data.ts

@ -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,
},
},
},
]

171
src/views/market/task/index.vue

@ -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>

89
src/views/market/task/modal.vue

@ -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>
Loading…
Cancel
Save