页面UI更新

This commit is contained in:
李琦
2026-05-30 16:54:01 +08:00
parent 6ec1b72063
commit 63ea420c03
24 changed files with 2287 additions and 151 deletions

View File

@@ -0,0 +1,539 @@
<template>
<view class="u-upload" v-if="!disabled">
<view
v-if="showUploadList"
class="u-list-item u-preview-wrap"
v-for="(item, index) in lists"
:key="index"
:style="{
width: $u.addUnit(width),
height: $u.addUnit(height)
}"
>
<view
v-if="deletable"
class="u-delete-icon"
@tap.stop="deleteItem(index)"
:style="{
background: delBgColor
}"
>
<u-icon class="u-icon" :name="delIcon" size="20" :color="delColor"></u-icon>
</view>
<u-line-progress
v-if="showProgress && item.progress > 0 && item.progress != 100 && !item.error"
:show-percent="false"
height="16"
class="u-progress"
:percent="item.progress"
></u-line-progress>
<view @tap.stop="retry(index)" v-if="item.error" class="u-error-btn">点击重试</view>
<image @tap.stop="doPreviewImage(item.url || item.path, index)" class="u-preview-image" v-if="!item.isImage" :src="item.url || item.path" :mode="imageMode"></image>
</view>
<slot name="file" :file="lists"></slot>
<view style="display: inline-block;" @tap="selectFile" v-if="maxCount > lists.length">
<slot name="addBtn"></slot>
<view
v-if="!customBtn"
class="u-list-item u-add-wrap"
hover-class="u-add-wrap__hover"
hover-stay-time="150"
:style="{
width: $u.addUnit(width),
height: $u.addUnit(height)
}"
>
<u-icon name="plus" class="u-add-btn" size="40"></u-icon>
<view class="u-add-tips">{{ uploadText }}</view>
</view>
</view>
</view>
</template>
<script>
import { prepareImagePath } from '@/utils/image-compress.js';
export default {
name: 'c-upload',
props: {
showUploadList: {
type: Boolean,
default: true
},
action: {
type: String,
default: ''
},
maxCount: {
type: [String, Number],
default: 52
},
showProgress: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
imageMode: {
type: String,
default: 'aspectFill'
},
header: {
type: Object,
default() {
return {};
}
},
formData: {
type: Object,
default() {
return {};
}
},
name: {
type: String,
default: 'file'
},
sizeType: {
type: Array,
default() {
return ['original', 'compressed'];
}
},
sourceType: {
type: Array,
default() {
return ['album', 'camera'];
}
},
previewFullImage: {
type: Boolean,
default: true
},
multiple: {
type: Boolean,
default: true
},
deletable: {
type: Boolean,
default: true
},
maxSize: {
type: [String, Number],
default: Number.MAX_VALUE
},
fileList: {
type: Array,
default() {
return [];
}
},
uploadText: {
type: String,
default: '选择图片'
},
autoUpload: {
type: Boolean,
default: true
},
showTips: {
type: Boolean,
default: true
},
customBtn: {
type: Boolean,
default: false
},
width: {
type: [String, Number],
default: 200
},
height: {
type: [String, Number],
default: 200
},
delBgColor: {
type: String,
default: '#fa3534'
},
delColor: {
type: String,
default: '#ffffff'
},
delIcon: {
type: String,
default: 'close'
},
toJson: {
type: Boolean,
default: true
},
beforeUpload: {
type: Function,
default: null
},
beforeRemove: {
type: Function,
default: null
},
limitType: {
type: Array,
default() {
return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'image'];
}
},
index: {
type: [Number, String],
default: ''
}
},
data() {
return {
lists: [],
isInCount: true,
uploading: false
};
},
watch: {
fileList: {
immediate: true,
handler(val) {
val.map(value => {
let tmp = this.lists.some(val => {
return val.url == value.url;
})
!tmp && this.lists.push({ url: value.url, error: false, progress: 100 });
});
}
},
lists(n) {
this.$emit('on-list-change', n, this.index);
}
},
methods: {
clear() {
this.lists = [];
},
reUpload() {
this.uploadFile();
},
selectFile() {
if (this.disabled) return;
const { maxCount, multiple, maxSize, sizeType, lists, sourceType } = this;
const newMaxCount = maxCount - lists.length;
uni.chooseImage({
count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
sourceType: sourceType,
sizeType,
success: async (res) => {
const listOldLength = this.lists.length;
for (let index = 0; index < res.tempFiles.length; index++) {
const val = res.tempFiles[index];
if (!this.checkFileExt(val)) continue;
if (!multiple && index >= 1) continue;
if (val.size > maxSize) {
this.$emit('on-oversize', val, this.lists, this.index);
this.showToast('超出允许的文件大小');
continue;
}
if (maxCount <= lists.length) {
this.$emit('on-exceed', val, this.lists, this.index);
this.showToast('超出最大允许的文件个数');
break;
}
try {
const prepared = await prepareImagePath({
path: val.path,
size: val.size,
});
if (!prepared) continue;
lists.push({
url: prepared.path,
progress: 0,
error: false,
file: {
...val,
path: prepared.path,
size: prepared.size,
},
});
} catch (error) {
this.$emit('on-choose-fail', error);
this.showToast('图片处理失败');
}
}
this.$emit('on-choose-complete', this.lists, this.index);
if (this.autoUpload) this.uploadFile(listOldLength);
},
fail: (error) => {
this.$emit('on-choose-fail', error);
}
});
},
showToast(message, force = false) {
if (this.showTips || force) {
uni.showToast({
title: message,
icon: 'none'
});
}
},
upload() {
this.uploadFile();
},
retry(index) {
this.lists[index].progress = 0;
this.lists[index].error = false;
this.lists[index].response = null;
uni.showLoading({
title: '重新上传'
});
this.uploadFile(index);
},
async uploadFile(index = 0) {
if (this.disabled) return;
if (this.uploading) return;
if (index >= this.lists.length) {
this.$emit('on-uploaded', this.lists, this.index);
return;
}
if (this.lists[index].progress == 100) {
if (this.autoUpload == false) this.uploadFile(index + 1);
return;
}
if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
let beforeResponse = this.beforeUpload.bind(this.$u.$parent.call(this))(index, this.lists);
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
await beforeResponse.then(res => {
}).catch(err => {
return this.uploadFile(index + 1);
})
} else if (beforeResponse === false) {
return this.uploadFile(index + 1);
}
}
if (!this.action) {
this.showToast('请配置上传地址', true);
return;
}
this.lists[index].error = false;
this.uploading = true;
const task = uni.uploadFile({
url: this.action,
filePath: this.lists[index].url,
name: this.name,
formData: this.formData,
header: this.header,
// #ifdef MP-ALIPAY
fileType:'image',
// #endif
success: res => {
let data = this.toJson && this.$u.test.jsonString(res.data) ? JSON.parse(res.data) : res.data;
if (![200, 201, 204].includes(res.statusCode)) {
this.uploadError(index, data);
} else {
this.lists[index].response = data;
this.lists[index].progress = 100;
this.lists[index].error = false;
this.$emit('on-success', data, index, this.lists, this.index);
}
},
fail: e => {
this.uploadError(index, e);
},
complete: res => {
uni.hideLoading();
this.uploading = false;
this.uploadFile(index + 1);
this.$emit('on-change', res, index, this.lists, this.index);
}
});
task.onProgressUpdate(res => {
if (res.progress > 0) {
this.lists[index].progress = res.progress;
this.$emit('on-progress', res, index, this.lists, this.index);
}
});
},
uploadError(index, err) {
this.lists[index].progress = 0;
this.lists[index].error = true;
this.lists[index].response = null;
this.$emit('on-error', err, index, this.lists, this.index);
this.showToast('上传失败,请重试');
},
deleteItem(index) {
uni.showModal({
title: '提示',
content: '您确定要删除此项吗?',
success: async (res) => {
if (res.confirm) {
if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
let beforeResponse = this.beforeRemove.bind(this.$u.$parent.call(this))(index, this.lists);
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
await beforeResponse.then(res => {
this.handlerDeleteItem(index);
}).catch(err => {
this.showToast('已终止移除');
})
} else if (beforeResponse === false) {
this.showToast('已终止移除');
} else {
this.handlerDeleteItem(index);
}
} else {
this.handlerDeleteItem(index);
}
}
}
});
},
handlerDeleteItem(index) {
if (this.lists[index].progress < 100 && this.lists[index].progress > 0) {
typeof this.lists[index].uploadTask != 'undefined' && this.lists[index].uploadTask.abort();
}
this.lists.splice(index, 1);
this.$forceUpdate();
this.$emit('on-remove', index, this.lists, this.index);
this.showToast('移除成功');
},
remove(index) {
if (index >= 0 && index < this.lists.length) {
this.lists.splice(index, 1);
this.$emit('on-list-change', this.lists, this.index);
}
},
doPreviewImage(url, index) {
if (!this.previewFullImage) return;
const images = this.lists.map(item => item.url || item.path);
uni.previewImage({
urls: images,
current: url,
success: () => {
this.$emit('on-preview', url, this.lists, this.index);
},
fail: () => {
uni.showToast({
title: '预览图片失败',
icon: 'none'
});
}
});
},
checkFileExt(file) {
let noArrowExt = false;
let fileExt = '';
const reg = /.+\./;
// #ifdef H5
fileExt = file.name.replace(reg, "").toLowerCase();
// #endif
// #ifndef H5
fileExt = file.path.replace(reg, "").toLowerCase();
// #endif
noArrowExt = this.limitType.some(ext => {
return ext.toLowerCase() === fileExt;
})
if (!noArrowExt) this.showToast(`不允许选择${fileExt}格式的文件`);
return noArrowExt;
}
}
};
</script>
<style lang="scss" scoped>
@import 'uview-ui/libs/css/style.components.scss';
.u-upload {
@include vue-flex;
flex-wrap: wrap;
align-items: center;
}
.u-list-item {
width: 200rpx;
height: 200rpx;
overflow: hidden;
margin: 10rpx;
background: rgb(244, 245, 246);
position: relative;
border-radius: 10rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.u-preview-wrap {
border: 1px solid rgb(235, 236, 238);
}
.u-add-wrap {
flex-direction: column;
color: $u-content-color;
font-size: 26rpx;
}
.u-add-tips {
margin-top: 20rpx;
line-height: 40rpx;
}
.u-add-wrap__hover {
background-color: rgb(235, 236, 238);
}
.u-preview-image {
display: block;
width: 100%;
height: 100%;
border-radius: 10rpx;
}
.u-delete-icon {
position: absolute;
top: 10rpx;
right: 10rpx;
z-index: 10;
background-color: $u-type-error;
border-radius: 100rpx;
width: 44rpx;
height: 44rpx;
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-icon {
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-progress {
position: absolute;
bottom: 10rpx;
left: 8rpx;
right: 8rpx;
z-index: 9;
width: auto;
}
.u-error-btn {
color: #ffffff;
background-color: $u-type-error;
font-size: 20rpx;
padding: 4px 0;
text-align: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 9;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,676 @@
<template>
<u-popup v-model="visible" mode="center" width="85%" border-radius="16" :mask-close-able="false">
<view class="popup-wrap">
<view class="popup-title">图片较大</view>
<view v-if="sizeText" class="size-text">{{ sizeText }}</view>
<view v-if="status === 'compressing'" class="compressing-panel">
<image
v-if="originalPath"
:src="originalPath"
mode="aspectFit"
class="preview-image single"
@click="previewImage(originalPath)"
/>
<text v-if="originalPath" class="preview-hint">点击预览</text>
<view class="compressing-status">
<u-loading mode="circle" />
<text class="progress-text">{{ progressText }}</text>
</view>
</view>
<view v-else-if="status === 'ready'" class="compare-panel">
<view class="preview-column">
<text class="preview-label">原图</text>
<image
:src="originalPath"
mode="aspectFit"
class="preview-image"
@click="previewImage(originalPath)"
/>
<text class="preview-size">{{ formatFileSize(originalSize) }}</text>
<text class="preview-hint">点击预览</text>
</view>
<view class="preview-column">
<text class="preview-label">压缩后</text>
<image
:src="compressedPath"
mode="aspectFit"
class="preview-image"
@click="previewImage(compressedPath)"
/>
<text class="preview-size">{{ formatFileSize(compressedSize) }}</text>
<text class="preview-hint">点击预览</text>
</view>
</view>
<view v-else-if="status === 'gifSkipped'" class="gif-panel">
<image
v-if="originalPath"
:src="originalPath"
mode="aspectFit"
class="preview-image single"
@click="previewImage(originalPath)"
/>
<text v-if="originalPath" class="preview-hint">点击预览</text>
<text class="hint-text">GIF 不支持压缩将上传原图</text>
</view>
<view v-else class="error-panel">
<image
v-if="originalPath"
:src="originalPath"
mode="aspectFit"
class="preview-image single"
@click="previewImage(originalPath)"
/>
<text v-if="originalPath" class="preview-hint">点击预览</text>
<text class="error-text">{{ errorMessage }}</text>
</view>
<view class="footer-actions">
<view class="btn btn-default" @click="finish('cancel')">取消</view>
<view
v-if="status === 'ready' || status === 'error'"
class="btn btn-default"
@click="finish('original')"
>
{{ originalButtonLabel }}
</view>
<view
v-if="status === 'ready'"
class="btn btn-primary"
@click="finish('compressed')"
>
{{ compressedButtonLabel }}
</view>
<view
v-if="status === 'gifSkipped'"
class="btn btn-primary"
@click="finish('original')"
>
{{ originalButtonLabel }}
</view>
</view>
</view>
</u-popup>
</template>
<script>
import {
formatFileSize,
formatSavingsPercent,
isGifPath,
tryCompressImage,
} from '@/utils/image-compress.js';
export default {
name: 'ImageCompressPopup',
data() {
return {
visible: false,
status: 'compressing',
progressText: '正在压缩,请稍候…',
errorMessage: '',
originalPath: '',
compressedPath: '',
originalSize: 0,
compressedSize: 0,
skipped: false,
resolver: null,
};
},
computed: {
sizeText() {
if (!this.originalSize) return '';
const originalText = formatFileSize(this.originalSize);
if (this.status === 'gifSkipped') {
return `原图 ${originalText}`;
}
if (this.compressedSize && this.status === 'ready') {
const savings = formatSavingsPercent(this.originalSize, this.compressedSize);
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}${savings}`;
}
if (this.compressedSize) {
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}`;
}
return `原图 ${originalText}`;
},
originalButtonLabel() {
if (!this.originalSize) return '使用原图';
return `使用原图 (${formatFileSize(this.originalSize)})`;
},
compressedButtonLabel() {
if (!this.compressedSize) return '使用压缩图';
return `使用压缩图 (${formatFileSize(this.compressedSize)})`;
},
},
methods: {
formatFileSize,
previewImage(current) {
if (!this.originalPath) return;
const urls =
this.status === 'ready' && this.compressedPath
? [this.originalPath, this.compressedPath]
: [this.originalPath];
uni.previewImage({
urls,
current,
});
},
open({ path, size }) {
return new Promise((resolve) => {
this.resolver = resolve;
this.originalPath = path;
this.originalSize = size;
this.compressedPath = '';
this.compressedSize = 0;
this.skipped = false;
this.errorMessage = '';
this.progressText = '正在压缩,请稍候…';
this.visible = true;
this.startCompress(path, size);
});
},
async startCompress(path, size) {
if (isGifPath(path)) {
this.skipped = true;
this.status = 'gifSkipped';
return;
}
this.status = 'compressing';
this.progressText = '正在压缩(质量 80%)…';
try {
const compressed = await tryCompressImage(path, [80, 60, 40], (quality) => {
this.progressText = `正在压缩(质量 ${quality}%)…`;
});
this.compressedPath = compressed.path;
this.compressedSize = compressed.size;
this.status = 'ready';
this.progressText = '';
} catch (error) {
this.status = 'error';
this.errorMessage = '图片压缩失败,请使用原图或取消';
this.progressText = '';
}
},
finish(choice) {
const result = { choice, path: null, size: 0, usedCompress: false };
if (choice === 'cancel') {
this.visible = false;
this.resolver && this.resolver(null);
this.resolver = null;
return;
}
if (choice === 'compressed' && this.status === 'ready') {
result.path = this.compressedPath;
result.size = this.compressedSize;
result.usedCompress = true;
} else {
result.path = this.originalPath;
result.size = this.originalSize;
result.usedCompress = false;
}
this.visible = false;
this.resolver && this.resolver(result);
this.resolver = null;
},
},
};
</script>
<style lang="scss" scoped>
.popup-wrap {
padding: 32rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
text-align: center;
margin-bottom: 16rpx;
}
.size-text {
font-size: 26rpx;
color: #666;
text-align: center;
margin-bottom: 24rpx;
}
.preview-image {
width: 100%;
height: 280rpx;
background: #fafafa;
border-radius: 12rpx;
}
.preview-image.single {
width: 100%;
}
.compare-panel {
display: flex;
gap: 16rpx;
}
.preview-column {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.preview-label {
font-size: 26rpx;
font-weight: 500;
margin-bottom: 12rpx;
}
.preview-size {
margin-top: 8rpx;
font-size: 22rpx;
color: #999;
}
.preview-hint {
margin-top: 4rpx;
font-size: 22rpx;
color: #bbb;
}
.compressing-panel,
.gif-panel,
.error-panel {
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
}
.compressing-status {
display: flex;
align-items: center;
gap: 16rpx;
}
.progress-text,
.hint-text {
font-size: 26rpx;
color: #666;
}
.error-text {
font-size: 26rpx;
color: #fa3534;
}
.footer-actions {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 32rpx;
}
.btn {
min-width: 160rpx;
padding: 16rpx 24rpx;
border-radius: 8rpx;
font-size: 26rpx;
text-align: center;
}
.btn-default {
background: #f5f5f5;
color: #333;
}
.btn-primary {
background: #2979ff;
color: #fff;
}
</style>

View File

@@ -3,6 +3,7 @@
// 下载安装方式
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
// npm安装方式
// "^u-upload$": "@/components/c-upload/c-upload.vue",
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
// 消息通知组件
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue",

View File

@@ -2,6 +2,7 @@
<view class="content safe-area-inset-bottom">
<!-- 全局消息通知组件 -->
<MessageNotification />
<image-compress-popup ref="imageCompressPopup" />
<!-- 1. 自定义吸顶导航栏 -->
<view class="custom-nav"
@@ -118,7 +119,7 @@
</view>
</view>
<!-- <view class="consult-btn">咨询</view>-->
<view class="consult-btn">查看</view>
<!-- <view class="consult-btn">查看</view>-->
</view>
<view class="doc-stats-bar">
<view class="stat-item">
@@ -214,12 +215,15 @@ import { getPlatformQualificationsApi } from '../../request/api/platform'
import { getStoreDrugListBySalespersonApi, getHomeTopProductsApi, getHomeZonesApi, checkOnlineConsultationConfigApi } from "@/request/api/product";
import request from "@/request/api/request";
import MessageNotification from "@/components/MessageNotification/MessageNotification.vue";
import ImageCompressPopup from '@/components/image-compress-popup/image-compress-popup.vue';
import { registerImageCompressPopup } from '@/utils/image-compress-modal.js';
import { setGuestMode, isGuestMode, isLoggedIn } from '@/common/utils/auth.js';
import {silentLogin} from "@/utils/login";
export default {
components: {
MessageNotification
MessageNotification,
ImageCompressPopup,
},
data() {
return {
@@ -275,6 +279,9 @@ export default {
}
},
onReady() {
if (this.$refs.imageCompressPopup) {
registerImageCompressPopup(this.$refs.imageCompressPopup);
}
// console.log('调试静默登陆', isLoggedIn());
// 判断是否已登录

View File

@@ -1,4 +1,5 @@
import http from './request'
import { get } from './http'
// 登录 授权 其他手机号授权
export async function getsLogin(params) {
@@ -106,7 +107,19 @@ export async function userInfo(params) {
// 就诊人保存
export async function userAdd(params) {
let data = await http('/oldApi/v1/patient/save', params)
let data = await http('/xkApi/patient/save', params)
return data
}
// 就诊人监护人信息(编辑回显)
export async function userGuardianInfo(params) {
const query = params.data || params
return await get('/patient/guardian-info', query, 3)
}
// 删除就诊人
export async function userPatientDel(params) {
let data = await http('/xkApi/patient/delete', params)
return data
}

View File

@@ -14,12 +14,66 @@ const arr = [
'password'
]
// 敏感数据
// 敏感数据:响应解密后保留明文原字段,脱敏值写入 {field}_tm
const sensitiveData = [
'id_card',
'idcard'
'idcard',
'guardian_id_card'
]
// 仅展示脱敏、编辑需明文的字段
const displayMaskFields = [
'mobile',
'express_mobile',
'patient_mobile',
]
/**
* 脱敏展示值(写入 field_tm
*/
function maskFieldValue(key, plainText) {
if (plainText == null || plainText === '') {
return plainText
}
const str = String(plainText)
switch (key) {
case 'express_name':
case 'express_region':
case 'accept_name':
case 'patient':
return str.slice(0, 1).padEnd(str.length, '*')
case 'mobile':
case 'express_mobile':
case 'patient_mobile':
case 'guardian_mobile':
if (str.length <= 7) {
return str
}
return str.slice(0, 3).padEnd(str.length - 4, '*') + str.slice(-4)
case 'id_card':
case 'idcard':
case 'guardian_id_card':
if (str.length <= 10) {
return str
}
return str.slice(0, 6).padEnd(str.length - 4, '*') + str.slice(-4)
default:
return str
}
}
/** 只读展示:优先 field_tm */
export function displayTm(obj, field) {
if (obj == null) {
return ''
}
const tm = obj[field + '_tm']
if (tm != null && tm !== '') {
return tm
}
return obj[field] ?? ''
}
const isDev = checkDev('dev');
// 环境URL配置
@@ -254,56 +308,39 @@ function request(url, params, method = 0) {
function getRes(obj, isDecode = true) {
try {
if (obj == null || typeof obj !== 'object') {
return obj
}
for (const key in obj) {
if (key.endsWith('_tm')) {
continue
}
if (Array.isArray(obj[key])) {
obj[key] = getRes(obj[key])
} else if (typeof obj[key] === 'object') {
obj[key] = getRes(obj[key])
} else {
// 判断obj[key]是否在arr中
if (arr.indexOf(key) !== -1) {
let aseFile = ''
obj[key] = getRes(obj[key], isDecode)
} else if (obj[key] !== null && typeof obj[key] === 'object') {
obj[key] = getRes(obj[key], isDecode)
} else if (arr.indexOf(key) !== -1) {
let plainValue = ''
if (isDecode === true) {
aseFile = customBase64Decode(obj[key])
plainValue = customBase64Decode(obj[key])
} else {
aseFile = customBase64Encode(obj[key])
plainValue = customBase64Encode(obj[key])
}
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
typeof aseFile === 'string' ? aseFile : ''
typeof plainValue === 'string' ? plainValue : ''
)
if (isGarbled) {
aseFile = obj[key]
plainValue = obj[key]
}
// 为了修改,暂时不脱敏
if (isDecode === true) {
if (sensitiveData.indexOf(key) !== -1) {
// 数据脱敏,自动匹配姓名、手机号、身份证
switch (key) {
case 'express_name':
case 'express_region':
case 'accept_name':
case 'patient':
// 保留前两个字符,其余用星号代替
aseFile = aseFile.slice(0, 1).padEnd(aseFile.length, '*');
break;
case 'mobile':
case 'express_mobile':
// 保留前三位和后四位,中间用星号代替
aseFile = aseFile.slice(0, 3).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
break;
case 'id_card':
case 'idcard':
// 保留前六位和后四位,中间用星号代替
aseFile = aseFile.slice(0, 6).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
break;
default:
// 默认情况下,全部用星号代替
break;
const needTm = sensitiveData.indexOf(key) !== -1
|| displayMaskFields.indexOf(key) !== -1
obj[key] = plainValue
if (needTm) {
obj[key + '_tm'] = maskFieldValue(key, plainValue)
}
}
}
obj[key] = aseFile
} else {
obj[key] = plainValue
}
}
}

View File

@@ -199,6 +199,7 @@ import { ChatManager } from '@/store/chat/chat.js';
import {getChatRegisterInfoApi, sendToUserApi, upLoadChatFileApi, getDoctorInfoApi, getRoomStatusApi} from "@/request/api/im";
import {getTransferAssistantConfigApi, getTransferConsultationAgreementInfoApi, getAgreementDetailApi} from "@/request/api/transferPrescription";
import { checkDev } from '@/utils/utils';
import { prepareImagePath } from '@/utils/image-compress.js';
// 根据环境获取上传URL
const isDev = checkDev('dev');
@@ -941,6 +942,23 @@ export default {
success: res => { res.tempFilePaths.forEach(file => { this.uploadAndSendFile(file, 'image'); }); }
});
}
// if (type === 'image') {
// uni.chooseImage({
// count: 9,
// sourceType: ['album', 'camera'],
// success: async (res) => {
// for (let index = 0; index < res.tempFiles.length; index++) {
// const tempFile = res.tempFiles[index];
// const prepared = await prepareImagePath({
// path: tempFile.path,
// size: tempFile.size,
// });
// if (!prepared) continue;
// this.uploadAndSendFile(prepared.path, 'image');
// }
// },
// });
// }
},
uploadAndSendFile(filePath, type) {

View File

@@ -521,12 +521,18 @@ export default {
// const storeId = ((this.registerType === '3' || this.registerType == '2') && this.delegateStoreId)
// ? this.delegateStoreId
// : (uni.getStorageSync('store_id') || '11001');
const originStoreId = uni.getStorageSync('store_id') || '';
const shouldSendOriginStoreId =
this.registerType === '2' ||
this.registerType === '3' ||
(this.registerType !== '0' && !!this.delegateStoreId);
registe({
method: "post",
data: {
user_id: uni.getStorageSync('user_id'),
user_patient_id: this.actived,
store_id: storeId,
origin_store_id: shouldSendOriginStoreId ? originStoreId : 0,
service_user_id: this.Did,
register_type: this.registerType // 新增:挂号类型参数
}

View File

@@ -75,9 +75,15 @@
<image :src="item.drug_image || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/empty/gwc.png'" mode=""></image>
</view>
<view class="it">
<view class="it_name">{{item.drug_name}}{{item.drug.function}}</view>
<view class="it_info" v-if="orderInfo.ProductOrder.prescription_type==2">
{{item.drug.usage}}
<view class="it_name">
<text>{{ item.drug_name }}</text>
<text
v-if="item.drug && item.drug.specification"
class="drug-spec">{{ item.drug.specification }}</text>
<text v-if="item.drug && item.drug.function">{{ item.drug.function }}</text>
</view>
<view class="it_info" v-if="orderInfo.ProductOrder.prescription_type==2 && item.drug">
{{ item.drug.usage }}
</view>
<!-- 添加查看说明书按钮 -->
<view class="instruction-btn" v-if="prescriptionStatus && item.drug && item.drug.instruction" @click="previewInstruction(item.drug.instruction)">
@@ -624,8 +630,8 @@ export default {
}).then((res) => {
if (res.data && (res.data.code == 0 || res.data.errcode == 0)) {
const data = res.data.data || res.data.result || {};
// 假设 status = 2 表示审核通过
this.prescriptionStatus = data.status === true || data.status === 2 || data.status == 2;
const ps = data.prescription_status ?? data.status;
this.prescriptionStatus = data.status === true || ps === 1 || ps === '1' || ps === 3 || ps === '3' || ps === 4 || ps === '4';
} else {
this.prescriptionStatus = false;
}
@@ -658,8 +664,6 @@ export default {
onShow() {
this.getInfo()
this.getList()
// 查询处方状态
this.getPrescriptionStatus()
},
onUnload() {
uni.removeStorageSync('userWay_order_id')
@@ -902,6 +906,13 @@ export default {
text-overflow: ellipsis;
overflow: hidden;
margin-bottom: 20rpx;
.drug-spec {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
font-weight: normal;
}
}
.it_info {

View File

@@ -64,6 +64,53 @@
</view>
</view>
<!-- 监护人信息6岁及以下 -->
<view class="card" v-if="showGuardianSection">
<view class="card_title">监护人信息</view>
<view class="cards_names" style="padding: 20rpx 0;">
<text>填写方式</text>
<u-radio-group v-model="guardianType" @change="onGuardianTypeChange">
<u-radio name="1" style="margin-right: 40rpx;">选择已有就诊人</u-radio>
<u-radio name="2">填写监护人信息</u-radio>
</u-radio-group>
</view>
<view class="card_relation" v-if="guardianType == '1'">
<text>监护人</text>
<u-input v-model="guardianPatientName" type="select" input-align="right" placeholder="请选择"
placeholder-style="text-align:right;color: #94A3B8;" @click="showGuardianPatient = true" />
<u-icon name="arrow-right" color="#A7ABB0" size="26" @click="showGuardianPatient = true"></u-icon>
<u-action-sheet :list="guardianPickerList" :safe-area-inset-bottom="true" v-model="showGuardianPatient"
@click="guardianPatientCallback">
</u-action-sheet>
</view>
<template v-if="guardianType == '2'">
<view class="card_name">
<text>姓名</text>
<u-input v-model="guardianForm.name" input-align="right" placeholder="请输入监护人姓名"
placeholder-style="text-align:right;color: #94A3B8;" :type="type" />
</view>
<view class="card_name">
<text>身份证号</text>
<u-input v-model="guardianForm.idCard" maxlength="18" input-align="right" placeholder="请输入身份证号"
placeholder-style="text-align:right;color: #94A3B8;" :type="type" />
</view>
<view class="card_telephone">
<text>手机号</text>
<u-input v-model="guardianForm.mobile" input-align="right" placeholder="请输入手机号"
placeholder-style="text-align:right; color: #94A3B8;" :type="type" />
</view>
<view class="card_relation">
<text>与患儿关系</text>
<u-input v-model="guardianRelations" type="select" input-align="right" placeholder="请选择"
placeholder-style="text-align:right;color: #94A3B8;" @click="showGuardianRelat = true" />
<u-icon name="arrow-right" color="#A7ABB0" size="26" @click="showGuardianRelat = true"></u-icon>
<u-action-sheet :list="actionSheetList" :safe-area-inset-bottom="true" v-model="showGuardianRelat"
@click="guardianRelationCallback">
</u-action-sheet>
</view>
</template>
</view>
<!-- 健康信息 -->
<view class="cards">
<view class="cards_title">
@@ -218,6 +265,7 @@
<script>
import {
userAdd,
userList,
userRelation
} from '../../request/api/api';
export default {
@@ -369,8 +417,36 @@
person_history: [],
family_history: [],
is_default: 0,
guardianType: '1',
guardianPatientName: '',
guardianUserPatientId: 0,
guardianRelations: '',
guardianRelation: '',
showGuardianPatient: false,
showGuardianRelat: false,
patientList: [],
guardianForm: {
name: '',
idCard: '',
mobile: '',
},
}
},
computed: {
showGuardianSection() {
const age = Number(this.form.age);
return this.form.age !== '' && !isNaN(age) && age <= 6;
},
guardianPickerList() {
const currentId = Number(this.addid) || 0;
return this.patientList
.filter((item) => Number(item.id) !== currentId)
.map((item) => ({
text: `${item.name}${item.age}岁)`,
key: item.id,
}));
},
},
onLoad(e) {
// console.log(e);
this.addid = e.id
@@ -439,17 +515,179 @@
})
},
loadPatientList() {
userList({
method: 'post',
data: {
store_id: uni.getStorageSync('store_id') || '11001',
},
}).then((res) => {
if (res.data.errcode == 0) {
this.patientList = res.data.data || [];
}
});
},
resetGuardian() {
this.guardianType = '1';
this.guardianPatientName = '';
this.guardianUserPatientId = 0;
this.guardianRelations = '';
this.guardianRelation = '';
this.guardianForm = {
name: '',
idCard: '',
mobile: '',
};
},
onGuardianTypeChange() {
this.guardianPatientName = '';
this.guardianUserPatientId = 0;
this.guardianRelations = '';
this.guardianRelation = '';
this.guardianForm = {
name: '',
idCard: '',
mobile: '',
};
},
guardianPatientCallback(index) {
const item = this.guardianPickerList[index];
if (!item) {
return;
}
this.guardianPatientName = item.text;
this.guardianUserPatientId = item.key;
},
guardianRelationCallback(index) {
this.guardianRelations = this.actionSheetList[index].text;
this.guardianRelation = this.actionSheetList[index].key;
},
isAgeInRange(age) {
const n = Number(age);
return age !== '' && !isNaN(n) && n >= 1 && n <= 200;
},
calcAgeFromIdCard(idCard) {
const reg =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!reg.test(idCard)) {
return false;
}
const orgBirthday = idCard.substring(6, 14);
const birthday =
orgBirthday.substring(0, 4) +
'-' +
orgBirthday.substring(4, 6) +
'-' +
orgBirthday.substring(6, 8);
const birthdays = new Date(birthday.replace(/-/g, '/'));
const d = new Date();
return (
d.getFullYear() -
birthdays.getFullYear() -
(d.getMonth() < birthdays.getMonth() ||
(d.getMonth() === birthdays.getMonth() && d.getDate() < birthdays.getDate()) ?
1 :
0)
);
},
validatePatientAge() {
if (!this.isAgeInRange(this.form.age)) {
uni.showToast({
title: '就诊人年龄需在1-200岁之间',
icon: 'none',
});
return false;
}
return true;
},
validateGuardian() {
if (!this.showGuardianSection) {
return true;
}
if (this.guardianType == '1') {
if (!this.guardianUserPatientId) {
uni.showToast({
title: '请选择监护人就诊人',
icon: 'none',
});
return false;
}
const guardianPatient = this.patientList.find(
(item) => Number(item.id) === Number(this.guardianUserPatientId)
);
if (guardianPatient && !this.isAgeInRange(guardianPatient.age)) {
uni.showToast({
title: '监护人年龄需在1-200岁之间',
icon: 'none',
});
return false;
}
return true;
}
if (!this.guardianForm.name || !this.guardianForm.idCard || !this.guardianForm.mobile) {
uni.showToast({
title: '请填写完整监护人信息',
icon: 'none',
});
return false;
}
if (!/^\d{11}$/.test(this.guardianForm.mobile)) {
uni.showToast({
title: '监护人手机号格式不正确',
icon: 'none',
});
return false;
}
const guardianAge = this.calcAgeFromIdCard(this.guardianForm.idCard);
if (guardianAge === false) {
uni.showToast({
title: '监护人身份证无效',
icon: 'none',
});
return false;
}
if (!this.isAgeInRange(guardianAge)) {
uni.showToast({
title: '监护人年龄需在1-200岁之间',
icon: 'none',
});
return false;
}
return true;
},
buildGuardianPayload(data) {
if (!this.showGuardianSection) {
return data;
}
data.guardian_type = Number(this.guardianType);
if (this.guardianType == '1') {
data.guardian_user_patient_id = this.guardianUserPatientId;
} else {
data.guardian_name = this.guardianForm.name;
data.guardian_id_card = this.guardianForm.idCard;
data.guardian_mobile = this.guardianForm.mobile;
data.guardian_relation = this.guardianRelation;
}
return data;
},
isSaveSuccess(res) {
return res.data.code === 0 || res.data.errcode == 0;
},
getSaveErrorMsg(res) {
return res.data.message || res.data.msg || '保存失败';
},
// 添加就诊人
getAdduser() {
userAdd({
method: "post",
data: {
if (!this.validatePatientAge() || !this.validateGuardian()) {
return;
}
const payload = this.buildGuardianPayload({
store_id: uni.getStorageSync('store_id') || '11001',
is_default: this.checked ? '1' : '0',
id: this.addid,
name: this.names,
id_card: this.form.identityCardNo,
sex: (this.form.sex) ,
sex: (this.form.sex) == '女' ? '2' : (this.form.sex == '男' ? '1' : this.form.sex),
age: this.form.age,
mobile: this.phone,
relation: this.relation,
@@ -462,11 +700,13 @@
allergic_history: this.allergic_history,
person_history: this.person_history,
family_history: this.family_history,
}
});
userAdd({
method: "post",
data: payload,
})
.then((res) => {
// console.log(res, 'res');
if (res.data.errcode == 0) {
if (this.isSaveSuccess(res)) {
uni.showToast({
title: "添加成功",
@@ -481,9 +721,9 @@
});
}, 400)
} else if (res.data.errcode != 0) {
} else {
this.$refs.uToast.show({
title: res.data.msg,
title: this.getSaveErrorMsg(res),
type: 'default',
icon: false
})
@@ -595,6 +835,9 @@
this.form.sex = sex;
this.form.age = age;
this.sex = org_gender% 2 == 1 ? 1 : 2;
if (age > 6) {
this.resetGuardian();
}
} else {
this.form.sex = "";
return false;
@@ -602,7 +845,8 @@
},
},
mounted() {
this.getAction()
this.getAction();
this.loadPatientList();
}
}
</script>

View File

@@ -10,13 +10,13 @@
<!-- 姓名 -->
<view class="card_name">
<text>姓名</text>
<u-input disabled v-model="names" input-align="right" placeholder="请输入真实姓名"
<u-input v-model="names" input-align="right" placeholder="请输入真实姓名"
placeholder-style="text-align:right;color: #94A3B8;" :type="type" />
</view>
<!-- 身份证号 -->
<view class="card_name">
<text>身份证号</text>
<u-input disabled v-model="form.identityCardNo" maxlength="18" input-align="right" placeholder="已经填写无法修改,请慎重填写"
<u-input v-model="form.identityCardNo" maxlength="18" input-align="right" placeholder="请输入身份证号"
placeholder-style="text-align:right;color: #94A3B8;padding-right:5px;" @input="inputChange"
:type="type" />
</view>
@@ -53,6 +53,53 @@
</view>
</view>
<!-- 监护人信息6岁及以下 -->
<view class="card" v-if="showGuardianSection">
<view class="card_title">监护人信息</view>
<view class="cards_names" style="padding: 20rpx 0;">
<text>填写方式</text>
<u-radio-group v-model="guardianType" @change="onGuardianTypeChange">
<u-radio name="1" style="margin-right: 40rpx;">选择已有就诊人</u-radio>
<u-radio name="2">填写监护人信息</u-radio>
</u-radio-group>
</view>
<view class="card_relation" v-if="guardianType == '1'">
<text>监护人</text>
<u-input v-model="guardianPatientName" type="select" input-align="right" placeholder="请选择"
placeholder-style="text-align:right;color: #94A3B8;" @click="showGuardianPatient = true" />
<u-icon name="arrow-right" color="#A7ABB0" size="26" @click="showGuardianPatient = true"></u-icon>
<u-action-sheet :list="guardianPickerList" :safe-area-inset-bottom="true" v-model="showGuardianPatient"
@click="guardianPatientCallback">
</u-action-sheet>
</view>
<template v-if="guardianType == '2'">
<view class="card_name">
<text>姓名</text>
<u-input v-model="guardianForm.name" input-align="right" placeholder="请输入监护人姓名"
placeholder-style="text-align:right;color: #94A3B8;" :type="type" />
</view>
<view class="card_name">
<text>身份证号</text>
<u-input v-model="guardianForm.idCard" maxlength="18" input-align="right" placeholder="请输入身份证号"
placeholder-style="text-align:right;color: #94A3B8;" :type="type" />
</view>
<view class="card_telephone">
<text>手机号</text>
<u-input v-model="guardianForm.mobile" input-align="right" placeholder="请输入手机号"
placeholder-style="text-align:right; color: #94A3B8;" :type="type" />
</view>
<view class="card_relation">
<text>与患儿关系</text>
<u-input v-model="guardianRelations" type="select" input-align="right" placeholder="请选择"
placeholder-style="text-align:right;color: #94A3B8;" @click="showGuardianRelat = true" />
<u-icon name="arrow-right" color="#A7ABB0" size="26" @click="showGuardianRelat = true"></u-icon>
<u-action-sheet :list="actionSheetList" :safe-area-inset-bottom="true" v-model="showGuardianRelat"
@click="guardianRelationCallback">
</u-action-sheet>
</view>
</template>
</view>
<!-- 健康信息 -->
<view class="cards">
<view class="cards_title">
@@ -194,6 +241,7 @@
import {
userAdd,
userEidtPatient,
userGuardianInfo,
userList,
userRelation
} from '../../request/api/api';
@@ -347,10 +395,37 @@
person_history: [],
family_history: [],
heathList: [], // 健康信息
guardianType: '1',
guardianPatientName: '',
guardianUserPatientId: 0,
guardianRelations: '',
guardianRelation: '',
showGuardianPatient: false,
showGuardianRelat: false,
allPatientList: [],
guardianForm: {
name: '',
idCard: '',
mobile: '',
},
}
},
computed: {
showGuardianSection() {
const age = Number(this.form.age);
return this.form.age !== '' && !isNaN(age) && age <= 6;
},
guardianPickerList() {
const currentId = Number(this.addid) || 0;
return this.allPatientList
.filter((item) => Number(item.id) !== currentId)
.map((item) => ({
text: `${item.name}${item.age}岁)`,
key: item.id,
}));
},
},
onLoad(e) {
// console.log(e);
this.id = e.id
this.getAction()
},
@@ -507,7 +582,10 @@
1 : 0);
this.form.sex = sex;
this.form.age = age;
this.sex = org_gender;
this.sex = org_gender % 2 == 1 ? 1 : 2;
if (age > 6) {
this.resetGuardian();
}
} else {
this.form.sex = "";
return false;
@@ -522,10 +600,10 @@
store_id: uni.getStorageSync('store_id') || '11001',
}
}).then((res) => {
// console.log(res, 'relation');
if (res.data.errcode == 0) {
this.actionSheetList = res.data.data
}
this.getInfolist()
})
},
@@ -538,14 +616,12 @@
store_id: uni.getStorageSync('store_id') || '11001',
}
}).then((res) => {
// console.log(res, 'info');
if (res.data.errcode == 0) {
that.infoList = res.data.data.filter((item) => item.id == that.id)
that.allPatientList = res.data.data || [];
that.infoList = that.allPatientList.filter((item) => item.id == that.id)
that.actived = that.infoList[0].id;
that.names = that.infoList[0].name
// that.relations = that.infoList[0].relations
that.phone = that.infoList[0].mobile
that.form.sex = (that.infoList[0].sex) % 2 == 0 ? '女' : '男'
that.form.age = that.infoList[0].age
@@ -554,15 +630,198 @@
that.actionSheetCallback(that.infoList[0].relation)
that.getHeath()
that.loadGuardianInfo()
}
})
},
loadGuardianInfo() {
if (!this.showGuardianSection) {
return;
}
userGuardianInfo({
user_patient_id: this.addid,
}).then((res) => {
if (!(res.data.code === 0 || res.data.errcode == 0)) {
return;
}
const info = res.data.result || res.data.data || {};
if (!info.guardian_type) {
return;
}
this.guardianType = String(info.guardian_type);
if (this.guardianType == '1') {
this.guardianUserPatientId = info.guardian_user_patient_id;
if (info.guardian_patient_preview) {
this.guardianPatientName = `${info.guardian_patient_preview.name}${info.guardian_patient_preview.age}岁)`;
}
} else {
this.guardianForm.name = info.guardian_name || '';
this.guardianForm.idCard = info.guardian_id_card || '';
this.guardianForm.mobile = info.guardian_mobile || '';
this.guardianRelation = info.guardian_relation;
const relationItem = this.actionSheetList.find((item) => item.key == info.guardian_relation);
if (relationItem) {
this.guardianRelations = relationItem.text;
}
}
});
},
resetGuardian() {
this.guardianType = '1';
this.guardianPatientName = '';
this.guardianUserPatientId = 0;
this.guardianRelations = '';
this.guardianRelation = '';
this.guardianForm = {
name: '',
idCard: '',
mobile: '',
};
},
onGuardianTypeChange() {
this.guardianPatientName = '';
this.guardianUserPatientId = 0;
this.guardianRelations = '';
this.guardianRelation = '';
this.guardianForm = {
name: '',
idCard: '',
mobile: '',
};
},
guardianPatientCallback(index) {
const item = this.guardianPickerList[index];
if (!item) {
return;
}
this.guardianPatientName = item.text;
this.guardianUserPatientId = item.key;
},
guardianRelationCallback(index) {
this.guardianRelations = this.actionSheetList[index].text;
this.guardianRelation = this.actionSheetList[index].key;
},
isAgeInRange(age) {
const n = Number(age);
return age !== '' && !isNaN(n) && n >= 1 && n <= 200;
},
calcAgeFromIdCard(idCard) {
const reg =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!reg.test(idCard)) {
return false;
}
const orgBirthday = idCard.substring(6, 14);
const birthday =
orgBirthday.substring(0, 4) +
'-' +
orgBirthday.substring(4, 6) +
'-' +
orgBirthday.substring(6, 8);
const birthdays = new Date(birthday.replace(/-/g, '/'));
const d = new Date();
return (
d.getFullYear() -
birthdays.getFullYear() -
(d.getMonth() < birthdays.getMonth() ||
(d.getMonth() === birthdays.getMonth() && d.getDate() < birthdays.getDate()) ?
1 :
0)
);
},
validatePatientAge() {
if (!this.isAgeInRange(this.form.age)) {
uni.showToast({
title: '就诊人年龄需在1-200岁之间',
icon: 'none',
});
return false;
}
return true;
},
validateGuardian() {
if (!this.showGuardianSection) {
return true;
}
if (this.guardianType == '1') {
if (!this.guardianUserPatientId) {
uni.showToast({
title: '请选择监护人就诊人',
icon: 'none',
});
return false;
}
const guardianPatient = this.allPatientList.find(
(item) => Number(item.id) === Number(this.guardianUserPatientId)
);
if (guardianPatient && !this.isAgeInRange(guardianPatient.age)) {
uni.showToast({
title: '监护人年龄需在1-200岁之间',
icon: 'none',
});
return false;
}
return true;
}
if (!this.guardianForm.name || !this.guardianForm.idCard || !this.guardianForm.mobile) {
uni.showToast({
title: '请填写完整监护人信息',
icon: 'none',
});
return false;
}
if (!/^\d{11}$/.test(this.guardianForm.mobile)) {
uni.showToast({
title: '监护人手机号格式不正确',
icon: 'none',
});
return false;
}
const guardianAge = this.calcAgeFromIdCard(this.guardianForm.idCard);
if (guardianAge === false) {
uni.showToast({
title: '监护人身份证无效',
icon: 'none',
});
return false;
}
if (!this.isAgeInRange(guardianAge)) {
uni.showToast({
title: '监护人年龄需在1-200岁之间',
icon: 'none',
});
return false;
}
return true;
},
buildGuardianPayload(data) {
if (!this.showGuardianSection) {
return data;
}
data.guardian_type = Number(this.guardianType);
if (this.guardianType == '1') {
data.guardian_user_patient_id = this.guardianUserPatientId;
} else {
data.guardian_name = this.guardianForm.name;
data.guardian_id_card = this.guardianForm.idCard;
data.guardian_mobile = this.guardianForm.mobile;
data.guardian_relation = this.guardianRelation;
}
return data;
},
isSaveSuccess(res) {
return res.data.code === 0 || res.data.errcode == 0;
},
getSaveErrorMsg(res) {
return res.data.message || res.data.msg || '保存失败';
},
// 添加就诊人
getAdduser() {
userAdd({
method: "post",
data: {
if (!this.validatePatientAge() || !this.validateGuardian()) {
return;
}
const payload = this.buildGuardianPayload({
store_id: uni.getStorageSync('store_id') || '11001',
id: this.addid,
name: this.names,
@@ -580,14 +839,16 @@
allergic_history: this.allergic_history,
person_history: this.person_history,
family_history: this.family_history,
}
});
userAdd({
method: "post",
data: payload,
})
.then((res) => {
console.log(res, 'res');
if (res.data.errcode == 0) {
if (this.isSaveSuccess(res)) {
uni.showToast({
title: "添加成功",
title: "保存成功",
duration: 1500,
icon: "success",
mask: false
@@ -599,15 +860,9 @@
});
}, 400)
} else if (res.data.errcode != 0) {
// uni.showToast({
// title: res.data.msg,
// icon: "error",
// duration: 1500,
// mask: false
// })
} else {
this.$refs.uToast.show({
title: res.data.msg,
title: this.getSaveErrorMsg(res),
type: 'default',
icon: false
})
@@ -675,9 +930,6 @@
})
}
},
mounted() {
this.getInfolist()
}
}
</script>

View File

@@ -24,13 +24,14 @@
<text class="sign">[{{item.text}}]</text>
<text class="default" v-if="item.is_default==1">默认</text>
</view>
<view class="info_sex" @tap.stop="toEidt(item.id)">
编辑
<view class="info_actions">
<text class="info_action_edit" @tap.stop="toEidt(item.id)">编辑</text>
<text class="info_action_del" @tap.stop="confirmDelete(item.id, item.name)">删除</text>
</view>
</view>
<view class="info_list_bottom">
<text class="id">{{item.id_card.replace(/(\w{6})\w*(\w{4})/,'$1********$2')}}</text>
<text class="telephone">{{item.mobile.replace(/(\d{3})\d*(\d{4})/, '$1****$2')}}</text>
<text class="id">{{item.id_card_tm || item.id_card}}</text>
<text class="telephone">{{item.mobile_tm || item.mobile}}</text>
</view>
</view>
@@ -66,6 +67,7 @@
import {
userList,
userPatientChoose,
userPatientDel,
userRelation
} from '../../request/api/api'
export default {
@@ -142,6 +144,45 @@
})
},
confirmDelete(id, name) {
uni.showModal({
title: '确认删除',
content: `删除后「${name}」的就诊人信息将无法恢复,是否继续?`,
confirmText: '确认删除',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.deletePatient(id);
}
},
});
},
deletePatient(id) {
userPatientDel({
method: 'post',
data: {
id,
store_id: uni.getStorageSync('store_id') || '11001',
},
}).then((res) => {
if (res.data.code === 0 || res.data.errcode == 0) {
uni.showToast({
title: '删除成功',
icon: 'success',
duration: 1500,
});
this.getInfolist();
} else {
uni.showToast({
title: res.data.message || res.data.msg || '删除失败',
icon: 'none',
duration: 1500,
});
}
});
},
// 默认就诊人
choose(e) {
userPatientChoose({
@@ -307,11 +348,23 @@
}
}
.info_sex {
.info_actions {
display: flex;
align-items: center;
gap: 24rpx;
}
.info_action_edit {
color: #1777FF;
font-size: 28rpx;
font-weight: 400;
}
.info_action_del {
color: #EF4444;
font-size: 28rpx;
font-weight: 400;
}
}
.info_list_bottom {

View File

@@ -69,7 +69,7 @@
<view class="info_item">
<view class="name">
电话
<text>{{infoList.patient.mobile}}</text>
<text>{{infoList.patient.mobile_tm || infoList.patient.mobile}}</text>
</view>
</view>
</view>
@@ -82,6 +82,26 @@
</view>
</view>
</view>
<view v-if="data.online_tcm_print && data.online_tcm_print.show" class="info">
<view v-if="data.online_tcm_print.tcm_syndrome" class="info_item">
<view class="names">
中医证候
<text>{{ data.online_tcm_print.tcm_syndrome }}</text>
</view>
</view>
<view v-if="data.online_tcm_print.tcm_method" class="info_item">
<view class="names">
中医治法
<text>{{ data.online_tcm_print.tcm_method }}</text>
</view>
</view>
<view v-if="data.online_tcm_print.tcm_disease" class="info_item">
<view class="names">
中医疾病
<text>{{ data.online_tcm_print.tcm_disease }}</text>
</view>
</view>
</view>
</view>
<view class="bg">
@@ -115,13 +135,14 @@
</block>
<block v-if="data.prescription_type==2||data.prescription_type == 5||data.prescription_type == 7">
<view class="item" v-for="(it,index) in infoList.repice" :key="item.id">
<view class="item" v-for="(it,index) in infoList.repice" :key="it.id || index">
<!-- v-for="(it,index) in item.content" :key="it.id" -->
<view class="name" style="justify-content: space-between;">
<view class="yp_name">
<text>{{ it.content.drug_name}} </text>
<text>{{ (it.content && it.content.drug_name) || '' }} </text>
<text
style="font-size: 24rpx;margin-left: 10rpx;">{{ it.content.specification }}</text>
v-if="it.content && it.content.specification"
class="drug-spec">{{ it.content.specification }}</text>
</view>
<text>x{{ it.number}}</text>
</view>
@@ -246,7 +267,7 @@
</view>
<view class="name">
<text>电话</text>
{{infoList.patient.mobile}}
{{infoList.patient.mobile_tm || infoList.patient.mobile}}
</view>
<view class="name">
<text>开具日期</text>
@@ -258,6 +279,50 @@
</view>
</view>
<view class="bg" v-if="infoList.repice && infoList.repice.length">
<view class="my_medical">
<view class="title">Rp</view>
<block v-if="data.prescription_type==1 || data.prescription_type==3">
<view class="items" v-for="(item,index) in infoList.repice" :key="item.id">
<view class="name">
<view class="_name" v-for="(it,index) in item.content" :key="it.id">
<view class="text">
<text>{{ it.name}}</text>
<text class="_abbr" v-if="it.order!=0">[{{useWay[it.order]}}]</text>
</view>
<text class="name_num">{{ it.number }}{{it.unit?it.unit.name:'g'}}</text>
</view>
</view>
<view class="details">
<view class="text">
<text>用法煎服每天 {{item.consumption}} </text>
<text v-if="item.deployment==2">{{' 每次 '+item.volume+' ml '}} </text>
<text> {{item.dosage}} </text>
</view>
</view>
</view>
</block>
<block v-if="data.prescription_type==2||data.prescription_type == 5||data.prescription_type == 7">
<view class="item" v-for="(it,index) in infoList.repice" :key="it.id || index">
<view class="name" style="justify-content: space-between;">
<view class="yp_name">
<text>{{ (it.content && it.content.drug_name) || '' }} </text>
<text
v-if="it.content && it.content.specification"
class="drug-spec">{{ it.content.specification }}</text>
</view>
<text>x{{ it.number}}</text>
</view>
<view class="details" v-if="data.prescription_type==2">
<text>用法{{ it.instruction }}</text>
</view>
</view>
</block>
</view>
</view>
<!-- 医嘱 -->
<view class="order_status" v-if="status==0" style="color: #E88F16;">
您的处方正在审核中
@@ -378,6 +443,25 @@
url: "/subPackages/my/record-pay?id=" + this.data.id
})
},
normalizeRepice(list) {
return (list || []).map((it) => {
let content = it.content
if (typeof content === 'string') {
try {
content = JSON.parse(content || '{}')
} catch (e) {
content = {}
}
}
if (!content || typeof content !== 'object' || Array.isArray(content)) {
content = Array.isArray(content) ? content : {}
}
return {
...it,
content,
}
})
},
getInfo() {
getPrescriptInfo({
method: "post",
@@ -389,7 +473,9 @@
// console.log(res, 'deta');
if (res.data.errcode === 0) {
this.infoList = res.data.data.content
const content = res.data.data.content
content.repice = this.normalizeRepice(content.repice)
this.infoList = content
this.rpList = res.data.data.pharmacistInfo
this.status = res.data.data.status
this.data = res.data.data
@@ -657,6 +743,12 @@
display: flex;
justify-content: space-between;
align-items: center;
.drug-spec {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
}
}
.details {

View File

@@ -100,6 +100,13 @@
<view class="it_info">
{{item.content.usage}}
</view>
<view
class="instruction-btn"
v-if="prescriptionStatus && item.content && item.content.instruction"
@click="previewInstruction(item.content.instruction)"
>
<text>查看说明书</text>
</view>
<view class="it_price">
<text>{{item.total_price}}</text>
<view class="it_num">
@@ -198,6 +205,7 @@
} from '../../request/api/api'
// 导入 getOrderTextApi 接口方法
import { getOrderTextApi } from '../../request/api/product'
import { getPrescriptionStatusApi } from '../../request/api/order'
export default {
data() {
@@ -266,7 +274,8 @@
color: '#111000',
fontWeight: '540',
fontSize: '33rpx'
}
},
prescriptionStatus: false,
}
},
onLoad(e) {
@@ -496,9 +505,36 @@
this.isshow = true
}
}
this.getPrescriptionStatus()
}
})
},
getPrescriptionStatus() {
if (!this.order_id) {
return
}
getPrescriptionStatusApi({
order_id: this.order_id
}).then((res) => {
if (res.data && (res.data.code == 0 || res.data.errcode == 0)) {
const data = res.data.data || res.data.result || {}
const ps = data.prescription_status ?? data.status
this.prescriptionStatus = data.status === true || ps === 1 || ps === '1' || ps === 3 || ps === '3' || ps === 4 || ps === '4'
} else {
this.prescriptionStatus = false
}
}).catch(() => {
this.prescriptionStatus = false
})
},
previewInstruction(instructionImage) {
if (instructionImage) {
uni.previewImage({
urls: [instructionImage],
current: instructionImage
})
}
},
// 代煎费
getServe() {
getUseWayPay({
@@ -915,6 +951,26 @@
margin-bottom: 20rpx;
}
.instruction-btn {
margin-top: 16rpx;
margin-bottom: 16rpx;
padding: 12rpx 24rpx;
background: #ECF5FF;
border-radius: 8rpx;
display: inline-flex;
align-items: center;
justify-content: center;
text {
font-size: 26rpx;
color: #2B85E4;
}
&:active {
opacity: 0.7;
}
}
.it_price {
display: flex;
align-items: center;

View File

@@ -61,6 +61,10 @@
<text class="label">批准文号</text>
<text class="val">{{ productDetail.guozi_no }}</text>
</view>
<view class="spec-item" v-if="manufacturerName">
<text class="label">厂家</text>
<text class="val">{{ manufacturerName }}</text>
</view>
</view>
<!-- 平台资质入口 -->
@@ -78,7 +82,7 @@
<!-- 4. 功效与用法 (卡片式布局 + 优化标题) -->
<view class="info-card" v-if="productDetail.category_type !== 'health_food'">
<view class="info-card" v-if="showFunctionSection">
<!-- 优化点医疗风格标题 -->
<view class="section-title-modern">
<view class="indicator"></view>
@@ -262,7 +266,8 @@ export default {
bar_code: '',
indications: '',
indications_array: [],
compliance_tip: '' // 合规提示文案,由后端返回
compliance_tip: '', // 合规提示文案,由后端返回
supplier: null
},
formData: {
id: '',
@@ -298,6 +303,14 @@ export default {
// 合规提示文案 - 从后端返回的 compliance_tip 字段获取
complianceTip() {
return this.productDetail.compliance_tip || '';
},
manufacturerName() {
return (this.productDetail.supplier && this.productDetail.supplier.name) || '';
},
showFunctionSection() {
if (this.productDetail.category_type === 'health_food') return false;
if (this.productDetail.type == 2 && this.productDetail.is_otc === 0) return false;
return true;
}
},
onLoad(options) {

View File

@@ -125,7 +125,7 @@
身份证号
</view>
<view class="word_num" v-if="registList.idcard">
{{registList.idcard.replace(/(\w{6})\w*(\w{4})/,'$1********$2')}}
{{registList.idcard_tm || registList.idcard}}
</view>
</view>
<view class="name_edit">
@@ -133,7 +133,7 @@
手机号
</view>
<view class="word_num" v-if="registList.mobile">
{{registList.mobile.replace(/(\d{3})\d*(\d{4})/, '$1****$2')}}
{{registList.mobile_tm || registList.mobile}}
</view>
</view>
</view>

View File

@@ -86,7 +86,7 @@
身份证号
</view>
<view class="word_num" v-if="registList.idcard">
{{registList.idcard.replace(/(\w{6})\w*(\w{4})/,'$1********$2')}}
{{registList.idcard_tm || registList.idcard}}
</view>
</view>
<view class="name_edit">
@@ -94,7 +94,7 @@
手机号
</view>
<view class="word_num" v-if="registList.mobile">
{{registList.mobile.replace(/(\d{3})\d*(\d{4})/, '$1****$2')}}
{{registList.mobile_tm || registList.mobile}}
</view>
</view>
</view>

View File

@@ -16,7 +16,7 @@
<view class="info">收货信息</view>
<text class="iconfont icon-guanbi" @click.stop="deleList(item.id)"></text>
</view>
<view class="name">{{ item.name }} {{ item.mobile }}</view>
<view class="name">{{ item.name }} {{ item.mobile_tm || item.mobile }}</view>
<view class="name">{{ item.region }}{{ item.detail_address }}</view>
<!-- 默认发货信息 -->

View File

@@ -5,7 +5,7 @@
<view class="head" v-if="logisticsList.express_name!=''">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/mr_tx.png" mode=""></image>
<view class="head_info">
<view class="name">{{logisticsList.express_name}} {{logisticsList.express_mobile}}</view>
<view class="name">{{logisticsList.express_name}} {{logisticsList.express_mobile_tm || logisticsList.express_mobile}}</view>
<view class="choose_area">{{logisticsList.express_region}}{{logisticsList.express_address}}</view>
</view>
</view>

View File

@@ -20,7 +20,7 @@
<view class="state">
<text>收货地址</text>
</view>
<view class="name">{{orderDetailList.express_name}} {{orderDetailList.express_mobile}}</view>
<view class="name">{{orderDetailList.express_name}} {{orderDetailList.express_mobile_tm || orderDetailList.express_mobile}}</view>
<view class="choose_area">{{orderDetailList.express_region}}{{orderDetailList.express_address}}</view>
</view>

View File

@@ -13,7 +13,7 @@
<!-- 收货地址 已添加地址 -->
<view class="head" v-else @click="toArea">
<view class="state"><text>收货地址</text></view>
<view class="name">{{addList[0].name}} {{addList[0].mobile}}</view>
<view class="name">{{addList[0].name}} {{addList[0].mobile_tm || addList[0].mobile}}</view>
<view class="choose_area">{{addList[0].region}}{{addList[0].detail_address}}</view>
</view>

View File

@@ -0,0 +1,23 @@
let popupInstance = null;
export function registerImageCompressPopup(instance) {
popupInstance = instance;
}
export function openImageCompressModal({ path, size }) {
if (!popupInstance || typeof popupInstance.open !== 'function') {
console.warn('image-compress-popup 未注册,将直接使用原图');
return Promise.resolve({
path,
size,
usedCompress: false,
});
}
return popupInstance.open({ path, size }).then((result) => {
if (!result) {
return null;
}
return result;
});
}

95
utils/image-compress.js Normal file
View File

@@ -0,0 +1,95 @@
import { openImageCompressModal } from '@/utils/image-compress-modal.js';
export const IMAGE_COMPRESS_THRESHOLD = 1 * 1024 * 1024;
export function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const value = bytes / k ** i;
return `${Number.parseFloat(value.toFixed(2))} ${sizes[i]}`;
}
/** 压缩节省比例文案,如「(约节省 42%)」 */
export function formatSavingsPercent(originalBytes, compressedBytes) {
if (originalBytes <= 0 || compressedBytes >= originalBytes) {
return '';
}
const percent = Math.round(
((originalBytes - compressedBytes) / originalBytes) * 100,
);
return percent > 0 ? `(约节省 ${percent}%` : '';
}
function getFileInfo(filePath) {
return new Promise((resolve, reject) => {
uni.getFileInfo({
filePath,
success: resolve,
fail: reject,
});
});
}
export function compressImagePath(src, quality) {
return new Promise((resolve, reject) => {
uni.compressImage({
src,
quality,
success: async (res) => {
try {
const info = await getFileInfo(res.tempFilePath);
resolve({
path: res.tempFilePath,
size: info.size,
});
} catch (error) {
reject(error);
}
},
fail: reject,
});
});
}
export async function tryCompressImage(
src,
qualities = [80, 60, 40],
onProgress,
) {
let result = null;
for (const quality of qualities) {
onProgress && onProgress(quality);
result = await compressImagePath(src, quality);
if (result.size <= IMAGE_COMPRESS_THRESHOLD) {
break;
}
}
return result;
}
export function isGifPath(path) {
return /\.gif$/i.test(path || '');
}
export async function prepareImagePath({ path, size }) {
if (size <= IMAGE_COMPRESS_THRESHOLD) {
return {
path,
size,
usedCompress: false,
};
}
const result = await openImageCompressModal({ path, size });
if (!result) {
return null;
}
return {
path: result.path,
size: result.size,
usedCompress: !!result.usedCompress,
};
}

View File

@@ -6,8 +6,8 @@ export function checkDev(key = '') {
// 判断某些模块是否开启
switch (key) {
case 'dev':
return false;
// return true;
// return false;
return true;
case 'open-im':
// return false;
return true;