feat: OA(ecology)项目拉取 + 会议邀请函PDF/微信分享 + 参会人/执行意向增强
OA ecology: - BizEcologyController + EcologyProjectService: 项目编号搜索/详情回填, 负责人静默建档 - ComplianceLoginService: 合规/负责人登录校验 + 视图 role 匹配 - application-prod/test.yml: ecology 只读库数据源 会议邀请函/分享: - InvitationPdfService + WxShareService: 邀请函 PDF 生成 + 微信分享 - PdfViewer.vue (pdfjs-dist) + MeetingInvitation.vue: PDF 预览 + 邀请函页 参会人/执行意向: - BizMeetingAttendee*: 参会人模块增强 - BizExecutionIntent*: 执行意向增强 签到/劳务/导入: - BizSignServiceImpl + SignFill.vue: 签到劳务 - FieldMismatch/ImportMismatch: 导入校验 VO 公示/会议详情: - PublicityDetail.vue / MeetingDetail.vue 等前端增强 配置: - test 短信 mock + 供应商/招标接口切生产域名
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div v-loading="loading" class="page-card invitation-page">
|
||||
<!-- 面包屑 -->
|
||||
<div class="breadcrumb">首页 / 会议邀请函</div>
|
||||
|
||||
<!-- 称谓 -->
|
||||
<p class="salutation">尊敬的 {{ attendeeName || '专家' }}:</p>
|
||||
|
||||
<!-- 正文 -->
|
||||
<p class="body-text">
|
||||
您好!由衷感谢您长期以来对北京整合医学学会工作的鼎力支持。您报名参与的
|
||||
<span class="highlight">【{{ projectName || '(项目待定)' }}】</span>,现定于
|
||||
<span class="highlight">【{{ meetingDate || '(会议日期待定)' }}】</span>正式举办。我们诚挚邀请您担任
|
||||
<span class="highlight">【{{ laborForm || '(角色待定)' }}】</span>。
|
||||
</p>
|
||||
|
||||
<!-- 落款 -->
|
||||
<div class="signature">
|
||||
<div>北京整合医学学会</div>
|
||||
<div>{{ issueDate }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 附件:日程海报 + 邀请函 缩略图 -->
|
||||
<div class="attach-row">
|
||||
<div class="attach-card">
|
||||
<div class="attach-label">日程海报</div>
|
||||
<div v-if="scheduleUrl" class="attach-thumb" @click="preview(scheduleUrl, '日程海报')">
|
||||
<img v-if="scheduleThumb" :src="scheduleThumb" alt="日程海报" />
|
||||
<div v-else class="thumb-loading">缩略图生成中…</div>
|
||||
</div>
|
||||
<div v-else class="attach-missing">未上传,请联系会务方</div>
|
||||
<div v-if="scheduleUrl" class="attach-actions">
|
||||
<el-button size="small" @click="preview(scheduleUrl, '日程海报')">预览</el-button>
|
||||
<el-button size="small" type="primary" @click="download(scheduleUrl, '日程海报')">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="attach-card">
|
||||
<div class="attach-label">邀请函</div>
|
||||
<div v-if="invitationUrl" class="attach-thumb" @click="preview(invitationUrl, '邀请函')">
|
||||
<img v-if="invitationThumb" :src="invitationThumb" alt="邀请函" />
|
||||
<div v-else class="thumb-loading">缩略图生成中…</div>
|
||||
</div>
|
||||
<div v-else class="attach-missing">未上传,请联系会务方</div>
|
||||
<div v-if="invitationUrl" class="attach-actions">
|
||||
<el-button size="small" @click="preview(invitationUrl, '邀请函')">预览</el-button>
|
||||
<el-button size="small" type="primary" @click="download(invitationUrl, '邀请函')">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览 dialog (PDF 走 PDF.js canvas 渲染, 图片走 el-image) -->
|
||||
<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">
|
||||
<PdfViewer v-if="isPdf(previewUrl)" :url="previewUrl" class="file-preview-pdf" />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="previewOpen = false">关闭</el-button>
|
||||
<el-button v-if="previewUrl" type="primary" @click="download(previewUrl, previewTitle)">下载</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getMeetingInvitation } from '@/api/business/meetingAttendee'
|
||||
import PdfViewer from '@/components/PdfViewer.vue'
|
||||
import * as pdfjsLib from 'pdfjs-dist'
|
||||
import pdfWorker from 'pdfjs-dist/build/pdf.worker.min.js?url'
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorker
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const loading = ref(false)
|
||||
const attendeeName = ref('')
|
||||
const laborForm = ref('')
|
||||
const projectName = ref('')
|
||||
const meetingDate = ref('')
|
||||
const issueDate = ref('')
|
||||
const scheduleUrl = ref('')
|
||||
const invitationUrl = ref('')
|
||||
const scheduleThumb = ref('')
|
||||
const invitationThumb = ref('')
|
||||
|
||||
const previewOpen = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewTitle = ref('')
|
||||
|
||||
// 缩略图渲染令牌: 路由切换/卸载时作废, 避免异步回写串页
|
||||
let renderToken = 0
|
||||
|
||||
function pad(n) { return String(n).padStart(2, '0') }
|
||||
function isPdf(url) {
|
||||
if (!url) return false
|
||||
return /\.pdf$/.test(url.split('?')[0].toLowerCase())
|
||||
}
|
||||
function isImg(url) {
|
||||
if (!url) return false
|
||||
return /\.(png|jpg|jpeg|gif|webp)$/.test(url.split('?')[0].toLowerCase())
|
||||
}
|
||||
// PDF.js 专用代理 URL: 不带 #toolbar fragment (PDF.js 需要干净字节流)
|
||||
function pdfProxyUrl(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)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
function fmtMeetingDate(v) {
|
||||
if (!v) return ''
|
||||
const d = new Date(String(v).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return v
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
function fmtCnDate(d) {
|
||||
if (!d) return ''
|
||||
return `${d.getFullYear()} 年 ${pad(d.getMonth() + 1)} 月 ${pad(d.getDate())} 日`
|
||||
}
|
||||
|
||||
/** PDF 首帧转缩略图 (canvas → dataURL); 图片直接返回原 url */
|
||||
async function renderThumb(url) {
|
||||
if (isImg(url)) return url
|
||||
if (!isPdf(url)) return ''
|
||||
const doc = await pdfjsLib.getDocument({ url: pdfProxyUrl(url) }).promise
|
||||
const page = await doc.getPage(1)
|
||||
const base = page.getViewport({ scale: 1 })
|
||||
const scale = Math.min(280 / base.width, 1.6) // 缩略图目标宽 ~280px
|
||||
const viewport = page.getViewport({ scale })
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = Math.floor(viewport.width)
|
||||
canvas.height = Math.floor(viewport.height)
|
||||
await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise
|
||||
const dataUrl = canvas.toDataURL('image/png')
|
||||
await doc.destroy()
|
||||
return dataUrl
|
||||
}
|
||||
|
||||
async function loadThumbs() {
|
||||
const token = renderToken
|
||||
if (scheduleUrl.value) {
|
||||
scheduleThumb.value = await renderThumb(scheduleUrl.value).catch(() => '')
|
||||
}
|
||||
if (token !== renderToken) return
|
||||
if (invitationUrl.value) {
|
||||
invitationThumb.value = await renderThumb(invitationUrl.value).catch(() => '')
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const resp = await getMeetingInvitation(route.params.attendeeId)
|
||||
const d = resp?.data || {}
|
||||
attendeeName.value = d.attendeeName || ''
|
||||
laborForm.value = d.laborForm || ''
|
||||
projectName.value = d.projectName || ''
|
||||
meetingDate.value = fmtMeetingDate(d.meetingTime)
|
||||
issueDate.value = fmtCnDate(new Date())
|
||||
scheduleUrl.value = d.scheduleUrl || ''
|
||||
invitationUrl.value = d.invitationUrl || ''
|
||||
scheduleThumb.value = ''
|
||||
invitationThumb.value = ''
|
||||
await loadThumbs()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '加载邀请函失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function preview(url, name) {
|
||||
if (!url) { ElMessage.warning('该附件尚未上传'); return }
|
||||
previewUrl.value = url
|
||||
previewTitle.value = name
|
||||
previewOpen.value = true
|
||||
}
|
||||
|
||||
function download(url, name) {
|
||||
if (!url) { ElMessage.warning('该附件尚未上传'); return }
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = name
|
||||
a.target = '_blank'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
|
||||
onMounted(() => { renderToken++; load() })
|
||||
onBeforeUnmount(() => { renderToken++ })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 doctor/SubmissionDetail.vue 一致: 单卡片, max-width 1200px, 不套小框) */
|
||||
.invitation-page { max-width: 1200px; padding: 16px 20px; }
|
||||
.salutation { font-size: 16px; color: #1a1a1a; margin: 20px 0 18px; }
|
||||
.body-text { font-size: 15px; color: #303133; margin: 0 0 28px; line-height: 1.9; text-indent: 2em; }
|
||||
.highlight { color: var(--brand-primary); font-weight: 600; }
|
||||
.signature { text-align: right; font-size: 15px; color: #303133; margin-bottom: 40px; }
|
||||
.signature div + div { margin-top: 4px; }
|
||||
|
||||
.attach-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 24px;
|
||||
}
|
||||
.attach-card {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.attach-label { font-size: 14px; color: #303133; font-weight: 600; }
|
||||
.attach-thumb {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
.attach-thumb img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.thumb-loading { font-size: 12px; color: #909399; }
|
||||
.attach-missing {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fdf6ec;
|
||||
border: 1px dashed #f3d19e;
|
||||
border-radius: 6px;
|
||||
color: #e6a23c;
|
||||
font-size: 13px;
|
||||
}
|
||||
.attach-actions { display: flex; gap: 8px; }
|
||||
|
||||
.file-preview { display: flex; justify-content: center; align-items: flex-start; min-height: 480px; }
|
||||
.file-preview-pdf { width: 100%; }
|
||||
.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; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.invitation-page { padding: 12px 14px; }
|
||||
.attach-row { flex-direction: column; }
|
||||
}
|
||||
</style>
|
||||
@@ -64,7 +64,7 @@
|
||||
<!-- 日程 / 邀请函 预览 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>
|
||||
<PdfViewer v-if="isPdf(previewUrl)" :url="previewUrl" class="file-preview-pdf" />
|
||||
<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>
|
||||
@@ -94,7 +94,7 @@
|
||||
<!-- 查看劳务 dialog: 已签状态显示, 含 PDF iframe 预览 -->
|
||||
<el-dialog v-model="laborOpen" title="查看劳务" width="720px">
|
||||
<div class="pdf-preview">
|
||||
<iframe v-if="detail.attendeeLaborProtocol" :src="proxyUrl(detail.attendeeLaborProtocol)" style="width:100%;height:70vh;border:1px solid #ebeef5"></iframe>
|
||||
<PdfViewer v-if="detail.attendeeLaborProtocol" :url="detail.attendeeLaborProtocol" class="file-preview-pdf" />
|
||||
<div v-else style="padding:24px;color:#909399;text-align:center">暂无签字 PDF</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -111,6 +111,7 @@ import { ElMessage } from 'element-plus'
|
||||
import QRCode from 'qrcode'
|
||||
import { bizList } from '@/api/public'
|
||||
import { stageLabel, stageTag, STAGE_OPTIONS } from '@/utils/meetingStage'
|
||||
import PdfViewer from '@/components/PdfViewer.vue'
|
||||
|
||||
const q = reactive({ projectNo: '', meetingName: '', currentStage: '' })
|
||||
const page = reactive({ pageNum: 1, pageSize: 20, total: 0 })
|
||||
@@ -259,7 +260,7 @@ load()
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; 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-pdf { width: 100%; }
|
||||
.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; }
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
<el-button type="primary" @click="goBack">返回</el-button>
|
||||
</div>
|
||||
<div v-else v-loading="loading">
|
||||
<!-- 已签署: 再次打开签署链接, 直接展示劳务协议 PDF (复用 publicity 的 OSS 代理预览) -->
|
||||
<!-- 已签署: 再次打开签署链接, 本页内嵌 PDF.js 渲染 (微信/移动端无 Chrome 内置 PDF viewer) -->
|
||||
<div v-if="signed" class="signed-pdf">
|
||||
<div class="sign-header">
|
||||
<div class="sign-header-title">{{ meetingName || '劳务协议' }}</div>
|
||||
<div v-if="periodDisplay" class="sign-header-period">期数:{{ periodDisplay }}</div>
|
||||
</div>
|
||||
<iframe v-if="laborPdfUrl" :src="proxyUrl(laborPdfUrl)" class="signed-pdf-frame"></iframe>
|
||||
<PdfViewer v-if="laborPdfUrl" :url="laborPdfUrl" class="signed-pdf-viewer" />
|
||||
<div v-else class="signed-pdf-empty">协议已签署,暂无可展示的 PDF</div>
|
||||
<div class="signed-pdf-actions">
|
||||
<el-button v-if="laborPdfUrl" type="primary" @click="downloadPdf">下载劳务协议</el-button>
|
||||
@@ -108,6 +108,7 @@ import { getInfo } from '@/api/auth'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import IdCardUploader from '@/components/IdCardUploader.vue'
|
||||
import AreaCascader from '@/components/AreaCascader.vue'
|
||||
import PdfViewer from '@/components/PdfViewer.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -247,16 +248,10 @@ async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await getSignInfo(attendeeId.value)
|
||||
// 已签署: 再次打开签署链接 → 整页跳转到 OSS proxy 的 PDF 地址 (全屏显示, 不用 iframe 内嵌)
|
||||
// 已签署: 再次打开签署链接 → 本页内嵌 PDF.js 渲染 (微信/移动端无 Chrome 内置 PDF viewer)
|
||||
if (data.signed || (data.laborProtocol && data.laborProtocol.trim())) {
|
||||
const url = data.laborProtocol
|
||||
if (url) {
|
||||
window.location.href = proxyUrl(url)
|
||||
return
|
||||
}
|
||||
// 无 PDF URL 兜底: 仍留在本页显示"已签署但无可展示 PDF"
|
||||
signed.value = true
|
||||
laborPdfUrl.value = ''
|
||||
laborPdfUrl.value = data.laborProtocol || ''
|
||||
meetingName.value = data.meetingName || ''
|
||||
periodNo.value = data.periodNo ?? null
|
||||
totalPeriods.value = data.totalPeriods ?? null
|
||||
@@ -357,14 +352,6 @@ async function onSubmit() {
|
||||
}
|
||||
}
|
||||
|
||||
// OSS 代理预览 (同 publicity / doctor/Meetings.vue): 重写 Content-Disposition 为 inline, 隐藏工具栏撑满宽度
|
||||
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 downloadPdf() {
|
||||
if (!laborPdfUrl.value) return
|
||||
const a = document.createElement('a')
|
||||
@@ -391,9 +378,9 @@ onMounted(init)
|
||||
.sign-header-title { font-size: 20px; font-weight: 600; color: #1a1a1a; line-height: 1.4; }
|
||||
.sign-header-period { margin-top: 6px; font-size: 14px; color: #595959; }
|
||||
|
||||
/* 已签署: 直接展示 PDF (宽度 100%, 高度不限制) */
|
||||
/* 已签署: 直接展示 PDF (PdfViewer 内部自适应宽度/高度 + 滚动) */
|
||||
.signed-pdf { display: flex; flex-direction: column; }
|
||||
.signed-pdf-frame { width: 100%; height: 424vw; border: 0; }
|
||||
.signed-pdf-viewer { width: 100%; }
|
||||
.signed-pdf-empty { padding: 48px 16px; text-align: center; color: #909399; }
|
||||
.signed-pdf-actions { margin-top: 16px; display: flex; gap: 12px; }
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
<!-- 操作按钮 — 从 el-form 提出来, 避免 Element Plus form-item 容器影响按钮布局 -->
|
||||
<div class="detail-actions">
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
<el-button v-if="showSaveAndSubmit" type="primary" :loading="submitting" @click="onSaveAndSubmit">保存并提交</el-button>
|
||||
<el-button @click="confirmCancel">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,8 +89,11 @@ const base = computed(() => `/${route.meta.role || 'doctor'}`)
|
||||
|
||||
const formRef = ref(null)
|
||||
const saving = ref(false)
|
||||
const submitting = ref(false)
|
||||
const loadingDetail = ref(false)
|
||||
const isEdit = !!route.params.planId
|
||||
// 保存并提交按钮: 仅 专家(doctor)/执行方(executor) 新建时显示 (点击直接提交审核)
|
||||
const showSaveAndSubmit = computed(() => !isEdit && ['doctor', 'executor'].includes(route.meta.role))
|
||||
|
||||
// 项目方向 options (复用 manager 侧接口)
|
||||
const planDirectionOptions = ref([])
|
||||
@@ -160,19 +164,20 @@ function confirmCancel() {
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
async function doSave(submit = false) {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
} catch (e) {
|
||||
ElMessage.warning('请填写必填项')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
const loadingRef = submit ? submitting : saving
|
||||
loadingRef.value = true
|
||||
try {
|
||||
let payload = { ...form }
|
||||
if (!isEdit) {
|
||||
// 新建: 强制 status='0' (未提交), submitter 后端兜底
|
||||
payload.status = '0'
|
||||
// 新建: 默认 status='0' (未提交); "保存并提交" 直接 status='1' (待审核), submitter 后端兜底
|
||||
payload.status = submit ? '1' : '0'
|
||||
payload.submitterId = userStore.user?.userId || null
|
||||
} else if (!payload.status) {
|
||||
payload.status = '0'
|
||||
@@ -182,16 +187,19 @@ async function onSave() {
|
||||
} else {
|
||||
await bizAdd('projectPlan', payload, { __silentError: true })
|
||||
}
|
||||
ElMessage.success(isEdit ? '修改成功' : '新建成功')
|
||||
ElMessage.success(submit ? '已保存并提交审核' : (isEdit ? '修改成功' : '新建成功'))
|
||||
goBack()
|
||||
} catch (e) {
|
||||
console.error('[submission-new] save failed', e)
|
||||
ElMessage.error(e?.msg || (isEdit ? '修改失败' : '保存失败'))
|
||||
ElMessage.error(e?.msg || (submit ? '保存并提交失败' : (isEdit ? '修改失败' : '保存失败')))
|
||||
} finally {
|
||||
saving.value = false
|
||||
loadingRef.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onSave() { return doSave(false) }
|
||||
async function onSaveAndSubmit() { return doSave(true) }
|
||||
|
||||
onMounted(() => {
|
||||
loadPlanDirectionOptions()
|
||||
if (isEdit) loadDetail()
|
||||
|
||||
Reference in New Issue
Block a user