CRM 客户管理

pull/853/head
wersd 2025-06-12 20:20:57 +08:00
parent 293dd72420
commit d9fe53ca5b
8 changed files with 59 additions and 30 deletions

View File

@ -4,7 +4,7 @@ NODE_ENV=production
VITE_DEV=true VITE_DEV=true
# 请求路径 # 请求路径
VITE_BASE_URL='http://192.168.6.122:48080' VITE_BASE_URL='http://www.sujieguanli.top:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务 # 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=client VITE_UPLOAD_TYPE=client

View File

@ -4,7 +4,7 @@ NODE_ENV=development
VITE_DEV=true VITE_DEV=true
# 请求路径 # 请求路径
VITE_BASE_URL='http://www.woyaoshouchong.asia:48080' VITE_BASE_URL='http://www.sujieguanli.top:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务 # 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=client VITE_UPLOAD_TYPE=client

View File

@ -144,8 +144,7 @@ const formData = ref({
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }], name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
customerId: [{ required: true, message: '客户不能为空', trigger: 'blur' }], customerId: [{ required: true, message: '客户不能为空', trigger: 'blur' }]
ownerUserId: [{ required: true, message: '负责人不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // Ref const formRef = ref() // Ref
const userOptions = ref<UserApi.UserVO[]>([]) // const userOptions = ref<UserApi.UserVO[]>([]) //

View File

@ -74,7 +74,10 @@ const message = useMessage()
const contractId = ref(0) // const contractId = ref(0) //
const loading = ref(true) // const loading = ref(true) //
const contract = ref<ContractApi.ContractVO>({} as ContractApi.ContractVO) // const contract = ref<ContractApi.ContractVO>({} as ContractApi.ContractVO) //
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // Ref //
const permissionListRef = ref<InstanceType<typeof PermissionList>>(
)
/** 编辑 */ /** 编辑 */
const formRef = ref() const formRef = ref()

View File

@ -20,20 +20,22 @@
<el-descriptions-item label="成交状态:"> <el-descriptions-item label="成交状态:">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_DEAL_STATUS" :value="customer.dealStatus" /> <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_DEAL_STATUS" :value="customer.dealStatus" />
</el-descriptions-item> </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="创建时间:"> <el-descriptions-item label="创建时间:">
{{ formatDate(customer.createTime) }} {{ formatDate(customer.createTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="地址:"> <el-descriptions-item label="地址:">
{{ customer.areaName }} {{ customer.detailAddress }} {{ customer.areaName }} {{ customer.detailAddress }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="客户类别:">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="customer.industryId" /> <!-- <el-descriptions-item label="备注:">{{ customer.remark }}</el-descriptions-item> -->
</el-descriptions-item>
<el-descriptions-item label="下次联系时间:">
{{ formatDate(customer.contactNextTime, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item label="备注:">{{ customer.remark }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</ContentWrap> </ContentWrap>
</template> </template>

View File

@ -54,7 +54,7 @@
<ContractList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> <ContractList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="附件"> <el-tab-pane v-hasPermi="['crm:customer:file:query']" label="附件">
<FileList v-if="customerId" :customerId="customerId" /> <FileList v-if="customerId" :customerId="customerId" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="操作日志"> <el-tab-pane label="操作日志">

View File

@ -37,7 +37,15 @@
<span style="margin: 0 8px;"> </span> <span style="margin: 0 8px;"> </span>
</template> </template>
<span class="meta-label">跟进方式</span><dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="item.type" class="mr-10px" /> <span class="meta-label">跟进方式</span><dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="item.type" class="mr-10px" />
</div>
<!-- 操作按钮统一放右上角 -->
<div class="followup-action-btns">
<el-button link class="followup-action-btn edit" @click="openForm('update', item.id)">
<Icon icon="ep:edit" class="mr-3px" /> 编辑
</el-button>
<el-button link class="followup-action-btn delete" @click="handleDelete(item.id)">
<Icon icon="ep:delete" class="mr-3px" /> 删除
</el-button>
</div> </div>
<span class="follow-row" v-if="!/<[a-z][\s\S]*>/i.test(item.content)">{{ item.content }}</span> <span class="follow-row" v-if="!/<[a-z][\s\S]*>/i.test(item.content)">{{ item.content }}</span>
<div class="follow-row" v-else v-html="'跟进内容:' + item.content"></div> <div class="follow-row" v-else v-html="'跟进内容:' + item.content"></div>
@ -66,12 +74,6 @@
</el-link> </el-link>
</div> </div>
</div> </div>
<el-button link type="primary" class="mr-10px followup-edit-btn" @click="openForm('update', item.id)">
<Icon icon="ep:edit" class="mr-3px" /> 编辑
</el-button>
<el-button link type="danger" class="mr-20px followup-delete-btn" @click="handleDelete(item.id)">
<Icon icon="ep:delete" class="mr-3px" /> 删除
</el-button>
<template #dot> <template #dot>
<span style="background-color: #67C23A;" class="dot-node-style"> <span style="background-color: #67C23A;" class="dot-node-style">
{{ item.creatorName ? item.creatorName[0] : '' }} {{ item.creatorName ? item.creatorName[0] : '' }}
@ -238,7 +240,7 @@ watch(
} }
.dot-node-style { .dot-node-style {
position: absolute; position: absolute;
left: -5px; left: 0px;
display: flex; display: flex;
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -272,23 +274,41 @@ watch(
} }
} }
.followup-update-btn { .followup-action-btns {
color: #7492f3 !important; position: absolute;
top: 10px;
right: 16px;
display: flex;
gap: 8px;
z-index: 2;
}
.followup-action-btn {
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
transition: background 0.2s, color 0.2s;
border: none; border: none;
background: #fef0f0; background: transparent;
border-radius: 4px; border-radius: 4px;
padding: 2px 10px; padding: 2px 10px;
margin: 10px 0 0 0; transition: background 0.2s, color 0.2s;
box-shadow: none;
&.edit {
color: #409eff !important;
&:hover {
color: #337ecc !important;
background: #ecf5ff;
}
}
&.delete {
color: #f56c6c !important;
&:hover { &:hover {
background: #fef0f0;
color: #c0392b !important; color: #c0392b !important;
background: #fef0f0;
}
} }
.icon { .icon {
font-size: 15px; font-size: 15px;
vertical-align: middle; vertical-align: middle;
} }
} }
</style> </style>

View File

@ -169,6 +169,7 @@ const formData = ref({
estimatedPiles: 0, estimatedPiles: 0,
siteStatus: undefined, siteStatus: undefined,
siteType: undefined, siteType: undefined,
ownerUserId: 0,
ownerId: undefined, ownerId: undefined,
managementId: undefined, managementId: undefined,
recommenderName: undefined, recommenderName: undefined,
@ -212,6 +213,10 @@ const open = async (type: string, id?: number) => {
formLoading.value = false formLoading.value = false
} }
} }
//
if (formType.value === 'create') {
formData.value.ownerUserId = useUserStore().getUser.id
}
} }
defineExpose({ open }) // open defineExpose({ open }) // open