开发会议邀约特性,基本跑通,未集成通讯平台
parent
92c9647c3b
commit
ca0cbe2ac9
|
|
@ -0,0 +1,114 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// ============================================================
|
||||
// 类型定义
|
||||
// ============================================================
|
||||
|
||||
// 评审项目条目(Excel 导入 & 保存时使用)
|
||||
export interface ReviewProjectItemVO {
|
||||
seqNo: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
agendaCategory: string
|
||||
projectTitle: string
|
||||
reporter: string
|
||||
reporterUnit: string
|
||||
}
|
||||
|
||||
// 会议保存 VO(新增/编辑)
|
||||
export interface ReviewMeetingSaveReqVO {
|
||||
id?: number
|
||||
name: string
|
||||
startTime: string | number
|
||||
endTime: string | number
|
||||
location: string
|
||||
expertIds: number[]
|
||||
projects?: ReviewProjectItemVO[]
|
||||
}
|
||||
|
||||
// 会议列表分页查询 VO
|
||||
export interface ReviewMeetingPageReqVO {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
name?: string
|
||||
status?: number
|
||||
startTime?: string[]
|
||||
}
|
||||
|
||||
// 会议响应 VO
|
||||
export interface ReviewMeetingRespVO {
|
||||
id: number
|
||||
name: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
location: string
|
||||
status: number // 0-草稿 1-已邀约 2-已结束 3-已取消
|
||||
expertIds: number[]
|
||||
expertCount: number
|
||||
projectCount: number
|
||||
createTime: string
|
||||
}
|
||||
|
||||
// 短信发送状态 VO
|
||||
export interface ReviewMeetingSmsLogRespVO {
|
||||
id: number
|
||||
expertId: number
|
||||
expertName: string
|
||||
mobile: string
|
||||
status: number // 0-待发送 1-成功 2-失败
|
||||
errorMsg: string
|
||||
retryCount: number
|
||||
sendTime: string
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// API 调用
|
||||
// ============================================================
|
||||
|
||||
/** 创建会议(保存草稿) */
|
||||
export const createReviewMeeting = (data: ReviewMeetingSaveReqVO) =>
|
||||
request.post({ url: '/project/review-meeting/create', data })
|
||||
|
||||
/** 更新会议 */
|
||||
export const updateReviewMeeting = (data: ReviewMeetingSaveReqVO) =>
|
||||
request.put({ url: '/project/review-meeting/update', data })
|
||||
|
||||
/** 取消会议 */
|
||||
export const cancelReviewMeeting = (id: number) =>
|
||||
request.put({ url: '/project/review-meeting/cancel', params: { id } })
|
||||
|
||||
/** 结束会议 */
|
||||
export const finishReviewMeeting = (id: number) =>
|
||||
request.put({ url: '/project/review-meeting/finish', params: { id } })
|
||||
|
||||
/** 获取会议详情 */
|
||||
export const getReviewMeeting = (id: number) =>
|
||||
request.get({ url: '/project/review-meeting/get', params: { id } })
|
||||
|
||||
/** 分页查询会议列表 */
|
||||
export const getReviewMeetingPage = (params: ReviewMeetingPageReqVO) =>
|
||||
request.get({ url: '/project/review-meeting/page', params })
|
||||
|
||||
/** 发送短信邀约 */
|
||||
export const sendSmsInvitation = (id: number) =>
|
||||
request.post({ url: '/project/review-meeting/send-sms', params: { id } })
|
||||
|
||||
/** 手动重发失败短信 */
|
||||
export const retrySmsLog = (smsLogId: number) =>
|
||||
request.post({ url: '/project/review-meeting/retry-sms', params: { smsLogId } })
|
||||
|
||||
/** 获取短信发送状态列表 */
|
||||
export const getSmsLogList = (reviewMeetingId: number) =>
|
||||
request.get({ url: '/project/review-meeting/sms-log-list', params: { reviewMeetingId } })
|
||||
|
||||
/** 解析 Excel 导入评审项目(返回项目列表,不落库) */
|
||||
export const importProjectsFromExcel = (file: File) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request.upload({ url: '/project/review-meeting/import-projects', data: formData })
|
||||
}
|
||||
|
||||
/** 下载导入模板 */
|
||||
export const getImportTemplate = () =>
|
||||
request.download({ url: '/project/review-meeting/get-import-template' })
|
||||
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// ============================================================
|
||||
// 类型定义
|
||||
// ============================================================
|
||||
|
||||
export interface ReviewMeetingProjectRespVO {
|
||||
id: number
|
||||
reviewMeetingId: number
|
||||
seqNo: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
agendaCategory: string
|
||||
projectTitle: string
|
||||
reporter: string
|
||||
reporterUnit: string
|
||||
host: string
|
||||
}
|
||||
|
||||
export interface ReviewMeetingProjectPageReqVO {
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
reviewMeetingId: number
|
||||
projectTitle?: string
|
||||
agendaCategory?: string
|
||||
reporter?: string
|
||||
}
|
||||
|
||||
export interface ReviewMeetingFileRespVO {
|
||||
id: number
|
||||
reviewMeetingProjectId: number
|
||||
fileName: string
|
||||
fileUrl: string
|
||||
fileSize: number
|
||||
fileType: string
|
||||
creator: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// API 调用
|
||||
// ============================================================
|
||||
|
||||
/** 分页查询评审项目列表 */
|
||||
export const getReviewProjectPage = (params: ReviewMeetingProjectPageReqVO) =>
|
||||
request.get({ url: '/project/review-project/page', params })
|
||||
|
||||
/** 创建评审项目 */
|
||||
export const createReviewProject = (data: Partial<ReviewMeetingProjectRespVO>) =>
|
||||
request.post({ url: '/project/review-project/create', data })
|
||||
|
||||
/** 更新评审项目信息 */
|
||||
export const updateReviewProject = (data: Partial<ReviewMeetingProjectRespVO>) =>
|
||||
request.put({ url: '/project/review-project/update', data })
|
||||
|
||||
/** 删除评审项目 */
|
||||
export const deleteReviewProject = (ids: number[]) =>
|
||||
request.delete({ url: '/project/review-project/delete', params: { ids: ids.join(',') } })
|
||||
|
||||
/** 更新评审项目主持人 */
|
||||
export const updateProjectHost = (projectId: number, host: string) =>
|
||||
request.put({ url: '/project/review-project/update-host', params: { projectId, host } })
|
||||
|
||||
/** 上传会议文件 */
|
||||
export const uploadMeetingFile = (
|
||||
reviewMeetingId: number,
|
||||
reviewMeetingProjectId: number,
|
||||
file: File
|
||||
) => {
|
||||
const formData = new FormData()
|
||||
formData.append('reviewMeetingId', String(reviewMeetingId))
|
||||
formData.append('reviewMeetingProjectId', String(reviewMeetingProjectId))
|
||||
formData.append('file', file)
|
||||
return request.upload({ url: '/project/review-project/upload-file', data: formData })
|
||||
}
|
||||
|
||||
/** 获取项目文件列表 */
|
||||
export const getMeetingFileList = (reviewMeetingProjectId: number) =>
|
||||
request.get({
|
||||
url: '/project/review-project/file-list',
|
||||
params: { reviewMeetingProjectId }
|
||||
})
|
||||
|
||||
/** 删除会议文件 */
|
||||
export const deleteMeetingFile = (id: number) =>
|
||||
request.delete({ url: '/project/review-project/delete-file', params: { id } })
|
||||
|
|
@ -39,6 +39,11 @@ export const updateUser = (data: UserVO) => {
|
|||
return request.put({ url: '/system/user/update', data })
|
||||
}
|
||||
|
||||
// 获取专家列表
|
||||
export const getExpertUserList = () => {
|
||||
return request.get({ url: '/system/user/expert-list' })
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
export const deleteUser = (id: number) => {
|
||||
return request.delete({ url: '/system/user/delete?id=' + id })
|
||||
|
|
|
|||
|
|
@ -723,6 +723,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
activeMenu: '/project/acceptance'
|
||||
},
|
||||
component: () => import('@/views/project/acceptance/detail/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'review-meeting/project/:meetingId(\\d+)',
|
||||
name: 'ReviewMeetingProject',
|
||||
meta: {
|
||||
title: '评审项目列表',
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/review/meeting'
|
||||
},
|
||||
component: () => import('@/views/review/meeting/ProjectList.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<el-dialog v-model="visible" :title="`会议文件 - ${projectTitle}`" width="720px">
|
||||
<!-- 上传区域 -->
|
||||
<el-upload
|
||||
:action="''"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:show-file-list="false"
|
||||
multiple
|
||||
accept=".doc,.docx,.xls,.xlsx,.pdf,.ppt,.pptx"
|
||||
>
|
||||
<el-button type="primary" plain>上传文件</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 doc、docx、xls、xlsx、pdf、ppt、pptx 格式,单文件不超过 50MB</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<el-table :data="fileList" v-loading="loading" border size="small">
|
||||
<el-table-column label="文件名" prop="fileName" show-overflow-tooltip min-width="200" />
|
||||
<el-table-column label="大小" width="90" align="right">
|
||||
<template #default="{ row }">{{ formatFileSize(row.fileSize) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" prop="fileType" width="70" align="center" />
|
||||
<el-table-column label="上传人" prop="creator" width="90" />
|
||||
<el-table-column label="上传时间" prop="createTime" width="165" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleDownload(row)">下载</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { UploadFile } from 'element-plus'
|
||||
import {
|
||||
getMeetingFileList,
|
||||
uploadMeetingFile,
|
||||
deleteMeetingFile,
|
||||
type ReviewMeetingFileRespVO
|
||||
} from '@/api/review/project'
|
||||
|
||||
defineOptions({ name: 'FileListDialog' })
|
||||
|
||||
const props = defineProps<{ reviewMeetingId: number }>()
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const projectTitle = ref('')
|
||||
const reviewMeetingProjectId = ref<number>()
|
||||
const fileList = ref<ReviewMeetingFileRespVO[]>([])
|
||||
|
||||
const open = async (projectId: number, title: string) => {
|
||||
reviewMeetingProjectId.value = projectId
|
||||
projectTitle.value = title
|
||||
visible.value = true
|
||||
await loadFiles()
|
||||
}
|
||||
|
||||
const loadFiles = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
fileList.value = await getMeetingFileList(reviewMeetingProjectId.value!)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = async (uploadFile: UploadFile) => {
|
||||
if (!uploadFile.raw) return
|
||||
if (uploadFile.raw.size > 50 * 1024 * 1024) {
|
||||
ElMessage.error('文件大小不能超过 50MB')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
await uploadMeetingFile(props.reviewMeetingId, reviewMeetingProjectId.value!, uploadFile.raw)
|
||||
ElMessage.success('上传成功')
|
||||
await loadFiles()
|
||||
} catch (e) {
|
||||
ElMessage.error('上传失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownload = (row: ReviewMeetingFileRespVO) => {
|
||||
window.open(row.fileUrl, '_blank')
|
||||
}
|
||||
|
||||
const handleDelete = async (row: ReviewMeetingFileRespVO) => {
|
||||
await ElMessageBox.confirm(`确定要删除文件「${row.fileName}」吗?`, '提示', { type: 'warning' })
|
||||
await deleteMeetingFile(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await loadFiles()
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (!bytes) return '-'
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
<template>
|
||||
<el-dialog v-model="visible" :title="dialogTitle" width="860px" :close-on-click-modal="false" top="5vh">
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="90px" v-loading="formLoading">
|
||||
<!-- 基本信息 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="会议标题" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入会议标题" :disabled="isView" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="会议时间" prop="meetingTimeRange">
|
||||
<el-date-picker
|
||||
v-model="formData.meetingTimeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value-format="x"
|
||||
:disabled="isView"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="会议地点" prop="location">
|
||||
<el-input v-model="formData.location" placeholder="请输入会议地点" :disabled="isView" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="参会专家" prop="expertIds">
|
||||
<el-select
|
||||
v-model="formData.expertIds"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="请选择参会专家"
|
||||
style="width: 100%"
|
||||
:disabled="isView"
|
||||
>
|
||||
<el-option
|
||||
v-for="expert in expertOptions"
|
||||
:key="expert.id"
|
||||
:label="expertLabel(expert)"
|
||||
:value="expert.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 导入评审项目 -->
|
||||
<div v-if="!isView" class="import-section">
|
||||
<el-divider>评审项目</el-divider>
|
||||
<el-upload
|
||||
:auto-upload="false"
|
||||
:on-change="handleExcelChange"
|
||||
:show-file-list="false"
|
||||
accept=".xls,.xlsx"
|
||||
>
|
||||
<el-button type="primary" plain>导入验收申请 Excel</el-button>
|
||||
</el-upload>
|
||||
<el-button type="success" plain @click="handleDownloadTemplate">下载导入模板</el-button>
|
||||
<el-text type="info" size="small" class="ml-10">格式:序号、开始时间、结束时间、议程分类、项目标题、汇报人、报告人单位</el-text>
|
||||
</div>
|
||||
|
||||
<!-- 评审项目预览列表 -->
|
||||
<div v-if="formData.projects && formData.projects.length > 0" class="mt-10">
|
||||
<el-table :data="formData.projects" border size="small" max-height="300">
|
||||
<el-table-column label="序号" prop="seqNo" width="60" align="center" />
|
||||
<el-table-column label="开始时间" prop="startTime" width="80" align="center" />
|
||||
<el-table-column label="结束时间" prop="endTime" width="80" align="center" />
|
||||
<el-table-column label="议程分类" prop="agendaCategory" width="100" />
|
||||
<el-table-column label="评审项目标题" prop="projectTitle" show-overflow-tooltip />
|
||||
<el-table-column label="汇报人" prop="reporter" width="80" />
|
||||
<el-table-column label="报告单位" prop="reporterUnit" show-overflow-tooltip width="120" />
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<template #footer v-if="!isView">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="submitForm">保存草稿</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { UploadFile } from 'element-plus'
|
||||
import {
|
||||
createReviewMeeting,
|
||||
updateReviewMeeting,
|
||||
getReviewMeeting,
|
||||
importProjectsFromExcel,
|
||||
getImportTemplate,
|
||||
type ReviewMeetingSaveReqVO,
|
||||
type ReviewProjectItemVO
|
||||
} from '@/api/review/meeting'
|
||||
import { getExpertUserList } from '@/api/system/user'
|
||||
import download from '@/utils/download'
|
||||
|
||||
defineOptions({ name: 'ReviewMeetingForm' })
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const visible = ref(false)
|
||||
const formLoading = ref(false)
|
||||
const formType = ref<'create' | 'update' | 'view'>('create')
|
||||
const isView = computed(() => formType.value === 'view')
|
||||
const dialogTitle = computed(() =>
|
||||
formType.value === 'create' ? '新增会议邀约' : formType.value === 'update' ? '编辑评审会议' : '查看评审会议'
|
||||
)
|
||||
|
||||
type FormData = ReviewMeetingSaveReqVO & { meetingTimeRange?: string[] }
|
||||
|
||||
const formData = reactive<FormData>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
location: '',
|
||||
expertIds: [],
|
||||
meetingTimeRange: undefined,
|
||||
projects: []
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '会议标题不能为空', trigger: 'blur' }],
|
||||
meetingTimeRange: [{ required: true, message: '会议时间不能为空', trigger: 'change' }],
|
||||
location: [{ required: true, message: '会议地点不能为空', trigger: 'blur' }],
|
||||
expertIds: [{ required: true, type: 'array', min: 1, message: '至少选择一位专家', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const isProjectsModified = ref(false)
|
||||
const formRef = ref()
|
||||
const expertOptions = ref<any[]>([])
|
||||
const expertLabel = (e: any) => `${e.nickname}${e.title ? `(${e.title})` : ''}${e.deptName ? ` ${e.deptName}` : ''}`
|
||||
|
||||
const open = async (type: 'create' | 'update' | 'view', id?: number) => {
|
||||
formType.value = type
|
||||
visible.value = true
|
||||
isProjectsModified.value = false
|
||||
resetForm()
|
||||
// 加载专家列表
|
||||
expertOptions.value = await getExpertUserList().catch(() => [])
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const detail = await getReviewMeeting(id)
|
||||
Object.assign(formData, detail)
|
||||
if (detail.startTime && detail.endTime) {
|
||||
formData.meetingTimeRange = [
|
||||
new Date(detail.startTime.replace(' ', 'T')).getTime(),
|
||||
new Date(detail.endTime.replace(' ', 'T')).getTime()
|
||||
]
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
formData.id = undefined
|
||||
formData.name = ''
|
||||
formData.startTime = ''
|
||||
formData.endTime = ''
|
||||
formData.location = ''
|
||||
formData.expertIds = []
|
||||
formData.meetingTimeRange = undefined
|
||||
formData.projects = []
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleExcelChange = async (uploadFile: UploadFile) => {
|
||||
if (!uploadFile.raw) return
|
||||
if (formData.projects && formData.projects.length > 0) {
|
||||
await ElMessageBox.confirm('重新导入将覆盖已有评审项目列表,是否继续?', '提示', { type: 'warning' })
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const result = await importProjectsFromExcel(uploadFile.raw)
|
||||
formData.projects = ((result as any).data || result) as ReviewProjectItemVO[]
|
||||
isProjectsModified.value = true
|
||||
ElMessage.success(`成功解析 ${formData.projects.length} 个评审项目`)
|
||||
} catch (e) {
|
||||
ElMessage.error('Excel 解析失败,请检查文件格式')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownloadTemplate = async () => {
|
||||
try {
|
||||
const data = await getImportTemplate()
|
||||
download.excel(data, '评审项目导入模板.xls')
|
||||
} catch (e) {
|
||||
ElMessage.error('下载模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
if (formData.meetingTimeRange?.length === 2) {
|
||||
formData.startTime = formData.meetingTimeRange[0]
|
||||
formData.endTime = formData.meetingTimeRange[1]
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const submitData = { ...formData }
|
||||
if (formType.value === 'update' && !isProjectsModified.value) {
|
||||
delete submitData.projects
|
||||
}
|
||||
if (formType.value === 'create') {
|
||||
await createReviewMeeting(submitData)
|
||||
ElMessage.success('创建成功,会议已保存为草稿')
|
||||
} else {
|
||||
await updateReviewMeeting(submitData)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
visible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.import-section { margin-top: 8px; display: flex; align-items: center; gap: 8px; }
|
||||
.ml-10 { margin-left: 10px; }
|
||||
.mt-10 { margin-top: 10px; }
|
||||
</style>
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 面包屑信息 -->
|
||||
<div class="meeting-summary">
|
||||
<el-descriptions :column="4" border size="small">
|
||||
<el-descriptions-item label="会议名称">{{ meetingInfo.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="会议时间">
|
||||
{{ meetingInfo.startTime ? formatDate(meetingInfo.startTime, 'YYYY-MM-DD HH:mm') : '' }} ~
|
||||
{{ meetingInfo.endTime ? formatDate(meetingInfo.endTime, 'YYYY-MM-DD HH:mm') : '' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="会议地点">{{ meetingInfo.location }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="STATUS_TAG_TYPE[meetingInfo.status]">{{ STATUS_LABEL[meetingInfo.status] }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" :inline="true" class="mt-10">
|
||||
<el-form-item label="项目标题" prop="projectTitle">
|
||||
<el-input v-model="queryParams.projectTitle" placeholder="请输入项目标题" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="议程分类" prop="agendaCategory">
|
||||
<el-input v-model="queryParams.agendaCategory" placeholder="请输入议程分类" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="汇报人" prop="reporter">
|
||||
<el-input v-model="queryParams.reporter" placeholder="请输入汇报人" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain @click="openForm('create')">
|
||||
<el-icon><Plus /></el-icon> 新增评审项目
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain :disabled="selectedIds.length === 0" @click="handleDelete()">
|
||||
<el-icon><Delete /></el-icon> 批量删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list" border @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="ID" prop="id" width="80" align="center" />
|
||||
<el-table-column label="序号" prop="seqNo" width="60" align="center" />
|
||||
<el-table-column label="开始时间" prop="startTime" width="80" align="center" />
|
||||
<el-table-column label="结束时间" prop="endTime" width="80" align="center" />
|
||||
<el-table-column label="议程分类" prop="agendaCategory" width="110" />
|
||||
<el-table-column label="评审项目标题" prop="projectTitle" show-overflow-tooltip min-width="180" />
|
||||
<el-table-column label="汇报人" prop="reporter" width="80" />
|
||||
<el-table-column label="报告单位" prop="reporterUnit" show-overflow-tooltip width="130" />
|
||||
<el-table-column label="主持人" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.host"
|
||||
placeholder="填写主持人"
|
||||
size="small"
|
||||
@blur="handleHostBlur(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="openForm('update', row)">编辑</el-button>
|
||||
<el-button type="primary" link @click="openFileDialog(row)">文件</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 编辑/新增项目弹窗 -->
|
||||
<el-dialog v-model="formVisible" :title="formType === 'create' ? '新增评审项目' : '编辑评审项目'" width="550px">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
|
||||
<el-form-item label="序号" prop="seqNo">
|
||||
<el-input-number v-model="formData.seqNo" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时间范围">
|
||||
<div style="display: flex; gap: 10px; width: 100%;">
|
||||
<el-time-picker v-model="formData.startTime" format="HH:mm" value-format="HH:mm" placeholder="开始时间" style="flex: 1" />
|
||||
<span>-</span>
|
||||
<el-time-picker v-model="formData.endTime" format="HH:mm" value-format="HH:mm" placeholder="结束时间" style="flex: 1" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="议程分类" prop="agendaCategory">
|
||||
<el-input v-model="formData.agendaCategory" placeholder="请输入议程分类" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目标题" prop="projectTitle">
|
||||
<el-input v-model="formData.projectTitle" placeholder="请输入项目标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="汇报人" prop="reporter">
|
||||
<el-input v-model="formData.reporter" placeholder="请输入汇报人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="报告单位" prop="reporterUnit">
|
||||
<el-input v-model="formData.reporterUnit" placeholder="请输入报告单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="主持人" prop="host">
|
||||
<el-input v-model="formData.host" placeholder="请输入主持人" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="formVisible = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="submitForm">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 文件管理弹窗 -->
|
||||
<FileListDialog ref="fileDialogRef" :review-meeting-id="reviewMeetingId" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Delete } from '@element-plus/icons-vue'
|
||||
import { getReviewMeeting } from '@/api/review/meeting'
|
||||
import { getReviewProjectPage, updateProjectHost, updateReviewProject, createReviewProject, deleteReviewProject, type ReviewMeetingProjectRespVO } from '@/api/review/project'
|
||||
import FileListDialog from './FileListDialog.vue'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'ReviewMeetingProject' })
|
||||
|
||||
const route = useRoute()
|
||||
const reviewMeetingId = Number(route.params.meetingId)
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref<ReviewMeetingProjectRespVO[]>([])
|
||||
const total = ref(0)
|
||||
const meetingInfo = ref<any>({})
|
||||
|
||||
const STATUS_LABEL: Record<number, string> = { 0: '草稿', 1: '已邀约', 2: '已结束', 3: '已取消' }
|
||||
const STATUS_TAG_TYPE: Record<number, string> = { 0: 'info', 1: 'primary', 2: 'success', 3: 'danger' }
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
reviewMeetingId,
|
||||
projectTitle: undefined as string | undefined,
|
||||
agendaCategory: undefined as string | undefined,
|
||||
reporter: undefined as string | undefined
|
||||
})
|
||||
|
||||
const queryFormRef = ref()
|
||||
const fileDialogRef = ref()
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await getReviewProjectPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => { queryParams.pageNo = 1; getList() }
|
||||
const resetQuery = () => { queryFormRef.value?.resetFields(); handleQuery() }
|
||||
|
||||
const selectedIds = ref<number[]>([])
|
||||
const handleSelectionChange = (val: ReviewMeetingProjectRespVO[]) => {
|
||||
selectedIds.value = val.map(v => v.id)
|
||||
}
|
||||
|
||||
const handleDelete = async (id?: number) => {
|
||||
const ids = id ? [id] : selectedIds.value
|
||||
if (ids.length === 0) return
|
||||
await ElMessageBox.confirm(`确认删除选中的 ${ids.length} 个项目吗?`, '警告', { type: 'warning' })
|
||||
await deleteReviewProject(ids)
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleHostBlur = async (row: ReviewMeetingProjectRespVO) => {
|
||||
await updateProjectHost(row.id, row.host || '')
|
||||
ElMessage.success('主持人已更新')
|
||||
}
|
||||
|
||||
const formVisible = ref(false)
|
||||
const formType = ref<'create' | 'update'>('create')
|
||||
const formLoading = ref(false)
|
||||
const formRef = ref()
|
||||
const formData = reactive<Partial<ReviewMeetingProjectRespVO>>({})
|
||||
const formRules = {
|
||||
projectTitle: [{ required: true, message: '项目标题不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const openForm = (type: 'create' | 'update', row?: ReviewMeetingProjectRespVO) => {
|
||||
formType.value = type
|
||||
if (type === 'create') {
|
||||
Object.keys(formData).forEach(key => delete formData[key as keyof ReviewMeetingProjectRespVO])
|
||||
formData.reviewMeetingId = reviewMeetingId
|
||||
} else if (row) {
|
||||
Object.assign(formData, row)
|
||||
}
|
||||
formVisible.value = true
|
||||
// Reset form validation state if needed by deferring to next tick
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
formLoading.value = true
|
||||
try {
|
||||
if (formType.value === 'create') {
|
||||
await createReviewProject(formData)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
await updateReviewProject(formData)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
formVisible.value = false
|
||||
getList()
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openFileDialog = (row: ReviewMeetingProjectRespVO) => {
|
||||
fileDialogRef.value?.open(row.id, row.projectTitle)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
meetingInfo.value = await getReviewMeeting(reviewMeetingId)
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.meeting-summary { margin-bottom: 16px; }
|
||||
.mt-10 { margin-top: 10px; }
|
||||
</style>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<el-dialog v-model="visible" :title="`发送状态 - ${meetingName}`" width="720px">
|
||||
<el-table :data="logList" v-loading="loading" border>
|
||||
<el-table-column label="专家" prop="expertName" width="100" />
|
||||
<el-table-column label="手机号" prop="mobile" width="130" />
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="SMS_STATUS_TYPE[row.status]">{{ SMS_STATUS_LABEL[row.status] }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="重试次数" prop="retryCount" width="80" align="center" />
|
||||
<el-table-column label="最后发送时间" prop="sendTime" width="165" />
|
||||
<el-table-column label="失败原因" prop="errorMsg" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status === 2" type="primary" link @click="handleRetry(row)">重新发送</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getSmsLogList, retrySmsLog, type ReviewMeetingSmsLogRespVO } from '@/api/review/meeting'
|
||||
|
||||
defineOptions({ name: 'SmsStatusDialog' })
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const meetingName = ref('')
|
||||
const reviewMeetingId = ref<number>()
|
||||
const logList = ref<ReviewMeetingSmsLogRespVO[]>([])
|
||||
|
||||
const SMS_STATUS_LABEL: Record<number, string> = { 0: '待发送', 1: '成功', 2: '失败' }
|
||||
const SMS_STATUS_TYPE: Record<number, string> = { 0: 'info', 1: 'success', 2: 'danger' }
|
||||
|
||||
const open = async (id: number, name: string) => {
|
||||
reviewMeetingId.value = id
|
||||
meetingName.value = name
|
||||
visible.value = true
|
||||
await loadData()
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
logList.value = await getSmsLogList(reviewMeetingId.value!)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleRetry = async (row: ReviewMeetingSmsLogRespVO) => {
|
||||
await retrySmsLog(row.id)
|
||||
ElMessage.success('已触发重新发送')
|
||||
await loadData()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索栏 -->
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" :inline="true">
|
||||
<el-form-item label="会议名称" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入会议名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="item in MEETING_STATUS_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="会议时间" prop="startTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.startTime"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button v-hasPermi="['review:meeting:create']" type="primary" plain @click="openForm('create')">
|
||||
<el-icon><Plus /></el-icon> 新增会议邀约
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" prop="id" width="80" align="center" />
|
||||
<el-table-column label="会议名称" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="goToProjectList(row)">{{ row.name }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开始时间" width="145">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.startTime, 'YYYY-MM-DD HH:mm') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" width="145">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.endTime, 'YYYY-MM-DD HH:mm') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="会议地点" prop="location" show-overflow-tooltip />
|
||||
<el-table-column label="参会专家数" prop="expertCount" width="100" align="center" />
|
||||
<el-table-column label="评审项目数" prop="projectCount" width="100" align="center" />
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="STATUS_TAG_TYPE[row.status]">{{ STATUS_LABEL[row.status] }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="进入评审项目列表" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="goToProjectList(row)">进入项目列表</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<!-- 草稿状态 -->
|
||||
<template v-if="row.status === 0">
|
||||
<el-button v-hasPermi="['review:meeting:update']" type="primary" link @click="openForm('update', row)">编辑</el-button>
|
||||
<el-button v-hasPermi="['review:meeting:send-sms']" type="success" link @click="handleSendSms(row)">发送短信邀约</el-button>
|
||||
<el-button v-hasPermi="['review:meeting:cancel']" type="danger" link @click="handleCancel(row)">取消</el-button>
|
||||
</template>
|
||||
<!-- 已邀约状态 -->
|
||||
<template v-else-if="row.status === 1">
|
||||
<el-button type="info" link @click="openForm('view', row)">查看</el-button>
|
||||
<el-button v-hasPermi="['review:meeting:send-sms']" type="info" link @click="openSmsStatus(row)">发送状态</el-button>
|
||||
<el-button v-hasPermi="['review:meeting:finish']" type="warning" link @click="handleFinish(row)">结束</el-button>
|
||||
<el-button v-hasPermi="['review:meeting:cancel']" type="danger" link @click="handleCancel(row)">取消</el-button>
|
||||
</template>
|
||||
<!-- 终态 -->
|
||||
<template v-else>
|
||||
<el-button type="info" link @click="openForm('view', row)">查看</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<MeetingForm ref="formRef" @success="getList" />
|
||||
|
||||
<!-- 短信状态弹窗 -->
|
||||
<SmsStatusDialog ref="smsStatusRef" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getReviewMeetingPage,
|
||||
cancelReviewMeeting,
|
||||
finishReviewMeeting,
|
||||
sendSmsInvitation,
|
||||
type ReviewMeetingRespVO,
|
||||
type ReviewMeetingPageReqVO
|
||||
} from '@/api/review/meeting'
|
||||
import MeetingForm from './MeetingForm.vue'
|
||||
import SmsStatusDialog from './SmsStatusDialog.vue'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'ReviewMeeting' })
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const list = ref<ReviewMeetingRespVO[]>([])
|
||||
const total = ref(0)
|
||||
|
||||
const MEETING_STATUS_OPTIONS = [
|
||||
{ value: 0, label: '草稿' },
|
||||
{ value: 1, label: '已邀约' },
|
||||
{ value: 2, label: '已结束' },
|
||||
{ value: 3, label: '已取消' }
|
||||
]
|
||||
const STATUS_LABEL: Record<number, string> = { 0: '草稿', 1: '已邀约', 2: '已结束', 3: '已取消' }
|
||||
const STATUS_TAG_TYPE: Record<number, string> = { 0: 'info', 1: 'primary', 2: 'success', 3: 'danger' }
|
||||
|
||||
const queryParams = reactive<ReviewMeetingPageReqVO & { pageNo: number; pageSize: number }>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
startTime: undefined
|
||||
})
|
||||
|
||||
const queryFormRef = ref()
|
||||
const formRef = ref()
|
||||
const smsStatusRef = ref()
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await getReviewMeetingPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => { queryParams.pageNo = 1; getList() }
|
||||
const resetQuery = () => { queryFormRef.value?.resetFields(); handleQuery() }
|
||||
|
||||
const openForm = (type: 'create' | 'update' | 'view', row?: ReviewMeetingRespVO) => {
|
||||
formRef.value?.open(type, row?.id)
|
||||
}
|
||||
|
||||
const goToProjectList = (row: ReviewMeetingRespVO) => {
|
||||
router.push({ name: 'ReviewMeetingProject', params: { meetingId: row.id }, query: { meetingName: row.name } })
|
||||
}
|
||||
|
||||
const handleCancel = async (row: ReviewMeetingRespVO) => {
|
||||
await ElMessageBox.confirm(`确定要取消会议「${row.name}」吗?`, '提示', { type: 'warning' })
|
||||
await cancelReviewMeeting(row.id)
|
||||
ElMessage.success('取消成功')
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleFinish = async (row: ReviewMeetingRespVO) => {
|
||||
await ElMessageBox.confirm(`确定要结束会议「${row.name}」吗?`, '提示', { type: 'warning' })
|
||||
await finishReviewMeeting(row.id)
|
||||
ElMessage.success('已结束')
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleSendSms = async (row: ReviewMeetingRespVO) => {
|
||||
await ElMessageBox.confirm(
|
||||
`确认向 ${row.expertCount} 位专家发送「${row.name}」会议邀约短信?`,
|
||||
'发送确认',
|
||||
{ type: 'info', confirmButtonText: '确认发送' }
|
||||
)
|
||||
await sendSmsInvitation(row.id)
|
||||
ElMessage.success('短信发送已触发,请稍后通过"发送状态"查看结果')
|
||||
getList()
|
||||
}
|
||||
|
||||
const openSmsStatus = (row: ReviewMeetingRespVO) => {
|
||||
smsStatusRef.value?.open(row.id, row.name)
|
||||
}
|
||||
|
||||
onMounted(() => getList())
|
||||
</script>
|
||||
Loading…
Reference in New Issue