CRM 客户管理 权限优化

pull/853/head
wersd 2025-06-15 15:40:16 +08:00
parent dc245aa345
commit d804171fed
15 changed files with 108 additions and 71 deletions

View File

@ -36,7 +36,6 @@ export enum BizTypeEnum {
CRM_RECEIVABLE = 7, // 回款
CRM_RECEIVABLE_PLAN = 8 // 回款计划
}
/**
* CRM
*/

View File

@ -1,9 +1,11 @@
import request from '@/config/axios'
import { da } from 'element-plus/es/locale'
export interface UserVO {
id: number
username: string
nickname: string
sort: number
deptId: number
postIds: string[]
email: string
@ -73,4 +75,4 @@ export const updateUserStatus = (id: number, status: number) => {
// 获取用户精简信息列表
export const getSimpleUserList = (): Promise<UserVO[]> => {
return request.get({ url: '/system/user/simple-list' })
}
}

View File

@ -58,7 +58,7 @@ import * as ContactApi from '@/api/crm/contact'
import ContactForm from './../ContactForm.vue'
import { DICT_TYPE } from '@/utils/dict'
import { BizTypeEnum } from '@/api/crm/permission'
import ContactListModal from './ContactListModal.vue'
defineOptions({ name: 'CrmContactList' })
const props = defineProps<{

View File

@ -62,7 +62,7 @@ import { getOperateLogPage } from '@/api/crm/operateLog'
import ContractForm from '@/views/crm/contract/ContractForm.vue'
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
import PermissionList from '@/views/crm/permission/components/PermissionList.vue'
import FollowUpList from '@/views/crm/followup/index.vue'
import FollowUpList from '@/views/crm/followup/components/FollowUpList.vue'
import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue'
import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue'

View File

@ -16,23 +16,19 @@
</div>
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="4" direction="horizontal" class="mb-10px">
<el-descriptions :column="3" direction="horizontal" class="mb-10px">
<el-descriptions-item label="成交状态:">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_DEAL_STATUS" :value="customer.dealStatus ?? ''" />
</el-descriptions-item>
<el-descriptions-item label="客户类别:">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="customer.industryId ?? ''" />
</el-descriptions-item>
<el-descriptions-item label="负责人:">{{ customer.ownerUserName }}</el-descriptions-item>
<el-descriptions-item label="责任人:">{{ customer.ownerUserName }}</el-descriptions-item>
<el-descriptions-item label="下次联系时间:">
{{ formatDate(customer.contactNextTime, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item label="创建时间:">
{{ formatDate(customer.createTime) }}
</el-descriptions-item>
<el-descriptions-item label="地址:">
{{ customer.areaName }} {{ customer.detailAddress }}
{{ customer.areaName }}{{ customer.detailAddress }}
</el-descriptions-item>
<!-- <el-descriptions-item label="备注:">{{ customer.remark }}</el-descriptions-item> -->

View File

@ -25,6 +25,7 @@
>
锁定
</el-button>
<text>{{ customerId }}</text>
<!-- <el-button v-if="!customer.ownerUserId" type="primary" @click="handleReceive"> </el-button>
<el-button v-if="!customer.ownerUserId" type="primary" @click="handleDistributeForm">
分配
@ -33,13 +34,13 @@
<el-col>
<el-tabs>
<el-tab-pane v-if="checkPermi(['crm:follow-up-record:query'])" label="跟进记录">
<FollowUpList :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
<FollowUpList :biz-id="customer.id" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane>
<!-- <el-tab-pane label="基本信息">
<CustomerDetailsInfo :customer="customer" />
</el-tab-pane> -->
<el-tab-pane v-if="checkPermi(['crm:contact:query'])" label="联系人" lazy>
<ContactList :biz-id="customerId" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
<el-tab-pane v-if="checkPermi(['crm:contact:query']) && customer.id != undefined" label="联系人" lazy>
<ContactList :biz-id="customer.id" :biz-type="BizTypeEnum.CRM_CUSTOMER" :customerId="customer.id" />
</el-tab-pane>
<!-- <el-tab-pane label="团队成员">
<PermissionList

View File

@ -11,27 +11,27 @@
<el-form-item label="客户名称" prop="name">
<el-input
v-model="queryParams.name"
class="!w-200px"
class="!w-150px"
clearable
placeholder="请输入客户名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="地区" prop="areaId">
<el-cascader
v-model="queryParams.areaId"
:options="areaList"
:props="defaultProps"
class="!w-200px"
class="!w-150px"
clearable
filterable
placeholder="请选择地区"
/>
</el-form-item>
</el-form-item>
<el-form-item label="客户类别" prop="industryId">
<el-select
v-model="queryParams.industryId"
class="!w-200px"
class="!w-150px"
clearable
placeholder="请选择客户类别"
>
@ -46,7 +46,7 @@
<el-form-item label="成交状态" prop="dealStatus">
<el-select
v-model="queryParams.dealStatus"
class="!w-200px"
class="!w-150px"
clearable
placeholder="请选择成交状态"
>
@ -58,6 +58,23 @@
/>
</el-select>
</el-form-item>
<el-form-item label="责任人">
<el-select
v-model="queryParams.ownerUserId"
filterable
remote
clearable
placeholder="请选择责任人"
style="width: 150px"
>
<el-option
v-for="user in userOptions"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
@ -189,7 +206,9 @@ import CustomerForm from './CustomerForm.vue'
import CustomerImportForm from './CustomerImportForm.vue'
import { defaultProps } from '@/utils/tree'
import { TabsPaneContext } from 'element-plus'
import * as UserApi from '@/api/system/user'
const userOptions = ref<any[]>([])
defineOptions({ name: 'CrmCustomer' })
const message = useMessage() //
@ -207,6 +226,7 @@ const queryParams = reactive({
areaId: undefined,
dealStatus: undefined,
industryId: undefined,
ownerUserId: undefined,
level: undefined,
source: undefined,
pool: undefined
@ -309,6 +329,10 @@ watch(
/** 初始化 **/
onMounted(() => {
//
UserApi.getSimpleUserList().then(res => {
userOptions.value = res
})
getList()
AreaApi.getAreaTree().then(res => {
areaList.value = res

View File

@ -2,11 +2,25 @@
<template>
<!-- 搜索栏 -->
<el-form :inline="true" :model="queryParams" class="mb-10px">
<el-form-item label="跟进内容">
<el-input v-model="queryParams.content" placeholder="请输入跟进内容" clearable @keyup.enter="handleQuery" />
<el-form-item label="跟进人">
<el-select
v-model="queryParams.creator"
filterable
remote
clearable
placeholder="请选择跟进人"
style="width: 150px"
>
<el-option
v-for="user in userOptions"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进方式">
<el-select v-model="queryParams.type" placeholder="请选择跟进方式" clearable>
<el-select v-model="queryParams.type" placeholder="请选择跟进方式" clearable style="width: 150px">
<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>
@ -17,9 +31,6 @@
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" />重置
</el-button>
<el-button v-hasPermi="['crm:follow-up-record:create']" @click="openForm">
<Icon icon="ep:edit" class="mr-5px" />写跟进
</el-button>
</el-form-item>
</el-form>
@ -32,6 +43,10 @@
placement="top"
>
<span>跟进人</span>{{ item.creatorName }}
<span v-if="Array.isArray(item.customer) && item.customer.length > 0 && item.customer[0].name" style="margin-left: 8px;">
客户名称{{ item.customer[0].name }}
</span>
<template #timestamp>
<div style="color:#999;font-size:13px;margin-bottom:2px;">跟进时间</div>
<span>{{ formatDate(item.createTime) }}</span>
@ -115,6 +130,10 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
import FollowUpRecordForm from './FollowUpRecordForm.vue'
import { BizTypeEnum } from '@/api/crm/permission'
import * as UserApi from '@/api/system/user'
const userOptions = ref<any[]>([])
/** 跟进记录列表 */
defineOptions({ name: 'FollowUpRecord' })
@ -132,9 +151,10 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
bizType: 0,
bizId: undefined as number | undefined, //
bizId: undefined as number | undefined,
content: '',
type: undefined
type: undefined,
creator: undefined as number | undefined //
})
/** 查询列表 */
@ -152,21 +172,11 @@ const getList = async () => {
/** 添加/修改操作 */
const formRef = ref<InstanceType<typeof FollowUpRecordForm>>()
//
const openForm = (type: 'create' | 'update' = 'create', id?: number) => {
// bizId
if (typeof props.bizId === 'number') {
if (type === 'update' && id) {
formRef.value?.open(props.bizType, props.bizId, id)
} else {
formRef.value?.open(props.bizType, props.bizId)
}
} else {
if (type === 'update' && id) {
formRef.value?.open(props.bizType, undefined, id)
} else {
formRef.value?.open(props.bizType, undefined)
}
}
const openForm = (type: 'create' | 'update', id: number) => {
formRef.value?.open(type, id)
}
/** 删除按钮操作 */
@ -211,10 +221,16 @@ const handleQuery = () => {
}
const resetQuery = () => {
queryParams.content = ''
queryParams.creator = undefined
queryParams.type = undefined
handleQuery()
}
onMounted(async () => {
//
userOptions.value = await UserApi.getSimpleUserList()
})
watch(
() => props.bizId,
() => {

View File

@ -1,6 +1,6 @@
<!-- 跟进记录的添加表单 -->
<template>
<Dialog v-model="dialogVisible" title="添加跟进记录" width="50%">
<Dialog v-model="dialogVisible" title="添加跟进记录" width="70%">
<el-form
ref="formRef"
v-loading="formLoading"
@ -30,7 +30,7 @@
</el-col>
<el-col :span="24">
<el-form-item label="跟进内容" prop="content">
<Editor v-model="formData.content" height="150px" />
<Editor v-model="formData.content" height="300px" />
</el-form-item>
</el-col>
<el-col :span="12">
@ -103,12 +103,7 @@ watch(
//
const res = await ContactApi.getContactPageByCustomer({ customerId: val, pageSize: 100 })
contactOptions.value = res.list || []
//
if (contactOptions.value.length > 0) {
formData.value.contactIds = [contactOptions.value[0].id]
} else {
formData.value.contactIds = []
}
} else {
contactOptions.value = []
formData.value.contactIds = []
@ -118,13 +113,12 @@ watch(
)
/** 打开弹窗 */
const open = async (bizType: number, bizId: number, id?: number) => {
const open = async (type: string, id: number) => {
dialogVisible.value = true
resetForm()
formData.value.bizType = bizType
formData.value.bizId = bizId
if (id) {
formType.value = 'update'
formType.value = type
if (formType.value === 'update') {
formLoading.value = true
try {
const data = await FollowUpRecordApi.getFollowUpRecord(id)
@ -132,8 +126,6 @@ const open = async (bizType: number, bizId: number, id?: number) => {
} finally {
formLoading.value = false
}
} else {
formType.value = 'create'
}
}
defineExpose({ open }) // open

View File

@ -77,7 +77,7 @@ const open = async (bizId: number) => {
dialogTitle.value = getDialogTitle()
resetForm()
formData.value.id = bizId
//
//
if (props.bizType === BizTypeEnum.CRM_CUSTOMER) {
formData.value.toBizTypes = [BizTypeEnum.CRM_CONTACT, BizTypeEnum.CRM_CONTRACT, BizTypeEnum.CRM_CUSTOMER]
}

View File

@ -73,22 +73,21 @@
<el-table-column label="场地名称" fixed="left" align="center" prop="name" />
<el-table-column align="center" label="地区" prop="areaId" width="200px" >
<template #default="scope">
<span>{{ scope.row.areaName || '暂无' }} {{ scope.row.detailAddress }}</span>
<span>{{ scope.row.areaName || '暂无' }}</span>
</template>
</el-table-column>
<el-table-column label="定位经度" align="center" prop="locationLng" />
<el-table-column label="定位纬度" align="center" prop="locationLat" />
<el-table-column label="推荐人" align="center" prop="recommenderName" />
<el-table-column label="可建充电桩数" align="center" prop="estimatedPiles" />
<el-table-column label="场地状态" align="center" prop="siteStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_STATION_SITE_STATUS" :value="scope.row.siteStatus" />
</template>
</el-table-column>
<el-table-column label="场地类型" align="center" prop="siteType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_STATION_SITE_TYPE" :value="scope.row.siteType" />
</template>
</el-table-column>
<el-table-column label="场地状态" align="center" prop="siteStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_STATION_SITE_STATUS" :value="scope.row.siteStatus" />
</template>
</el-table-column>
<el-table-column label="填报人" align="center" prop="creatorName" />
<el-table-column label="操作" fixed="right" align="center" min-width="180px">
<template #default="scope">

View File

@ -63,10 +63,9 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="岗位编号" align="center" prop="id" />
<el-table-column label="岗位顺序" align="center" prop="sort" />
<el-table-column label="岗位名称" align="center" prop="name" />
<el-table-column label="岗位编码" align="center" prop="code" />
<el-table-column label="岗位顺序" align="center" prop="sort" />
<el-table-column label="岗位备注" align="center" prop="remark" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">

View File

@ -85,7 +85,7 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="角色编号" prop="id" />
<el-table-column align="center" label="显示顺序" prop="sort" />
<el-table-column align="center" label="角色名称" prop="name" />
<el-table-column label="角色类型" align="center" prop="type">
<template #default="scope">
@ -93,7 +93,6 @@
</template>
</el-table-column>
<el-table-column align="center" label="角色标识" prop="code" />
<el-table-column align="center" label="显示顺序" prop="sort" />
<el-table-column align="center" label="备注" prop="remark" />
<el-table-column align="center" label="状态" prop="status">
<template #default="scope">

View File

@ -26,6 +26,13 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="显示顺序" prop="sort">
<el-input v-model="formData.sort" maxlength="11" placeholder="请选择显示顺序" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="手机号码" prop="mobile">
@ -119,6 +126,7 @@ const formData = ref({
mobile: '',
email: '',
id: undefined,
sort: 0,
username: '',
password: '',
sex: undefined,
@ -131,6 +139,7 @@ const formRules = reactive<FormRules>({
username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }],
email: [
{
type: 'email',
@ -206,6 +215,7 @@ const resetForm = () => {
mobile: '',
email: '',
id: undefined,
sort: 0,
username: '',
password: '',
sex: undefined,

View File

@ -96,7 +96,7 @@
</ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="用户编号" align="center" key="id" prop="id" />
<el-table-column align="center" label="显示顺序" prop="sort" />
<el-table-column
label="用户名称"
align="center"