Commit 7511e0c8 by pangchong

feat: 事件详情开发

parent 1228eb37
......@@ -36,5 +36,6 @@ declare module 'vue' {
RangePickey: (typeof import('./src/components/range-pickey/index.vue'))['default']
GlobalAvatar: (typeof import('./src/components/global-avatar/index.vue'))['default']
GlobalModel: (typeof import('./src/components/global-model/index.vue'))['default']
FilePreview: (typeof import('./src/components/file-preview/index.vue'))['default']
}
}
/* HTML5 display-role reset for older browsers */
html,
body,
div,
......@@ -83,8 +84,6 @@ video {
padding: 0;
border: 0;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
......@@ -137,6 +136,7 @@ div {
box-sizing: border-box;
}
/* 滚动轴 */
::-webkit-scrollbar {
border-radius: 10px;
-moz-border-radius: 10px;
......@@ -144,7 +144,6 @@ div {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
-moz-border-radius: 10px;
......@@ -153,10 +152,18 @@ div {
background-clip: padding-box;
min-height: 28px;
}
::-webkit-scrollbar-thumb:hover {
border-radius: 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
background-color: var(--color-fill-4);
}
/* arco.design */
.arco-table-th {
background: var(--color-fill-1);
border-bottom: 1px solid var(--color-neutral-3);
}
.arco-table-td {
background: inherit;
}
<template>
<global-model v-model:visible="showPreview" width="80%" :footer="false" modal-class="file-preview" :title="file.title" :onClose="onClose">
<div style="height: 80vh">
<template v-if="file.type == 'image'">
<a-image :preview="false" width="100%" height="100%" :src="file.src"></a-image>
</template>
<template v-else-if="file.type == 'video'">
<video width="100%" height="100%" :src="file.src" controls="true"></video>
</template>
<template v-else>
<iframe :src="file.src" frameborder="0" width="100%" height="100%"></iframe>
</template>
</div>
</global-model>
</template>
<script setup lang="ts">
const showPreview = ref(false)
interface FileType {
title?: string
src: string
type?: 'image' | 'file' | 'video'
}
const file = reactive<FileType>({
title: '预览',
src: '',
type: 'image'
})
const open = (params: FileType) => {
Object.assign(file, params)
showPreview.value = true
}
const onClose = () => {
Object.assign(file, {
title: '预览',
src: '',
type: 'image'
})
}
defineExpose({
open
})
</script>
<style lang="less">
.file-preview .arco-modal-body {
padding: 16px;
}
</style>
import Day from '@/utils/dayjs'
/**
* 通用的格式化时间
* @param input {String | Date | Number | null} 可以被传入Date构造函数的任意数据
......@@ -5,37 +7,22 @@
* @param defaultVal {String} 当传入数据不合法时的默认值,默认为'--’
* @return String 格式化之后的字符串
*/
export const formatDate = (
input: Date | string | number | null,
until = "all",
defaultVal: string = '--'
): string => {
export const formatDate = (input: Date | string | number | null, until = 'all', defaultVal: string = '--'): string => {
// @ts-ignore
if (input === null || input === 0||input < 0) return defaultVal;
const date = new Date(input);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const min = date.getMinutes();
const sec = date.getSeconds();
if (until === 'day')
return `${year}-${month.toString().padStart(2, "0")}-${day
.toString()
.padStart(2, "0")}`
if (until === "min") {
return `${year}-${month.toString().padStart(2, "0")}-${day
.toString()
.padStart(2, "0")} ${hour.toString().padStart(2, "0")}:${min
.toString()
.padStart(2, "0")}`;
if (input === null || input === 0 || input < 0) return defaultVal
const date = new Date(input)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const min = date.getMinutes()
const sec = date.getSeconds()
if (until === 'day') return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
if (until === 'min') {
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`
}
return `${year}-${month.toString().padStart(2, "0")}-${day
.toString()
.padStart(2, "0")} ${hour.toString().padStart(2, "0")}:${min
.toString()
.padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
};
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`
}
/**
* 加上年月日时分秒的中国本土化的格式化时间
......@@ -44,34 +31,34 @@ export const formatDate = (
* @param defaultVal {String} 当传入数据不合法时的默认值,默认为'--’
* @return String 格式化之后的字符串
*/
export const formatDateZh = (
input: Date | string | number | null,
until = "all",
defaultVal: string = '--'
): string => {
export const formatDateZh = (input: Date | string | number | null, until = 'all', defaultVal: string = '--'): string => {
// @ts-ignore
if (input === null || input === 0||input < 0) return defaultVal;
const date = new Date(input);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const min = date.getMinutes();
const sec = date.getSeconds();
if (until === 'day')
return `${year}${month.toString().padStart(2, "0")}${day
.toString()
.padStart(2, "0")}日`
if (until === "min") {
return `${year}${month.toString().padStart(2, "0")}${day
.toString()
.padStart(2, "0")}${hour.toString().padStart(2, "0")}:${min
.toString()
.padStart(2, "0")}`;
if (input === null || input === 0 || input < 0) return defaultVal
const date = new Date(input)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const min = date.getMinutes()
const sec = date.getSeconds()
if (until === 'day') return `${year}${month.toString().padStart(2, '0')}${day.toString().padStart(2, '0')}日`
if (until === 'min') {
return `${year}${month.toString().padStart(2, '0')}${day.toString().padStart(2, '0')}${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`
}
return `${year}${month.toString().padStart(2, "0")}${day
return `${year}${month.toString().padStart(2, '0')}${day.toString().padStart(2, '0')}${hour.toString().padStart(2, '0')}:${min
.toString()
.padStart(2, "0")}${hour.toString().padStart(2, "0")}:${min
.toString()
.padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
};
\ No newline at end of file
.padStart(2, '0')}:${sec.toString().padStart(2, '0')}`
}
/**
* 时间戳格式化
* 1715072168340 => 2024-05-07 16:56:08
*/
export const timeStampFormat = (timeStamp: string | number, opt?: { format?: string }): string => {
if (!timeStamp) {
return ''
}
timeStamp = parseInt(String(timeStamp))
const format = opt?.format || 'YYYY-MM-DD HH:mm:ss'
return Day(timeStamp).format(format)
}
......@@ -11,7 +11,8 @@ export const eventListColumns: TableColumnData[] = [
{
title: '事件名称',
dataIndex: 'title',
slotName: 'title'
slotName: 'title',
width: 400
},
{
title: '发起人',
......@@ -58,22 +59,26 @@ export const eventDetailsColumns1: TableColumnData[] = [
{
title: '文件名',
align: 'center',
dataIndex: 'fileName'
dataIndex: 'fileName',
slotName: 'fileName'
},
{
title: '文件大小',
align: 'center',
dataIndex: 'fileSize'
dataIndex: 'fileSize',
slotName: 'fileSize'
},
{
title: '发送人',
align: 'center',
dataIndex: 'fromId'
dataIndex: 'fromId',
slotName: 'fromId'
},
{
title: '发送时间',
align: 'center',
dataIndex: 'updatedTime'
dataIndex: 'createdTime',
slotName: 'createdTime'
},
{
title: '操作',
......@@ -87,27 +92,31 @@ export const eventDetailsColumns2: TableColumnData[] = [
{
title: '文件名',
align: 'center',
dataIndex: 'fileName'
dataIndex: 'fileName',
slotName: 'fileName'
},
{
title: '文件大小',
align: 'center',
dataIndex: 'fileSize'
},
{
title: '视频时长',
align: 'center',
dataIndex: 'videoDuration'
dataIndex: 'fileSize',
slotName: 'fileSize'
},
// {
// title: '视频时长',
// align: 'center',
// dataIndex: 'videoDuration'
// },
{
title: '发送人',
align: 'center',
dataIndex: 'fromId'
dataIndex: 'fromId',
slotName: 'fromId'
},
{
title: '发送时间',
align: 'center',
dataIndex: 'updatedTime'
dataIndex: 'createdTime',
slotName: 'createdTime'
},
{
title: '操作',
......
<template>
<div class="g-block ml-4 px-4">
<template v-if="details && record">
<div class="flex justify-between">
<div class="flex items-center space-x-8">
<icon-left :size="20" />
<span>2023-10-26 10:15:54通话事件</span>
<icon-left :size="20" @click="showList" class="cursor-pointer" />
<span>{{ record.title }}</span>
</div>
<a-radio-group v-model="active">
<template v-for="(item, i) in tabs" :key="i">
......@@ -17,132 +18,75 @@
</div>
<div class="flex items-center py-8">
<a-avatar-group :size="40">
<global-avatar :icon-size="15"></global-avatar>
<global-avatar :icon-size="15"></global-avatar>
<global-avatar :icon-size="15" v-for="item in record.userList" :key="item.id"></global-avatar>
</a-avatar-group>
<span class="ml-2">专家1,维修1 参与过该事件</span>
<span class="ml-2">{{ getParticipantsName(record.userList) }}</span>
</div>
<div class="overflow-y-auto">
<div class="overflow-y-auto" v-if="details?.total">
<template v-if="active == 0">
<div class="mb-4">
<template v-for="item in details.list" :key="item._id">
<div class="flex-center mb-4" v-if="item.fileType == 'txt'">
<div class="flex-center flex-col shadow-lg rounded bg-fill-bg2 p-4">
<div>{{ item.content }}</div>
<div class="mt-2">{{ timeStampFormat(item.createdTime) }}</div>
</div>
</div>
<div class="mb-4" v-else>
<div class="flex items-center space-x-4">
<global-avatar :icon-size="12"></global-avatar>
<global-avatar :icon-size="15"></global-avatar>
<div class="space-x-4">
<span>专家</span>
<span>11698288374814</span>
<span>{{ getInitiatorInfo(item.fromId).nickname }}</span>
<span>{{ timeStampFormat(item.createdTime) }}</span>
</div>
</div>
<div class="flex items-center space-x-8 mt-4 ml-14">
<icon-play-circle class="cursor-pointer" :size="25" />
<icon-download class="cursor-pointer" :size="25" />
</div>
</div>
<div class="flex-center mb-4" v-for="item in 10">
<div class="flex-center flex-col shadow-lg rounded bg-fill-bg2 p-4">
<div>专家1加入了通话</div>
<div class="mt-2">1698288370706</div>
<icon-play-circle class="cursor-pointer" :size="25" @click="handlePreview(item)" />
<icon-download class="cursor-pointer" :size="25" @click="handleDown(item)" />
</div>
</div>
</template>
</template>
<template v-else>
<a-table :columns="eventDetailsColumns1" :data="[]" :bordered="false" :pagination="false" />
<a-table :columns="getEventDetailsColumns" :data="getEventDetailsData" :bordered="false" :pagination="false">
<template #fileName="{ record }">
{{ JSON.parse(record.content).fileName }}
</template>
<template #fileSize="{ record }">{{ (parseFloat(JSON.parse(record.content).fileSize) / 1024 / 1024).toFixed(2) }} MB</template>
<template #createdTime="{ record }">
{{ timeStampFormat(record.createdTime) }}
</template>
<template #fromId="{ record }">
{{ getInitiatorInfo(record.fromId).nickname }}
</template>
<template #operation="{ record }">
<a-button type="text" @click="handleDown(record)">下载</a-button>
</template>
</a-table>
</template>
</div>
<template v-else>
<a-empty class="w-full h-full flex-center flex-col" />
</template>
</template>
</div>
<file-preview ref="previewRef"></file-preview>
</template>
<script setup lang="ts">
import { IconLeft, IconPlayCircle, IconDownload } from '@arco-design/web-vue/es/icon'
import { eventDetailsColumns1, eventDetailsColumns2 } from './constants/data.config'
import { timeStampFormat } from '@/utils/dataTypes/dateRelated/formatDate'
import { directlyDownloadFromURL } from '@/utils/downloadData/downloadBlob'
const details = {
total: '3',
pageIndex: '1',
pageSize: '10',
list: [
{
_id: '6539d2f6561a4d043c508871',
channelId: '2_1698288370581',
closeTime: '1970-01-01T08:00:00',
companyId: '1',
content: 'https://video.anyremote.cn:444/89169459-01e2-4594-a01a-cbbca9dd5bab/0_20231026024612670.mp4',
createdTime: 1698288374814,
fileMd5: '',
fileName: '0_20231026024612670.mp4',
fileSize: '351399',
fileSizeTxt: '',
fileType: 'video',
fileUploadId: '1717372034058698754',
fileUrl: 'https://video.anyremote.cn:444/89169459-01e2-4594-a01a-cbbca9dd5bab/0_20231026024612670.mp4',
fromId: '2',
isDel: '0',
isTop: '0',
msgId: '',
quoteId: '',
readUid: '2',
receiveUid: ',7,',
status: '',
toId: '-2',
updatedTime: 1698288374814,
videoDuration: '1',
videoDurationTxt: ''
},
{
_id: '6539d2f2561a4d043c508870',
channelId: '2_1698288370581',
closeTime: '1970-01-01T08:00:00',
companyId: '1',
content: '专家1加入了通话',
createdTime: 1698288370706,
fileMd5: '',
fileName: '',
fileSize: '0',
fileSizeTxt: '',
fileType: 'txt',
fileUploadId: '0',
fileUrl: '',
fromId: '2',
isDel: '0',
isTop: '0',
msgId: '',
quoteId: '',
readUid: '2',
receiveUid: ',7,',
status: '',
toId: '',
updatedTime: 1698288370706,
videoDuration: '0',
videoDurationTxt: ''
},
{
_id: '6539d2f2561a4d043c50886f',
channelId: '2_1698288370581',
closeTime: '1970-01-01T08:00:00',
companyId: '1',
content: '维修1加入了通话',
createdTime: 1698288370696,
fileMd5: '',
fileName: '',
fileSize: '0',
fileSizeTxt: '',
fileType: 'txt',
fileUploadId: '0',
fileUrl: '',
fromId: '7',
isDel: '0',
isTop: '0',
msgId: '',
quoteId: '',
readUid: '7',
receiveUid: ',7,',
status: '',
toId: '',
updatedTime: 1698288370696,
videoDuration: '0',
videoDurationTxt: ''
}
]
interface Props {
details?: any
record?: any
}
const ps = withDefaults(defineProps<Props>(), {
details: () => undefined,
record: () => undefined
})
const es = defineEmits(['showList'])
const active = ref(0)
const tabs = ref([
......@@ -151,6 +95,55 @@ const tabs = ref([
{ value: 2, label: '文档' },
{ value: 3, label: '视频' }
])
//返回列表
const showList = () => {
es('showList')
active.value = 0
}
//获取表格列名称
const getEventDetailsColumns = computed(() => {
if (active.value == 1 || active.value == 2) {
return eventDetailsColumns1
} else {
return eventDetailsColumns2
}
})
//获取表格数据
const getEventDetailsData = computed(() => {
if (active.value == 1) {
return ps.details?.list.filter((item) => item.fileType == 'image')
}
if (active.value == 2) {
return ps.details?.list.filter((item) => item.fileType == 'file')
}
if (active.value == 3) {
return ps.details?.list.filter((item) => item.fileType == 'video')
}
return []
})
//获取所有参与人名称
const getParticipantsName = (userList: any[]) => {
return userList.map((item) => item.nickname).join(',')
}
//获取参与人信息
const getInitiatorInfo = (id) => {
return ps.record?.userList.find((item) => item.id == id)
}
//预览文件
const previewRef = ref()
const handlePreview = (file: any) => {
const content = JSON.parse(file.content)
const params = {
src: content.fileUrl,
type: file.fileType
}
previewRef.value!.open(params)
}
//下载文件
const handleDown = (file: any) => {
const content = JSON.parse(file.content)
directlyDownloadFromURL(content.fileUrl, content.fileName)
}
</script>
<style lang="less" scoped>
.arco-tag {
......
<template>
<!-- 事件列表 -->
<event-list></event-list>
<event-list v-show="!showDetails" @show-details="handleShowDetails" @show-list="handleShowList"></event-list>
<!-- 事件详情 -->
<!-- <event-details></event-details> -->
<event-details v-show="showDetails" :record="record" :details="details" @show-list="handleShowList"></event-details>
</template>
<script setup lang="ts">
import EventList from './eventList.vue'
import EventDetails from './eventDetails.vue'
const showDetails = ref(false)
const record = ref()
const details = ref()
//显示详情
const handleShowDetails = (data: any) => {
showDetails.value = true
record.value = data.record
details.value = data.details
}
//显示列表
const handleShowList = () => {
showDetails.value = false
record.value = undefined
details.value = undefined
}
</script>
<style lang="less" scoped></style>
......@@ -11,14 +11,20 @@
<template #index="{ rowIndex }">
{{ rowIndex + 1 }}
</template>
<template #title="{ record }">
<div class="flex items-center w-full space-x-4">
<template #title="{ record, rowIndex }">
<a-spin :loading="loadingTitle && record.isFocus">
<div class="flex items-center w-full space-x-4" v-if="record.isFocus">
<div class="flex-auto">
<a-input :default-value="record.title"></a-input>
<a-input v-model="record.title" :ref="(vc: any) => (titleArr[rowIndex] = vc)" allow-clear></a-input>
</div>
<icon-close class="cursor-pointer" />
<icon-check class="cursor-pointer" />
<icon-close class="cursor-pointer" @click="handleClose(record, rowIndex)" />
<icon-check class="cursor-pointer" @click="handleConfirm(record, rowIndex)" />
</div>
<div class="flex justify-between items-center w-full" v-else>
<div>{{ record.title }}</div>
<div class="min-w-5"><icon-edit class="cursor-pointer" @click="handleFocus(record, rowIndex)" /></div>
</div>
</a-spin>
</template>
<template #initiator="{ record }">
<div class="flex items-center">
......@@ -29,16 +35,18 @@
<template #participants="{ record }">
<div class="flex items-center">
<a-avatar-group :size="32" :max-count="2">
<global-avatar :icon-size="12" v-for="item in record.userList"></global-avatar>
<global-avatar :icon-size="12" v-for="item in record.userList" :key="item.id"></global-avatar>
</a-avatar-group>
<span class="ml-2">{{ getParticipantsName(record.userList) }}</span>
<a-tooltip :content="getParticipantsName(record.userList)">
<span class="ml-2 truncate max-w-20">{{ getParticipantsName(record.userList) }}</span>
</a-tooltip>
</div>
</template>
<template #createdTime="{ record }">
{{ Day(parseInt(record.createdTime)).format('YYYY-MM-DD HH:mm:ss') }}
{{ timeStampFormat(record.createdTime) }}
</template>
<template #operation="{ record }">
<a-button type="text" @click="showDetails(record.channelId)">查看详情</a-button>
<a-button type="text" @click="showDetails(record)">查看详情</a-button>
</template>
</a-table>
</div>
......@@ -54,10 +62,12 @@ import { eventListColumns } from './constants/data.config'
import { alova } from '@/api/alova-instance'
import useContactsStore from '@/store/contacts/index'
import { storeToRefs } from 'pinia'
import Day from '@/utils/dayjs'
import { timeStampFormat } from '@/utils/dataTypes/dateRelated/formatDate'
import { Message } from '@arco-design/web-vue'
import { IconCheck, IconClose } from '@arco-design/web-vue/es/icon'
import { IconCheck, IconClose, IconEdit } from '@arco-design/web-vue/es/icon'
import { useRequest } from 'alova'
const es = defineEmits(['showDetails', 'showList'])
const beginTime = ref('')
const endTime = ref('')
const keyword = ref('')
......@@ -117,7 +127,11 @@ const {
debounce: [800],
abortLast: true,
total: (res) => parseInt(res.data.total),
data: (res) => res.data.list,
data: (res) => {
return res.data?.list?.map((item) => {
return { ...item, _title: item.title }
})
},
sendable: () => {
return !!chooseContactsItem?.value?.id
}
......@@ -128,32 +142,69 @@ const getInitiatorName = (record: any) => {
const initiatorId = record.channelId.split('_')[0]
return record.userList.find((item: any) => item.id == initiatorId)?.nickname
}
//获取参与人名称
//获取所有参与人名称
const getParticipantsName = (userList: any[]) => {
return userList.map((item) => item.nickname).join(',')
}
//修改事件标题
const titleArr = ref([])
const handleFocus = (record: any, rowIndex: number) => {
if (record.isFocus) {
record.isFocus = false
} else {
record.isFocus = true
nextTick(() => {
titleArr.value[rowIndex].focus()
})
}
}
const updateRemoteEventName = (record: any) => {
const params = {
uid: 2,
projectName: 'mu',
id: record.id,
title: record.title
}
return alova.Post<any>('/call/updateRemoteEventName', params)
}
const { loading: loadingTitle, send: sendUpdateTitle } = useRequest((record: any) => updateRemoteEventName(record), {
immediate: false
})
const handleConfirm = async (record: any, rowIndex: number) => {
if (!record.title) {
titleArr.value[rowIndex].focus()
return Message.error('请输入事件名称!')
}
const res = await sendUpdateTitle(record, rowIndex)
if (res.code == 200) {
handleFocus(record, rowIndex)
} else {
titleArr.value[rowIndex].focus()
Message.error(res.message)
}
}
const handleClose = (record: any, rowIndex: number) => {
record.title = record._title
handleFocus(record, rowIndex)
}
//显示详情
const showDetails = async (channelId: string) => {
const showDetails = async (record: any) => {
const params = {
uid: 2,
projectName: 'mu',
channelId
channelId: record.channelId
}
// @ts-ignore
const res = await alova.Post<any>('/call/getChatRoomMsgList', params, { meta: { loading: true } })
if (res.code == 200) {
console.log(res.data)
es('showDetails', { details: res.data, record })
} else {
Message.error(res.message)
}
}
//数据发生改变就展示列表
watch(tableData, () => {
es('showList')
})
</script>
<style lang="less" scoped>
:deep(.arco-table-th) {
background: var(--color-fill-1);
border-bottom: 1px solid var(--color-neutral-3);
}
:deep(.arco-table-td) {
background: inherit;
}
</style>
<style lang="less" scoped></style>
......@@ -33,11 +33,7 @@
</template>
</div>
</div>
<global-model v-model:visible="showPreview" width="80%" :footer="false" :simple="true">
<div style="height: 80vh">
<iframe :src="data.src" frameborder="0" width="100%" height="100%"></iframe>
</div>
</global-model>
<file-preview ref="previewRef"></file-preview>
</template>
<script setup lang="ts">
......@@ -54,9 +50,9 @@ const ps = withDefaults(defineProps<Props>(), {
data: () => {}
})
//预览文件
const showPreview = ref(false)
const previewRef = ref()
const handlePreview = () => {
showPreview.value = true
previewRef.value!.open({ src: ps.data.src, type: 'file' })
}
//下载文件
const handleDown = () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment