瀏覽代碼

save

master
leiyun 2 天之前
父節點
當前提交
9be8ec1929
共有 6 個文件被更改,包括 193 次插入20 次删除
  1. +10
    -4
      pages/index/index.vue
  2. +45
    -0
      pages/myinfo/myinfo.vue
  3. +5
    -1
      pages/profile/profile.vue
  4. +46
    -2
      pages/sign/sign.vue
  5. +82
    -13
      pages/verify/verify.vue
  6. +5
    -0
      utils/request.js

+ 10
- 4
pages/index/index.vue 查看文件

@@ -106,10 +106,16 @@ const handleJoin = () => {
})
return
}
// 审核通过 -> 不可点击
if (patientStatus.value === 1) return
// 待审核 -> 不可点击
if (patientStatus.value === 0) return
// 审核通过 -> 跳转到个人中心
if (patientStatus.value === 1) {
uni.switchTab({ url: '/pages/profile/profile' })
return
}
// 待审核 -> 跳转到个人中心
if (patientStatus.value === 0) {
uni.switchTab({ url: '/pages/profile/profile' })
return
}
// 已拒绝 -> 跳转资料页重新提交
if (patientStatus.value === 2) {
uni.navigateTo({ url: '/pages/myinfo/myinfo' })


+ 45
- 0
pages/myinfo/myinfo.vue 查看文件

@@ -104,6 +104,17 @@
</view>
<view class="sign-btn primary" v-else @tap="goSign('privacy')">去签署</view>
</view>
<view class="sign-item" v-if="isMinor">
<view class="sign-left">
<text class="sign-name">个人信息处理同意书(监护人)</text>
<text :class="['sign-status', signedPrivacyJhr ? 'signed' : '']">{{ signedPrivacyJhr ? '已签署' : '未签署' }}</text>
</view>
<view class="sign-btns" v-if="signedPrivacyJhr">
<view class="sign-btn view" @tap="previewSign('privacy_jhr')">查看</view>
<view class="sign-btn resign" @tap="goSign('privacy_jhr')">重签</view>
</view>
<view class="sign-btn primary" v-else @tap="goSign('privacy_jhr')">去签署</view>
</view>
<view class="sign-item">
<view class="sign-left">
<text class="sign-name">声明与承诺</text>
@@ -157,6 +168,7 @@ const form = reactive({
documents: [],
sign_income: '',
sign_privacy: '',
sign_privacy_jhr: '',
sign_promise: '',
income_amount: ''
})
@@ -212,8 +224,23 @@ const maskedPhone = computed(() => {
// 签署状态:form 中有新签的 URL 或 info 中有已保存的 URL
const signedIncome = computed(() => form.sign_income || info.value.sign_income)
const signedPrivacy = computed(() => form.sign_privacy || info.value.sign_privacy)
const signedPrivacyJhr = computed(() => form.sign_privacy_jhr || info.value.sign_privacy_jhr)
const signedPromise = computed(() => form.sign_promise || info.value.sign_promise)

// 判断是否未成年(从身份证号解析年龄)
const isMinor = computed(() => {
const idCard = info.value.id_card || ''
if (idCard.length !== 18) return false
const birthYear = parseInt(idCard.substring(6, 10))
const birthMonth = parseInt(idCard.substring(10, 12))
const birthDay = parseInt(idCard.substring(12, 14))
const now = new Date()
let age = now.getFullYear() - birthYear
const monthDiff = (now.getMonth() + 1) - birthMonth
if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < birthDay)) age--
return age < 18
})

const regionText = computed(() => {
const parts = []
if (form.province_code) {
@@ -247,6 +274,8 @@ const onSignResult = (data) => {
if (data.amount) form.income_amount = data.amount
} else if (data.type === 'privacy') {
form.sign_privacy = data.url
} else if (data.type === 'privacy_jhr') {
form.sign_privacy_jhr = data.url
} else if (data.type === 'promise') {
form.sign_promise = data.url
}
@@ -271,6 +300,7 @@ const previewSign = (type) => {
const urlMap = {
income: form.sign_income || info.value.sign_income,
privacy: form.sign_privacy || info.value.sign_privacy,
privacy_jhr: form.sign_privacy_jhr || info.value.sign_privacy_jhr,
promise: form.sign_promise || info.value.sign_promise
}
const url = urlMap[type]
@@ -335,6 +365,7 @@ const loadInfo = async () => {
form.documents = res.data.documents || []
form.sign_income = res.data.sign_income || ''
form.sign_privacy = res.data.sign_privacy || ''
form.sign_privacy_jhr = res.data.sign_privacy_jhr || ''
form.sign_promise = res.data.sign_promise || ''
form.income_amount = res.data.income_amount || ''
// 设置地区选择器默认索引
@@ -385,6 +416,19 @@ const handleSubmit = async () => {
if (!form.address.trim()) {
return uni.showToast({ title: '请填写详细地址', icon: 'none' })
}
// 签名校验:全部必须签
if (!signedIncome.value) {
return uni.showToast({ title: '请签署个人可支配收入声明', icon: 'none' })
}
if (!signedPrivacy.value) {
return uni.showToast({ title: '请签署个人信息处理同意书', icon: 'none' })
}
if (isMinor.value && !signedPrivacyJhr.value) {
return uni.showToast({ title: '请签署个人信息处理同意书(监护人)', icon: 'none' })
}
if (!signedPromise.value) {
return uni.showToast({ title: '请签署声明与承诺', icon: 'none' })
}
// 已通过状态需要二次确认
if (info.value.status === 1) {
showConfirmPopup.value = true
@@ -410,6 +454,7 @@ const doSubmit = async () => {
documents: form.documents,
sign_income: form.sign_income,
sign_privacy: form.sign_privacy,
sign_privacy_jhr: form.sign_privacy_jhr,
sign_promise: form.sign_promise,
income_amount: form.income_amount || null,
// #ifdef MP-WEIXIN


+ 5
- 1
pages/profile/profile.vue 查看文件

@@ -53,7 +53,7 @@
<text class="text">实名认证</text>
<text v-if="isAuthed" class="extra authed">已认证</text>
<text v-else class="extra link">去认证</text>
<u-icon name="arrow-right" size="16" color="#c0c4cc" />
<u-icon v-if="!isAuthed" name="arrow-right" size="16" color="#c0c4cc" />
</view>
</view>

@@ -180,6 +180,10 @@ const goMessage = () => {

const goVerify = () => {
if (!checkLogin()) return
if (isAuthed.value) {
uni.showToast({ title: '已认证', icon: 'none' })
return
}
uni.navigateTo({ url: '/pages/verify/verify' })
}



+ 46
- 2
pages/sign/sign.vue 查看文件

@@ -11,7 +11,24 @@
<view class="form-label">请填写您的个人年可支配收入(元)</view>
<view class="amount-wrap">
<text class="amount-prefix">¥</text>
<u-input v-model="incomeAmount" type="number" placeholder="请输入金额" border="none" />
<u-input v-model="incomeAmount" type="number" placeholder="请输入金额" border="none" maxlength="7" />
</view>
</view>

<!-- 监护人信息(仅privacy_jhr类型) -->
<view class="section" v-if="signType === 'privacy_jhr'">
<view class="form-label">监护人信息</view>
<view class="form-row">
<text class="row-label">监护人姓名</text>
<u-input v-model="guardianName" placeholder="请输入监护人姓名" border="surround" maxlength="20" />
</view>
<view class="form-row">
<text class="row-label">监护人身份证</text>
<u-input v-model="guardianIdCard" placeholder="请输入监护人身份证号" border="surround" maxlength="18" />
</view>
<view class="form-row">
<text class="row-label">与患者关系</text>
<u-input v-model="guardianRelation" placeholder="如:父亲、母亲" border="surround" maxlength="10" />
</view>
</view>

@@ -46,6 +63,9 @@ const signType = ref('')
const docTitle = ref('')
const docContent = ref('')
const incomeAmount = ref('')
const guardianName = ref('')
const guardianIdCard = ref('')
const guardianRelation = ref('')
const signImageUrl = ref('')
const submitting = ref(false)

@@ -83,13 +103,22 @@ const confirmSign = async () => {
if (signType.value === 'income' && (!incomeAmount.value || Number(incomeAmount.value) <= 0)) {
return uni.showToast({ title: '请填写有效的收入金额', icon: 'none' })
}
if (signType.value === 'privacy_jhr') {
if (!guardianName.value.trim()) return uni.showToast({ title: '请输入监护人姓名', icon: 'none' })
if (!guardianIdCard.value.trim()) return uni.showToast({ title: '请输入监护人身份证号', icon: 'none' })
if (!/^\d{17}[\dXx]$/.test(guardianIdCard.value)) return uni.showToast({ title: '监护人身份证格式不正确', icon: 'none' })
if (!guardianRelation.value.trim()) return uni.showToast({ title: '请输入与患者关系', icon: 'none' })
}

submitting.value = true
try {
const params = {
type: signType.value,
signImage: signImageUrl.value,
amount: signType.value === 'income' ? incomeAmount.value : undefined
amount: signType.value === 'income' ? incomeAmount.value : undefined,
guardianName: signType.value === 'privacy_jhr' ? guardianName.value.trim() : undefined,
guardianIdCard: signType.value === 'privacy_jhr' ? guardianIdCard.value.trim() : undefined,
guardianRelation: signType.value === 'privacy_jhr' ? guardianRelation.value.trim() : undefined
}
const res = await post('/api/mp/sign', params)

@@ -162,6 +191,21 @@ const confirmSign = async () => {
background: #f8f8f8;
}

.form-row {
margin-bottom: 20rpx;

&:last-child {
margin-bottom: 0;
}
}

.row-label {
font-size: 26rpx;
color: #555;
margin-bottom: 12rpx;
display: block;
}

.sign-label {
font-size: 28rpx;
color: #333;


+ 82
- 13
pages/verify/verify.vue 查看文件

@@ -60,17 +60,17 @@

<view class="form-row">
<text class="form-label">证件姓名</text>
<input class="form-input" v-model="form.name" placeholder="上传身份证后自动识别" />
<input class="form-input" v-model="form.name" :placeholder="certType === 'child' ? '请输入姓名' : '上传身份证后自动识别'" />
</view>
<view class="form-row">
<text class="form-label">证件号码</text>
<input class="form-input" v-model="form.idNo" placeholder="上传身份证后自动识别" />
<input class="form-input" v-model="form.idNo" :placeholder="certType === 'child' ? '请输入身份证' : '上传身份证后自动识别'" />
</view>
<view class="form-row">
<view class="form-row" v-if="certType !== 'child'">
<text class="form-label">发证机关</text>
<input class="form-input" v-model="form.authority" placeholder="上传身份证后自动识别" />
</view>
<view class="form-row border-none">
<view class="form-row border-none" v-if="certType !== 'child'">
<text class="form-label">有效期限</text>
<input class="form-input" v-model="form.validity" placeholder="上传身份证后自动识别" />
</view>
@@ -100,6 +100,18 @@
<view class="submit-bar">
<button class="submit-btn" @tap="handleSubmit">提交认证</button>
</view>

<!-- 绑定已有患者确认弹窗 -->
<u-popup :show="showBindPopup" mode="center" round="12" :safeAreaInsetBottom="false" @close="showBindPopup = false">
<view class="confirm-popup">
<view class="confirm-title">提示</view>
<view class="confirm-content">检测到患者信息已存在({{ bindPatientInfo.name }} {{ bindPatientInfo.phone }}),是否确认绑定该患者信息?</view>
<view class="confirm-btns">
<u-button text="取消" size="normal" :plain="true" shape="circle" @click="showBindPopup = false" />
<u-button text="确认绑定" size="normal" color="#0E63E3" shape="circle" @click="doSubmit(true)" />
</view>
</view>
</u-popup>
</view>
</template>

@@ -116,8 +128,20 @@ const placeholderBack = CDN_BASE + '/images/patient/user/idcard-back.webp?v=1'
const certType = ref('idcard')
const countdown = ref(0)
const submitting = ref(false)
const showBindPopup = ref(false)
const bindPatientInfo = ref({ name: '', phone: '' })
let timer = null

// 身份证号格式校验(18位,含校验位)
const validateIdCard = (id) => {
if (!/^\d{17}[\dXx]$/.test(id)) return false
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
let sum = 0
for (let i = 0; i < 17; i++) sum += parseInt(id[i]) * weights[i]
return checkCodes[sum % 11] === id[17].toUpperCase()
}

// 证件类型映射: certType -> idCardType (后端字段)
const certTypeMap = { idcard: 1, child: 2, temp: 3 }

@@ -193,12 +217,6 @@ const doOcrFront = async (imageUrl) => {
const d = res.data
form.name = d.name || ''
form.idNo = d.idNum || ''
// 从身份证号解析性别和生日
if (d.idNum && d.idNum.length === 18) {
const genderNum = parseInt(d.idNum.charAt(16))
form.gender = genderNum % 2 === 1 ? '男' : '女'
form.birthday = `${d.idNum.substring(6, 10)}-${d.idNum.substring(10, 12)}-${d.idNum.substring(12, 14)}`
}
uni.hideLoading()
} catch (e) {
uni.hideLoading()
@@ -281,13 +299,28 @@ const handleSubmit = async () => {
}
if (!form.name) return uni.showToast({ title: '请输入证件姓名', icon: 'none' })
if (!form.idNo) return uni.showToast({ title: '请输入证件号码', icon: 'none' })
// 身份证格式校验
if (!validateIdCard(form.idNo)) return uni.showToast({ title: '身份证号格式不正确', icon: 'none' })

// 从身份证号解析性别和生日(提交时统一解析,确保一定有值)
if (form.idNo.length === 18) {
const genderNum = parseInt(form.idNo.charAt(16))
form.gender = genderNum % 2 === 1 ? '男' : '女'
form.birthday = `${form.idNo.substring(6, 10)}-${form.idNo.substring(10, 12)}-${form.idNo.substring(12, 14)}`
}

if (!form.phone || form.phone.length !== 11) return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
if (!form.smsCode || form.smsCode.length !== 6) return uni.showToast({ title: '请输入6位验证码', icon: 'none' })

await doSubmit(false)
}

const doSubmit = async (confirmBind) => {
showBindPopup.value = false
submitting.value = true
try {
await post('/api/mp/authSubmit', {
idCardType,
idCardType: certTypeMap[certType.value],
idCardFront: form.frontImage,
idCardBack: form.backImage,
photo: form.childImage,
@@ -298,7 +331,8 @@ const handleSubmit = async () => {
issuingAuthority: form.authority,
validPeriod: form.validity,
mobile: form.phone,
code: form.smsCode
code: form.smsCode,
confirmBind: confirmBind || undefined
})

// 刷新用户信息缓存
@@ -310,7 +344,16 @@ const handleSubmit = async () => {
uni.showToast({ title: '认证成功', icon: 'success' })
setTimeout(() => { uni.navigateBack() }, 1500)
} catch (e) {
if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
if (e && e.code === 1010) {
// 患者已存在,弹出确认绑定弹窗
bindPatientInfo.value = {
name: e.data?.patientName || '',
phone: e.data?.patientPhone || ''
}
showBindPopup.value = true
} else if (e && e.msg) {
uni.showToast({ title: e.msg, icon: 'none' })
}
} finally {
submitting.value = false
}
@@ -611,4 +654,30 @@ const handleSubmit = async () => {
opacity: 0.85;
}
}

.confirm-popup {
padding: 48rpx 40rpx 40rpx;
width: 560rpx;
}

.confirm-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 28rpx;
}

.confirm-content {
font-size: 28rpx;
color: #666;
line-height: 1.7;
text-align: center;
margin-bottom: 40rpx;
}

.confirm-btns {
display: flex;
gap: 24rpx;
}
</style>

+ 5
- 0
utils/request.js 查看文件

@@ -49,6 +49,11 @@ export function initRequest(http) {
return Promise.reject({ code, msg: msg || '请先登录' })
}

// 1010: 需要前端确认的场景,不自动弹toast,由业务层处理
if (code === 1010) {
return Promise.reject({ code, data, msg: msg || '' })
}

uni.showToast({ title: msg || '请求失败', icon: 'none' })
return Promise.reject({ code, msg: msg || '请求失败' })
},


Loading…
取消
儲存