This commit is contained in:
parent
a9132acd98
commit
0ac3fd5645
111
api/cashier.ts
111
api/cashier.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import { get, post } from '../utils/request';
|
import { get, post, postForm } from '../utils/request';
|
||||||
|
|
||||||
export interface MerchantConfigQuery {
|
export interface MerchantConfigQuery {
|
||||||
merchant_id?: string;
|
merchant_id?: string;
|
||||||
|
|
@ -56,6 +56,11 @@ export interface SupplierInfo {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MerchantListItem {
|
||||||
|
merchant_id: string | number;
|
||||||
|
merchant_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiResult<T = any> {
|
export interface ApiResult<T = any> {
|
||||||
code: number | string;
|
code: number | string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
|
|
@ -72,6 +77,79 @@ export interface TradeQueryResult {
|
||||||
real_money?: string;
|
real_money?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TradeListItem {
|
||||||
|
trade_no: string;
|
||||||
|
pay_status: number;
|
||||||
|
real_money: string;
|
||||||
|
merchant_id?: number | string;
|
||||||
|
create_time?: string;
|
||||||
|
wuye_money?: string;
|
||||||
|
pay_time?: string | null;
|
||||||
|
refund_time?: string | null;
|
||||||
|
pay_type?: number;
|
||||||
|
supplier_name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TradeListQuery {
|
||||||
|
trade_no?: string;
|
||||||
|
page?: number | string;
|
||||||
|
limit?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformScoreResult {
|
||||||
|
score?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScoreRecordItem {
|
||||||
|
id?: string | number;
|
||||||
|
score?: string | number;
|
||||||
|
change_score?: string | number;
|
||||||
|
type?: string | number;
|
||||||
|
remark?: string;
|
||||||
|
create_time?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScoreRecordQuery {
|
||||||
|
page?: number | string;
|
||||||
|
limit?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CashTradeInfo {
|
||||||
|
trade_no?: string;
|
||||||
|
pay_status?: number;
|
||||||
|
pay_type?: number;
|
||||||
|
total_money?: string;
|
||||||
|
reduce_money?: string;
|
||||||
|
real_money?: string;
|
||||||
|
merchant_id?: number | string;
|
||||||
|
supplier_id?: number | string;
|
||||||
|
timeout?: number;
|
||||||
|
create_time?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CashTradeInfoResult {
|
||||||
|
trade?: CashTradeInfo;
|
||||||
|
body?: string;
|
||||||
|
subject?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CashTradePayPayload {
|
||||||
|
trade_no: string;
|
||||||
|
platform?: string;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CashTradePayResult {
|
||||||
|
trade_no?: string;
|
||||||
|
app_id?: string;
|
||||||
|
timestamp?: string;
|
||||||
|
nonce_str?: string;
|
||||||
|
package?: string;
|
||||||
|
sign_type?: string;
|
||||||
|
pay_sign?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const merchantConfig = (data: MerchantConfigQuery = {}) => get<ApiResult>('v1/cashier/merchant', data);
|
export const merchantConfig = (data: MerchantConfigQuery = {}) => get<ApiResult>('v1/cashier/merchant', data);
|
||||||
|
|
||||||
export const createCashierOrder = (data: CashierOrderPayload) =>
|
export const createCashierOrder = (data: CashierOrderPayload) =>
|
||||||
|
|
@ -81,14 +159,45 @@ export const cashierPay = (data: { order_id: string }) => post<ApiResult<Cashier
|
||||||
|
|
||||||
export const cashierRecordList = (data: any = {}) => get('v1/cashier/records', data);
|
export const cashierRecordList = (data: any = {}) => get('v1/cashier/records', data);
|
||||||
|
|
||||||
|
export const tradeList = (data: TradeListQuery = {}) =>
|
||||||
|
get<ApiResult<TradeListItem[]>>('user/v1/trade.list', {
|
||||||
|
trade_no: data.trade_no ?? '',
|
||||||
|
page: data.page ?? 1,
|
||||||
|
limit: data.limit ?? 10
|
||||||
|
});
|
||||||
|
|
||||||
export const bindMerchantByUrl = (url: string) =>
|
export const bindMerchantByUrl = (url: string) =>
|
||||||
get<ApiResult<BoundMerchantInfo>>('user/v1/bing.merchant', { url });
|
get<ApiResult<BoundMerchantInfo>>('user/v1/bing.merchant', { url });
|
||||||
|
|
||||||
export const supplierByUrl = (url: string) =>
|
export const supplierByUrl = (url: string) =>
|
||||||
get<ApiResult<SupplierInfo>>('user/v1/supplier', { url });
|
get<ApiResult<SupplierInfo>>('user/v1/supplier', { url });
|
||||||
|
|
||||||
|
export const merchantListByUrl = (url: string) =>
|
||||||
|
get<ApiResult<MerchantListItem[]>>('user/v1/merchant.list', { url });
|
||||||
|
|
||||||
|
export const changeMerchant = (data: { merchant_id: string | number; url: string }) =>
|
||||||
|
postForm<ApiResult>('user/v1/merchant.change', data);
|
||||||
|
|
||||||
export const queryTrade = (tradeNo: string) =>
|
export const queryTrade = (tradeNo: string) =>
|
||||||
get<ApiResult<TradeQueryResult>>('user/v1/trade/query', { trade_no: tradeNo });
|
get<ApiResult<TradeQueryResult>>('user/v1/trade/query', { trade_no: tradeNo });
|
||||||
|
|
||||||
export const cancelTrade = (tradeNo: string) =>
|
export const cancelTrade = (tradeNo: string) =>
|
||||||
get<ApiResult>('user/v1/trade/cancel', { trade_no: tradeNo });
|
get<ApiResult>('user/v1/trade/cancel', { trade_no: tradeNo });
|
||||||
|
|
||||||
|
export const platformScore = () => get<ApiResult<PlatformScoreResult>>('user/v1/platform.score');
|
||||||
|
|
||||||
|
export const platformScoreRecord = (data: ScoreRecordQuery = {}) =>
|
||||||
|
get<ApiResult<ScoreRecordItem[]>>('user/v1/platform.score.record', {
|
||||||
|
page: data.page ?? 1,
|
||||||
|
limit: data.limit ?? 10
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cashTradeInfo = (tradeNo: string) =>
|
||||||
|
get<ApiResult<CashTradeInfoResult>>('user/v1/cash.trade.info', { trade_no: tradeNo });
|
||||||
|
|
||||||
|
export const cashTradePay = (data: CashTradePayPayload) =>
|
||||||
|
post<ApiResult<CashTradePayResult>>('user/v1/cash.trade', {
|
||||||
|
trade_no: data.trade_no,
|
||||||
|
platform: data.platform ?? 'mp-weixin',
|
||||||
|
remark: data.remark ?? ''
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,19 @@ export const APP_CONFIG = {
|
||||||
API_BASE_URL: 'https://dev.api.leapy.cn',
|
API_BASE_URL: 'https://dev.api.leapy.cn',
|
||||||
APP_ID: '123018',
|
APP_ID: '123018',
|
||||||
RESOURCE_URL: 'https://resource.leapy.cn/staff/',
|
RESOURCE_URL: 'https://resource.leapy.cn/staff/',
|
||||||
|
PROPERTY_IMG_URL: 'https://resource.leapy.cn/',
|
||||||
STORAGE_TOKEN_KEY: 'cashier:auth:token',
|
STORAGE_TOKEN_KEY: 'cashier:auth:token',
|
||||||
// 小程序代码可被反编译,密钥仅用于避免 token 明文落盘,不替代服务端的有效期与失效策略。
|
// 小程序代码可被反编译,密钥仅用于避免 token 明文落盘,不替代服务端的有效期与失效策略。
|
||||||
STORAGE_ENCRYPTION_KEY: 'cashier-storage-key-123018-v1-32'
|
STORAGE_ENCRYPTION_KEY: 'cashier-storage-key-123018-v1-32'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const PROPERTY_APP = {
|
||||||
|
APP_ID: 'wx42076f086669c49e',
|
||||||
|
BIND_HOUSE_PATH: 'pagesC/certification/index?from=cashier',
|
||||||
|
MINE_PATH: 'pages/mine/mine',
|
||||||
|
WECHAT_APP_ID: 'wx02de0e1de9f4bda3'
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const DEFAULT_MERCHANT = {
|
export const DEFAULT_MERCHANT = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '上海大观园',
|
name: '上海大观园',
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@
|
||||||
"postcss" : true,
|
"postcss" : true,
|
||||||
"minified" : true
|
"minified" : true
|
||||||
},
|
},
|
||||||
"usingComponents" : true
|
"usingComponents" : true,
|
||||||
|
"navigateToMiniProgramAppIdList" : [
|
||||||
|
"wx42076f086669c49e"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"mp-alipay" : {
|
"mp-alipay" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
|
|
|
||||||
16
pages.json
16
pages.json
|
|
@ -34,6 +34,22 @@
|
||||||
"navigationBarTitleText": "付款记录"
|
"navigationBarTitleText": "付款记录"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/index",
|
||||||
|
"style": {
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "个人中心"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/score-records",
|
||||||
|
"style": {
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "积分流水"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/house/bind",
|
"path": "pages/house/bind",
|
||||||
"style": {
|
"style": {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="cashier-page">
|
<view class="cashier-page">
|
||||||
|
<view class="mine-entry" @tap="goMine">
|
||||||
|
<text class="ri-user-3-line mine-entry-icon"></text>
|
||||||
|
<text>我的</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<block v-if="!hasMerchantUrl">
|
<block v-if="!hasMerchantUrl">
|
||||||
<view class="empty-page">
|
<view class="empty-page">
|
||||||
<image class="empty-bg" :src="emptyStateBackground" mode="aspectFill" />
|
<image class="empty-bg" :src="emptyStateBackground" mode="aspectFill" />
|
||||||
|
|
@ -14,6 +19,10 @@
|
||||||
<text class="ri-qr-scan-2-line empty-scan-button-icon"></text>
|
<text class="ri-qr-scan-2-line empty-scan-button-icon"></text>
|
||||||
<text>扫一扫商家收款码</text>
|
<text>扫一扫商家收款码</text>
|
||||||
</button>
|
</button>
|
||||||
|
<view class="empty-mine-link" hover-class="empty-mine-link-hover" @tap="goMine">
|
||||||
|
<text class="ri-user-3-line empty-mine-link-icon"></text>
|
||||||
|
<text>个人中心</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -46,7 +55,6 @@
|
||||||
{{ benefitTitle }}
|
{{ benefitTitle }}
|
||||||
<text class="benefit-value">¥ {{ accumulationAmount }}</text>
|
<text class="benefit-value">¥ {{ accumulationAmount }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="propertyName" class="benefit-property li-two-line">绑定物业:{{ propertyName }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -60,15 +68,51 @@
|
||||||
:disabled="paying"
|
:disabled="paying"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view class="bind-row" @tap.stop="handleBindHouse">
|
<view
|
||||||
<text class="bind-text">绑定房源</text>
|
class="property-row"
|
||||||
<text class="bind-hint">后期绑定</text>
|
hover-class="property-row-hover"
|
||||||
<text class="ri-arrow-right-s-line bind-arrow"></text>
|
@tap.stop="handlePropertyRow"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="property-name li-single-line"
|
||||||
|
:class="{ 'property-name-action': isPropertyActionText }"
|
||||||
|
>
|
||||||
|
{{ propertyRowText }}
|
||||||
|
</text>
|
||||||
|
<text class="ri-arrow-right-s-line property-arrow"></text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view v-if="propertySheetVisible" class="property-sheet-mask" @tap="closePropertySheet">
|
||||||
|
<view class="property-sheet" @tap.stop>
|
||||||
|
<view class="property-sheet-header">
|
||||||
|
<text class="property-sheet-title">{{ propertySheetTitle }}</text>
|
||||||
|
<text class="ri-close-line property-sheet-close" @tap="closePropertySheet"></text>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="property-sheet-list">
|
||||||
|
<view
|
||||||
|
v-for="item in propertyList"
|
||||||
|
:key="String(item.merchant_id)"
|
||||||
|
class="property-sheet-item"
|
||||||
|
:class="{ active: isSameMerchantId(item.merchant_id, propertyMerchantId) }"
|
||||||
|
@tap="handleSwitchProperty(item)"
|
||||||
|
>
|
||||||
|
<text class="property-sheet-item-name li-two-line">{{ item.merchant_name }}</text>
|
||||||
|
<text
|
||||||
|
v-if="isSameMerchantId(item.merchant_id, propertyMerchantId)"
|
||||||
|
class="ri-check-line property-sheet-item-check"
|
||||||
|
></text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="property-sheet-footer" @tap="goBindProperty">
|
||||||
|
<text class="ri-add-line property-sheet-footer-icon"></text>
|
||||||
|
<text>绑定新房源</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="keyboard" :class="{ disabled: paying }">
|
<view class="keyboard" :class="{ disabled: paying }">
|
||||||
<view class="keypad">
|
<view class="keypad">
|
||||||
<view class="number-grid">
|
<view class="number-grid">
|
||||||
|
|
@ -104,10 +148,11 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||||
import { bindMerchantByUrl, cancelTrade, supplierByUrl } from '@/api/cashier';
|
import { bindMerchantByUrl, cancelTrade, changeMerchant, merchantListByUrl, supplierByUrl } from '@/api/cashier';
|
||||||
|
import type { MerchantListItem } from '@/api/cashier';
|
||||||
import { postForm } from '@/utils/request';
|
import { postForm } from '@/utils/request';
|
||||||
import { DEFAULT_MERCHANT } from '@/config';
|
import { DEFAULT_MERCHANT, APP_CONFIG, PROPERTY_APP } from '@/config';
|
||||||
import {
|
import {
|
||||||
getPropertyBindingCacheKey,
|
getPropertyBindingCacheKey,
|
||||||
getPropertyBindingCacheKeyByHash,
|
getPropertyBindingCacheKeyByHash,
|
||||||
|
|
@ -141,6 +186,10 @@ const paying = ref(false);
|
||||||
const activeMerchantUrl = ref('');
|
const activeMerchantUrl = ref('');
|
||||||
const scoreName = ref('物业公积金');
|
const scoreName = ref('物业公积金');
|
||||||
const propertyName = ref('');
|
const propertyName = ref('');
|
||||||
|
const propertyMerchantId = ref('');
|
||||||
|
const propertyList = ref<MerchantListItem[]>([]);
|
||||||
|
const propertySheetVisible = ref(false);
|
||||||
|
const switchingProperty = ref(false);
|
||||||
const scanning = ref(false);
|
const scanning = ref(false);
|
||||||
const numberKeys = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
const numberKeys = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
|
||||||
|
|
@ -148,10 +197,17 @@ const canPay = computed(() => Number(amount.value) > 0 && !paying.value);
|
||||||
const payableAmount = computed(() => Number(amount.value || 0).toFixed(2));
|
const payableAmount = computed(() => Number(amount.value || 0).toFixed(2));
|
||||||
const accumulationAmount = computed(() => {
|
const accumulationAmount = computed(() => {
|
||||||
const value = Number(amount.value || 0) * Number(merchant.value.accumulationRate || 0);
|
const value = Number(amount.value || 0) * Number(merchant.value.accumulationRate || 0);
|
||||||
return value.toFixed(3);
|
return value.toFixed(2);
|
||||||
});
|
});
|
||||||
const showBenefitTip = computed(() => Number(merchant.value.accumulationRate) > 0);
|
const showBenefitTip = computed(() => Number(merchant.value.accumulationRate) > 0);
|
||||||
const benefitTitle = computed(() => `预计获得${scoreName.value || '物业公积金'}`);
|
const benefitTitle = computed(() => `预计获得${scoreName.value || '物业公积金'}`);
|
||||||
|
const propertyRowText = computed(() => {
|
||||||
|
if (propertyName.value) return propertyName.value;
|
||||||
|
if (propertyList.value.length) return '选择物业';
|
||||||
|
return '业主去绑定';
|
||||||
|
});
|
||||||
|
const isPropertyActionText = computed(() => !propertyName.value);
|
||||||
|
const propertySheetTitle = computed(() => (propertyName.value ? '切换物业' : '选择物业'));
|
||||||
|
|
||||||
interface PropertyBindingCache {
|
interface PropertyBindingCache {
|
||||||
merchant_id?: string;
|
merchant_id?: string;
|
||||||
|
|
@ -190,8 +246,46 @@ const resolvePropertyFromPayload = (payload: Record<string, any> = {}) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyPropertyInfo = (property?: { merchant_name?: string; score_name?: string }) => {
|
const hasBindingPayload = (payload?: Record<string, any> | null) => {
|
||||||
|
if (!payload || typeof payload !== 'object') return false;
|
||||||
|
const merchantData = payload.merchant || payload;
|
||||||
|
const merchantId = merchantData.merchant_id;
|
||||||
|
const merchantName = merchantData.merchant_name;
|
||||||
|
return merchantId !== undefined && merchantId !== null && merchantId !== '' && !!merchantName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildBindingCache = (payload: Record<string, any> = {}): PropertyBindingCache => {
|
||||||
|
const merchantData = payload.merchant || payload;
|
||||||
|
return {
|
||||||
|
merchant_id: merchantData.merchant_id,
|
||||||
|
merchant_name: merchantData.merchant_name,
|
||||||
|
score_name: merchantData.config?.score_name,
|
||||||
|
fee_rate: resolveFeeRateFromPayload(payload)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearPropertyBinding = () => {
|
||||||
|
propertyName.value = '';
|
||||||
|
propertyMerchantId.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMerchantList = async (merchantUrl: string) => {
|
||||||
|
const response = await merchantListByUrl(merchantUrl);
|
||||||
|
if (Number(response?.code) !== 200) {
|
||||||
|
throw new Error(response?.msg || '获取物业列表失败');
|
||||||
|
}
|
||||||
|
return Array.isArray(response?.data) ? response.data : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyPropertyInfo = (property?: {
|
||||||
|
merchant_id?: string | number;
|
||||||
|
merchant_name?: string;
|
||||||
|
score_name?: string;
|
||||||
|
}) => {
|
||||||
if (!property) return;
|
if (!property) return;
|
||||||
|
if (property.merchant_id !== undefined && property.merchant_id !== null && property.merchant_id !== '') {
|
||||||
|
propertyMerchantId.value = String(property.merchant_id);
|
||||||
|
}
|
||||||
if (property.merchant_name) {
|
if (property.merchant_name) {
|
||||||
propertyName.value = property.merchant_name;
|
propertyName.value = property.merchant_name;
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +294,10 @@ const applyPropertyInfo = (property?: { merchant_name?: string; score_name?: str
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSameMerchantId = (left: string | number, right: string | number) => {
|
||||||
|
return String(left) === String(right);
|
||||||
|
};
|
||||||
|
|
||||||
const buildSupplierCache = (payload: Record<string, any> = {}, previous?: SupplierCache | null): SupplierCache => {
|
const buildSupplierCache = (payload: Record<string, any> = {}, previous?: SupplierCache | null): SupplierCache => {
|
||||||
const supplierData = payload.supplier || {};
|
const supplierData = payload.supplier || {};
|
||||||
return {
|
return {
|
||||||
|
|
@ -226,14 +324,11 @@ const applyMerchantInfo = (data: any = {}) => {
|
||||||
|
|
||||||
const applyBindingInfo = (binding?: PropertyBindingCache | null) => {
|
const applyBindingInfo = (binding?: PropertyBindingCache | null) => {
|
||||||
if (!binding) return;
|
if (!binding) return;
|
||||||
const updates: Record<string, unknown> = {
|
|
||||||
merchant_id: binding.merchant_id,
|
|
||||||
merchant_name: binding.merchant_name
|
|
||||||
};
|
|
||||||
if (hasFeeRate(binding.fee_rate)) {
|
if (hasFeeRate(binding.fee_rate)) {
|
||||||
updates.accumulationRate = parseFeeRate(binding.fee_rate);
|
applyMerchantInfo({
|
||||||
|
accumulationRate: parseFeeRate(binding.fee_rate)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
applyMerchantInfo(updates);
|
|
||||||
applyPropertyInfo(binding);
|
applyPropertyInfo(binding);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -263,6 +358,9 @@ const initWithMerchantUrl = async (merchantUrl: string) => {
|
||||||
hasMerchantUrl.value = true;
|
hasMerchantUrl.value = true;
|
||||||
activeMerchantUrl.value = merchantUrl;
|
activeMerchantUrl.value = merchantUrl;
|
||||||
propertyName.value = '';
|
propertyName.value = '';
|
||||||
|
propertyMerchantId.value = '';
|
||||||
|
propertyList.value = [];
|
||||||
|
propertySheetVisible.value = false;
|
||||||
scoreName.value = '物业公积金';
|
scoreName.value = '物业公积金';
|
||||||
await loadPropertyBinding(merchantUrl);
|
await loadPropertyBinding(merchantUrl);
|
||||||
await loadSupplier(merchantUrl);
|
await loadSupplier(merchantUrl);
|
||||||
|
|
@ -319,29 +417,46 @@ const handleMissingMerchantUrl = () => {
|
||||||
promptScan();
|
promptScan();
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPropertyBinding = async (merchantUrl: string) => {
|
const resolvePropertyListFallback = async (merchantUrl: string) => {
|
||||||
|
try {
|
||||||
|
const list = await fetchMerchantList(merchantUrl);
|
||||||
|
propertyList.value = list;
|
||||||
|
|
||||||
|
if (!list.length) {
|
||||||
|
clearPropertyBinding();
|
||||||
|
encryptedStorage.remove(syncBindingCacheForUrl(merchantUrl));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('property list fallback', error);
|
||||||
|
clearPropertyBinding();
|
||||||
|
propertyList.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPropertyBinding = async (merchantUrl: string, options: { skipFallback?: boolean } = {}) => {
|
||||||
const cacheKey = syncBindingCacheForUrl(merchantUrl);
|
const cacheKey = syncBindingCacheForUrl(merchantUrl);
|
||||||
const cached = encryptedStorage.get<PropertyBindingCache>(cacheKey);
|
const cached = encryptedStorage.get<PropertyBindingCache>(cacheKey);
|
||||||
if (cached) {
|
if (cached?.merchant_name) {
|
||||||
applyBindingInfo(cached);
|
applyBindingInfo(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await bindMerchantByUrl(merchantUrl);
|
const response = await bindMerchantByUrl(merchantUrl);
|
||||||
if (Number(response?.code) !== 200 || !response?.data) return;
|
if (Number(response?.code) !== 200 || !hasBindingPayload(response?.data)) {
|
||||||
|
if (!options.skipFallback) {
|
||||||
|
await resolvePropertyListFallback(merchantUrl);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = response.data || {};
|
const latestBinding = buildBindingCache(response.data || {});
|
||||||
const merchantData = payload.merchant || payload;
|
|
||||||
const latestBinding: PropertyBindingCache = {
|
|
||||||
merchant_id: merchantData.merchant_id,
|
|
||||||
merchant_name: merchantData.merchant_name,
|
|
||||||
score_name: merchantData.config?.score_name,
|
|
||||||
fee_rate: resolveFeeRateFromPayload(payload)
|
|
||||||
};
|
|
||||||
applyBindingInfo(latestBinding);
|
applyBindingInfo(latestBinding);
|
||||||
encryptedStorage.set(cacheKey, latestBinding, PROPERTY_CACHE_TTL_SECONDS);
|
encryptedStorage.set(cacheKey, latestBinding, PROPERTY_CACHE_TTL_SECONDS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('bind merchant fallback cache', error);
|
console.log('bind merchant fallback cache', error);
|
||||||
|
if (!options.skipFallback) {
|
||||||
|
await resolvePropertyListFallback(merchantUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -391,11 +506,129 @@ const deleteKey = () => {
|
||||||
amount.value = amount.value.slice(0, -1);
|
amount.value = amount.value.slice(0, -1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBindHouse = () => {
|
const goBindProperty = () => {
|
||||||
if (paying.value) return;
|
if (paying.value) return;
|
||||||
uni.navigateTo({
|
propertySheetVisible.value = false;
|
||||||
url: '/pages/house/bind'
|
|
||||||
|
if (activeMerchantUrl.value) {
|
||||||
|
encryptedStorage.remove(getPropertyBindingCacheKey(activeMerchantUrl.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.navigateToMiniProgram({
|
||||||
|
appId: PROPERTY_APP.APP_ID,
|
||||||
|
path: PROPERTY_APP.BIND_HOUSE_PATH,
|
||||||
|
extraData: { from: 'cashier' },
|
||||||
|
envVersion: APP_CONFIG.API_BASE_URL.includes('dev.') ? 'trial' : 'release',
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '跳转失败,请稍后重试',
|
||||||
|
icon: 'none'
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const goMine = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/mine/index'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closePropertySheet = () => {
|
||||||
|
propertySheetVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openPropertySheet = async () => {
|
||||||
|
if (!activeMerchantUrl.value) return;
|
||||||
|
|
||||||
|
const list = await fetchMerchantList(activeMerchantUrl.value);
|
||||||
|
if (!list.length) {
|
||||||
|
goBindProperty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyList.value = list;
|
||||||
|
propertySheetVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePropertyRow = async () => {
|
||||||
|
if (paying.value || switchingProperty.value) return;
|
||||||
|
if (!activeMerchantUrl.value) return;
|
||||||
|
|
||||||
|
if (!propertyName.value) {
|
||||||
|
try {
|
||||||
|
if (propertyList.value.length) {
|
||||||
|
propertySheetVisible.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await openPropertySheet();
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '获取物业列表失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await openPropertySheet();
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '获取物业列表失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchToProperty = async (
|
||||||
|
item: MerchantListItem,
|
||||||
|
merchantUrl: string,
|
||||||
|
options: { closeSheet?: boolean; skipBindingFallback?: boolean } = {}
|
||||||
|
) => {
|
||||||
|
if (paying.value || switchingProperty.value) return false;
|
||||||
|
if (isSameMerchantId(item.merchant_id, propertyMerchantId.value)) {
|
||||||
|
if (options.closeSheet) closePropertySheet();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchingProperty.value = true;
|
||||||
|
try {
|
||||||
|
const response = await changeMerchant({
|
||||||
|
merchant_id: item.merchant_id,
|
||||||
|
url: merchantUrl
|
||||||
|
});
|
||||||
|
if (Number(response?.code) !== 200) {
|
||||||
|
throw new Error(response?.msg || '切换物业失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.closeSheet) closePropertySheet();
|
||||||
|
applyPropertyInfo(item);
|
||||||
|
encryptedStorage.set(
|
||||||
|
getPropertyBindingCacheKey(merchantUrl),
|
||||||
|
{
|
||||||
|
merchant_id: item.merchant_id,
|
||||||
|
merchant_name: item.merchant_name
|
||||||
|
},
|
||||||
|
PROPERTY_CACHE_TTL_SECONDS
|
||||||
|
);
|
||||||
|
await loadPropertyBinding(merchantUrl, { skipFallback: options.skipBindingFallback ?? true });
|
||||||
|
await loadSupplier(merchantUrl);
|
||||||
|
return true;
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '切换物业失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
switchingProperty.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSwitchProperty = async (item: MerchantListItem) => {
|
||||||
|
if (!activeMerchantUrl.value) return;
|
||||||
|
await switchToProperty(item, activeMerchantUrl.value, { closeSheet: true, skipBindingFallback: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildResultUrl = (tradeNo: string) => {
|
const buildResultUrl = (tradeNo: string) => {
|
||||||
|
|
@ -481,6 +714,12 @@ onLoad((options: any) => {
|
||||||
}
|
}
|
||||||
initWithMerchantUrl(merchantUrl);
|
initWithMerchantUrl(merchantUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
if (!hasMerchantUrl.value || !activeMerchantUrl.value) return;
|
||||||
|
loadPropertyBinding(activeMerchantUrl.value);
|
||||||
|
loadSupplier(activeMerchantUrl.value);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
@ -490,6 +729,29 @@ onLoad((options: any) => {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-entry {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(env(safe-area-inset-top) + 16rpx);
|
||||||
|
left: 24rpx;
|
||||||
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
padding: 12rpx 20rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
color: #1f5bd8;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(17, 24, 39, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-entry-icon {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-page {
|
.empty-page {
|
||||||
|
|
@ -587,6 +849,26 @@ onLoad((options: any) => {
|
||||||
background: #174dc1;
|
background: #174dc1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-mine-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.96);
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-mine-link-hover {
|
||||||
|
opacity: 0.86;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-mine-link-icon {
|
||||||
|
margin-right: 8rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.brand-only {
|
.brand-only {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -727,14 +1009,6 @@ onLoad((options: any) => {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.benefit-property {
|
|
||||||
display: block;
|
|
||||||
margin-top: 8rpx;
|
|
||||||
color: #AEB4BE;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-value {
|
.benefit-value {
|
||||||
margin-left: 8rpx;
|
margin-left: 8rpx;
|
||||||
color: #4B6CB7;
|
color: #4B6CB7;
|
||||||
|
|
@ -762,32 +1036,129 @@ onLoad((options: any) => {
|
||||||
color: #C2C7D0;
|
color: #C2C7D0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bind-row {
|
.property-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
margin-top: 28rpx;
|
margin-top: 28rpx;
|
||||||
padding: 8rpx 0;
|
padding: 12rpx 4rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bind-text {
|
.property-row-hover {
|
||||||
color: #8B929E;
|
opacity: 0.88;
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bind-hint {
|
.property-name {
|
||||||
margin-left: 10rpx;
|
flex: 0 1 auto;
|
||||||
color: #C2C7D0;
|
max-width: calc(100% - 36rpx);
|
||||||
font-size: 24rpx;
|
color: #374151;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bind-arrow {
|
.property-name-action {
|
||||||
|
color: #1F5BD8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-arrow {
|
||||||
|
flex-shrink: 0;
|
||||||
margin-left: 4rpx;
|
margin-left: 4rpx;
|
||||||
color: #D1D5DB;
|
color: #C2C7D0;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.property-sheet-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
background: rgba(17, 24, 39, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 68vh;
|
||||||
|
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 28rpx 32rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-title {
|
||||||
|
color: #111827;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-close {
|
||||||
|
color: #9CA3AF;
|
||||||
|
font-size: 40rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-list {
|
||||||
|
max-height: 48vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 28rpx 32rpx;
|
||||||
|
border-top: 1rpx solid #F3F4F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-item.active {
|
||||||
|
background: #F8FAFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-item-name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #1F2937;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-item.active .property-sheet-item-name {
|
||||||
|
color: #1F5BD8;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-item-check {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #1F5BD8;
|
||||||
|
font-size: 36rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
margin: 8rpx 32rpx 0;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-top: 1rpx solid #F3F4F6;
|
||||||
|
color: #1F5BD8;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property-sheet-footer-icon {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.keyboard {
|
.keyboard {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@
|
||||||
v-if="status === 'success'"
|
v-if="status === 'success'"
|
||||||
class="action-btn primary"
|
class="action-btn primary"
|
||||||
hover-class="button-hover"
|
hover-class="button-hover"
|
||||||
@tap="goRecords"
|
@tap="goMine"
|
||||||
>
|
>
|
||||||
付款记录
|
个人中心
|
||||||
</button>
|
</button>
|
||||||
<button class="action-btn" :class="{ primary: status === 'failed' }" hover-class="button-hover-light" @tap="goCashier">
|
<button class="action-btn" :class="{ primary: status === 'failed' }" hover-class="button-hover-light" @tap="goCashier">
|
||||||
{{ status === 'failed' ? '返回收银台' : '继续付款' }}
|
{{ status === 'failed' ? '返回收银台' : '继续付款' }}
|
||||||
|
|
@ -59,7 +59,7 @@ const POLL_MAX_ATTEMPTS = 120;
|
||||||
type ResultStatus = 'pending' | 'success' | 'failed';
|
type ResultStatus = 'pending' | 'success' | 'failed';
|
||||||
|
|
||||||
const amount = ref('0.00');
|
const amount = ref('0.00');
|
||||||
const accumulation = ref('0.000');
|
const accumulation = ref('0.00');
|
||||||
const merchantName = ref('');
|
const merchantName = ref('');
|
||||||
const merchantUrl = ref('');
|
const merchantUrl = ref('');
|
||||||
const scoreName = ref('物业公积金');
|
const scoreName = ref('物业公积金');
|
||||||
|
|
@ -127,9 +127,9 @@ const pollTradeStatus = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const goRecords = () => {
|
const goMine = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/records/index'
|
url: '/pages/mine/index'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ const goCashier = () => {
|
||||||
|
|
||||||
onLoad((options: any) => {
|
onLoad((options: any) => {
|
||||||
amount.value = options?.amount || '0.00';
|
amount.value = options?.amount || '0.00';
|
||||||
accumulation.value = options?.accumulation || '0.000';
|
accumulation.value = options?.accumulation || '0.00';
|
||||||
merchantName.value = decodeOption(options?.merchant_name || options?.merchantName);
|
merchantName.value = decodeOption(options?.merchant_name || options?.merchantName);
|
||||||
merchantUrl.value = decodeOption(options?.merchant_url || options?.url);
|
merchantUrl.value = decodeOption(options?.merchant_url || options?.url);
|
||||||
scoreName.value = decodeOption(options?.score_name) || '物业公积金';
|
scoreName.value = decodeOption(options?.score_name) || '物业公积金';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
<template>
|
||||||
|
<view class="mine-page">
|
||||||
|
<view class="score-card">
|
||||||
|
<text class="score-label">平台积分</text>
|
||||||
|
<text class="score-value">¥ {{ platformScoreText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="fund-card" hover-class="fund-card-hover" @tap="goPropertyFund">
|
||||||
|
<view class="fund-card-left">
|
||||||
|
<image class="fund-icon-image" :src="fundIconUrl" mode="aspectFit" />
|
||||||
|
<view class="fund-text">
|
||||||
|
<text class="fund-title">物业基金</text>
|
||||||
|
<text class="fund-desc">查看我的物业基金余额</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="fund-action">
|
||||||
|
<text class="fund-action-text">去查看</text>
|
||||||
|
<text class="ri-arrow-right-s-line fund-action-arrow"></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="menu-card">
|
||||||
|
<view class="menu-item" hover-class="menu-item-hover" @tap="goRecords">
|
||||||
|
<text class="menu-label">付款记录</text>
|
||||||
|
<text class="ri-arrow-right-s-line menu-arrow"></text>
|
||||||
|
</view>
|
||||||
|
<view class="menu-item no-border" hover-class="menu-item-hover" @tap="goScoreRecords">
|
||||||
|
<text class="menu-label">积分流水</text>
|
||||||
|
<text class="ri-arrow-right-s-line menu-arrow"></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { onPullDownRefresh, onShow } from '@dcloudio/uni-app';
|
||||||
|
import { platformScore } from '@/api/cashier';
|
||||||
|
import { APP_CONFIG, PROPERTY_APP } from '@/config';
|
||||||
|
|
||||||
|
const platformScoreText = ref('0.00');
|
||||||
|
const loading = ref(false);
|
||||||
|
const fundIconUrl = `${APP_CONFIG.PROPERTY_IMG_URL}property/new/cashier/fangwu.png`;
|
||||||
|
|
||||||
|
const loadPlatformScore = async () => {
|
||||||
|
const response = await platformScore();
|
||||||
|
if (Number(response?.code) !== 200) {
|
||||||
|
throw new Error(response?.msg || '获取平台积分失败');
|
||||||
|
}
|
||||||
|
const score = Number(response?.data?.score || 0);
|
||||||
|
platformScoreText.value = Number.isFinite(score) ? score.toFixed(2) : '0.00';
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshPage = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
await loadPlatformScore();
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '加载失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goPropertyFund = () => {
|
||||||
|
uni.navigateToMiniProgram({
|
||||||
|
appId: PROPERTY_APP.APP_ID,
|
||||||
|
path: PROPERTY_APP.MINE_PATH,
|
||||||
|
extraData: { from: 'cashier' },
|
||||||
|
envVersion: APP_CONFIG.API_BASE_URL.includes('dev.') ? 'trial' : 'release',
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({ title: '跳转失败,请稍后重试', icon: 'none' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const goRecords = () => {
|
||||||
|
uni.navigateTo({ url: '/pages/records/index' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const goScoreRecords = () => {
|
||||||
|
uni.navigateTo({ url: '/pages/mine/score-records' });
|
||||||
|
};
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
refreshPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
onPullDownRefresh(() => {
|
||||||
|
refreshPage();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mine-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: #f4f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-card {
|
||||||
|
padding: 40rpx 32rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: linear-gradient(135deg, #1f5bd8 0%, #3d7cf5 100%);
|
||||||
|
box-shadow: 0 12rpx 32rpx rgba(31, 91, 216, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 64rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
padding: 28rpx 28rpx 28rpx 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: linear-gradient(135deg, #f8faff 0%, #eef4ff 100%);
|
||||||
|
border: 1rpx solid rgba(31, 91, 216, 0.12);
|
||||||
|
box-shadow: 0 8rpx 22rpx rgba(31, 91, 216, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-card-hover {
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-card-left {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-icon-image {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-text {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-title {
|
||||||
|
color: #111827;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-desc {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-action {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-action-text {
|
||||||
|
color: #1f5bd8;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-action-arrow {
|
||||||
|
color: #1f5bd8;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-card {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
padding: 8rpx 0;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 8rpx 22rpx rgba(17, 24, 39, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 32rpx;
|
||||||
|
border-bottom: 1rpx solid #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item.no-border {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item-hover {
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-label {
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow {
|
||||||
|
color: #c2c7d0;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
<template>
|
||||||
|
<view class="score-records-page">
|
||||||
|
<view v-if="records.length" class="records-list">
|
||||||
|
<view v-for="(item, index) in records" :key="recordKey(item, index)" class="record-card">
|
||||||
|
<view class="record-header">
|
||||||
|
<text class="record-title">{{ item.remark || formatRecordType(item) }}</text>
|
||||||
|
<text class="record-score" :class="scoreClass(item)">{{ formatScoreChange(item) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="record-meta-row">
|
||||||
|
<text class="record-meta-label">时间</text>
|
||||||
|
<text class="record-meta-value">{{ item.create_time || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="item.score !== undefined && item.score !== null && item.score !== ''" class="record-meta-row">
|
||||||
|
<text class="record-meta-label">余额</text>
|
||||||
|
<text class="record-meta-value">¥ {{ formatMoney(item.score) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="!loading" class="records-empty">
|
||||||
|
<wd-status-tip :image="emptyImage" tip="暂无积分流水" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="records.length && (loading || finished)" class="records-footer">
|
||||||
|
<wd-loadmore :state="loadMoreState" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
||||||
|
import { platformScoreRecord } from '@/api/cashier';
|
||||||
|
import type { ScoreRecordItem } from '@/api/cashier';
|
||||||
|
import { APP_CONFIG } from '@/config';
|
||||||
|
|
||||||
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
const records = ref<ScoreRecordItem[]>([]);
|
||||||
|
const page = ref(1);
|
||||||
|
const loading = ref(false);
|
||||||
|
const finished = ref(false);
|
||||||
|
|
||||||
|
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
|
||||||
|
const loadMoreState = computed(() => (loading.value ? 'loading' : 'finished'));
|
||||||
|
|
||||||
|
const formatMoney = (value?: string | number) => {
|
||||||
|
const amount = Number(value || 0);
|
||||||
|
if (!Number.isFinite(amount)) return '0.00';
|
||||||
|
return amount.toFixed(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatRecordType = (item: ScoreRecordItem) => {
|
||||||
|
const type = String(item.type ?? '');
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
'1': '积分收入',
|
||||||
|
'2': '积分支出',
|
||||||
|
'3': '积分返还'
|
||||||
|
};
|
||||||
|
return map[type] || '积分变动';
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatScoreChange = (item: ScoreRecordItem) => {
|
||||||
|
const change = item.change_score ?? item.score;
|
||||||
|
const amount = Number(change || 0);
|
||||||
|
if (!Number.isFinite(amount)) return '0.00';
|
||||||
|
const prefix = amount > 0 ? '+' : '';
|
||||||
|
return `${prefix}${amount.toFixed(2)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scoreClass = (item: ScoreRecordItem) => {
|
||||||
|
const change = Number(item.change_score ?? item.score ?? 0);
|
||||||
|
return change >= 0 ? 'score-plus' : 'score-minus';
|
||||||
|
};
|
||||||
|
|
||||||
|
const recordKey = (item: ScoreRecordItem, index: number) => String(item.id ?? `${item.create_time || 'record'}-${index}`);
|
||||||
|
|
||||||
|
const fetchRecords = async (reset = false) => {
|
||||||
|
if (loading.value) return;
|
||||||
|
if (!reset && finished.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const nextPage = reset ? 1 : page.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await platformScoreRecord({
|
||||||
|
page: nextPage,
|
||||||
|
limit: PAGE_SIZE
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Number(response?.code) !== 200) {
|
||||||
|
throw new Error(response?.msg || '获取积分流水失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Array.isArray(response?.data) ? response.data : [];
|
||||||
|
records.value = reset ? list : records.value.concat(list);
|
||||||
|
page.value = nextPage + 1;
|
||||||
|
finished.value = list.length < PAGE_SIZE;
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '获取积分流水失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
if (reset) {
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
fetchRecords(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
onPullDownRefresh(() => {
|
||||||
|
finished.value = false;
|
||||||
|
fetchRecords(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
onReachBottom(() => {
|
||||||
|
fetchRecords(false);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.score-records-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: #f4f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-card {
|
||||||
|
padding: 28rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 8rpx 22rpx rgba(17, 24, 39, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-title {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-score {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-plus {
|
||||||
|
color: #1f5bd8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-minus {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-meta-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-meta-label {
|
||||||
|
color: #8b929e;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-meta-value {
|
||||||
|
color: #374151;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-empty {
|
||||||
|
padding-top: 300rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-footer {
|
||||||
|
padding: 12rpx 0 24rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,6 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="checkout-page">
|
<view class="checkout-page">
|
||||||
<view class="checkout-sheet">
|
<view v-if="loading" class="checkout-loading">
|
||||||
|
<text class="ri-loader-4-line loading-spinner"></text>
|
||||||
|
<text class="loading-text">正在加载订单...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="loadError" class="checkout-error">
|
||||||
|
<text class="error-text">{{ loadError }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="checkout-sheet">
|
||||||
|
<view v-if="returning" class="returning-mask">
|
||||||
|
<text class="ri-loader-4-line loading-spinner"></text>
|
||||||
|
<text class="returning-text">支付成功,正在返回...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="countdown-pill">
|
<view class="countdown-pill">
|
||||||
<view class="countdown-pill-inner">
|
<view class="countdown-pill-inner">
|
||||||
<text class="countdown-label">支付剩余时间</text>
|
<text class="countdown-label">支付剩余时间</text>
|
||||||
|
|
@ -10,9 +24,9 @@
|
||||||
|
|
||||||
<view class="amount-block">
|
<view class="amount-block">
|
||||||
<text class="amount-currency">¥</text>
|
<text class="amount-currency">¥</text>
|
||||||
<text class="amount-value">{{ order.amount }}</text>
|
<text class="amount-value">{{ amountText }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="merchant-name">{{ order.merchantName }}</view>
|
<view class="merchant-name">{{ subjectText }}</view>
|
||||||
|
|
||||||
<view class="info-card">
|
<view class="info-card">
|
||||||
<view class="info-row">
|
<view class="info-row">
|
||||||
|
|
@ -22,12 +36,12 @@
|
||||||
class="info-input"
|
class="info-input"
|
||||||
placeholder="点击填写订单备注"
|
placeholder="点击填写订单备注"
|
||||||
placeholder-class="info-placeholder"
|
placeholder-class="info-placeholder"
|
||||||
:disabled="paying"
|
:disabled="paying || expired || returning"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-row">
|
<view class="info-row">
|
||||||
<text class="info-label">订单信息</text>
|
<text class="info-label">订单信息</text>
|
||||||
<text class="info-value">{{ order.orderDesc }}</text>
|
<text class="info-value">{{ bodyText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -42,17 +56,16 @@
|
||||||
</view>
|
</view>
|
||||||
<text class="ri-checkbox-circle-fill pay-method-check"></text>
|
<text class="ri-checkbox-circle-fill pay-method-check"></text>
|
||||||
</view>
|
</view>
|
||||||
<view class="pay-method-tip">支付完成后,您需手动切回原应用</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="pay-button"
|
class="pay-button"
|
||||||
hover-class="pay-button-hover"
|
hover-class="pay-button-hover"
|
||||||
:loading="paying"
|
:loading="paying"
|
||||||
:disabled="expired"
|
:disabled="expired || returning || !tradeNo"
|
||||||
@tap="handlePay"
|
@tap="handlePay"
|
||||||
>
|
>
|
||||||
确认支付 ¥{{ order.amount }}
|
确认支付 ¥{{ amountText }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<view class="provider-footer">卓享汇收银台 提供服务</view>
|
<view class="provider-footer">卓享汇收银台 提供服务</view>
|
||||||
|
|
@ -61,15 +74,30 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onUnmounted, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad, onUnload } from '@dcloudio/uni-app';
|
||||||
import { buildMockOrderFromOptions, MOCK_PAY_CHECKOUT_ORDER, type PayCheckoutOrder } from '@/mock/pay-checkout';
|
import { cancelTrade, cashTradeInfo, cashTradePay, queryTrade } from '@/api/cashier';
|
||||||
|
import { isCheckoutClosing, returnToHostMiniProgram, resetCheckoutClosing } from '@/utils/checkout-return';
|
||||||
|
import { isPaymentCancelled, requestWxPayment } from '@/utils/wxpay';
|
||||||
|
|
||||||
const order = ref<PayCheckoutOrder>({ ...MOCK_PAY_CHECKOUT_ORDER });
|
const POLL_INTERVAL_MS = 1000;
|
||||||
|
const POLL_MAX_ATTEMPTS = 120;
|
||||||
|
|
||||||
|
const tradeNo = ref('');
|
||||||
|
const subjectText = ref('');
|
||||||
|
const bodyText = ref('');
|
||||||
|
const amountText = ref('0.00');
|
||||||
const remark = ref('');
|
const remark = ref('');
|
||||||
|
const loading = ref(true);
|
||||||
|
const loadError = ref('');
|
||||||
const paying = ref(false);
|
const paying = ref(false);
|
||||||
|
const returning = ref(false);
|
||||||
const remainSeconds = ref(0);
|
const remainSeconds = ref(0);
|
||||||
|
|
||||||
let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
|
let hasPayAttempted = false;
|
||||||
|
let paymentSucceeded = false;
|
||||||
|
let timeoutHandled = false;
|
||||||
|
|
||||||
const expired = computed(() => remainSeconds.value <= 0);
|
const expired = computed(() => remainSeconds.value <= 0);
|
||||||
const countdownText = computed(() => {
|
const countdownText = computed(() => {
|
||||||
|
|
@ -85,6 +113,23 @@ const clearCountdown = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseDateTime = (value?: string) => {
|
||||||
|
if (!value) return NaN;
|
||||||
|
return new Date(value.replace(/-/g, '/')).getTime();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveExpireAt = (trade: Record<string, any> = {}) => {
|
||||||
|
const timeoutSeconds = Number(trade.timeout || 0);
|
||||||
|
const createAt = parseDateTime(trade.create_time);
|
||||||
|
if (Number.isFinite(createAt) && timeoutSeconds > 0) {
|
||||||
|
return createAt + timeoutSeconds * 1000;
|
||||||
|
}
|
||||||
|
if (timeoutSeconds > 0) {
|
||||||
|
return Date.now() + timeoutSeconds * 1000;
|
||||||
|
}
|
||||||
|
return Date.now() + 5 * 60 * 1000;
|
||||||
|
};
|
||||||
|
|
||||||
const startCountdown = (expireAt: number) => {
|
const startCountdown = (expireAt: number) => {
|
||||||
clearCountdown();
|
clearCountdown();
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
|
|
@ -92,32 +137,212 @@ const startCountdown = (expireAt: number) => {
|
||||||
remainSeconds.value = next;
|
remainSeconds.value = next;
|
||||||
if (next <= 0) {
|
if (next <= 0) {
|
||||||
clearCountdown();
|
clearCountdown();
|
||||||
|
handleTimeout();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tick();
|
tick();
|
||||||
countdownTimer = setInterval(tick, 1000);
|
countdownTimer = setInterval(tick, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePay = () => {
|
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||||
if (paying.value || expired.value) return;
|
|
||||||
|
const isTradeNotPayable = (message = '') => {
|
||||||
|
const text = String(message);
|
||||||
|
return text.includes('订单状态') || text.includes('不允许付款') || text.includes('已取消') || text.includes('已关闭');
|
||||||
|
};
|
||||||
|
|
||||||
|
const finishCancelledAndReturn = async () => {
|
||||||
|
if (isCheckoutClosing() || !tradeNo.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cancelTrade(tradeNo.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('cancel trade failed', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await backToHost('cancelled', 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pollTradePaid = async () => {
|
||||||
|
for (let attempt = 0; attempt < POLL_MAX_ATTEMPTS; attempt++) {
|
||||||
|
try {
|
||||||
|
const response = await queryTrade(tradeNo.value);
|
||||||
|
if (Number(response?.code) === 200 && Number(response?.data?.pay_status) === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('trade query failed', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt < POLL_MAX_ATTEMPTS - 1) {
|
||||||
|
await sleep(POLL_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const backToHost = async (status: 'success' | 'cancelled' | 'timeout', payStatus?: number) => {
|
||||||
|
if (isCheckoutClosing() || !tradeNo.value) return false;
|
||||||
|
|
||||||
|
const returned = await returnToHostMiniProgram({
|
||||||
|
status,
|
||||||
|
trade_no: tradeNo.value,
|
||||||
|
pay_status: payStatus
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!returned) {
|
||||||
|
resetCheckoutClosing();
|
||||||
|
}
|
||||||
|
|
||||||
|
return returned;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTimeout = async () => {
|
||||||
|
if (timeoutHandled || paymentSucceeded || isCheckoutClosing()) return;
|
||||||
|
timeoutHandled = true;
|
||||||
|
clearCountdown();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await cancelTrade(tradeNo.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('timeout cancel failed', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await backToHost('timeout', 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadOrderInfo = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
loadError.value = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await cashTradeInfo(tradeNo.value);
|
||||||
|
if (Number(response?.code) !== 200 || !response?.data) {
|
||||||
|
throw new Error(response?.msg || '获取订单信息失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = response.data || {};
|
||||||
|
const trade = payload.trade || {};
|
||||||
|
if (Number(trade.pay_status) === 1) {
|
||||||
|
paymentSucceeded = true;
|
||||||
|
await backToHost('success', 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subjectText.value = payload.subject || '';
|
||||||
|
bodyText.value = payload.body || '';
|
||||||
|
amountText.value = Number(trade.real_money || 0).toFixed(2);
|
||||||
|
startCountdown(resolveExpireAt(trade));
|
||||||
|
} catch (error: any) {
|
||||||
|
loadError.value = error?.message || '获取订单信息失败';
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePay = async () => {
|
||||||
|
if (paying.value || expired.value || returning.value || !tradeNo.value) return;
|
||||||
|
|
||||||
paying.value = true;
|
paying.value = true;
|
||||||
setTimeout(() => {
|
hasPayAttempted = true;
|
||||||
paying.value = false;
|
|
||||||
|
try {
|
||||||
|
const response = await cashTradePay({
|
||||||
|
trade_no: tradeNo.value,
|
||||||
|
platform: 'mp-weixin',
|
||||||
|
remark: remark.value.trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Number(response?.code) !== 200 || !response?.data) {
|
||||||
|
const msg = response?.msg || '获取支付参数失败';
|
||||||
|
if (isTradeNotPayable(msg)) {
|
||||||
|
await finishCancelledAndReturn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = response.data || {};
|
||||||
|
const params = {
|
||||||
|
timeStamp: String(payload.timestamp || payload.timeStamp || ''),
|
||||||
|
nonceStr: payload.nonce_str || payload.nonceStr || '',
|
||||||
|
package: payload.package || '',
|
||||||
|
signType: payload.sign_type || payload.signType || 'RSA',
|
||||||
|
paySign: payload.pay_sign || payload.paySign || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!params.paySign) {
|
||||||
|
throw new Error('支付参数不完整');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await requestWxPayment(params);
|
||||||
|
} catch (error) {
|
||||||
|
if (isPaymentCancelled(error)) {
|
||||||
|
await finishCancelledAndReturn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
returning.value = true;
|
||||||
|
const paid = await pollTradePaid();
|
||||||
|
if (paid) {
|
||||||
|
paymentSucceeded = true;
|
||||||
|
await backToHost('success', 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await backToHost('success', 0);
|
||||||
|
} catch (error: any) {
|
||||||
|
const message = error?.message || '支付失败';
|
||||||
|
if (isTradeNotPayable(message)) {
|
||||||
|
await finishCancelledAndReturn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '支付接口待对接',
|
title: message,
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}, 500);
|
} finally {
|
||||||
|
paying.value = false;
|
||||||
|
returning.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodeQuery = (value?: string) => {
|
||||||
|
if (!value) return '';
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(value);
|
||||||
|
} catch (error) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoad((options: any) => {
|
onLoad((options: any) => {
|
||||||
order.value = buildMockOrderFromOptions(options || {});
|
const nextTradeNo = decodeQuery(options?.trade_no || options?.tradeNo).trim();
|
||||||
remark.value = order.value.remark;
|
if (!nextTradeNo) {
|
||||||
startCountdown(order.value.expireAt);
|
loadError.value = '缺少交易单号';
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tradeNo.value = nextTradeNo;
|
||||||
|
loadOrderInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnload(() => {
|
||||||
clearCountdown();
|
clearCountdown();
|
||||||
|
|
||||||
|
if (isCheckoutClosing() || paymentSucceeded || !tradeNo.value) return;
|
||||||
|
|
||||||
|
const currentTradeNo = tradeNo.value;
|
||||||
|
cancelTrade(currentTradeNo).catch(() => {});
|
||||||
|
returnToHostMiniProgram({
|
||||||
|
status: 'cancelled',
|
||||||
|
trade_no: currentTradeNo,
|
||||||
|
pay_status: 0
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -129,10 +354,59 @@ onUnmounted(() => {
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkout-loading,
|
||||||
|
.checkout-error {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
color: #1f5bd8;
|
||||||
|
font-size: 56rpx;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text,
|
||||||
|
.error-text,
|
||||||
|
.returning-text {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
.checkout-sheet {
|
.checkout-sheet {
|
||||||
|
position: relative;
|
||||||
padding: 16rpx 32rpx 0;
|
padding: 16rpx 32rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.returning-mask {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.countdown-pill {
|
.countdown-pill {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -205,7 +479,7 @@ onUnmounted(() => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 24rpx;
|
gap: 24rpx;
|
||||||
padding: 28rpx 28rpx;
|
padding: 28rpx;
|
||||||
border-bottom: 1rpx solid #f0f2f5;
|
border-bottom: 1rpx solid #f0f2f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,7 +506,7 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.pay-method-card {
|
.pay-method-card {
|
||||||
padding: 28rpx 28rpx 24rpx;
|
padding: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pay-method-title {
|
.pay-method-title {
|
||||||
|
|
@ -278,13 +552,6 @@ onUnmounted(() => {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pay-method-tip {
|
|
||||||
margin-top: 20rpx;
|
|
||||||
color: #9ca3af;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pay-button {
|
.pay-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,265 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="records-page">
|
<view class="records-page">
|
||||||
<view class="records-empty">
|
<view v-if="records.length" class="records-list">
|
||||||
<wd-status-tip :image="uni.$globalData?.RESOURCE_URL + 'tip/message.png'" tip="暂无付款记录" />
|
<view v-for="item in records" :key="item.trade_no" class="record-card">
|
||||||
|
<view class="record-header">
|
||||||
|
<text class="record-merchant li-single-line">{{ item.supplier_name || '收款商家' }}</text>
|
||||||
|
<text class="record-status" :class="statusClass(item.pay_status)">{{ formatPayStatus(item.pay_status) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="record-amount-row">
|
||||||
|
<text class="record-amount-label">实付金额</text>
|
||||||
|
<text class="record-amount">¥ {{ formatMoney(item.real_money) }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="showWuyeMoney(item)" class="record-meta-row">
|
||||||
|
<text class="record-meta-label">物业返还</text>
|
||||||
|
<text class="record-meta-value">¥ {{ formatMoney(item.wuye_money) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="record-meta-row">
|
||||||
|
<text class="record-meta-label">支付方式</text>
|
||||||
|
<text class="record-meta-value">{{ formatPayType(item.pay_type) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="record-meta-row">
|
||||||
|
<text class="record-meta-label">支付时间</text>
|
||||||
|
<text class="record-meta-value">{{ item.pay_time || item.create_time || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="record-footer">
|
||||||
|
<text class="record-trade-no">单号 {{ item.trade_no }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="!loading" class="records-empty">
|
||||||
|
<wd-status-tip :image="emptyImage" tip="暂无付款记录" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="records.length && (loading || finished)" class="records-footer">
|
||||||
|
<wd-loadmore :state="loadMoreState" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
||||||
|
import { tradeList } from '@/api/cashier';
|
||||||
|
import type { TradeListItem } from '@/api/cashier';
|
||||||
|
import { APP_CONFIG } from '@/config';
|
||||||
|
|
||||||
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
const records = ref<TradeListItem[]>([]);
|
||||||
|
const page = ref(1);
|
||||||
|
const loading = ref(false);
|
||||||
|
const finished = ref(false);
|
||||||
|
|
||||||
|
const emptyImage = `${APP_CONFIG.RESOURCE_URL}tip/message.png`;
|
||||||
|
|
||||||
|
const loadMoreState = computed(() => (loading.value ? 'loading' : 'finished'));
|
||||||
|
|
||||||
|
const formatMoney = (value?: string | number) => {
|
||||||
|
const amount = Number(value || 0);
|
||||||
|
if (!Number.isFinite(amount)) return '0.00';
|
||||||
|
return amount.toFixed(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPayStatus = (status?: number) => {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
0: '未支付',
|
||||||
|
1: '已支付',
|
||||||
|
2: '部分退款',
|
||||||
|
3: '全额退款',
|
||||||
|
4: '已取消'
|
||||||
|
};
|
||||||
|
return map[Number(status)] || '未知状态';
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusClass = (status?: number) => {
|
||||||
|
const value = Number(status);
|
||||||
|
if (value === 1) return 'status-success';
|
||||||
|
if (value === 0) return 'status-pending';
|
||||||
|
if (value === 4) return 'status-cancel';
|
||||||
|
return 'status-refund';
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPayType = (payType?: number) => {
|
||||||
|
const map: Record<number, string> = {
|
||||||
|
1: '微信',
|
||||||
|
2: '支付宝',
|
||||||
|
3: '云闪付',
|
||||||
|
4: '现金',
|
||||||
|
5: '余额',
|
||||||
|
6: '线下'
|
||||||
|
};
|
||||||
|
return map[Number(payType)] || '-';
|
||||||
|
};
|
||||||
|
|
||||||
|
const showWuyeMoney = (item: TradeListItem) => Number(item.wuye_money || 0) > 0;
|
||||||
|
|
||||||
|
const fetchRecords = async (reset = false) => {
|
||||||
|
if (loading.value) return;
|
||||||
|
if (!reset && finished.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const nextPage = reset ? 1 : page.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await tradeList({
|
||||||
|
trade_no: '',
|
||||||
|
page: nextPage,
|
||||||
|
limit: PAGE_SIZE
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Number(response?.code) !== 200) {
|
||||||
|
throw new Error(response?.msg || '获取付款记录失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Array.isArray(response?.data) ? response.data : [];
|
||||||
|
records.value = reset ? list : records.value.concat(list);
|
||||||
|
page.value = nextPage + 1;
|
||||||
|
finished.value = list.length < PAGE_SIZE;
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.message || '获取付款记录失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
if (reset) {
|
||||||
|
uni.stopPullDownRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
fetchRecords(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
onPullDownRefresh(() => {
|
||||||
|
finished.value = false;
|
||||||
|
fetchRecords(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
onReachBottom(() => {
|
||||||
|
fetchRecords(false);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.records-page {
|
.records-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #F4F6F8;
|
padding: 24rpx;
|
||||||
|
background: #f4f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-card {
|
||||||
|
padding: 28rpx 28rpx 24rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 8rpx 22rpx rgba(17, 24, 39, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-merchant {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-status {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 6rpx 16rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
background: #e8f2ff;
|
||||||
|
color: #1f5bd8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pending {
|
||||||
|
background: #fff7e8;
|
||||||
|
color: #d48806;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-cancel {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-refund {
|
||||||
|
background: #fff1f0;
|
||||||
|
color: #cf1322;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-amount-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-amount-label {
|
||||||
|
color: #8b929e;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-amount {
|
||||||
|
color: #111827;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-meta-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-meta-label {
|
||||||
|
color: #8b929e;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-meta-value {
|
||||||
|
max-width: 420rpx;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 26rpx;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-footer {
|
||||||
|
margin-top: 22rpx;
|
||||||
|
padding-top: 20rpx;
|
||||||
|
border-top: 1rpx solid #eef1f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-trade-no {
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 22rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.records-empty {
|
.records-empty {
|
||||||
padding-top: 300rpx;
|
padding-top: 300rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.records-footer {
|
||||||
|
padding: 12rpx 0 24rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,5 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
export type CheckoutReturnStatus = 'success' | 'cancelled' | 'timeout';
|
||||||
|
|
||||||
|
export interface CheckoutReturnExtraData {
|
||||||
|
status: CheckoutReturnStatus;
|
||||||
|
trade_no: string;
|
||||||
|
pay_status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let closing = false;
|
||||||
|
|
||||||
|
export const isCheckoutClosing = () => closing;
|
||||||
|
|
||||||
|
export const returnToHostMiniProgram = (extraData: CheckoutReturnExtraData) => {
|
||||||
|
if (closing) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
closing = true;
|
||||||
|
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.navigateBackMiniProgram({
|
||||||
|
extraData,
|
||||||
|
success: () => resolve(true),
|
||||||
|
fail: () => {
|
||||||
|
closing = false;
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
closing = false;
|
||||||
|
resolve(false);
|
||||||
|
// #endif
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetCheckoutClosing = () => {
|
||||||
|
closing = false;
|
||||||
|
};
|
||||||
|
|
@ -72,3 +72,5 @@ export const syncActiveMerchantContext = (merchantUrl: string) => {
|
||||||
switched: Boolean(previousHash && previousHash !== currentHash)
|
switched: Boolean(previousHash && previousHash !== currentHash)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getActiveMerchantUrl = () => encryptedStorage.get<string>(ACTIVE_QR_URL_CACHE_KEY) || '';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue