Browse Source

no message

master
xpz2018 10 months ago
parent
commit
b8cee4fbc8
13 changed files with 1151 additions and 113 deletions
  1. 2
      .env
  2. 21
      src/api/clue/index.ts
  3. 94
      src/api/essentialData/index.ts
  4. 4
      src/api/essentialData/model.ts
  5. 20
      src/components/Cropper/src/CropperAvatar.vue
  6. 26
      src/layouts/default/header/index.vue
  7. 1
      src/utils/http/axios/index.ts
  8. 363
      src/views/clue/clueList/data.ts
  9. 78
      src/views/clue/clueList/index.vue
  10. 76
      src/views/clue/clueList/modal.vue
  11. 11
      src/views/clue/cluePool/index.vue
  12. 350
      src/views/clue/customer/data.ts
  13. 218
      src/views/clue/customer/index.vue

2
.env

@ -1,5 +1,5 @@
# port # port
VITE_PORT = 3100
VITE_PORT = 3200
# spa-title # spa-title
VITE_GLOB_APP_TITLE = 招亲宝管理后台 VITE_GLOB_APP_TITLE = 招亲宝管理后台

21
src/api/clue/index.ts

@ -1,20 +1,17 @@
import { defHttp } from '/@/utils/http/axios' import { defHttp } from '/@/utils/http/axios'
import { PageResultModel } from '/@/api/model/baseModel' import { PageResultModel } from '/@/api/model/baseModel'
export const getChatMessagePage = (params: any) =>
export const getCluePage = (params: any) =>
defHttp.get<PageResultModel<any>>({ defHttp.get<PageResultModel<any>>({
url: '/dating-agency-cim/user/page/room-content-abstract/by/operator',
url: '/dating-clue-service/user/page/dating-clue-pool',
params, params,
}) })
export const getChatMessageRoomList = (params: any) =>
defHttp.get<PageResultModel<any>>({
url: '/dating-agency-cim/user/page/room',
params,
})
export const createClueRecord = (params: any) => defHttp.post({ url: '/dating-clue-service/user/create/dating-clue', params })
export const editClueRecord = (params: any) => defHttp.post({ url: '/dating-clue-service/user/edit/dating-clue', params })
export const allocateCluing = (params: any) => defHttp.post({ url: '/dating-clue-service/user/batch-allocate/dating-clue', params })
export const getClueInfo = (id: string) => defHttp.get<any>({ url: '/dating-clue-service/user/get/dating-clue-detail', params: { id } })
export const getChatRoomMessageList = (params: any) =>
defHttp.get<PageResultModel<any>>({
url: '/dating-agency-cim/user/page/room-content-record/by/operator',
params,
})

94
src/api/essentialData/index.ts

@ -0,0 +1,94 @@
import { defHttp } from '/@/utils/http/axios'
import { EssentialDataModel } from '/@/api/essentialData/model'
enum Api {
GetIncomeList = '/dating-agency-service/user/get/income/list',
GetNationList = '/dating-agency-service/user/get/nation/list',
GetAccountType = '/dating-agency-service/user/get/account/type',
GetEducationList = '/dating-agency-service/user/get/education/list',
GetOccupationList = '/dating-agency-service/user/get/occupation/list',
GetBodilyFormList = '/dating-agency-service/user/get/bodily/form/list',
GetFamilyTiesList = '/dating-agency-service/user/get/family/ties/list',
GetPropertyPermits = '/dating-agency-service/user/get/property/permits',
GetNationalityList = '/dating-agency-service/user/get/nationality/list',
GetIdentityTypeList = '/dating-agency-service/user/get/identity/type/list',
GetConstellationList = '/dating-agency-service/user/get/constellation/list',
GetMaritalStatusList = '/dating-agency-service/user/get/marital/status/list',
GetCarPurchaseSituation = '/dating-agency-service/user/get/car/purchase/situation',
}
/**
*
*/
export const getEducationList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetEducationList })
/**
*
*/
export const getAccountTypeList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetAccountType })
/**
*
*/
export const getMaritalStatusList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetMaritalStatusList })
/**
*
*/
export const getConstellationList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetConstellationList })
/**
*
*/
export const getBodilyFormList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetBodilyFormList })
/**
*
*/
export const getCarPurchaseSituation = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetCarPurchaseSituation })
/**
*
*/
export const getFamilyTiesList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetFamilyTiesList })
/**
*
*/
export const getIncomeList = () => defHttp.get<EssentialDataModel[]>({ url: Api.GetIncomeList })
/**
*
*/
export const getNationList = () => defHttp.get<EssentialDataModel[]>({ url: Api.GetNationList })
/**
*
*/
export const getNationalityList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetNationalityList })
/**
*
*/
export const getIdentityTypeList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetIdentityTypeList })
/**
*
*/
export const getOccupationList = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetOccupationList })
/**
*
*/
export const getPropertyPermits = () =>
defHttp.get<EssentialDataModel[]>({ url: Api.GetPropertyPermits })

4
src/api/essentialData/model.ts

@ -0,0 +1,4 @@
export interface EssentialDataModel {
desc: string
value: number
}

20
src/components/Cropper/src/CropperAvatar.vue

@ -1,16 +1,12 @@
<template> <template>
<div :class="getClass" :style="getStyle"> <div :class="getClass" :style="getStyle">
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal"> <div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
<Icon
icon="ant-design:cloud-upload-outlined"
:size="getIconWidth"
:style="getImageWrapperStyle"
color="#d6d6d6"
/>
</div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" /> <img :src="sourceValue" v-if="sourceValue" alt="avatar" />
<img :src="avatar" v-else-if="avatar" alt="avatar" /> <img :src="avatar" v-else-if="avatar" alt="avatar" />
<div class="flex-col-center-center" :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle" v-else>
<Icon icon="ant-design:cloud-upload-outlined" :size="getIconWidth" color="#d6d6d6"/>
<div style="color:#d6d6d6;">上传头像</div>
</div>
</div> </div>
<a-button <a-button
:class="`${prefixCls}-upload-btn`" :class="`${prefixCls}-upload-btn`"
@ -77,7 +73,7 @@
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px') const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px')
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px')
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 3 + 'px')
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) })) const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }))
@ -144,13 +140,13 @@
} }
&-image-mask { &-image-mask {
opacity: 0%;
// opacity: 0%;
position: absolute; position: absolute;
width: inherit; width: inherit;
height: inherit; height: inherit;
border-radius: inherit; border-radius: inherit;
border: inherit;
background: rgb(0 0 0 / 40%);
// border: inherit;
// background: rgb(0 0 0 / 40%);
cursor: pointer; cursor: pointer;
transition: opacity 0.4s; transition: opacity 0.4s;

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

@ -35,16 +35,20 @@
<div :class="`${prefixCls}-action`"> <div :class="`${prefixCls}-action`">
<AppSearch :class="`${prefixCls}-action__item `" v-if="getShowSearch" /> <AppSearch :class="`${prefixCls}-action__item `" v-if="getShowSearch" />
<Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
<!-- <Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" /> -->
<FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
<AppLocalePicker
<!-- <FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" /> -->
<!-- <AppLocalePicker
v-if="getShowLocalePicker" v-if="getShowLocalePicker"
:reload="true" :reload="true"
:showText="false" :showText="false"
:class="`${prefixCls}-action__item`" :class="`${prefixCls}-action__item`"
/>
/> -->
<div class="flex-row-center-start" style="padding-right: 8px;">
<span style="color: #333;font-size: 20px;font-weight: bold;">{{orgName}}</span>
<span style="background: #333;height: 24px;width: 2px;margin-top: 6px;margin-left: 18px;"></span>
</div>
<UserDropDown :theme="getHeaderTheme" /> <UserDropDown :theme="getHeaderTheme" />
@ -78,6 +82,7 @@
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent' import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'
import { useLocale } from '/@/locales/useLocale' import { useLocale } from '/@/locales/useLocale'
import { ORG_ID_KEY, ORG_LIST_KEY } from '/@/enums/cacheEnum'
export default defineComponent({ export default defineComponent({
name: 'LayoutHeader', name: 'LayoutHeader',
@ -166,6 +171,16 @@
return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null
}) })
const orgName = computed(() => {
var orgId = localStorage.getItem(ORG_ID_KEY) + ''
console.log(orgId)
var orgString = localStorage.getItem(ORG_LIST_KEY) + ''
var orgList = JSON.parse(orgString)
var org = orgList.find(item => item.orgId === orgId)
console.log(org)
return org.fullName
})
return { return {
prefixCls, prefixCls,
getHeaderClass, getHeaderClass,
@ -188,6 +203,7 @@
getShowSettingButton, getShowSettingButton,
getShowSetting, getShowSetting,
getShowSearch, getShowSearch,
orgName,
} }
}, },
}) })

