This commit is contained in:
Your Name
2026-05-29 09:28:27 +08:00
parent 0f974be7a1
commit 7b46204454
7 changed files with 1037 additions and 39 deletions
+267 -26
View File
@@ -374,6 +374,53 @@
<text>数据仅供参考请遵医嘱</text>
</view>
<!-- 无就诊卡授权手机号后可录入 -->
<view v-if="phoneGateVisible" class="input-modal-mask" @click="closePhoneGate">
<view class="input-modal phone-gate-modal" @click.stop>
<view class="input-modal-grip" />
<view class="input-modal-head">
<view class="input-modal-title-wrap">
<text class="input-modal-title">授权手机号</text>
<text class="input-modal-sub">绑定手机号后即可记录血糖无需先填写就诊卡</text>
</view>
<view class="input-modal-close" @click="closePhoneGate">
<text class="input-modal-close-icon">×</text>
</view>
</view>
<view class="phone-gate-body">
<text class="phone-gate-tip">微信仅提供手机号请选择性别以便写入就诊档案</text>
<view class="phone-gate-gender">
<text class="phone-gate-gender-label">性别</text>
<view class="phone-gate-gender-row">
<view
class="phone-gate-gender-opt"
:class="{ active: phoneGateSex === 1 }"
@click="phoneGateSex = 1"
>
<text></text>
</view>
<view
class="phone-gate-gender-opt"
:class="{ active: phoneGateSex === 2 }"
@click="phoneGateSex = 2"
>
<text></text>
</view>
</view>
</view>
<button
class="phone-gate-btn"
open-type="getPhoneNumber"
:loading="phoneBinding"
:disabled="phoneBinding || !phoneGateSex"
@getphonenumber="onPhoneGateAuthorize"
>
微信授权手机号
</button>
</view>
</view>
</view>
<!-- 录入今日数据弹层 -->
<view v-if="inputForm.visible" class="input-modal-mask" @click="closeInputForm">
<view class="input-modal" @click.stop>
@@ -528,7 +575,11 @@ const gender = ref(0)
/** 登录 / 就诊卡门禁校验中(避免未登录时闪屏) */
const authChecking = ref(false)
let gateRedirected = false
const userMobile = ref('')
const phoneGateVisible = ref(false)
const phoneGateSex = ref(0)
const phoneBinding = ref(false)
const loading = ref(false)
const cardsLoading = ref(false)
@@ -1020,15 +1071,6 @@ async function ensureLoggedIn() {
return authSessionPromise
}
function redirectToCardEntry() {
if (gateRedirected) return
gateRedirected = true
const returnUrl = encodeURIComponent('/tongji/pages/index')
uni.redirectTo({
url: `/pages/Card/edit_card?add=1&returnUrl=${returnUrl}`
})
}
// ============ 就诊卡 ============
async function fetchCardList() {
if (!hasAuthToken()) return []
@@ -1062,6 +1104,145 @@ async function applyCard(card) {
gender.value = Number(card.gender) || 0
}
function readUserMobile() {
try {
const userData = uni.getStorageSync('userData') || {}
userMobile.value = String(userData.mobile || userData.phone || '').trim()
} catch (e) {
userMobile.value = ''
}
return userMobile.value
}
function syncPhoneGateSexFromUser() {
try {
const userData = uni.getStorageSync('userData') || {}
const sex = Number(userData.sex)
phoneGateSex.value = sex === 1 || sex === 2 ? sex : 0
} catch (e) {
phoneGateSex.value = 0
}
}
function openPhoneGate() {
syncPhoneGateSexFromUser()
phoneGateVisible.value = true
}
async function refreshUserMobile() {
try {
const res = await proxy.apiUrl({ url: '/api/user/info', method: 'POST' }, false)
if (res && res.code === 1 && res.data) {
uni.setStorageSync('userData', res.data)
userMobile.value = String(res.data.mobile || res.data.phone || '').trim()
}
} catch (e) {
/* 静默 */
}
return userMobile.value
}
async function ensureDailyContextByPhone(sex) {
if (diagnosisId.value) return true
const payload = {}
const sexVal = sex ?? phoneGateSex.value
if (sexVal === 1 || sexVal === 2) {
payload.sex = sexVal
}
const res = await proxy.apiUrl({
url: '/api/tcm/dailyEnsurePhoneContext',
method: 'POST',
data: payload
}, false)
if (res && res.code === 1 && res.data && res.data.diagnosis_id) {
await applyCard({
id: res.data.diagnosis_id,
patient_id: res.data.patient_id,
patient_name: res.data.patient_name || '',
age: res.data.age || 0,
gender: res.data.gender || 0
})
if (res.data.created) {
await fetchCardList()
}
return true
}
if (res && res.data && res.data.need_mobile) {
return 'need_mobile'
}
showUserToast(formatUserMessage(res?.msg, '无法开始录入'))
return false
}
async function ensureCanRecordGlucose() {
if (diagnosisId.value) return true
readUserMobile()
if (!userMobile.value) {
openPhoneGate()
return false
}
const ok = await ensureDailyContextByPhone()
if (ok === 'need_mobile') {
openPhoneGate()
return false
}
return !!ok
}
function closePhoneGate() {
if (phoneBinding.value) return
phoneGateVisible.value = false
}
async function onPhoneGateAuthorize(e) {
const detail = e?.detail || {}
if (detail.errMsg && !String(detail.errMsg).includes('ok')) {
showUserToast('需要授权手机号后才能录入血糖')
return
}
const code = detail.code
if (!code) {
showUserToast('获取手机号失败,请重试')
return
}
if (phoneGateSex.value !== 1 && phoneGateSex.value !== 2) {
showUserToast('请先选择性别')
return
}
phoneBinding.value = true
try {
const res = await proxy.apiUrl({
url: '/api/user/getMobileByMnp',
method: 'POST',
data: { code, sex: phoneGateSex.value }
}, false)
if (!res || res.code !== 1) {
showUserToast(formatUserMessage(res?.msg, '绑定手机号失败'))
return
}
await refreshUserMobile()
phoneGateVisible.value = false
const ok = await ensureDailyContextByPhone(phoneGateSex.value)
if (ok === true) {
await openInputForm(true)
} else if (ok === 'need_mobile') {
openPhoneGate()
}
} catch (err) {
showUserToast('网络异常,请稍后再试')
} finally {
phoneBinding.value = false
}
}
async function onSelectCard(card) {
if (!card || Number(card.id) === Number(diagnosisId.value)) return
bloodRecords.value = []
@@ -1150,10 +1331,10 @@ function onRefresh() {
fetchAll()
}
function goMorePage() {
async function goMorePage() {
if (!diagnosisId.value) {
showUserToast('请先选择就诊卡')
return
const ok = await ensureCanRecordGlucose()
if (!ok || !diagnosisId.value) return
}
uni.navigateTo({ url: '/tongji/pages/more' })
}
@@ -1384,16 +1565,20 @@ function onInputField(field, e) {
inputForm.value[field] = val
}
async function openInputForm() {
if (!diagnosisId.value) {
showUserToast('请先选择就诊卡')
return
}
async function openInputForm(skipEnsure = false) {
const token = uni.getStorageSync('token')
if (!token) {
showUserToast('请先登录后再录入')
return
}
if (!skipEnsure) {
const can = await ensureCanRecordGlucose()
if (!can) return
}
if (!diagnosisId.value) {
showUserToast('请先授权手机号')
return
}
// 微信小程序 canvas 是原生组件、z-index 无法被弹层覆盖,
// 这里通过 v-if 卸载它;下次显示时 selectorQuery 拿到的会是新节点,
// 因此先清空缓存的 ctx / node,避免下次 ensureCanvas 复用旧引用。
@@ -1434,7 +1619,7 @@ function closeInputForm() {
}
/** 任一录入弹层打开时需卸载 canvas(小程序原生组件会盖住普通 view)*/
const recordOverlayOpen = computed(() => inputForm.value.visible)
const recordOverlayOpen = computed(() => inputForm.value.visible || phoneGateVisible.value)
async function submitInputForm() {
if (inputForm.value.submitting || inputForm.value.loading) return
@@ -2153,7 +2338,6 @@ async function bootstrap(options) {
}
isInviteViewer.value = false
gateRedirected = false
authChecking.value = true
// 登录完成前不写 diagnosisId,避免模板/子流程提前打业务接口
@@ -2192,15 +2376,16 @@ async function bootstrap(options) {
if (!diagnosisId.value || !patientId.value) {
readUserContext()
}
if (!diagnosisId.value) {
authChecking.value = false
redirectToCardEntry()
return
readUserMobile()
if (!diagnosisId.value && userMobile.value) {
await ensureDailyContextByPhone()
}
}
authChecking.value = false
await fetchAll()
if (diagnosisId.value) {
await fetchAll()
}
}
onLoad((options) => {
@@ -2208,7 +2393,7 @@ onLoad((options) => {
})
onShow(() => {
if (authChecking.value || gateRedirected) return
if (authChecking.value) return
nextTick(() => scheduleChartRedraw())
})
@@ -4110,6 +4295,62 @@ async function onFamilyLike() {
line-height: 1;
font-weight: 700;
}
.phone-gate-body {
padding: 32rpx;
}
.phone-gate-tip {
display: block;
font-size: 28rpx;
color: var(--primary-container);
line-height: 1.6;
margin-bottom: 24rpx;
}
.phone-gate-gender {
margin-bottom: 32rpx;
}
.phone-gate-gender-label {
display: block;
font-size: 28rpx;
font-weight: 700;
color: var(--primary);
margin-bottom: 16rpx;
}
.phone-gate-gender-row {
display: flex;
gap: 20rpx;
}
.phone-gate-gender-opt {
flex: 1;
min-height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 20rpx;
background: #ffffff;
border: 2rpx solid rgba(32, 78, 43, 0.25);
color: var(--primary-container);
font-size: 32rpx;
font-weight: 600;
}
.phone-gate-gender-opt.active {
border-color: var(--primary);
color: var(--primary);
box-shadow: inset 0 0 0 1rpx var(--primary);
}
.phone-gate-btn {
width: 100%;
min-height: 96rpx;
line-height: 96rpx;
border-radius: 20rpx;
background: #ffffff;
color: var(--primary);
font-size: 32rpx;
font-weight: 700;
border: 2rpx solid var(--primary);
}
.phone-gate-btn::after {
border: none;
}
.input-modal-loading {
min-height: 520rpx;
padding: 120rpx 0;