Skip to content

Commit 631c105

Browse files
YunaiVgitee-org
authored andcommitted
!346 feat:新增公共操作日志详情组件
Merge pull request !346 from puhui999/dev-crm
2 parents 8aa49f2 + 261d8b2 commit 631c105

File tree

8 files changed

+252
-27
lines changed

8 files changed

+252
-27
lines changed

src/api/crm/customer/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,8 @@ export const exportCustomer = async (params) => {
6767
export const queryAllList = async () => {
6868
return await request.get({ url: `/crm/customer/query-all-list` })
6969
}
70+
71+
// 查询客户操作日志
72+
export const getOperateLogPage = async (params) => {
73+
return await request.get({ url: '/crm/customer/operate-log-page', params })
74+
}

src/api/system/operatelog/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,32 @@ export type OperateLogVO = {
2323
resultData: string
2424
}
2525

26+
export type OperateLogV2VO = {
27+
id: number
28+
userNickname: string
29+
traceId: string
30+
userType: number
31+
userId: number
32+
userName: string
33+
type: string
34+
subType: string
35+
bizId: number
36+
action: string
37+
extra: string
38+
requestMethod: string
39+
requestUrl: string
40+
userIp: string
41+
userAgent: string
42+
creator: string
43+
creatorName: string
44+
createTime: Date
45+
// 数据扩展-渲染时使用
46+
title: string // 操作标题(如果为空则取 name 值)
47+
colSize: number // 变更记录行数
48+
contentStrList: string[]
49+
tagsContentList: string[]
50+
}
51+
2652
// 查询操作日志列表
2753
export const getOperateLogPage = (params: PageParam) => {
2854
return request.get({ url: '/system/operate-log/page', params })

src/components/OperateLogV2/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import OperateLogV2 from './src/OperateLogV2.vue'
2+
3+
export { OperateLogV2 }
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<template>
2+
<div class="p-20px">
3+
<el-timeline>
4+
<el-timeline-item
5+
v-for="(log, index) in logDataList"
6+
:key="index"
7+
:timestamp="formatDate(log.createTime)"
8+
placement="top"
9+
>
10+
<div class="el-timeline-right-content">
11+
<el-row>
12+
<el-col :span="24" class="mb-10px">
13+
=======================
14+
<el-tag class="mr-10px" type="success">{{ log.userName }}</el-tag>
15+
<span>{{ log.title }}</span>
16+
=======================
17+
</el-col>
18+
<!-- 先处理一下有几行-->
19+
<template v-for="colNum in log.colSize" :key="colNum + 'col'">
20+
<el-col :span="24" class="mb-10px">
21+
<!-- 处理每一行-->
22+
<template
23+
v-for="(tagVal, index2) in log.tagsContentList.slice(
24+
(colNum - 1) * 3,
25+
3 * colNum
26+
)"
27+
:key="index2"
28+
>
29+
<el-tag class="mx-10px"> {{ tagVal }}</el-tag>
30+
<span>{{ log.contentStrList[index2] }}</span>
31+
</template>
32+
</el-col>
33+
</template>
34+
</el-row>
35+
</div>
36+
<template #dot>
37+
<span :style="{ backgroundColor: getUserTypeColor(log.userType) }" class="dot-node-style">
38+
{{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}
39+
</span>
40+
</template>
41+
</el-timeline-item>
42+
</el-timeline>
43+
</div>
44+
</template>
45+
46+
<script lang="ts" setup>
47+
import { OperateLogV2VO } from '@/api/system/operatelog'
48+
import { formatDate } from '@/utils/formatTime'
49+
import { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict'
50+
import { ElTag } from 'element-plus'
51+
52+
const props = defineProps<{
53+
logList: OperateLogV2VO[] // 操作日志列表
54+
}>()
55+
defineOptions({ name: 'OperateLogV2' })
56+
57+
/** 获得 userType 颜色 */
58+
const getUserTypeColor = (type: number) => {
59+
const dict = getDictObj(DICT_TYPE.USER_TYPE, type)
60+
switch (dict?.colorType) {
61+
case 'success':
62+
return '#67C23A'
63+
case 'info':
64+
return '#909399'
65+
case 'warning':
66+
return '#E6A23C'
67+
case 'danger':
68+
return '#F56C6C'
69+
}
70+
return '#409EFF'
71+
}
72+
const logDataList = ref<OperateLogV2VO[]>([]) // 操作日志列表
73+
// 提取 tag 所需内容和位置
74+
const renderTags = (content: string) => {
75+
let newStr = unref(content).slice() // 去掉引用
76+
newStr = newStr.replaceAll('【】', '【空】').replaceAll('', '') // 处理掉分号 特殊:处理一下空的情况
77+
const regex = /【([^【】]+)】/g
78+
const fg = '|' // 原始位置替换符号
79+
let match: any[] | null
80+
let matchStr: string[] = []
81+
let oldStr: string[] = []
82+
while ((match = regex.exec(newStr)) !== null) {
83+
matchStr.push(match[1]) // 提取值
84+
oldStr.push(match[0]) // 原值
85+
}
86+
// 为什么重新循环不放在 while 中一起是因为替换重新赋值过后 match 值就不准确了
87+
oldStr.forEach((item) => {
88+
newStr = newStr.replace(item, fg)
89+
})
90+
return [newStr.split(fg), matchStr]
91+
}
92+
const initLog = () => {
93+
logDataList.value = props.logList.map((logItem) => {
94+
const keyValue = renderTags(logItem.action)
95+
// 挂载数据
96+
logItem.contentStrList = keyValue[0]
97+
if (keyValue[0][0] === '') {
98+
logItem.title = logItem.subType
99+
} else {
100+
logItem.title = keyValue[0][0]
101+
logItem.contentStrList.splice(0, 1)
102+
}
103+
logItem.colSize = keyValue[0].length / 3 // 变更记录行数
104+
logItem.tagsContentList = keyValue[1]
105+
return logItem
106+
})
107+
}
108+
watch(
109+
() => props.logList.length,
110+
(newObj) => {
111+
if (newObj) {
112+
initLog()
113+
console.log(logDataList.value)
114+
}
115+
},
116+
{
117+
immediate: true,
118+
deep: true
119+
}
120+
)
121+
</script>
122+
123+
<style lang="scss" scoped>
124+
// 时间线样式调整
125+
:deep(.el-timeline) {
126+
margin: 10px 0 0 160px;
127+
128+
.el-timeline-item__wrapper {
129+
position: relative;
130+
top: -20px;
131+
132+
.el-timeline-item__timestamp {
133+
position: absolute !important;
134+
top: 10px;
135+
left: -150px;
136+
}
137+
}
138+
139+
.el-timeline-right-content {
140+
display: flex;
141+
align-items: center;
142+
min-height: 30px;
143+
padding: 10px;
144+
background-color: #fff;
145+
146+
&::before {
147+
position: absolute;
148+
top: 10px;
149+
left: 13px; /* 将伪元素水平居中 */
150+
border-color: transparent #fff transparent transparent; /* 尖角颜色,左侧朝向 */
151+
border-style: solid;
152+
border-width: 8px; /* 调整尖角大小 */
153+
content: ''; /* 必须设置 content 属性 */
154+
}
155+
}
156+
}
157+
158+
.dot-node-style {
159+
position: absolute;
160+
left: -5px;
161+
display: flex;
162+
width: 20px;
163+
height: 20px;
164+
font-size: 10px;
165+
color: #fff;
166+
border-radius: 50%;
167+
justify-content: center;
168+
align-items: center;
169+
}
170+
</style>

src/views/crm/customer/CustomerForm.vue

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<template>
2-
<Dialog :title="dialogTitle" v-model="dialogVisible">
2+
<Dialog v-model="dialogVisible" :title="dialogTitle">
33
<el-form
44
ref="formRef"
5+
v-loading="formLoading"
56
:model="formData"
67
:rules="formRules"
78
label-width="100px"
8-
v-loading="formLoading"
99
>
1010
<el-row>
1111
<el-col :span="12">
@@ -17,7 +17,7 @@
1717
<el-form-item label="所属行业" prop="industryId">
1818
<el-select v-model="formData.industryId" placeholder="请选择所属行业">
1919
<el-option
20-
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
20+
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
2121
:key="dict.value"
2222
:label="dict.label"
2323
:value="dict.value"
@@ -31,7 +31,7 @@
3131
<el-form-item label="客户来源" prop="source">
3232
<el-select v-model="formData.source" placeholder="请选择客户来源">
3333
<el-option
34-
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
34+
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
3535
:key="dict.value"
3636
:label="dict.label"
3737
:value="dict.value"
@@ -43,7 +43,7 @@
4343
<el-form-item label="客户等级" prop="level">
4444
<el-select v-model="formData.level" placeholder="请选择客户等级">
4545
<el-option
46-
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
46+
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
4747
:key="dict.value"
4848
:label="dict.label"
4949
:value="dict.value"
@@ -120,9 +120,9 @@
120120
<el-form-item label="下次联系时间" prop="contactNextTime">
121121
<el-date-picker
122122
v-model="formData.contactNextTime"
123+
placeholder="选择下次联系时间"
123124
type="date"
124125
value-format="x"
125-
placeholder="选择下次联系时间"
126126
/>
127127
</el-form-item>
128128
</el-col>
@@ -139,13 +139,13 @@
139139
</el-col>
140140
</el-form>
141141
<template #footer>
142-
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
142+
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
143143
<el-button @click="dialogVisible = false">取 消</el-button>
144144
</template>
145145
</Dialog>
146146
</template>
147-
<script setup lang="ts">
148-
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
147+
<script lang="ts" setup>
148+
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
149149
import * as CustomerApi from '@/api/crm/customer'
150150
import * as AreaApi from '@/api/system/area'
151151
import { defaultProps } from '@/utils/tree'

src/views/crm/customer/detail/CustomerDetailsHeader.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<el-button v-hasPermi="['crm:customer:update']" @click="openForm(customer.id)">
1515
编辑
1616
</el-button>
17+
<el-button @click="transfer">转移</el-button>
1718
<el-button>更改成交状态</el-button>
1819
</div>
1920
</div>
@@ -26,22 +27,24 @@
2627
<el-descriptions-item label="成交状态">
2728
{{ customer.dealStatus ? '已成交' : '未成交' }}
2829
</el-descriptions-item>
29-
<el-descriptions-item label="负责人">{{ customer.ownerUserName }} </el-descriptions-item>
30+
<el-descriptions-item label="负责人">{{ customer.ownerUserName }}</el-descriptions-item>
3031
<!-- TODO wanwan 首要联系人? -->
3132
<el-descriptions-item label="首要联系人" />
3233
<!-- TODO wanwan 首要联系人电话? -->
33-
<el-descriptions-item label="首要联系人电话">{{ customer.mobile }} </el-descriptions-item>
34+
<el-descriptions-item label="首要联系人电话">{{ customer.mobile }}</el-descriptions-item>
3435
</el-descriptions>
3536
</ContentWrap>
3637

3738
<!-- 表单弹窗:添加/修改 -->
3839
<CustomerForm ref="formRef" @success="emit('refresh')" />
3940
</template>
40-
<script setup lang="ts">
41+
<script lang="ts" setup>
4142
import { DICT_TYPE } from '@/utils/dict'
4243
import * as CustomerApi from '@/api/crm/customer'
4344
import CustomerForm from '../CustomerForm.vue'
4445
46+
defineOptions({ name: 'CustomerDetailsHeader' })
47+
4548
const { customer, loading } = defineProps<{
4649
customer: CustomerApi.CustomerVO // 客户信息
4750
loading: boolean // 加载中

src/views/crm/customer/detail/CustomerDetailsInfo.vue

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<ContentWrap>
3-
<el-collapse class="" v-model="activeNames">
3+
<el-collapse v-model="activeNames" class="">
44
<el-collapse-item name="basicInfo">
55
<template #title>
66
<span class="text-base font-bold">基本信息</span>
@@ -20,11 +20,11 @@
2020
</el-descriptions-item>
2121
<el-descriptions-item label="手机">{{ customer.mobile }}</el-descriptions-item>
2222
<el-descriptions-item label="电话">{{ customer.telephone }}</el-descriptions-item>
23-
<el-descriptions-item label="邮箱">{{ customer.email }} </el-descriptions-item>
24-
<el-descriptions-item label="QQ">{{ customer.qq }} </el-descriptions-item>
25-
<el-descriptions-item label="微信">{{ customer.wechat }} </el-descriptions-item>
26-
<el-descriptions-item label="网址">{{ customer.website }} </el-descriptions-item>
27-
<el-descriptions-item label="所在地">{{ customer.areaName }} </el-descriptions-item>
23+
<el-descriptions-item label="邮箱">{{ customer.email }}</el-descriptions-item>
24+
<el-descriptions-item label="QQ">{{ customer.qq }}</el-descriptions-item>
25+
<el-descriptions-item label="微信">{{ customer.wechat }}</el-descriptions-item>
26+
<el-descriptions-item label="网址">{{ customer.website }}</el-descriptions-item>
27+
<el-descriptions-item label="所在地">{{ customer.areaName }}</el-descriptions-item>
2828
<el-descriptions-item label="详细地址"
2929
>{{ customer.detailAddress }}
3030
</el-descriptions-item>
@@ -38,17 +38,17 @@
3838
</el-descriptions-item>
3939
</el-descriptions>
4040
<el-descriptions :column="1">
41-
<el-descriptions-item label="客户描述">{{ customer.description }} </el-descriptions-item>
42-
<el-descriptions-item label="备注">{{ customer.remark }} </el-descriptions-item>
41+
<el-descriptions-item label="客户描述">{{ customer.description }}</el-descriptions-item>
42+
<el-descriptions-item label="备注">{{ customer.remark }}</el-descriptions-item>
4343
</el-descriptions>
4444
</el-collapse-item>
4545
<el-collapse-item name="systemInfo">
4646
<template #title>
4747
<span class="text-base font-bold">系统信息</span>
4848
</template>
4949
<el-descriptions :column="2">
50-
<el-descriptions-item label="负责人">{{ customer.ownerUserName }} </el-descriptions-item>
51-
<el-descriptions-item label="创建人">{{ customer.creatorName }} </el-descriptions-item>
50+
<el-descriptions-item label="负责人">{{ customer.ownerUserName }}</el-descriptions-item>
51+
<el-descriptions-item label="创建人">{{ customer.creatorName }}</el-descriptions-item>
5252
<el-descriptions-item label="创建时间">
5353
{{ customer.createTime ? formatDate(customer.createTime) : '空' }}
5454
</el-descriptions-item>
@@ -60,15 +60,16 @@
6060
</el-collapse>
6161
</ContentWrap>
6262
</template>
63-
<script setup lang="ts">
63+
<script lang="ts" setup>
6464
import * as CustomerApi from '@/api/crm/customer'
6565
import { DICT_TYPE } from '@/utils/dict'
6666
import { formatDate } from '@/utils/formatTime'
6767
68+
defineOptions({ name: 'CustomerDetailsInfo' })
6869
const { customer } = defineProps<{
6970
customer: CustomerApi.CustomerVO // 客户明细
7071
}>()
7172
7273
const activeNames = ref(['basicInfo', 'systemInfo']) // 展示的折叠面板
7374
</script>
74-
<style scoped lang="scss"></style>
75+
<style lang="scss" scoped></style>

0 commit comments

Comments
 (0)