页合并 (admin/manager 共用, route.path 角色感知): - expert: Experts.vue + ExpertNew.vue (list/new/edit/view 一套) - sponsor-orgs: SponsorOrgs.vue - sponsor-people: SponsorPeople.vue + SponsorPersonNew.vue + SponsorPersonDetail.vue - executor-orgs: ExecutorOrgs.vue - executor-people: ExecutorPeople.vue + ExecutorPersonNew.vue + ExecutorPersonDetail.vue - 给 SponsorPeople / ExecutorPeople op 列补上 查看/编辑 按钮 (潜在 bug 修复) - 详情弹窗统一用 el-descriptions 风格 (替代 admin 旧版 ElMessageBox.alert) 后端: - BizSignController + BizSignServiceImpl + PdfService (劳务签署 PDF 流程) - BizPublicityIntentController (公示意向: 支持/执行) - BizPublicitySupportIntent / BizPublicityExecutionIntent ExportVo - BizMeetingAttendee 字段合并 (bank_region 替代 bankProvince/bankCity) - BizExpert 字段合并 (id_card_attachments CSV, bank_region) - Excel 导入模板精简 (开户行 1 列) 前端: - doctor/SignFill + SignContract + SignSuccess (劳务签署 3 页流程) - ImportResultDialog 通用组件 - IdCardUploader 重构 (单 v-model:idCardAttachments, CSV 格式) - AreaCascader 调整 - Login.vue + AdminLayout.vue + PortalShell.vue + Home.vue 适配 DB: 字段合并 (id_card_front/back → id_card_attachments, bank_province/city → bank_region)
287 lines
10 KiB
Vue
287 lines
10 KiB
Vue
<template>
|
|
<div class="sign-fill">
|
|
<div v-loading="loading">
|
|
<el-form :model="form" :rules="rules" ref="formRef" label-position="top" class="sign-fill-form">
|
|
<!-- 参会人信息 -->
|
|
<div class="section-title">参会人信息</div>
|
|
<el-form-item label="手机号" prop="phone">
|
|
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
|
</el-form-item>
|
|
<el-form-item label="姓名" prop="name">
|
|
<el-input v-model="form.name" placeholder="请输入姓名" maxlength="50" />
|
|
</el-form-item>
|
|
<el-form-item label="工作单位" prop="workUnit">
|
|
<el-input v-model="form.workUnit" placeholder="请输入工作单位" maxlength="200" />
|
|
</el-form-item>
|
|
<el-form-item label="科室" prop="department">
|
|
<el-input v-model="form.department" placeholder="请输入科室" maxlength="100" />
|
|
</el-form-item>
|
|
|
|
<!-- 银行账户 -->
|
|
<div class="section-title">银行账户</div>
|
|
<el-form-item label="账户名称(持卡人姓名)" prop="accountName">
|
|
<el-input v-model="form.accountName" placeholder="请输入持卡人姓名" maxlength="100" />
|
|
</el-form-item>
|
|
<el-form-item label="身份证号" prop="idCard">
|
|
<el-input v-model="form.idCard" placeholder="请输入身份证号" maxlength="50" />
|
|
</el-form-item>
|
|
<el-form-item label="银行卡号" prop="bankCard">
|
|
<el-input v-model="form.bankCard" placeholder="请输入银行卡号" maxlength="30" />
|
|
</el-form-item>
|
|
<el-form-item label="银行名称" prop="bankName">
|
|
<el-input v-model="form.bankName" placeholder="请输入银行名称" maxlength="100" />
|
|
</el-form-item>
|
|
<el-form-item label="开户银行(支行)" prop="bankBranch">
|
|
<el-input v-model="form.bankBranch" placeholder="请输入开户行名称" maxlength="100" />
|
|
</el-form-item>
|
|
<el-form-item label="开户银行地址" prop="bankRegion">
|
|
<AreaCascader
|
|
v-model="form.bankRegion"
|
|
format="string"
|
|
join-sep="/"
|
|
:max-level="2"
|
|
placeholder="请选择省/市"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="银行详细地址" prop="bankAddress">
|
|
<el-input v-model="form.bankAddress" placeholder="如: 海淀支行 / XX 路 1 号" maxlength="200" />
|
|
</el-form-item>
|
|
<el-form-item label="身份证附件">
|
|
<IdCardUploader
|
|
v-model="form.idCardAttachments"
|
|
:dir="'ry8080/idcard/'"
|
|
/>
|
|
</el-form-item>
|
|
|
|
<!-- 劳务信息 (只读, admin 预填) -->
|
|
<div class="section-title">本次劳务</div>
|
|
<el-form-item label="劳务形式">
|
|
<div class="labor-form-cell">
|
|
<el-checkbox-group v-model="laborFormSelected">
|
|
<el-checkbox v-for="opt in laborFormOptions" :key="opt.value" :value="opt.value" :label="opt.label" />
|
|
</el-checkbox-group>
|
|
<div v-if="laborFormSelected.includes('__other__')" class="other-input">
|
|
<el-input v-model="laborFormOther" placeholder="请输入其他劳务形式" maxlength="100" />
|
|
</div>
|
|
</div>
|
|
</el-form-item>
|
|
<el-form-item label="医务职称">
|
|
<el-select v-model="form.title" placeholder="请选择" clearable filterable style="width: 240px">
|
|
<el-option v-for="t in titleOptions" :key="t" :value="t" :label="t" />
|
|
<template #footer>
|
|
<el-input v-if="showTitleOther" v-model="titleOther" placeholder="其他职称" maxlength="50" @blur="form.title = titleOther" />
|
|
<el-button v-else link type="primary" @click="showTitleOther = true">+ 其他</el-button>
|
|
</template>
|
|
</el-select>
|
|
</el-form-item>
|
|
|
|
<el-form-item>
|
|
<el-button type="primary" :loading="submitting" @click="onSubmit">下一步</el-button>
|
|
<el-button @click="goBack">取消</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { ElMessage } from 'element-plus'
|
|
import { getSignInfo, saveSignProfile } from '@/api/business/sign'
|
|
import IdCardUploader from '@/components/IdCardUploader.vue'
|
|
import AreaCascader from '@/components/AreaCascader.vue'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const attendeeId = computed(() => Number(route.query.attendeeId) || null)
|
|
|
|
const loading = ref(false)
|
|
const submitting = ref(false)
|
|
const formRef = ref(null)
|
|
|
|
// 劳务形式 (checkboxOther 多选)
|
|
const laborFormOptions = ref([])
|
|
const laborFormSelected = ref([])
|
|
const laborFormOther = ref('')
|
|
|
|
// 职称 (selectOther, 含 "其他" 输入)
|
|
const titleOptions = ref([])
|
|
const showTitleOther = ref(false)
|
|
const titleOther = ref('')
|
|
|
|
const form = reactive({
|
|
name: '', phone: '', workUnit: '', department: '',
|
|
accountName: '', idCard: '', bankCard: '', bankName: '', bankBranch: '',
|
|
bankRegion: '', // AreaCascader v-model (string 格式, "省/市")
|
|
bankAddress: '', // 银行详细地址
|
|
idCardAttachments: '', // 身份证正反面 URL CSV (IdCardUploader)
|
|
title: ''
|
|
})
|
|
const feeDisplay = reactive({ preTax: '—', tax: '—', fee: '—' })
|
|
|
|
const rules = {
|
|
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
|
phone: [
|
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
|
{ pattern: /^1[0-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
|
],
|
|
workUnit: [{ required: true, message: '请输入工作单位', trigger: 'blur' }],
|
|
department: [{ required: true, message: '请输入科室', trigger: 'blur' }],
|
|
accountName: [{ required: true, message: '请输入持卡人姓名', trigger: 'blur' }],
|
|
idCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
|
|
bankCard: [{ required: true, message: '请输入银行卡号', trigger: 'blur' }],
|
|
bankName: [{ required: true, message: '请输入银行名称', trigger: 'blur' }],
|
|
bankBranch: [{ required: true, message: '请输入开户行支行', trigger: 'blur' }]
|
|
}
|
|
|
|
async function load() {
|
|
if (!attendeeId.value) {
|
|
ElMessage.error('参数缺失, 请从工作台进入')
|
|
router.push('/doctor/home')
|
|
return
|
|
}
|
|
loading.value = true
|
|
try {
|
|
const { data } = await getSignInfo(attendeeId.value)
|
|
// 预填 (current 优先, 否则用 defaults)
|
|
const cur = data.current || {}
|
|
const def = data.defaults || {}
|
|
Object.assign(form, {
|
|
name: cur.name || def.name || '',
|
|
phone: cur.phone || def.phone || '',
|
|
workUnit: cur.workUnit || def.workUnit || '',
|
|
department: cur.department || def.department || '',
|
|
accountName: cur.accountName || '',
|
|
idCard: cur.idCard || def.idCard || '',
|
|
bankCard: cur.bankCard || def.bankCard || '',
|
|
bankName: cur.bankName || def.bankName || '',
|
|
bankBranch: cur.bankBranch || '',
|
|
bankRegion: cur.bankRegion || def.bankRegion || '',
|
|
bankAddress: cur.bankAddress || def.bankAddress || '',
|
|
idCardAttachments: cur.idCardAttachments || '',
|
|
title: cur.title || def.title || ''
|
|
})
|
|
if (cur.laborForm) {
|
|
try {
|
|
const parsed = JSON.parse(cur.laborForm)
|
|
if (Array.isArray(parsed)) {
|
|
laborFormSelected.value = parsed.filter(x => x !== '__other__' && !x.startsWith('__other__:'))
|
|
const otherItem = parsed.find(x => x === '__other__' || x.startsWith('__other__:'))
|
|
if (otherItem) {
|
|
laborFormSelected.value.push('__other__')
|
|
laborFormOther.value = otherItem.startsWith('__other__:') ? otherItem.substring('__other__:'.length) : ''
|
|
}
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
feeDisplay.preTax = cur.feePreTax || '—'
|
|
feeDisplay.tax = cur.tax || '—'
|
|
feeDisplay.fee = cur.fee || '—'
|
|
|
|
laborFormOptions.value = (data.laborFormOptions || []).map(o => ({ value: o.value, label: o.label }))
|
|
titleOptions.value = data.titleOptions || []
|
|
} catch (e) {
|
|
ElMessage.error(e?.msg || '加载失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function onSubmit() {
|
|
const valid = await formRef.value.validate().catch(() => false)
|
|
if (!valid) return
|
|
if (!form.idCardAttachments || form.idCardAttachments.split(',').every(s => !s.trim())) {
|
|
ElMessage.warning('请上传身份证正反面')
|
|
return
|
|
}
|
|
// 劳务形式序列化 (JSON 数组字符串, 含 __other__ + 自定义内容)
|
|
let laborForm = ''
|
|
if (laborFormSelected.value.length) {
|
|
const arr = laborFormSelected.value.map(x => {
|
|
if (x === '__other__' && laborFormOther.value) {
|
|
return '__other__:' + laborFormOther.value
|
|
}
|
|
return x
|
|
})
|
|
laborForm = JSON.stringify(arr)
|
|
}
|
|
|
|
submitting.value = true
|
|
try {
|
|
await saveSignProfile(attendeeId.value, {
|
|
...form,
|
|
laborForm
|
|
})
|
|
ElMessage.success('已保存, 即将进入签署页')
|
|
setTimeout(() => {
|
|
router.push('/doctor/sign-contract?attendeeId=' + attendeeId.value)
|
|
}, 500)
|
|
} catch (e) {
|
|
ElMessage.error(e?.msg || '保存失败')
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
function goBack() {
|
|
router.push('/doctor/home')
|
|
}
|
|
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* 手机端全屏表单 (无 navbar / 无 page-card) */
|
|
.sign-fill { padding: 16px; }
|
|
|
|
/* 章节标题: 左边 3px 蓝竖线 */
|
|
.section-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #1a1a1a;
|
|
margin: 16px 0 12px;
|
|
padding-left: 10px;
|
|
border-left: 3px solid var(--brand-primary);
|
|
line-height: 1;
|
|
}
|
|
|
|
/* 开户地址 3 列布局 */
|
|
.region-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
width: 100%;
|
|
}
|
|
.region-row .el-input { flex: 1; }
|
|
|
|
/* 劳务形式多选 + 其他输入 */
|
|
.labor-form-cell { width: 100%; }
|
|
.labor-form-cell .other-input { margin-top: 8px; }
|
|
|
|
/* mobile 上下结构: label 在 input 上方 */
|
|
.sign-fill-form :deep(.el-form-item__label) {
|
|
display: block;
|
|
float: none;
|
|
text-align: left;
|
|
width: auto !important;
|
|
padding: 0 0 6px 0;
|
|
font-size: 13px;
|
|
color: #303133;
|
|
}
|
|
.sign-fill-form :deep(.el-form-item__content) {
|
|
margin-left: 0 !important;
|
|
}
|
|
.sign-fill-form :deep(.el-form-item) {
|
|
margin-bottom: 16px;
|
|
}
|
|
/* 身份证附件 2 个框排成 1 行 */
|
|
.sign-fill-form :deep(.ht-image-upload-content) {
|
|
display: inline-block;
|
|
}
|
|
.sign-fill-form :deep(.ht-image-upload-content + .ht-image-upload-content) {
|
|
margin-left: 12px;
|
|
}
|
|
/* mobile 端地址 3 个输入框 1 行 */
|
|
.region-row { display: flex; gap: 8px; }
|
|
.region-row .el-input { flex: 1; }
|
|
</style> |