- mapper: selectFields 加 COALESCE(bp.name, u.user_name), LEFT JOIN biz_person (uk_person_user_id 防笛卡尔积) - mapper: insert 补 7 字段 (plan_name/plan_direction/plan_category/project_form/design_file_url/is_settled/project_no), plan_name NOT NULL 缺默认 - SQL: 紧急迁移脚本 add submitter_id + idx + biz_person UNIQUE (ALTER, 不 DROP) - SubmissionNew: page-card + breadcrumb + max-width 1200px, accept=.pdf/.png/.jpg/.jpeg, __silentError 防双 toast - SubmissionNew: el-upload drag 模式 A (对齐 Meetings.vue, 删 design-uploader 自定义 CSS) - SubmissionDetail: 删状态/投稿人/投稿时间/审计, 改 Preview 组件展示设计文件
244 lines
8.3 KiB
Vue
244 lines
8.3 KiB
Vue
<template>
|
|
<div v-loading="loadingDetail" class="page-card doctor-submission-new">
|
|
<div class="breadcrumb">
|
|
<span>首页 / 我的项目设计投稿 / {{ isEdit ? '修改项目设计投稿' : '新建项目设计投稿' }}</span>
|
|
</div>
|
|
|
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="submission-form">
|
|
<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
|
|
drag
|
|
action="#"
|
|
accept=".pdf,.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 / 图片, 单文件 ≤ 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-item>
|
|
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
|
<el-button @click="confirmCancel">取消</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</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', '.png', '.jpg', '.jpeg']
|
|
const ext = '.' + (file.name.split('.').pop() || '').toLowerCase()
|
|
if (!accept.includes(ext)) { ElMessage.error('仅支持 PDF / 图片格式'); 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, { __silentError: true })
|
|
} else {
|
|
await bizAdd('projectPlan', payload, { __silentError: true })
|
|
}
|
|
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>
|
|
/* 整页面板 (与 admin/ExpertNew.vue 一致: max-width 1200px) */
|
|
.doctor-submission-new { max-width: 1200px; padding: 16px; }
|
|
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
|
</style> |