refactor(doctor/submissions): 合并到 biz_project_plan + 删除 biz_submission 死代码

- /doctor/submissions 改走 biz_project_plan,与 /manager/plans 字段/组件/状态字典完全对齐
- BizProjectPlan 加 submitter_id (BIGINT) + submitter_name (LEFT JOIN 查询字段)
- mapper 改 LEFT JOIN biz_person + sys_user,COALESCE(bp.name, u.user_name) 兜底
- biz_person.user_id 加 UNIQUE 索引 (1:1 LEFT JOIN 无笛卡尔积)
- controller 加 doctor 角色自动过滤: list 按 submitter_id, add/edit 强制 submitter_id
- 删除 7 个 BizSubmission 死代码 (Controller/Service/Mapper/Domain/XML/Enum) + 1 utils
- 路由参数 :subId → :planId (路径名 submission 保留产品文案)
- 新增 migration_submission_to_projectPlan_2026_08_18.sql
This commit is contained in:
郭庆泰
2026-08-18 00:28:40 +08:00
parent b41cf77cac
commit c9fc6ebdb8
17 changed files with 245 additions and 562 deletions
+4 -3
View File
@@ -44,8 +44,9 @@ const routes = [
{ path: 'orgs', name: 'admin-orgs', component: () => import('@/views/admin/Orgs.vue'), meta: { title: '公司管理' } },
{ path: 'article', name: 'admin-article', component: () => import('@/views/admin/BizArticleAdmin.vue'), meta: { title: '协议管理' } },
{ path: 'article/edit/:id', name: 'admin-article-edit', component: () => import('@/views/admin/BizArticleEdit.vue'), meta: { title: '编辑文章' } },
{ path: 'special-plan', name: 'admin-special-plan', component: () => import('@/views/admin/BizSpecialPlanAdmin.vue'), meta: { title: '七大专项计划' } },
{ path: 'special-plan', name: 'admin-special-plan', component: () => import('@/views/admin/BizSpecialPlanAdmin.vue'), meta: { title: '专项计划管理' } },
{ path: 'special-plan/edit/:id', name: 'admin-special-plan-edit', component: () => import('@/views/admin/BizSpecialPlanEdit.vue'), meta: { title: '编辑专项计划' } },
{ path: 'project-category', name: 'admin-project-category', component: () => import('@/views/admin/ProjectCategory.vue'), meta: { title: '项目类别管理' } },
{ path: 'account', name: 'admin-account', component: () => import('@/views/admin/Account.vue'), meta: { title: '账号信息' } }
]
},
@@ -92,8 +93,8 @@ const routes = [
{ path: 'messages', name: 'doctor-messages', component: () => import('@/views/doctor/Messages.vue'), meta: { title: '消息通知' } },
{ path: 'submissions', name: 'doctor-submissions', component: () => import('@/views/doctor/Submissions.vue'), meta: { title: '我的项目设计投稿' } },
{ path: 'submission/new', name: 'doctor-submission-new', component: () => import('@/views/doctor/SubmissionNew.vue'), meta: { title: '新建项目设计投稿' } },
{ path: 'submission/detail/:subId', name: 'doctor-submission-detail', component: () => import('@/views/doctor/SubmissionDetail.vue'), meta: { title: '投稿详情' } },
{ path: 'submission/edit/:subId', name: 'doctor-submission-edit', component: () => import('@/views/doctor/SubmissionNew.vue'), meta: { title: '修改项目设计投稿' } },
{ path: 'submission/detail/:planId', name: 'doctor-submission-detail', component: () => import('@/views/doctor/SubmissionDetail.vue'), meta: { title: '投稿详情' } },
{ path: 'submission/edit/:planId', name: 'doctor-submission-edit', component: () => import('@/views/doctor/SubmissionNew.vue'), meta: { title: '修改项目设计投稿' } },
{ path: 'account', name: 'doctor-account', component: () => import('@/views/doctor/Account.vue'), meta: { title: '账号信息' } }
]
},
-41
View File
@@ -1,41 +0,0 @@
// 投稿状态字典 - 与后端 SubmissionStatus 枚举同步
// value 用英文 code (PENDING/APPROVED/REJECTED/DRAFT), 前端通过 label 映射中文
// locked=true 表示锁定(不可修改/提交)
export const SUBMISSION_STATUS = {
PENDING: { code: 'PENDING', label: '待审核', type: 'warning', locked: true },
APPROVED: { code: 'APPROVED', label: '审核通过', type: 'success', locked: true },
REJECTED: { code: 'REJECTED', label: '已退回', type: 'danger', locked: false },
DRAFT: { code: 'DRAFT', label: '待提交', type: 'info', locked: false }
}
// 容错: 把数据库返回的 status (字符串) 规范成 enum, 找不到原样返回
export function parseStatus(val) {
if (val === null || val === undefined || val === '') return null
const key = String(val).trim().toUpperCase()
return SUBMISSION_STATUS[key] ? key : null
}
// 获取状态 label, 容错返回 val 原值
export function statusLabel(val) {
const key = parseStatus(val)
return key ? SUBMISSION_STATUS[key].label : (val ?? '')
}
// 获取 el-tag type
export function statusType(val) {
const key = parseStatus(val)
return key ? SUBMISSION_STATUS[key].type : 'info'
}
// 是否锁定 (待审核/审核通过不可修改/提交)
export function isLocked(val) {
const key = parseStatus(val)
if (!key) return false
return SUBMISSION_STATUS[key].locked === true
}
// 筛选项列表 (用于 el-select options)
export const STATUS_OPTIONS = Object.values(SUBMISSION_STATUS).map(s => ({
label: s.label,
value: s.code
}))
+9 -9
View File
@@ -37,12 +37,12 @@
<a class="more" @click.prevent="$router.push('/doctor/submissions')">更多 </a>
</h2>
<ul class="simple-list">
<li class="simple-item" v-for="s in pendingAgreements" :key="s.subId">
<li class="simple-item" v-for="s in pendingAgreements" :key="s.planId">
<div class="item-main">
<span class="item-title">{{ s.title }}</span>
<span class="item-title">{{ s.planName }}</span>
</div>
<span class="item-status" :class="{ done: s.status === 'done' }">
{{ s.status === 'done' ? '已完成' : '待签署' }}
<span class="item-status" :class="{ done: s.status === '2' }">
{{ s.status === '2' ? '已通过' : (s.status === '3' ? '已退回' : (s.status === '1' ? '审核中' : '待提交')) }}
</span>
</li>
<li v-if="!pendingAgreements.length" class="empty">暂无待签协议</li>
@@ -109,13 +109,13 @@ async function load() {
upcomingMeetings.value = (data?.rows || []).slice(0, 5)
} catch (e) { upcomingMeetings.value = [] }
// 待签署协议
// 待签署协议 (改走 biz_project_plan, 后端 doctor 角色已自动按当前用户过滤)
try {
const { data } = await bizList('submission', { pageNum: 1, pageSize: 5 })
const { data } = await bizList('projectPlan', { pageNum: 1, pageSize: 5 })
pendingAgreements.value = (data?.rows || []).slice(0, 5).map(s => ({
subId: s.subId,
title: s.title,
status: s.status === '审核通过' ? 'done' : 'pending'
planId: s.planId,
planName: s.planName,
status: s.status
}))
} catch (e) { pendingAgreements.value = [] }
+20 -33
View File
@@ -11,12 +11,12 @@
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="策划方案名称">
<el-input :model-value="display.title" readonly />
<el-input :model-value="display.planName" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目方向">
<el-input :model-value="display.direction" readonly />
<el-input :model-value="display.planDirectionTitle" readonly />
</el-form-item>
</el-col>
</el-row>
@@ -29,7 +29,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="项目类别">
<el-input :model-value="display.projectCategory" readonly />
<el-input :model-value="display.planCategory" readonly />
</el-form-item>
</el-col>
</el-row>
@@ -57,15 +57,18 @@
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-col :span="8">
<el-form-item label="状态">
<el-tag v-if="detail.status !== null && detail.status !== undefined && detail.status !== ''" :type="statusType(detail.status)" disable-transitions>
{{ statusLabel(detail.status) }}
</el-tag>
<audit-status-tag v-if="detail.status" :status="detail.status" />
<span v-else>-</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="8">
<el-form-item label="投稿人">
<el-input :model-value="display.submitterName" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="投稿时间">
<el-input :model-value="display.createTime" readonly />
</el-form-item>
@@ -86,6 +89,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
import AuditStatusTag from '@/components/AuditStatusTag.vue'
const route = useRoute()
const router = useRouter()
@@ -103,24 +107,13 @@ function fmtTime(t) {
try { return new Date(t).toLocaleString('zh-CN', { hour12: false }) } catch { return t }
}
// 状态 → el-tag 类型映射
const STATUS_TYPE_MAP = {
'待审核': 'warning',
'审核通过': 'success',
'已退回': 'danger',
'未结题': 'info',
'已结题': 'success'
}
function statusType(s) {
return STATUS_TYPE_MAP[s] || 'info'
}
const display = computed(() => ({
title: fmt(detail.value.title),
direction: fmt(detail.value.direction),
planName: fmt(detail.value.planName),
planDirectionTitle: fmt(detail.value.planDirectionTitle || detail.value.planDirection),
projectForm: fmt(detail.value.projectForm),
projectCategory: fmt(detail.value.projectCategory),
planCategory: fmt(detail.value.planCategory),
remark: fmt(detail.value.remark),
status: fmt(detail.value.status),
submitterName: fmt(detail.value.submitterName),
createTime: fmtTime(detail.value.createTime)
}))
@@ -131,15 +124,15 @@ function goBack() {
function downloadName() {
const url = detail.value.designFileUrl || ''
const last = url.split('/').pop()
return last || `submission-${detail.value.subId}`
return last || `submission-${detail.value.planId}`
}
async function load() {
const subId = route.params.subId
if (!subId) { ElMessage.warning('参数缺失'); goBack(); return }
const planId = route.params.planId
if (!planId) { ElMessage.warning('参数缺失'); goBack(); return }
loading.value = true
try {
const res = await request.get(`/business/submission/${subId}`)
const res = await request.get(`/business/projectPlan/${planId}`)
if (res?.code === 200) {
detail.value = res.data || {}
} else {
@@ -180,12 +173,6 @@ onMounted(load)
font-size: 14px;
word-break: break-all;
}
.remark-cell {
min-height: 110px;
line-height: 1.6;
padding: 12px 14px;
white-space: pre-wrap;
}
.readonly-form :deep(.el-link) { font-weight: 500; }
.detail-actions { margin-top: 16px; }
+46 -60
View File
@@ -10,19 +10,14 @@
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="策划方案名称" prop="title">
<el-input v-model="form.title" placeholder="请输入策划方案名称" maxlength="200" show-word-limit />
<el-form-item label="策划方案名称" prop="planName">
<el-input v-model="form.planName" placeholder="请输入策划方案名称" maxlength="200" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目方向" prop="direction">
<el-select v-model="form.direction" placeholder="请选择" style="width:100%">
<el-option label="学术项目" value="学术项目" />
<el-option label="科研项目" value="科研项目" />
<el-option label="共识" value="共识" />
<el-option label="指南" value="指南" />
<el-option label="会议项目" value="会议项目" />
<el-option label="对外交流" value="对外交流" />
<el-form-item label="项目方向" prop="planDirectionId">
<el-select v-model="form.planDirectionId" placeholder="请选择" style="width:100%">
<el-option v-for="opt in planDirectionOptions" :key="opt.id" :label="opt.title" :value="opt.id" />
</el-select>
</el-form-item>
</el-col>
@@ -40,16 +35,8 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目类别" prop="projectCategory">
<el-select v-model="form.projectCategory" placeholder="请选择" style="width:100%">
<el-option label="学术会议类" value="学术会议类" />
<el-option label="专项科研类" value="专项科研类" />
<el-option label="调研征集类" value="调研征集类" />
<el-option label="慈善帮扶类" value="慈善帮扶类" />
<el-option label="标准制定类" value="标准制定类" />
<el-option label="患者援助类" value="患者援助类" />
<el-option label="专业培训类" value="专业培训类" />
</el-select>
<el-form-item label="项目类别" prop="planCategory">
<dict-select v-model="form.planCategory" dict-type="biz_project_category" value-field="label" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
@@ -69,7 +56,7 @@
:on-remove="onDesignRemove"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将设计文件拖到此处<em>点击上传</em></div>
<div class="el-upload__text">将设计文件拖到此处,<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">支持 PDF / Word / 图片, 单文件 20MB</div>
</el-upload>
</el-form-item>
@@ -100,7 +87,9 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/store/user'
import request from '@/utils/request'
import { bizAdd, bizUpdate } from '@/api/public'
import { uploadToOss } from '@/utils/oss'
import DictSelect from '@/components/DictSelect.vue'
const router = useRouter()
const route = useRoute()
@@ -111,24 +100,33 @@ const saving = ref(false)
const loadingDetail = ref(false)
const designFileList = ref([])
const designUploading = ref(false)
const isEdit = !!route.params.subId
const isEdit = !!route.params.planId
// 项目方向 options (复用 manager 侧接口)
const planDirectionOptions = ref([])
async function loadPlanDirectionOptions() {
try {
const { data } = await request.get('/business/specialPlan/options')
planDirectionOptions.value = (data && data.data) || data || []
} catch { planDirectionOptions.value = [] }
}
const form = reactive({
subId: null,
title: '',
direction: '',
planId: null,
planName: '',
planDirectionId: null,
projectForm: '',
projectCategory: '',
planCategory: '',
designFileUrl: '',
status: 'DRAFT',
status: '0',
remark: ''
})
const rules = {
title: [{ required: true, message: '请输入策划方案名称', trigger: 'blur' }],
direction: [{ required: true, message: '请选择项目方向', trigger: 'change' }],
planName: [{ required: true, message: '请输入策划方案名称', trigger: 'blur' }],
planDirectionId: [{ required: true, message: '请选择项目方向', trigger: 'change' }],
projectForm: [{ required: true, message: '请选择项目形式', trigger: 'change' }],
projectCategory: [{ required: true, message: '请选择项目类别', trigger: 'change' }],
planCategory: [{ required: true, message: '请选择项目类别', trigger: 'change' }],
designFileUrl: [{ required: true, message: '请上传设计文件', trigger: 'change' }]
}
@@ -151,7 +149,6 @@ async function uploadDesign(opts) {
ElMessage.success('设计文件上传成功')
} catch (e) {
ElMessage.error('上传失败: ' + (e?.message || e))
// 上传失败时清空 file-list,避免 el-upload 保留失败文件
designFileList.value = []
} finally {
designUploading.value = false
@@ -164,24 +161,23 @@ function onDesignRemove() {
}
async function loadDetail() {
const subId = route.params.subId
if (!subId) return
const planId = route.params.planId
if (!planId) return
loadingDetail.value = true
try {
const res = await request.get(`/business/submission/${subId}`)
const res = await request.get(`/business/projectPlan/${planId}`)
if (res?.code === 200 && res.data) {
const d = res.data
form.subId = d.subId
form.title = d.title || ''
form.direction = d.direction || ''
form.planId = d.planId
form.planName = d.planName || ''
form.planDirectionId = d.planDirectionId ?? null
form.projectForm = d.projectForm || ''
form.projectCategory = d.projectCategory || ''
form.planCategory = d.planCategory || ''
form.designFileUrl = d.designFileUrl || ''
form.status = d.status && String(d.status).trim() ? String(d.status).trim().toUpperCase() : 'DRAFT'
form.status = d.status && String(d.status).trim() ? String(d.status) : '0'
form.remark = d.remark || ''
// 已上传的设计文件回显到 file-list
if (d.designFileUrl) {
const name = d.designFileUrl.split('/').pop() || `${d.title || '设计文件'}.pdf`
const name = d.designFileUrl.split('/').pop() || `${d.planName || '设计文件'}.pdf`
designFileList.value = [{ name, url: d.designFileUrl }]
}
} else {
@@ -202,8 +198,7 @@ function goBack() {
}
function confirmCancel() {
// 检查表单是否有内容
const hasContent = form.title || form.direction || form.projectForm || form.projectCategory || form.designFileUrl || form.remark
const hasContent = form.planName || form.planDirectionId || form.projectForm || form.planCategory || form.designFileUrl || form.remark
if (!hasContent) { goBack(); return }
ElMessageBox.confirm('确定取消新建?未保存的内容将丢失', '提示', { type: 'warning' })
.then(() => goBack())
@@ -221,29 +216,19 @@ async function onSave() {
try {
let payload = { ...form }
if (!isEdit) {
// 新建: 强制设为 DRAFT(待提交)
payload.status = 'DRAFT'
// 写入当前登录用户信息
// 新建: 强制 status='0' (未提交), submitter 后端兜底
payload.status = '0'
payload.submitterId = userStore.user?.userId || null
payload.submitterName = userStore.user?.userName || userStore.user?.nickName || ''
} else if (!payload.status) {
// 修改: 如果状态丢失, 兜底 DRAFT(待提交)
payload.status = 'DRAFT'
payload.status = '0'
}
let res
if (isEdit) {
// 修改: 走 PUT /business/submission
res = await request.put('/business/submission', payload)
await bizUpdate('projectPlan', payload)
} else {
// 新建: 走 POST /business/submission
res = await request.post('/business/submission', payload)
}
if (res?.code === 200) {
ElMessage.success(isEdit ? '修改成功' : '新建成功')
goBack()
} else {
ElMessage.error(res?.msg || (isEdit ? '修改失败' : '保存失败'))
await bizAdd('projectPlan', payload)
}
ElMessage.success(isEdit ? '修改成功' : '新建成功')
goBack()
} catch (e) {
console.error('[submission-new] save failed', e)
ElMessage.error(e?.msg || (isEdit ? '修改失败' : '保存失败'))
@@ -253,6 +238,7 @@ async function onSave() {
}
onMounted(() => {
loadPlanDirectionOptions()
if (isEdit) loadDetail()
})
</script>
+64 -33
View File
@@ -3,18 +3,29 @@
<div class="breadcrumb">首页 / 我的项目设计投稿</div>
<el-form inline :model="q" class="filter-form">
<el-form-item label="投稿名称"><el-input v-model="q.title" placeholder="输入投稿名称" clearable style="width: 200px" /></el-form-item>
<el-form-item label="学科方向"><el-input v-model="q.direction" placeholder="输入学科方向" clearable style="width: 160px" /></el-form-item>
<el-form-item label="策划方案名称"><el-input v-model="q.planName" placeholder="输入策划方案名称" clearable style="width: 200px" /></el-form-item>
<el-form-item label="项目方向">
<el-select v-model="q.planDirectionId" placeholder="请选择" clearable style="width: 200px">
<el-option v-for="opt in planDirectionOptions" :key="opt.id" :label="opt.title" :value="opt.id" />
</el-select>
</el-form-item>
<el-form-item label="项目类别">
<dict-select v-model="q.planCategory" dict-type="biz_project_category" value-field="label" style="width: 160px" />
</el-form-item>
<el-form-item label="形式">
<el-select v-model="q.projectForm" placeholder="请选择" clearable style="width: 160px">
<el-select v-model="q.projectForm" placeholder="请选择" clearable style="width: 140px">
<el-option label="线上" value="线上" />
<el-option label="线下" value="线下" />
<el-option label="线上+线下" value="线上+线下" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="q.status" placeholder="请选择" clearable style="width: 180px">
<el-option v-for="opt in STATUS_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value" />
<el-select v-model="q.status" placeholder="请选择" clearable style="width: 140px">
<el-option label="待提交" value="0" />
<el-option label="待审核" value="1" />
<el-option label="通过" value="2" />
<el-option label="拒绝" value="3" />
</el-select>
</el-form-item>
<el-form-item label="备注"><el-input v-model="q.remark" placeholder="输入备注" clearable style="width: 200px" /></el-form-item>
@@ -28,8 +39,11 @@
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelectionChange">
<el-table-column type="selection" width="48" />
<el-table-column prop="title" label="投稿名称" min-width="220" show-overflow-tooltip />
<el-table-column prop="direction" label="学科方向" width="120" />
<el-table-column prop="planName" label="投稿名称" min-width="220" show-overflow-tooltip />
<el-table-column label="项目方向" min-width="220" show-overflow-tooltip>
<template #default="{ row }">{{ row.planDirectionTitle || row.planDirection || '-' }}</template>
</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">
<template #default="{ row }">
@@ -37,11 +51,9 @@
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.status !== null && row.status !== undefined && row.status !== ''" :type="statusType(row.status)" disable-transitions>
{{ statusLabel(row.status) }}
</el-tag>
<audit-status-tag :status="row.status" />
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
@@ -70,24 +82,42 @@
</template>
<script setup>
import { reactive, ref } from 'vue'
import { reactive, ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import request from '@/utils/request'
import { bizList, bizUpdate } from '@/api/public'
import { STATUS_OPTIONS, statusLabel, statusType, isLocked } from '@/utils/submissionStatus'
import DictSelect from '@/components/DictSelect.vue'
import AuditStatusTag from '@/components/AuditStatusTag.vue'
const router = useRouter()
const q = reactive({ title: '', direction: '', projectForm: '', status: '', remark: '' })
const q = reactive({
planName: '',
planDirectionId: '',
planCategory: '',
projectForm: '',
status: '',
remark: ''
})
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const rows = ref([])
const loading = ref(false)
const selected = ref([])
// 项目方向 options (复用 manager 侧接口)
const planDirectionOptions = ref([])
async function loadPlanDirectionOptions() {
try {
const { data } = await request.get('/business/specialPlan/options')
planDirectionOptions.value = (data && data.data) || data || []
} catch { planDirectionOptions.value = [] }
}
async function load() {
loading.value = true
try {
const { data } = await bizList('submission', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
const { data } = await bizList('projectPlan', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
rows.value = data?.rows || []
page.total = data?.total || 0
} catch (e) { rows.value = []; page.total = 0 }
@@ -95,8 +125,9 @@ async function load() {
}
function reset() {
q.title = ''
q.direction = ''
q.planName = ''
q.planDirectionId = ''
q.planCategory = ''
q.projectForm = ''
q.status = ''
q.remark = ''
@@ -104,14 +135,13 @@ function reset() {
load()
}
// 原型规则: "待审核和审核通过后, 操作只显示查看按钮, 无法进行修改"
// 字典驱动: 通过 isLocked() 判断, true 表示锁定(待审核 0 / 审核通过 1)
// 锁定的状态 (待审核 '1' / 通过 '2') 不可修改/提交; 未提交 '0' / 拒绝 '3' 可操作
function canModify(row) {
return !isLocked(row.status)
return row.status !== '1' && row.status !== '2'
}
function canSubmit(row) {
// 锁定的不能再次提交; 其他状态(未结题/已结题/已退回/待提交)均可提交进入待审核
return !isLocked(row.status)
// 已通过 '2' 无需再提交; 待审核 '1' 也不允许重复提交; 只有未提交 '0' / 拒绝 '3' 可提交
return row.status === '0' || row.status === '3'
}
function onSelectionChange(arr) { selected.value = arr }
@@ -120,10 +150,10 @@ function onCreate() {
router.push({ path: '/doctor/submission/new' })
}
function onView(row) {
router.push({ path: `/doctor/submission/detail/${row.subId}` })
router.push({ path: `/doctor/submission/detail/${row.planId}` })
}
function onEdit(row) {
router.push({ path: `/doctor/submission/edit/${row.subId}` })
router.push({ path: `/doctor/submission/edit/${row.planId}` })
}
function onDownload(row) {
if (!row.designFileUrl) { ElMessage.warning('该投稿暂无设计文件'); return }
@@ -131,13 +161,12 @@ function onDownload(row) {
}
async function onSubmit(row) {
if (row.status === 'APPROVED') { ElMessage.info('该投稿已审核通过,无需重复提交'); return }
try {
await ElMessageBox.confirm(`确定要提交投稿「${row.title}」吗提交后将进入审核流程`, '提交确认', { type: 'warning' })
await ElMessageBox.confirm(`确定要提交投稿「${row.planName}」吗?提交后将进入审核流程`, '提交确认', { type: 'warning' })
} catch { return }
try {
// 提交后状态变为 PENDING(待审核)
await bizUpdate('submission', { subId: row.subId, status: 'PENDING' })
// 提交后状态变为 '1' (待审核)
await bizUpdate('projectPlan', { planId: row.planId, status: '1' })
ElMessage.success('已提交,等待审核')
load()
} catch (e) { ElMessage.error(e?.msg || '提交失败') }
@@ -146,19 +175,21 @@ async function onSubmit(row) {
async function onBatch() {
if (!selected.value.length) { ElMessage.warning('请先勾选要提交的投稿'); return }
try {
await ElMessageBox.confirm(`确定要批量提交选中的 ${selected.value.length} 条投稿吗`, '批量提交', { type: 'warning' })
await ElMessageBox.confirm(`确定要批量提交选中的 ${selected.value.length} 条投稿吗?`, '批量提交', { type: 'warning' })
} catch { return }
let ok = 0
for (const r of selected.value) {
// 锁定状态 (PENDING 待审核 / APPROVED 审核通过) 跳过提交
if (isLocked(r.status)) continue
try { await bizUpdate('submission', { subId: r.subId, status: 'PENDING' }); ok++ } catch {}
if (!canSubmit(r)) continue
try { await bizUpdate('projectPlan', { planId: r.planId, status: '1' }); ok++ } catch {}
}
ElMessage.success(`已提交 ${ok}`)
load()
}
load()
onMounted(() => {
loadPlanDirectionOptions()
load()
})
</script>
<style scoped>