diff --git a/src/api/crm/business/index.ts b/src/api/crm/business/index.ts index 41ec35d8..810ec6e9 100644 --- a/src/api/crm/business/index.ts +++ b/src/api/crm/business/index.ts @@ -1,10 +1,3 @@ -/* - * @Author: zyna - * @Date: 2023-12-02 13:08:56 - * @LastEditTime: 2023-12-17 16:28:20 - * @FilePath: \yudao-ui-admin-vue3\src\api\crm\business\index.ts - * @Description: - */ import request from '@/config/axios' export interface BusinessVO { @@ -43,6 +36,11 @@ export const getBusiness = async (id: number) => { return await request.get({ url: `/crm/business/get?id=` + id }) } +// 获得 CRM 商机列表(精简) +export const getSimpleBusinessList = async () => { + return await request.get({ url: `/crm/business/simple-all-list` }) +} + // 新增 CRM 商机 export const createBusiness = async (data: BusinessVO) => { return await request.post({ url: `/crm/business/create`, data }) @@ -63,7 +61,12 @@ export const exportBusiness = async (params) => { return await request.download({ url: `/crm/business/export-excel`, params }) } -//联系人关联商机列表 +// 联系人关联商机列表 export const getBusinessPageByContact = async (params) => { return await request.get({ url: `/crm/business/page-by-contact`, params }) } + +// 获得 CRM 商机列表 +export const getBusinessListByIds = async (val: number[]) => { + return await request.get({ url: '/crm/business/list-by-ids', params: { ids: val.join(',') } }) +} diff --git a/src/api/crm/contact/index.ts b/src/api/crm/contact/index.ts index 44eebe80..4144c931 100644 --- a/src/api/crm/contact/index.ts +++ b/src/api/crm/contact/index.ts @@ -71,6 +71,11 @@ export const getSimpleContactList = async () => { return await request.get({ url: `/crm/contact/simple-all-list` }) } +// 获得 CRM 联系人列表 +export const getContactListByIds = async (val: number[]) => { + return await request.get({ url: '/crm/contact/list-by-ids', params: { ids: val.join(',') } }) +} + // 批量新增联系人商机关联 export const createContactBusinessList = async (data: ContactBusinessReqVO) => { return await request.post({ url: `/crm/contact/create-business-list`, data }) @@ -84,4 +89,4 @@ export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => { // 查询联系人操作日志 export const getOperateLogPage = async (params: any) => { return await request.get({ url: '/crm/contact/operate-log-page', params }) -} \ No newline at end of file +} diff --git a/src/api/crm/customer/index.ts b/src/api/crm/customer/index.ts index 5ec8a3ea..4542c86a 100644 --- a/src/api/crm/customer/index.ts +++ b/src/api/crm/customer/index.ts @@ -64,8 +64,8 @@ export const exportCustomer = async (params: any) => { } // 客户列表 -export const queryAllList = async () => { - return await request.get({ url: `/crm/customer/query-all-list` }) +export const getSimpleCustomerList = async () => { + return await request.get({ url: `/crm/customer/list-all-simple` }) } // 查询客户操作日志 @@ -75,18 +75,28 @@ export const getOperateLogPage = async (id: number) => { // ======================= 业务操作 ======================= +export interface TransferReqVO { + id: number | undefined // 客户编号 + newOwnerUserId: number | undefined // 新负责人的用户编号 + oldOwnerPermissionLevel: number | undefined // 老负责人加入团队后的权限级别 +} + +// 客户转移 +export const transfer = async (data: TransferReqVO) => { + return await request.put({ url: '/crm/customer/transfer', data }) +} + // 锁定/解锁客户 export const lockCustomer = async (id: number, lockStatus: boolean) => { return await request.put({ url: `/crm/customer/lock`, data: { id, lockStatus } }) } -// TODO @puhui999:方法名,改成和后端一致哈 // 领取公海客户 -export const receive = async (ids: any[]) => { +export const receiveCustomer = async (ids: any[]) => { return await request.put({ url: '/crm/customer/receive', params: { ids: ids.join(',') } }) } // 客户放入公海 -export const putPool = async (id: number) => { +export const putCustomerPool = async (id: number) => { return await request.put({ url: `/crm/customer/put-pool?id=${id}` }) } diff --git a/src/api/crm/followup/index.ts b/src/api/crm/followup/index.ts new file mode 100644 index 00000000..f6b66105 --- /dev/null +++ b/src/api/crm/followup/index.ts @@ -0,0 +1,46 @@ +import request from '@/config/axios' + +// 跟进记录 VO +export interface FollowUpRecordVO { + id: number // 编号 + bizType: number // 数据类型 + bizId: number // 数据编号 + type: number // 跟进类型 + content: string // 跟进内容 + nextTime: Date // 下次联系时间 + businessIds: number[] // 关联的商机编号数组 + contactIds: number[] // 关联的联系人编号数组 +} + +// 跟进记录 API +export const FollowUpRecordApi = { + // 查询跟进记录分页 + getFollowUpRecordPage: async (params: any) => { + return await request.get({ url: `/crm/follow-up-record/page`, params }) + }, + + // 查询跟进记录详情 + getFollowUpRecord: async (id: number) => { + return await request.get({ url: `/crm/follow-up-record/get?id=` + id }) + }, + + // 新增跟进记录 + createFollowUpRecord: async (data: FollowUpRecordVO) => { + return await request.post({ url: `/crm/follow-up-record/create`, data }) + }, + + // 修改跟进记录 + updateFollowUpRecord: async (data: FollowUpRecordVO) => { + return await request.put({ url: `/crm/follow-up-record/update`, data }) + }, + + // 删除跟进记录 + deleteFollowUpRecord: async (id: number) => { + return await request.delete({ url: `/crm/follow-up-record/delete?id=` + id }) + }, + + // 导出跟进记录 Excel + exportFollowUpRecord: async (params) => { + return await request.download({ url: `/crm/follow-up-record/export-excel`, params }) + } +} diff --git a/src/api/crm/message/index.ts b/src/api/crm/message/index.ts index fcd5fbd7..098729eb 100644 --- a/src/api/crm/message/index.ts +++ b/src/api/crm/message/index.ts @@ -34,6 +34,7 @@ export interface CustomerVO { } // 查询客户列表 +// TODO @芋艿:看看是不是后续融合到 getCustomerPage 里; export const getTodayCustomerPage = async (params) => { return await request.get({ url: `/crm/message/todayCustomer`, params }) } diff --git a/src/components/OperateLogV2/src/OperateLogV2.vue b/src/components/OperateLogV2/src/OperateLogV2.vue index ae8aad40..b766fb44 100644 --- a/src/components/OperateLogV2/src/OperateLogV2.vue +++ b/src/components/OperateLogV2/src/OperateLogV2.vue @@ -1,6 +1,6 @@ +<!-- 某个记录的操作日志列表,目前主要用于 CRM 客户、商机等详情界面 --> <template> - <!-- TODO @puhui999:左边不用有空隙哈 --> - <div class="p-20px"> + <div class="pt-20px"> <el-timeline> <el-timeline-item v-for="(log, index) in logList" @@ -58,7 +58,7 @@ const getUserTypeColor = (type: number) => { <style lang="scss" scoped> // 时间线样式调整 :deep(.el-timeline) { - margin: 10px 0 0 160px; + margin: 10px 0 0 110px; .el-timeline-item__wrapper { position: relative; diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 35320edb..7e805159 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -537,6 +537,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ noCache: true, hidden: true }, + // TODO @db52:后面搞,搞成菜单 component: () => import('@/views/crm/message/index.vue') } ] diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 8b543d15..2c8e47ff 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -162,9 +162,7 @@ export enum DICT_TYPE { MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型 // ========== MALL - 商品模块 ========== - PRODUCT_UNIT = 'product_unit', // 商品单位 PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态 - PROMOTION_TYPE_ENUM = 'promotion_type_enum', // 营销类型枚举 // ========== MALL - 交易模块 ========== EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式 @@ -204,5 +202,6 @@ export enum DICT_TYPE { CRM_CUSTOMER_SOURCE = 'crm_customer_source', CRM_PRODUCT_STATUS = 'crm_product_status', CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别 - CRM_PRODUCT_UNIT = 'crm_product_unit' // 产品单位 + CRM_PRODUCT_UNIT = 'crm_product_unit', // 产品单位 + CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type' // 跟进方式 } diff --git a/src/views/crm/contact/ContactForm.vue b/src/views/crm/contact/ContactForm.vue index fc691a21..f8c35af6 100644 --- a/src/views/crm/contact/ContactForm.vue +++ b/src/views/crm/contact/ContactForm.vue @@ -1,25 +1,25 @@ <template> - <Dialog :title="dialogTitle" v-model="dialogVisible" :width="820"> + <Dialog v-model="dialogVisible" :title="dialogTitle" :width="820"> <el-form ref="formRef" + v-loading="formLoading" :model="formData" :rules="formRules" label-width="110px" - v-loading="formLoading" > <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="姓名" prop="name"> - <el-input input-style="width:190px;" v-model="formData.name" placeholder="请输入姓名" /> + <el-input v-model="formData.name" input-style="width:190px;" placeholder="请输入姓名" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="负责人" prop="ownerUserId"> <el-select v-model="formData.ownerUserId" + lable-key="nickname" placeholder="请选择负责人" value-key="id" - lable-key="nickname" > <el-option v-for="item in userList" @@ -36,9 +36,9 @@ <el-form-item label="客户名称" prop="customerName"> <el-select v-model="formData.customerId" + lable-key="name" placeholder="请选择客户" value-key="id" - lable-key="name" > <el-option v-for="item in customerList" @@ -66,8 +66,8 @@ <el-col :span="12"> <el-form-item label="手机号" prop="mobile"> <el-input - input-style="width:190px;" v-model="formData.mobile" + input-style="width:190px;" placeholder="请输入手机号" /> </el-form-item> @@ -82,8 +82,8 @@ <el-col :span="12"> <el-form-item label="邮箱" prop="email"> <el-input - input-style="width:190px;" v-model="formData.email" + input-style="width:190px;" placeholder="请输入邮箱" /> </el-form-item> @@ -98,8 +98,8 @@ <el-col :span="12"> <el-form-item label="微信" prop="wechat"> <el-input - input-style="width:190px;" v-model="formData.wechat" + input-style="width:190px;" placeholder="请输入微信" /> </el-form-item> @@ -108,9 +108,9 @@ <el-form-item label="下次联系时间" prop="contactNextTime"> <el-date-picker v-model="formData.contactNextTime" + placeholder="选择下次联系时间" type="datetime" value-format="x" - placeholder="选择下次联系时间" /> </el-form-item> </el-col> @@ -129,8 +129,8 @@ <el-col :span="12"> <el-form-item label="地址" prop="detailAddress"> <el-input - input-style="width:190px;" v-model="formData.detailAddress" + input-style="width:190px;" placeholder="请输入地址" /> </el-form-item> @@ -143,16 +143,16 @@ <el-option v-for="item in allContactList" :key="item.id" + :disabled="item.id == formData.id" :label="item.name" :value="item.id" - :disabled="item.id == formData.id" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="职位" prop="post"> - <el-input input-style="width:190px;" v-model="formData.post" placeholder="请输入职位" /> + <el-input v-model="formData.post" input-style="width:190px;" placeholder="请输入职位" /> </el-form-item> </el-col> </el-row> @@ -180,14 +180,14 @@ </el-row> </el-form> <template #footer> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> <el-button @click="dialogVisible = false">取 消</el-button> </template> </Dialog> </template> -<script setup lang="ts"> +<script lang="ts" setup> import * as ContactApi from '@/api/crm/contact' -import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict' +import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict' import * as UserApi from '@/api/system/user' import * as CustomerApi from '@/api/crm/customer' import * as AreaApi from '@/api/system/area' @@ -242,7 +242,7 @@ const open = async (type: string, id?: number) => { resetForm() allContactList.value = await ContactApi.getSimpleContactList() userList.value = await UserApi.getSimpleUserList() - customerList.value = await CustomerApi.queryAllList() + customerList.value = await CustomerApi.getSimpleCustomerList() areaList.value = await AreaApi.getAreaTree() // 修改时,设置数据 if (id) { diff --git a/src/views/crm/contact/index.vue b/src/views/crm/contact/index.vue index fc467ea1..c1c7f347 100644 --- a/src/views/crm/contact/index.vue +++ b/src/views/crm/contact/index.vue @@ -2,21 +2,21 @@ <ContentWrap> <!-- 搜索工作栏 --> <el-form - class="-mb-15px" - :model="queryParams" ref="queryFormRef" :inline="true" + :model="queryParams" + class="-mb-15px" label-width="68px" > <el-form-item label="客户" prop="customerId"> <el-select v-model="queryParams.customerId" class="!w-240px" + clearable + lable-key="name" placeholder="请选择客户" value-key="id" - lable-key="name" @keyup.enter="handleQuery" - clearable > <el-option v-for="item in customerList" @@ -30,8 +30,8 @@ <el-input v-model="queryParams.name" class="!w-240px" - placeholder="请输入姓名" clearable + placeholder="请输入姓名" @keyup.enter="handleQuery" /> </el-form-item> @@ -39,8 +39,8 @@ <el-input v-model="queryParams.mobile" class="!w-240px" - placeholder="请输入手机号" clearable + placeholder="请输入手机号" @keyup.enter="handleQuery" /> </el-form-item> @@ -48,8 +48,8 @@ <el-input v-model="queryParams.telephone" class="!w-240px" - placeholder="请输入电话" clearable + placeholder="请输入电话" @keyup.enter="handleQuery" /> </el-form-item> @@ -57,8 +57,8 @@ <el-input v-model="queryParams.qq" class="!w-240px" - placeholder="请输入QQ" clearable + placeholder="请输入QQ" @keyup.enter="handleQuery" /> </el-form-item> @@ -66,8 +66,8 @@ <el-input v-model="queryParams.wechat" class="!w-240px" - placeholder="请输入微信" clearable + placeholder="请输入微信" @keyup.enter="handleQuery" /> </el-form-item> @@ -75,25 +75,33 @@ <el-input v-model="queryParams.email" class="!w-240px" - placeholder="请输入电子邮箱" clearable + placeholder="请输入电子邮箱" @keyup.enter="handleQuery" /> </el-form-item> <el-form-item> - <el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> 搜索 </el-button> - <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button> - <el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:contact:create']"> - <Icon icon="ep:plus" class="mr-5px" /> 新增 + <el-button @click="handleQuery"> + <Icon class="mr-5px" icon="ep:search" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon class="mr-5px" icon="ep:refresh" /> + 重置 + </el-button> + <el-button v-hasPermi="['crm:contact:create']" type="primary" @click="openForm('create')"> + <Icon class="mr-5px" icon="ep:plus" /> + 新增 </el-button> <el-button - type="success" - plain - @click="handleExport" - :loading="exportLoading" v-hasPermi="['crm:contact:export']" + :loading="exportLoading" + plain + type="success" + @click="handleExport" > - <Icon icon="ep:download" class="mr-5px" /> 导出 + <Icon class="mr-5px" icon="ep:download" /> + 导出 </el-button> </el-form-item> </el-form> @@ -101,30 +109,30 @@ <!-- 列表 --> <ContentWrap> - <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> - <el-table-column label="姓名" fixed="left" align="center" prop="name" width="140"> + <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> + <el-table-column align="center" fixed="left" label="姓名" prop="name" width="140"> <template #default="scope"> - <el-link type="primary" :underline="false" @click="openDetail(scope.row.id)"> + <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)"> {{ scope.row.name }} </el-link> </template> </el-table-column> - <el-table-column label="客户名称" fixed="left" align="center" prop="customerName" width="120"> + <el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120"> <template #default="scope"> <el-link - type="primary" :underline="false" + type="primary" @click="openCustomerDetail(scope.row.customerId)" > {{ scope.row.customerName }} </el-link> </template> </el-table-column> - <el-table-column label="手机" align="center" prop="mobile" width="120" /> - <el-table-column label="电话" align="center" prop="telephone" width="120" /> - <el-table-column label="邮箱" align="center" prop="email" width="120" /> - <el-table-column label="职位" align="center" prop="post" width="120" /> - <el-table-column label="地址" align="center" prop="detailAddress" width="120" /> + <el-table-column align="center" label="手机" prop="mobile" width="120" /> + <el-table-column align="center" label="电话" prop="telephone" width="120" /> + <el-table-column align="center" label="邮箱" prop="email" width="120" /> + <el-table-column align="center" label="职位" prop="post" width="120" /> + <el-table-column align="center" label="地址" prop="detailAddress" width="120" /> <el-table-column :formatter="dateFormatter" align="center" @@ -132,56 +140,56 @@ prop="contactNextTime" width="180px" /> - <el-table-column label="备注" align="center" prop="remark" /> - <el-table-column label="关键决策人" align="center" prop="master" width="100"> + <el-table-column align="center" label="备注" prop="remark" /> + <el-table-column align="center" label="关键决策人" prop="master" width="100"> <template #default="scope"> <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" /> </template> </el-table-column> - <el-table-column label="直属上级" align="center" prop="parentName" width="140" /> + <el-table-column align="center" label="直属上级" prop="parentName" width="140" /> <el-table-column - label="最后跟进时间" - align="center" - prop="contactLastTime" :formatter="dateFormatter" + align="center" + label="最后跟进时间" + prop="contactLastTime" width="180px" /> - <el-table-column label="性别" align="center" prop="sex"> + <el-table-column align="center" label="性别" prop="sex"> <template #default="scope"> <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" /> </template> </el-table-column> - <el-table-column label="负责人" align="center" prop="ownerUserName" width="120" /> - <el-table-column label="创建人" align="center" prop="creatorName" width="120" /> + <el-table-column align="center" label="负责人" prop="ownerUserName" width="120" /> + <el-table-column align="center" label="创建人" prop="creatorName" width="120" /> <el-table-column + :formatter="dateFormatter" + align="center" label="更新时间" - align="center" prop="updateTime" - :formatter="dateFormatter" width="180px" /> <el-table-column - label="创建时间" - align="center" - prop="createTime" :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" width="180px" /> - <el-table-column label="操作" align="center" fixed="right" width="200"> + <el-table-column align="center" fixed="right" label="操作" width="200"> <template #default="scope"> <el-button + v-hasPermi="['crm:contact:update']" link type="primary" @click="openForm('update', scope.row.id)" - v-hasPermi="['crm:contact:update']" > 编辑 </el-button> <el-button + v-hasPermi="['crm:contact:delete']" link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['crm:contact:delete']" > 删除 </el-button> @@ -190,9 +198,9 @@ </el-table> <!-- 分页 --> <Pagination - :total="total" - v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" + v-model:page="queryParams.pageNo" + :total="total" @pagination="getList" /> </ContentWrap> @@ -201,7 +209,7 @@ <ContactForm ref="formRef" @success="getList" /> </template> -<script setup lang="ts"> +<script lang="ts" setup> import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as ContactApi from '@/api/crm/contact' @@ -295,6 +303,7 @@ const { push } = useRouter() const openDetail = (id: number) => { push({ name: 'CrmContactDetail', params: { id } }) } + /** 打开客户详情 */ const openCustomerDetail = (id: number) => { push({ name: 'CrmCustomerDetail', params: { id } }) @@ -303,6 +312,6 @@ const openCustomerDetail = (id: number) => { /** 初始化 **/ onMounted(async () => { await getList() - customerList.value = await CustomerApi.queryAllList() + customerList.value = await CustomerApi.getSimpleCustomerList() }) </script> diff --git a/src/views/crm/customer/detail/index.vue b/src/views/crm/customer/detail/index.vue index 15472124..41b9a0be 100644 --- a/src/views/crm/customer/detail/index.vue +++ b/src/views/crm/customer/detail/index.vue @@ -1,33 +1,57 @@ <template> <CustomerDetailsHeader :customer="customer" :loading="loading"> - <!-- @puhui999:返回是不是可以去掉哈,貌似用途可能不大 --> - <el-button @click="close">返回</el-button> - <!-- TODO puhui999: 按钮数据权限收尾统一完善,需要按权限分级和客户状态来动态显示匹配的按钮 --> - <el-button v-hasPermi="['crm:customer:update']" type="primary" @click="openForm"> + <el-button + v-if="permissionListRef?.validateWrite" + v-hasPermi="['crm:customer:update']" + type="primary" + @click="openForm" + > 编辑 </el-button> - <!-- TODO @puhui999:转移的操作接入 --> - <el-button type="primary" @click="transfer">转移</el-button> - <!-- TODO @puhui999:修改成交状态的接入 --> - <el-button>更改成交状态</el-button> - <el-button v-if="customer.lockStatus" @click="handleUnlock">解锁</el-button> - <el-button v-if="!customer.lockStatus" @click="handleLock">锁定</el-button> - <el-button v-if="!customer.ownerUserId" type="primary" @click="receive">领取客户</el-button> - <el-button v-if="customer.ownerUserId" @click="putPool">客户放入公海</el-button> + <el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer"> + 转移 + </el-button> + <el-button v-if="permissionListRef?.validateWrite">更改成交状态</el-button> + <el-button + v-if="customer.lockStatus && permissionListRef?.validateOwnerUser" + @click="handleUnlock" + > + 解锁 + </el-button> + <el-button + v-if="!customer.lockStatus && permissionListRef?.validateOwnerUser" + @click="handleLock" + > + 锁定 + </el-button> + <el-button v-if="!customer.ownerUserId" type="primary" @click="handleReceive"> + 领取客户 + </el-button> + <el-button + v-if="customer.ownerUserId && permissionListRef?.validateOwnerUser" + @click="handlePutPool" + > + 客户放入公海 + </el-button> </CustomerDetailsHeader> <el-col> <el-tabs> - <el-tab-pane label="详细资料"> - <CustomerDetailsInfo :customer="customer" /> + <el-tab-pane label="跟进记录"> + <FollowUpList :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> </el-tab-pane> - <el-tab-pane label="操作日志"> - <OperateLogV2 :log-list="logList" /> + <el-tab-pane label="基本信息"> + <CustomerDetailsInfo :customer="customer" /> </el-tab-pane> <el-tab-pane label="联系人" lazy> <ContactList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> </el-tab-pane> <el-tab-pane label="团队成员"> - <PermissionList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> + <PermissionList + ref="permissionListRef" + :biz-id="customer.id!" + :biz-type="BizTypeEnum.CRM_CUSTOMER" + :show-action="!permissionListRef?.isPool || false" + /> </el-tab-pane> <el-tab-pane label="商机" lazy> <BusinessList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> @@ -39,12 +63,16 @@ <ReceivablePlanList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> <ReceivableList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> </el-tab-pane> + <el-tab-pane label="操作日志"> + <OperateLogV2 :log-list="logList" /> + </el-tab-pane> <el-tab-pane label="回访" lazy>TODO 待开发</el-tab-pane> </el-tabs> </el-col> <!-- 表单弹窗:添加/修改 --> <CustomerForm ref="formRef" @success="getCustomer" /> + <CrmTransferForm ref="crmTransferFormRef" @success="close" /> </template> <script lang="ts" setup> import { useTagsViewStore } from '@/store/modules/tagsView' @@ -58,6 +86,8 @@ import BusinessList from '@/views/crm/business/components/BusinessList.vue' // import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue' // 回款列表 import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue' // 回款计划列表 import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限) +import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue' +import FollowUpList from '@/views/crm/followup/index.vue' import { BizTypeEnum } from '@/api/crm/permission' import type { OperateLogV2VO } from '@/api/system/operatelog' @@ -67,7 +97,9 @@ const customerId = ref(0) // 客户编号 const loading = ref(true) // 加载中 const message = useMessage() // 消息弹窗 const { delView } = useTagsViewStore() // 视图操作 -const { currentRoute, push } = useRouter() // 路由 +const { currentRoute } = useRouter() // 路由 + +const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref /** 获取详情 */ const customer = ref<CustomerApi.CustomerVO>({} as CustomerApi.CustomerVO) // 客户详情 @@ -88,7 +120,10 @@ const openForm = () => { } /** 客户转移 */ -const transfer = () => {} +const crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 客户转移表单 ref +const transfer = () => { + crmTransferFormRef.value?.open('客户转移', customerId.value, CustomerApi.transfer) +} /** 锁定客户 */ const handleLock = async () => { @@ -106,19 +141,18 @@ const handleUnlock = async () => { await getCustomer() } -// TODO @puhui999:下面两个方法的命名,也用 handleXXX 风格哈 /** 领取客户 */ -const receive = async () => { +const handleReceive = async () => { await message.confirm(`确定领取客户【${customer.value.name}】 吗?`) - await CustomerApi.receive([unref(customerId.value)]) + await CustomerApi.receiveCustomer([unref(customerId.value)]) message.success(`领取客户【${customer.value.name}】成功`) await getCustomer() } /** 客户放入公海 */ -const putPool = async () => { +const handlePutPool = async () => { await message.confirm(`确定将客户【${customer.value.name}】放入公海吗?`) - await CustomerApi.putPool(unref(customerId.value)) + await CustomerApi.putCustomerPool(unref(customerId.value)) message.success(`客户【${customer.value.name}】放入公海成功`) close() } @@ -135,15 +169,13 @@ const getOperateLog = async () => { const close = () => { delView(unref(currentRoute)) - // TODO 先返回到客户列表 - push({ name: 'CrmCustomer' }) } /** 初始化 */ const { params } = useRoute() onMounted(() => { if (!params.id) { - ElMessage.warning('参数错误,客户不能为空!') + message.warning('参数错误,客户不能为空!') close() return } diff --git a/src/views/crm/customer/index.vue b/src/views/crm/customer/index.vue index 3ca7b022..b2cee2f7 100644 --- a/src/views/crm/customer/index.vue +++ b/src/views/crm/customer/index.vue @@ -100,14 +100,10 @@ <!-- 列表 --> <ContentWrap> - <!-- TODO @puhui999:是不是就 3 重呀,我负责的,我参与的,我下属的 --> <el-tabs v-model="activeName" @tab-click="handleClick"> - <el-tab-pane label="客户列表" name="1" /> - <el-tab-pane label="我负责的" name="2" /> - <el-tab-pane label="我关注的" name="3" /> - <el-tab-pane label="我参与的" name="4" /> - <el-tab-pane label="下属负责的" name="5" /> - <el-tab-pane label="客户公海" name="6" /> + <el-tab-pane label="我负责的" name="1" /> + <el-tab-pane label="我参与的" name="2" /> + <el-tab-pane label="下属负责的" name="3" /> </el-tabs> <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> <el-table-column align="center" label="编号" prop="id" /> @@ -149,7 +145,9 @@ <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" /> </template> </el-table-column> - <!-- TODO @puhui999:距进入公海天数 --> + <el-table-column align="center" label="距离进入公海" prop="poolDay"> + <template #default="scope"> {{ scope.row.poolDay }} 天</template> + </el-table-column> <el-table-column :formatter="dateFormatter" align="center" @@ -251,43 +249,27 @@ const activeName = ref('1') // 列表 tab enum CrmSceneTypeEnum { OWNER = 1, - FOLLOW = 2, - INVOLVED = 3, - SUBORDINATE = 4 + INVOLVED = 2, + SUBORDINATE = 3 } const handleClick = (tab: TabsPaneContext) => { switch (tab.paneName) { case '1': - resetQuery() - break - case '2': resetQuery(() => { queryParams.value.sceneType = CrmSceneTypeEnum.OWNER }) break - case '3': - resetQuery(() => { - queryParams.value.sceneType = CrmSceneTypeEnum.FOLLOW - }) - break - // TODO @puhui999:这个貌似报错? - case '4': + case '2': resetQuery(() => { queryParams.value.sceneType = CrmSceneTypeEnum.INVOLVED }) break - case '5': + case '3': resetQuery(() => { queryParams.value.sceneType = CrmSceneTypeEnum.SUBORDINATE }) break - // TODO @puhui999:公海单独一个菜单哈。 - case '6': - resetQuery(() => { - queryParams.value.pool = true - }) - break } } diff --git a/src/views/crm/customer/pool/index.vue b/src/views/crm/customer/pool/index.vue new file mode 100644 index 00000000..c7b13a04 --- /dev/null +++ b/src/views/crm/customer/pool/index.vue @@ -0,0 +1,279 @@ +<template> + <ContentWrap> + <!-- 搜索工作栏 --> + <el-form + ref="queryFormRef" + :inline="true" + :model="queryParams" + class="-mb-15px" + label-width="68px" + > + <el-form-item label="客户名称" prop="name"> + <el-input + v-model="queryParams.name" + class="!w-240px" + clearable + placeholder="请输入客户名称" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="手机" prop="mobile"> + <el-input + v-model="queryParams.mobile" + class="!w-240px" + clearable + placeholder="请输入手机" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="所属行业" prop="industryId"> + <el-select + v-model="queryParams.industryId" + class="!w-240px" + clearable + placeholder="请选择所属行业" + > + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="客户等级" prop="level"> + <el-select + v-model="queryParams.level" + class="!w-240px" + clearable + placeholder="请选择客户等级" + > + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="客户来源" prop="source"> + <el-select + v-model="queryParams.source" + class="!w-240px" + clearable + placeholder="请选择客户来源" + > + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon class="mr-5px" icon="ep:search" /> + 搜索 + </el-button> + <el-button @click="resetQuery(undefined)"> + <Icon class="mr-5px" icon="ep:refresh" /> + 重置 + </el-button> + <el-button + v-hasPermi="['crm:customer:export']" + :loading="exportLoading" + plain + type="success" + @click="handleExport" + > + <Icon class="mr-5px" icon="ep:download" /> + 导出 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> + <el-table-column align="center" label="编号" prop="id" /> + <el-table-column align="center" label="客户名称" prop="name" width="160"> + <template #default="scope"> + <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)"> + {{ scope.row.name }} + </el-link> + </template> + </el-table-column> + <el-table-column align="center" label="手机" prop="mobile" width="120" /> + <el-table-column align="center" label="电话" prop="telephone" width="120" /> + <el-table-column align="center" label="客户来源" prop="source" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" /> + </template> + </el-table-column> + <el-table-column align="center" label="所属行业" prop="industryId" width="120"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" /> + </template> + </el-table-column> + <el-table-column align="center" label="客户等级" prop="level" width="120"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" /> + </template> + </el-table-column> + <el-table-column align="center" label="网址" prop="website" width="200" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="下次联系时间" + prop="contactNextTime" + width="180px" + /> + <el-table-column align="center" label="备注" prop="remark" width="200" /> + <el-table-column align="center" label="成交状态" prop="dealStatus"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" /> + </template> + </el-table-column> + <el-table-column align="center" label="距离进入公海" prop="poolDay"> + <template #default="scope"> {{ scope.row.poolDay }}天</template> + </el-table-column> + <el-table-column + :formatter="dateFormatter" + align="center" + label="最后跟进时间" + prop="contactLastTime" + width="180px" + /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="updateTime" + width="180px" + /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" + width="180px" + /> + <el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" /> + <el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" /> + <el-table-column align="center" label="创建人" prop="creatorName" width="100px" /> + <el-table-column align="center" fixed="right" label="操作" min-width="150"> + <template #default="scope"> + <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)"> + 详情 + </el-link> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.pageNo" + :total="total" + @pagination="getList" + /> + </ContentWrap> +</template> + +<script lang="ts" setup> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' +import * as CustomerApi from '@/api/crm/customer' + +defineOptions({ name: 'CrmCustomerPool' }) + +const message = useMessage() // 消息弹窗 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = ref({ + pageNo: 1, + pageSize: 10, + name: '', + mobile: '', + industryId: undefined, + level: undefined, + source: undefined, + sceneType: undefined, + pool: true +}) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await CustomerApi.getCustomerPage(queryParams.value) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.value.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + queryParams.value = { + pageNo: 1, + pageSize: 10, + name: '', + mobile: '', + industryId: undefined, + level: undefined, + source: undefined, + sceneType: undefined, + pool: true + } + handleQuery() +} + +/** 打开客户详情 */ +const { currentRoute, push } = useRouter() +const openDetail = (id: number) => { + push({ name: 'CrmCustomerDetail', params: { id } }) +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await CustomerApi.exportCustomer(queryParams.value) + download.excel(data, '客户.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 监听路由变化更新列表 */ +watch( + () => currentRoute.value, + () => { + getList() + } +) + +/** 初始化 **/ +onMounted(() => { + getList() +}) +</script> diff --git a/src/views/crm/followup/FollowUpRecordForm.vue b/src/views/crm/followup/FollowUpRecordForm.vue new file mode 100644 index 00000000..ea75fdb9 --- /dev/null +++ b/src/views/crm/followup/FollowUpRecordForm.vue @@ -0,0 +1,167 @@ +<!-- 跟进记录的添加表单弹窗 --> +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="120px" + > + <el-row> + <el-col :span="12"> + <el-form-item label="跟进类型" prop="type"> + <el-select v-model="formData.type" placeholder="请选择跟进类型"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="下次联系时间" prop="nextTime"> + <el-date-picker + v-model="formData.nextTime" + placeholder="选择下次联系时间" + type="date" + value-format="x" + /> + </el-form-item> + </el-col> + <!-- TODO @puhui999:不搞富文本哈;然后加个附件、图片两个 form-item 哈 --> + <el-col :span="24"> + <el-form-item label="跟进内容" prop="content"> + <Editor v-model="formData.content" height="300px" /> + </el-form-item> + </el-col> + <!-- TODO @puhui999:因为不考虑编辑的情况,是不是关联要是个弹窗选择哈? --> + <el-col :span="24"> + <el-form-item label="关联联系人" prop="contactIds"> + <el-select v-model="formData.contactIds" multiple placeholder="请选择"> + <el-option + v-for="item in allContactList" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + <contact-list v-model:contactIds="formData.contactIds" /> + </el-form-item> + <!-- <el-form-item label="关联联系人" prop="contactIds">--> + <!-- <el-button @click="handleAddContact">--> + <!-- <Icon class="mr-5px" icon="ep:plus" />--> + <!-- 选择添加联系人--> + <!-- </el-button>--> + <!-- <contact-list v-model:contactIds="formData.contactIds" />--> + <!-- </el-form-item>--> + </el-col> + <el-col :span="24"> + <el-form-item label="关联商机" prop="businessIds"> + <el-select v-model="formData.businessIds" multiple placeholder="请选择"> + <el-option + v-for="item in allBusinessList" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + <business-list v-model:businessIds="formData.businessIds" /> + </el-form-item> + <!-- <el-form-item label="关联商机" prop="businessIds">--> + <!-- <el-button @click="handleAddBusiness">--> + <!-- <Icon class="mr-5px" icon="ep:plus" />--> + <!-- 选择添加商机--> + <!-- </el-button>--> + <!-- <business-list v-model:businessIds="formData.businessIds" />--> + <!-- </el-form-item>--> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup' +import { BusinessList, ContactList } from './components' +import * as ContactApi from '@/api/crm/contact' +import * as BusinessApi from '@/api/crm/business' + +defineOptions({ name: 'FollowUpRecordForm' }) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref<FollowUpRecordVO>({} as FollowUpRecordVO) +const formRules = reactive({ + type: [{ required: true, message: '跟进类型不能为空', trigger: 'change' }], + content: [{ required: true, message: '跟进内容不能为空', trigger: 'blur' }], + nextTime: [{ required: true, message: '下次联系时间不能为空', trigger: 'blur' }] +}) + +const formRef = ref() // 表单 Ref +const allContactList = ref<ContactApi.ContactVO[]>([]) // 所有联系人列表 +const allBusinessList = ref<BusinessApi.BusinessVO[]>([]) // 所有商家列表 + +/** 打开弹窗 */ +const open = async (bizType: number, bizId: number, type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + formData.value.bizType = bizType + formData.value.bizId = bizId + allContactList.value = await ContactApi.getSimpleContactList() + allBusinessList.value = await BusinessApi.getSimpleBusinessList() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await FollowUpRecordApi.getFollowUpRecord(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + await formRef.value.validate() + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as FollowUpRecordVO + if (formType.value === 'create') { + await FollowUpRecordApi.createFollowUpRecord(data) + message.success(t('common.createSuccess')) + } else { + await FollowUpRecordApi.updateFollowUpRecord(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formRef.value?.resetFields() + formData.value = {} as FollowUpRecordVO +} +</script> diff --git a/src/views/crm/followup/components/BusinessList.vue b/src/views/crm/followup/components/BusinessList.vue new file mode 100644 index 00000000..49511194 --- /dev/null +++ b/src/views/crm/followup/components/BusinessList.vue @@ -0,0 +1,71 @@ +<template> + <el-table :data="list" :show-overflow-tooltip="true" :stripe="true" height="200"> + <el-table-column align="center" label="商机名称" prop="name" /> + <el-table-column align="center" label="客户名称" prop="customerName" /> + <el-table-column align="center" label="商机金额" prop="price" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="预计成交日期" + prop="dealTime" + width="120px" + /> + <el-table-column align="center" label="商机状态类型" prop="statusTypeName" width="120" /> + <el-table-column align="center" label="商机状态" prop="statusName" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="更新时间" + prop="updateTime" + width="180px" + /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" + width="180px" + /> + <el-table-column align="center" label="负责人" prop="ownerUserName" width="120" /> + <el-table-column align="center" label="创建人" prop="creatorName" width="120" /> + <el-table-column align="center" label="备注" prop="remark" /> + <el-table-column align="center" fixed="right" label="操作" width="130"> + <template #default="scope"> + <el-button link type="danger" @click="handleDelete(scope.row.id)"> 移除</el-button> + </template> + </el-table-column> + </el-table> +</template> + +<script lang="ts" setup> +import { dateFormatter } from '@/utils/formatTime' +import * as BusinessApi from '@/api/crm/business' + +defineOptions({ name: 'BusinessList' }) +const props = withDefaults(defineProps<{ businessIds: number[] }>(), { + businessIds: () => [] +}) +const list = ref<BusinessApi.BusinessVO[]>([] as BusinessApi.BusinessVO[]) +watch( + () => props.businessIds, + (val) => { + if (!val || val.length === 0) { + return + } + list.value = BusinessApi.getBusinessListByIds(val) as unknown as BusinessApi.BusinessVO[] + } +) +const emits = defineEmits<{ + (e: 'update:businessIds', businessIds: number[]): void +}>() +const handleDelete = (id: number) => { + const index = list.value.findIndex((item) => item.id === id) + if (index !== -1) { + list.value.splice(index, 1) + } + emits( + 'update:businessIds', + list.value.map((item) => item.id) + ) +} +</script> diff --git a/src/views/crm/followup/components/BusinessListSelectForm.vue b/src/views/crm/followup/components/BusinessListSelectForm.vue new file mode 100644 index 00000000..2ff6ab4a --- /dev/null +++ b/src/views/crm/followup/components/BusinessListSelectForm.vue @@ -0,0 +1,79 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%"> + <el-row> + <el-col :span="12"> + <el-form-item label="跟进类型" prop="type"> + <el-select v-model="formData.type" placeholder="请选择跟进类型"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="下次联系时间" prop="nextTime"> + <el-date-picker + v-model="formData.nextTime" + placeholder="选择下次联系时间" + type="date" + value-format="x" + /> + </el-form-item> + </el-col> + </el-row> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +/** 跟进记录 表单 */ +defineOptions({ name: 'BusinessListSelectForm' }) + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formData = ref([]) + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await FollowUpRecordApi.getFollowUpRecord(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + await formRef.value.validate() + // 提交请求 + formLoading.value = true + try { + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formRef.value?.resetFields() +} +</script> diff --git a/src/views/crm/followup/components/ContactList.vue b/src/views/crm/followup/components/ContactList.vue new file mode 100644 index 00000000..2cd7ccf0 --- /dev/null +++ b/src/views/crm/followup/components/ContactList.vue @@ -0,0 +1,97 @@ +<template> + <el-table :data="list" :show-overflow-tooltip="true" :stripe="true" height="200"> + <el-table-column align="center" fixed="left" label="姓名" prop="name" width="140" /> + <el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120" /> + <el-table-column align="center" label="手机" prop="mobile" width="120" /> + <el-table-column align="center" label="电话" prop="telephone" width="120" /> + <el-table-column align="center" label="邮箱" prop="email" width="120" /> + <el-table-column align="center" label="职位" prop="post" width="120" /> + <el-table-column align="center" label="地址" prop="detailAddress" width="120" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="下次联系时间" + prop="contactNextTime" + width="180px" + /> + <el-table-column align="center" label="关键决策人" prop="master" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" /> + </template> + </el-table-column> + <el-table-column align="center" label="直属上级" prop="parentName" width="140" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="最后跟进时间" + prop="contactLastTime" + width="180px" + /> + <el-table-column align="center" label="性别" prop="sex"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" /> + </template> + </el-table-column> + <el-table-column align="center" label="负责人" prop="ownerUserName" width="120" /> + <el-table-column align="center" label="创建人" prop="creatorName" width="120" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="更新时间" + prop="updateTime" + width="180px" + /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" + width="180px" + /> + <el-table-column align="center" label="备注" prop="remark" /> + <el-table-column align="center" fixed="right" label="操作" width="130"> + <template #default="scope"> + <el-button link type="danger" @click="handleDelete(scope.row.id)"> 移除</el-button> + </template> + </el-table-column> + </el-table> +</template> + +<script lang="ts" setup> +import { dateFormatter } from '@/utils/formatTime' +import { DICT_TYPE } from '@/utils/dict' +import * as ContactApi from '@/api/crm/contact' + +defineOptions({ name: 'ContactList' }) +const props = withDefaults(defineProps<{ contactIds: number[] }>(), { + contactIds: () => [] +}) +const list = ref<ContactApi.ContactVO[]>([] as ContactApi.ContactVO[]) +const getContactList = async () => { + list.value = (await ContactApi.getContactListByIds( + props.contactIds + )) as unknown as ContactApi.ContactVO[] +} +watch( + () => props.contactIds, + (val) => { + if (!val || val.length === 0) { + return + } + getContactList() + } +) +const emits = defineEmits<{ + (e: 'update:contactIds', contactIds: number[]): void +}>() +const handleDelete = (id: number) => { + const index = list.value.findIndex((item) => item.id === id) + if (index !== -1) { + list.value.splice(index, 1) + } + emits( + 'update:contactIds', + list.value.map((item) => item.id) + ) +} +</script> diff --git a/src/views/crm/followup/components/index.ts b/src/views/crm/followup/components/index.ts new file mode 100644 index 00000000..b3e7f257 --- /dev/null +++ b/src/views/crm/followup/components/index.ts @@ -0,0 +1,4 @@ +import BusinessList from './BusinessList.vue' +import ContactList from './ContactList.vue' + +export { BusinessList, ContactList } diff --git a/src/views/crm/followup/index.vue b/src/views/crm/followup/index.vue new file mode 100644 index 00000000..5960d1d9 --- /dev/null +++ b/src/views/crm/followup/index.vue @@ -0,0 +1,137 @@ +<!-- 某个记录的跟进记录列表,目前主要用于 CRM 客户、商机等详情界面 --> +<template> + <!-- 操作栏 --> + <el-row class="mb-10px" justify="end"> + <el-button @click="openForm('create')"> + <Icon class="mr-5px" icon="ep:edit" /> + 写跟进 + </el-button> + </el-row> + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> + <el-table-column align="center" label="编号" prop="id" /> + <el-table-column align="center" label="跟进人" prop="creatorName" /> + <el-table-column align="center" label="跟进类型" prop="type"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="scope.row.type" /> + </template> + </el-table-column> + <el-table-column align="center" label="跟进内容" prop="content" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="下次联系时间" + prop="nextTime" + width="180px" + /> + <!-- TODO @puhui999:点击后,查看关联联系人 --> + <el-table-column align="center" label="关联联系人" prop="contactIds" /> + <!-- TODO @puhui999:点击后,查看关联商机 --> + <el-table-column align="center" label="关联商机" prop="businessIds" /> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" + width="180px" + /> + <el-table-column align="center" label="操作"> + <template #default="scope"> + <el-button + v-hasPermi="['crm:follow-up-record:update']" + link + type="primary" + @click="openForm('update', scope.row.id)" + > + 编辑 + </el-button> + <el-button + v-hasPermi="['crm:follow-up-record:delete']" + link + type="danger" + @click="handleDelete(scope.row.id)" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.pageNo" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <FollowUpRecordForm ref="formRef" @success="getList" /> +</template> + +<script lang="ts" setup> +import { dateFormatter } from '@/utils/formatTime' +import { DICT_TYPE } from '@/utils/dict' +import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup' +import FollowUpRecordForm from './FollowUpRecordForm.vue' + +/** 跟进记录列表 */ +defineOptions({ name: 'FollowUpRecord' }) +const props = defineProps<{ + bizType: number + bizId: number +}>() +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const list = ref<FollowUpRecordVO[]>([]) // 列表的数据 +const total = ref(0) // 列表的总页数 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + bizType: 0, + bizId: 0 +}) + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await FollowUpRecordApi.getFollowUpRecordPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 添加/修改操作 */ +const formRef = ref<InstanceType<typeof FollowUpRecordForm>>() +const openForm = (type: string, id?: number) => { + formRef.value?.open(props.bizType, props.bizId, type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await FollowUpRecordApi.deleteFollowUpRecord(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +watch( + () => props.bizId, + () => { + queryParams.bizType = props.bizType + queryParams.bizId = props.bizId + getList() + } +) +</script> diff --git a/src/views/crm/message/index.vue b/src/views/crm/message/index.vue index 1f809524..1dd972aa 100644 --- a/src/views/crm/message/index.vue +++ b/src/views/crm/message/index.vue @@ -20,7 +20,7 @@ </el-col> </el-row> </template> - +<!-- @dbh52:模块改成 backlog 会更合适,待办事项 --> <script lang="ts" setup> import TodayCustomer from './tables/TodayCustomer.vue' import FollowLeads from './tables/FollowLeads.vue' diff --git a/src/views/crm/message/tables/TodayCustomer.vue b/src/views/crm/message/tables/TodayCustomer.vue index df70e599..f4b59be1 100644 --- a/src/views/crm/message/tables/TodayCustomer.vue +++ b/src/views/crm/message/tables/TodayCustomer.vue @@ -125,11 +125,12 @@ import { DICT_TYPE } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import * as MessageApi from '@/api/crm/message' -const title = ref('今日需联系客户') +const title = ref('今日需联系客户') // TODO @dbh52:这个不用枚举一个变量哈; const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 const queryParams = ref<{ + // TODO @dbh52:这个 ref 类型定义可以去掉哈。之前定义的原因,是因为 idea 报错了;默认 idea 可以推导出类型 pageNo: number pageSize: number contactStatus: number | undefined @@ -149,6 +150,7 @@ const CONTACT_STATUS = [ ] const SCENE_TYPES = [ + // TODO 芋艿:貌似可以搞成全局枚举 { label: '我负责的', value: 1 }, { label: '我跟进的', value: 2 }, { label: '我参与的', value: 3 }, @@ -182,6 +184,7 @@ const resetQuery = (func: Function | undefined = undefined) => { contactStatus: 1, sceneType: 1 } + // TODO @dbh52:这里的 func 是不是可以去掉哈; func && func() handleQuery() } diff --git a/src/views/crm/permission/components/PermissionList.vue b/src/views/crm/permission/components/PermissionList.vue index f25f6598..b098f261 100644 --- a/src/views/crm/permission/components/PermissionList.vue +++ b/src/views/crm/permission/components/PermissionList.vue @@ -1,103 +1,112 @@ <template> <!-- 操作栏 --> - <el-row justify="end"> - <el-button @click="openForm"> - <Icon class="mr-5px" icon="fluent:people-team-add-20-filled" /> - 添加团队成员 + <el-row v-if="showAction" justify="end"> + <el-button v-if="validateOwnerUser" type="primary" @click="openForm"> + <Icon class="mr-5px" icon="ep:plus" /> + 新增 </el-button> - <el-button @click="handleUpdate"> + <el-button v-if="validateOwnerUser" @click="handleUpdate"> <Icon class="mr-5px" icon="ep:edit" /> 编辑 </el-button> - <el-button @click="handleDelete"> + <el-button v-if="validateOwnerUser" @click="handleDelete"> <Icon class="mr-5px" icon="ep:delete" /> 移除 </el-button> - <el-button type="danger" @click="handleQuit"> 退出团队</el-button> + <el-button v-if="!validateOwnerUser && list.length > 0" type="danger" @click="handleQuit"> + 退出团队 + </el-button> </el-row> - - <!-- 列表 --> - <ContentWrap class="mt-10px"> - <el-table - v-loading="loading" - :data="list" - :show-overflow-tooltip="true" - :stripe="true" - @selection-change="handleSelectionChange" - > - <!-- TODO @puhui999:负责人不允许选中 --> - <el-table-column type="selection" width="55" /> - <el-table-column align="center" label="姓名" prop="nickname" /> - <el-table-column align="center" label="部门" prop="deptName" /> - <el-table-column align="center" label="岗位" prop="postNames" /> - <el-table-column align="center" label="权限" prop="level"> - <template #default="{ row }"> - <dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" /> - </template> - </el-table-column> - <el-table-column - :formatter="dateFormatter" - align="center" - label="加入时间" - prop="createTime" - /> - </el-table> - </ContentWrap> + <!-- 团队成员展示 --> + <el-table + ref="elTableRef" + v-loading="loading" + :data="list" + :show-overflow-tooltip="true" + :stripe="true" + class="mt-20px" + @selection-change="handleSelectionChange" + > + <el-table-column type="selection" width="55" /> + <el-table-column align="center" label="姓名" prop="nickname" /> + <el-table-column align="center" label="部门" prop="deptName" /> + <el-table-column align="center" label="岗位" prop="postNames" /> + <el-table-column align="center" label="权限级别" prop="level"> + <template #default="{ row }"> + <dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" /> + </template> + </el-table-column> + <el-table-column :formatter="dateFormatter" align="center" label="加入时间" prop="createTime" /> + </el-table> <!-- 表单弹窗:添加/修改 --> <CrmPermissionForm ref="formRef" @success="getList" /> </template> <script lang="ts" setup> import { dateFormatter } from '@/utils/formatTime' -import { DICT_TYPE } from '@/utils/dict' +import { ElTable } from 'element-plus' import * as PermissionApi from '@/api/crm/permission' -import { PermissionLevelEnum } from '@/api/crm/permission' import { useUserStoreWithOut } from '@/store/modules/user' import CrmPermissionForm from './PermissionForm.vue' +import { DICT_TYPE } from '@/utils/dict' defineOptions({ name: 'CrmPermissionList' }) -const props = defineProps<{ - bizType: number // 业务类型 - bizId: number // 业务编号 -}>() const message = useMessage() // 消息 + +const props = defineProps<{ + bizType: number // 模块类型 + bizId: number | undefined // 模块数据编号 + showAction: boolean //是否展示操作按钮 +}>() const loading = ref(true) // 列表的加载中 const list = ref<PermissionApi.PermissionVO[]>([]) // 列表的数据 +const formData = ref({ + ownerUserId: 0 +}) +const userStore = useUserStoreWithOut() // 用户信息缓存 /** 查询列表 */ const getList = async () => { loading.value = true try { - list.value = await PermissionApi.getPermissionList({ + const data = await PermissionApi.getPermissionList({ bizType: props.bizType, bizId: props.bizId }) + list.value = data + const permission = list.value.find( + (item) => + item.userId === userStore.getUser.id && + item.level === PermissionApi.PermissionLevelEnum.OWNER + ) + if (permission) { + formData.value.ownerUserId = userStore.getUser.id + } } finally { loading.value = false } } - -/** 选中团队成员 */ const multipleSelection = ref<PermissionApi.PermissionVO[]>([]) // 选择的团队成员 +const elTableRef = ref<InstanceType<typeof ElTable>>() const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => { + if (val.findIndex((item) => item.level === PermissionApi.PermissionLevelEnum.OWNER) !== -1) { + message.warning('不能选择负责人!') + elTableRef.value?.clearSelection() + return + } multipleSelection.value = val } -/** 添加团队成员 */ -const formRef = ref<InstanceType<typeof CrmPermissionForm>>() // 权限表单 Ref -const openForm = () => { - formRef.value?.open('create', props.bizType, props.bizId) -} - /** 编辑团队成员 */ +const formRef = ref<InstanceType<typeof CrmPermissionForm>>() // 权限表单 Ref const handleUpdate = () => { if (multipleSelection.value?.length === 0) { message.warning('请先选择团队成员后操作!') return } - const ids = multipleSelection.value?.map((item) => item.id) as number[] - formRef.value?.open('update', props.bizType, props.bizId, ids) + const ids = multipleSelection.value?.map((item) => item.id) as unknown as number[] + formRef.value?.open('update', props.bizType, props.bizId!, ids) } /** 移除团队成员 */ @@ -106,36 +115,75 @@ const handleDelete = async () => { message.warning('请先选择团队成员后操作!') return } - await message.delConfirm('是否删除选择的团队成员?') + await message.delConfirm() const ids = multipleSelection.value?.map((item) => item.id) await PermissionApi.deletePermissionBatch({ bizType: props.bizType, bizId: props.bizId, ids }) - message.success('删除成功') } +/** 添加团队成员 */ +const openForm = () => { + formRef.value?.open('create', props.bizType, props.bizId!) +} + +// 校验负责人权限和编辑权限 +const validateOwnerUser = ref(false) +const validateWrite = ref(false) +const isPool = ref(false) +watch( + list, + (newArr) => { + isPool.value = false + if (newArr?.length > 0) { + isPool.value = !list.value.some( + (item) => item.level === PermissionApi.PermissionLevelEnum.OWNER + ) + validateOwnerUser.value = false + validateWrite.value = false + const userId = userStore.getUser?.id + list.value + .filter((item) => item.userId === userId) + .forEach((item) => { + if (item.level === PermissionApi.PermissionLevelEnum.OWNER) { + validateOwnerUser.value = true + validateWrite.value = true + } else if (item.level === PermissionApi.PermissionLevelEnum.WRITE) { + validateWrite.value = true + } + }) + } else { + isPool.value = true + } + }, + { + immediate: true + } +) + +defineExpose({ openForm, validateOwnerUser, validateWrite, isPool }) /** 退出团队 */ -const userStore = useUserStoreWithOut() // 用户信息缓存 const handleQuit = async () => { const permission = list.value.find( - (item) => item.userId === userStore.getUser.id && item.level === PermissionLevelEnum.OWNER + (item) => + item.userId === userStore.getUser.id && item.level === PermissionApi.PermissionLevelEnum.OWNER ) if (permission) { message.warning('负责人不能退出团队!') return } - await message.confirm('确认退出团队吗?') const userPermission = list.value.find((item) => item.userId === userStore.getUser.id) - await PermissionApi.deleteSelfPermission(userPermission?.id) + if (userPermission) { + await PermissionApi.deleteSelfPermission(userPermission.id!) + } } -/** 监听打开的 bizId + bizType,从而加载最新的列表 */ watch( - () => [props.bizId, props.bizType], - (val) => { - if (!val[0]) { + () => props.bizId, + (bizId) => { + if (!bizId) { return } getList() diff --git a/src/views/crm/permission/components/TransferForm.vue b/src/views/crm/permission/components/TransferForm.vue new file mode 100644 index 00000000..c9f5eed1 --- /dev/null +++ b/src/views/crm/permission/components/TransferForm.vue @@ -0,0 +1,123 @@ +<!-- 转移数据的表单弹窗,目前主要用于 CRM 客户、商机等详情界面 --> +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="30%"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="150px" + > + <el-form-item label="选择新负责人" prop="newOwnerUserId"> + <el-select v-model="formData.newOwnerUserId"> + <el-option + v-for="item in userOptions" + :key="item.id" + :label="item.nickname" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item label="老负责人"> + <el-radio-group + v-model="oldOwnerHandler" + @change="formData.oldOwnerPermissionLevel = undefined" + > + <el-radio :label="false" size="large">移除</el-radio> + <el-radio :label="true" size="large">加入团队</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item v-if="oldOwnerHandler" label="老负责人权限级别" prop="oldOwnerPermissionLevel"> + <el-radio-group v-model="formData.oldOwnerPermissionLevel"> + <template + v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PERMISSION_LEVEL)" + :key="dict.value" + > + <el-radio v-if="dict.value != PermissionLevelEnum.OWNER" :label="dict.value"> + {{ dict.label }} + </el-radio> + </template> + </el-radio-group> + </el-form-item> + </el-form> + <!-- TODO 转移客户时,需要额外有【联系人】【商机】【合同】的 checkbox 选择 --> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import * as UserApi from '@/api/system/user' +import type { TransferReqVO } from '@/api/crm/customer' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { PermissionLevelEnum } from '@/api/crm/permission' + +defineOptions({ name: 'CrmTransferForm' }) + +const message = useMessage() // 消息弹窗 +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 +const oldOwnerHandler = ref(false) // 老负责人的处理方式 +const formData = ref<TransferReqVO>({ + id: undefined, // 客户编号 + newOwnerUserId: undefined, // 新负责人的用户编号 + oldOwnerPermissionLevel: undefined // 老负责人加入团队后的权限级别 +}) +const formRules = reactive({ + newOwnerUserId: [{ required: true, message: '新负责人不能为空', trigger: 'blur' }], + oldOwnerPermissionLevel: [ + { required: true, message: '老负责人加入团队后的权限级别不能为空', trigger: 'blur' } + ] +}) +const formRef = ref() // 表单 Ref +const transferFuncRef = ref<Function>(() => {}) // 转移所需回调 + +/** 打开弹窗 */ +const open = async (title: string, bizId: number, transferFunc: Function) => { + dialogVisible.value = true + dialogTitle.value = title + transferFuncRef.value = transferFunc + resetForm() + formData.value.id = bizId +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value + await transferFuncRef.value(unref(data)) + message.success(dialogTitle.value + '成功') + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formRef.value?.resetFields() + formData.value = { + id: undefined, // 客户编号 + newOwnerUserId: undefined, // 新负责人的用户编号 + oldOwnerPermissionLevel: undefined // 老负责人加入团队后的权限级别 + } +} +onMounted(async () => { + // 获得用户列表 + // TODO 芋艿:用户列表的选择组件 + userOptions.value = await UserApi.getSimpleUserList() +}) +</script> diff --git a/src/views/crm/product/ProductForm.vue b/src/views/crm/product/ProductForm.vue index e12af4c8..76ca7778 100644 --- a/src/views/crm/product/ProductForm.vue +++ b/src/views/crm/product/ProductForm.vue @@ -47,7 +47,7 @@ <el-form-item label="产品单位" prop="unit"> <el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位"> <el-option - v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)" + v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PRODUCT_UNIT)" :key="dict.value" :label="dict.label" :value="dict.value" diff --git a/src/views/crm/product/detail/ProductDetailsHeader.vue b/src/views/crm/product/detail/ProductDetailsHeader.vue index 356f1dbc..34c94da8 100644 --- a/src/views/crm/product/detail/ProductDetailsHeader.vue +++ b/src/views/crm/product/detail/ProductDetailsHeader.vue @@ -22,7 +22,7 @@ {{ productCategoryList?.find((c) => c.id === product.categoryId)?.name }} </el-descriptions-item> <el-descriptions-item label="产品单位"> - <dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="product.unit" /> + <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="product.unit" /> </el-descriptions-item> <el-descriptions-item label="产品价格">{{ fenToYuan(product.price) }}元</el-descriptions-item> <el-descriptions-item label="产品编码">{{ product.no }}</el-descriptions-item> diff --git a/src/views/crm/product/detail/ProductDetailsInfo.vue b/src/views/crm/product/detail/ProductDetailsInfo.vue index 85046b85..ba9e89af 100644 --- a/src/views/crm/product/detail/ProductDetailsInfo.vue +++ b/src/views/crm/product/detail/ProductDetailsInfo.vue @@ -14,10 +14,10 @@ {{ productCategoryList?.find((c) => c.id === product.categoryId)?.name }} </el-descriptions-item> <el-descriptions-item label="是否上下架"> - <dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="product.status"/> + <dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="product.status" /> </el-descriptions-item> <el-descriptions-item label="单位"> - <dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="product.unit"/> + <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="product.unit" /> </el-descriptions-item> </el-descriptions> </el-collapse-item> @@ -25,12 +25,12 @@ </ContentWrap> </template> <script setup lang="ts"> -import {DICT_TYPE} from '@/utils/dict' +import { DICT_TYPE } from '@/utils/dict' import * as ProductApi from '@/api/crm/product' -import {fenToYuan} from '@/utils' +import { fenToYuan } from '@/utils' import * as ProductCategoryApi from '@/api/crm/product/productCategory' -const {product} = defineProps<{ +const { product } = defineProps<{ product: ProductApi.ProductVO }>() diff --git a/src/views/crm/product/index.vue b/src/views/crm/product/index.vue index 5100d7f4..ca9c6cda 100644 --- a/src/views/crm/product/index.vue +++ b/src/views/crm/product/index.vue @@ -60,7 +60,7 @@ <el-table-column label="产品类型" align="center" prop="categoryName" width="160" /> <el-table-column label="产品单位" align="center" prop="unit"> <template #default="scope"> - <dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="scope.row.unit" /> + <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" /> </template> </el-table-column> <el-table-column label="产品编码" align="center" prop="no" />