1
src/utils/http/axios/index.ts

@ -36,6 +36,7 @@ const suffix = ifProd ? 'quzhaoqin.com' : 'qniao.cn'
const _prefixList: UrlMap = { const _prefixList: UrlMap = {
'/uec': `https://api-client-uec${urlEnv}.${suffix}`, '/uec': `https://api-client-uec${urlEnv}.${suffix}`,
'/dating-agency': `https://dating-agency-api${urlEnv}.${suffix}`, '/dating-agency': `https://dating-agency-api${urlEnv}.${suffix}`,
'/dating-clue': `https://dating-agency-api${urlEnv}.${suffix}`,
} }
/** /**

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

@ -6,14 +6,19 @@ const userTypeList = [
] ]
export const tableColumns: BasicColumn[] = [ export const tableColumns: BasicColumn[] = [
{ title: '用户昵称', dataIndex: 'nickName' },
{ title: '用户身份', dataIndex: 'userMarriageInformationType',
{ title: '用户信息', dataIndex: 'userinfo', slots: { customRender: 'userinfo' } },
{ width: 80, title: '性别', dataIndex: 'genderCode' },
{ width: 160, title: '渠道来源', dataIndex: 'channel' },
{ width: 120, title: '状态', dataIndex: 'status' },
{ width: 160, title: '手机号码', dataIndex: 'userPhone' },
{ width: 120, title: '核验人', dataIndex: 'nickName' },
{ width: 120, title: '跟进状态', dataIndex: 'userMarriageInformationType',
customRender: ({ text }) => { customRender: ({ text }) => {
return userTypeList.find((find) => find.value === text)?.label return userTypeList.find((find) => find.value === text)?.label
}, },
}, },
// { title: '客户手机号', dataIndex: 'userPhone' },
{ title: '最新消息时间', dataIndex: 'contentTime' },
{ width: 200, title: '最后跟进时间', dataIndex: 'contentTime' },
{ width: 200, title: '录入时间', dataIndex: 'contentTime' },
] ]
export const tableFormSchema: FormSchema[] = [ export const tableFormSchema: FormSchema[] = [
@ -21,6 +26,354 @@ export const tableFormSchema: FormSchema[] = [
field: 'nickName', field: 'nickName',
label: '用户昵称', label: '用户昵称',
component: 'Input', component: 'Input',
colProps: { span: 8},
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
{
field: 'nickName',
label: '用户昵称',
component: 'Input',
colProps: { span: 6},
},
]
import { ref } from 'vue'
import dayjs, { Dayjs } from 'dayjs'
import { genderList } from '/@/enums/customerEnum'
import { useAddressData } from '/@/hooks/common'
import {
getIncomeList,
getNationList,
getEducationList,
getFamilyTiesList,
getOccupationList,
getBodilyFormList,
getPropertyPermits,
getAccountTypeList,
getIdentityTypeList,
getMaritalStatusList,
getConstellationList,
getCarPurchaseSituation,
} from '/@/api/essentialData'
// 基础信息的额外数据
export const basicInfoData = ref<any>({})
// 获取地区数据
const { addressList, domicilePlaceList } = useAddressData()
// 获取职业列表
export const occupationList = ref<any>([])
getOccupationList().then((res) => {
handleOccupationList(res)
occupationList.value = res || []
})
function handleOccupationList(data: any, ifFirst = true) {
data?.forEach?.((item: any) => {
const { industry, industryCode, occupation, occupationCode, occupationList } = item
item.label = ifFirst ? industry : occupation
item.value = ifFirst ? industryCode : occupationCode
if (occupationList?.length) {
item.children = occupationList
handleOccupationList(occupationList, false)
}
})
}
export const addressName = ref<string[]>([])
// 基本信息
export const modalFormSchema: FormSchema[] = [
{
field: 'genderCode',
label: '性别',
colProps: { span: 8 },
component: 'Select',
componentProps: ({ formModel }) => {
return {
options: genderList,
disabled: !!formModel.name && formModel.name !== -1,
onChange: (_: any, v: any) => {
basicInfoData.value.genderValue = v?.label
},
}
},
},
{ field: 'nickName', label: '昵称', component: 'Input', colProps: { span: 8 }, },
{ field: 'phone', label: '电话号码', component: 'Input', colProps: { span: 8 }, },
{ field: 'weChatId', label: '微信号', component: 'Input', colProps: { span: 8 }, },
{
field: 'address',
label: '居住地',
colProps: { span: 8 },
component: 'Cascader',
componentProps: {
options: addressList,
onChange: (_: any, v: any) => {
basicInfoData.value.provinceName = v?.[0]?.label
basicInfoData.value.cityName = v?.[1]?.label
basicInfoData.value.districtName = v?.[2]?.label
},
},
},
{
field: 'domicilePlace',
label: '户口所在地',
colProps: { span: 8 },
component: 'Cascader',
componentProps: () => {
return {
options: domicilePlaceList.value,
onChange: (_: any, v: any) => {
basicInfoData.value.domicilePlaceProvinceName = v?.[0]?.label
basicInfoData.value.domicilePlaceCityName = v?.[1]?.label
},
}
},
},
{
field: 'birthDate',
label: '出生日期',
colProps: { span: 8 },
component: 'DatePicker',
componentProps: ({ formModel }) => {
return {
style: { width: '100%' },
disabled: !!formModel.name && formModel.name !== -1,
disabledDate: (current: Dayjs) => {
const date: Dayjs = dayjs().subtract(18, 'year')
return current && current > date.endOf('year')
},
}
},
},
{ field: 'height', label: '身高(cm)', component: 'InputNumber', colProps: { span: 8 }, },
{
field: 'educationCode',
label: '学历',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getEducationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.education = v?.label
},
},
},
{
field: 'incomeCode',
label: '月收入',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getIncomeList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.income = v?.label
},
},
},
// {
// field: 'hometown',
// label: '家乡',
// colProps: { span: 8 },
// component: 'Cascader',
// componentProps: () => {
// return {
// options: domicilePlaceList.value,
// onChange: (_: any, v: any) => {
// basicInfoData.value.hometownProvinceName = v?.[0]?.label
// basicInfoData.value.hometownCityName = v?.[1]?.label
// },
// }
// },
// },
{
field: 'maritalStatusCode',
label: '婚姻状况',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getMaritalStatusList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.maritalStatusName = v?.label
},
},
},
{ field: 'childrenNum', label: '孩子数量', component: 'InputNumber', colProps: { span: 8 }, },
{
field: 'nationCode',
label: '民族',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'cn',
valueField: 'id',
api: getNationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.nation = v?.label
},
},
},
// {
// field: 'bodilyFormCode',
// label: '体型',
// colProps: { span: 8 },
// component: 'ApiSelect',
// componentProps: {
// labelField: 'desc',
// api: getBodilyFormList,
// getPopupContainer: () => document.body,
// onChange: (_: any, v: any) => {
// basicInfoData.value.bodilyForm = v?.label
// },
// },
// },
// {
// field: 'accountTypeCode',
// label: '户口',
// colProps: { span: 8 },
// component: 'ApiSelect',
// componentProps: {
// labelField: 'desc',
// api: getAccountTypeList,
// getPopupContainer: () => document.body,
// onChange: (_: any, v: any) => {
// basicInfoData.value.accountTypeName = v?.label
// },
// },
// },
// {
// field: 'nativePlaceCode',
// label: '籍贯',
// colProps: { span: 8 },
// component: 'Select',
// componentProps: {
// options: domicilePlaceList,
// getPopupContainer: () => document.body,
// onChange: (_: any, v: any) => {
// basicInfoData.value.nativePlaceName = v?.label
// },
// },
// },
{
label: '职业',
field: 'occupationList',
colProps: { span: 8 },
component: 'Cascader',
componentProps: {
options: occupationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
const industry = v?.[0] || {}
const occupation = v?.[1] || {}
basicInfoData.value.industry = industry.label
basicInfoData.value.industryCode = industry.value
basicInfoData.value.occupation = occupation.label
basicInfoData.value.occupationCode = occupation.value
},
},
},
{
field: 'onlyChild',
label: '是否独生子女',
colProps: { span: 8 },
component: 'Select',
componentProps: {
options: [
{ label: '是', value: 1 },
{ label: '否', value: 0 },
],
},
},
{
field: 'propertyPermitsCode',
label: '购房情况',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getPropertyPermits,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.propertyPermits = v?.label
},
},
},
{
field: 'carPurchaseSituationCode',
label: '购车情况',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getCarPurchaseSituation,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.carPurchaseSituation = v?.label
},
},
},
{
field: 'constellationCode',
label: '星座',
colProps: { span: 8 },
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getConstellationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoData.value.constellation = v?.label
},
},
},
{
field: 'profilePhoto',
component: 'Upload',
label: '资料头像',
colProps: { span: 8 },
slot: 'profilePhoto',
},
{
field: 'describeInfo',
label: '个人描述',
component: 'InputTextArea',
colProps: { span: 16 },
componentProps: ({ formModel }) => {
return {
autoSize: {
minRows: 4,
},
disabled: formModel?.describeAudit,
}
},
}, },
] ]

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

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

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

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

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

@ -27,7 +27,7 @@
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { tableColumns, tableFormSchema } from './data' import { tableColumns, tableFormSchema } from './data'
import { BasicTable, useTable, TableAction } from '/@/components/Table' import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { getChatMessagePage } from '/@/api/clue'
import { getCluePage } from '/@/api/clue'
const [registerTable] = useTable({ const [registerTable] = useTable({
bordered: true, bordered: true,
@ -35,7 +35,7 @@
columns: tableColumns, columns: tableColumns,
showIndexColumn: true, showIndexColumn: true,
showTableSetting: false, showTableSetting: false,
api: getChatMessagePage,
api: getCluePage,
formConfig: { formConfig: {
labelWidth: 120, labelWidth: 120,
schemas: tableFormSchema, schemas: tableFormSchema,
@ -57,13 +57,12 @@
}, },
}) })
const router = useRouter() const router = useRouter()
function toDetail(record: any) { function toDetail(record: any) {
const { miId, userMarriageInformationType } = record
const { miId } = record
router.push({ router.push({
query: { miId, type: userMarriageInformationType },
path: '/message/chatInfo',
query: { miId },
path: '/clue/customer',
}) })
} }
</script> </script>

350
src/views/clue/customer/data.ts

@ -1,26 +1,340 @@
import { BasicColumn, FormSchema } from '/@/components/Table'
const userTypeList = [
{ label: '正常用户', value: 1 },
{ label: '录入用户', value: 2},
{ label: '红娘', value: 3 },
]
import { useAddressData } from '/@/hooks/common'
import { ref } from 'vue'
import dayjs, { Dayjs } from 'dayjs'
import { FormSchema } from '/@/components/Form'
import { genderList } from '/@/enums/customerEnum'
import {
getIncomeList,
getNationList,
getEducationList,
getFamilyTiesList,
getOccupationList,
getBodilyFormList,
getPropertyPermits,
getAccountTypeList,
getIdentityTypeList,
getMaritalStatusList,
getConstellationList,
getCarPurchaseSituation,
} from '/@/api/essentialData'
// 基础信息的额外数据
export const basicInfoMoreData = ref<any>({})
// 获取地区数据
const { addressList, domicilePlaceList } = useAddressData()
// 获取职业列表
export const occupationList = ref<any>([])
getOccupationList().then((res) => {
handleOccupationList(res)
occupationList.value = res || []
})
function handleOccupationList(data: any, ifFirst = true) {
data?.forEach?.((item: any) => {
const { industry, industryCode, occupation, occupationCode, occupationList } = item
item.label = ifFirst ? industry : occupation
item.value = ifFirst ? industryCode : occupationCode
if (occupationList?.length) {
item.children = occupationList
handleOccupationList(occupationList, false)
}
})
}
export const tableColumns: BasicColumn[] = [
{ title: '用户昵称', dataIndex: 'nickName' },
{ title: '用户身份', dataIndex: 'userMarriageInformationType',
customRender: ({ text }) => {
return userTypeList.find((find) => find.value === text)?.label
// 基本信息
export const basicSchema: FormSchema[] = [
{ field: 'nickName', label: '昵称', component: 'Input' },
{
field: 'address',
label: '居住地',
component: 'Cascader',
componentProps: {
options: addressList,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.provinceName = v?.[0]?.label
basicInfoMoreData.value.cityName = v?.[1]?.label
basicInfoMoreData.value.districtName = v?.[2]?.label
},
}, },
}, },
// { title: '客户手机号', dataIndex: 'userPhone' },
{ title: '最新消息时间', dataIndex: 'contentTime' },
{
field: 'domicilePlace',
label: '户口所在地',
component: 'Cascader',
componentProps: () => {
return {
options: domicilePlaceList.value,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.domicilePlaceProvinceName = v?.[0]?.label
basicInfoMoreData.value.domicilePlaceCityName = v?.[1]?.label
},
}
},
},
{
field: 'genderCode',
label: '性别',
component: 'Select',
componentProps: ({ formModel }) => {
return {
options: genderList,
disabled: !!formModel.name && formModel.name !== -1,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.genderValue = v?.label
},
}
},
},
{
field: 'birthDate',
label: '出生日期',
component: 'DatePicker',
componentProps: ({ formModel }) => {
return {
style: { width: '100%' },
disabled: !!formModel.name && formModel.name !== -1,
disabledDate: (current: Dayjs) => {
const date: Dayjs = dayjs().subtract(18, 'year')
return current && current > date.endOf('year')
},
}
},
},
{ field: 'height', label: '身高', component: 'InputNumber' },
{
field: 'educationCode',
label: '学历',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getEducationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.education = v?.label
},
},
},
{
field: 'incomeCode',
label: '月收入',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getIncomeList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.income = v?.label
},
},
},
{ field: 'name', label: '姓名', component: 'Input', componentProps: { disabled: true } },
{
field: 'hometown',
label: '家乡',
component: 'Cascader',
componentProps: () => {
return {
options: domicilePlaceList.value,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.hometownProvinceName = v?.[0]?.label
basicInfoMoreData.value.hometownCityName = v?.[1]?.label
},
}
},
},
{
field: 'identityType',
label: '身份',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getIdentityTypeList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.identityTypeName = v?.label
},
},
},
{
field: 'maritalStatusCode',
label: '婚姻状况',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getMaritalStatusList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.maritalStatusName = v?.label
},
},
},
{
field: 'nationCode',
label: '民族',
component: 'ApiSelect',
componentProps: {
labelField: 'cn',
valueField: 'id',
api: getNationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.nation = v?.label
},
},
},
{
field: 'bodilyFormCode',
label: '体型',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getBodilyFormList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.bodilyForm = v?.label
},
},
},
{
field: 'accountTypeCode',
label: '户口',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getAccountTypeList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.accountTypeName = v?.label
},
},
},
{
field: 'nativePlaceCode',
label: '籍贯',
component: 'Select',
componentProps: {
options: domicilePlaceList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.nativePlaceName = v?.label
},
},
},
{
label: '职业',
field: 'occupationList',
component: 'Cascader',
componentProps: {
options: occupationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
const industry = v?.[0] || {}
const occupation = v?.[1] || {}
basicInfoMoreData.value.industry = industry.label
basicInfoMoreData.value.industryCode = industry.value
basicInfoMoreData.value.occupation = occupation.label
basicInfoMoreData.value.occupationCode = occupation.value
},
},
},
{
field: 'onlyChild',
label: '是否独生子女',
component: 'Select',
componentProps: {
options: [
{ label: '是', value: 1 },
{ label: '否', value: 0 },
],
},
},
{
field: 'propertyPermitsCode',
label: '购房情况',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getPropertyPermits,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.propertyPermits = v?.label
},
},
},
{
field: 'carPurchaseSituationCode',
label: '购车情况',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getCarPurchaseSituation,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.carPurchaseSituation = v?.label
},
},
},
{
field: 'constellationCode',
label: '星座',
component: 'ApiSelect',
componentProps: {
labelField: 'desc',
api: getConstellationList,
getPopupContainer: () => document.body,
onChange: (_: any, v: any) => {
basicInfoMoreData.value.constellation = v?.label
},
},
},
{
field: 'describeInfo',
label: '个人描述',
component: 'InputTextArea',
colProps: { span: 24 },
componentProps: ({ formModel }) => {
return {
autoSize: {
minRows: 4,
},
disabled: formModel?.describeAudit,
}
},
},
{ field: 'empty1', label: '', slot: 'empty1', component: 'Input' },
] ]
basicSchema.forEach((schema) => {
!['photoList', 'name', 'avatar', 'empty1'].includes(schema.field) && (schema.required = true)
!schema.colProps && (schema.colProps = { span: 8 })
})
export const tableFormSchema: FormSchema[] = [
export const modalFormSchema: FormSchema[] = [
{
field: 'onlyChild',
label: '跟进方式',
component: 'Select',
componentProps: {
options: [
{ label: '是', value: 1 },
{ label: '否', value: 0 },
],
},
},
{ {
field: 'nickName',
label: '用户昵称',
required: true,
field: 'storeName',
label: '门店名称',
component: 'Input', component: 'Input',
colProps: { span: 8},
}, },
]
{
field: 'remark',
label: '备注',
required: false,
component: 'InputTextArea',
componentProps: {
autosize: {
minRows: 3,
},
},
},
]

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

@ -1,56 +1,184 @@
<template> <template>
<Card :borderd="false">
</Card>
<div class="flex-col" style="height:100%;padding: 16px;">
<div class="flex-row" style="background: white;width: 100%;padding: 10px;">
<CropperAvatar :value="avatar" width="90px" @change="uploadAvatarAfter"/>
<div class="flex-col" style="flex: 1;margin-left: 10px;">
<div class="flex-row-center-start">
<span style="font-size: 18px;font-weight: bold;">用户昵称</span>
<!-- <Icon icon="ant-design:edit-twotone" size="18px" style="margin-left: 12px;color:#0960bd;"/> -->
<Button type="link"><template #icon><Icon icon="ant-design:edit-twotone" size="16px" style="margin-left: 12px;color:#0960bd;"/></template></Button>
<span style="color: #666;margin: 0 12px;">(ID23762736 | 渠道来源抖音)</span>
<Tag color="#d5d5d5">#线索阶段-0</Tag>
</div>
<div class="flex-row-center-start" style="margin-top: 8px;">
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">电话</span>
<span style="color: #333;font-weight: bold;">18565126880</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">微信</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">客户标签</span>
<Tag color="#f50">无效</Tag>
<Button style="height: 22px;line-height: 22px;width: 24px;border: 1px solid #0960bd;"><template #icon><Icon icon="ant-design:plus-outlined" size="14px" style="color:#0960bd;display: block;"/></template></Button>
</div>
</div>
<div class="flex-row-center-start" style="margin-top: 8px;">
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">下次跟进时间</span>
<span style="color: #333;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">跟进人</span>
<span style="color: #333;">彭杰</span>
</div>
<div class="flex-row-center-end" style="flex:1;padding-right: 32px;">
<Button size="small" type="primary">查看下一个客户</Button>
</div>
</div>
</div>
</div>
<div class="flex-col" style="background: white;width: 100%;margin-top: 16px;padding: 12px 40px 12px 40px;">
<div class="flex-row-center-space">
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
</div>
<div class="flex-row-center-space" style="margin-top: 8px;">
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--</span>
</div>
<div class="flex-row-center-start" style="width: 240px;">
<span style="color: #666;">签约红娘</span>
<span style="color: #333;font-weight: bold;">--dddd</span>
</div>
</div>
</div>
<div class="flex-row" style="width: 100%;flex: 1;margin-top: 16px;">
<div style="flex: 1;background: white;padding: 0px 10px 10px 10px;">
<Tabs v-model:activeKey="activeKey" :animated="false">
<TabPane key="1" tab="客户资料">
<div class="flex-col" style="padding-right: 48px; flex:1;padding-top: 16px;">
<BasicForm @register="registerForm1"></BasicForm>
</div>
</TabPane>
<TabPane key="2" tab="邀约进店">
<Spin :spinning="spinning">
<Result subTitle="暂无数据"></Result>
</Spin>
</TabPane>
<TabPane key="3" tab="合同">
<Result subTitle="暂无数据"></Result>
</TabPane>
<TabPane key="4" tab="约见记录">
<Result subTitle="暂无数据"></Result>
</TabPane>
<template v-if="activeKey == '1'" #tabBarExtraContent>
<Button type="link"><template #icon><Icon icon="ant-design:edit-twotone" size="16px" style="margin-right: -4px;"/></template>编辑资料</Button>
</template>
</Tabs>
</div>
<div style="width: 480px;background: white;margin-left: 16px;padding: 0px 10px 10px 10px;">
<Tabs v-model:activeKey="activeKey2" :animated="false">
<TabPane key="1" tab="跟进小计">
<div class="flex-col-center-center" style="flex:1;padding-top: 16px;">
<BasicForm @register="registerForm"></BasicForm>
<Button type="primary" style="width: 160px;margin-top: 48px;">保存</Button>
</div>
</TabPane>
<TabPane key="2" tab="跟进记录" style="flex:1;padding: 16px 16px 0px 16px;">
<Timeline>
<TimelineItem color="green">Create a services site 2015-09-01</TimelineItem>
<TimelineItem color="red">
<p>Solve initial network problems 1</p>
<p>Solve initial network problems 2</p>
<p>Solve initial network problems 3 2015-09-01</p>
</TimelineItem>
<TimelineItem>
<p>Technical testing 1</p>
<p>Technical testing 3 2015-09-01</p>
</TimelineItem>
<TimelineItem color="gray">
<p>Technical testing 1</p>
<p>Technical testing 2</p>
<p>Technical testing 3 2015-09-01</p>
</TimelineItem>
<TimelineItem color="gray">
<p>Technical testing 1</p>
<p>Technical testing 3 2015-09-01</p>
</TimelineItem>
</Timeline>
</TabPane>
</Tabs>
</div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import moment from 'moment/moment'
import { computed, ref } from 'vue'
import { Card } from 'ant-design-vue'
import { useRouter } from 'vue-router'
import { tableColumns, tableFormSchema } from './data'
import { BasicTable, useTable, TableAction } from '/@/components/Table'
import { getChatMessagePage } from '/@/api/clue'
import { onActivated, ref } from 'vue'
// import { Spin, Result } from 'ant-design-vue'
import { Tabs, Card, Table, Button, TabPane, Tag, Timeline, TimelineItem, Result, Spin } from 'ant-design-vue'
import { Icon } from '/@/components/Icon'
import { CropperAvatar } from '/@/components/Cropper';
import BasicForm from '/@/components/Form/src/BasicForm.vue'
import { useForm } from '/@/components/Form'
import { basicSchema, modalFormSchema } from './data'
import { getClueInfo } from '/@/api/clue'
const activeKey = ref<string>('1')
const activeKey2 = ref<string>('1')
const avatar = ref<string>('')
const [registerTable] = useTable({
bordered: true,
useSearchForm: true,
columns: tableColumns,
showIndexColumn: true,
showTableSetting: false,
api: getChatMessagePage,
formConfig: {
labelWidth: 120,
schemas: tableFormSchema,
},
// beforeFetch: (arg) => {
// const { orderTime } = arg
// if (orderTime) {
// arg.orderTimeFrom = moment(orderTime[0]).format('YYYY-MM-DD 00:00:00')
// arg.orderTimeTo = moment(orderTime[1]).format('YYYY-MM-DD 23:59:59')
// delete arg.orderTime
// }
// },
actionColumn: {
width: 160,
title: '操作',
fixed: 'right',
dataIndex: 'action',
slots: { customRender: 'action' },
},
//
const [registerForm1, { validate: validate1, setFieldsValue: setFieldsValue1 }] = useForm({
labelWidth: 120,
schemas: basicSchema,
baseColProps: { span: 22 },
showActionButtonGroup: false,
}) })
const router = useRouter()
function toDetail(record: any) {
const { miId, userMarriageInformationType } = record
router.push({
query: { miId, type: userMarriageInformationType },
path: '/message/chatInfo',
})
//
function uploadAvatarAfter(value) {
//
// setFieldsValue1({
// avatar: value,
// });
} }
const [registerForm, { setFieldsValue, resetFields, validate, getFieldsValue, updateSchema }] =
useForm({
labelWidth: 90,
schemas: modalFormSchema,
baseColProps: { span: 22 },
showActionButtonGroup: false,
})
const spinning = ref<boolean>(false)
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

Loading…
Cancel
Save