Files
guoju0808/ry-vue3/src/views/auth/RegisterExpert.vue
T
郭庆泰 cf5790229a 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)
2026-08-15 12:41:10 +08:00

199 lines
8.2 KiB
Vue

<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>