feat: 会议提交剩余时间+解冻 + 会务/劳务材料批量下载 + 多角色人员/账号模块完善
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,37 +15,25 @@
|
||||
<!-- 个人信息 -->
|
||||
<el-tab-pane label="个人信息" name="profile">
|
||||
<div class="card">
|
||||
<!-- 头像 + 用户信息 -->
|
||||
<div class="avatar-block">
|
||||
<div class="avatar-circle" :class="{ 'has-file': avatarUrl, 'is-uploading': avatarUploading }" @click="onAvatarClick">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="avatar-img" />
|
||||
<template v-else>{{ avatarChar }}</template>
|
||||
<span class="avatar-tip">{{ avatarUploading ? '上传中...' : '点击更换' }}</span>
|
||||
</div>
|
||||
<div class="avatar-meta">
|
||||
<div class="name">{{ form.name || '专家账号' }}</div>
|
||||
<div class="role">专家账号</div>
|
||||
<div class="phone-row">
|
||||
<span class="phone-label">联系电话:</span>
|
||||
<span class="phone-value">{{ form.phone || '未设置' }}</span>
|
||||
<el-button class="btn-edit-phone" link type="primary" @click="openPhoneDialog">
|
||||
<el-icon><EditPen /></el-icon> 修改
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基本资料 (2 列) -->
|
||||
<div class="section-title">基本资料</div>
|
||||
<el-form :model="form" class="form-grid" label-position="right" label-width="120px">
|
||||
<el-form-item label="专家姓名" :required="true">
|
||||
<el-input v-model="form.name" placeholder="请填写姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<div class="phone-row">
|
||||
<span class="phone-value">{{ form.phone || '未设置' }}</span>
|
||||
<el-button link type="primary" @click="openPhoneDialog">
|
||||
<el-icon><EditPen /></el-icon> 修改
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="科室" :required="true">
|
||||
<doctor-dept-select v-model="form.department" value-field="label" placeholder="输入关键词搜索" filterable />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区" :required="true">
|
||||
<area-cascader v-model="form.region" format="string" join-sep="/" placeholder="请选择省/市/区" />
|
||||
<area-cascader v-model="form.region" format="string" join-sep="/" :max-level="2" placeholder="请选择省/市" />
|
||||
</el-form-item>
|
||||
<el-form-item label="证件号码" :required="true">
|
||||
<el-input v-model="form.idCardNo" placeholder="请填写证件号码" />
|
||||
@@ -131,9 +119,6 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 隐藏的 avatar 上传 input -->
|
||||
<input ref="avatarInput" type="file" hidden accept="image/*" @change="onAvatarFileChange" />
|
||||
|
||||
<!-- 修改手机号 dialog (独立于其它字段) -->
|
||||
<el-dialog v-model="phoneDialogVisible" title="修改联系电话" width="420px" :close-on-click-modal="false">
|
||||
<el-form :model="phoneForm" label-width="100px">
|
||||
@@ -159,12 +144,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { reactive, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { EditPen } from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import request from '@/utils/request'
|
||||
import { uploadToOss } from '@/utils/oss'
|
||||
import AreaCascader from '@/components/AreaCascader.vue'
|
||||
import DoctorDeptSelect from '@/components/DoctorDeptSelect.vue'
|
||||
import DoctorTitleSelect from '@/components/DoctorTitleSelect.vue'
|
||||
@@ -209,50 +193,8 @@ const pwdError = ref('')
|
||||
const saveError = ref('')
|
||||
const saving = ref(false)
|
||||
|
||||
const avatarUrl = ref(store.user?.avatar || '')
|
||||
const avatarChar = computed(() => (form.name || '专').slice(0, 1))
|
||||
const avatarUploading = ref(false)
|
||||
const avatarInput = ref(null)
|
||||
|
||||
let snapshot = ref(null)
|
||||
|
||||
function onAvatarClick() {
|
||||
if (avatarUploading.value) return
|
||||
avatarInput.value?.click()
|
||||
}
|
||||
|
||||
async function onAvatarFileChange(e) {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
e.target.value = '' // 重置以便可重选
|
||||
if (!file.type.startsWith('image/')) {
|
||||
return ElMessage.warning('请选择图片文件')
|
||||
}
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
return ElMessage.warning('图片大小不能超过 5MB')
|
||||
}
|
||||
avatarUploading.value = true
|
||||
try {
|
||||
const url = await uploadToOss(file, 'ry8080/avatar/')
|
||||
await request({
|
||||
url: '/system/user/profile/avatarUrl',
|
||||
method: 'put',
|
||||
data: { avatarUrl: url }
|
||||
})
|
||||
avatarUrl.value = url
|
||||
if (store.user) {
|
||||
store.user.avatar = url
|
||||
// 同步 localStorage, 否则刷新后头像丢失
|
||||
localStorage.setItem('ry_user', JSON.stringify(store.user))
|
||||
}
|
||||
ElMessage.success('头像更新成功')
|
||||
} catch (err) {
|
||||
ElMessage.error(err?.msg || '头像上传失败')
|
||||
} finally {
|
||||
avatarUploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
if (window.parent && window.parent.document.getElementById('contentFrame')) {
|
||||
window.parent.goHome?.()
|
||||
@@ -296,7 +238,6 @@ async function loadUserProfile() {
|
||||
store.user.phonenumber = u.phonenumber || store.user.phonenumber
|
||||
localStorage.setItem('ry_user', JSON.stringify(store.user))
|
||||
}
|
||||
avatarUrl.value = u.avatar || avatarUrl.value
|
||||
} catch (e) {
|
||||
console.warn('loadUserProfile:', e?.msg)
|
||||
}
|
||||
@@ -305,16 +246,10 @@ async function loadUserProfile() {
|
||||
onMounted(async () => {
|
||||
form.name = store.user?.userName || ''
|
||||
form.phone = store.user?.phonenumber || ''
|
||||
avatarUrl.value = store.user?.avatar || ''
|
||||
await Promise.all([loadUserProfile(), loadExpertProfile()])
|
||||
snapshot = ref(JSON.parse(JSON.stringify(form)))
|
||||
})
|
||||
|
||||
function onAvatarChange(url) {
|
||||
avatarUrl.value = url
|
||||
if (store.user) store.user.avatar = url
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
saveError.value = ''
|
||||
const required = ['name', 'region', 'workUnit', 'department', 'doctorTitle', 'idCardNo', 'bankCardNo', 'bankName', 'bankRegion', 'bankAddress']
|
||||
@@ -499,36 +434,9 @@ onUnmounted(() => {
|
||||
.form-grid :deep(.el-form-item) { margin-bottom: 14px; }
|
||||
.form-grid :deep(.el-form-item__label) { color: #606266; }
|
||||
|
||||
/* 头像区 (含电话显示) */
|
||||
.avatar-block { display: flex; align-items: center; gap: 24px; padding: 16px 0 24px; border-bottom: 1px dashed #ebeef5; margin-bottom: 24px; }
|
||||
.avatar-circle {
|
||||
width: 80px; height: 80px; border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
font-size: 28px; font-weight: 600;
|
||||
cursor: pointer; flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.avatar-circle:hover { transform: scale(1.04); }
|
||||
.avatar-circle.is-uploading { cursor: wait; opacity: 0.75; }
|
||||
.avatar-circle .avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.avatar-tip {
|
||||
font-size: 11px; opacity: 0.85; margin-top: 4px;
|
||||
position: absolute; bottom: 4px; left: 0; right: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.avatar-meta { display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
||||
.avatar-meta .name { font-size: 18px; font-weight: 600; color: #262626; }
|
||||
.avatar-meta .role { font-size: 13px; color: #909399; }
|
||||
.phone-row { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #606266; margin-top: 4px; }
|
||||
.phone-label { color: #909399; }
|
||||
/* 联系电话 (基本资料 form 内显示) */
|
||||
.phone-row { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #606266; }
|
||||
.phone-value { color: #262626; font-weight: 500; }
|
||||
.btn-edit-phone { padding: 0 4px; }
|
||||
|
||||
/* 银行信息 + 密码 form 单列 */
|
||||
.password-form { max-width: 480px; }
|
||||
|
||||
@@ -21,10 +21,25 @@
|
||||
<el-tag :type="stageTag(row)" size="small">{{ stageLabel('doctor', row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<el-table-column label="日程" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" :disabled="!row.scheduleUrl" @click="onPreview(row.scheduleUrl, '日程海报')">查看</el-button>
|
||||
<el-button link :disabled="!row.scheduleUrl" @click="onDownload(row.scheduleUrl, `${row.meetingName || '会议'}_日程海报`)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="邀请函" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" :disabled="!row.projectInvitationUrl" @click="onPreview(row.projectInvitationUrl, '邀请函')">查看</el-button>
|
||||
<el-button link :disabled="!row.projectInvitationUrl" @click="onDownload(row.projectInvitationUrl, `${row.meetingName || '会议'}_邀请函`)">下载</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签署状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.attendeeLaborProtocol ? 'success' : 'info'" size="small">{{ row.attendeeLaborProtocol ? '已签署' : '未签署' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link type="primary" :disabled="!row.invitationUrl" @click="onDownloadFile(row)">下载</el-button>
|
||||
<el-button v-if="!row.attendeeLaborProtocol" link type="primary" @click="onSign(row)">签署劳务</el-button>
|
||||
<el-button v-else link type="primary" @click="onViewLabor(row)">查看劳务</el-button>
|
||||
</template>
|
||||
@@ -42,6 +57,23 @@
|
||||
style="margin-top: 12px; text-align: right;"
|
||||
/>
|
||||
|
||||
<!-- 日程 / 邀请函 预览 dialog (PDF iframe / 图片 img, 复用 publicity 的 OSS 代理预览技术) -->
|
||||
<el-dialog v-model="previewOpen" :title="previewTitle" width="780px" top="6vh" :close-on-click-modal="false" destroy-on-close>
|
||||
<div v-if="previewUrl" class="file-preview">
|
||||
<iframe v-if="isPdf(previewUrl)" :src="proxyUrl(previewUrl)" class="file-preview-iframe"></iframe>
|
||||
<el-image v-else-if="isImg(previewUrl)" :src="previewUrl" :preview-src-list="[previewUrl]" fit="contain" class="file-preview-img" />
|
||||
<div v-else class="file-preview-fallback">
|
||||
<p>该文件类型暂不支持页内预览, 请在新窗口打开查看。</p>
|
||||
<el-button type="primary" @click="openPreviewNew">在新窗口打开</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无附件" />
|
||||
<template #footer>
|
||||
<el-button @click="previewOpen = false">关闭</el-button>
|
||||
<el-button v-if="previewUrl" type="primary" @click="onDownload(previewUrl, previewTitle)">下载</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 签署劳务 dialog: 二维码 (带 token, 手机扫码可直接登录填写) -->
|
||||
<el-dialog v-model="signOpen" title="签署劳务" width="380px" align-center destroy-on-close>
|
||||
<div class="sign-qr-wrap">
|
||||
@@ -57,18 +89,12 @@
|
||||
|
||||
<!-- 查看劳务 dialog: 已签状态显示, 含 PDF iframe 预览 -->
|
||||
<el-dialog v-model="laborOpen" title="查看劳务" width="720px">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="项目编号">{{ detail.projectNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="会议名称">{{ detail.meetingName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="签署状态">已签署</el-descriptions-item>
|
||||
<el-descriptions-item label="签署时间">{{ detail.updateTime || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="pdf-preview">
|
||||
<iframe v-if="detail.attendeeLaborProtocol" :src="detail.attendeeLaborProtocol" style="width:100%;height:420px;border:1px solid #ebeef5"></iframe>
|
||||
<iframe v-if="detail.attendeeLaborProtocol" :src="proxyUrl(detail.attendeeLaborProtocol)" style="width:100%;height:70vh;border:1px solid #ebeef5"></iframe>
|
||||
<div v-else style="padding:24px;color:#909399;text-align:center">暂无签字 PDF</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button v-if="detail.attendeeLaborProtocol" link type="primary" @click="downloadPdf(detail.attendeeLaborProtocol, detail.meetingName)">下载</el-button>
|
||||
<el-button v-if="detail.attendeeLaborProtocol" type="primary" @click="downloadPdf(detail.attendeeLaborProtocol, detail.meetingName)">下载</el-button>
|
||||
<el-button @click="laborOpen=false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -77,7 +103,7 @@
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import QRCode from 'qrcode'
|
||||
import { bizList } from '@/api/public'
|
||||
import { stageLabel, stageTag, STAGE_OPTIONS } from '@/utils/meetingStage'
|
||||
@@ -88,6 +114,9 @@ const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const detail = ref({})
|
||||
const laborOpen = ref(false)
|
||||
const previewOpen = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewTitle = ref('')
|
||||
const signOpen = ref(false)
|
||||
const signForm = reactive({ meetingName: '' })
|
||||
const signLink = ref('')
|
||||
@@ -119,27 +148,47 @@ function onViewLabor(row) {
|
||||
laborOpen.value = true
|
||||
}
|
||||
|
||||
function onView(row) {
|
||||
detail.value = row
|
||||
// 复用 detail dialog: 复用 meetingInfo 字段直接展示
|
||||
// 这里直接展示一个只读 dialog, 没有劳务签署按钮
|
||||
ElMessageBox.alert(
|
||||
`项目编号: ${row.projectNo || '-'}\n会议名称: ${row.meetingName || '-'}\n当前阶段: ${stageLabel('doctor', row)}`,
|
||||
'会议详情',
|
||||
{ confirmButtonText: '关闭' }
|
||||
)
|
||||
// ===== 日程 / 邀请函 预览 (与 publicity 同技术: OSS 代理重写 inline + iframe/img) =====
|
||||
function isPdf(url) {
|
||||
if (!url) return false
|
||||
const path = url.split('?')[0].toLowerCase()
|
||||
return /\.pdf$/.test(path)
|
||||
}
|
||||
|
||||
function onDownloadFile(row) {
|
||||
if (!row.invitationUrl) { ElMessage.warning('该会议暂无邀请函附件'); return }
|
||||
function isImg(url) {
|
||||
if (!url) return false
|
||||
const path = url.split('?')[0].toLowerCase()
|
||||
return /\.(png|jpg|jpeg|gif|webp)$/.test(path)
|
||||
}
|
||||
// OSS bucket 设置 Content-Disposition: attachment, iframe 直接访问会被强制下载,
|
||||
// 走 /common/oss/proxy 后端代理重写为 inline (#toolbar=0 隐藏工具栏, #zoom=page-width 撑满宽度)
|
||||
function proxyUrl(url) {
|
||||
if (!url) return url
|
||||
if (url.includes('hwtossbamlorgcn.oss-cn-beijing.aliyuncs.com')) {
|
||||
return import.meta.env.VITE_APP_BASE_API + '/common/oss/proxy?url=' + encodeURIComponent(url) + '#toolbar=0&zoom=page-width'
|
||||
}
|
||||
return url
|
||||
}
|
||||
function onPreview(url, title) {
|
||||
if (!url) { ElMessage.warning('暂无附件'); return }
|
||||
previewUrl.value = url
|
||||
previewTitle.value = title
|
||||
previewOpen.value = true
|
||||
}
|
||||
function onDownload(url, name) {
|
||||
if (!url) { ElMessage.warning('暂无附件'); return }
|
||||
const a = document.createElement('a')
|
||||
a.href = row.invitationUrl
|
||||
a.download = `${row.meetingName || '会议'}_邀请函.pdf`
|
||||
a.href = url
|
||||
a.download = name
|
||||
a.target = '_blank'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
function openPreviewNew() {
|
||||
const url = proxyUrl(previewUrl.value)
|
||||
if (!url) return
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
async function onSign(row) {
|
||||
signForm.meetingName = row.meetingName || ''
|
||||
@@ -206,6 +255,12 @@ load()
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
|
||||
.pdf-preview { margin-top: 12px; }
|
||||
.file-preview { display: flex; justify-content: center; align-items: flex-start; min-height: 480px; }
|
||||
.file-preview-iframe { width: 100%; height: 70vh; border: 1px solid #ebeef5; border-radius: 4px; }
|
||||
.file-preview-img { width: 100%; }
|
||||
.file-preview-img :deep(.el-image__inner) { width: 100%; height: auto; max-height: 70vh; object-fit: contain; }
|
||||
.file-preview-fallback { text-align: center; padding: 48px 24px; color: #606266; }
|
||||
.file-preview-fallback p { margin-bottom: 16px; }
|
||||
.sign-link-tip { font-size: 13px; color: #606266; margin-bottom: 10px; }
|
||||
.sign-link-hint { font-size: 12px; color: #909399; margin-top: 10px; }
|
||||
.sign-qr-wrap { text-align: center; padding: 12px 0; }
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
<el-form-item><el-button type="primary" @click="load">查找</el-button><el-button @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="filter-tip">*下载项目公示的红头文件</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="projectNo" label="项目编号" width="160" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="240" show-overflow-tooltip />
|
||||
@@ -139,7 +137,6 @@ load()
|
||||
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
|
||||
.filter-tip { background: #fffbe6; border: 1px solid #ffe58f; color: #ad6800; padding: 8px 12px; border-radius: 4px; font-size: 12px; margin-bottom: 12px; }
|
||||
.status { font-size: 13px; color: #595959; }
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep); border-color: var(--brand-primary-deep); }
|
||||
|
||||
@@ -45,9 +45,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="planCategory" label="项目类别" width="120" />
|
||||
<el-table-column prop="projectForm" label="形式" width="100" />
|
||||
<el-table-column label="设计文件" width="100">
|
||||
<el-table-column label="设计文件" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="row.designFileUrl" type="primary" :href="row.designFileUrl" target="_blank">下载</el-link>
|
||||
<template v-if="row.designFileUrl">
|
||||
<el-button link type="primary" @click="openPreview(row.designFileUrl, '设计文件')">查看</el-button>
|
||||
<el-link type="primary" :href="row.designFileUrl" target="_blank">下载</el-link>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -77,6 +80,7 @@
|
||||
style="margin-top: 12px; text-align: right;"
|
||||
/>
|
||||
|
||||
<Preview v-model="previewOpen" :url="previewUrl" :title="previewTitle" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -88,6 +92,7 @@ import request from '@/utils/request'
|
||||
import { bizList, bizUpdate } from '@/api/public'
|
||||
import DictSelect from '@/components/DictSelect.vue'
|
||||
import AuditStatusTag from '@/components/AuditStatusTag.vue'
|
||||
import Preview from '@/components/Preview.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -107,6 +112,11 @@ const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const selected = ref([])
|
||||
|
||||
// 设计文件预览
|
||||
const previewOpen = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewTitle = ref('附件预览')
|
||||
|
||||
// 项目方向 options (复用 manager 侧接口)
|
||||
const planDirectionOptions = ref([])
|
||||
async function loadPlanDirectionOptions() {
|
||||
@@ -184,6 +194,12 @@ async function onBatch() {
|
||||
load()
|
||||
}
|
||||
|
||||
function openPreview(url, title) {
|
||||
previewUrl.value = url
|
||||
previewTitle.value = title || '附件预览'
|
||||
previewOpen.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadPlanDirectionOptions()
|
||||
load()
|
||||
|
||||
Reference in New Issue
Block a user