hn-hlw-app/pages/member/detail.vue

510 lines
14 KiB
Vue
Raw Normal View History

2026-07-27 11:26:39 +08:00
<template>
<view class="container" :class="{ 'container--elder': elderMode }">
<template v-if="logined">
<view class="main">
<scroll-view scroll-y="true" class="main-scroll">
<view class="card">
<uni-list>
<uni-list-item direction="column">
<template v-slot:body>
<view class="info-item">
<text class="item-label">姓名</text>
2026-07-31 16:21:41 +08:00
<input v-model="form.name" :maxlength="20" class="item-value" type="text" placeholder="请输入姓名" />
2026-07-27 11:26:39 +08:00
</view>
<view class="info-item">
<text class="item-label">证件类型</text>
<text class="item-value">身份证</text>
</view>
2026-08-06 18:26:58 +08:00
<view v-if="isYbQrcode" class="info-item">
<text class="item-label">证件号</text>
<text class="item-value">{{ maskCertNo }}</text>
</view>
<view v-else class="info-item">
2026-07-27 11:26:39 +08:00
<text class="item-label">证件号</text>
<input v-model="form.certNo" :maxlength="18" class="item-value" type="idcard"
placeholder="请输入证件号" />
</view>
<view class="info-item">
<text class="item-label">手机号</text>
<input v-model="form.mobile" :maxlength="11" class="item-value" type="number"
placeholder="请输入手机号" />
</view>
<view class="info-item address-item">
<view class="address-header">
<text class="item-label">地址</text>
<text class="choose-address" @click="chooseAddress">选择地址</text>
</view>
<textarea v-model="form.address" :maxlength="200" class="address-textarea"
placeholder="请输入详细地址"></textarea>
</view>
</template>
</uni-list-item>
</uni-list>
</view>
</scroll-view>
</view>
<view class="save-footer">
2026-08-04 16:45:11 +08:00
<view class="footer-actions">
<view v-if="canDelete" class="delete-btn" :class="{ 'delete-btn--disabled': deleting }"
@click="removePatient">
{{ deleting ? '删除中...' : '删除' }}
</view>
<view class="save-btn" :class="{ 'save-btn--disabled': loading || deleting }" @click="confirm">
{{ loading ? '保存中...' : '保存' }}
</view>
2026-07-27 11:26:39 +08:00
</view>
</view>
<view class="safearea-bottom"></view>
</template>
<re-login v-else height="80vh" />
</view>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import useUser from '@/hooks/useUser';
2026-07-31 16:21:41 +08:00
import {
addHisCustomer,
addHlwPatient,
2026-08-04 16:45:11 +08:00
deleteHlwPatient,
2026-07-31 16:21:41 +08:00
getHisCustomer,
getHlwPatient,
updateHlwPatient,
} from '@/utils/api';
import { get, remove, set } from '@/utils/cache';
2026-07-27 11:26:39 +08:00
import { isChinaId } from '@/utils/validator';
import { toast } from '@/utils/widget';
import reLogin from '@/components/re-login.vue';
const id = ref('');
2026-07-31 16:21:41 +08:00
const type = ref('');
const feeType = ref('');
2026-07-27 11:26:39 +08:00
const loading = ref(false);
2026-08-04 16:45:11 +08:00
const deleting = ref(false);
2026-07-27 11:26:39 +08:00
const elderMode = ref(false);
const loadedPatientKey = ref('');
2026-08-04 16:45:11 +08:00
const loadedPatientIsSelf = ref(null);
2026-07-31 16:21:41 +08:00
const scannedCard = ref(null);
2026-07-27 11:26:39 +08:00
const form = ref({
name: '',
certNo: '',
mobile: '',
address: '',
});
2026-07-31 16:21:41 +08:00
const { userInfo, logined, setTemporaryPatient } = useUser();
2026-07-27 11:26:39 +08:00
const accountId = computed(() => userInfo.value && userInfo.value.userId ? userInfo.value.userId : '');
2026-07-31 16:21:41 +08:00
const isYbQrcode = computed(() => type.value === 'ybQrcode');
2026-08-04 16:45:11 +08:00
const canDelete = computed(() => (
Boolean(id.value)
&& loadedPatientKey.value === `${accountId.value}:${id.value}`
&& loadedPatientIsSelf.value === false
));
2026-08-06 18:26:58 +08:00
const maskCertNo = computed(() => {
if (isYbQrcode.value && form.value.certNo) {
const pre = form.value.certNo.slice(0, 4);
const post = form.value.certNo.slice(-4);
return `${pre}****${post}`;
}
return '';
});
2026-07-27 11:26:39 +08:00
function validateForm() {
if (!form.value.name.trim()) {
toast('姓名不能为空');
return false;
}
if (!form.value.certNo.trim()) {
toast('证件号不能为空');
return false;
}
if (!isChinaId(form.value.certNo.trim())[0]) {
toast('请输入有效的身份证号');
return false;
}
if (!/^1[3-9]\d{9}$/.test(form.value.mobile.trim())) {
toast('请输入有效的手机号');
return false;
}
return true;
}
function chooseAddress() {
uni.chooseLocation({
success: location => {
const address = typeof location.address === 'string' ? location.address.trim() : '';
const name = typeof location.name === 'string' ? location.name.trim() : '';
form.value.address = address && name && !address.endsWith(name)
? `${address}${name}`
: address || name;
},
fail: error => {
if (!error || !/cancel/i.test(error.errMsg || '')) {
toast('选择地址失败');
}
},
});
}
async function confirm() {
2026-08-04 16:45:11 +08:00
if (loading.value || deleting.value || !validateForm()) return;
2026-07-27 11:26:39 +08:00
if (!accountId.value) {
toast('请先登录');
return;
}
loading.value = true;
try {
2026-07-31 16:21:41 +08:00
if (isYbQrcode.value) {
await saveYbQrcodePatient();
return;
}
2026-07-27 11:26:39 +08:00
const payload = {
accountId: accountId.value,
name: form.value.name.trim(),
certNo: form.value.certNo.trim(),
mobile: form.value.mobile.trim(),
address: form.value.address.trim(),
};
const api = id.value ? updateHlwPatient : addHlwPatient;
const res = await api(id.value ? { ...payload, id: id.value } : payload);
if (!res || !res.success) {
toast((res && res.message) || '保存失败');
return;
}
toast('保存成功');
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
} else {
uni.redirectTo({ url: '/pages/member/list' });
}
} catch (error) {
toast(error.message || error || '保存失败');
} finally {
loading.value = false;
}
}
2026-08-04 16:45:11 +08:00
function confirmDelete() {
return new Promise(resolve => {
uni.showModal({
title: '删除就诊人',
content: '删除后无法恢复,确定删除该就诊人档案吗?',
confirmText: '删除',
confirmColor: '#d14343',
success: result => resolve(Boolean(result.confirm)),
fail: () => resolve(false),
});
});
}
async function removePatient() {
if (loading.value || deleting.value || !canDelete.value) return;
const confirmed = await confirmDelete();
if (!confirmed || !canDelete.value) return;
deleting.value = true;
try {
const res = await deleteHlwPatient({
accountId: accountId.value,
id: id.value,
});
if (!res || !res.success) {
toast((res && res.message) || '删除失败');
return;
}
toast('删除成功');
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
} else {
uni.redirectTo({ url: '/pages/member/list' });
}
} catch (error) {
toast(error.message || error || '删除失败');
} finally {
deleting.value = false;
}
}
2026-07-31 16:21:41 +08:00
async function saveYbQrcodePatient() {
if (!scannedCard.value) {
toast('医保扫码信息已失效,请重新扫码');
return;
}
const idCard = form.value.certNo.trim();
const res = await addHisCustomer({
idCard,
name: form.value.name.trim(),
mobile: form.value.mobile.trim(),
address: form.value.address.trim(),
mdtrt_cert_type: '03',
mdtrt_cert_no: scannedCard.value.CardNum,
card_sn: scannedCard.value.CardIDCode,
});
const patients = Array.isArray(res && res.list) ? res.list : [];
const hisArchive = patients.find(patient => patient.socialno === idCard) || patients[0] || null;
if (!res || !res.success || !hisArchive) {
toast((res && res.message) || '医保建档失败');
return;
}
hisArchive.socialno = hisArchive.socialno || idCard;
hisArchive.tmbxx = Array.isArray(res.tmbxx) ? res.tmbxx : [];
set('current-his-archive', hisArchive);
setTemporaryPatient({
name: form.value.name.trim(),
certNo: idCard,
mobile: form.value.mobile.trim(),
address: form.value.address.trim(),
});
uni.redirectTo({
url: `/pages/consult/disease-description?socialno=${idCard}&feeType=${feeType.value}`,
});
}
async function loadScannedCard() {
const card = get('current-scanned-card');
remove('current-scanned-card');
if (!card || !card.Name || !card.IDNum) {
toast('医保扫码信息已失效,请重新扫码');
uni.navigateBack();
return;
}
scannedCard.value = card;
form.value.name = card.Name;
form.value.certNo = card.IDNum;
loading.value = true;
try {
const res = await getHisCustomer(card.IDNum);
const patients = Array.isArray(res.list) ? res.list : [];
const patient = patients.find(item => item.socialno === card.IDNum);
if (patient) {
form.value.mobile = patient.tel || patient.mobile || '';
form.value.address = patient.address || '';
}
} catch (error) {
toast(error.message || error || '查询已有档案失败');
}
loading.value = false;
}
2026-07-27 11:26:39 +08:00
async function loadPatient() {
if (!id.value || !accountId.value) return;
const patientKey = `${accountId.value}:${id.value}`;
if (loadedPatientKey.value === patientKey) return;
2026-08-04 16:45:11 +08:00
loadedPatientIsSelf.value = null;
2026-07-27 11:26:39 +08:00
loading.value = true;
try {
const res = await getHlwPatient(accountId.value, id.value);
if (!res || !res.success || !res.data) {
toast((res && res.message) || '查询就诊人失败');
return;
}
form.value = {
name: res.data.name || '',
certNo: res.data.certNo || '',
mobile: res.data.mobile || '',
address: res.data.address || '',
};
2026-08-04 16:45:11 +08:00
const patientCertNo = typeof res.data.certNo === 'string' ? res.data.certNo.trim() : '';
const userCertNo = typeof userInfo.value.certNo === 'string' ? userInfo.value.certNo.trim() : '';
loadedPatientIsSelf.value = userCertNo ? patientCertNo === userCertNo : null;
2026-07-27 11:26:39 +08:00
loadedPatientKey.value = patientKey;
} catch (error) {
toast(error.message || error || '查询就诊人失败');
} finally {
loading.value = false;
}
}
onLoad(options => {
id.value = options.id || '';
2026-07-31 16:21:41 +08:00
type.value = options.type || '';
feeType.value = options.feeType || '';
2026-08-06 18:26:58 +08:00
if (type.value === 'ybQrcode') {
uni.setNavigationBarTitle({
title: '医保授权身份确认',
});
} else {
uni.setNavigationBarTitle({
title: isYbQrcode.value ? '医保建档' : (id.value ? '编辑就诊人' : '新增就诊人'),
});
}
2026-07-27 11:26:39 +08:00
const savedElderMode = uni.getStorageSync('elderMode');
if (savedElderMode !== null && savedElderMode !== undefined) {
elderMode.value = savedElderMode;
}
2026-07-31 16:21:41 +08:00
if (isYbQrcode.value) {
loadScannedCard();
}
2026-07-27 11:26:39 +08:00
});
watch([id, accountId], loadPatient, { immediate: true });
</script>
<style lang="scss">
.container {
display: flex;
flex-direction: column;
background-color: #f5f5f5;
height: 100vh;
--font-scale: 1;
--spacing-scale: 1;
--border-radius-scale: 1;
@at-root &--elder {
--font-scale: 1.4;
--spacing-scale: 1.2;
--border-radius-scale: 1.25;
}
}
.main {
position: relative;
flex-grow: 1;
}
.main-scroll {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.card {
margin: calc(30rpx * var(--spacing-scale));
border-radius: calc(16rpx * var(--border-radius-scale));
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.info-item {
background: #fff;
display: flex;
justify-content: space-between;
align-items: center;
padding: calc(24rpx * var(--spacing-scale)) calc(30rpx * var(--spacing-scale));
border-bottom: 1rpx solid #eee;
min-height: calc(80rpx * var(--spacing-scale));
&:last-child {
border-bottom: none;
}
}
.address-item {
display: block;
}
.address-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-bottom: calc(20rpx * var(--spacing-scale));
}
.choose-address {
color: $theme-brown-primary;
font-size: calc(30rpx * var(--font-scale));
font-weight: 500;
}
.address-textarea {
box-sizing: border-box;
width: 100%;
height: calc(160rpx * var(--spacing-scale));
padding: calc(20rpx * var(--spacing-scale));
border-radius: calc(8rpx * var(--border-radius-scale));
background: #f7f7f7;
color: #333;
font-size: calc(30rpx * var(--font-scale));
line-height: 1.5;
}
.item-label {
color: #666;
font-size: calc(30rpx * var(--font-scale));
width: calc(200rpx * var(--spacing-scale));
font-weight: 500;
}
.item-value {
color: #333;
font-size: calc(30rpx * var(--font-scale));
font-weight: 500;
flex: 1;
text-align: right;
.container--elder & {
font-weight: 600;
}
}
.save-footer {
padding: calc(24rpx * var(--spacing-scale)) calc(30rpx * var(--spacing-scale));
background: white;
}
2026-08-04 16:45:11 +08:00
.footer-actions {
display: flex;
gap: calc(20rpx * var(--spacing-scale));
}
2026-07-27 11:26:39 +08:00
.save-btn {
2026-08-04 16:45:11 +08:00
flex: 1;
2026-07-27 11:26:39 +08:00
height: calc(80rpx * var(--spacing-scale));
background: $theme-brown-primary;
color: white;
font-size: calc(28rpx * var(--font-scale));
border-radius: calc(16rpx * var(--border-radius-scale));
text-align: center;
line-height: calc(80rpx * var(--spacing-scale));
font-weight: 500;
&--disabled {
opacity: 0.6;
}
}
2026-08-04 16:45:11 +08:00
.delete-btn {
box-sizing: border-box;
width: calc(220rpx * var(--spacing-scale));
height: calc(80rpx * var(--spacing-scale));
border: 2rpx solid #d14343;
color: #d14343;
background: #fff;
font-size: calc(28rpx * var(--font-scale));
border-radius: calc(16rpx * var(--border-radius-scale));
text-align: center;
line-height: calc(76rpx * var(--spacing-scale));
font-weight: 500;
&--disabled {
opacity: 0.6;
}
}
2026-07-27 11:26:39 +08:00
.safearea-bottom {
height: env(safe-area-inset-bottom);
flex-shrink: 0;
}
</style>