Files
guoju0808/ry-vue3/src/views/doctor/SubmissionNew.vue
T
郭庆泰 c9fc6ebdb8 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
2026-08-18 00:28:40 +08:00

266 lines
9.4 KiB
Vue

<template>
<div class="submission-new">
<!-- 面包屑 -->
<div class="breadcrumb">
<a @click="goBack">我的项目设计投稿</a> &gt; {{ isEdit ? '修改项目设计投稿' : '新建项目设计投稿' }}
</div>
<!-- 表单卡片 -->
<el-card v-loading="loadingDetail" shadow="never" class="form-card">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
<el-row :gutter="12">
<el-col :span="12">
<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="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>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="项目形式" prop="projectForm">
<el-select v-model="form.projectForm" placeholder="请选择" style="width:100%">
<el-option label="线上" value="线上" />
<el-option label="线下" value="线下" />
<el-option label="线上+线下" value="线上+线下" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<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>
<el-row :gutter="12">
<el-col :span="24">
<el-form-item label="设计文件" prop="designFileUrl">
<el-upload
class="design-uploader"
drag
action="#"
accept=".pdf,.doc,.docx,.png,.jpg,.jpeg"
:show-file-list="true"
:file-list="designFileList"
:before-upload="beforeDesignUpload"
:http-request="uploadDesign"
:on-remove="onDesignRemove"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将设计文件拖到此处,<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">支持 PDF / Word / 图片, 单文件 20MB</div>
</el-upload>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="4" placeholder="请输入备注 (0/500)" maxlength="500" show-word-limit />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 操作按钮 -->
<div class="form-actions">
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
<el-button @click="confirmCancel">取消</el-button>
</div>
</div>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
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()
const userStore = useUserStore()
const formRef = ref(null)
const saving = ref(false)
const loadingDetail = ref(false)
const designFileList = ref([])
const designUploading = ref(false)
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({
planId: null,
planName: '',
planDirectionId: null,
projectForm: '',
planCategory: '',
designFileUrl: '',
status: '0',
remark: ''
})
const rules = {
planName: [{ required: true, message: '请输入策划方案名称', trigger: 'blur' }],
planDirectionId: [{ required: true, message: '请选择项目方向', trigger: 'change' }],
projectForm: [{ required: true, message: '请选择项目形式', trigger: 'change' }],
planCategory: [{ required: true, message: '请选择项目类别', trigger: 'change' }],
designFileUrl: [{ required: true, message: '请上传设计文件', trigger: 'change' }]
}
function beforeDesignUpload(file) {
const max = 20 * 1024 * 1024
if (file.size > max) { ElMessage.error('设计文件不能超过 20MB'); return false }
const accept = ['.pdf', '.doc', '.docx', '.png', '.jpg', '.jpeg']
const ext = '.' + (file.name.split('.').pop() || '').toLowerCase()
if (!accept.includes(ext)) { ElMessage.error('仅支持 PDF / Word / 图片格式'); return false }
return true
}
async function uploadDesign(opts) {
const file = opts.file
designUploading.value = true
try {
const url = await uploadToOss(file, 'ry8080/submission/design/')
form.designFileUrl = url
designFileList.value = [{ name: file.name, url }]
ElMessage.success('设计文件上传成功')
} catch (e) {
ElMessage.error('上传失败: ' + (e?.message || e))
designFileList.value = []
} finally {
designUploading.value = false
}
}
function onDesignRemove() {
form.designFileUrl = ''
designFileList.value = []
}
async function loadDetail() {
const planId = route.params.planId
if (!planId) return
loadingDetail.value = true
try {
const res = await request.get(`/business/projectPlan/${planId}`)
if (res?.code === 200 && res.data) {
const d = res.data
form.planId = d.planId
form.planName = d.planName || ''
form.planDirectionId = d.planDirectionId ?? null
form.projectForm = d.projectForm || ''
form.planCategory = d.planCategory || ''
form.designFileUrl = d.designFileUrl || ''
form.status = d.status && String(d.status).trim() ? String(d.status) : '0'
form.remark = d.remark || ''
if (d.designFileUrl) {
const name = d.designFileUrl.split('/').pop() || `${d.planName || '设计文件'}.pdf`
designFileList.value = [{ name, url: d.designFileUrl }]
}
} else {
ElMessage.error(res?.msg || '加载失败')
goBack()
}
} catch (e) {
console.error('[submission-new/edit] loadDetail failed', e)
ElMessage.error(e?.msg || '加载失败')
goBack()
} finally {
loadingDetail.value = false
}
}
function goBack() {
router.push({ path: '/doctor/submissions', query: { _t: Date.now() } })
}
function confirmCancel() {
const hasContent = form.planName || form.planDirectionId || form.projectForm || form.planCategory || form.designFileUrl || form.remark
if (!hasContent) { goBack(); return }
ElMessageBox.confirm('确定取消新建?未保存的内容将丢失', '提示', { type: 'warning' })
.then(() => goBack())
.catch(() => {})
}
async function onSave() {
try {
await formRef.value.validate()
} catch (e) {
ElMessage.warning('请填写必填项')
return
}
saving.value = true
try {
let payload = { ...form }
if (!isEdit) {
// 新建: 强制 status='0' (未提交), submitter 后端兜底
payload.status = '0'
payload.submitterId = userStore.user?.userId || null
} else if (!payload.status) {
payload.status = '0'
}
if (isEdit) {
await bizUpdate('projectPlan', payload)
} else {
await bizAdd('projectPlan', payload)
}
ElMessage.success(isEdit ? '修改成功' : '新建成功')
goBack()
} catch (e) {
console.error('[submission-new] save failed', e)
ElMessage.error(e?.msg || (isEdit ? '修改失败' : '保存失败'))
} finally {
saving.value = false
}
}
onMounted(() => {
loadPlanDirectionOptions()
if (isEdit) loadDetail()
})
</script>
<style scoped>
.submission-new { max-width: 1000px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 16px; }
.breadcrumb a { color: var(--brand-primary); cursor: pointer; text-decoration: none; }
.breadcrumb a:hover { color: var(--brand-primary-deep, var(--brand-primary)); }
.form-card { border-radius: 6px; }
:deep(.form-card .el-card__body) { padding: 24px 28px; }
.form-actions { margin-top: 16px; display: flex; gap: 8px; }
: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, var(--brand-primary)); border-color: var(--brand-primary-deep, var(--brand-primary)); }
:deep(.el-button) { border-radius: 4px; }
:deep(.el-input__wrapper), :deep(.el-textarea__inner) { border-radius: 4px; }
/* 设计文件上传控件: 占满表单行宽 */
.design-uploader { width: 100%; }
.design-uploader :deep(.el-upload) { width: 100%; }
.design-uploader :deep(.el-upload-dragger) { width: 100%; }
</style>