feat: 锁定诊所、公账支付
This commit is contained in:
@@ -70,6 +70,9 @@ export function createBusinessApi(prefix, headerFlag) {
|
||||
submitWithdrawal(data) {
|
||||
return bizRequest({ url: `${prefix}-withdrawal/withdrawal`, method: 'POST', data });
|
||||
},
|
||||
getWithdrawableOrders(params) {
|
||||
return bizRequest({ url: `${prefix}-withdrawal/withdrawable-orders`, method: 'GET', data: params || {} });
|
||||
},
|
||||
passWithdrawal(data) {
|
||||
return bizRequest({ url: `${prefix}-withdrawal/pass-application`, method: 'POST', data });
|
||||
},
|
||||
|
||||
@@ -178,6 +178,11 @@ export function submitWithdrawal(data) {
|
||||
return clinicRequest({ url: `${PREFIX}-withdrawal/withdrawal`, method: 'POST', data });
|
||||
}
|
||||
|
||||
/** 可提订单明细 xk-api GET /clinic-admin-withdrawal/withdrawable-orders */
|
||||
export function getWithdrawableOrders(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-withdrawal/withdrawable-orders`, method: 'GET', data: params || {} });
|
||||
}
|
||||
|
||||
export function getReconciliationList(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-reconciliation/list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
78
api/publicAccountPay.js
Normal file
78
api/publicAccountPay.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 公账支付(xk-api,取值 unwrapXkApi → code / result / message)
|
||||
* 医生走 /public-account-pay;诊所管理员走 /clinic-admin-public-account-pay
|
||||
*/
|
||||
import { req } from '@/common/js/index.js'
|
||||
|
||||
function isClinicAdmin() {
|
||||
return uni.getStorageSync('loginMode') === 'clinic_admin'
|
||||
}
|
||||
|
||||
function dailyPrefix() {
|
||||
return isClinicAdmin()
|
||||
? '/newApi/clinic-admin-public-account-pay/'
|
||||
: '/newApi/public-account-pay/'
|
||||
}
|
||||
|
||||
function dailyHeader() {
|
||||
return isClinicAdmin() ? { _clinicAdmin: '1' } : {}
|
||||
}
|
||||
|
||||
/** 申请公账支付 xk-api POST /public-account-pay/apply */
|
||||
export function applyPublicAccountPayApi(data) {
|
||||
return req.request({
|
||||
url: '/newApi/public-account-pay/apply',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 日汇总详情 xk-api GET .../daily-detail */
|
||||
export function getPublicAccountDailyBillInfoApi(id) {
|
||||
return req.request({
|
||||
url: dailyPrefix() + 'daily-detail',
|
||||
method: 'GET',
|
||||
data: { id },
|
||||
header: dailyHeader(),
|
||||
})
|
||||
}
|
||||
|
||||
/** 补出日汇总 xk-api POST .../daily-generate */
|
||||
export function generatePublicAccountDailyBillApi(data) {
|
||||
return req.request({
|
||||
url: dailyPrefix() + 'daily-generate',
|
||||
method: 'POST',
|
||||
data,
|
||||
header: dailyHeader(),
|
||||
})
|
||||
}
|
||||
|
||||
/** 上传日汇总凭证 xk-api POST .../daily-upload */
|
||||
export function uploadPublicAccountDailyBillApi(data) {
|
||||
return req.request({
|
||||
url: dailyPrefix() + 'daily-upload',
|
||||
method: 'POST',
|
||||
data,
|
||||
header: dailyHeader(),
|
||||
})
|
||||
}
|
||||
|
||||
/** 催办确认到账 xk-api POST .../daily-urge */
|
||||
export function urgePublicAccountDailyBillApi(data) {
|
||||
return req.request({
|
||||
url: dailyPrefix() + 'daily-urge',
|
||||
method: 'POST',
|
||||
data,
|
||||
header: dailyHeader(),
|
||||
})
|
||||
}
|
||||
|
||||
/** 锁店现算状态(配色/日汇总状态/联系人)xk-api GET .../lock-status */
|
||||
export function getPublicAccountLockStatusApi() {
|
||||
return req.request({
|
||||
url: dailyPrefix() + 'lock-status',
|
||||
method: 'GET',
|
||||
data: {},
|
||||
header: dailyHeader(),
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import errorCode from './errorCode.js';
|
||||
import {Base64} from "js-base64";
|
||||
import { getDoctorWxEnv } from '@/config/app-env.js';
|
||||
import { resolveDoctorWxAuthToken } from '@/utils/doctorWxAuthToken.js';
|
||||
import { setPublicAccountLock } from '@/utils/publicAccountLock.js'
|
||||
|
||||
const { legacyApiBase } = getDoctorWxEnv();
|
||||
|
||||
@@ -156,6 +157,20 @@ const resInterceptor = (response, conf = {}) => {
|
||||
if (res == null || typeof res !== 'object') {
|
||||
return res
|
||||
}
|
||||
// 4101 锁店:先解密联系人(会写出 phone_txt 再脱敏 phone),再落库给锁店页拨号
|
||||
const bizCode = Number(res.code != null ? res.code : res.errcode)
|
||||
if (bizCode === 4101) {
|
||||
const payload = res.result || {}
|
||||
if (payload.contact_phones) {
|
||||
payload.contact_phones = getRes(payload.contact_phones, true)
|
||||
}
|
||||
setPublicAccountLock(payload)
|
||||
return {
|
||||
wakaryReqToReject: true,
|
||||
msg: res.message || '公账日账单已逾期',
|
||||
res: response
|
||||
}
|
||||
}
|
||||
if (res.data != null) {
|
||||
res.data = getRes(res.data)
|
||||
}
|
||||
@@ -163,7 +178,6 @@ const resInterceptor = (response, conf = {}) => {
|
||||
res.result = getRes(res.result)
|
||||
}
|
||||
// Laravel notAuth 返回 HTTP 200 + 业务码 401,需与 HTTP 401 走同一套登录失效处理
|
||||
const bizCode = Number(res.code != null ? res.code : res.errcode)
|
||||
if (bizCode === 401) {
|
||||
return handleAuthExpired(res.msg || res.message || '账号需重新登录', response, conf)
|
||||
}
|
||||
@@ -222,62 +236,68 @@ function _responseLog(res, conf = {}, describe = null) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 解密后写出 {字段}_txt 明文,给锁店拨号等场景用;原字段仍脱敏 */
|
||||
const PHONE_TXT_KEYS = ['phone', 'mobile', 'call_mobile', 'express_mobile', 'patient_mobile', 'accept_tel']
|
||||
|
||||
function getRes(obj, isDecode = true) {
|
||||
if (obj == null || typeof obj !== 'object') {
|
||||
return obj
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
if (obj[i] != null && typeof obj[i] === 'object') {
|
||||
obj[i] = getRes(obj[i], isDecode)
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
try {
|
||||
for (const key in obj) {
|
||||
if (Array.isArray(obj[key])) {
|
||||
obj[key] = getRes(obj[key])
|
||||
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
obj[key] = getRes(obj[key])
|
||||
} else {
|
||||
// 判断obj[key]是否在arr中
|
||||
if (arr.indexOf(key) !== -1) {
|
||||
if (obj[key] == null || obj[key] === '') continue
|
||||
let aseFile = ''
|
||||
if (isDecode === true) {
|
||||
aseFile = customBase64Decode(obj[key])
|
||||
} else {
|
||||
aseFile = customBase64Encode(obj[key])
|
||||
}
|
||||
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
|
||||
typeof aseFile === 'string' ? aseFile : ''
|
||||
)
|
||||
if (isGarbled) {
|
||||
aseFile = 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':
|
||||
case 'phone':
|
||||
// 保留前三位和后四位,中间用星号代替
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
obj[key] = aseFile
|
||||
if (obj[key] != null && typeof obj[key] === 'object') {
|
||||
obj[key] = getRes(obj[key], isDecode)
|
||||
} else if (arr.indexOf(key) !== -1) {
|
||||
if (obj[key] == null || obj[key] === '') continue
|
||||
let aseFile = ''
|
||||
if (isDecode === true) {
|
||||
aseFile = customBase64Decode(obj[key])
|
||||
} else {
|
||||
aseFile = customBase64Encode(obj[key])
|
||||
}
|
||||
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
|
||||
typeof aseFile === 'string' ? aseFile : ''
|
||||
)
|
||||
if (isGarbled) {
|
||||
aseFile = obj[key]
|
||||
}
|
||||
// 请求必须加密,不能跳过;解密后先留明文 _txt 再脱敏
|
||||
if (isDecode === true && PHONE_TXT_KEYS.indexOf(key) !== -1) {
|
||||
const plain = String(aseFile || '')
|
||||
if (/^1\d{10}$/.test(plain)) {
|
||||
obj[key + '_txt'] = plain
|
||||
}
|
||||
}
|
||||
if (isDecode === true && 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':
|
||||
case 'phone':
|
||||
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;
|
||||
}
|
||||
}
|
||||
obj[key] = aseFile
|
||||
}
|
||||
}
|
||||
return obj
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
@afterleave="releasePageGuard"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- z-index 高于自定义 tabbar(998) / 工作台看板浮卡,避免编辑抽屉被统计卡盖住 -->
|
||||
<u-popup
|
||||
:value="popupShow"
|
||||
mode="bottom"
|
||||
border-radius="24"
|
||||
height="80%"
|
||||
:z-index="10080"
|
||||
:safe-area-inset-bottom="true"
|
||||
:mask-close-able="true"
|
||||
@input="onPopupInput"
|
||||
|
||||
13
pages.json
13
pages.json
@@ -150,6 +150,14 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "处方详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "public_account_lock/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公账已锁定",
|
||||
"navigationStyle": "custom",
|
||||
"disableSwipeBack": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -324,6 +332,10 @@
|
||||
"path": "withdrawal/record-detail",
|
||||
"style": { "navigationBarTitleText": "提现详情", "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "withdrawal/withdrawable-orders",
|
||||
"style": { "navigationBarTitleText": "可提订单", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||
},
|
||||
{
|
||||
"path": "reconciliation/index",
|
||||
"style": { "navigationBarTitleText": "对账单", "navigationStyle": "custom", "enablePullDownRefresh": true }
|
||||
@@ -375,6 +387,7 @@
|
||||
{ "path": "withdrawal/apply", "style": { "navigationBarTitleText": "申请提现", "navigationStyle": "custom" } },
|
||||
{ "path": "withdrawal/card-edit", "style": { "navigationBarTitleText": "编辑账户", "navigationStyle": "custom" } },
|
||||
{ "path": "withdrawal/record-detail", "style": { "navigationBarTitleText": "提现详情", "navigationStyle": "custom" } },
|
||||
{ "path": "withdrawal/withdrawable-orders", "style": { "navigationBarTitleText": "可提订单", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||
{ "path": "withdrawal/settlement/index", "style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||
{ "path": "withdrawal/settlement/detail", "style": { "navigationBarTitleText": "结算明细", "navigationStyle": "custom" } },
|
||||
{ "path": "withdrawal/account-change/index", "style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom", "enablePullDownRefresh": true } },
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<view class="row"><text class="label">时间</text><text class="value">{{ item.created_at }}</text></view>
|
||||
<view class="row" v-if="item.remark"><text class="label">备注</text><text class="value">{{ item.remark }}</text></view>
|
||||
<view
|
||||
v-if="Number(item.order_type) === 1 && item.order_id"
|
||||
v-if="item.order_id || (item.orders && item.orders.length)"
|
||||
class="order-link"
|
||||
@click="goOrderDetail(item)"
|
||||
>查看订单详情</view>
|
||||
@@ -87,23 +87,39 @@ export default {
|
||||
this.reload()
|
||||
},
|
||||
reload() {
|
||||
this.load(1, false)
|
||||
return this.load(1, false)
|
||||
},
|
||||
load(page, append) {
|
||||
this.loading = true
|
||||
const params = { page, pageSize: 15 }
|
||||
if (this.sourceType) params.source_type = this.sourceType
|
||||
if (this.orderNo.trim()) params.order_no = this.orderNo.trim()
|
||||
this.bizApi.getAccountChangeLogList(params).then(w => {
|
||||
return this.bizApi.getAccountChangeLogList(params).then(w => {
|
||||
const items = (w.data && w.data.items) || []
|
||||
this.list = append ? this.list.concat(items) : items
|
||||
this.page = page
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
canViewChangeOrder(item) {
|
||||
const orders = item.orders || []
|
||||
return (Number(item.order_id) > 0) || orders.length > 0
|
||||
},
|
||||
goOrderDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: `${this.bizRoot}/order/detail?id=${item.order_id}`,
|
||||
})
|
||||
const orders = item.orders || []
|
||||
const target = orders.length > 0 ? orders[0] : item
|
||||
const orderType = Number(target.order_type || item.order_type || 0)
|
||||
const orderId = target.order_id || item.order_id
|
||||
if (orderType === 1 && orderId) {
|
||||
uni.navigateTo({ url: `${this.bizRoot}/order/detail?id=${orderId}` })
|
||||
return
|
||||
}
|
||||
if (orderType === 2 && orderId) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${orderId}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,12 +1,61 @@
|
||||
<template>
|
||||
<business-page-layout title="申请提现" :show-back="true">
|
||||
<view class="tip">可提现余额:{{ balanceText }}</view>
|
||||
<view v-if="gapText" class="gap">
|
||||
<text>{{ gapText }}</text>
|
||||
<view
|
||||
v-for="row in gapApplies"
|
||||
:key="row.id"
|
||||
class="gap-order"
|
||||
@click.stop="goApplyDetail(row.id)"
|
||||
>{{ row.order_no }} · {{ row.check_status_txt || '' }} · {{ row.dakuan_status_txt || '' }}</view>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">提现方式</text>
|
||||
<view class="mode-row">
|
||||
<text
|
||||
v-for="m in modes"
|
||||
:key="m.value"
|
||||
class="mode-btn"
|
||||
:class="{ on: mode === m.value }"
|
||||
@click="switchMode(m.value)"
|
||||
>{{ m.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="mode === 1" class="field">
|
||||
<text class="label">提现金额</text>
|
||||
<input v-model="amount" type="digit" placeholder="请输入金额" />
|
||||
<view class="quick">
|
||||
<text v-for="q in quickAmounts" :key="q.key" class="q-btn" @click="amount = String(q.value)">{{ q.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="quick">
|
||||
<text v-for="q in quickAmounts" :key="q.key" class="q-btn" @click="amount = String(q.value)">{{ q.value }}</text>
|
||||
<view v-if="mode === 3" class="field">
|
||||
<text class="label">时间范围</text>
|
||||
<picker mode="date" :value="startDate" @change="onStartDate">
|
||||
<view class="picker">开始:{{ startDate || '请选择' }}</view>
|
||||
</picker>
|
||||
<picker mode="date" :value="endDate" @change="onEndDate">
|
||||
<view class="picker">结束:{{ endDate || '请选择' }}</view>
|
||||
</picker>
|
||||
<picker :range="categoryLabels" @change="onCategory">
|
||||
<view class="picker">类型:{{ currentCategoryLabel }}</view>
|
||||
</picker>
|
||||
<view class="preview" @click="loadOrders">预览订单({{ orderList.length }} 笔)</view>
|
||||
</view>
|
||||
<view v-if="mode === 2 || mode === 3" class="order-list">
|
||||
<view
|
||||
v-for="item in orderList"
|
||||
:key="item.uk"
|
||||
class="order-item"
|
||||
@click="toggleOrder(item)"
|
||||
>
|
||||
<text class="chk">{{ isSelected(item) ? '☑' : '☐' }}</text>
|
||||
<view class="order-main">
|
||||
<text class="no">{{ item.order_no || '-' }}</text>
|
||||
<text class="sub">{{ item.category_txt }} · {{ item.remain }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="orderList.length === 0" class="empty">暂无可提订单</view>
|
||||
</view>
|
||||
<button class="submit" @click="submit" :loading="loading">提交申请</button>
|
||||
</business-page-layout>
|
||||
@@ -15,7 +64,7 @@
|
||||
<script>
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
|
||||
import { formatMoney } from '@/subPackages/sub_business_shared/common/format.js'
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin],
|
||||
@@ -24,7 +73,27 @@ export default {
|
||||
return {
|
||||
amount: '',
|
||||
balanceText: '-',
|
||||
balance: 0,
|
||||
gapText: '',
|
||||
gapApplies: [],
|
||||
loading: false,
|
||||
mode: 2,
|
||||
category: 0,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
orderList: [],
|
||||
selectedUks: [],
|
||||
modes: [
|
||||
{ label: '按订单', value: 2 },
|
||||
{ label: '按时间', value: 3 },
|
||||
{ label: '自定义', value: 1 },
|
||||
],
|
||||
categoryOptions: [
|
||||
{ label: '全部类型', value: 0 },
|
||||
{ label: '中药', value: 1 },
|
||||
{ label: '西药', value: 2 },
|
||||
{ label: '其他', value: 3 },
|
||||
],
|
||||
quickAmounts: [
|
||||
{ key: '1000', value: 1000 },
|
||||
{ key: '3000', value: 3000 },
|
||||
@@ -33,24 +102,151 @@ export default {
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
categoryLabels() {
|
||||
return this.categoryOptions.map(o => o.label)
|
||||
},
|
||||
currentCategoryLabel() {
|
||||
const hit = this.categoryOptions.find(o => o.value === this.category)
|
||||
return hit ? hit.label : '全部类型'
|
||||
},
|
||||
selectedSum() {
|
||||
const picked = this.orderList.filter(item => this.selectedUks.indexOf(item.uk) >= 0)
|
||||
return picked.reduce((sum, item) => sum + Number(item.remain || 0), 0)
|
||||
},
|
||||
rangeSum() {
|
||||
return this.orderList.reduce((sum, item) => sum + Number(item.remain || 0), 0)
|
||||
},
|
||||
requestedSum() {
|
||||
if (this.mode === 2) return this.selectedSum
|
||||
if (this.mode === 3) return this.rangeSum
|
||||
return 0
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
if (!this.bizApi.getMyBalance) return
|
||||
this.bizApi.getMyBalance().then(w => {
|
||||
if (w.ok && w.data) {
|
||||
const b = w.data.balance
|
||||
this.balance = Number(b || 0)
|
||||
this.balanceText = b != null ? formatMoney(b) : '-'
|
||||
const gap = Number(w.data.custom_gap || 0)
|
||||
this.gapApplies = w.data.custom_gap_applies || []
|
||||
if (gap > 0) {
|
||||
this.gapText = `按单合计比余额多 ${gap.toFixed(2)} 元,点打款单号可看申请→审核→打款进度`
|
||||
}
|
||||
}
|
||||
})
|
||||
this.loadOrders()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 切换提现方式:清已选和旧列表,避免按时间窄集残留到按订单导致漏提
|
||||
* 按订单立刻重拉全量;按时间等用户点预览;自定义不拉单
|
||||
*/
|
||||
switchMode(next) {
|
||||
if (this.mode === next) return
|
||||
this.mode = next
|
||||
this.selectedUks = []
|
||||
this.orderList = []
|
||||
if (next === 2) {
|
||||
this.loadOrders()
|
||||
}
|
||||
},
|
||||
onStartDate(e) { this.startDate = e.detail.value },
|
||||
onEndDate(e) { this.endDate = e.detail.value },
|
||||
onCategory(e) {
|
||||
this.category = this.categoryOptions[Number(e.detail.value || 0)].value
|
||||
},
|
||||
toTs(dateStr, end) {
|
||||
if (!dateStr) return 0
|
||||
const t = new Date(dateStr.replace(/-/g, '/') + (end ? ' 23:59:59' : ' 00:00:00')).getTime()
|
||||
return t ? Math.floor(t / 1000) : 0
|
||||
},
|
||||
loadOrders() {
|
||||
if (!this.bizApi.getWithdrawableOrders) return
|
||||
const params = { page: 1, pageSize: 50, category: this.category }
|
||||
if (this.mode === 3) {
|
||||
params.start_at = this.toTs(this.startDate, false)
|
||||
params.end_at = this.toTs(this.endDate, true)
|
||||
}
|
||||
this.bizApi.getWithdrawableOrders(params).then(w => {
|
||||
const items = ((w.data && w.data.items) || []).map(item => ({
|
||||
...item,
|
||||
uk: item.order_id + '_' + item.order_type,
|
||||
}))
|
||||
this.orderList = items
|
||||
})
|
||||
},
|
||||
isSelected(item) {
|
||||
return this.selectedUks.indexOf(item.uk) >= 0
|
||||
},
|
||||
toggleOrder(item) {
|
||||
if (this.mode !== 2) return
|
||||
const idx = this.selectedUks.indexOf(item.uk)
|
||||
if (idx >= 0) {
|
||||
this.selectedUks.splice(idx, 1)
|
||||
} else {
|
||||
this.selectedUks.push(item.uk)
|
||||
}
|
||||
},
|
||||
goApplyDetail(id) {
|
||||
if (!id) return
|
||||
uni.navigateTo({ url: `${this.bizRoot}/withdrawal/record-detail?id=${id}` })
|
||||
},
|
||||
submit() {
|
||||
const amt = Number(this.amount)
|
||||
if (!amt || amt <= 0) {
|
||||
uni.showToast({ title: '请输入有效金额', icon: 'none' })
|
||||
let payload = { mode: this.mode, category: this.category }
|
||||
if (this.mode === 1) {
|
||||
const amt = Number(this.amount)
|
||||
if (!amt || amt <= 0) {
|
||||
uni.showToast({ title: '请输入有效金额', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payload.amount = amt
|
||||
} else if (this.mode === 2) {
|
||||
const picked = this.orderList.filter(item => this.selectedUks.indexOf(item.uk) >= 0)
|
||||
if (picked.length === 0) {
|
||||
uni.showToast({ title: '请选择订单', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payload.order_ids = picked.map(item => ({
|
||||
order_id: item.order_id,
|
||||
order_type: item.order_type,
|
||||
}))
|
||||
} else {
|
||||
if (!this.startDate || !this.endDate) {
|
||||
uni.showToast({ title: '请选择时间范围', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payload.start_at = this.toTs(this.startDate, false)
|
||||
payload.end_at = this.toTs(this.endDate, true)
|
||||
}
|
||||
if (this.mode !== 1 && this.requestedSum > Number(this.balance || 0) + 0.0001) {
|
||||
this.confirmGapThenSubmit(payload)
|
||||
return
|
||||
}
|
||||
this.doSubmit(payload)
|
||||
},
|
||||
confirmGapThenSubmit(payload) {
|
||||
const requested = this.requestedSum
|
||||
const balance = Number(this.balance || 0)
|
||||
const gap = (requested - balance).toFixed(2)
|
||||
uni.showModal({
|
||||
title: '超过可提余额',
|
||||
content: `按单合计 ${requested.toFixed(2)} 元超过余额 ${balance.toFixed(2)} 元,差额 ${gap} 元。确认后按余额申请。`,
|
||||
confirmText: '按余额提',
|
||||
cancelText: '暂不提现',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
payload.accept_gap = 1
|
||||
this.doSubmit(payload)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
doSubmit(payload) {
|
||||
this.loading = true
|
||||
this.bizApi.submitWithdrawal({ amount: amt }).then(w => {
|
||||
this.bizApi.submitWithdrawal(payload).then(w => {
|
||||
if (w.ok) {
|
||||
uni.showToast({ title: '提交成功' })
|
||||
setTimeout(() => uni.navigateBack(), 800)
|
||||
@@ -64,42 +260,35 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tip {
|
||||
.tip, .field, .gap {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 32rpx;
|
||||
background: #fff;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.field {
|
||||
background: #fff;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
background: #fff;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.quick {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
.q-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
background: #eef8f6;
|
||||
color: #6acdbb;
|
||||
.gap { color: #B45309; background: #FFF7ED; }
|
||||
.gap-order { display: block; margin-top: 12rpx; color: #0F766E; }
|
||||
.label { display: block; font-size: 26rpx; color: #999; margin-bottom: 12rpx; }
|
||||
.mode-row { display: flex; gap: 16rpx; }
|
||||
.mode-btn {
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #F2F3F5;
|
||||
color: #4E5969;
|
||||
font-size: 26rpx;
|
||||
&.on { background: #eef8f6; color: #6acdbb; }
|
||||
}
|
||||
.submit {
|
||||
background: #6acdbb;
|
||||
color: #fff;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
.quick { display: flex; flex-wrap: wrap; gap: 16rpx; margin-top: 16rpx; }
|
||||
.q-btn { padding: 12rpx 24rpx; background: #eef8f6; color: #6acdbb; border-radius: 8rpx; font-size: 26rpx; }
|
||||
.picker { padding: 16rpx 0; color: #1D2129; }
|
||||
.preview { margin-top: 12rpx; color: #6acdbb; }
|
||||
.order-list { background: #fff; border-radius: 16rpx; padding: 8rpx 24rpx; margin-bottom: 32rpx; }
|
||||
.order-item { display: flex; gap: 16rpx; padding: 20rpx 0; border-bottom: 1rpx solid #F2F3F5; }
|
||||
.chk { color: #6acdbb; font-size: 32rpx; }
|
||||
.order-main { flex: 1; }
|
||||
.no { display: block; color: #1D2129; font-size: 26rpx; }
|
||||
.sub { color: #86909C; font-size: 24rpx; }
|
||||
.empty { text-align: center; color: #86909C; padding: 32rpx 0; }
|
||||
.submit { background: #6acdbb; color: #fff; border-radius: 44rpx; }
|
||||
</style>
|
||||
|
||||
@@ -21,9 +21,30 @@
|
||||
|
||||
<!-- 核心统计网格 -->
|
||||
<view class="stats-grid">
|
||||
<view v-for="b in balanceItems" :key="b.key" class="stat-island">
|
||||
<view
|
||||
v-for="b in balanceItems"
|
||||
:key="b.key"
|
||||
class="stat-island"
|
||||
:class="{ 'stat-island-balance': b.key == 'balance' }"
|
||||
hover-class="stat-hover"
|
||||
@click="onStatClick(b.key)"
|
||||
>
|
||||
<text class="stat-label">{{ b.label }}</text>
|
||||
<text class="stat-val">{{ b.display }}</text>
|
||||
<view v-if="b.key == 'balance'" class="stat-break">
|
||||
<view class="stat-chip" @click.stop="onStatClick('tcm')">
|
||||
<text class="stat-chip-label">中药</text>
|
||||
<text class="stat-chip-val">{{ tcmDisplay }}</text>
|
||||
</view>
|
||||
<view class="stat-chip" @click.stop="onStatClick('western')">
|
||||
<text class="stat-chip-label">西药</text>
|
||||
<text class="stat-chip-val">{{ westernDisplay }}</text>
|
||||
</view>
|
||||
<view class="stat-chip" @click.stop="onStatClick('other')">
|
||||
<text class="stat-chip-label">其他</text>
|
||||
<text class="stat-chip-val">{{ otherDisplay }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -97,6 +118,9 @@ export default {
|
||||
{ key: 'reviewing', label: '审核中金额 (元)', display: '0.00' },
|
||||
{ key: 'pending', label: '待结算收益 (元)', display: '0.00' },
|
||||
],
|
||||
tcmDisplay: '0.00',
|
||||
westernDisplay: '0.00',
|
||||
otherDisplay: '0.00',
|
||||
actions: [
|
||||
{ key: 'apply', label: '申请提现' },
|
||||
{ key: 'settlement', label: '结算记录' },
|
||||
@@ -125,6 +149,16 @@ export default {
|
||||
return `**** **** **** ${last4}`;
|
||||
},
|
||||
|
||||
onStatClick(key) {
|
||||
const map = { balance: 0, tcm: 1, western: 2, other: 3 }
|
||||
if (map[key] !== undefined) {
|
||||
uni.navigateTo({ url: `${this.bizRoot}/withdrawal/withdrawable-orders?category=${map[key]}` })
|
||||
return
|
||||
}
|
||||
if (key === 'pending') {
|
||||
uni.navigateTo({ url: `${this.bizRoot}/withdrawal/settlement/index` })
|
||||
}
|
||||
},
|
||||
// 快捷操作跳转
|
||||
onAction(key) {
|
||||
if (key === 'apply') {
|
||||
@@ -145,8 +179,8 @@ export default {
|
||||
|
||||
// 获取绑定的银行卡信息
|
||||
loadCard() {
|
||||
if (!this.bizApi.getMyCard) return
|
||||
this.bizApi.getMyCard().then(w => {
|
||||
if (!this.bizApi.getMyCard) return Promise.resolve()
|
||||
return this.bizApi.getMyCard().then(w => {
|
||||
if (w.ok && w.data) {
|
||||
this.card = w.data;
|
||||
this.cardTitle = w.data.bank_user_name || '我的账户';
|
||||
@@ -156,8 +190,8 @@ export default {
|
||||
|
||||
// 获取余额数据
|
||||
loadBalance() {
|
||||
if (!this.bizApi.getMyBalance) return
|
||||
this.bizApi.getMyBalance().then(w => {
|
||||
if (!this.bizApi.getMyBalance) return Promise.resolve()
|
||||
return this.bizApi.getMyBalance().then(w => {
|
||||
if (w.ok && w.data) {
|
||||
const fmt = (v) => formatMoney(v || 0).replace('¥', '')
|
||||
const map = {
|
||||
@@ -165,6 +199,9 @@ export default {
|
||||
reviewing: fmt(w.data.withdrawn_frozen),
|
||||
pending: fmt(w.data.pending_earnings),
|
||||
}
|
||||
this.tcmDisplay = fmt(w.data.tcm_balance)
|
||||
this.westernDisplay = fmt(w.data.western_balance)
|
||||
this.otherDisplay = fmt(w.data.other_balance)
|
||||
this.balanceItems = this.balanceItems.map(item => ({
|
||||
...item,
|
||||
display: map[item.key] ?? '0.00',
|
||||
@@ -173,24 +210,25 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 刷新提现列表
|
||||
/**
|
||||
* 下拉刷新:卡、余额、列表一起重拉
|
||||
* 下拉刷新:卡、余额、列表一起重拉,返回 Promise 给 mixin 等请求结束再收起转圈
|
||||
*/
|
||||
reload() {
|
||||
this.loadCard()
|
||||
this.loadBalance()
|
||||
this.reloadList()
|
||||
return Promise.all([
|
||||
this.loadCard(),
|
||||
this.loadBalance(),
|
||||
this.reloadList(),
|
||||
])
|
||||
},
|
||||
reloadList() {
|
||||
this.page = 1;
|
||||
this.loadList(1, false);
|
||||
return this.loadList(1, false);
|
||||
},
|
||||
|
||||
// 加载提现列表数据
|
||||
loadList(page, append) {
|
||||
this.loading = true;
|
||||
this.bizApi.getWithdrawalList({ page, pageSize: 15 }).then(w => {
|
||||
return this.bizApi.getWithdrawalList({ page, pageSize: 15 }).then(w => {
|
||||
if (w.ok && w.data) {
|
||||
const items = (w.data.items || []).map(item => ({
|
||||
...item,
|
||||
@@ -288,11 +326,15 @@ $color-text-muted: #86909C;
|
||||
/* 统计网格 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.stat-island-balance {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.stat-island {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
@@ -301,6 +343,7 @@ $color-text-muted: #86909C;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.stat-hover { background: #F7F8FA; }
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
@@ -315,6 +358,35 @@ $color-text-muted: #86909C;
|
||||
font-family: DIN Alternate, monospace, sans-serif;
|
||||
}
|
||||
|
||||
.stat-break {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.stat-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
background: #F5F7F8;
|
||||
border: 1px solid rgba(106, 205, 187, 0.3);
|
||||
border-radius: 999rpx;
|
||||
box-shadow: 0 0 8rpx rgba(106, 205, 187, 0.12);
|
||||
}
|
||||
|
||||
.stat-chip-label {
|
||||
font-size: 20rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.stat-chip-val {
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-main;
|
||||
}
|
||||
|
||||
/* 金刚区操作栏 */
|
||||
.action-bar {
|
||||
display: flex;
|
||||
|
||||
@@ -17,10 +17,26 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="progress">
|
||||
<view
|
||||
v-for="n in progressNodes"
|
||||
:key="n.title"
|
||||
class="progress-item"
|
||||
:class="{ 'is-done': n.state === 'done', 'is-on': n.state === 'on', 'is-fail': n.state === 'fail' }"
|
||||
>
|
||||
<text class="progress-title">{{ n.title }}</text>
|
||||
<text class="progress-desc">{{ n.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 明细信息 -->
|
||||
<view class="info-list">
|
||||
<view class="row"><text class="label">订单号</text><text class="value font-mono">{{ info.order_no || '-' }}</text></view>
|
||||
<view class="row"><text class="label">申请金额</text><text class="value">{{ formatMoney(info.apply_cash) }}</text></view>
|
||||
<view class="row" v-if="info.mode_txt"><text class="label">提现方式</text><text class="value">{{ info.mode_txt }}</text></view>
|
||||
<view class="row"><text class="label">中药</text><text class="value">{{ formatMoney(info.tcm_amount) }}</text></view>
|
||||
<view class="row"><text class="label">西药</text><text class="value">{{ formatMoney(info.western_amount) }}</text></view>
|
||||
<view class="row"><text class="label">其他</text><text class="value">{{ formatMoney(info.other_amount) }}</text></view>
|
||||
<view class="row"><text class="label">手续费</text><text class="value">{{ formatMoney(info.charge_cash) }}</text></view>
|
||||
<view class="row"><text class="label">申请时间</text><text class="value">{{ formatDateTime(info.apply_time) }}</text></view>
|
||||
|
||||
@@ -35,6 +51,18 @@
|
||||
<view class="row"><text class="label">打款状态</text><text class="value">{{ dakuanStatusText(info.dakuan_status) }}</text></view>
|
||||
<view class="row" v-if="info.dakuan_time"><text class="label">打款时间</text><text class="value">{{ formatDateTime(info.dakuan_time) }}</text></view>
|
||||
</view>
|
||||
<view v-if="info.orders && info.orders.length" class="order-box">
|
||||
<text class="order-hd">关联订单</text>
|
||||
<view
|
||||
v-for="od in orderRows"
|
||||
:key="od.row_key"
|
||||
class="order-row"
|
||||
@click="goOrder(od)"
|
||||
>
|
||||
<text>{{ od.order_no || '-' }} · {{ od.category_txt || '' }}</text>
|
||||
<text class="order-amt">{{ od.amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -51,12 +79,59 @@ import {
|
||||
} from '@/subPackages/sub_business_shared/common/display.js'
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
|
||||
function buildProgressNodes(info) {
|
||||
const check = Number((info && info.check_status) || 0)
|
||||
const dakuan = Number((info && info.dakuan_status) || 0)
|
||||
const applyTime = (info && info.apply_time) || '已提交'
|
||||
const checkTime = (info && info.check_time) || ''
|
||||
const dakuanTime = (info && info.dakuan_time) || ''
|
||||
const checkResult = (info && info.check_result) || ''
|
||||
const apply = { title: '申请', desc: applyTime, state: 'done' }
|
||||
let audit = { title: '审核', desc: '待审核', state: 'wait' }
|
||||
if (check === 1) audit = { title: '审核', desc: '待审核', state: 'on' }
|
||||
else if (check === 2) audit = { title: '审核', desc: checkTime || '审核通过', state: 'done' }
|
||||
else if (check === 3) audit = { title: '审核', desc: checkResult || '已拒绝', state: 'fail' }
|
||||
else if (check === 4) audit = { title: '审核', desc: checkResult || '提现失败', state: 'fail' }
|
||||
let pay = { title: '打款', desc: '待打款', state: 'wait' }
|
||||
let arrived = { title: '到账', desc: '尚未打款', state: 'wait' }
|
||||
if (check === 1) {
|
||||
pay = { title: '打款', desc: '待审核通过后打款', state: 'wait' }
|
||||
} else if (check === 3) {
|
||||
pay = { title: '打款', desc: '已拒绝,未打款', state: 'wait' }
|
||||
arrived = { title: '到账', desc: '未到账', state: 'wait' }
|
||||
} else if (check === 4 || dakuan === -1) {
|
||||
pay = { title: '打款', desc: dakuanTime || '打款失败', state: 'fail' }
|
||||
arrived = { title: '到账', desc: '未到账', state: 'fail' }
|
||||
} else if (dakuan === 1) {
|
||||
pay = { title: '打款', desc: dakuanTime || '打款成功', state: 'done' }
|
||||
arrived = { title: '到账', desc: dakuanTime || '已到账', state: 'done' }
|
||||
} else if (check === 2) {
|
||||
pay = { title: '打款', desc: '打款处理中', state: 'on' }
|
||||
arrived = { title: '到账', desc: '等待到账', state: 'wait' }
|
||||
}
|
||||
return [apply, audit, pay, arrived]
|
||||
}
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin],
|
||||
components: { BusinessPageLayout },
|
||||
data() {
|
||||
return { id: 0, info: {} }
|
||||
},
|
||||
computed: {
|
||||
progressNodes() {
|
||||
return buildProgressNodes(this.info)
|
||||
},
|
||||
// 非 H5 的 :key 不能写表达式,预先落到单一字段
|
||||
orderRows() {
|
||||
const list = (this.info && this.info.orders) || []
|
||||
return list.map((od, i) => {
|
||||
const row = Object.assign({}, od)
|
||||
row.row_key = String(od.id || od.order_id || i)
|
||||
return row
|
||||
})
|
||||
},
|
||||
},
|
||||
onLoad(q) {
|
||||
this.id = Number(q.id || 0)
|
||||
this.bizApi.getWithdrawalDetail(this.id).then(w => {
|
||||
@@ -71,6 +146,20 @@ export default {
|
||||
},
|
||||
dakuanStatusText(s) {
|
||||
return dakuanStatusLabel(s)
|
||||
},
|
||||
goOrder(item) {
|
||||
const orderType = Number(item.order_type || 0)
|
||||
if (orderType === 1 && item.order_id) {
|
||||
uni.navigateTo({ url: `${this.bizRoot}/order/detail?id=${item.order_id}` })
|
||||
return
|
||||
}
|
||||
if (orderType === 2 && item.order_id) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${item.order_id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -168,4 +257,42 @@ $color-text-muted: #86909C;
|
||||
background-color: #EAEFEF;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
.order-box { margin-top: 24rpx; }
|
||||
.order-hd { display: block; font-size: 26rpx; color: #86909C; margin-bottom: 12rpx; }
|
||||
.order-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #6acdbb;
|
||||
}
|
||||
.order-amt { color: #1D2129; }
|
||||
.progress {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.progress-item {
|
||||
flex: 1;
|
||||
padding: 16rpx 8rpx;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
background: #F5F7F8;
|
||||
}
|
||||
.progress-item.is-done { background: rgba(106, 205, 187, 0.12); }
|
||||
.progress-item.is-on { background: #FFF3E8; }
|
||||
.progress-item.is-fail { background: #FFECE8; }
|
||||
.progress-title {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #1D2129;
|
||||
}
|
||||
.progress-desc {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
font-size: 20rpx;
|
||||
color: #86909C;
|
||||
}
|
||||
</style>
|
||||
@@ -55,7 +55,7 @@
|
||||
<view class="card-actions">
|
||||
<text class="action-link" @click.stop="goSettlementDetail(item)">结算明细</text>
|
||||
<text
|
||||
v-if="Number(item.order_type) === 1"
|
||||
v-if="Number(item.order_type) === 1 || Number(item.order_type) === 2"
|
||||
class="action-link primary"
|
||||
@click.stop="goOrderDetail(item)"
|
||||
>订单详情</text>
|
||||
@@ -103,7 +103,7 @@ export default {
|
||||
this.reload()
|
||||
},
|
||||
reload() {
|
||||
this.load(1, false)
|
||||
return this.load(1, false)
|
||||
},
|
||||
load(page, append) {
|
||||
this.loading = true
|
||||
@@ -116,7 +116,7 @@ export default {
|
||||
if (this.orderNo.trim()) {
|
||||
params.order_no = this.orderNo.trim()
|
||||
}
|
||||
this.bizApi.getLedgerLogList(params).then(w => {
|
||||
return this.bizApi.getLedgerLogList(params).then(w => {
|
||||
const items = (w.data && w.data.items) || []
|
||||
this.list = append ? this.list.concat(items) : items
|
||||
this.page = page
|
||||
@@ -134,13 +134,18 @@ export default {
|
||||
})
|
||||
},
|
||||
goOrderDetail(item) {
|
||||
if (Number(item.order_type) !== 1 || !item.order_id) {
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
const orderType = Number(item.order_type || 0)
|
||||
if (orderType === 1 && item.order_id) {
|
||||
uni.navigateTo({ url: `${this.bizRoot}/order/detail?id=${item.order_id}` })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `${this.bizRoot}/order/detail?id=${item.order_id}`,
|
||||
})
|
||||
if (orderType === 2 && item.order_id) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${item.order_id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<business-page-layout :title="pageTitle" :show-back="true">
|
||||
<view class="page">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item._wxKey"
|
||||
class="card"
|
||||
hover-class="card-hover"
|
||||
>
|
||||
<view class="row head">
|
||||
<text class="no">{{ item.order_no || '-' }}</text>
|
||||
<text class="amt">{{ item.remain }}</text>
|
||||
</view>
|
||||
<view class="row"><text class="label">分类</text><text class="value">{{ item.category_txt || '-' }}</text></view>
|
||||
<view class="row"><text class="label">订单类型</text><text class="value">{{ item.order_type_txt || '-' }}</text></view>
|
||||
<view class="row"><text class="label">结算时间</text><text class="value">{{ item.settled_at_txt || '-' }}</text></view>
|
||||
<text class="link" @click="goOrder(item)">查看订单</text>
|
||||
</view>
|
||||
<view v-if="!loading && list.length === 0" class="empty">暂无可提订单</view>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
import pullRefreshMixin from '@/subPackages/sub_business_shared/common/pullRefreshMixin.js'
|
||||
import { withWxKey } from '@/utils/wxListKey.js'
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin, pullRefreshMixin],
|
||||
components: { BusinessPageLayout },
|
||||
data() {
|
||||
return {
|
||||
category: 0,
|
||||
pageTitle: '可提订单',
|
||||
list: [],
|
||||
page: 1,
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
onLoad(q) {
|
||||
this.category = Number(q.category || 0)
|
||||
const titles = { 0: '全部可提订单', 1: '中药可提订单', 2: '西药可提订单', 3: '其他可提订单' }
|
||||
this.pageTitle = titles[this.category] || '可提订单'
|
||||
},
|
||||
onShow() {
|
||||
this.reload()
|
||||
},
|
||||
onReachBottom() {
|
||||
this.load(this.page + 1, true)
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
return this.load(1, false)
|
||||
},
|
||||
load(page, append) {
|
||||
if (!this.bizApi.getWithdrawableOrders) return Promise.resolve()
|
||||
this.loading = true
|
||||
return this.bizApi.getWithdrawableOrders({
|
||||
page,
|
||||
pageSize: 15,
|
||||
category: this.category,
|
||||
}).then(w => {
|
||||
const items = withWxKey((w.data && w.data.items) || [], 'order_id', 'wo')
|
||||
this.list = append ? this.list.concat(items) : items
|
||||
this.page = page
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
goOrder(item) {
|
||||
const orderType = Number(item.order_type || 0)
|
||||
if (orderType === 1 && item.order_id) {
|
||||
uni.navigateTo({ url: `${this.bizRoot}/order/detail?id=${item.order_id}` })
|
||||
return
|
||||
}
|
||||
if (orderType === 2 && item.order_id) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${item.order_id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { min-height: 100vh; padding: 24rpx; background: #F5F7F8; }
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
border: 1px solid rgba(106, 205, 187, 0.3);
|
||||
box-shadow: 0 0 6px rgba(106, 205, 187, 0.18);
|
||||
}
|
||||
.card-hover { background: #FAFAFA; }
|
||||
.row { display: flex; justify-content: space-between; padding: 8rpx 0; font-size: 26rpx; }
|
||||
.head { margin-bottom: 8rpx; }
|
||||
.no { font-weight: 600; color: #1D2129; }
|
||||
.amt { font-weight: 700; color: #1D2129; font-family: DIN Alternate, monospace; }
|
||||
.label { color: #86909C; }
|
||||
.value { color: #1D2129; }
|
||||
.link { display: block; margin-top: 12rpx; color: #6acdbb; font-size: 26rpx; }
|
||||
.empty { text-align: center; color: #86909C; padding: 80rpx 0; }
|
||||
</style>
|
||||
@@ -356,10 +356,11 @@ export default {
|
||||
}
|
||||
|
||||
/* ================= 经营看板:hero 下沉浮白卡(统一工作台看板规格) ================= */
|
||||
/* z-index 只为压住 hero 圆角,必须远低于弹层;禁止再给下方 content 加 z-index,否则常用编辑弹层会被看板盖住 */
|
||||
.dashboard-wrapper {
|
||||
margin: -30rpx 30rpx 0;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
@@ -414,7 +415,5 @@ export default {
|
||||
/* ================= 主体内容区:个人功能区(分区/宫格样式在 workbench-user-entries 组件内) ================= */
|
||||
.content-wrapper {
|
||||
margin-top: 30rpx; /* 轻微向上偏移,与顶部区域产生连接感 */
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
@@ -30,7 +30,7 @@
|
||||
<view class="row"><text class="label">时间</text><text class="value">{{ item.created_at }}</text></view>
|
||||
<view class="row" v-if="item.remark"><text class="label">备注</text><text class="value">{{ item.remark }}</text></view>
|
||||
<view
|
||||
v-if="Number(item.order_type) === 1 && item.order_id"
|
||||
v-if="item.order_id || (item.orders && item.orders.length)"
|
||||
class="order-link"
|
||||
@click="goOrderDetail(item)"
|
||||
>查看订单详情</view>
|
||||
@@ -100,10 +100,26 @@ export default {
|
||||
this.page = page
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
canViewChangeOrder(item) {
|
||||
const orders = item.orders || []
|
||||
return (Number(item.order_id) > 0) || orders.length > 0
|
||||
},
|
||||
goOrderDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/sub_clinic_admin/order/detail?id=' + item.order_id,
|
||||
})
|
||||
const orders = item.orders || []
|
||||
const target = orders.length > 0 ? orders[0] : item
|
||||
const orderType = Number(target.order_type || item.order_type || 0)
|
||||
const orderId = target.order_id || item.order_id
|
||||
if (orderType === 1 && orderId) {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_admin/order/detail?id=' + orderId })
|
||||
return
|
||||
}
|
||||
if (orderType === 2 && orderId) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${orderId}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,12 +1,61 @@
|
||||
<template>
|
||||
<clinic-page-layout title="申请提现" :show-back="true">
|
||||
<view class="tip">可提现余额:{{ balanceText }}</view>
|
||||
<view v-if="gapText" class="gap">
|
||||
<text>{{ gapText }}</text>
|
||||
<view
|
||||
v-for="row in gapApplies"
|
||||
:key="row.id"
|
||||
class="gap-order"
|
||||
@click.stop="goApplyDetail(row.id)"
|
||||
>{{ row.order_no }} · {{ row.check_status_txt || '' }} · {{ row.dakuan_status_txt || '' }}</view>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">提现方式</text>
|
||||
<view class="mode-row">
|
||||
<text
|
||||
v-for="m in modes"
|
||||
:key="m.value"
|
||||
class="mode-btn"
|
||||
:class="{ on: mode === m.value }"
|
||||
@click="mode = m.value"
|
||||
>{{ m.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="mode === 1" class="field">
|
||||
<text class="label">提现金额</text>
|
||||
<input v-model="amount" type="digit" placeholder="请输入金额" />
|
||||
<view class="quick">
|
||||
<text v-for="q in quickAmounts" :key="q.key" class="q-btn" @click="amount = String(q.value)">{{ q.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="quick">
|
||||
<text v-for="q in quickAmounts" :key="q.key" class="q-btn" @click="amount = String(q.value)">{{ q.value }}</text>
|
||||
<view v-if="mode === 3" class="field">
|
||||
<text class="label">时间范围</text>
|
||||
<picker mode="date" :value="startDate" @change="onStartDate">
|
||||
<view class="picker">开始:{{ startDate || '请选择' }}</view>
|
||||
</picker>
|
||||
<picker mode="date" :value="endDate" @change="onEndDate">
|
||||
<view class="picker">结束:{{ endDate || '请选择' }}</view>
|
||||
</picker>
|
||||
<picker :range="categoryLabels" @change="onCategory">
|
||||
<view class="picker">类型:{{ currentCategoryLabel }}</view>
|
||||
</picker>
|
||||
<view class="preview" @click="loadOrders">预览订单({{ orderList.length }} 笔)</view>
|
||||
</view>
|
||||
<view v-if="mode === 2 || mode === 3" class="order-list">
|
||||
<view
|
||||
v-for="item in orderList"
|
||||
:key="item.uk"
|
||||
class="order-item"
|
||||
@click="toggleOrder(item)"
|
||||
>
|
||||
<text class="chk">{{ isSelected(item) ? '☑' : '☐' }}</text>
|
||||
<view class="order-main">
|
||||
<text class="no">{{ item.order_no || '-' }}</text>
|
||||
<text class="sub">{{ item.category_txt }} · {{ item.remain }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="orderList.length === 0" class="empty">暂无可提订单</view>
|
||||
</view>
|
||||
<button class="submit" @click="submit" :loading="loading">提交申请</button>
|
||||
</clinic-page-layout>
|
||||
@@ -15,7 +64,7 @@
|
||||
<script>
|
||||
import ClinicPageLayout from '@/subPackages/sub_clinic_admin/components/clinic-page-layout.vue'
|
||||
import { formatMoney } from '@/subPackages/sub_clinic_admin/common/format.js'
|
||||
import { getClinicAdminBalance, submitWithdrawal } from '@/api/clinicAdmin.js'
|
||||
import { getClinicAdminBalance, getWithdrawableOrders, submitWithdrawal } from '@/api/clinicAdmin.js'
|
||||
|
||||
export default {
|
||||
components: { ClinicPageLayout },
|
||||
@@ -23,7 +72,27 @@
|
||||
return {
|
||||
amount: '',
|
||||
balanceText: '-',
|
||||
balance: 0,
|
||||
gapText: '',
|
||||
gapApplies: [],
|
||||
loading: false,
|
||||
mode: 2,
|
||||
category: 0,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
orderList: [],
|
||||
selectedUks: [],
|
||||
modes: [
|
||||
{ label: '按订单', value: 2 },
|
||||
{ label: '按时间', value: 3 },
|
||||
{ label: '自定义', value: 1 },
|
||||
],
|
||||
categoryOptions: [
|
||||
{ label: '全部类型', value: 0 },
|
||||
{ label: '中药', value: 1 },
|
||||
{ label: '西药', value: 2 },
|
||||
{ label: '其他', value: 3 },
|
||||
],
|
||||
quickAmounts: [
|
||||
{ key: '1000', value: 1000 },
|
||||
{ key: '3000', value: 3000 },
|
||||
@@ -32,23 +101,136 @@
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
categoryLabels() {
|
||||
return this.categoryOptions.map(o => o.label)
|
||||
},
|
||||
currentCategoryLabel() {
|
||||
const hit = this.categoryOptions.find(o => o.value === this.category)
|
||||
return hit ? hit.label : '全部类型'
|
||||
},
|
||||
selectedSum() {
|
||||
const picked = this.orderList.filter(item => this.selectedUks.indexOf(item.uk) >= 0)
|
||||
return picked.reduce((sum, item) => sum + Number(item.remain || 0), 0)
|
||||
},
|
||||
rangeSum() {
|
||||
return this.orderList.reduce((sum, item) => sum + Number(item.remain || 0), 0)
|
||||
},
|
||||
requestedSum() {
|
||||
if (this.mode === 2) return this.selectedSum
|
||||
if (this.mode === 3) return this.rangeSum
|
||||
return 0
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
getClinicAdminBalance().then(w => {
|
||||
if (w.ok && w.data) {
|
||||
const b = w.data.balance
|
||||
this.balance = Number(b || 0)
|
||||
this.balanceText = b != null ? formatMoney(b) : '-'
|
||||
const gap = Number(w.data.custom_gap || 0)
|
||||
this.gapApplies = w.data.custom_gap_applies || []
|
||||
if (gap > 0) {
|
||||
this.gapText = `按单合计比余额多 ${gap.toFixed(2)} 元,点打款单号可看申请→审核→打款进度`
|
||||
}
|
||||
}
|
||||
})
|
||||
this.loadOrders()
|
||||
},
|
||||
methods: {
|
||||
onStartDate(e) { this.startDate = e.detail.value },
|
||||
onEndDate(e) { this.endDate = e.detail.value },
|
||||
onCategory(e) {
|
||||
this.category = this.categoryOptions[Number(e.detail.value || 0)].value
|
||||
},
|
||||
toTs(dateStr, end) {
|
||||
if (!dateStr) return 0
|
||||
const t = new Date(dateStr.replace(/-/g, '/') + (end ? ' 23:59:59' : ' 00:00:00')).getTime()
|
||||
return t ? Math.floor(t / 1000) : 0
|
||||
},
|
||||
loadOrders() {
|
||||
const params = { page: 1, pageSize: 50, category: this.category }
|
||||
if (this.mode === 3) {
|
||||
params.start_at = this.toTs(this.startDate, false)
|
||||
params.end_at = this.toTs(this.endDate, true)
|
||||
}
|
||||
getWithdrawableOrders(params).then(w => {
|
||||
const items = ((w.data && w.data.items) || []).map(item => ({
|
||||
...item,
|
||||
uk: item.order_id + '_' + item.order_type,
|
||||
}))
|
||||
this.orderList = items
|
||||
})
|
||||
},
|
||||
isSelected(item) {
|
||||
return this.selectedUks.indexOf(item.uk) >= 0
|
||||
},
|
||||
toggleOrder(item) {
|
||||
if (this.mode !== 2) return
|
||||
const idx = this.selectedUks.indexOf(item.uk)
|
||||
if (idx >= 0) {
|
||||
this.selectedUks.splice(idx, 1)
|
||||
} else {
|
||||
this.selectedUks.push(item.uk)
|
||||
}
|
||||
},
|
||||
goApplyDetail(id) {
|
||||
if (!id) return
|
||||
uni.navigateTo({ url: `/subPackages/sub_clinic_admin/withdrawal/record-detail?id=${id}` })
|
||||
},
|
||||
submit() {
|
||||
const amt = Number(this.amount)
|
||||
if (!amt || amt <= 0) {
|
||||
uni.showToast({ title: '请输入有效金额', icon: 'none' })
|
||||
let payload = { mode: this.mode, category: this.category }
|
||||
if (this.mode === 1) {
|
||||
const amt = Number(this.amount)
|
||||
if (!amt || amt <= 0) {
|
||||
uni.showToast({ title: '请输入有效金额', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payload.amount = amt
|
||||
} else if (this.mode === 2) {
|
||||
const picked = this.orderList.filter(item => this.selectedUks.indexOf(item.uk) >= 0)
|
||||
if (picked.length === 0) {
|
||||
uni.showToast({ title: '请选择订单', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payload.order_ids = picked.map(item => ({
|
||||
order_id: item.order_id,
|
||||
order_type: item.order_type,
|
||||
}))
|
||||
} else {
|
||||
if (!this.startDate || !this.endDate) {
|
||||
uni.showToast({ title: '请选择时间范围', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payload.start_at = this.toTs(this.startDate, false)
|
||||
payload.end_at = this.toTs(this.endDate, true)
|
||||
}
|
||||
if (this.mode !== 1 && this.requestedSum > Number(this.balance || 0) + 0.0001) {
|
||||
this.confirmGapThenSubmit(payload)
|
||||
return
|
||||
}
|
||||
this.doSubmit(payload)
|
||||
},
|
||||
confirmGapThenSubmit(payload) {
|
||||
const requested = this.requestedSum
|
||||
const balance = Number(this.balance || 0)
|
||||
const gap = (requested - balance).toFixed(2)
|
||||
uni.showModal({
|
||||
title: '超过可提余额',
|
||||
content: `按单合计 ${requested.toFixed(2)} 元超过余额 ${balance.toFixed(2)} 元,差额 ${gap} 元。确认后按余额申请。`,
|
||||
confirmText: '按余额提',
|
||||
cancelText: '暂不提现',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
payload.accept_gap = 1
|
||||
this.doSubmit(payload)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
doSubmit(payload) {
|
||||
this.loading = true
|
||||
submitWithdrawal({ amount: amt }).then(w => {
|
||||
submitWithdrawal(payload).then(w => {
|
||||
if (w.ok) {
|
||||
uni.showToast({ title: '提交成功' })
|
||||
setTimeout(() => uni.navigateBack(), 800)
|
||||
@@ -62,42 +244,35 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tip {
|
||||
.tip, .field, .gap {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 32rpx;
|
||||
background: #fff;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.field {
|
||||
background: #fff;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
background: #fff;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.quick {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
.q-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
background: #eef8f6;
|
||||
color: #6acdbb;
|
||||
.gap { color: #B45309; background: #FFF7ED; }
|
||||
.gap-order { display: block; margin-top: 12rpx; color: #0F766E; }
|
||||
.label { display: block; font-size: 26rpx; color: #999; margin-bottom: 12rpx; }
|
||||
.mode-row { display: flex; gap: 16rpx; }
|
||||
.mode-btn {
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #F2F3F5;
|
||||
color: #4E5969;
|
||||
font-size: 26rpx;
|
||||
&.on { background: #eef8f6; color: #6acdbb; }
|
||||
}
|
||||
.submit {
|
||||
background: #6acdbb;
|
||||
color: #fff;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
.quick { display: flex; flex-wrap: wrap; gap: 16rpx; margin-top: 16rpx; }
|
||||
.q-btn { padding: 12rpx 24rpx; background: #eef8f6; color: #6acdbb; border-radius: 8rpx; font-size: 26rpx; }
|
||||
.picker { padding: 16rpx 0; color: #1D2129; }
|
||||
.preview { margin-top: 12rpx; color: #6acdbb; }
|
||||
.order-list { background: #fff; border-radius: 16rpx; padding: 8rpx 24rpx; margin-bottom: 32rpx; }
|
||||
.order-item { display: flex; gap: 16rpx; padding: 20rpx 0; border-bottom: 1rpx solid #F2F3F5; }
|
||||
.chk { color: #6acdbb; font-size: 32rpx; }
|
||||
.order-main { flex: 1; }
|
||||
.no { display: block; color: #1D2129; font-size: 26rpx; }
|
||||
.sub { color: #86909C; font-size: 24rpx; }
|
||||
.empty { text-align: center; color: #86909C; padding: 32rpx 0; }
|
||||
.submit { background: #6acdbb; color: #fff; border-radius: 44rpx; }
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,13 @@
|
||||
|
||||
<!-- 核心统计网格 -->
|
||||
<view class="stats-grid">
|
||||
<view v-for="b in balanceItems" :key="b.key" class="stat-island">
|
||||
<view
|
||||
v-for="b in balanceItems"
|
||||
:key="b.key"
|
||||
class="stat-island"
|
||||
hover-class="stat-hover"
|
||||
@click="onStatClick(b.key)"
|
||||
>
|
||||
<text class="stat-label">{{ b.label }}</text>
|
||||
<text class="stat-val">{{ b.display }}</text>
|
||||
</view>
|
||||
@@ -93,6 +99,9 @@ export default {
|
||||
},
|
||||
balanceItems: [
|
||||
{ key: 'balance', label: '可提现余额 (元)', display: '0.00' },
|
||||
{ key: 'tcm', label: '中药可提 (元)', display: '0.00' },
|
||||
{ key: 'western', label: '西药可提 (元)', display: '0.00' },
|
||||
{ key: 'other', label: '其他可提 (元)', display: '0.00' },
|
||||
{ key: 'reviewing', label: '审核中金额 (元)', display: '0.00' },
|
||||
{ key: 'pending', label: '待结算收益 (元)', display: '0.00' },
|
||||
],
|
||||
@@ -124,6 +133,16 @@ export default {
|
||||
return `**** **** **** ${last4}`;
|
||||
},
|
||||
|
||||
onStatClick(key) {
|
||||
const map = { balance: 0, tcm: 1, western: 2, other: 3 }
|
||||
if (map[key] !== undefined) {
|
||||
uni.navigateTo({ url: `/subPackages/sub_clinic_admin/withdrawal/withdrawable-orders?category=${map[key]}` })
|
||||
return
|
||||
}
|
||||
if (key === 'pending') {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_admin/withdrawal/settlement/index' })
|
||||
}
|
||||
},
|
||||
// 快捷操作跳转
|
||||
onAction(key) {
|
||||
if (key === 'apply') {
|
||||
@@ -159,6 +178,9 @@ export default {
|
||||
const fmt = (v) => formatMoney(v || 0).replace('¥', '')
|
||||
const map = {
|
||||
balance: fmt(w.data.balance),
|
||||
tcm: fmt(w.data.tcm_balance),
|
||||
western: fmt(w.data.western_balance),
|
||||
other: fmt(w.data.other_balance),
|
||||
reviewing: fmt(w.data.withdrawn_frozen),
|
||||
pending: fmt(w.data.pending_earnings),
|
||||
}
|
||||
@@ -298,6 +320,7 @@ $color-text-muted: #86909C;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.stat-hover { background: #F7F8FA; }
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
|
||||
@@ -17,10 +17,26 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="progress">
|
||||
<view
|
||||
v-for="n in progressNodes"
|
||||
:key="n.title"
|
||||
class="progress-item"
|
||||
:class="{ 'is-done': n.state === 'done', 'is-on': n.state === 'on', 'is-fail': n.state === 'fail' }"
|
||||
>
|
||||
<text class="progress-title">{{ n.title }}</text>
|
||||
<text class="progress-desc">{{ n.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 明细信息 -->
|
||||
<view class="info-list">
|
||||
<view class="row"><text class="label">订单号</text><text class="value font-mono">{{ info.order_no || '-' }}</text></view>
|
||||
<view class="row"><text class="label">申请金额</text><text class="value">{{ formatMoney(info.apply_cash) }}</text></view>
|
||||
<view class="row" v-if="info.mode_txt"><text class="label">提现方式</text><text class="value">{{ info.mode_txt }}</text></view>
|
||||
<view class="row"><text class="label">中药</text><text class="value">{{ formatMoney(info.tcm_amount) }}</text></view>
|
||||
<view class="row"><text class="label">西药</text><text class="value">{{ formatMoney(info.western_amount) }}</text></view>
|
||||
<view class="row"><text class="label">其他</text><text class="value">{{ formatMoney(info.other_amount) }}</text></view>
|
||||
<view class="row"><text class="label">手续费</text><text class="value">{{ formatMoney(info.charge_cash) }}</text></view>
|
||||
<view class="row"><text class="label">申请时间</text><text class="value">{{ formatDateTime(info.apply_time) }}</text></view>
|
||||
|
||||
@@ -35,6 +51,18 @@
|
||||
<view class="row"><text class="label">打款状态</text><text class="value">{{ dakuanStatusText(info.dakuan_status) }}</text></view>
|
||||
<view class="row" v-if="info.dakuan_time"><text class="label">打款时间</text><text class="value">{{ formatDateTime(info.dakuan_time) }}</text></view>
|
||||
</view>
|
||||
<view v-if="info.orders && info.orders.length" class="order-box">
|
||||
<text class="order-hd">关联订单</text>
|
||||
<view
|
||||
v-for="od in orderRows"
|
||||
:key="od.row_key"
|
||||
class="order-row"
|
||||
@click="goOrder(od)"
|
||||
>
|
||||
<text>{{ od.order_no || '-' }} · {{ od.category_txt || '' }}</text>
|
||||
<text class="order-amt">{{ od.amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -51,11 +79,58 @@ import {
|
||||
} from '@/subPackages/sub_clinic_admin/common/display.js'
|
||||
import { getWithdrawalDetail } from '@/api/clinicAdmin.js'
|
||||
|
||||
function buildProgressNodes(info) {
|
||||
const check = Number((info && info.check_status) || 0)
|
||||
const dakuan = Number((info && info.dakuan_status) || 0)
|
||||
const applyTime = (info && info.apply_time) || '已提交'
|
||||
const checkTime = (info && info.check_time) || ''
|
||||
const dakuanTime = (info && info.dakuan_time) || ''
|
||||
const checkResult = (info && info.check_result) || ''
|
||||
const apply = { title: '申请', desc: applyTime, state: 'done' }
|
||||
let audit = { title: '审核', desc: '待审核', state: 'wait' }
|
||||
if (check === 1) audit = { title: '审核', desc: '待审核', state: 'on' }
|
||||
else if (check === 2) audit = { title: '审核', desc: checkTime || '审核通过', state: 'done' }
|
||||
else if (check === 3) audit = { title: '审核', desc: checkResult || '已拒绝', state: 'fail' }
|
||||
else if (check === 4) audit = { title: '审核', desc: checkResult || '提现失败', state: 'fail' }
|
||||
let pay = { title: '打款', desc: '待打款', state: 'wait' }
|
||||
let arrived = { title: '到账', desc: '尚未打款', state: 'wait' }
|
||||
if (check === 1) {
|
||||
pay = { title: '打款', desc: '待审核通过后打款', state: 'wait' }
|
||||
} else if (check === 3) {
|
||||
pay = { title: '打款', desc: '已拒绝,未打款', state: 'wait' }
|
||||
arrived = { title: '到账', desc: '未到账', state: 'wait' }
|
||||
} else if (check === 4 || dakuan === -1) {
|
||||
pay = { title: '打款', desc: dakuanTime || '打款失败', state: 'fail' }
|
||||
arrived = { title: '到账', desc: '未到账', state: 'fail' }
|
||||
} else if (dakuan === 1) {
|
||||
pay = { title: '打款', desc: dakuanTime || '打款成功', state: 'done' }
|
||||
arrived = { title: '到账', desc: dakuanTime || '已到账', state: 'done' }
|
||||
} else if (check === 2) {
|
||||
pay = { title: '打款', desc: '打款处理中', state: 'on' }
|
||||
arrived = { title: '到账', desc: '等待到账', state: 'wait' }
|
||||
}
|
||||
return [apply, audit, pay, arrived]
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { ClinicPageLayout },
|
||||
data() {
|
||||
return { id: 0, info: {} }
|
||||
},
|
||||
computed: {
|
||||
progressNodes() {
|
||||
return buildProgressNodes(this.info)
|
||||
},
|
||||
// 非 H5 的 :key 不能写表达式,预先落到单一字段
|
||||
orderRows() {
|
||||
const list = (this.info && this.info.orders) || []
|
||||
return list.map((od, i) => {
|
||||
const row = Object.assign({}, od)
|
||||
row.row_key = String(od.id || od.order_id || i)
|
||||
return row
|
||||
})
|
||||
},
|
||||
},
|
||||
onLoad(q) {
|
||||
this.id = Number(q.id || 0)
|
||||
getWithdrawalDetail(this.id).then(w => {
|
||||
@@ -70,6 +145,20 @@ export default {
|
||||
},
|
||||
dakuanStatusText(s) {
|
||||
return dakuanStatusLabel(s)
|
||||
},
|
||||
goOrder(item) {
|
||||
const orderType = Number(item.order_type || 0)
|
||||
if (orderType === 1 && item.order_id) {
|
||||
uni.navigateTo({ url: `/subPackages/sub_clinic_admin/order/detail?id=${item.order_id}` })
|
||||
return
|
||||
}
|
||||
if (orderType === 2 && item.order_id) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${item.order_id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -167,4 +256,42 @@ $color-text-muted: #86909C;
|
||||
background-color: #EAEFEF;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
.order-box { margin-top: 24rpx; }
|
||||
.order-hd { display: block; font-size: 26rpx; color: #86909C; margin-bottom: 12rpx; }
|
||||
.order-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #6acdbb;
|
||||
}
|
||||
.order-amt { color: #1D2129; }
|
||||
.progress {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.progress-item {
|
||||
flex: 1;
|
||||
padding: 16rpx 8rpx;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
background: #F5F7F8;
|
||||
}
|
||||
.progress-item.is-done { background: rgba(106, 205, 187, 0.12); }
|
||||
.progress-item.is-on { background: #FFF3E8; }
|
||||
.progress-item.is-fail { background: #FFECE8; }
|
||||
.progress-title {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #1D2129;
|
||||
}
|
||||
.progress-desc {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
font-size: 20rpx;
|
||||
color: #86909C;
|
||||
}
|
||||
</style>
|
||||
@@ -55,7 +55,7 @@
|
||||
<view class="card-actions">
|
||||
<text class="action-link" @click.stop="goSettlementDetail(item)">结算明细</text>
|
||||
<text
|
||||
v-if="Number(item.order_type) === 1"
|
||||
v-if="Number(item.order_type) === 1 || Number(item.order_type) === 2"
|
||||
class="action-link primary"
|
||||
@click.stop="goOrderDetail(item)"
|
||||
>订单详情</text>
|
||||
@@ -135,13 +135,18 @@ export default {
|
||||
})
|
||||
},
|
||||
goOrderDetail(item) {
|
||||
if (Number(item.order_type) !== 1 || !item.order_id) {
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
const orderType = Number(item.order_type || 0)
|
||||
if (orderType === 1 && item.order_id) {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_admin/order/detail?id=' + item.order_id })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/sub_clinic_admin/order/detail?id=' + item.order_id,
|
||||
})
|
||||
if (orderType === 2 && item.order_id) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${item.order_id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
106
subPackages/sub_clinic_admin/withdrawal/withdrawable-orders.vue
Normal file
106
subPackages/sub_clinic_admin/withdrawal/withdrawable-orders.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<clinic-page-layout :title="pageTitle" :show-back="true">
|
||||
<view class="page">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item._wxKey"
|
||||
class="card"
|
||||
hover-class="card-hover"
|
||||
>
|
||||
<view class="row head">
|
||||
<text class="no">{{ item.order_no || '-' }}</text>
|
||||
<text class="amt">{{ item.remain }}</text>
|
||||
</view>
|
||||
<view class="row"><text class="label">分类</text><text class="value">{{ item.category_txt || '-' }}</text></view>
|
||||
<view class="row"><text class="label">订单类型</text><text class="value">{{ item.order_type_txt || '-' }}</text></view>
|
||||
<view class="row"><text class="label">结算时间</text><text class="value">{{ item.settled_at_txt || '-' }}</text></view>
|
||||
<text class="link" @click="goOrder(item)">查看订单</text>
|
||||
</view>
|
||||
<view v-if="!loading && list.length === 0" class="empty">暂无可提订单</view>
|
||||
</view>
|
||||
</clinic-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClinicPageLayout from '@/subPackages/sub_clinic_admin/components/clinic-page-layout.vue'
|
||||
import { getWithdrawableOrders } from '@/api/clinicAdmin.js'
|
||||
import pullRefreshMixin from '@/subPackages/sub_business_shared/common/pullRefreshMixin.js'
|
||||
import { withWxKey } from '@/utils/wxListKey.js'
|
||||
|
||||
export default {
|
||||
mixins: [pullRefreshMixin],
|
||||
components: { ClinicPageLayout },
|
||||
data() {
|
||||
return {
|
||||
category: 0,
|
||||
pageTitle: '可提订单',
|
||||
list: [],
|
||||
page: 1,
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
onLoad(q) {
|
||||
this.category = Number(q.category || 0)
|
||||
const titles = { 0: '全部可提订单', 1: '中药可提订单', 2: '西药可提订单', 3: '其他可提订单' }
|
||||
this.pageTitle = titles[this.category] || '可提订单'
|
||||
},
|
||||
onShow() {
|
||||
this.reload()
|
||||
},
|
||||
onReachBottom() {
|
||||
this.load(this.page + 1, true)
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
this.load(1, false)
|
||||
},
|
||||
load(page, append) {
|
||||
this.loading = true
|
||||
getWithdrawableOrders({
|
||||
page,
|
||||
pageSize: 15,
|
||||
category: this.category,
|
||||
}).then(w => {
|
||||
const items = withWxKey((w.data && w.data.items) || [], 'order_id', 'wo')
|
||||
this.list = append ? this.list.concat(items) : items
|
||||
this.page = page
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
goOrder(item) {
|
||||
const orderType = Number(item.order_type || 0)
|
||||
if (orderType === 1 && item.order_id) {
|
||||
uni.navigateTo({ url: `/subPackages/sub_clinic_admin/order/detail?id=${item.order_id}` })
|
||||
return
|
||||
}
|
||||
if (orderType === 2 && item.order_id) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_business_shared/user-patient/register-detail?id=${item.order_id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { min-height: 100vh; padding: 24rpx; background: #F5F7F8; }
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
border: 1px solid rgba(106, 205, 187, 0.3);
|
||||
box-shadow: 0 0 6px rgba(106, 205, 187, 0.18);
|
||||
}
|
||||
.card-hover { background: #FAFAFA; }
|
||||
.row { display: flex; justify-content: space-between; padding: 8rpx 0; font-size: 26rpx; }
|
||||
.head { margin-bottom: 8rpx; }
|
||||
.no { font-weight: 600; color: #1D2129; }
|
||||
.amt { font-weight: 700; color: #1D2129; font-family: DIN Alternate, monospace; }
|
||||
.label { color: #86909C; }
|
||||
.value { color: #1D2129; }
|
||||
.link { display: block; margin-top: 12rpx; color: #6acdbb; font-size: 26rpx; }
|
||||
.empty { text-align: center; color: #86909C; padding: 80rpx 0; }
|
||||
</style>
|
||||
@@ -395,10 +395,11 @@ export default {
|
||||
}
|
||||
|
||||
/* ================= 核心资产看板区 ================= */
|
||||
/* z-index 只为压住 hero,必须低于常用编辑弹层(约 10080),避免浮卡盖住抽屉 */
|
||||
.asset-dashboard {
|
||||
margin: -80rpx 30rpx 40rpx;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 主卡片:黑金/深色沉浸质感 */
|
||||
|
||||
@@ -321,10 +321,11 @@ export default {
|
||||
}
|
||||
|
||||
/* ================= 平台看板:hero 下沉浮白卡(统一工作台看板规格) ================= */
|
||||
/* z-index 只为压住 hero 圆角,必须远低于弹层(常用编辑 / 预约抽屉约 10080);禁止再给下方 content 加 z-index,否则会形成层叠上下文把 u-popup 困在看板下面 */
|
||||
.dashboard-wrapper {
|
||||
margin: -30rpx 30rpx 0;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
@@ -379,7 +380,5 @@ export default {
|
||||
/* ================= 主体内容区:个人功能区(分区/宫格样式在 workbench-user-entries 组件内) ================= */
|
||||
.content-wrapper {
|
||||
margin-top: 30rpx; /* 轻微向上偏移,与顶部区域产生连接感 */
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -233,7 +233,6 @@ export default {
|
||||
],
|
||||
form: createDefaultDoctorInputForm(),
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pageTitle() { return this.id ? '编辑医生预填' : '新建医生预填' },
|
||||
|
||||
@@ -383,6 +383,5 @@ export default {
|
||||
.stat-value-highlight { font-size: 36rpx; font-weight: 700; color: #059669; font-family: "DIN Alternate", sans-serif; }
|
||||
.stat-unit { font-size: 20rpx; color: #94A3B8; margin-left: 4rpx; }
|
||||
|
||||
/* 个人功能区(分区/宫格样式在 workbench-user-entries 组件内) */
|
||||
.content-wrapper { position: relative; z-index: 10; }
|
||||
/* 个人功能区不要加 z-index:否则会形成层叠上下文,把「编辑常用」弹层困在看板/tabbar 下面 */
|
||||
</style>
|
||||
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<view v-if="show" class="pap-mask">
|
||||
<view class="pap-sheet">
|
||||
<view class="pap-head">
|
||||
<text class="pap-title">申请公账支付</text>
|
||||
<text class="pap-close" @click="close">关闭</text>
|
||||
</view>
|
||||
<view class="pap-body">
|
||||
<view class="pap-card">
|
||||
<view class="pap-row">
|
||||
<text class="pap-label">订单号</text>
|
||||
<text class="pap-value">{{ orderNo || '—' }}</text>
|
||||
</view>
|
||||
<view class="pap-row">
|
||||
<text class="pap-label">待支付</text>
|
||||
<text class="pap-value pap-price">¥{{ amount || '0.00' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="pap-hint">付款凭证选填。按天模式下订单会立即标为已付并计入日汇总;按订单模式需管理员确认到账。</text>
|
||||
<view class="pap-upload">
|
||||
<text class="pap-label">付款凭证(选填)</text>
|
||||
<view class="pap-imgs">
|
||||
<view
|
||||
v-for="(url, idx) in voucherUrls"
|
||||
:key="idx"
|
||||
class="pap-img-wrap"
|
||||
>
|
||||
<image class="pap-img" :src="url" mode="aspectFill"></image>
|
||||
<text class="pap-img-del" @click="removeImage(idx)">×</text>
|
||||
</view>
|
||||
<view v-if="voucherUrls.length < 9" class="pap-img-add" @click="chooseImage">
|
||||
<text class="pap-img-add-txt">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pap-remark">
|
||||
<text class="pap-label">备注</text>
|
||||
<textarea
|
||||
class="pap-textarea"
|
||||
:value="applyRemark"
|
||||
placeholder="可选"
|
||||
maxlength="200"
|
||||
@input="onRemarkInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pap-foot">
|
||||
<view class="pap-btn" :class="{ 'pap-btn--off': submitting }" @click="submit">
|
||||
<text class="pap-btn-txt">{{ submitting ? '提交中...' : '提交申请' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 医生小程序申请公账支付抽屉(父级用 page-container 拦截返回)
|
||||
*/
|
||||
import { applyPublicAccountPayApi } from '@/api/publicAccountPay.js';
|
||||
import { unwrapXkApi } from '@/utils/api-response.js';
|
||||
import { uploadLegacyAdminImage } from '@/api/upload.js';
|
||||
|
||||
export default {
|
||||
name: 'ApplyPublicAccountPayDrawer',
|
||||
props: {
|
||||
value: { type: Boolean, default: false },
|
||||
orderId: { type: [Number, String], default: 0 },
|
||||
orderNo: { type: String, default: '' },
|
||||
amount: { type: [Number, String], default: '' },
|
||||
storeId: { type: [Number, String], default: 0 },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
voucherUrls: [],
|
||||
applyRemark: '',
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
show() {
|
||||
return this.value;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val) {
|
||||
this.voucherUrls = [];
|
||||
this.applyRemark = '';
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
onRemarkInput(e) {
|
||||
this.applyRemark = (e && e.detail && e.detail.value) || '';
|
||||
},
|
||||
chooseImage() {
|
||||
const remain = 9 - this.voucherUrls.length;
|
||||
if (remain <= 0) return;
|
||||
uni.chooseImage({
|
||||
count: remain,
|
||||
success: async (res) => {
|
||||
const paths = res.tempFilePaths || [];
|
||||
try {
|
||||
uni.showLoading({ title: '上传中' });
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const url = await uploadLegacyAdminImage({
|
||||
filePath: paths[i],
|
||||
formData: { store_id: String(this.storeId || '11001') },
|
||||
});
|
||||
if (url) this.voucherUrls.push(url);
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
removeImage(idx) {
|
||||
this.voucherUrls.splice(idx, 1);
|
||||
},
|
||||
async submit() {
|
||||
const oid = Number(this.orderId || 0);
|
||||
if (!oid) {
|
||||
uni.showToast({ title: '订单无效', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.submitting) return;
|
||||
this.submitting = true;
|
||||
try {
|
||||
const res = await applyPublicAccountPayApi({
|
||||
order_id: oid,
|
||||
voucher_urls: this.voucherUrls,
|
||||
apply_remark: this.applyRemark,
|
||||
});
|
||||
const wrap = unwrapXkApi(res);
|
||||
if (!wrap.ok) {
|
||||
uni.showToast({ title: wrap.message || '申请失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const payload = wrap.payload || {}
|
||||
const daily = payload.bill_mode === 'daily'
|
||||
uni.showToast({ title: daily ? '已申请,订单已记为已付' : '已提交,等待确认到账', icon: 'none' });
|
||||
this.$emit('success');
|
||||
this.close();
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.message) || '申请失败', icon: 'none' });
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pap-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
background: rgba(15, 23, 42, 0.45);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.pap-sheet {
|
||||
width: 100%;
|
||||
max-height: 86vh;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pap-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.pap-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
.pap-close {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
.pap-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 24rpx 32rpx 16rpx;
|
||||
}
|
||||
.pap-card {
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
border: 1px solid rgba(106, 205, 187, 0.35);
|
||||
box-shadow: 0 0 12rpx rgba(106, 205, 187, 0.18);
|
||||
background: #fff;
|
||||
}
|
||||
.pap-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.pap-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.pap-label {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
.pap-value {
|
||||
font-size: 26rpx;
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
}
|
||||
.pap-price {
|
||||
color: #0f766e;
|
||||
}
|
||||
.pap-hint {
|
||||
display: block;
|
||||
margin: 20rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.pap-upload,
|
||||
.pap-remark {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.pap-imgs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
.pap-img-wrap,
|
||||
.pap-img-add {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 12rpx;
|
||||
position: relative;
|
||||
}
|
||||
.pap-img {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.pap-img-del {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
line-height: 32rpx;
|
||||
text-align: center;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.pap-img-add {
|
||||
border: 1px dashed #d1d5db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.pap-img-add-txt {
|
||||
font-size: 48rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
.pap-textarea {
|
||||
margin-top: 12rpx;
|
||||
width: 100%;
|
||||
min-height: 140rpx;
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f8fafc;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.pap-foot {
|
||||
padding: 16rpx 32rpx 40rpx;
|
||||
}
|
||||
.pap-btn {
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
background: #6acdbb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.pap-btn--off {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.pap-btn-txt {
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -33,6 +33,51 @@
|
||||
<text class="rx-permission-loading__text">{{ prescriptionTypeDenyMessage || '当前门店未开通开方权限' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 开方成功页:代付码 / 申请公账 / 继续开方 -->
|
||||
<view v-else-if="sentSuccess" class="rx-sent-page">
|
||||
<view class="rx-sent-card">
|
||||
<text class="rx-sent-ok">处方已发送</text>
|
||||
<text class="rx-sent-sub">已推送到患者微信 · 支付完成后门店按方发药</text>
|
||||
</view>
|
||||
<view class="rx-sent-card">
|
||||
<view class="rx-sent-row">
|
||||
<text class="rx-sent-label">订单号</text>
|
||||
<text class="rx-sent-value">{{ sentOrderNo || '—' }}</text>
|
||||
</view>
|
||||
<view class="rx-sent-row">
|
||||
<text class="rx-sent-label">患者</text>
|
||||
<text class="rx-sent-value">{{ sentPatientName || '—' }}</text>
|
||||
</view>
|
||||
<view class="rx-sent-row">
|
||||
<text class="rx-sent-label">订单状态</text>
|
||||
<text class="rx-sent-tag">{{ sentOrderPaid == 1 ? '已支付(公账先用后付)' : '待患者支付' }}</text>
|
||||
</view>
|
||||
<view class="rx-sent-row">
|
||||
<text class="rx-sent-label">总金额</text>
|
||||
<text class="rx-sent-price">¥{{ sentTotalPayPrice || '0.00' }}</text>
|
||||
</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 && publicAccountPayEnabled && !publicAccountDailyMode && sentOrderPaid != 1" class="rx-sent-btn" @click="openPublicAccountApply">
|
||||
<text class="rx-sent-btn-txt rx-sent-btn-txt--dark">申请公账支付</text>
|
||||
</view>
|
||||
<view class="rx-sent-btn" @click="continuePrescribe">
|
||||
<text class="rx-sent-btn-txt rx-sent-btn-txt--dark">继续开方</text>
|
||||
</view>
|
||||
</view>
|
||||
<proxy-pay-qrcode-modal v-model="proxyPayShow" :order-id="sentOrderId"></proxy-pay-qrcode-modal>
|
||||
<apply-public-account-pay-drawer
|
||||
v-model="showPublicAccountApply"
|
||||
:order-id="sentOrderId"
|
||||
:order-no="sentOrderNo"
|
||||
:amount="sentTotalPayPrice"
|
||||
:store-id="currentStoreId"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 用 block 替代 template:微信小程序对 template+v-else 大块切换易触发渲染层 not iterable -->
|
||||
<block v-else>
|
||||
<!-- 外层:处方 | 病历(VIP) -->
|
||||
@@ -816,6 +861,9 @@ import WesternMedicineUsageModal from './components/modals/WesternMedicineUsageM
|
||||
import ChineseMedicineConfig from './components/ChineseMedicineConfig.vue';
|
||||
import TraditionalTermPickerModal from './components/modals/TraditionalTermPickerModal.vue';
|
||||
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 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';
|
||||
@@ -853,6 +901,7 @@ const PRESCRIPTION_OVERLAY_KEYS = [
|
||||
'showMedicalRecordOverlay',
|
||||
'showAiPrescriptionDrawer',
|
||||
'showGoldenFormulaModal',
|
||||
'showPublicAccountApply',
|
||||
];
|
||||
|
||||
export default {
|
||||
@@ -871,6 +920,8 @@ export default {
|
||||
ChineseMedicineConfig,
|
||||
TraditionalTermPickerModal,
|
||||
TransferPatientDrawer,
|
||||
ApplyPublicAccountPayDrawer,
|
||||
ProxyPayQrcodeModal,
|
||||
WarehouseSelectDrawer,
|
||||
OrderPricePercentAdjustPopup,
|
||||
},
|
||||
@@ -938,6 +989,15 @@ export default {
|
||||
showCommonPrescriptionModal: false,
|
||||
showAiPrescriptionDrawer: false,
|
||||
showGoldenFormulaModal: false,
|
||||
/** 开方成功页 + 申请公账抽屉 */
|
||||
sentSuccess: false,
|
||||
sentOrderId: 0,
|
||||
sentOrderNo: '',
|
||||
sentTotalPayPrice: '',
|
||||
sentOrderPaid: 0,
|
||||
sentPatientName: '',
|
||||
showPublicAccountApply: false,
|
||||
proxyPayShow: false,
|
||||
/** 金方抽屉层级:0关闭 / 1列表 / 2对照(配合 page-container 返回栈) */
|
||||
goldenFormulaLayer: 0,
|
||||
/** 打开 AI 出方时带入的主诉(可在抽屉内再编辑) */
|
||||
@@ -967,6 +1027,8 @@ export default {
|
||||
/** 是否可查看毛利率 0/1(xk-api see_rate) */
|
||||
seeRate: 0,
|
||||
enableOrderPricePercentAdjust: 0,
|
||||
publicAccountPayEnabled: false,
|
||||
publicAccountDailyMode: false,
|
||||
priceAdjustVisible: false,
|
||||
priceDiscount: 100,
|
||||
priceAdjustQuickOptions: [],
|
||||
@@ -1226,6 +1288,9 @@ export default {
|
||||
return this.hasStoreVipPermission(VIP_FEATURE.GOLDEN_FORMULA);
|
||||
},
|
||||
navbarTitle() {
|
||||
if (this.sentSuccess) {
|
||||
return '处方已发送';
|
||||
}
|
||||
if (this.isSalespersonTransferMode) {
|
||||
return '传方';
|
||||
}
|
||||
@@ -1811,6 +1876,8 @@ export default {
|
||||
const data = (res.result != null ? res.result : res.data != null ? res.data : res) || res;
|
||||
this.seeRate = Number(data.see_rate ?? 0);
|
||||
this.enableOrderPricePercentAdjust = Number(data.enable_order_price_percent_adjust ?? 0);
|
||||
this.publicAccountPayEnabled = Number(data.public_account_pay_enabled ?? 0) === 1;
|
||||
this.publicAccountDailyMode = String(data.public_account_bill_mode || '') === 'daily';
|
||||
await this.loadPriceAdjustConfigForStore(params.store_id || storeId);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1847,6 +1914,8 @@ export default {
|
||||
}
|
||||
const priceStoreId = this.selectedStoreId || data.store_id || storeId;
|
||||
this.enableOrderPricePercentAdjust = Number(data.enable_order_price_percent_adjust ?? 0);
|
||||
this.publicAccountPayEnabled = Number(data.public_account_pay_enabled ?? 0) === 1;
|
||||
this.publicAccountDailyMode = String(data.public_account_bill_mode || '') === 'daily';
|
||||
await this.loadPriceAdjustConfigForStore(priceStoreId);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -3350,12 +3419,14 @@ export default {
|
||||
}
|
||||
|
||||
const res = await addWestPrescription(params);
|
||||
if (res && (res.code === 0 || res.errcode === 0)) {
|
||||
const wrap = unwrapXkApi(res);
|
||||
if (wrap.ok) {
|
||||
this.$toast('发送成功');
|
||||
await this.syncMedicalRecordAfterSend();
|
||||
const needTransfer = res?.result?.need_transfer || res?.data?.need_transfer || res?.need_transfer;
|
||||
const payload = wrap.payload || {};
|
||||
const needTransfer = payload.need_transfer || res.need_transfer;
|
||||
if (needTransfer) {
|
||||
const transferId = res?.result?.transfer_prescription_id || res?.data?.transfer_prescription_id || res?.transfer_prescription_id;
|
||||
const transferId = payload.transfer_prescription_id || res.transfer_prescription_id;
|
||||
if (transferId) {
|
||||
uni.showModal({
|
||||
title: '转诊提示', content: '该处方已触发转诊流程,转诊消息已发送给委托诊所的在线问诊医生。', showCancel: false, confirmText: '确定',
|
||||
@@ -3372,11 +3443,16 @@ export default {
|
||||
uni.showToast({ title: '处方消息同步失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
this.sentOrderId = Number(payload.order_id || 0);
|
||||
this.sentOrderNo = payload.order_no || '';
|
||||
this.sentTotalPayPrice = payload.total_pay_price || this.totalPrice || '0.00';
|
||||
this.sentOrderPaid = Number(payload.is_pay || 0);
|
||||
this.sentPatientName = (this.patientInfo && this.patientInfo.name) || '';
|
||||
this.clearPrescriptionData();
|
||||
PrescriptionStorage.clearAllPrescriptionData(this.registerId);
|
||||
this.safeNavigateBack(1500);
|
||||
this.sentSuccess = true;
|
||||
} else {
|
||||
this.$toast(res.msg || res.message || '发送失败');
|
||||
this.$toast(wrap.message || '发送失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送处方失败:', error);
|
||||
@@ -3664,6 +3740,26 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.safeNavigateBack(0);
|
||||
},
|
||||
/** 成功页继续开方:回到本页编辑态 */
|
||||
continuePrescribe() {
|
||||
this.sentSuccess = false;
|
||||
this.showPublicAccountApply = false;
|
||||
this.proxyPayShow = false;
|
||||
},
|
||||
openProxyPayFromSuccess() {
|
||||
if (!this.sentOrderId) {
|
||||
this.$toast('订单尚未生成');
|
||||
return;
|
||||
}
|
||||
this.proxyPayShow = true;
|
||||
},
|
||||
openPublicAccountApply() {
|
||||
if (!this.sentOrderId) {
|
||||
this.$toast('订单尚未生成');
|
||||
return;
|
||||
}
|
||||
this.showPublicAccountApply = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -3681,6 +3777,81 @@ export default {
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
}
|
||||
.rx-sent-page {
|
||||
flex: 1;
|
||||
padding: 32rpx;
|
||||
}
|
||||
.rx-sent-card {
|
||||
margin-bottom: 24rpx;
|
||||
padding: 32rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
border: 1px solid rgba(106, 205, 187, 0.35);
|
||||
box-shadow: 0 0 12rpx rgba(106, 205, 187, 0.18);
|
||||
}
|
||||
.rx-sent-ok {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
text-align: center;
|
||||
}
|
||||
.rx-sent-sub {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
}
|
||||
.rx-sent-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.rx-sent-label {
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
.rx-sent-value {
|
||||
font-size: 26rpx;
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
}
|
||||
.rx-sent-tag {
|
||||
font-size: 24rpx;
|
||||
color: #c2410c;
|
||||
}
|
||||
.rx-sent-price {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #0f766e;
|
||||
}
|
||||
.rx-sent-ops {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.rx-sent-btn {
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
border: 1px solid rgba(106, 205, 187, 0.45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
}
|
||||
.rx-sent-btn--main {
|
||||
background: #6acdbb;
|
||||
border-color: #6acdbb;
|
||||
}
|
||||
.rx-sent-btn-txt {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
.rx-sent-btn-txt--dark {
|
||||
color: #0f766e;
|
||||
}
|
||||
/* 基础与通用样式 */
|
||||
.page-bg {
|
||||
background-color: #F4F6F8;
|
||||
|
||||
821
subPackages/sub_workbench/public_account_lock/index.vue
Normal file
821
subPackages/sub_workbench/public_account_lock/index.vue
Normal file
@@ -0,0 +1,821 @@
|
||||
<template>
|
||||
<view class="lock-page" :class="cardToneClass">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<page-container
|
||||
v-if="guardShow"
|
||||
:show="guardShow"
|
||||
:overlay="false"
|
||||
@beforeleave="onLeave"
|
||||
@leave="onLeave"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<u-navbar title="公账已锁定" :is-back="false" title-size="34" title-color="#fff" :background="navbarBg"></u-navbar>
|
||||
<lock-warning-decor :color="alertColor" :tapes-only="step != 'hint'">
|
||||
<view class="lock-body" :class="{ 'is-form': step != 'hint' }">
|
||||
<view class="lock-card" :class="cardToneClass">
|
||||
<block v-if="step == 'upload'">
|
||||
<text class="lock-badge">上传付款凭证</text>
|
||||
<text class="lock-title">处理逾期日账单</text>
|
||||
<text class="lock-desc">{{ unlockHint }}</text>
|
||||
<text class="lock-label">付款凭证(必传)</text>
|
||||
<view class="lock-imgs">
|
||||
<view v-for="(url, idx) in voucherUrls" :key="idx" class="lock-img-wrap">
|
||||
<image class="lock-img" :src="url" mode="aspectFill"></image>
|
||||
<text class="lock-img-del" @click="removeImage(idx)">×</text>
|
||||
</view>
|
||||
<view v-if="voucherUrls.length < 9" class="lock-img-add" @click="chooseImage">
|
||||
<text class="lock-img-add-txt">+</text>
|
||||
</view>
|
||||
</view>
|
||||
<textarea
|
||||
class="lock-remark"
|
||||
:value="payRemark"
|
||||
placeholder="备注(选填)"
|
||||
maxlength="200"
|
||||
@input="onRemarkInput"
|
||||
/>
|
||||
<view class="lock-btn-row">
|
||||
<view class="lock-btn lock-btn--ghost" @click="backToHint">
|
||||
<text class="lock-btn-txt lock-btn-txt--dark">返回说明</text>
|
||||
</view>
|
||||
<view class="lock-btn" :class="{ 'is-off': submitting }" @click="submit">
|
||||
<text class="lock-btn-txt">{{ submitting ? '提交中...' : '提交凭证' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<block v-else-if="step == 'wait'">
|
||||
<text class="lock-badge">等待管理员审核</text>
|
||||
<text class="lock-title">凭证已提交</text>
|
||||
<text class="lock-desc">平台管理员确认到账后自动解锁。可催办,或拨打下方号码联系管理员。</text>
|
||||
<view class="lock-stat">
|
||||
<view class="lock-stat-item">
|
||||
<text class="lock-stat-label">账单日</text>
|
||||
<text class="lock-stat-val">{{ billDateTxt || '—' }}</text>
|
||||
</view>
|
||||
<view class="lock-stat-item">
|
||||
<text class="lock-stat-label">应付款</text>
|
||||
<text class="lock-stat-val lock-stat-val--amt">¥{{ amountTxt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="contactPhones.length" class="lock-flow">
|
||||
<text class="lock-flow-title">联系管理员</text>
|
||||
<text class="lock-phone-hint">点击下方手机号即可拨打电话</text>
|
||||
<view
|
||||
v-for="item in contactPhones"
|
||||
:key="item.row_key"
|
||||
class="lock-phone"
|
||||
@click="callPhone(item.tel)"
|
||||
>
|
||||
<text class="lock-phone-name">{{ item.name || '管理员' }}</text>
|
||||
<view class="lock-phone-right">
|
||||
<text class="lock-phone-num">{{ item.tel }}</text>
|
||||
<text class="lock-phone-call">拨打</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text v-else class="lock-desc">暂未配置可拨打的管理员号码,请使用催办。</text>
|
||||
<view class="lock-btn-row">
|
||||
<view class="lock-btn lock-btn--ghost" @click="goPay">
|
||||
<text class="lock-btn-txt lock-btn-txt--dark">重新上传</text>
|
||||
</view>
|
||||
<view class="lock-btn" :class="{ 'is-off': urging }" @click="urge">
|
||||
<text class="lock-btn-txt">{{ urging ? '催办中...' : '催办' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
<text class="lock-badge">功能已锁定</text>
|
||||
<text class="lock-title">公账日账单已逾期</text>
|
||||
<text class="lock-desc">历史日账单未结清,接诊、开方已暂停。本提示不可关闭。</text>
|
||||
<view class="lock-stat">
|
||||
<view class="lock-stat-item">
|
||||
<text class="lock-stat-label">账单日</text>
|
||||
<text class="lock-stat-val">{{ billDateTxt || '—' }}</text>
|
||||
</view>
|
||||
<view class="lock-stat-item">
|
||||
<text class="lock-stat-label">应付款</text>
|
||||
<text class="lock-stat-val lock-stat-val--amt">¥{{ amountTxt }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="lock-flow">
|
||||
<text class="lock-flow-title">解锁流程</text>
|
||||
<text class="lock-flow-item">1. 账单日次日 {{ lockTimeTxt }} 后未处理即锁定;更早的账单全天锁定。</text>
|
||||
<text class="lock-flow-item">2. 在本提示中上传付款凭证(转账截图)。</text>
|
||||
<text v-if="unlockByVoucher" class="lock-flow-item">3. 提交成功后立刻解锁,接诊和开方可继续。</text>
|
||||
<text v-else class="lock-flow-item">3. 提交后仍锁定,等平台管理员确认到账后自动解锁。</text>
|
||||
<text class="lock-flow-item">4. 未达解锁条件前,无法返回,也无法继续业务。</text>
|
||||
</view>
|
||||
<view class="lock-btn-row">
|
||||
<view class="lock-btn" :class="{ 'is-off': openingPay }" @click="goPay">
|
||||
<text class="lock-btn-txt">{{ openingPay ? '准备中...' : '去上传凭证' }}</text>
|
||||
</view>
|
||||
<view v-if="canViewProgress" class="lock-btn lock-btn--ghost" @click="goWait">
|
||||
<text class="lock-btn-txt lock-btn-txt--dark">查看审核进度</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</lock-warning-decor>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 分包锁店页:锁定说明与上传在同一卡片内切换,警示 UI 对齐 PC 锁框
|
||||
*/
|
||||
import LockWarningDecor from './lock-warning-decor.vue'
|
||||
import {
|
||||
getPublicAccountDailyBillInfoApi,
|
||||
generatePublicAccountDailyBillApi,
|
||||
uploadPublicAccountDailyBillApi,
|
||||
urgePublicAccountDailyBillApi,
|
||||
getPublicAccountLockStatusApi,
|
||||
} from '@/api/publicAccountPay.js'
|
||||
import {
|
||||
uploadLegacyAdminImage,
|
||||
uploadClinicAdminChatFileApi,
|
||||
extractUploadUrl,
|
||||
} from '@/api/upload.js'
|
||||
import { unwrapXkApi } from '@/utils/api-response.js'
|
||||
import {
|
||||
getPublicAccountLock,
|
||||
isPublicAccountLocked,
|
||||
clearPublicAccountLock,
|
||||
goHomeAfterPublicAccountUnlock,
|
||||
} from '@/utils/publicAccountLock.js'
|
||||
|
||||
export default {
|
||||
components: { LockWarningDecor },
|
||||
data() {
|
||||
return {
|
||||
guardShow: true,
|
||||
step: 'hint',
|
||||
payload: {},
|
||||
dailyId: 0,
|
||||
voucherUrls: [],
|
||||
payRemark: '',
|
||||
submitting: false,
|
||||
openingPay: false,
|
||||
urging: false,
|
||||
allowLeave: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
navbarBg() {
|
||||
if (this.alertColor === 'blue') {
|
||||
return { background: 'linear-gradient(90deg, #1d4ed8 0%, #2563eb 100%)' }
|
||||
}
|
||||
if (this.alertColor === 'red') {
|
||||
return { background: 'linear-gradient(90deg, #b91c1c 0%, #dc2626 100%)' }
|
||||
}
|
||||
return { background: 'linear-gradient(90deg, #b45309 0%, #d97706 100%)' }
|
||||
},
|
||||
billDateTxt() {
|
||||
const date = String((this.payload && this.payload.bill_date) || '')
|
||||
if (date.length === 8) {
|
||||
return date.slice(0, 4) + '-' + date.slice(4, 6) + '-' + date.slice(6, 8)
|
||||
}
|
||||
return date
|
||||
},
|
||||
amountTxt() {
|
||||
return (this.payload && this.payload.platform_amount) || '0.00'
|
||||
},
|
||||
alertColor() {
|
||||
const color = (this.payload && this.payload.alert_color) || 'yellow'
|
||||
if (color === 'blue' || color === 'red') {
|
||||
return color
|
||||
}
|
||||
return 'yellow'
|
||||
},
|
||||
cardToneClass() {
|
||||
return {
|
||||
'is-blue': this.alertColor === 'blue',
|
||||
'is-yellow': this.alertColor === 'yellow',
|
||||
'is-red': this.alertColor === 'red',
|
||||
}
|
||||
},
|
||||
unlockByVoucher() {
|
||||
return String((this.payload && this.payload.unlock_mode) || '') === 'voucher'
|
||||
},
|
||||
lockTimeTxt() {
|
||||
return (this.payload && this.payload.lock_time) || '09:00'
|
||||
},
|
||||
unlockHint() {
|
||||
if (this.unlockByVoucher) {
|
||||
return '提交成功后立刻解锁,接诊和开方可继续。'
|
||||
}
|
||||
return '提交后仍锁定,需平台管理员确认到账后才会解锁。'
|
||||
},
|
||||
// 只展示配置里允许拨打的号码
|
||||
contactPhones() {
|
||||
const list = (this.payload && this.payload.contact_phones) || []
|
||||
const rows = []
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const item = list[i]
|
||||
const tel = this.resolveDialPhone(item)
|
||||
if (!tel) {
|
||||
continue
|
||||
}
|
||||
rows.push({
|
||||
name: (item && item.name) || '管理员',
|
||||
tel: tel,
|
||||
row_key: tel + '-' + i,
|
||||
})
|
||||
}
|
||||
return rows
|
||||
},
|
||||
// 任一字段是 3 就给看进度;不能 status=0 优先盖掉 bill_status=3
|
||||
canViewProgress() {
|
||||
return this.resolveBillStatus(
|
||||
this.payload && this.payload.status,
|
||||
this.payload && this.payload.bill_status
|
||||
) === 3
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.syncPayload()
|
||||
this.refreshLockStatus()
|
||||
},
|
||||
onBackPress() {
|
||||
return !this.allowLeave
|
||||
},
|
||||
methods: {
|
||||
syncPayload() {
|
||||
const cached = getPublicAccountLock()
|
||||
if (isPublicAccountLocked(cached)) {
|
||||
this.payload = cached
|
||||
this.dailyId = Number((cached && cached.daily_id) || 0)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 已传凭证优先认 3,避免锁缓存里的 0 盖掉进度入口
|
||||
*/
|
||||
resolveBillStatus() {
|
||||
let found = 0
|
||||
let has3 = false
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
if (arguments[i] == null || arguments[i] === '') {
|
||||
continue
|
||||
}
|
||||
const num = Number(arguments[i])
|
||||
if (num === 3) {
|
||||
has3 = true
|
||||
}
|
||||
if (found === 0) {
|
||||
found = num
|
||||
}
|
||||
}
|
||||
return has3 ? 3 : found
|
||||
},
|
||||
applyBillStatus(status) {
|
||||
const num = Number(status)
|
||||
if (num !== 0 && num !== 1 && num !== 2 && num !== 3) {
|
||||
return
|
||||
}
|
||||
this.payload.status = num
|
||||
this.payload.bill_status = num
|
||||
},
|
||||
/**
|
||||
* 进页拉系统配置配色、日汇总状态、可拨打号码
|
||||
* 为什么:4101 本地缓存可能缺 alert_color,改后台配置后也不会自动刷新
|
||||
*/
|
||||
async refreshLockStatus() {
|
||||
try {
|
||||
const res = await getPublicAccountLockStatusApi()
|
||||
const wrap = unwrapXkApi(res)
|
||||
if (!wrap.ok) {
|
||||
await this.syncBillStatusFromDetail()
|
||||
return
|
||||
}
|
||||
const live = wrap.payload || {}
|
||||
if (live.locked === false || live.locked === 0) {
|
||||
this.allowLeave = true
|
||||
clearPublicAccountLock()
|
||||
this.guardShow = false
|
||||
goHomeAfterPublicAccountUnlock()
|
||||
return
|
||||
}
|
||||
const nextStatus = this.resolveBillStatus(
|
||||
live.status,
|
||||
live.bill_status,
|
||||
this.payload && this.payload.status,
|
||||
this.payload && this.payload.bill_status
|
||||
)
|
||||
this.payload = Object.assign({}, this.payload || {}, live)
|
||||
this.applyBillStatus(nextStatus)
|
||||
this.dailyId = Number((this.payload && this.payload.daily_id) || 0)
|
||||
this.persistPayload()
|
||||
await this.syncBillStatusFromDetail()
|
||||
} catch (e) {
|
||||
await this.syncBillStatusFromDetail()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 日汇总详情才是 status 真源,锁缓存停在 0 时这里能把进度入口拉出来
|
||||
*/
|
||||
async syncBillStatusFromDetail() {
|
||||
if (this.dailyId < 1) {
|
||||
this.dailyId = Number((this.payload && this.payload.daily_id) || 0)
|
||||
}
|
||||
if (this.dailyId < 1) {
|
||||
return
|
||||
}
|
||||
const res = await getPublicAccountDailyBillInfoApi(this.dailyId)
|
||||
const wrap = unwrapXkApi(res)
|
||||
if (!wrap.ok) {
|
||||
return
|
||||
}
|
||||
const detail = wrap.payload || {}
|
||||
if (detail.platform_amount) {
|
||||
this.payload.platform_amount = detail.platform_amount
|
||||
}
|
||||
if (detail.status != null) {
|
||||
this.applyBillStatus(detail.status)
|
||||
}
|
||||
this.persistPayload()
|
||||
},
|
||||
goWait() {
|
||||
if (!this.canViewProgress) {
|
||||
uni.showToast({ title: '暂无待审核进度', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.step = 'wait'
|
||||
},
|
||||
persistPayload() {
|
||||
if (this.payload) {
|
||||
uni.setStorageSync('xk_public_account_lock', this.payload)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 拨号用明文。phone 会被拦截器脱敏,优先 phone_txt(拦截器解密后写出)
|
||||
*/
|
||||
resolveDialPhone(item) {
|
||||
if (!item) {
|
||||
return ''
|
||||
}
|
||||
const raw = String(item.phone_txt || item.tel || item.phone || '')
|
||||
const digits = raw.replace(/[^\d]/g, '')
|
||||
if (/^1\d{10}$/.test(digits)) {
|
||||
return digits
|
||||
}
|
||||
if (/^1\d{10}$/.test(raw)) {
|
||||
return raw
|
||||
}
|
||||
return ''
|
||||
},
|
||||
callPhone(phone) {
|
||||
const num = String(phone || '').replace(/[^\d]/g, '')
|
||||
if (!/^1\d{10}$/.test(num)) {
|
||||
uni.showToast({ title: '号码无效,请催办联系管理员', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.makePhoneCall({ phoneNumber: num })
|
||||
},
|
||||
async urge() {
|
||||
if (this.urging) {
|
||||
return
|
||||
}
|
||||
if (this.dailyId < 1) {
|
||||
uni.showToast({ title: '暂无待审核日账单', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.urging = true
|
||||
try {
|
||||
const res = await urgePublicAccountDailyBillApi({ id: this.dailyId })
|
||||
const wrap = unwrapXkApi(res)
|
||||
if (!wrap.ok) {
|
||||
uni.showToast({ title: wrap.message || '催办失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '已向管理员发送催办', icon: 'none' })
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.message) || '催办失败', icon: 'none' })
|
||||
} finally {
|
||||
this.urging = false
|
||||
}
|
||||
},
|
||||
onRemarkInput(e) {
|
||||
this.payRemark = (e && e.detail && e.detail.value) || ''
|
||||
},
|
||||
onLeave() {
|
||||
this.guardShow = false
|
||||
this.$nextTick(() => {
|
||||
this.guardShow = true
|
||||
})
|
||||
},
|
||||
backToHint() {
|
||||
this.step = 'hint'
|
||||
},
|
||||
async goPay() {
|
||||
if (this.openingPay) return
|
||||
this.openingPay = true
|
||||
try {
|
||||
await this.ensureDailyId()
|
||||
if (this.dailyId < 1) {
|
||||
uni.showToast({ title: '暂无待支付日账单', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.step = 'upload'
|
||||
} finally {
|
||||
this.openingPay = false
|
||||
}
|
||||
},
|
||||
async ensureDailyId() {
|
||||
if (this.dailyId > 0) {
|
||||
await this.loadDetail()
|
||||
return
|
||||
}
|
||||
const storeId = Number((this.payload && this.payload.store_id) || 0)
|
||||
if (!this.billDateTxt) {
|
||||
return
|
||||
}
|
||||
const res = await generatePublicAccountDailyBillApi({
|
||||
store_id: storeId,
|
||||
bill_date: this.billDateTxt,
|
||||
})
|
||||
const wrap = unwrapXkApi(res)
|
||||
if (!wrap.ok) {
|
||||
uni.showToast({ title: wrap.message || '暂无待支付日账单', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.dailyId = Number((wrap.payload && wrap.payload.id) || 0)
|
||||
if (this.payload) {
|
||||
this.payload.daily_id = this.dailyId
|
||||
this.persistPayload()
|
||||
}
|
||||
await this.loadDetail()
|
||||
},
|
||||
async loadDetail() {
|
||||
await this.syncBillStatusFromDetail()
|
||||
},
|
||||
chooseImage() {
|
||||
const remain = 9 - this.voucherUrls.length
|
||||
if (remain <= 0) return
|
||||
uni.chooseImage({
|
||||
count: remain,
|
||||
success: async (res) => {
|
||||
const paths = res.tempFilePaths || []
|
||||
try {
|
||||
uni.showLoading({ title: '上传中' })
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const url = await this.uploadOne(paths[i])
|
||||
if (url) this.voucherUrls.push(url)
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
async uploadOne(filePath) {
|
||||
if (uni.getStorageSync('loginMode') === 'clinic_admin') {
|
||||
const res = await uploadClinicAdminChatFileApi({ filePath, formData: {} })
|
||||
return extractUploadUrl(res)
|
||||
}
|
||||
const storeId = String((this.payload && this.payload.store_id) || '11001')
|
||||
return uploadLegacyAdminImage({
|
||||
filePath,
|
||||
formData: { store_id: storeId },
|
||||
})
|
||||
},
|
||||
removeImage(idx) {
|
||||
this.voucherUrls.splice(idx, 1)
|
||||
},
|
||||
async submit() {
|
||||
if (!this.voucherUrls.length) {
|
||||
uni.showToast({ title: '请至少上传 1 张付款凭证', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (this.dailyId < 1) {
|
||||
uni.showToast({ title: '暂无待支付日账单', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (this.submitting) return
|
||||
this.submitting = true
|
||||
try {
|
||||
const res = await uploadPublicAccountDailyBillApi({
|
||||
id: this.dailyId,
|
||||
voucher_urls: this.voucherUrls,
|
||||
pay_remark: this.payRemark,
|
||||
})
|
||||
const wrap = unwrapXkApi(res)
|
||||
if (!wrap.ok) {
|
||||
uni.showToast({ title: wrap.message || '上传失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (wrap.payload && wrap.payload.unlocked) {
|
||||
uni.showToast({ title: '已解锁', icon: 'none' })
|
||||
this.allowLeave = true
|
||||
clearPublicAccountLock()
|
||||
this.guardShow = false
|
||||
setTimeout(() => {
|
||||
goHomeAfterPublicAccountUnlock()
|
||||
}, 300)
|
||||
return
|
||||
}
|
||||
const result = wrap.payload || {}
|
||||
this.applyBillStatus(this.resolveBillStatus(result.status, result.bill_status, 3))
|
||||
if (result.contact_phones && result.contact_phones.length) {
|
||||
this.payload.contact_phones = result.contact_phones
|
||||
}
|
||||
this.persistPayload()
|
||||
uni.showToast({ title: '已提交,等待管理员审核', icon: 'none' })
|
||||
this.step = 'wait'
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.message) || '上传失败', icon: 'none' })
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.lock-page {
|
||||
min-height: 100vh;
|
||||
background: #17120a;
|
||||
}
|
||||
.lock-page.is-blue {
|
||||
background: #0a1220;
|
||||
}
|
||||
.lock-page.is-red {
|
||||
background: #200a0a;
|
||||
}
|
||||
.lock-body {
|
||||
padding: 8rpx 32rpx 80rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.lock-body.is-form {
|
||||
text-align: left;
|
||||
}
|
||||
.lock-card {
|
||||
padding: 24rpx 8rpx 8rpx;
|
||||
background: transparent;
|
||||
}
|
||||
.lock-badge {
|
||||
display: inline-block;
|
||||
padding: 4rpx 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: #d97706;
|
||||
border: 1px solid rgba(217, 119, 6, 0.4);
|
||||
border-radius: 999rpx;
|
||||
background: rgba(217, 119, 6, 0.12);
|
||||
}
|
||||
.lock-card.is-blue .lock-badge {
|
||||
color: #2563eb;
|
||||
border-color: rgba(37, 99, 235, 0.4);
|
||||
background: rgba(37, 99, 235, 0.12);
|
||||
}
|
||||
.lock-card.is-red .lock-badge {
|
||||
color: #dc2626;
|
||||
border-color: rgba(220, 38, 38, 0.4);
|
||||
background: rgba(220, 38, 38, 0.12);
|
||||
}
|
||||
.lock-title {
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #f8fafc;
|
||||
}
|
||||
.lock-desc {
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.lock-stat {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.lock-stat-item,
|
||||
.lock-flow {
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 1px solid rgba(217, 119, 6, 0.3);
|
||||
box-shadow: 0 0 12rpx rgba(217, 119, 6, 0.16);
|
||||
background: rgba(15, 23, 42, 0.55);
|
||||
}
|
||||
.lock-card.is-blue .lock-stat-item,
|
||||
.lock-card.is-blue .lock-flow {
|
||||
border-color: rgba(37, 99, 235, 0.3);
|
||||
box-shadow: 0 0 12rpx rgba(37, 99, 235, 0.16);
|
||||
}
|
||||
.lock-card.is-red .lock-stat-item,
|
||||
.lock-card.is-red .lock-flow {
|
||||
border-color: rgba(220, 38, 38, 0.3);
|
||||
box-shadow: 0 0 12rpx rgba(220, 38, 38, 0.16);
|
||||
}
|
||||
.lock-stat-item {
|
||||
flex: 1;
|
||||
}
|
||||
.lock-stat-label {
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.lock-stat-val {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #f8fafc;
|
||||
}
|
||||
.lock-stat-val--amt {
|
||||
color: #d97706;
|
||||
}
|
||||
.lock-card.is-blue .lock-stat-val--amt {
|
||||
color: #2563eb;
|
||||
}
|
||||
.lock-card.is-red .lock-stat-val--amt {
|
||||
color: #dc2626;
|
||||
}
|
||||
.lock-flow {
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
.lock-flow-title {
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #d97706;
|
||||
}
|
||||
.lock-card.is-blue .lock-flow-title {
|
||||
color: #2563eb;
|
||||
}
|
||||
.lock-card.is-red .lock-flow-title {
|
||||
color: #dc2626;
|
||||
}
|
||||
.lock-flow-item {
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #64748b;
|
||||
}
|
||||
.lock-flow-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.lock-label {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.lock-imgs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.lock-img-wrap,
|
||||
.lock-img-add {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 12rpx;
|
||||
position: relative;
|
||||
}
|
||||
.lock-img {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.lock-img-del {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
line-height: 32rpx;
|
||||
text-align: center;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.lock-img-add {
|
||||
border: 1px dashed rgba(148, 163, 184, 0.45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(15, 23, 42, 0.45);
|
||||
}
|
||||
.lock-img-add-txt {
|
||||
font-size: 48rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.lock-remark {
|
||||
width: 100%;
|
||||
min-height: 140rpx;
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 24rpx;
|
||||
background: rgba(15, 23, 42, 0.55);
|
||||
color: #e2e8f0;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.lock-btn-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.lock-btn-row .lock-btn {
|
||||
flex: 1;
|
||||
}
|
||||
.lock-btn {
|
||||
height: 88rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #d97706;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.lock-page.is-blue .lock-btn {
|
||||
background: #2563eb;
|
||||
}
|
||||
.lock-page.is-red .lock-btn {
|
||||
background: #dc2626;
|
||||
}
|
||||
.lock-btn--ghost {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(217, 119, 6, 0.45);
|
||||
}
|
||||
.lock-page.is-blue .lock-btn--ghost {
|
||||
border-color: rgba(37, 99, 235, 0.45);
|
||||
}
|
||||
.lock-page.is-red .lock-btn--ghost {
|
||||
border-color: rgba(220, 38, 38, 0.45);
|
||||
}
|
||||
.lock-btn.is-off {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.lock-btn-txt {
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.lock-btn-txt--dark {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.lock-btn--mb {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.lock-phone-hint {
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.lock-phone {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12rpx 0;
|
||||
border-bottom: 1px solid #eef2f6;
|
||||
}
|
||||
.lock-phone:last-child {
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.lock-phone-name {
|
||||
font-size: 26rpx;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.lock-phone-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.lock-phone-num {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #d97706;
|
||||
}
|
||||
.lock-phone-call {
|
||||
font-size: 22rpx;
|
||||
color: #d97706;
|
||||
padding: 4rpx 12rpx;
|
||||
border: 1px solid rgba(217, 119, 6, 0.4);
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
.lock-page.is-blue .lock-phone-num,
|
||||
.lock-page.is-blue .lock-phone-call {
|
||||
color: #2563eb;
|
||||
}
|
||||
.lock-page.is-blue .lock-phone-call {
|
||||
border-color: rgba(37, 99, 235, 0.4);
|
||||
}
|
||||
.lock-page.is-red .lock-phone-num,
|
||||
.lock-page.is-red .lock-phone-call {
|
||||
color: #dc2626;
|
||||
}
|
||||
.lock-page.is-red .lock-phone-call {
|
||||
border-color: rgba(220, 38, 38, 0.4);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<!-- 对齐 PC LockWarningCanvas:说明页三角+脉冲环,各页都有上下警戒条 -->
|
||||
<view class="decor" :class="toneClass">
|
||||
<view class="tape tape--top">
|
||||
<view class="tape-bar tape-bar--fwd"></view>
|
||||
</view>
|
||||
<view v-if="!tapesOnly" class="hero">
|
||||
<view class="ring ring--1"></view>
|
||||
<view class="ring ring--2"></view>
|
||||
<view class="ring ring--3"></view>
|
||||
<view class="glow"></view>
|
||||
<view class="tri-wrap">
|
||||
<view class="tri"></view>
|
||||
<text class="bang">!</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="slot-wrap">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="tape tape--bottom">
|
||||
<view class="tape-bar tape-bar--rev"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 锁店警示装饰,视觉对齐 PC 锁框(警戒条 / 三角 / 脉冲环)
|
||||
* 小程序不用 canvas,用 CSS 动画避免真机画布兼容问题
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: 'yellow',
|
||||
},
|
||||
tapesOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
toneClass() {
|
||||
return {
|
||||
'is-blue': this.color === 'blue',
|
||||
'is-yellow': this.color === 'yellow',
|
||||
'is-red': this.color === 'red',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.decor {
|
||||
--tape: #d97706;
|
||||
--ink: #1c1917;
|
||||
--ring: rgba(217, 119, 6, 0.35);
|
||||
--tri: #d97706;
|
||||
--glow: rgba(217, 119, 6, 0.22);
|
||||
pointer-events: none;
|
||||
}
|
||||
.slot-wrap {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.decor.is-blue {
|
||||
--tape: #2563eb;
|
||||
--ink: #0f172a;
|
||||
--ring: rgba(37, 99, 235, 0.35);
|
||||
--tri: #2563eb;
|
||||
--glow: rgba(37, 99, 235, 0.22);
|
||||
}
|
||||
.decor.is-red {
|
||||
--tape: #dc2626;
|
||||
--ink: #1c1917;
|
||||
--ring: rgba(220, 38, 38, 0.35);
|
||||
--tri: #dc2626;
|
||||
--glow: rgba(220, 38, 38, 0.22);
|
||||
}
|
||||
.tape {
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 36rpx;
|
||||
overflow: hidden;
|
||||
background: var(--tape);
|
||||
}
|
||||
.tape--top {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
.tape--bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
.tape-bar {
|
||||
width: 240%;
|
||||
height: 100%;
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
var(--tape) 0,
|
||||
var(--tape) 16rpx,
|
||||
var(--ink) 16rpx,
|
||||
var(--ink) 32rpx
|
||||
);
|
||||
}
|
||||
.tape-bar--fwd {
|
||||
animation: tape-fwd 0.9s linear infinite;
|
||||
}
|
||||
.tape-bar--rev {
|
||||
animation: tape-rev 0.9s linear infinite;
|
||||
}
|
||||
@keyframes tape-fwd {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-32rpx);
|
||||
}
|
||||
}
|
||||
@keyframes tape-rev {
|
||||
from {
|
||||
transform: translateX(-32rpx);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
.hero {
|
||||
position: relative;
|
||||
height: 280rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.glow {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
margin-left: -140rpx;
|
||||
margin-top: -140rpx;
|
||||
border-radius: 50%;
|
||||
background: var(--glow);
|
||||
}
|
||||
.ring {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
border: 2rpx solid var(--ring);
|
||||
border-radius: 50%;
|
||||
animation: ring-pulse 2.4s ease-out infinite;
|
||||
}
|
||||
.ring--1 {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-left: -40rpx;
|
||||
margin-top: -40rpx;
|
||||
}
|
||||
.ring--2 {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-left: -40rpx;
|
||||
margin-top: -40rpx;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
.ring--3 {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-left: -40rpx;
|
||||
margin-top: -40rpx;
|
||||
animation-delay: 1.6s;
|
||||
}
|
||||
@keyframes ring-pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 0.45;
|
||||
}
|
||||
100% {
|
||||
transform: scale(3.4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.tri-wrap {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 120rpx;
|
||||
height: 110rpx;
|
||||
margin-left: -60rpx;
|
||||
margin-top: -58rpx;
|
||||
}
|
||||
.tri {
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0 auto;
|
||||
border-left: 60rpx solid transparent;
|
||||
border-right: 60rpx solid transparent;
|
||||
border-bottom: 104rpx solid var(--tri);
|
||||
filter: drop-shadow(0 0 12rpx var(--glow));
|
||||
}
|
||||
.bang {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 36rpx;
|
||||
text-align: center;
|
||||
font-size: 48rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
77
utils/publicAccountLock.js
Normal file
77
utils/publicAccountLock.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 公账逾期锁店:4101 写入本地并强制跳到分包锁店页
|
||||
*/
|
||||
const STORAGE_KEY = 'xk_public_account_lock'
|
||||
export const PUBLIC_ACCOUNT_LOCK_PAGE = '/subPackages/sub_workbench/public_account_lock/index'
|
||||
|
||||
let jumping = false
|
||||
|
||||
export function setPublicAccountLock(payload) {
|
||||
const data = payload && typeof payload === 'object' ? payload : {}
|
||||
uni.setStorageSync(STORAGE_KEY, data)
|
||||
uni.$emit('public-account-lock', data)
|
||||
redirectToPublicAccountLockPage()
|
||||
}
|
||||
|
||||
export function getPublicAccountLock() {
|
||||
return uni.getStorageSync(STORAGE_KEY) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已锁店:漏单未出日汇总时 daily_id=0,要用 bill_date / locked
|
||||
*/
|
||||
export function isPublicAccountLocked(payload) {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return false
|
||||
}
|
||||
if (payload.locked === true || payload.locked === 1) {
|
||||
return true
|
||||
}
|
||||
return !!(payload.daily_id || payload.bill_date)
|
||||
}
|
||||
|
||||
export function clearPublicAccountLock() {
|
||||
uni.removeStorageSync(STORAGE_KEY)
|
||||
uni.$emit('public-account-lock-clear')
|
||||
}
|
||||
|
||||
export function isOnPublicAccountLockPage() {
|
||||
const pages = getCurrentPages()
|
||||
const cur = pages.length ? pages[pages.length - 1] : null
|
||||
const route = cur && cur.route ? String(cur.route) : ''
|
||||
return route.indexOf('public_account_lock') !== -1
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截器命中 4101 后跳分包锁店页;已在该页则不再跳,避免死循环
|
||||
*/
|
||||
export function redirectToPublicAccountLockPage() {
|
||||
if (isOnPublicAccountLockPage() || jumping) {
|
||||
return
|
||||
}
|
||||
jumping = true
|
||||
// reLaunch 清掉上一页,模拟器返回不能退回业务页;tabBar 页也不能用 redirectTo
|
||||
uni.reLaunch({
|
||||
url: PUBLIC_ACCOUNT_LOCK_PAGE,
|
||||
complete() {
|
||||
jumping = false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 解锁后回各自工作台
|
||||
*/
|
||||
export function goHomeAfterPublicAccountUnlock() {
|
||||
const mode = uni.getStorageSync('loginMode')
|
||||
const identity = Number(uni.getStorageSync('identity') || 1)
|
||||
if (mode === 'clinic_admin') {
|
||||
uni.reLaunch({ url: '/subPackages/sub_clinic_admin/home/index' })
|
||||
return
|
||||
}
|
||||
if (identity === 2) {
|
||||
uni.switchTab({ url: '/pages/pharmacist/index' })
|
||||
return
|
||||
}
|
||||
uni.switchTab({ url: '/pages/workbench/index' })
|
||||
}
|
||||
Reference in New Issue
Block a user