feat: 锁定诊所、公账支付
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
<u-popup :value="show" mode="center" border-radius="24" :mask-close-able="true" @input="onPopupInput" @close="close">
|
||||
<view class="ppay">
|
||||
<view class="ppay__header">
|
||||
<text class="ppay__title">患者代付码</text>
|
||||
<text class="ppay__title">患者订单代付码</text>
|
||||
<text class="ppay__close" @click="close">关闭</text>
|
||||
</view>
|
||||
<view class="ppay__body">
|
||||
@@ -22,7 +22,7 @@
|
||||
<text class="ppay__loading-text">代付码生成中...</text>
|
||||
</view>
|
||||
<block v-else-if="qrcode">
|
||||
<!-- 后端返回完整 data:image/png;base64 数据,长按可转发给患者 -->
|
||||
<!-- src 是本地临时文件:小程序 <image> 不能稳渲染 data URI,长按仍可转发给患者 -->
|
||||
<image class="ppay__qrcode" :src="qrcode" mode="aspectFit" show-menu-by-longpress></image>
|
||||
<view class="ppay__meta">
|
||||
<view class="ppay__meta-row">
|
||||
@@ -34,7 +34,7 @@
|
||||
<text class="ppay__meta-value ppay__meta-value--price">¥{{ totalPayPrice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="ppay__tip">请让患者用微信「扫一扫」,进入小程序完成支付</view>
|
||||
<view class="ppay__tip">请让患者用微信「扫一扫」或者长按图片识别二维码打开「惠佑康」小程序,进入小程序完成支付</view>
|
||||
</block>
|
||||
<view v-else class="ppay__error">
|
||||
<text class="ppay__error-text">{{ errorText || '代付码生成失败' }}</text>
|
||||
@@ -52,10 +52,13 @@
|
||||
*
|
||||
* 用途:本次挂号处方存在待支付订单时,医生出示小程序码给患者扫码代付
|
||||
* (复用 PC 接诊台同款后端 proxy-pay-qrcode,scene=ppay={orderId},患者端解析链路零改动)。
|
||||
* 展示:接口返回 data URI,写入 USER_DATA_PATH 再绑 <image>,不上传 OSS。
|
||||
* 本组件与 base64ToLocalPath 都在 sub_workbench 分包,避免打进主包。
|
||||
* 读值:xk-api 形态,拦截器已返回 body → code / result / message(unwrapXkApi)
|
||||
*/
|
||||
import { getProxyPayQrcodeApi } from '@/api/reception.js'
|
||||
import { unwrapXkApi } from '@/utils/api-response.js'
|
||||
import { base64ToLocalPath, unlinkLocalFile } from '../../utils/base64ToLocalPath.js'
|
||||
|
||||
export default {
|
||||
name: 'ProxyPayQrcodeModal',
|
||||
@@ -89,12 +92,14 @@ export default {
|
||||
this.pageGuard = true
|
||||
this.load()
|
||||
} else {
|
||||
this.clearLocalQrcode()
|
||||
this.scheduleReleaseGuard()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearReleaseTimer()
|
||||
this.clearLocalQrcode()
|
||||
},
|
||||
methods: {
|
||||
releasePageGuard() {
|
||||
@@ -120,21 +125,30 @@ export default {
|
||||
onPopupInput(v) {
|
||||
this.show = v
|
||||
},
|
||||
/** 请求代付码(xk-api,取值 code/result/message) */
|
||||
/**
|
||||
* 清掉上一张本地码,避免 USER_DATA_PATH 堆积
|
||||
* 为什么先置空再 unlink:避免删除过程中 <image> 仍指向已删文件
|
||||
*/
|
||||
async clearLocalQrcode() {
|
||||
const path = this.qrcode
|
||||
this.qrcode = ''
|
||||
await unlinkLocalFile(path)
|
||||
},
|
||||
/** 请求代付码:接口仍返回 base64,写入本地后再绑 <image>(不上传 OSS) */
|
||||
async load() {
|
||||
if (!this.orderId) {
|
||||
this.qrcode = ''
|
||||
await this.clearLocalQrcode()
|
||||
this.errorText = '缺少订单信息'
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.qrcode = ''
|
||||
this.errorText = ''
|
||||
await this.clearLocalQrcode()
|
||||
try {
|
||||
const res = await getProxyPayQrcodeApi({ order_id: this.orderId })
|
||||
const { ok, payload, message } = unwrapXkApi(res)
|
||||
if (ok && payload && payload.qrcode) {
|
||||
this.qrcode = payload.qrcode
|
||||
this.qrcode = await base64ToLocalPath(payload.qrcode, 'ppay')
|
||||
this.orderNo = payload.order_no || ''
|
||||
this.totalPayPrice = payload.total_pay_price || ''
|
||||
} else {
|
||||
@@ -58,9 +58,9 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="rx-sent-ops">
|
||||
<!-- <view v-if="sentOrderId && sentOrderPaid != 1" class="rx-sent-btn rx-sent-btn--main" @click="openProxyPayFromSuccess">-->
|
||||
<!-- <text class="rx-sent-btn-txt">代付码</text>-->
|
||||
<!-- </view>-->
|
||||
<view v-if="sentOrderId && sentOrderPaid != 1" class="rx-sent-btn rx-sent-btn--main" @click="openProxyPayFromSuccess">
|
||||
<text class="rx-sent-btn-txt">代付码</text>
|
||||
</view>
|
||||
<view v-if="sentOrderId && publicAccountPayEnabled && !publicAccountDailyMode && sentOrderPaid != 1" class="rx-sent-btn" @click="openPublicAccountApply">
|
||||
<text class="rx-sent-btn-txt rx-sent-btn-txt--dark">申请公账支付</text>
|
||||
</view>
|
||||
@@ -863,7 +863,7 @@ import TraditionalTermPickerModal from './components/modals/TraditionalTermPicke
|
||||
import TransferPatientDrawer from './components/modals/TransferPatientDrawer.vue';
|
||||
import ApplyPublicAccountPayDrawer from './components/modals/ApplyPublicAccountPayDrawer.vue';
|
||||
import { unwrapXkApi } from '@/utils/api-response.js';
|
||||
import ProxyPayQrcodeModal from '@/components/proxy-pay-qrcode-modal/proxy-pay-qrcode-modal.vue';
|
||||
import ProxyPayQrcodeModal from '../components/proxy-pay-qrcode-modal/proxy-pay-qrcode-modal.vue';
|
||||
import WarehouseSelectDrawer from './components/modals/WarehouseSelectDrawer.vue';
|
||||
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue';
|
||||
import { applyRatioToDrugs, formatPriceDiscountLabel, normalizeQuickOptions } from '@/subPackages/sub_business_shared/common/pricePercentAdjust.js';
|
||||
@@ -1408,6 +1408,10 @@ export default {
|
||||
}
|
||||
},
|
||||
onUnload() {
|
||||
if (this._tabChangingTimer) {
|
||||
clearTimeout(this._tabChangingTimer);
|
||||
this._tabChangingTimer = null;
|
||||
}
|
||||
if (this.isCommonPrescription) {
|
||||
uni.removeStorageSync('add');
|
||||
}
|
||||
@@ -1499,6 +1503,11 @@ export default {
|
||||
// 延后关掉 loading,避免小程序在同帧大块挂载时触发 clickCheckTask / not iterable
|
||||
this.$nextTick(() => {
|
||||
this.pageLoading = false;
|
||||
this.$nextTick(() => {
|
||||
const holdMs = this._needHoldRxTabAfterReuse ? 320 : 50;
|
||||
this._needHoldRxTabAfterReuse = false;
|
||||
this.syncSwiperCurrentFromCategory(holdMs);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -1601,18 +1610,45 @@ export default {
|
||||
focusActiveTab() {
|
||||
this.syncSwiperCurrentFromCategory();
|
||||
},
|
||||
/** 根据 activeCategory 同步 swiperCurrent */
|
||||
syncSwiperCurrentFromCategory() {
|
||||
/**
|
||||
* 复用/导入后切到处方主 Tab,并按种类对齐 u-tabs + swiper
|
||||
* 为什么单独抽:只改 activeCategory 不会动 swiperCurrent,微信里 Tab 高亮和内容会错位
|
||||
* @param {number} category 处方类型 1中药 2西药 3/5/6/7 简单商品
|
||||
*/
|
||||
activateRxCategoryTab(category) {
|
||||
const pt = Number(category);
|
||||
if (pt) {
|
||||
this.activeCategory = pt;
|
||||
}
|
||||
if (this.canUseMedicalRecord && this.mainTab !== 'rx') {
|
||||
this.mainTab = 'rx';
|
||||
if (this.registerId) {
|
||||
PrescriptionStorage.saveRxMrTab('rx', this.registerId);
|
||||
}
|
||||
}
|
||||
this.syncSwiperCurrentFromCategory(320);
|
||||
this._needHoldRxTabAfterReuse = true;
|
||||
},
|
||||
/**
|
||||
* 根据 activeCategory 同步 swiper / u-tabs 下标
|
||||
* 微信 swiper 挂载或改 current 会异步抛 @change,nextTick 太短挡不住,用短延时锁回路
|
||||
* @param {number} holdMs 锁住 tabs↔swiper 回路的毫秒数
|
||||
*/
|
||||
syncSwiperCurrentFromCategory(holdMs = 50) {
|
||||
const list = this.swiperCategories || [];
|
||||
const idx = list.findIndex((c) => Number(c.value) === Number(this.activeCategory));
|
||||
const next = idx >= 0 ? idx : 0;
|
||||
if (next === this.swiperCurrent) return;
|
||||
// 程序化改 current 会触发 swiper @change,用 tabChanging 挡住回路
|
||||
this.tabChanging = true;
|
||||
this.swiperCurrent = next;
|
||||
this.$nextTick(() => {
|
||||
if (next !== this.swiperCurrent) {
|
||||
this.swiperCurrent = next;
|
||||
}
|
||||
if (this._tabChangingTimer) {
|
||||
clearTimeout(this._tabChangingTimer);
|
||||
}
|
||||
this._tabChangingTimer = setTimeout(() => {
|
||||
this.tabChanging = false;
|
||||
});
|
||||
this._tabChangingTimer = null;
|
||||
}, holdMs);
|
||||
},
|
||||
/** Tab 点击切换(与订单列表 onTabChange 一致) */
|
||||
onRxTabChange(index) {
|
||||
@@ -3013,10 +3049,17 @@ export default {
|
||||
this.$toast('患者信息异常');
|
||||
return false;
|
||||
}
|
||||
const typeTabs = this.swiperCategories || [];
|
||||
if (typeTabs.length && !typeTabs.some((c) => Number(c.value) === pt)) {
|
||||
this.$toast('当前门店未开通该处方类型,无法复用');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.saveToLocalStorage();
|
||||
this.activeCategory = pt;
|
||||
PrescriptionStorage.saveActiveCategory(pt, this.registerId);
|
||||
// 复用必须切到「处方」主 Tab + 对应种类;只改 activeCategory 不会动 u-tabs/swiper
|
||||
this.activateRxCategoryTab(pt);
|
||||
|
||||
const repiceList = Array.isArray(content.repice) ? content.repice : [];
|
||||
if (pt === 1) {
|
||||
@@ -3100,7 +3143,12 @@ export default {
|
||||
}
|
||||
const detail = res.result || res.data || res;
|
||||
const ok = await this.applyHistoricalPrescriptionDetail(detail);
|
||||
if (ok) this.$toast('已复用历史处方,请核对后发送');
|
||||
if (ok) {
|
||||
this.$toast('已复用历史处方,请核对后发送');
|
||||
this.$nextTick(() => {
|
||||
this.activateRxCategoryTab(this.activeCategory);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('复用处方失败:', e);
|
||||
this.$toast('加载处方详情失败');
|
||||
|
||||
83
subPackages/sub_workbench/utils/base64ToLocalPath.js
Normal file
83
subPackages/sub_workbench/utils/base64ToLocalPath.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 把后端返回的 data URI / 纯 base64 写成小程序本地文件
|
||||
* 为什么:微信 <image> 不能稳定渲染 data:image/...;base64(体积大或带 chunk_split 换行时真机空白),
|
||||
* 写成 USER_DATA_PATH 后用本地路径展示,无需上传 OSS
|
||||
* 放在 sub_workbench 分包:主包不能引用本文件,避免把工具打进主包
|
||||
*/
|
||||
|
||||
/**
|
||||
* 取可写的用户目录(真机优先 wx.env,H5/开发工具兜底 uni.env)
|
||||
* @returns {string}
|
||||
*/
|
||||
function getUserDataPath() {
|
||||
if (typeof wx !== 'undefined' && wx.env && wx.env.USER_DATA_PATH) {
|
||||
return wx.env.USER_DATA_PATH
|
||||
}
|
||||
if (uni.env && uni.env.USER_DATA_PATH) {
|
||||
return uni.env.USER_DATA_PATH
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* data URI / 纯 base64 写入本地临时文件,返回可供 <image :src> 绑定的路径
|
||||
* @param {string} base64 完整 data:image/xxx;base64,xxx 或纯 base64(可含 chunk_split 空白)
|
||||
* @param {string} prefix 文件名前缀,默认 img
|
||||
* @returns {Promise<string>} 本地文件路径
|
||||
*/
|
||||
export function base64ToLocalPath(base64, prefix = 'img') {
|
||||
return new Promise((resolve, reject) => {
|
||||
const raw = String(base64 || '').trim()
|
||||
if (!raw) {
|
||||
reject(new Error('图片数据为空'))
|
||||
return
|
||||
}
|
||||
const root = getUserDataPath()
|
||||
if (!root) {
|
||||
reject(new Error('无法获取临时目录'))
|
||||
return
|
||||
}
|
||||
const match = /^data:image\/(\w+);base64,/i.exec(raw)
|
||||
const format = (match && match[1] ? match[1] : 'png').toLowerCase()
|
||||
const body = (match ? raw.slice(match[0].length) : raw).replace(/\s/g, '')
|
||||
if (!body) {
|
||||
reject(new Error('图片数据无效'))
|
||||
return
|
||||
}
|
||||
const filePath = `${root}/${prefix}_${Date.now()}.${format}`
|
||||
uni.getFileSystemManager().writeFile({
|
||||
filePath,
|
||||
data: body,
|
||||
encoding: 'base64',
|
||||
success() {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail() {
|
||||
reject(new Error('本地图片写入失败'))
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除本地临时文件;已删或不存在不抛错,避免阻断关弹窗
|
||||
* @param {string} filePath writeFile 得到的路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function unlinkLocalFile(filePath) {
|
||||
return new Promise((resolve) => {
|
||||
if (!filePath) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
uni.getFileSystemManager().unlink({
|
||||
filePath,
|
||||
success() {
|
||||
resolve()
|
||||
},
|
||||
fail() {
|
||||
resolve()
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -325,7 +325,7 @@ import {
|
||||
VIP_FEATURE,
|
||||
} from '@/utils/vip.js'
|
||||
import VipGate from '@/components/vip-gate/vip-gate.vue'
|
||||
import ProxyPayQrcodeModal from '@/components/proxy-pay-qrcode-modal/proxy-pay-qrcode-modal.vue'
|
||||
import ProxyPayQrcodeModal from './components/proxy-pay-qrcode-modal/proxy-pay-qrcode-modal.vue'
|
||||
|
||||
function emptyHealthInquiry() {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user