提交 | 用户 | age
|
00fe0e
|
1 |
<template> |
63d608
|
2 |
<div style="overflow: auto"> |
H |
3 |
<div v-if="groupedEmails.length != 0"> |
|
4 |
<div v-for="(item, index) in groupedEmails" :key="index"> |
|
5 |
<div class="span-title">{{ `${item.name}(${item.data.length})` }}</div> |
|
6 |
<vxe-table |
|
7 |
ref="vxeTableRef" |
|
8 |
style="margin: 10px 0" |
|
9 |
:showHeader="false" |
|
10 |
:data="item.data" |
|
11 |
size="small" |
|
12 |
min-height="40px" |
|
13 |
:row-config="{ isCurrent: true, isHover: true }" |
db42d0
|
14 |
:menu-config="{ enabled: true }" |
63d608
|
15 |
@cell-click="cellClickEvent" |
H |
16 |
@checkbox-change="selectChangeEvent" |
db42d0
|
17 |
@cell-menu="onCellContextMenu" |
63d608
|
18 |
> |
H |
19 |
<vxe-column type="checkbox" width="30"></vxe-column> |
|
20 |
<vxe-column field="sender" title="发件人" data-index="sender" min-width="300px"> |
|
21 |
<template #default="{ row }"> |
|
22 |
<div style="display: flex; align-items: center"> |
|
23 |
<div |
|
24 |
v-if="row.mailType != 0" |
|
25 |
class="dot" |
a9a03d
|
26 |
:class="fnIsItHighlighted(row) ? 'dot-color' : ''" |
63d608
|
27 |
@click.stop="fnRowUpdateRead(row)" |
H |
28 |
></div> |
|
29 |
<a-tooltip placement="bottom"> |
|
30 |
<template #title> |
|
31 |
<span>陌生人</span> |
|
32 |
</template> |
|
33 |
<a-avatar size="small" style="margin-right: 8px" :src="row.avatar" /> |
|
34 |
</a-tooltip> |
12f730
|
35 |
|
63d608
|
36 |
<a-popover placement="bottom"> |
H |
37 |
<template #content> |
|
38 |
<div |
|
39 |
class="p-2" |
|
40 |
style=" |
|
41 |
display: flex; |
|
42 |
align-items: center; |
|
43 |
border-bottom: 1px solid rgb(5 5 5 / 6%); |
|
44 |
" |
|
45 |
> |
|
46 |
<a-avatar size="small" style="margin-right: 8px" :src="row.avatar" /> |
|
47 |
<span style="color: #000; font-weight: 700">{{ row.sender }}</span> |
a9a03d
|
48 |
<CopyOutlined @click="copyText(row.sender)" /> |
63d608
|
49 |
</div> |
H |
50 |
<div class="display-flex p-2"> |
|
51 |
<a-button type="link" size="small">新建客户</a-button> |
|
52 |
<a-dropdown> |
|
53 |
<a style="margin-right: 5px" class="ant-dropdown-link" @click.prevent> |
|
54 |
<DownOutlined /> |
|
55 |
</a> |
|
56 |
<template #overlay> |
|
57 |
<a-menu> |
|
58 |
<a-menu-item> |
|
59 |
<a href="javascript:;">添加到已有客户</a> |
|
60 |
</a-menu-item> |
|
61 |
</a-menu> |
|
62 |
</template> |
|
63 |
</a-dropdown> |
|
64 |
<a-button type="link" size="small">添加为线索</a-button> |
|
65 |
|
|
66 |
<a-dropdown style="margin-right: 5px"> |
|
67 |
<a style="margin-right: 5px" class="ant-dropdown-link" @click.prevent> |
|
68 |
<DownOutlined /> |
|
69 |
</a> |
|
70 |
<template #overlay> |
|
71 |
<a-menu> |
|
72 |
<a-menu-item> |
|
73 |
<a href="javascript:;">添加到通讯录</a> |
|
74 |
</a-menu-item> |
|
75 |
</a-menu> |
|
76 |
</template> |
|
77 |
</a-dropdown> |
|
78 |
<a-button type="link" size="small">往来邮件</a-button></div |
|
79 |
> |
|
80 |
</template> |
db42d0
|
81 |
<div :class="fnIsItHighlighted(row) ? 'title-dot-color' : ''"> |
63d608
|
82 |
<span style="font-weight: 700">{{ row.senderName }}</span |
H |
83 |
><span style="padding: 0 8px">|</span> |
|
84 |
<span style="font-weight: 500">{{ row.sender }}</span> |
00fe0e
|
85 |
</div> |
63d608
|
86 |
</a-popover> |
H |
87 |
</div> |
|
88 |
</template> |
|
89 |
</vxe-column> |
db42d0
|
90 |
<vxe-column |
H |
91 |
show-overflow |
|
92 |
field="icon" |
|
93 |
title="表题" |
|
94 |
data-index="icon" |
|
95 |
width="100" |
|
96 |
align="right" |
|
97 |
> |
|
98 |
<template #default="{ row }"> |
|
99 |
<a-tooltip placement="bottom"> |
|
100 |
<template #title> |
|
101 |
<span>有附件</span> |
|
102 |
</template> |
|
103 |
<span v-show="row.attachmentList?.length > 0"> |
|
104 |
<PaperClipOutlined /> |
|
105 |
</span> |
|
106 |
</a-tooltip> |
|
107 |
</template> |
|
108 |
</vxe-column> |
|
109 |
|
63d608
|
110 |
<vxe-column |
H |
111 |
show-overflow |
|
112 |
field="subject" |
|
113 |
title="表题" |
|
114 |
data-index="subject" |
|
115 |
min-width="250" |
|
116 |
> |
|
117 |
<template #default="{ row }"> |
|
118 |
<span |
|
119 |
class="title-dot" |
a9a03d
|
120 |
:class="fnIsItHighlighted(row) ? 'title-dot-color' : ''" |
63d608
|
121 |
style="font-weight: 500" |
H |
122 |
>{{ row.subject || '(无主题)' }}</span |
|
123 |
> |
|
124 |
- |
|
125 |
<span style="color: #999">{{ row.subject }}</span> |
|
126 |
</template> |
|
127 |
</vxe-column> |
|
128 |
<vxe-column field="action" title="Action" width="190"> |
|
129 |
<template #default="{ row, rowIndex }"> |
|
130 |
<span style="display: flex; justify-content: space-around"> |
|
131 |
<span>{{ |
|
132 |
row.mailType !== 0 |
db42d0
|
133 |
? formatToDateDay(row.receiveTime || row.createTime) |
63d608
|
134 |
: formatToDateDay(row.createTime) |
H |
135 |
}}</span> |
00fe0e
|
136 |
|
63d608
|
137 |
<TooltipAndDropdown |
H |
138 |
:tooltipTitle="'待处理邮件'" |
|
139 |
:initialDropdownOpen="false" |
|
140 |
:initialTooltipOpen="false" |
|
141 |
:showTooltip="!!row.handleTime" |
|
142 |
:row="row" |
|
143 |
:docCodeS="[row.docCode]" |
|
144 |
/> |
|
145 |
<span style="margin-left: 5px"><PushpinOutlined @click.stop="fnTagging" /></span> |
|
146 |
</span> |
|
147 |
</template> |
|
148 |
</vxe-column> |
db42d0
|
149 |
</vxe-table> |
H |
150 |
<ContextMenu |
|
151 |
v-if="showMenu" |
|
152 |
:style="menuStyle" |
|
153 |
:selected-cell="selectedCell" |
|
154 |
@close-menu="showMenu = false" |
|
155 |
/> </div |
63d608
|
156 |
></div> |
74a35f
|
157 |
|
a9a03d
|
158 |
<div v-else style="display: flex; align-items: center; justify-content: center; height: 70vh"> |
63d608
|
159 |
<a-empty /> |
00fe0e
|
160 |
</div> |
ccfd07
|
161 |
<DrawerDetail |
H |
162 |
ref="drawerDetailRef" |
|
163 |
v-model="openDrawerDetail" |
|
164 |
:mailId="rowMailId" |
74a35f
|
165 |
:selectAllRow="selectRow" |
ccfd07
|
166 |
:allList="dataSource" |
a9a03d
|
167 |
:isDrafts="isDrafts" |
ccfd07
|
168 |
/> |
74a35f
|
169 |
<a-dropdown :trigger="['click']" placement="bottomLeft" ref="dropdownRefs"> </a-dropdown> |
00fe0e
|
170 |
</div> |
H |
171 |
</template> |
|
172 |
|
|
173 |
<script lang="ts" setup> |
|
174 |
name: 'ListPageTable'; |
|
175 |
import { |
|
176 |
FieldTimeOutlined, |
|
177 |
PushpinOutlined, |
|
178 |
CopyOutlined, |
|
179 |
DownOutlined, |
db42d0
|
180 |
PaperClipOutlined, |
00fe0e
|
181 |
} from '@ant-design/icons-vue'; |
H |
182 |
|
12f730
|
183 |
import { ref, watch, defineProps, defineEmits, computed, defineExpose, inject } from 'vue'; |
00fe0e
|
184 |
|
H |
185 |
// 定义属性 |
|
186 |
interface Props { |
12f730
|
187 |
pageList: []; |
a9a03d
|
188 |
isDrafts?: boolean; |
00fe0e
|
189 |
} |
12f730
|
190 |
const props = defineProps<Props>(); |
00fe0e
|
191 |
|
12f730
|
192 |
const groupedEmails = ref<GroupedDataItem[]>([]); |
00fe0e
|
193 |
|
12f730
|
194 |
const dataSource = ref([]); |
H |
195 |
watch( |
|
196 |
() => props.pageList, |
|
197 |
(newValue) => { |
63d608
|
198 |
dataSource.value = newValue || []; |
H |
199 |
groupedEmails.value = groupEmailsByDate(newValue || []); |
12f730
|
200 |
}, |
H |
201 |
); |
00fe0e
|
202 |
import dayjs from 'dayjs'; |
H |
203 |
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; |
|
204 |
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; |
|
205 |
import isoWeek from 'dayjs/plugin/isoWeek'; |
|
206 |
dayjs.extend(isSameOrAfter); |
|
207 |
dayjs.extend(isSameOrBefore); |
|
208 |
dayjs.extend(isoWeek); |
|
209 |
|
|
210 |
interface EmailItem { |
|
211 |
id: number; |
|
212 |
subject: string; |
|
213 |
} |
|
214 |
|
|
215 |
// 确保 groupedData 的结构正确且 data 是 EmailItem 类型的数组 |
|
216 |
interface GroupedDataItem { |
|
217 |
key: string; |
|
218 |
data: EmailItem[]; |
|
219 |
name: string; |
|
220 |
} |
|
221 |
|
|
222 |
function groupEmailsByDate(dataSource) { |
|
223 |
const today = dayjs(); |
|
224 |
const yesterday = dayjs().subtract(1, 'day'); |
|
225 |
const startOfWeek = dayjs().startOf('week'); |
|
226 |
const startOfMonth = dayjs().startOf('month'); |
|
227 |
const startOfLastMonth = dayjs().subtract(1, 'month').startOf('month'); |
|
228 |
const endOfLastMonth = dayjs().subtract(1, 'month').endOf('month'); |
|
229 |
|
|
230 |
const groupedData: GroupedDataItem[] = [ |
|
231 |
{ |
|
232 |
data: [], |
|
233 |
name: '今天', |
|
234 |
key: 'today', |
|
235 |
}, |
|
236 |
{ |
|
237 |
data: [], |
|
238 |
name: '昨天', |
|
239 |
key: 'yesterday', |
|
240 |
}, |
|
241 |
{ |
|
242 |
data: [], |
|
243 |
name: '本周', |
|
244 |
key: 'thisWeek', |
|
245 |
}, |
|
246 |
{ |
|
247 |
data: [], |
|
248 |
name: '这个月', |
|
249 |
key: 'thisMonth', |
|
250 |
}, |
|
251 |
{ |
|
252 |
data: [], |
|
253 |
name: '上个月', |
|
254 |
key: 'lastMonth', |
|
255 |
}, |
|
256 |
{ |
|
257 |
data: [], |
|
258 |
name: '更早', |
|
259 |
key: 'earlier', |
|
260 |
}, |
|
261 |
]; |
|
262 |
|
|
263 |
dataSource.forEach((item: any) => { |
|
264 |
try { |
74a35f
|
265 |
const emailDate = dayjs(item.createTime); |
00fe0e
|
266 |
if (emailDate.isSame(today, 'day')) { |
H |
267 |
groupedData[0].data.push(item); |
|
268 |
} else if (emailDate.isSame(yesterday, 'day')) { |
|
269 |
groupedData[1].data.push(item); |
|
270 |
} else if (emailDate.isSameOrAfter(startOfWeek) && emailDate.isBefore(today, 'day')) { |
|
271 |
groupedData[2].data.push(item); |
|
272 |
} else if (emailDate.isSameOrAfter(startOfMonth) && emailDate.isBefore(today, 'day')) { |
|
273 |
groupedData[3].data.push(item); |
|
274 |
} else if ( |
|
275 |
emailDate.isSameOrAfter(startOfLastMonth) && |
|
276 |
emailDate.isSameOrBefore(endOfLastMonth) |
|
277 |
) { |
|
278 |
groupedData[4].data.push(item); |
|
279 |
} else { |
|
280 |
groupedData[5].data.push(item); |
|
281 |
} |
|
282 |
} catch (error) { |
|
283 |
console.error(`Error processing item date: ${item.date}`, error); |
|
284 |
} |
|
285 |
}); |
|
286 |
// 将结果按中文映射进行返回 |
|
287 |
const result = <{ data: any; name: string; key: string }[]>[]; |
|
288 |
groupedData.forEach((group: { data: any; name: string; key: string }) => { |
|
289 |
if (group.data.length > 0) { |
|
290 |
result.push(group); |
|
291 |
} |
|
292 |
}); |
|
293 |
|
|
294 |
return result; |
|
295 |
} |
|
296 |
|
|
297 |
// 右键菜单 |
db42d0
|
298 |
import ContextMenu from '@/views/email/components/ContextMenu/index.vue'; |
H |
299 |
const showMenu = ref(false); |
|
300 |
const menuStyle = ref({}); |
|
301 |
const selectedCell = ref(null); |
|
302 |
const onCellContextMenu = ({ |
|
303 |
type, |
|
304 |
row, |
|
305 |
rowIndex, |
|
306 |
$rowIndex, |
|
307 |
column, |
|
308 |
columnIndex, |
|
309 |
$columnIndex, |
|
310 |
$event, |
|
311 |
}) => { |
|
312 |
$event.preventDefault(); // 阻止默认的浏览器右键菜单? |
|
313 |
selectedCell.value = { row, column, columnIndex }; // 保存当前选中的单元格数据 |
|
314 |
|
|
315 |
// 计算菜单初始位置 |
|
316 |
let menuX = $event.clientX; |
|
317 |
let menuY = $event.clientY; |
|
318 |
|
|
319 |
// 获取菜单的宽度和高度 |
|
320 |
const menuWidth = 200; // 假设菜单宽度为200px,可以根据实际情况调整 |
|
321 |
const menuHeight = 800; // 假设菜单高度为150px,可以根据实际情况调整 |
|
322 |
// 获取窗口宽度和高度 |
|
323 |
const windowWidth = window.innerWidth; |
|
324 |
const windowHeight = window.innerHeight; |
|
325 |
|
|
326 |
// 检查菜单是否超出窗口的右边界,如果是,则向左调整 |
|
327 |
if (menuX + menuWidth > windowWidth) { |
|
328 |
menuX = windowWidth - menuWidth; |
|
329 |
} |
|
330 |
|
|
331 |
// 检查菜单是否超出窗口的下边界,如果是,则向上调整 |
|
332 |
if (menuY + menuHeight > windowHeight) { |
|
333 |
menuY = windowHeight - menuHeight; |
|
334 |
} |
|
335 |
menuStyle.value = { |
|
336 |
position: 'fixed', |
|
337 |
top: `${menuY}px`, |
|
338 |
left: `${menuX}px`, |
|
339 |
}; |
|
340 |
showMenu.value = true; |
00fe0e
|
341 |
}; |
H |
342 |
|
db42d0
|
343 |
// 监听全局点击事件,点击页面其他地方关闭菜单 |
H |
344 |
document.addEventListener('click', () => { |
|
345 |
showMenu.value = false; |
|
346 |
}); |
|
347 |
|
67287b
|
348 |
const vxeTableRef = ref(); |
a9a03d
|
349 |
function fnIsItHighlighted(row) { |
db42d0
|
350 |
if (props.isDrafts) { |
H |
351 |
return row.readFlag && props.isDrafts; |
|
352 |
} else { |
|
353 |
return row.readFlag; |
|
354 |
} |
a9a03d
|
355 |
} |
67287b
|
356 |
function fnSelectAll(is) { |
a9a03d
|
357 |
try { |
H |
358 |
if (!vxeTableRef.value) { |
|
359 |
return; |
|
360 |
} |
|
361 |
vxeTableRef.value.forEach((row) => { |
|
362 |
row.setAllCheckboxRow(is); |
|
363 |
}); |
|
364 |
selectChangeEvent(); |
|
365 |
} catch (error) { |
|
366 |
console.log(error); |
|
367 |
} |
67287b
|
368 |
} |
00fe0e
|
369 |
|
12f730
|
370 |
function selectChangeEvent() { |
H |
371 |
const isAll = getCheckboxRecords().length === dataSource.value.length; |
|
372 |
const data = { |
|
373 |
isAll, |
|
374 |
records: getCheckboxRecords(), |
|
375 |
}; |
|
376 |
emit('updateSelectAll', data); |
67287b
|
377 |
} |
H |
378 |
function getCheckboxRecords() { |
|
379 |
const list = new Set(); |
|
380 |
|
|
381 |
vxeTableRef.value.forEach((row) => { |
|
382 |
const records = row.getCheckboxRecords(); // 假设该方法返回一个数组 |
|
383 |
if (Array.isArray(records)) { |
|
384 |
// 确保 records 是数组 |
|
385 |
records.forEach((record) => list.add(record)); // 将记录添加到 Set 中 |
|
386 |
} |
|
387 |
}); |
|
388 |
|
|
389 |
return Array.from(list); // 将 Set 转换回数组 |
|
390 |
} |
00fe0e
|
391 |
// 操作row |
H |
392 |
function fnProcessingTime(row) { |
|
393 |
console.log(row); |
|
394 |
} |
|
395 |
function fnTagging(row) { |
|
396 |
console.log(row); |
|
397 |
} |
|
398 |
import DrawerDetail from './drawerDetail.vue'; |
|
399 |
// 详情内容 |
|
400 |
const openDrawerDetail = ref(false); |
ccfd07
|
401 |
const rowMailId = ref(''); |
74a35f
|
402 |
const selectRow = ref([]); |
00fe0e
|
403 |
const cellClickEvent = (event) => { |
74a35f
|
404 |
selectRow.value = []; |
ccfd07
|
405 |
rowMailId.value = event.row.docCode; |
74a35f
|
406 |
selectRow.value.push({ docCode: event.row.docCode }); |
a9a03d
|
407 |
fnRowUpdateRead(event.row); |
00fe0e
|
408 |
openDrawerDetail.value = true; |
H |
409 |
}; |
67287b
|
410 |
|
12f730
|
411 |
// 更新祖父组件数据 |
H |
412 |
const getDataList = inject('getDataList'); |
|
413 |
|
74a35f
|
414 |
import { updateReadApi, updateHandleAPi } from '@/api/email/userList'; |
12f730
|
415 |
// 标志未读/经读 |
H |
416 |
function fnRowUpdateRead(row) { |
|
417 |
const data = { |
|
418 |
status: !row.readFlag, |
|
419 |
list: [row.docCode], |
|
420 |
}; |
|
421 |
pushReadApi(data); |
|
422 |
} |
|
423 |
function pushReadApi(params) { |
|
424 |
updateReadApi(params).then((res) => { |
|
425 |
if (res.code == 0) { |
|
426 |
// |
ccfd07
|
427 |
getDataList({}); |
12f730
|
428 |
} |
H |
429 |
}); |
|
430 |
} |
a9a03d
|
431 |
import { useMessage } from '@/hooks/web/useMessage'; |
H |
432 |
|
|
433 |
const { createMessage } = useMessage(); |
|
434 |
const copyText = async (value) => { |
|
435 |
try { |
|
436 |
await navigator.clipboard.writeText(value); |
|
437 |
setTimeout(() => { |
|
438 |
createMessage.success('复制成功'); |
|
439 |
}, 500); |
|
440 |
} catch (err) { |
|
441 |
console.error('复制失败: ', err); |
|
442 |
} |
|
443 |
}; |
12f730
|
444 |
const emit = defineEmits(['selectAll', 'updateSelectAll']); |
67287b
|
445 |
defineExpose({ |
H |
446 |
fnSelectAll, |
|
447 |
}); |
74a35f
|
448 |
|
H |
449 |
import TooltipAndDropdown from './TooltipAndDropdown .vue'; |
|
450 |
import { formatToDateDay } from '@/utils/dateUtil'; |
00fe0e
|
451 |
</script> |
H |
452 |
<style scoped lang="less"> |
|
453 |
.display-flex { |
|
454 |
display: flex; |
|
455 |
align-items: center; |
|
456 |
justify-content: space-between; |
|
457 |
} |
|
458 |
|
|
459 |
.head { |
|
460 |
display: flex; |
|
461 |
justify-content: space-between; |
|
462 |
width: 100%; |
|
463 |
border-bottom: 1px solid rgb(5 5 5 / 6%); |
|
464 |
|
|
465 |
/* 增加选择器特异性 */ |
|
466 |
& .left { |
|
467 |
width: 20%; |
|
468 |
|
|
469 |
& .left-box { |
|
470 |
display: flex; |
|
471 |
align-items: center; |
|
472 |
justify-content: space-flex-start; |
|
473 |
width: 100%; |
|
474 |
|
|
475 |
& .icon { |
|
476 |
margin-right: 15px; |
|
477 |
font-size: 16px; |
|
478 |
} |
|
479 |
} |
|
480 |
} |
|
481 |
|
|
482 |
& .right { |
|
483 |
display: flex; |
|
484 |
align-items: center; |
|
485 |
} |
|
486 |
} |
|
487 |
|
|
488 |
.left-bt { |
|
489 |
display: flex; |
|
490 |
align-items: center; |
|
491 |
justify-content: center; |
|
492 |
padding-left: 27px; |
|
493 |
background: #fffbe6; |
|
494 |
} |
|
495 |
|
|
496 |
.span-title { |
63d608
|
497 |
width: 100%; |
db42d0
|
498 |
height: 30px; |
63d608
|
499 |
padding: 5px; |
00fe0e
|
500 |
color: #000; |
H |
501 |
font-weight: 700; |
db42d0
|
502 |
line-height: 30px; |
63d608
|
503 |
text-align: left; |
00fe0e
|
504 |
} |
H |
505 |
|
|
506 |
.table { |
|
507 |
height: 80vh; |
|
508 |
} |
|
509 |
|
|
510 |
.my-menus { |
|
511 |
background-color: #f8f8f9; |
|
512 |
} |
12f730
|
513 |
// 圆点 |
H |
514 |
.dot { |
|
515 |
display: inline-block; |
|
516 |
width: 8px; |
|
517 |
height: 8px; |
|
518 |
margin-right: 10px; |
|
519 |
border-radius: 50%; |
|
520 |
background-color: #0a6aff; |
|
521 |
} |
|
522 |
|
|
523 |
.dot-color { |
|
524 |
background-color: #d9d9d9; |
|
525 |
color: #d9d9d9; |
|
526 |
} |
|
527 |
|
|
528 |
.title-dot { |
|
529 |
color: #0a6aff; |
|
530 |
} |
|
531 |
|
|
532 |
.title-dot-color { |
|
533 |
color: #999; |
|
534 |
} |
00fe0e
|
535 |
</style> |