feat(review-meeting): 会议列表操作下拉化并支持弹窗预览纪要

pull/874/head
Codewoc 2026-04-11 17:08:37 +08:00
parent 506c26f7ab
commit 0b140a145e
1 changed files with 206 additions and 66 deletions

View File

@ -91,84 +91,105 @@
</el-table-column>
<el-table-column
label="操作"
width="600"
width="180"
align="center"
:fixed="useFixedActionColumn ? 'right' : false"
>
<template #default="{ row }">
<template v-if="row.status === 0">
<div class="op-group">
<a v-hasPermi="['review:meeting:update']" class="op-link" @click="goToEdit(row.id)"
>编辑</a
>
<a
v-hasPermi="['review:meeting:send-sms']"
class="op-link"
@click="handleSendSms(row)"
>发送短信邀约</a
>
<a
v-hasPermi="['review:meeting:cancel']"
class="op-link op-danger"
@click="handleCancel(row)"
>取消</a
<div class="op-group compact">
<el-dropdown
trigger="click"
@command="(command: string) => handleActionCommand(row, command)"
>
<span class="op-dropdown-trigger">更多</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-hasPermi="['review:meeting:update']" command="edit">
编辑
</el-dropdown-item>
<el-dropdown-item v-hasPermi="['review:meeting:send-sms']" command="sendSms">
发送短信邀约
</el-dropdown-item>
<el-dropdown-item
v-hasPermi="['review:meeting:cancel']"
command="cancel"
class="danger-dropdown-item"
>
取消
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<template v-else-if="row.status === 1">
<div class="op-group">
<a class="op-link" @click="goToEdit(row.id, 'view')">查看</a>
<a
v-hasPermi="['review:meeting:send-sms']"
class="op-link"
@click="openSmsStatus(row)"
>短信状态</a
>
<a
v-if="row.mailSent"
v-hasPermi="['review:meeting:send-mail']"
class="op-link"
@click="openMailStatus(row)"
>邮件状态</a
>
<a
v-else
v-hasPermi="['review:meeting:send-mail']"
class="op-link"
@click="handleSendMail(row)"
>发送议程</a
>
<a
v-hasPermi="['review:meeting:update']"
class="op-link"
@click="triggerUploadMinutes(row)"
>上传纪要</a
>
<a
v-hasPermi="['review:meeting:export']"
class="op-link"
:class="{ disabled: isExporting(row.id) }"
@click="handleExportMaterials(row)"
>
{{ getExportText(row.id) }}
</a>
<a v-hasPermi="['review:meeting:finish']" class="op-link" @click="handleFinish(row)"
>结束</a
>
<a
v-hasPermi="['review:meeting:cancel']"
class="op-link op-danger"
@click="handleCancel(row)"
>取消</a
<div class="op-group compact">
<el-dropdown
trigger="click"
@command="(command: string) => handleActionCommand(row, command)"
>
<span class="op-dropdown-trigger">更多</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="view">查看</el-dropdown-item>
<el-dropdown-item v-hasPermi="['review:meeting:send-sms']" command="smsStatus">
短信状态
</el-dropdown-item>
<el-dropdown-item
v-hasPermi="['review:meeting:send-mail']"
:command="row.mailSent ? 'mailStatus' : 'sendMail'"
>
{{ row.mailSent ? '邮件状态' : '发送议程' }}
</el-dropdown-item>
<el-dropdown-item v-if="hasMinutesAttachment(row)" command="previewMinutes">
预览纪要
</el-dropdown-item>
<el-dropdown-item v-hasPermi="['review:meeting:update']" command="uploadMinutes">
{{ hasMinutesAttachment(row) ? '重新上传纪要' : '上传纪要' }}
</el-dropdown-item>
<el-dropdown-item
v-hasPermi="['review:meeting:export']"
command="export"
:disabled="isExporting(row.id)"
>
{{ getExportText(row.id) }}
</el-dropdown-item>
<el-dropdown-item
v-hasPermi="['review:meeting:finish']"
command="finish"
class="danger-dropdown-item"
>
结束
</el-dropdown-item>
<el-dropdown-item
v-hasPermi="['review:meeting:cancel']"
command="cancel"
class="danger-dropdown-item"
>
取消
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<template v-else>
<div class="op-group">
<a class="op-link" @click="goToEdit(row.id, 'view')">查看</a>
<a v-hasPermi="['review:meeting:create']" class="op-link" @click="handleCopy(row)"
>复制新会议</a
<div class="op-group compact">
<el-dropdown
trigger="click"
@command="(command: string) => handleActionCommand(row, command)"
>
<span class="op-dropdown-trigger">更多</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="view">查看</el-dropdown-item>
<el-dropdown-item v-hasPermi="['review:meeting:create']" command="copy">
复制新会议
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</template>
@ -189,6 +210,13 @@
<!-- 邮件状态弹窗 -->
<MailStatusDialog ref="mailStatusRef" />
<FilePreviewDialog
v-model="minutesPreviewVisible"
:meeting-id="minutesPreviewMeetingId"
attachment-type="minutes"
:file-name="minutesPreviewFileName"
/>
<input
ref="minutesUploadInputRef"
type="file"
@ -221,6 +249,7 @@ import {
} from '@/api/review/meeting'
import SmsStatusDialog from './SmsStatusDialog.vue'
import MailStatusDialog from './MailStatusDialog.vue'
import FilePreviewDialog from './components/FilePreviewDialog.vue'
import { formatDate } from '@/utils/formatTime'
import download from '@/utils/download'
@ -259,6 +288,9 @@ const smsStatusRef = ref()
const mailStatusRef = ref()
const minutesUploadInputRef = ref<HTMLInputElement>()
const pendingMinutesMeeting = ref<ReviewMeetingRespVO>()
const minutesPreviewVisible = ref(false)
const minutesPreviewMeetingId = ref(0)
const minutesPreviewFileName = ref('')
const exportTaskMap = reactive<Record<number, ReviewMeetingMaterialExportTaskRespVO | undefined>>(
{}
)
@ -356,11 +388,80 @@ const handleCopy = async (row: ReviewMeetingRespVO) => {
})
}
type MeetingActionCommand =
| 'edit'
| 'view'
| 'sendSms'
| 'smsStatus'
| 'mailStatus'
| 'sendMail'
| 'previewMinutes'
| 'uploadMinutes'
| 'export'
| 'finish'
| 'cancel'
| 'copy'
const handleActionCommand = async (row: ReviewMeetingRespVO, command: string) => {
const action = command as MeetingActionCommand
switch (action) {
case 'edit':
goToEdit(row.id)
break
case 'view':
goToEdit(row.id, 'view')
break
case 'sendSms':
await handleSendSms(row)
break
case 'smsStatus':
openSmsStatus(row)
break
case 'mailStatus':
openMailStatus(row)
break
case 'sendMail':
await handleSendMail(row)
break
case 'previewMinutes':
handlePreviewMinutes(row)
break
case 'uploadMinutes':
triggerUploadMinutes(row)
break
case 'export':
await handleExportMaterials(row)
break
case 'finish':
await handleFinish(row)
break
case 'cancel':
await handleCancel(row)
break
case 'copy':
await handleCopy(row)
break
default:
break
}
}
const triggerUploadMinutes = (row: ReviewMeetingRespVO) => {
pendingMinutesMeeting.value = row
minutesUploadInputRef.value?.click()
}
const hasMinutesAttachment = (row: ReviewMeetingRespVO) => !!row.minutesAttachmentUrl
const handlePreviewMinutes = (row: ReviewMeetingRespVO) => {
if (!hasMinutesAttachment(row)) {
return
}
minutesPreviewMeetingId.value = row.id
minutesPreviewFileName.value = row.minutesAttachmentName || ''
minutesPreviewVisible.value = true
}
const handleMinutesFileChange = async (event: Event) => {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
@ -377,9 +478,14 @@ const handleMinutesFileChange = async (event: Event) => {
return
}
try {
await uploadMinutesAttachment(pendingMinutesMeeting.value.id, file)
const meeting = pendingMinutesMeeting.value
const attachment = await uploadMinutesAttachment(meeting.id, file)
meeting.minutesAttachmentName = attachment.name
meeting.minutesAttachmentUrl = attachment.url
meeting.minutesAttachmentType = attachment.type
meeting.minutesAttachmentSize = attachment.size
ElMessage.success('会议纪要上传成功,整改通知后台生成中')
getList()
await getList()
} finally {
input.value = ''
pendingMinutesMeeting.value = undefined
@ -729,6 +835,40 @@ onBeforeUnmount(() => {
.op-link.disabled:hover {
color: #8a94a6;
}
.op-group.compact {
gap: 0;
}
.op-dropdown-trigger {
display: inline-flex;
align-items: center;
gap: 5px;
color: #295abc;
font-size: 14px;
font-weight: 500;
line-height: 1;
cursor: pointer;
user-select: none;
}
.op-dropdown-trigger::after {
content: '';
width: 6px;
height: 6px;
margin-top: -3px;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(45deg);
}
.op-dropdown-trigger:hover {
color: #1f4ca5;
text-decoration: underline;
}
:deep(.el-dropdown-menu__item.danger-dropdown-item) {
color: #d63b42;
}
:deep(.el-dropdown-menu__item.danger-dropdown-item:not(.is-disabled):focus) {
color: #c22e35;
background-color: rgba(252, 79, 84, 0.12);
}
/* ── 表格样式 ── */
:deep(.review-table .el-table__header-wrapper th) {