refactor: 合并 biz_support_unit/biz_execution_unit/biz_service_org 为 biz_org, 删除 2 张孤儿表, 改 biz_person.work_unit → org_id FK
主要改动: - SQL: biz_support_unit → biz_org, 加 org_type, 加 business_nature; 删 biz_execution_unit, biz_service_org - SQL: biz_project.support_unit_id/name + service_org_id/name → org_id/name/type - SQL: biz_meeting.support_unit_name → org_name - SQL: biz_person.work_unit → org_id (FK) - 后端: 新 BizOrg entity/mapper/service/controller - 后端: BizProject/BizMeeting 字段重命名 - 后端: 删 12 个 BizSupportUnit/BizExecutionUnit/BizServiceOrg Java 文件 - 后端: BizPersonImportVO.workUnit → orgName (导入时查 biz_org 取 org_id) - 后端: BizAuthController.registerExecutor 完整实现 (原 registerSupplier stub) - 前端: 新 admin/Orgs.vue + manager/Orgs.vue (原 SupportUnits) - 前端: RegisterExecutor.vue (原 RegisterSupplier, 单页 2 步) - 前端: sponsor/executor/manager 多个文件 workUnit → orgId/orgName 重命名 - 前端: 统一 supplier → executor, 业务命名 sponsor(赞助方) / executor(执行方=供应商) - 前端: 全工程 execution → executor (company type / role / person unit_type)
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div class="page-wrap">
|
||||
<div class="page-card">
|
||||
<div class="page-title">专家注册</div>
|
||||
|
||||
<!-- Stepper (原型: 1 基本信息 → 2 注册成功) -->
|
||||
<div class="stepper">
|
||||
<div class="step">
|
||||
<div class="step-circle" :class="{ active: step === 0, done: step > 0 }">1</div>
|
||||
<div class="step-label" :class="{ active: step === 0, done: step > 0 }">基本信息</div>
|
||||
</div>
|
||||
<div class="step-line" :class="{ done: step > 0 }"></div>
|
||||
<div class="step">
|
||||
<div class="step-circle" :class="{ active: step === 1 }">2</div>
|
||||
<div class="step-label" :class="{ active: step === 1 }">注册成功</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单 (原型 1:1 抄: 11 个字段) -->
|
||||
<el-form v-if="step === 0" :model="form" :rules="rules" ref="formRef" label-width="100px" style="max-width:760px;margin:24px auto;">
|
||||
<el-form-item label="姓名" prop="realName">
|
||||
<el-input v-model="form.realName" placeholder="请输入您的真实姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作单位" prop="workUnit">
|
||||
<el-input v-model="form.workUnit" placeholder="请输入工作单位(医院全称)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="科室" prop="department">
|
||||
<DoctorDeptSelect v-model="form.department" placeholder="请输入所在科室" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职称" prop="doctorTitle">
|
||||
<DoctorTitleSelect v-model="form.doctorTitle" placeholder="请输入职称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号码" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="短信验证码" prop="code">
|
||||
<div class="sms-row">
|
||||
<el-input v-model="form.code" placeholder="请输入收到的验证码" maxlength="6" />
|
||||
<el-button :disabled="smsCountdown > 0 || !form.phone" @click="sendCode">
|
||||
{{ smsCountdown > 0 ? `${smsCountdown}s 后重新获取` : '获取验证码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录密码" prop="password">
|
||||
<el-input v-model="form.password" type="password" show-password placeholder="请输入登录密码(6-20位)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="form.confirmPassword" type="password" show-password placeholder="请再次输入登录密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="执业证书/证明">
|
||||
<OssImageUploader v-model="form.licenseCertUrl" dir="ry8080/cert/license/" placeholder="+" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职称证明">
|
||||
<OssImageUploader v-model="form.titleCertUrl" dir="ry8080/cert/title/" placeholder="+" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="agreed">已阅读并接受 <a href="javascript:void(0)" @click="showAgreement = true">《隐私保护条款及授权书》</a></el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :disabled="!agreed" @click="onSubmit" :loading="submitting" style="width: 100%;">同意协议并注册</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 注册成功 -->
|
||||
<div v-else class="success-page">
|
||||
<el-result icon="success" title="注册成功" sub-title="请等待管理员审核,审核通过后即可登录">
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="$router.push('/login')">前往登录</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐私协议弹窗 -->
|
||||
<el-dialog v-model="showAgreement" title="隐私保护条款及授权书" width="600px">
|
||||
<div class="agreement-content">
|
||||
<p>1. 我们仅在为您提供专家注册、项目评审等服务的过程中,收集您的姓名、手机号、工作单位、职称等必要信息。</p>
|
||||
<p>2. 您的个人信息将严格保密,仅用于项目流程内部使用,不会向任何第三方披露。</p>
|
||||
<p>3. 您有权随时查看、更正、删除您的个人信息。</p>
|
||||
<p>4. 注册即表示您同意上述条款。</p>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { registerExpert } from '@/api/public'
|
||||
import request from '@/utils/request'
|
||||
import DoctorDeptSelect from '@/components/DoctorDeptSelect.vue'
|
||||
import DoctorTitleSelect from '@/components/DoctorTitleSelect.vue'
|
||||
import OssImageUploader from '@/components/OssImageUploader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const step = ref(0)
|
||||
const submitting = ref(false)
|
||||
|
||||
const formRef = ref()
|
||||
const form = reactive({
|
||||
realName: '',
|
||||
workUnit: '',
|
||||
department: '',
|
||||
doctorTitle: '',
|
||||
phone: '',
|
||||
code: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
licenseCertUrl: '',
|
||||
titleCertUrl: ''
|
||||
})
|
||||
const rules = {
|
||||
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
workUnit: [{ required: true, message: '请输入工作单位', trigger: 'blur' }],
|
||||
department: [{ required: true, message: '请选择科室', trigger: 'change' }],
|
||||
doctorTitle: [{ required: true, message: '请选择职称', trigger: 'change' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
{ pattern: /^1\d{10}$/, message: '手机号格式错误', trigger: 'blur' }
|
||||
],
|
||||
code: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }],
|
||||
password: [
|
||||
{ required: true, message: '请输入登录密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度 6-20 位', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入登录密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, cb) => {
|
||||
if (value !== form.password) cb(new Error('两次密码不一致'))
|
||||
else cb()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const agreed = ref(false)
|
||||
const showAgreement = ref(false)
|
||||
|
||||
const smsCountdown = ref(0)
|
||||
let smsTimer = null
|
||||
const smsUuid = ref('') // 短信验证码 uuid (verify 时回传)
|
||||
async function sendCode() {
|
||||
if (!/^1\d{10}$/.test(form.phone)) {
|
||||
return ElMessage.warning('请先输入正确的手机号')
|
||||
}
|
||||
try {
|
||||
const resp = await request({ url: '/business/auth/registerSendSms', method: 'post', data: { phone: form.phone } })
|
||||
smsUuid.value = resp?.data || ''
|
||||
ElMessage.success('验证码已发送')
|
||||
smsCountdown.value = 60
|
||||
smsTimer = setInterval(() => {
|
||||
smsCountdown.value--
|
||||
if (smsCountdown.value <= 0) {
|
||||
clearInterval(smsTimer)
|
||||
smsTimer = null
|
||||
}
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
ElMessage.warning(e?.msg || '验证码发送失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
await formRef.value.validate()
|
||||
submitting.value = true
|
||||
try {
|
||||
await registerExpert({ ...form, uuid: smsUuid.value })
|
||||
ElMessage.success('注册成功')
|
||||
step.value = 1
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '注册失败')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stepper { display: flex; align-items: center; justify-content: center; margin: 24px 0 8px; }
|
||||
.step { display: flex; align-items: center; gap: 8px; }
|
||||
.step-circle { width: 28px; height: 28px; border-radius: 50%; background: #fff; border: 1px solid #d9d9d9; color: #909399; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 500; }
|
||||
.step-circle.active { background: #1890ff; border-color: #1890ff; color: #fff; }
|
||||
.step-circle.done { background: #52c41a; border-color: #52c41a; color: #fff; }
|
||||
.step-label { font-size: 14px; color: #909399; }
|
||||
.step-label.active { color: #1890ff; font-weight: 500; }
|
||||
.step-label.done { color: #52c41a; }
|
||||
.step-line { width: 80px; height: 1px; background: #d9d9d9; margin: 0 12px; }
|
||||
.step-line.done { background: #52c41a; }
|
||||
.sms-row { display: flex; gap: 12px; width: 100%; }
|
||||
.sms-row .el-input { flex: 1; }
|
||||
.agreement-content { line-height: 1.8; color: #606266; }
|
||||
.agreement-content p { margin-bottom: 12px; }
|
||||
.success-page { padding: 60px 0; text-align: center; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user