ソースを参照

接单中心详情创建

chenyidong 3 ヶ月 前
コミット
0caad9363c

+ 8 - 0
pages.json

@@ -206,6 +206,14 @@
206 206
 				"enablePullDownRefresh": true,
207 207
 				"navigationStyle": "custom"
208 208
 			}
209
+		},
210
+		{
211
+			"path": "pages/orderDetail/index",
212
+			"style": {
213
+				"navigationBarTitleText": "",
214
+				"enablePullDownRefresh": true,
215
+				"navigationStyle": "custom"
216
+			}
209 217
 		}
210 218
 	],
211 219
 	"globalStyle": {

+ 38 - 275
pages/order/components/commission/filterQuery.vue

@@ -5,7 +5,7 @@
5 5
 				<view class="filterQuery" :style="{ height : mapHeight }">
6 6
 						<view class="query_wrap">
7 7
 							<view class="title">
8
-								单日期
8
+								单日期
9 9
 							</view>
10 10
 							<view class="dept_wrap form_input_pointer_events" @click="handleShowJtimePickerPopup">
11 11
 								<u--input v-model="createTimeTxt" disabled style='pointer-events: none !important'
@@ -13,44 +13,28 @@
13 13
 								</u--input>
14 14
 								<u-icon slot="right" name="arrow-right"></u-icon>
15 15
 							</view>
16
-				
16
+
17 17
 							<view class="title">
18
-								机构部门
18
+								物品
19 19
 							</view>
20
-							<view class="dept_wrap form_input_pointer_events" @click="handleShowDept">
21
-								<u--input v-model="value.deptName" disabled style='pointer-events: none !important'
22
-									disabledColor="#ffffff" placeholder="点击选择" border="none">
23
-								</u--input>
24
-								<u-icon slot="right" name="arrow-right"></u-icon>
20
+							<view class="search_input">
21
+								<u--input clearable prefixIcon="search" v-model="value.item"
22
+									placeholder="请输入物品名字"></u--input>
25 23
 							</view>
26 24
 				
27 25
 							<view class="title">
28
-								接单人
26
+								电话
29 27
 							</view>
30
-							<view class="person_wrap">
31
-								<view class="person_item" v-for="(item,index) in identificationUserInfos" :key="item.userId">
32
-									<view class="person_top">
33
-										<u-avatar :src="$avatar(item.avatar)" size="40px"></u-avatar>
34
-									</view>
35
-									<view class="person_bottom">
36
-										{{item.nickName}}
37
-									</view>
38
-								</view>
39
-								<view class="person_item more_item" @click="handleShowIdentificationUser">
40
-									<view class="person_top">
41
-										<image src="/static/case/icon-more.png" mode="" class="more"></image>
42
-									</view>
43
-									<view class="person_bottom">
44
-										更多
45
-									</view>
46
-								</view>
28
+							<view class="search_input">
29
+								<u--input clearable prefixIcon="search" v-model="value.phone"
30
+									placeholder="请输入电话"></u--input>
47 31
 							</view>
48 32
 
49 33
 							<view class="title">
50
-								单人
34
+								收单人
51 35
 							</view>
52 36
 							<view class="person_wrap">
53
-								<view class="person_item" v-for="(item,index) in createByUserInfos" :key="item.userId">
37
+								<view class="person_item" v-for="(item,index) in receiptUserInfos" :key="item.userId">
54 38
 									<view class="person_top">
55 39
 										<u-avatar :src="$avatar(item.avatar)" size="40px"></u-avatar>
56 40
 									</view>
@@ -58,7 +42,7 @@
58 42
 										{{item.nickName}}
59 43
 									</view>
60 44
 								</view>
61
-								<view class="person_item more_item" @click="handleShowCreateByUser">
45
+								<view class="person_item more_item" @click="handleShowReceiptUser">
62 46
 									<view class="person_top">
63 47
 										<image src="/static/case/icon-more.png" mode="" class="more"></image>
64 48
 									</view>
@@ -67,37 +51,6 @@
67 51
 									</view>
68 52
 								</view>
69 53
 							</view>
70
-
71
-							<view class="title">
72
-								物品
73
-							</view>
74
-							<view class="search_input">
75
-								<u--input clearable prefixIcon="search" v-model="value.item"
76
-									placeholder="请输入物品名字"></u--input>
77
-							</view>
78
-				
79
-							<view class="title">
80
-								品牌
81
-							</view>
82
-							<view class="search_input">
83
-								<u--input clearable prefixIcon="search" v-model="value.brand" placeholder="请输入品牌名字"></u--input>
84
-							</view>
85
-				
86
-							<view class="title">
87
-								电话
88
-							</view>
89
-							<view class="search_input">
90
-								<u--input clearable prefixIcon="search" v-model="value.phone"
91
-									placeholder="请输入电话"></u--input>
92
-							</view>
93
-
94
-							<view class="title">
95
-								标签
96
-							</view>
97
-							<view class="option_list">
98
-								<ld-select :list="clueTagGroupVoList" label-key="name" value-key="id" placeholder="请选择标签"
99
-									v-model="value.allTagList" multiple clearable></ld-select>
100
-							</view>
101 54
 				
102 55
 						</view>
103 56
 						<u-tabbar :fixed="true" inactiveColor="#ffffff" class="case_tabbar" :placeholder="true"
@@ -108,24 +61,15 @@
108 61
 								@click="handleEnter"></u-tabbar-item>
109 62
 						</u-tabbar>
110 63
 				
111
-						<ba-tree-picker :selectParent="false" v-if="identificationUserListData.length > 0" ref="identificationUser" :multiple="false"
112
-							border title="接单人" :localdata="identificationUserListData" valueKey="id" textKey="label" childrenKey="children"
113
-							:selectedValues="value.identification" @select-change="identificationUserSeletchang" />
114
-				
115
-						<ba-tree-picker :selectParent="false" v-if="createByUserListData.length > 0" ref="createByUser" :multiple="false"
116
-							border title="发单人" :localdata="createByUserListData" valueKey="id" textKey="label" childrenKey="children"
117
-							:selectedValues="value.createBy" @select-change="createByUserSeletchang" />
118
-				
119
-						<ba-tree-picker :selectParent="true" v-if="deptList.length > 0" ref="dept" :multiple='false'
120
-							@select-change="deptSeletchang" border title="机构部门" :localdata="deptList" valueKey="deptId"
121
-							textKey="deptName" childrenKey="children" :selectedValues="value.deptId"
122
-							:personNames="value.deptName" />
64
+						<ba-tree-picker :selectParent="false" v-if="receiptUserListData.length > 0" ref="receiptUser" :multiple="false"
65
+							border title="收单人" :localdata="receiptUserListData" valueKey="id" textKey="label" childrenKey="children"
66
+							:selectedValues="value.receiptUserId" @select-change="receiptUserSeletchang" />
123 67
 				
124 68
 						<jtimePickerPopup :isShowShortTimeList="true" shortTimeTitle="快捷时间" :shortTimeList="shortTimeList"
125 69
 							:isShowSeletTimeTitle="true" seletTimeTitle="时间选择" cancelText="取消" confirmText="确认"
126 70
 							:endSelectMonth="-1" :endSelectDay="-1" beginTimePlaceHolder='开始时间' endTimePlaceHolder="结束时间"
127 71
 							:isDateTypeRange="true" :isShowSelectedTimeEcho="true" @confirm="getSelectTime"
128
-							ref="jtimePickerPopup">
72
+							ref="jtimePickerPopup" :default-select="1">
129 73
 						</jtimePickerPopup>
130 74
 				
131 75
 					</view>
@@ -136,8 +80,7 @@
136 80
 
137 81
 <script>
138 82
 	import {
139
-		filterCustomerManager,
140
-		handleTree
83
+		filterCustomerManager
141 84
 	} from '@/utils/util';
142 85
 	import jtimePickerPopup from '@/uni_modules/jtime-picker-popup/components/JTimePicker/JTimePicker.vue';
143 86
 	export default {
@@ -155,51 +98,8 @@
155 98
 		emits: ["getList"],
156 99
 		data() {
157 100
 			return {
158
-
159
-				statusDict: [{
160
-						dictLabel: '待处理',
161
-						dictValue: '1'
162
-					},
163
-					{
164
-						dictLabel: '进行中',
165
-						dictValue: '2'
166
-					},
167
-					{
168
-						dictLabel: '已完成',
169
-						dictValue: '3'
170
-					},
171
-					{
172
-						dictLabel: '已取消',
173
-						dictValue: '4'
174
-					},
175
-				],
176
-				
177
-				stateDict: [{
178
-						dictLabel: '初始阶段',
179
-						dictValue: '1'
180
-					},
181
-					{
182
-						dictLabel: '处理阶段',
183
-						dictValue: '2'
184
-					},
185
-					{
186
-						dictLabel: '完成阶段',
187
-						dictValue: '3'
188
-					},
189
-				],
190
-
191
-
192 101
 				showFilter: false,
193
-
194
-				dictCascadeData: [],
195
-
196
-				identificationUserListData: [], // 接单人列表 
197
-				createByUserListData: [], // 发单人列表 
198
-
199
-				deptList: [],
200
-
201
-				dateRange: [], // 日期范围
202
-
102
+				receiptUserListData: [], // 收单人列表 
203 103
 				shortTimeList: [{
204 104
 						unit: 'day',
205 105
 						key: '全部',
@@ -241,45 +141,12 @@
241 141
 						value: -1
242 142
 					},
243 143
 				],
244
-
245 144
 				createTimeTxt: null,
246
-
247
-				identificationUserInfos: [], // 接单人用户信息
248
-				createByUserInfos: [], // 发单人用户信息
249
-
250
-				clueTagGroupVoList: [],
251
-
252
-				defaultRegion: ['广东省', '广州市', '番禺区'],
253
-
254
-				sortParams: [{
255
-					value: 1,
256
-					text: '企业名称'
257
-				}, {
258
-					value: 2,
259
-					text: '项目名称'
260
-				}, {
261
-					value: 3,
262
-					text: '项目状态'
263
-				}, {
264
-					value: 4,
265
-					text: '项目等级'
266
-				}, {
267
-					value: 5,
268
-					text: '省市区'
269
-				}, {
270
-					value: 6,
271
-					text: '委托日期'
272
-				}, {
273
-					value: 7,
274
-					text: '创建日期'
275
-				}, {
276
-					value: 8,
277
-					text: '跟进时间'
278
-				}]
145
+				receiptUserInfos: [], // 收单人用户信息
279 146
 			}
280 147
 		},
281 148
 		mounted() {
282
-			this.handleInitTag();
149
+			this.handleInitReceiptUser();
283 150
 		},
284 151
 		methods: {
285 152
 			handleOpen(){
@@ -287,131 +154,58 @@
287 154
 					this.$refs.jtimePickerPopup.handleInit();
288 155
 				}
289 156
 			},
290
-
291 157
 			getSelectTime(timeValue) {
292
-				this.value.sendDateStart = timeValue.beginTime;
293
-				this.value.sendDateEnd = timeValue.endTime;
294
-				if (this.value.sendDateStart && this.value.sendDateEnd) {
295
-					this.createTimeTxt = this.value.sendDateStart + "至" + this.value.sendDateEnd;
158
+				this.value.receiptDateStart = timeValue.beginTime;
159
+				this.value.receiptDateEnd = timeValue.endTime;
160
+				if (this.value.receiptDateStart && this.value.receiptDateEnd) {
161
+					this.createTimeTxt = this.value.receiptDateStart + "至" + this.value.receiptDateEnd;
296 162
 				} else {
297 163
 					this.createTimeTxt = "";
298 164
 				}
299 165
 			},
300
-			deptSeletchang(ids, names) {
301
-				this.value.deptId = ids[0];
302
-				this.value.deptName = names[0];
303
-			},
304 166
 			handleShowJtimePickerPopup() {
305 167
 				this.$refs.jtimePickerPopup.pickerShow();
306 168
 			},
307
-			handleShowDept() {
308
-				this.$refs.dept._show();
309
-			},
310
-
311
-
312
-			identificationUserSeletchang(ids, names) {
313
-				this.value.identification = ids[0];
169
+			receiptUserSeletchang(ids, names) {
170
+				this.value.receiptUserId = ids[0];
314 171
 				if (ids.length > 0) {
315
-					// 暂时只做单向
316 172
 					uni.$u.api.getUserByUserIds({
317 173
 						userIds: ids.join(",")
318 174
 					}).then(res => {
319 175
 						const {
320 176
 							data
321 177
 						} = res;
322
-						this.identificationUserInfos = data;
178
+						this.receiptUserInfos = data;
323 179
 					})
324 180
 				} else {
325
-					this.identificationUserInfos = [];
181
+					this.receiptUserInfos = [];
326 182
 				}
327 183
 			},
328
-			createByUserSeletchang(ids, names) {
329
-				this.value.createBy = ids[0];
330
-				if (ids.length > 0) {
331
-					// 暂时只做单向
332
-					uni.$u.api.getUserByUserIds({
333
-						userIds: ids.join(",")
334
-					}).then(res => {
335
-						const {
336
-							data
337
-						} = res;
338
-						this.createByUserInfos = data;
339
-					})
340
-				} else {
341
-					this.createByUserInfos = [];
342
-				}
343
-			},
344
-			handleShowIdentificationUser() {
345
-				this.$refs.identificationUser._show();
346
-			},
347
-			handleShowCreateByUser() {
348
-				this.$refs.createByUser._show();
184
+			handleShowReceiptUser() {
185
+				this.$refs.receiptUser._show();
349 186
 			},
350
-			handleInitTag() {
351
-				uni.$u.api.selectAllDeptList({
352
-					isDept: 2
353
-				}).then(({
354
-					data
355
-				}) => {
356
-					this.deptList = handleTree(data, 'deptId');
357
-				});
358
-
359
-				// 获取接单人
187
+			handleInitReceiptUser() {
188
+				// 获取收单人数据
360 189
 				uni.$u.api.getDeptOwner({ excludeDeptIds : [100,369,378,356] }).then(res => {
361
-					this.identificationUserListData = filterCustomerManager(res.data);
362
-				});
363
-				
364
-				// 获取发单人
365
-				uni.$u.api.getDeptCustomerByOrg({ deptId : 369 }).then(res => {
366
-					this.createByUserListData = filterCustomerManager(res.data);
367
-				});
368
-
369
-				// 获取所有标签
370
-				uni.$u.api.getClueTagGroupVoList({ tagGroupApplication : '2' }).then(({
371
-					data
372
-				}) => {
373
-					this.clueTagGroupVoList = data.reduce((acc, cur) => {
374
-						return acc.concat(cur.clueTagDataList);
375
-					}, []);
190
+					this.receiptUserListData = filterCustomerManager(res.data);
376 191
 				});
377 192
 			},
378 193
 			handleClose() {
379 194
 				this.showFilter = false;
380 195
 			},
381
-			handleClickTag(item, attr) {
382
-				if (item.isCheck) {
383
-					const index = this.value[attr].findIndex(v => v == item.dictValue);
384
-					this.value[attr].splice(index, 1);
385
-				} else {
386
-					this.value[attr].push(item.dictValue);
387
-				}
388
-			},
389 196
 			handleReset() {
390
-				this.value.sendDateStart = undefined;
391
-				this.value.sendDateEnd = undefined;
392
-				this.value.deptId = undefined;
393
-				this.value.deptName = undefined;
394
-				this.value.status = '';
395
-				this.value.state = '';
396
-				this.value.identification = undefined;
397
-				this.value.createBy = undefined;
197
+				this.value.receiptDateStart = undefined;
198
+				this.value.receiptDateEnd = undefined;
398 199
 				this.value.item = '';
399 200
 				this.value.phone = '';
400
-				this.value.allTagList = [];
201
+				this.value.receiptUserId = undefined;
401 202
 				this.createTimeTxt = null;
402
-				this.identificationUserInfos = [];
403
-				this.createByUserInfos = [];
203
+				this.receiptUserInfos = [];
404 204
 			},
405 205
 			handleEnter() {
406 206
 				this.$emit("getList");
407 207
 				this.showFilter = false;
408 208
 			},
409
-			handleChangeSort(sort) {
410
-				this.value.sort = sort;
411
-			},
412
-			handleChangeSortField(value) {
413
-				this.value.sortField = value;
414
-			},
415 209
 			show() {
416 210
 				this.showFilter = true;
417 211
 			}
@@ -433,8 +227,6 @@
433 227
 			margin-bottom: 20rpx;
434 228
 		}
435 229
 
436
-		.pick_regions_wrap {}
437
-
438 230
 		.person_wrap {
439 231
 			display: flex;
440 232
 			flex-wrap: wrap;
@@ -490,35 +282,6 @@
490 282
 				color: #202020;
491 283
 				margin-bottom: 10px;
492 284
 			}
493
-
494
-			.option_list {
495
-				display: flex;
496
-				flex-wrap: wrap;
497
-				margin-bottom: 15px;
498
-
499
-				.option_item {
500
-					width: calc(33.33% - 10px);
501
-					margin-right: 10px;
502
-					margin-bottom: 10px;
503
-
504
-					::v-deep .u-tag__text {
505
-						margin: auto;
506
-						font-size: 14px;
507
-					}
508
-
509
-					&:nth-child(6n) {
510
-						margin-right: 0px;
511
-					}
512
-				}
513
-			}
514
-
515
-			.follow_option_list {
516
-				.option_item {
517
-					::v-deep .u-tag__text {
518
-						font-size: 12px;
519
-					}
520
-				}
521
-			}
522 285
 		}
523 286
 	}
524 287
 

+ 12 - 7
pages/order/components/commission/myCommission.vue

@@ -66,7 +66,7 @@
66 66
 				</view>
67 67
 			</view>
68 68
 		</view>
69
-		
69
+
70 70
 		<filter-query ref="filter" v-model="queryParams" @getList="resetData" :mapHeight="mapHeight"></filter-query>
71 71
 
72 72
 		<!-- 加载更多 -->
@@ -79,6 +79,7 @@
79 79
 <script>
80 80
 	import pullUpRefresh from "@/utils/pullUpRefresh";
81 81
 	import filterQuery from "./filterQuery.vue";
82
+	import dayjs from "dayjs";
82 83
 	export default {
83 84
 		mixins: [pullUpRefresh],
84 85
 		components: {
@@ -97,11 +98,15 @@
97 98
 		data() {
98 99
 			return {
99 100
 				queryParams: {
100
-					phone : "",
101
+					receiptDateEnd: dayjs().format("YYYY-MM-DD"),
102
+					receiptDateStart: dayjs().startOf('month').format("YYYY-MM-DD"),
103
+					item: undefined,
104
+					phone: undefined,
105
+					receiptUserId: undefined,
101 106
 					pageNum: 1,
102 107
 					pageSize: 10,
103 108
 				},
104
-				
109
+
105 110
 				mapHeight: "0px"
106 111
 			}
107 112
 		},
@@ -163,8 +168,8 @@
163 168
 			display: flex;
164 169
 			background: #fff;
165 170
 			padding: 14px 0;
166
-		
167
-		
171
+
172
+
168 173
 			.query,
169 174
 			.search {
170 175
 				display: flex;
@@ -174,11 +179,11 @@
174 179
 				font-weight: 700;
175 180
 				color: #202020;
176 181
 			}
177
-		
182
+
178 183
 			.query {
179 184
 				flex: 1;
180 185
 			}
181
-		
186
+
182 187
 			.search {
183 188
 				flex: 2;
184 189
 				padding-left: 20px;

+ 9 - 6
pages/order/components/orderCenter/orderCenter.vue

@@ -118,6 +118,15 @@
118 118
 			}
119 119
 		},
120 120
 		methods: {
121
+			handleToDetail(row) {
122
+				const {
123
+					id,
124
+					item
125
+				} = row;
126
+				uni.navigateTo({
127
+					url: `/pages/orderDetail/index?orderId=${id}&item=${item}&type=${this.queryParams.type}`,
128
+				});
129
+			},
121 130
 			handleKeyword() {
122 131
 				this.resetData();
123 132
 			},
@@ -159,12 +168,6 @@
159 168
 					}
160 169
 				});
161 170
 			},
162
-			handleToDetail(item) {
163
-				// 跳转到详情页面
164
-				uni.navigateTo({
165
-					url: `/pages/orderDetail/index?id=${item.id}`
166
-				});
167
-			},
168 171
 			async getList() {
169 172
 				const {
170 173
 					pageNum,

+ 62 - 0
pages/orderDetail/index.vue

@@ -0,0 +1,62 @@
1
+<template>
2
+	<view>
3
+		<u-navbar placeholder :autoBack="true" v-hideNav>
4
+			<template slot="center">
5
+				<view class="navbar_center_wrap">
6
+					<text class="name">{{item}}</text>
7
+				</view>
8
+			</template>
9
+		</u-navbar>
10
+		<detail :orderId="orderId" :params="params"></detail>
11
+	</view>
12
+</template>
13
+
14
+<script>
15
+	import detail from "./page/detail.vue";
16
+	import qs from 'qs';
17
+	export default {
18
+		components: {
19
+			detail
20
+		},
21
+		onPullDownRefresh() {
22
+			uni.stopPullDownRefresh();
23
+			// 刷新
24
+			uni.redirectTo({
25
+				url : `/pages/clueDetail/index?` + qs.stringify(this.params),
26
+			})
27
+		},
28
+		onLoad(option) {
29
+			const {	item , orderId , type } = option;
30
+			console.log(option);
31
+			this.item = item;
32
+			this.orderId = orderId;
33
+			this.params = option;
34
+			uni.setNavigationBarTitle({
35
+				title: item
36
+			});
37
+		},
38
+		data() {
39
+			return {
40
+				item: "", 
41
+				orderId: "",
42
+				params : {},
43
+			}
44
+		},
45
+		mounted(){
46
+		}
47
+	}
48
+</script>
49
+
50
+<style lang="scss" scoped>
51
+	.navbar_center_wrap{
52
+		text-align: center;
53
+		width: 200rpx;
54
+		display: flex;
55
+		flex-direction: column;
56
+		align-items: center;
57
+		.pinyin{
58
+			font-size: 22rpx;
59
+			color: #b3b3c6;
60
+		}
61
+	}
62
+</style>

+ 492 - 0
pages/orderDetail/page/detail.vue

@@ -0,0 +1,492 @@
1
+<template>
2
+	<view class="clueDetail_wrap">
3
+		<view class="telPhone">
4
+			<view class="left">
5
+				<view class="phone">电话:
6
+					<show-real-text :real="clueDetail.telephone" :type='params.type'
7
+						v-if="clueDetail.telephone"></show-real-text>
8
+					<template v-if="clueDetail.telAddr">
9
+						({{clueDetail.telAddr}})
10
+					</template>
11
+				</view>
12
+				<view class="copy_btn" v-if="params.type != '1'" @click="handleCopy(clueDetail)">复制</view>
13
+			</view>
14
+			<view class="right">
15
+				运营人 : {{ clueDetail.clueOperationName }}
16
+			</view>
17
+		</view>
18
+		<view class="clueDetail_top_info">
19
+			<view class="top_info_item">
20
+				<view class="top">
21
+					{{defaultText(clueDetail.clueOwnerName)}}
22
+				</view>
23
+				<view class="bottom">
24
+					线索所属人
25
+				</view>
26
+			</view>
27
+			<view class="top_info_item">
28
+				<view class="top">
29
+					{{defaultText(clueDetail.appName)}}
30
+				</view>
31
+				<view class="bottom">
32
+					流量来源
33
+				</view>
34
+			</view>
35
+			<view class="top_info_item">
36
+				<view class="top">
37
+					{{defaultText(clueDetail.clueType)}}
38
+				</view>
39
+				<view class="bottom">
40
+					线索类型
41
+				</view>
42
+			</view>
43
+			<view class="top_info_item">
44
+				<view class="top">
45
+					{{defaultText(clueDetail.createTime)}}
46
+				</view>
47
+				<view class="bottom">
48
+					线索创建时间
49
+				</view>
50
+			</view>
51
+			<view class="top_info_item">
52
+				<view class="top">
53
+					{{defaultText(clueDetail.clueDataSource)}}
54
+				</view>
55
+				<view class="bottom">
56
+					线索渠道
57
+				</view>
58
+			</view>
59
+			<view class="top_info_item">
60
+				<view class="top">
61
+					{{crmFollowStatusFormat(clueDetail.handleState)}}
62
+				</view>
63
+				<view class="bottom">
64
+					跟进状态
65
+				</view>
66
+			</view>
67
+		</view>
68
+
69
+
70
+		<view class="clue_state_wrap">
71
+			<view class="clue_state_top_wrap">
72
+				<view class="top_left">线索阶段</view>
73
+				<view class="top_right" @click="handleIsInvalid">
74
+					<template v-if="clueDetail.clueState === '6'">已标记无效</template>
75
+					<template v-else>标记无效</template>
76
+				</view>
77
+			</view>
78
+			<view class="steps_wrap">
79
+				<u-steps :current="currentSteps" dot>
80
+					<u-steps-item :title="item.dictLabel" @click.native="handleClickStepsItem(item)"
81
+						v-for="(item,index) in crmCluePhaseDict" :key="index"></u-steps-item>
82
+				</u-steps>
83
+			</view>
84
+		</view>
85
+
86
+		<view class="clue_tag_wrap">
87
+			<view class="clue_tag_add_btn" @click="handleAddClueTag">
88
+				+ 添加标签
89
+			</view>
90
+			<u-tag :text="tag.name" plain plainFill :closable="true" @close="hanldeTagClose(tag)" borderColor="#fff"
91
+				v-for="(tag) in clueDetail.clueTags" :key="tag.id" style="margin-left: 10px;margin-bottom: 10px;"
92
+				:bgColor="tag.color" color="#fff"></u-tag>
93
+		</view>
94
+
95
+
96
+		<yui-tabs :tabs="tabs" v-model="activeIndex" :lineWidth="'120rpx'" :isLazyRender="false"
97
+			color="#108cff" titleActiveColor="#108cff" :swipeable="true" :swiper="false" :ellipsis="false" :scroll-threshold="3">
98
+			<template #clueInfo>
99
+				<clueInfo :clueId="clueDetail.id" v-if="clueDetail.id" :params="params" ref="clueInfoRef" type="1"></clueInfo>
100
+			</template>
101
+			<template #advertising>
102
+				<advertising :clueId="clueDetail.id" v-if="clueDetail.id" ref="advertisingRef"></advertising>
103
+			</template>
104
+			<template #followRecord>
105
+				<followRecord :clueId="clueDetail.id" v-if="clueDetail.id" ref="followRecordRef" type="1"></followRecord>
106
+			</template>
107
+			<template #callRecord>
108
+				<callRecord :clueId="clueDetail.id" :clueDetail="clueDetail" v-if="clueDetail.id" ref="callRecordRef">
109
+				</callRecord>
110
+			</template>
111
+			<template #orderFollow>
112
+				<followRecord :clueId="clueDetail.id" v-if="clueDetail.id" ref="followRecordRef" type="3"></followRecord>
113
+			</template>
114
+		</yui-tabs>
115
+		<u-tabbar class="clueDetail_tabber" :fixed="true" inactiveColor="#ffffff" :placeholder="true"
116
+			:safeAreaInsetBottom="true">
117
+			<u-tabbar-item text="拨打电话" icon="../../static/clueDetail/icon-phone.png"
118
+				@click="handleCallPhone"></u-tabbar-item>
119
+			<!-- 			<u-tabbar-item text="转移线索" icon="../../static/clueDetail/icon-clue.png"
120
+				@click="handleDistribution"></u-tabbar-item> -->
121
+			<u-tabbar-item text="发单" icon="../../static/clueDetail/icon-order.png"
122
+				@click="handleClueSend"></u-tabbar-item>
123
+			<u-tabbar-item text="写新跟进" icon="../../static/caseDetail/icon-follow.png"
124
+				@click="handleAddFollow"></u-tabbar-item>
125
+			<u-tabbar-item text="上传录音" icon="../../static/caseDetail/icon-record.png"
126
+				@click="handleUploadRecord"></u-tabbar-item>
127
+		</u-tabbar>
128
+
129
+		<distribution-modal :clueDetail="clueDetail" ref="distribution"
130
+			@handleSuccess="distributionSuccess"></distribution-modal>
131
+
132
+		<group-select class="clueTagsSelect" :list="clueTagGroupVoList" scrollHeight="720rpx" groupName="groupName"
133
+			groupChild="clueTagDataList" label-key="name" value-key="id" placeholder="请选择线索标签" v-model="checkTags"
134
+			multiple clearable ref="clueTag" @confirm="handleClueTagConfirm"></group-select>
135
+	</view>
136
+</template>
137
+
138
+<script>
139
+	import {
140
+		cloneDeep
141
+	} from "lodash";
142
+	import {
143
+		selectDictLabel
144
+	} from "@/utils/util";
145
+	import clueInfo from "../tabs/clueInfo/index";
146
+	import advertising from "../tabs/advertising/index";
147
+	import followRecord from "../tabs/followRecord/index";
148
+	import callRecord from "../tabs/callRecord/index";
149
+	export default {
150
+		components: {
151
+			clueInfo,
152
+			advertising,
153
+			followRecord,
154
+			callRecord,
155
+		},
156
+		props: {
157
+			clueId: {
158
+				type: String,
159
+				required: true
160
+			},
161
+			params: {
162
+				type: Object,
163
+				required: true
164
+			},
165
+		},
166
+		computed: {
167
+			currentSteps() {
168
+				const index = this.crmCluePhaseDict.findIndex(v => v.dictValue === this.clueDetail.clueState);
169
+				return index;
170
+			},
171
+		},
172
+		data() {
173
+			return {
174
+
175
+				showModal: false,
176
+
177
+				clueDetail: {},
178
+
179
+				checkTags: [],
180
+
181
+				clueTagGroupVoList: [],
182
+
183
+				crmHandelStatusDict: [],
184
+				crmCluePhaseDict: [],
185
+
186
+				tabs: [{
187
+						label: '基础信息',
188
+						slot: 'clueInfo'
189
+					}, {
190
+						label: '广告信息',
191
+						slot: 'advertising'
192
+					}, {
193
+						label: '跟进记录',
194
+						slot: 'followRecord'
195
+					},
196
+					{
197
+						label: '通话记录',
198
+						slot: 'callRecord'
199
+					},
200
+					{
201
+						label: '后端跟进',
202
+						slot: 'orderFollow'
203
+					}
204
+				],
205
+				activeIndex: 0,
206
+			}
207
+		},
208
+		methods: {
209
+			distributionSuccess() {
210
+				this.handleInit();
211
+			},
212
+			async hanldeTagClose(tag) {
213
+				const {
214
+					cfId: id,
215
+					clueTags
216
+				} = this.clueDetail;
217
+				const copyClueTags = cloneDeep(clueTags);
218
+				if (id == null) {
219
+					this.$message.error("修改异常");
220
+					return;
221
+				}
222
+				const index = copyClueTags.findIndex(v => v.id === tag.id);
223
+				if (index !== -1) {
224
+					copyClueTags.splice(index, 1);
225
+					const allTags = copyClueTags.map(v => v.id).join(",");
226
+					await uni.$u.api.updateClueFixedFieldsAllTags({
227
+						id,
228
+						allTags
229
+					});
230
+					this.clueDetail.clueTags = copyClueTags;
231
+					this.checkTags = this.clueDetail.clueTags.map(v => v.id);
232
+				}
233
+			},
234
+			handleDistribution() {
235
+				this.$refs.distribution.show();
236
+			},
237
+			confirm() {
238
+
239
+			},
240
+			async handleClueTagConfirm() {
241
+				const allTags = this.checkTags.join(",");
242
+				await uni.$u.api.updateClueFixedFieldsAllTags({
243
+					id: this.clueDetail.cfId,
244
+					allTags
245
+				});
246
+				this.getDetail();
247
+			},
248
+			handleAddClueTag() {
249
+				this.$refs.clueTag.showModal();
250
+			},
251
+			handleIsInvalid() {
252
+				const invalidDict = {
253
+					dictValue: '6'
254
+				};
255
+				this.handleClickStepsItem(invalidDict);
256
+			},
257
+			async handleClickStepsItem(item) {
258
+				const {
259
+					cfId: id,
260
+					clueState
261
+				} = this.clueDetail;
262
+				if (clueState === '6') {
263
+					if (clueState === item.dictValue) {
264
+						item.dictValue = '1';
265
+					} else {
266
+						uni.$u.toast("当前标记无效,请先取消");
267
+						return;
268
+					}
269
+				}
270
+				if (clueState === item.dictValue) {
271
+					return;
272
+				}
273
+				if (id == null) {
274
+					this.$message.error("修改异常");
275
+					return;
276
+				}
277
+				uni.$u.api.updateClueFixedFieldsClueState({
278
+					id,
279
+					clueState: item.dictValue
280
+				}).then(() => {
281
+					this.$set(this.clueDetail, "clueState", item.dictValue);
282
+				});
283
+			},
284
+			defaultText(text) {
285
+				return text ? text : "-";
286
+			},
287
+			crmFollowStatusFormat(v) {
288
+				return selectDictLabel(this.crmHandelStatusDict, v);
289
+			},
290
+			handleCopy(item) {
291
+				uni.setClipboardData({
292
+					data: item.telephone,
293
+					success: function() {
294
+						uni.$u.toast("复制成功");
295
+					}
296
+				});
297
+			},
298
+			// 添加联系人
299
+			handleCallPhone() {
300
+				uni.makePhoneCall({
301
+					phoneNumber: this.clueDetail.telephone,
302
+					success: () => {
303
+						this.$store.commit("call/SET_FORM", {
304
+							clueId: this.clueId,
305
+							type: '3',
306
+							callee: this.clueDetail.telephone
307
+						})
308
+					}
309
+				});
310
+			},
311
+			// 发单
312
+			async handleClueSend() {
313
+				const {
314
+					data: count
315
+				} = await uni.$u.api.getClueSendFormCountByClueId({
316
+					clueId: this.clueId
317
+				});
318
+				if (count > 0) {
319
+					uni.showModal({
320
+						title: '该线索已发单是否再次发单?',
321
+						success: function (res) {
322
+							if (res.confirm) {
323
+								uni.navigateTo({
324
+									url: `/pages/orderForm/index?clueId=${this.clueId}`
325
+								})
326
+							}
327
+						}
328
+					});
329
+				}else{
330
+					uni.navigateTo({
331
+						url: `/pages/orderForm/index?clueId=${this.clueId}`
332
+					})
333
+				}
334
+			},
335
+			// 添加催记
336
+			handleAddFollow() {
337
+				uni.navigateTo({
338
+					url: `/pages/addFollow/index?clueId=${this.clueId}`
339
+				})
340
+			},
341
+			handleUploadRecord() {
342
+				uni.navigateTo({
343
+					url: `/pages/uploadRecord/index?clueId=${this.clueId}`
344
+				})
345
+			},
346
+			// 初始化详情页
347
+			async handleInit() {
348
+				this.$getDicts('crm_follow_status').then((res => {
349
+					this.crmHandelStatusDict = res;
350
+				}))
351
+				this.$getDicts('crm_clue_phase').then((res => {
352
+					this.crmCluePhaseDict = res.filter(v => v.dictValue !== '6');
353
+				}))
354
+				uni.$u.api.getClueTagGroupVoList({
355
+					tagGroupApplication: '1'
356
+				}).then(({
357
+					data
358
+				}) => {
359
+					this.clueTagGroupVoList = data;
360
+				});
361
+				this.getDetail();
362
+			},
363
+			getDetail() {
364
+				uni.$u.api.getClueMainInfoVoById({
365
+					id: this.clueId
366
+				}).then(res => {
367
+					this.clueDetail = res.data;
368
+					this.checkTags = this.clueDetail.clueTags.map(v => v.id);
369
+				});
370
+			},
371
+			// 标签点击事件
372
+			tabClick(index, item) {
373
+				// // 根据索引调用对应组件的getData方法
374
+				// const componentRefs = [
375
+				// 	this.$refs.clueInfoRef,
376
+				// 	this.$refs.advertisingRef,
377
+				// 	this.$refs.followRecordRef,
378
+				// 	this.$refs.callRecordRef
379
+				// ];
380
+
381
+				// const currentComponent = componentRefs[index];
382
+				// if (currentComponent && typeof currentComponent.getData === 'function') {
383
+				// 	currentComponent.getData();
384
+				// }
385
+			},
386
+			// 标签切换事件
387
+			tabChange(index, item) {
388
+				// console.log("tabChange", index, item);
389
+			},
390
+		},
391
+		created() {
392
+			this.handleInit();
393
+		},
394
+	}
395
+</script>
396
+
397
+<style lang="scss" scoped>
398
+	.clueTagsSelect {
399
+		height: 0;
400
+		overflow: hidden;
401
+	}
402
+
403
+	.clue_tag_wrap {
404
+		display: flex;
405
+		align-items: center;
406
+		background-color: #fff;
407
+		padding: 0 10px;
408
+		margin: 20px 0;
409
+		flex-wrap: wrap;
410
+		min-height: 50px;
411
+
412
+		.clue_tag_add_btn {
413
+			font-size: 14px;
414
+			color: #108cff;
415
+		}
416
+	}
417
+
418
+	.clue_state_wrap {
419
+		background-color: #fff;
420
+		padding: 10px;
421
+		margin-bottom: 20px;
422
+
423
+		.clue_state_top_wrap {
424
+			display: flex;
425
+			justify-content: space-between;
426
+			margin-bottom: 10px;
427
+
428
+			.top_left {
429
+				font-size: 16px;
430
+			}
431
+
432
+			.top_right {
433
+				font-size: 14px;
434
+				color: #4fa5fe;
435
+			}
436
+		}
437
+
438
+		.steps_wrap {}
439
+	}
440
+
441
+	.clueDetail_tabber {
442
+		::v-deep .u-tabbar__content {
443
+			background: #108cff;
444
+			border-top-right-radius: 10px;
445
+			border-top-left-radius: 10px;
446
+		}
447
+	}
448
+
449
+	.clueDetail_wrap {
450
+		.telPhone {
451
+			display: flex;
452
+			background: #fff;
453
+			padding: 20px;
454
+			justify-content: space-between;
455
+
456
+			.left {
457
+				display: flex;
458
+			}
459
+
460
+			.copy_btn {
461
+				color: #4fa5fe;
462
+				margin-left: 10px;
463
+			}
464
+		}
465
+
466
+		.clueDetail_top_info {
467
+			display: flex;
468
+			flex-wrap: wrap;
469
+			background-color: #ffffff;
470
+			padding-top: 20px;
471
+			margin: 18px 0;
472
+
473
+			.top_info_item {
474
+				width: 33.33%;
475
+				text-align: center;
476
+				margin-bottom: 20px;
477
+
478
+				.top {
479
+					font-size: 15px;
480
+					color: #202020;
481
+					margin-bottom: 5px;
482
+					font-weight: bold;
483
+				}
484
+
485
+				.bottom {
486
+					font-size: 15px;
487
+					color: #c0c0c7;
488
+				}
489
+			}
490
+		}
491
+	}
492
+</style>

+ 196 - 0
pages/orderDetail/tabs/advertising/index.vue

@@ -0,0 +1,196 @@
1
+<template>
2
+	<view class="caseInfo_wrap">
3
+		<template>
4
+			<view class="caseInfo_content_wrap">
5
+				<view class="caseInfo_title">
6
+					<image src="/static/clueDetail/icon-caseInfo.png" mode=""></image>
7
+					<text class="info_text">广告信息</text>
8
+				</view>
9
+				<view class="caseInfo_main_wrap">
10
+					<view class="Info_item" v-for="(item,index) in caseInfoColumn" :key="item.prop">
11
+						<text class="label">{{item.label}}</text>
12
+						<view v-if="item.prop === 'advName'" class="advName_wrap">
13
+							<view class="copy_btn" @click="handleCopy(clueAdInfo[item.prop])">复制</view>
14
+							<text class="value">{{clueAdInfo[item.prop] ? clueAdInfo[item.prop] : '-'}}</text>
15
+						</view>
16
+						<text v-else-if="item.color" class="value highlight" :style="handleStyle(item.color)">
17
+							{{clueAdInfo[item.prop]}}
18
+						</text>
19
+						<show-real-text v-else-if="item.dsProp" class="value" :real="clueAdInfo[item.prop]"
20
+							:normal="clueAdInfo[item.dsProp]" :accessType="item.accessType" :key="item.prop + index">
21
+						</show-real-text>
22
+						<text v-else class="value">{{clueAdInfo[item.prop] ? clueAdInfo[item.prop] : '-'}}</text>
23
+					</view>
24
+				</view>
25
+			</view>
26
+		</template>
27
+	</view>
28
+</template>
29
+
30
+<script>
31
+	const caseInfoColumn = [{
32
+			prop: 'advId',
33
+			label: '广告主ID',
34
+		},
35
+		{
36
+			prop: 'advName',
37
+			label: '广告主名称',
38
+		},
39
+		{
40
+			prop: 'promotionId',
41
+			label: '广告ID',
42
+		},
43
+		{
44
+			prop: 'promotionName',
45
+			label: '广告名称',
46
+		},
47
+		{
48
+			prop: 'originExternalUrl',
49
+			label: '落地页链接',
50
+		},
51
+		{
52
+			prop: 'titleId',
53
+			label: '标题ID',
54
+		},
55
+		{
56
+			prop: 'videoId',
57
+			label: '视频ID',
58
+		},
59
+		{
60
+			prop: 'imageId',
61
+			label: '图片ID',
62
+		},
63
+		{
64
+			prop: 'douyinId',
65
+			label: '企业号ID',
66
+		},
67
+		{
68
+			prop: 'douyinName',
69
+			label: '企业号名称',
70
+		},
71
+		{
72
+			prop: 'corporateToolName',
73
+			label: '企业号工具',
74
+		},
75
+		{
76
+			prop: 'clueDataSourceDetail',
77
+			label: '线索渠道详情',
78
+		},
79
+	]
80
+	export default {
81
+		props: {
82
+			clueId: {
83
+				required: true
84
+			},
85
+		},
86
+		methods: {
87
+			handleCopy(value) {
88
+				uni.setClipboardData({
89
+					data: value,
90
+					success: function() {
91
+						uni.$u.toast("复制成功");
92
+					}
93
+				});
94
+			},
95
+			async getData() {
96
+				const {
97
+					data
98
+				} = await uni.$u.api.getClueAdInfoByClueId({
99
+					clueId: this.clueId
100
+				});
101
+				this.clueAdInfo = data ? data : {};
102
+			}
103
+		},
104
+		created() {
105
+			this.getData();
106
+		},
107
+		data() {
108
+			return {
109
+				clueAdInfo: {},
110
+
111
+				caseInfoColumn,
112
+			}
113
+		}
114
+	}
115
+</script>
116
+
117
+<style lang="scss" scoped>
118
+	.caseInfo_wrap {
119
+		min-height: 400px;
120
+		.caseInfo_title {
121
+			display: flex;
122
+			align-items: center;
123
+			height: 80rpx;
124
+			background: #f4f4f6;
125
+			padding-left: 40rpx;
126
+
127
+			image {
128
+				width: 24rpx;
129
+				height: 24rpx;
130
+				margin-right: 10rpx;
131
+			}
132
+
133
+			.info_text {
134
+				font-size: 24rpx;
135
+				color: #202020;
136
+			}
137
+
138
+			.tabs_wrap {
139
+				display: flex;
140
+
141
+				.tab_item {
142
+					margin-left: 30rpx;
143
+				}
144
+			}
145
+		}
146
+
147
+		.caseInfo_main_wrap {
148
+			.caseCards_list_wrap {
149
+				background: #ebf6ff;
150
+				margin-bottom: 10rpx;
151
+				margin-left: 10rpx;
152
+				margin-right: 10rpx;
153
+			}
154
+			
155
+
156
+			.Info_item {
157
+				padding: 18rpx 40rpx;
158
+				display: flex;
159
+				justify-content: space-between;
160
+
161
+				.label {
162
+					color: #999999;
163
+					font-size: 26rpx;
164
+					width: 160rpx;
165
+				}
166
+
167
+				.value {
168
+					font-size: 26rpx;
169
+					color: #202020;
170
+					border-radius: 100rpx;
171
+					text-align: right;
172
+					width: 500rpx;
173
+				}
174
+
175
+				.highlight {
176
+					font-size: 22rpx;
177
+					font-weight: 900;
178
+					padding: 6rpx 20rpx;
179
+				}
180
+			}
181
+			
182
+			.advName_wrap{
183
+				display: flex;
184
+				align-items: center;
185
+				.copy_btn {
186
+					color: #4fa5fe;
187
+					margin-right: 10px;
188
+				}
189
+				.value{
190
+					width: auto;
191
+				}
192
+			}
193
+		}
194
+
195
+	}
196
+</style>

+ 204 - 0
pages/orderDetail/tabs/callRecord/index.vue

@@ -0,0 +1,204 @@
1
+<template>
2
+	<view class="callRecord_wrap">
3
+		<view v-if="dataList.length > 0" class="timeline-list">
4
+			<view v-for="(data, index) in dataList" :key="index" class="timeline-item">
5
+				<view class="timeline-icon">
6
+					<uni-icons type="phone" size="20" color="#6cc040"></uni-icons>
7
+				</view>
8
+				<view class="timeline-content">
9
+					<view class="timestamp">{{ titleText(data) }}</view>
10
+					<view class="info_top">
11
+						<view class="info_left">
12
+							<text class="name_text">{{ data.caller }} 打给 {{ data.callee }}</text>
13
+						</view>
14
+						<view class="info_right">
15
+							{{ data.type === 1 ? data.followState : '' }}
16
+						</view>
17
+						<view class="delete-btn" @tap="handleDelete({ id: data.id })">
18
+							<uni-icons type="trash" size="20" color="#ff6666"></uni-icons>
19
+						</view>
20
+					</view>
21
+					<view class="file_name" v-if="data.type === '3'">{{data.fileName}}</view>
22
+					<view class="call_file">
23
+						<view class="audio-container">
24
+							<audio :src="data.fileUrl" controls ></audio>
25
+						</view>
26
+					</view>
27
+				</view>
28
+			</view>
29
+		</view>
30
+		<view v-else class="empty_wrap">
31
+			<uni-icons type="empty" size="60" color="#cccccc"></uni-icons>
32
+			<text class="empty-text">暂无通话记录</text>
33
+		</view>
34
+	</view>
35
+</template>
36
+<script>
37
+	export default {
38
+		props: {
39
+			clueId: {
40
+				type: [String, Number],
41
+				required: true
42
+			},
43
+			clueDetail: {
44
+				type: Object,
45
+				default: () => ({})
46
+			}
47
+		},
48
+		data() {
49
+			return {
50
+				dataList: []
51
+			}
52
+		},
53
+		methods: {
54
+			titleText(data) {
55
+				return `${data.createTime} 号码(${data.caller}) ${(data.type === 1 ?  data.hangupCauseName: '上传录音')}`
56
+			},
57
+			handleDelete({
58
+				id
59
+			}) {
60
+				uni.showModal({
61
+					title: '提示',
62
+					content: '是否确定删除?',
63
+					confirmColor: '#6cc040',
64
+					success: async (res) => {
65
+						if (res.confirm) {
66
+							try {
67
+								await uni.$u.api.deleteClueFile([id])
68
+								uni.showToast({
69
+									title: '删除成功',
70
+									icon: 'success',
71
+									duration: 2000
72
+								})
73
+								this.getData()
74
+							} catch (error) {
75
+								uni.showToast({
76
+									title: '删除失败',
77
+									icon: 'error',
78
+									duration: 2000
79
+								})
80
+							}
81
+						}
82
+					}
83
+				})
84
+			},
85
+			async getData() {
86
+				const {
87
+					data
88
+				} = await uni.$u.api.getCallClueFileByClueId({
89
+					clueId: this.clueId
90
+				});
91
+				this.dataList = data;
92
+			},
93
+		},
94
+		mounted() {
95
+			this.getData();
96
+		},
97
+		beforeDestroy(){
98
+			uni.$off('uploadRecordSuccess');  
99
+		},
100
+		created() {
101
+			uni.$on('uploadRecordSuccess',()=>{
102
+				this.getData();
103
+			});
104
+		}
105
+	}
106
+</script>
107
+<style lang="scss" scoped>
108
+	.callRecord_wrap {
109
+		position: relative;
110
+		padding: 20rpx;
111
+		min-height: 400px;
112
+		box-sizing: border-box;
113
+
114
+		.loading-mask {
115
+			position: absolute;
116
+			top: 0;
117
+			left: 0;
118
+			right: 0;
119
+			bottom: 0;
120
+			background-color: rgba(255, 255, 255, 0.8);
121
+			display: flex;
122
+			justify-content: center;
123
+			align-items: center;
124
+			z-index: 999;
125
+		}
126
+
127
+		.timeline-list {
128
+			.timeline-item {
129
+				display: flex;
130
+				margin-bottom: 30rpx;
131
+
132
+				.timeline-icon {
133
+					width: 60rpx;
134
+					height: 60rpx;
135
+					border-radius: 50%;
136
+					background-color: #f1f3f4;
137
+					display: flex;
138
+					justify-content: center;
139
+					align-items: center;
140
+					margin-right: 20rpx;
141
+					flex-shrink: 0;
142
+				}
143
+
144
+				.timeline-content {
145
+					width:690rpx;
146
+
147
+					.timestamp {
148
+						font-size: 24rpx;
149
+						color: #999;
150
+						margin-bottom: 10rpx;
151
+					}
152
+
153
+					.info_top {
154
+						display: flex;
155
+						justify-content: space-between;
156
+						align-items: center;
157
+						margin-bottom: 20rpx;
158
+
159
+						.info_left {
160
+							.name_text {
161
+								font-size: 28rpx;
162
+								font-weight: 500;
163
+							}
164
+						}
165
+
166
+						.info_right {
167
+							color: #6cc040;
168
+							font-size: 26rpx;
169
+						}
170
+					}
171
+					.file_name{
172
+						color: #999;
173
+						font-size: 26rpx;
174
+					}
175
+
176
+					.call_file {
177
+						width: 100%;
178
+						border-radius: 12rpx;
179
+						display: flex;
180
+						align-items: center;
181
+						justify-content: space-between;
182
+						box-sizing: border-box;
183
+
184
+						.audio-container {
185
+							width: 100%;
186
+						}
187
+					}
188
+				}
189
+			}
190
+		}
191
+
192
+		.empty_wrap {
193
+			text-align: center;
194
+			padding: 100rpx 0;
195
+
196
+			.empty-text {
197
+				display: block;
198
+				margin-top: 30rpx;
199
+				color: #999;
200
+				font-size: 28rpx;
201
+			}
202
+		}
203
+	}
204
+</style>

+ 232 - 0
pages/orderDetail/tabs/clueInfo/index.vue

@@ -0,0 +1,232 @@
1
+<template>
2
+	<view class="caseInfo_wrap">
3
+		<template v-if="clueDetail.id">
4
+			<view class="caseInfo_content_wrap">
5
+				<view class="caseInfo_title">
6
+					<image src="/static/clueDetail/icon-caseInfo.png" mode=""></image>
7
+					<text class="info_text">线索基础信息</text>
8
+				</view>
9
+				<view class="caseInfo_main_wrap">
10
+					<template v-if="params.type === '1'">
11
+						<view class="Info_item" v-for="(item,index) in caseInfoColumn" :key="item.prop">
12
+							<text class="label">{{item.label}}</text>
13
+							<text v-if="item.color" class="value highlight" :style="handleStyle(item.color)">
14
+								{{clueDetail[item.prop]}}
15
+							</text>
16
+							<show-real-text :real="clueDetail.telephone" :type='params.type'
17
+								v-if="item.prop === 'telephone'"></show-real-text>
18
+							<text v-else class="value">{{clueDetail[item.prop]}}</text>
19
+						</view>
20
+					</template>
21
+					<template v-if="params.type === '2'">
22
+						<view class="Info_item" v-for="(item,index) in caseInfoColumn" :key="item.prop">
23
+							<text class="label">{{item.label}}</text>
24
+							<view class="input_wrap" v-if="item.input">
25
+								<input v-model="clueDetail[item.prop]" style="text-align: right; padding-right: 10px;"
26
+									@blur="updateClueMainInfo" />
27
+							</view>
28
+							<text v-else-if="item.color" class="value highlight" :style="handleStyle(item.color)">
29
+								{{clueDetail[item.prop]}}
30
+							</text>
31
+							<text v-else class="value">{{clueDetail[item.prop]}}</text>
32
+						</view>
33
+					</template>
34
+				</view>
35
+			</view>
36
+		</template>
37
+	</view>
38
+</template>
39
+
40
+<script>
41
+	import {
42
+		cloneDeep,
43
+		isEqual
44
+	} from 'lodash';
45
+
46
+	const caseInfoColumn = [{
47
+			prop: 'name',
48
+			label: '姓名',
49
+			input: true
50
+		},
51
+		{
52
+			prop: 'telephone',
53
+			label: '电话',
54
+			input: true
55
+		},
56
+		{
57
+			prop: 'genderTypeCode',
58
+			label: '性别',
59
+		},
60
+		{
61
+			prop: 'age',
62
+			label: '年龄',
63
+		},
64
+		{
65
+			prop: 'weixin',
66
+			label: '微信',
67
+			input: true
68
+		},
69
+		{
70
+			prop: 'corporateStatus',
71
+			label: '企业号状态',
72
+		},
73
+		{
74
+			prop: 'userDouyinId',
75
+			label: '抖音号',
76
+		},
77
+		{
78
+			prop: 'autoAddress',
79
+			label: '自动定位城市',
80
+		},
81
+		{
82
+			prop: 'telAddr',
83
+			label: '手机号归属地',
84
+		},
85
+		{
86
+			prop: 'createTime',
87
+			label: '线索创建时间',
88
+		},
89
+		{
90
+			prop: 'updateTime',
91
+			label: '最新修改时间',
92
+		},
93
+		{
94
+			prop: 'address',
95
+			label: '详细地址',
96
+		},
97
+		{
98
+			prop: 'remark',
99
+			label: '备注',
100
+		},
101
+		{
102
+			prop: 'qq',
103
+			label: 'QQ号',
104
+		},
105
+		{
106
+			prop: 'email',
107
+			label: '邮箱',
108
+		},
109
+		{
110
+			prop: 'date',
111
+			label: '日期',
112
+		},
113
+		{
114
+			prop: 'manualAddressCode',
115
+			label: '手动填写地域',
116
+		}
117
+	]
118
+	export default {
119
+		props: {
120
+			clueId: {
121
+				required: true
122
+			},
123
+			params: {
124
+				type: Object,
125
+				required: true
126
+			},
127
+		},
128
+		methods: {
129
+			async updateClueMainInfo() {
130
+				if (!this.clueDetail.name) {
131
+					uni.$u.toast("姓名不能为空");
132
+					return;
133
+				}
134
+				if (!isEqual(this.cloneClueDetail, this.clueDetail)) {
135
+					await uni.$u.api.updateClueMainInfo(this.clueDetail);
136
+					uni.$u.toast("修改成功");
137
+					this.getData();
138
+				}
139
+			},
140
+			async getData() {
141
+				const {
142
+					data
143
+				} = await uni.$u.api.getClueMainInfoById({
144
+					id: this.clueId
145
+				});
146
+				this.clueDetail = data;
147
+				this.cloneClueDetail = cloneDeep(data);
148
+			}
149
+		},
150
+		created() {
151
+			this.getData();
152
+		},
153
+		data() {
154
+			return {
155
+				cloneClueDetail: {},
156
+				clueDetail: {},
157
+
158
+				caseInfoColumn,
159
+			}
160
+		}
161
+	}
162
+</script>
163
+
164
+<style lang="scss" scoped>
165
+	.caseInfo_wrap {
166
+		min-height: 400px;
167
+
168
+		.caseInfo_title {
169
+			display: flex;
170
+			align-items: center;
171
+			height: 80rpx;
172
+			background: #f4f4f6;
173
+			padding-left: 40rpx;
174
+
175
+			image {
176
+				width: 24rpx;
177
+				height: 24rpx;
178
+				margin-right: 10rpx;
179
+			}
180
+
181
+			.info_text {
182
+				font-size: 24rpx;
183
+				color: #202020;
184
+			}
185
+
186
+			.tabs_wrap {
187
+				display: flex;
188
+
189
+				.tab_item {
190
+					margin-left: 30rpx;
191
+				}
192
+			}
193
+		}
194
+
195
+		.caseInfo_main_wrap {
196
+			.caseCards_list_wrap {
197
+				background: #ebf6ff;
198
+				margin-bottom: 10rpx;
199
+				margin-left: 10rpx;
200
+				margin-right: 10rpx;
201
+			}
202
+
203
+			.Info_item {
204
+				padding: 18rpx 40rpx;
205
+				display: flex;
206
+				justify-content: space-between;
207
+
208
+				.label {
209
+					color: #999999;
210
+					font-size: 26rpx;
211
+				}
212
+
213
+				.input_wrap {
214
+					border-bottom: 1px solid #ddd;
215
+				}
216
+
217
+				.value {
218
+					font-size: 26rpx;
219
+					color: #202020;
220
+					border-radius: 100rpx;
221
+				}
222
+
223
+				.highlight {
224
+					font-size: 22rpx;
225
+					font-weight: 900;
226
+					padding: 6rpx 20rpx;
227
+				}
228
+			}
229
+		}
230
+
231
+	}
232
+</style>

+ 249 - 0
pages/orderDetail/tabs/followRecord/index.vue

@@ -0,0 +1,249 @@
1
+<template>
2
+	<view class="followRecord_wrap">
3
+		<rxl-timeline class="followRecord_timeLine_wrap" v-if="!isEmpty(dataList)">
4
+			<rxl-timeline-item :timestamp="key" :size="10" color="#108cff" v-for="(followList,key,index) in dataList"
5
+				:key="key+index">
6
+				<view slot="body">
7
+					<view class="body_wrap" v-for="(item) in followList" :key="item.id">
8
+						<view class="body_top">
9
+							<view class="body_top_left">
10
+								<image src="@/static/case/icon-avatar.png" mode=""></image>
11
+								<text class="follow">{{item.createNickname}}</text>
12
+							</view>
13
+							<view class="body_top_right" @click="handleDelete(item)">
14
+								删除
15
+							</view>
16
+						</view>
17
+						<view class="body_center">
18
+							{{item.content}}
19
+						</view>
20
+						<view class="body_bottom">
21
+							<u-icon name="clock" color="#C0C0C7"></u-icon>
22
+							<text class="date">{{item.createTime}}</text>
23
+						</view>
24
+					</view>
25
+				</view>
26
+			</rxl-timeline-item>
27
+		</rxl-timeline>
28
+		<view v-else class="empty_wrap">
29
+			暂无跟进记录
30
+		</view>
31
+		
32
+		<u-modal :show="showModal"
33
+			@confirm="confirm" 
34
+			ref="uModal" 
35
+			:asyncClose="true" 
36
+			showCancelButton
37
+			@cancel="showModal = false"
38
+			cancelColor="#409eff"
39
+			:confirmText="'删除'" confirmColor="#f8836c">
40
+			<view class="modal_box">
41
+				确定要删除跟进记录
42
+			</view>
43
+		</u-modal>
44
+	</view>
45
+</template>
46
+<script>
47
+	import {
48
+		groupBy,
49
+		isEmpty
50
+	} from "lodash";
51
+	export default {
52
+		props: {
53
+			clueId: {
54
+				required: true
55
+			},
56
+			orderId: {
57
+				type: String | Number,
58
+				required: false
59
+			},
60
+			type: {
61
+				type: String,
62
+				required: true
63
+				// '1' : 线索跟进
64
+				// '2' : 订单跟进(通过orderId)
65
+				// '3' : 订单跟进(通过clueId)
66
+				// '4' : 重复线索跟进
67
+				// 其他 : 重复订单跟进
68
+			}
69
+		},
70
+		data() {
71
+			return {
72
+				list: [0, 1],
73
+				dataList: {},
74
+				loading: false,
75
+				
76
+				showModal: false,
77
+				currentDeleteFollowId : undefined
78
+			}
79
+		},
80
+		methods: {
81
+			handleDelete(item){
82
+				const { id } = item;
83
+				this.currentDeleteFollowId = id;
84
+				this.showModal = true;
85
+			},
86
+			isEmpty,
87
+			async getData() {
88
+				this.loading = true;
89
+				let data;
90
+				
91
+				// 根据type类型调用不同接口
92
+				if (this.type === '1') {
93
+					// 线索跟进
94
+					data = await uni.$u.api.getClueFollowList({
95
+						clueId: this.clueId
96
+					});
97
+				} else if (this.type === '2') {
98
+					// 订单跟进(通过orderId)
99
+					data = await uni.$u.api.getOrderFollowList({
100
+						orderId: this.orderId
101
+					});
102
+				} else if (this.type === '3') {
103
+					// 订单跟进(通过clueId)
104
+					data = await uni.$u.api.getOrderFollowListByClueId({
105
+						clueId: this.clueId
106
+					});
107
+				} else if (this.type === '4') {
108
+					// 重复线索跟进
109
+					data = await uni.$u.api.getDuplicateClueFollowByClueId({
110
+						clueId: this.clueId
111
+					});
112
+				} else {
113
+					// 重复订单跟进
114
+					data = await uni.$u.api.getDuplicateOrderFollowListByClueId({
115
+						clueId: this.clueId
116
+					});
117
+				}
118
+				
119
+				this.dataList = data.data;
120
+				this.loading = false;
121
+			},
122
+			async confirm() {
123
+				try {
124
+					// 根据type类型调用不同删除接口
125
+					if (this.type === '1' || this.type === '4') {
126
+						// 线索跟进和重复线索跟进使用deleteClueFollow
127
+						await uni.$u.api.deleteClueFollow([this.currentDeleteFollowId]);
128
+					} else {
129
+						// 订单跟进(通过orderId)、订单跟进(通过clueId)、重复订单跟进使用deleteOrderFollow
130
+						await uni.$u.api.deleteOrderFollow([this.currentDeleteFollowId]);
131
+					}
132
+					
133
+					uni.$u.toast("删除成功");
134
+					this.getData();
135
+				} catch (error) {
136
+					console.error('删除跟进记录失败:', error);
137
+					uni.$u.toast("删除失败");
138
+				} finally {
139
+					this.showModal = false;
140
+					this.currentDeleteFollowId = undefined;
141
+				}
142
+			},
143
+		},
144
+		mounted() {
145
+			this.getData();
146
+		},
147
+		beforeDestroy(){
148
+			uni.$off('addFollowSuccess');  
149
+		},
150
+		created() {
151
+			uni.$on('addFollowSuccess',()=>{
152
+				this.getData();
153
+			});
154
+		}
155
+	}
156
+</script>
157
+<style lang="scss" scoped>
158
+	.followRecord_wrap {
159
+		min-height: 400px;
160
+		background: #f5f6f8;
161
+		position: relative;
162
+
163
+		.empty_wrap {
164
+			text-align: center;
165
+			color: #999999;
166
+			position: absolute;
167
+			left: 50%;
168
+			top: 50%;
169
+			transform: translate(-50%, -50%);
170
+		}
171
+	}
172
+
173
+	.followRecord_timeLine_wrap {
174
+		background: #ffffff;
175
+
176
+		.timeline-item {
177
+			&:last-child {
178
+				::v-deep .left-line .timeline-item-tail {
179
+					display: none;
180
+				}
181
+			}
182
+		}
183
+	}
184
+
185
+	.body_wrap {
186
+		margin-bottom: 20rpx;
187
+		background-color: #f6f7f8;
188
+		padding: 20rpx;
189
+		border-radius: 20rpx;
190
+
191
+		&:last-child {
192
+			margin-bottom: 0;
193
+		}
194
+
195
+		.body_top {
196
+			display: flex;
197
+			align-items: center;
198
+			justify-content: space-between;
199
+
200
+			.body_top_left {
201
+				display: flex;
202
+				align-items: center;
203
+
204
+				image {
205
+					width: 46rpx;
206
+					height: 46rpx;
207
+					margin-right: 12rpx;
208
+				}
209
+
210
+				.follow {
211
+					color: #202020;
212
+					font-size: 30rpx;
213
+					margin-right: 12rpx;
214
+				}
215
+
216
+				.debt {
217
+					color: #999999;
218
+					font-size: 24rpx;
219
+				}
220
+			}
221
+
222
+			.body_top_right {
223
+				color: #108CFF;
224
+				font-size: 24rpx;
225
+			}
226
+		}
227
+
228
+		.body_center {
229
+			margin: 30rpx 0;
230
+			padding-left: 56rpx;
231
+			overflow: hidden;
232
+		}
233
+
234
+		.body_bottom {
235
+			display: flex;
236
+			padding-left: 56rpx;
237
+
238
+			::v-deep .u-icon__icon {
239
+				font-size: 24rpx !important;
240
+			}
241
+
242
+			.date {
243
+				color: #c0c0c7;
244
+				font-size: 22rpx;
245
+				margin-left: 10rpx;
246
+			}
247
+		}
248
+	}
249
+</style>