feat:饭卡充值
This commit is contained in:
1 parent
f30da78c80
commit
0b8dd47764
16 files changed
+1678
-958
No files matched your search
+8
-1
@@ -86,6 +86,13 @@
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/relation/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "图谱列表",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/map-address/search",
|
||||
"style": {
|
||||
@@ -640,4 +647,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,23 @@
|
||||
<template>
|
||||
<crud-layout title="电子钱包" mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<z-paging
|
||||
ref="pageRef"
|
||||
height="100%"
|
||||
:fixed="false"
|
||||
v-model="pageHook.list"
|
||||
:default-page-size="10"
|
||||
safe-area-inset-bottom
|
||||
:loading-more-enabled="true"
|
||||
:show-scrollbar="false"
|
||||
@query="query"
|
||||
:auto-clean-list-when-reload="false"
|
||||
:auto-scroll-to-top-when-reload="false"
|
||||
@scroll="pageHook.scroll">
|
||||
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
|
||||
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
|
||||
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
|
||||
<template #top>
|
||||
<view class="adm-search mx-30rpx mt-16rpx">
|
||||
<admin-user-picker v-model="state.params.userId" @change="reloadList" />
|
||||
<tm-input
|
||||
class="mt-16rpx"
|
||||
v-model="state.params.cardNumber"
|
||||
type="text"
|
||||
height="72"
|
||||
minHeight="72"
|
||||
fontSize="28"
|
||||
:showClear="true"
|
||||
placeholder="饭卡卡号"
|
||||
style="border: 4rpx solid #0779fd; border-radius: 14rpx"
|
||||
:focus-highlight="false"
|
||||
color="#ECF4F9"
|
||||
@confirm="reloadList"
|
||||
@clear="reloadList" />
|
||||
<tm-input class="mt-16rpx" v-model="state.params.cardNumber" type="text" height="72" minHeight="72"
|
||||
fontSize="28" :showClear="true" placeholder="饭卡卡号"
|
||||
style="border: 4rpx solid #0779fd; border-radius: 14rpx" :focus-highlight="false"
|
||||
color="#ECF4F9" @confirm="reloadList" @clear="reloadList" />
|
||||
<view class="mt-16rpx flex items-center gap-12rpx">
|
||||
<view class="flex-1 min-w-0" @tap="openWalletTypeDrawer">
|
||||
<tm-input
|
||||
:modelValue="walletTypeLabel"
|
||||
placeholder="饭卡类型"
|
||||
readonly
|
||||
height="72"
|
||||
minHeight="72"
|
||||
fontSize="28"
|
||||
<tm-input :modelValue="walletTypeLabel" placeholder="饭卡类型" readonly height="72"
|
||||
minHeight="72" fontSize="28"
|
||||
style="border: 4rpx solid #0779fd; border-radius: 14rpx; pointer-events: none"
|
||||
color="#ECF4F9" />
|
||||
</view>
|
||||
<text
|
||||
v-if="state.params.walletTypeId"
|
||||
class="second-text-26 color-#999 shrink-0 px-8rpx"
|
||||
<text v-if="state.params.walletTypeId" class="second-text-26 color-#999 shrink-0 px-8rpx"
|
||||
@tap="clearWalletType">
|
||||
清空
|
||||
</text>
|
||||
@@ -58,8 +31,7 @@
|
||||
|
||||
<view v-for="row in pageHook.list" :key="row.id" class="adm-card mx-30rpx mb-16rpx">
|
||||
<view class="flex items-center gap-20rpx">
|
||||
<image
|
||||
class="adm-avatar"
|
||||
<image class="adm-avatar"
|
||||
:src="row.userAvatar ? filePath(row.userAvatar) : '/static/icon/default-avatar.png'"
|
||||
mode="aspectFill" />
|
||||
<view class="flex-1 min-w-0">
|
||||
@@ -80,7 +52,8 @@
|
||||
<text class="label">饭卡类型</text>
|
||||
<view class="flex items-center gap-8rpx flex-wrap">
|
||||
<text class="value">{{ row.walletTypeName || "-" }}</text>
|
||||
<text v-if="row.walletTypeUniqueCode" class="adm-type-tag">{{ row.walletTypeUniqueCode }}</text>
|
||||
<text v-if="row.walletTypeUniqueCode" class="adm-type-tag">{{ row.walletTypeUniqueCode
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="adm-wallet-meta__item">
|
||||
@@ -98,10 +71,7 @@
|
||||
</view>
|
||||
<view v-if="canUpdateBalance" class="adm-card__divider" />
|
||||
<view v-if="canUpdateBalance" class="flex justify-end">
|
||||
<tm-button
|
||||
class="px-24rpx"
|
||||
size="s"
|
||||
:linear="['to right', '#00B960', '#00552C99']"
|
||||
<tm-button class="px-24rpx" size="s" :linear="['to right', '#00B960', '#00552C99']"
|
||||
@click.stop="openBalance(row)">
|
||||
充值/扣款
|
||||
</tm-button>
|
||||
@@ -109,30 +79,14 @@
|
||||
</view>
|
||||
</z-paging>
|
||||
|
||||
<tm-drawer
|
||||
v-model:show="state.walletTypeShow"
|
||||
position="bottom"
|
||||
size="58%"
|
||||
title="饭卡类型"
|
||||
:show-close="true"
|
||||
:show-footer="false"
|
||||
bg-color="#fff">
|
||||
<tm-drawer v-model:show="state.walletTypeShow" position="bottom" size="58%" title="饭卡类型" :show-close="true"
|
||||
:show-footer="false" bg-color="#fff">
|
||||
<view class="wallet-type-body">
|
||||
<tm-input
|
||||
v-model="state.walletTypeKeyword"
|
||||
placeholder="搜索饭卡类型"
|
||||
:showClear="true"
|
||||
height="72"
|
||||
minHeight="72"
|
||||
fontSize="28"
|
||||
color="#f3f4f6"
|
||||
@input="onWalletTypeKeywordInput"
|
||||
<tm-input v-model="state.walletTypeKeyword" placeholder="搜索饭卡类型" :showClear="true" height="72"
|
||||
minHeight="72" fontSize="28" color="#f3f4f6" @input="onWalletTypeKeywordInput"
|
||||
@clear="loadWalletTypes" />
|
||||
<scroll-view scroll-y class="wallet-type-list mt-16rpx">
|
||||
<view
|
||||
v-for="item in filteredWalletTypes"
|
||||
:key="item.id"
|
||||
class="wallet-type-item"
|
||||
<view v-for="item in filteredWalletTypes" :key="item.id" class="wallet-type-item"
|
||||
:class="{ 'wallet-type-item--active': String(item.id) === String(state.params.walletTypeId) }"
|
||||
@tap="pickWalletType(item)">
|
||||
<text class="sub-text-30 color-#0779fd">{{ item.name || "-" }}</text>
|
||||
@@ -145,20 +99,9 @@
|
||||
</view>
|
||||
</tm-drawer>
|
||||
|
||||
<tm-drawer
|
||||
:show="state.balanceShow"
|
||||
position="bottom"
|
||||
size="62%"
|
||||
:show-title="false"
|
||||
:show-close="false"
|
||||
:show-footer="true"
|
||||
:show-cancel="true"
|
||||
cancel-text="取消"
|
||||
confirm-text="确认"
|
||||
@confirm="submitBalance"
|
||||
@cancel="closeBalance"
|
||||
@close="closeBalance"
|
||||
@update:show="(v: boolean) => (state.balanceShow = v)">
|
||||
<tm-drawer :show="state.balanceShow" position="bottom" size="62%" :show-title="false" :show-close="false"
|
||||
:show-footer="true" :show-cancel="true" cancel-text="取消" confirm-text="确认" @confirm="submitBalance"
|
||||
@cancel="closeBalance" @close="closeBalance" @update:show="(v: boolean) => (state.balanceShow = v)">
|
||||
<view class="adm-drawer">
|
||||
<view class="adm-drawer-accent adm-drawer-accent--green" />
|
||||
<view class="adm-drawer-head">
|
||||
@@ -182,15 +125,11 @@
|
||||
<view class="adm-field">
|
||||
<text class="adm-field__label">变动类型</text>
|
||||
<view class="flex gap-16rpx">
|
||||
<view
|
||||
class="adm-chip"
|
||||
:class="{ 'adm-chip--active': state.balanceForm.changeType === 1 }"
|
||||
<view class="adm-chip" :class="{ 'adm-chip--active': state.balanceForm.changeType === 1 }"
|
||||
@tap="state.balanceForm.changeType = 1">
|
||||
充值
|
||||
</view>
|
||||
<view
|
||||
class="adm-chip"
|
||||
:class="{ 'adm-chip--active': state.balanceForm.changeType === -1 }"
|
||||
<view class="adm-chip" :class="{ 'adm-chip--active': state.balanceForm.changeType === -1 }"
|
||||
@tap="state.balanceForm.changeType = -1">
|
||||
扣款
|
||||
</view>
|
||||
|
||||
+440
-493
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<tm-drawer
|
||||
v-model:show="show"
|
||||
position="right"
|
||||
size="88%"
|
||||
title="被监护人操作"
|
||||
:show-close="true"
|
||||
:show-footer="false"
|
||||
bg-color="#f7faff"
|
||||
:content-margin="0"
|
||||
>
|
||||
<view v-if="ward" class="drawer-content">
|
||||
<view class="drawer-hero relative overflow-hidden bg-white px-20rpx pb-15rpx pt-6rpx">
|
||||
<image
|
||||
class="drawer-hero__image"
|
||||
src="/static/illustration/guardian-action-hero.png"
|
||||
mode="aspectFit"
|
||||
aria-label="被监护人人脸、饭卡与预约服务插画"
|
||||
/>
|
||||
<view class="relative z-2 flex items-center">
|
||||
<view class="h-74rpx w-74rpx shrink-0 overflow-hidden rounded-20rpx border-4rpx border-white border-solid bg-#eef8ff">
|
||||
<image class="h-full w-full" :src="avatarUrl" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="ml-14rpx min-w-0 flex-1 pr-148rpx">
|
||||
<view class="main-text-28 show-line">{{ ward.wardName || "被监护人" }}</view>
|
||||
<view class="mt-4rpx flex flex-wrap items-center gap-6rpx">
|
||||
<view class="rounded-full bg-#eaf5ff px-10rpx py-4rpx text-19rpx color-#007ad6 font-700">
|
||||
{{ ward.walletTypeName || "饭卡" }}
|
||||
</view>
|
||||
<view class="rounded-full px-10rpx py-4rpx text-19rpx font-700" :class="statusClass">
|
||||
{{ ward.cardStatusName || "状态异常" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="relative z-2 mt-13rpx grid grid-cols-2 gap-9rpx">
|
||||
<view class="rounded-16rpx border-1rpx border-#e8f0f8 border-solid bg-white px-14rpx py-11rpx">
|
||||
<view class="second-text-18">饭卡余额</view>
|
||||
<view class="mt-2rpx text-29rpx color-#1f2937 font-800">¥{{ hooks.useMoney(ward.balance || 0) }}</view>
|
||||
</view>
|
||||
<view class="rounded-16rpx border-1rpx border-#e8f0f8 border-solid bg-white px-14rpx py-11rpx">
|
||||
<view class="second-text-18">脱敏卡号</view>
|
||||
<view class="mt-3rpx text-24rpx color-#1f2937 font-700">{{ ward.maskedCardNumber || "--" }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="relative z-2 mt-9rpx grid grid-cols-3 gap-7rpx">
|
||||
<view class="col-span-2 rounded-16rpx border-1rpx border-#dcecf9 border-solid bg-#f7fbff px-12rpx py-9rpx">
|
||||
<view class="flex items-center">
|
||||
<view class="h-30rpx w-30rpx flex-row-center rounded-full bg-#e3f3ff">
|
||||
<tm-icon name="emotion-happy-line" :size="17" color="#007ad6" />
|
||||
</view>
|
||||
<view class="ml-6rpx text-19rpx color-#334155 font-700">人脸情况</view>
|
||||
</view>
|
||||
<view class="mt-6rpx grid grid-cols-2">
|
||||
<view
|
||||
v-for="metric in faceMetrics"
|
||||
:key="metric.key"
|
||||
class="flex min-w-0 items-baseline first:border-r-1rpx first:border-#dcecf9 first:border-r-solid last:pl-10rpx"
|
||||
>
|
||||
<view class="show-line text-18rpx color-#64748b">{{ metric.label }}</view>
|
||||
<view class="ml-6rpx shrink-0 text-23rpx font-800" :style="{ color: metric.color }">
|
||||
{{ countText(metric.value) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-16rpx border-1rpx border-#fed7aa border-solid bg-#fffaf3 px-11rpx py-9rpx">
|
||||
<view class="flex items-center">
|
||||
<view class="h-30rpx w-30rpx flex-row-center rounded-full bg-#ffedd5">
|
||||
<tm-icon name="bowl-line" :size="17" color="#c2410c" />
|
||||
</view>
|
||||
<view class="show-line ml-5rpx text-18rpx color-#7c4a19 font-700">预约订单</view>
|
||||
</view>
|
||||
<view class="mt-6rpx flex items-baseline">
|
||||
<view class="text-18rpx color-#9a5b22">待核销</view>
|
||||
<view class="ml-6rpx text-23rpx color-#c2410c font-800">{{ countText(ward.pendingReservationCount) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!ward.rechargeEnabled" class="relative z-2 mt-10rpx flex items-start rounded-15rpx bg-#fff7ed px-13rpx py-9rpx">
|
||||
<tm-icon name="error-warning-line" :size="22" color="#c2410c" />
|
||||
<view class="ml-7rpx flex-1 text-19rpx color-#9a3412 leading-28rpx">
|
||||
{{ ward.disabledReason || "当前饭卡暂不可充值" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-20rpx pt-14rpx">
|
||||
<view class="flex items-end justify-between">
|
||||
<view>
|
||||
<view class="main-text-26">业务办理</view>
|
||||
<view class="second-text-18 mt-2rpx">选择一项服务,返回后列表自动刷新</view>
|
||||
</view>
|
||||
<view class="rounded-full bg-#eaf5ff px-10rpx py-4rpx text-18rpx color-#007ad6 font-700">3 项</view>
|
||||
</view>
|
||||
<view class="mt-10rpx flex-col gap-8rpx">
|
||||
<view
|
||||
v-for="action in actions"
|
||||
:key="action.key"
|
||||
class="min-h-88rpx flex items-center rounded-18rpx border-1rpx border-solid px-16rpx py-12rpx"
|
||||
:class="action.disabled ? 'border-#e5e7eb bg-#f3f4f6 opacity-55' : 'active:opacity-80'"
|
||||
:style="action.disabled ? undefined : { backgroundColor: action.background, borderColor: action.borderColor }"
|
||||
@tap="handleAction(action)"
|
||||
>
|
||||
<view class="h-54rpx w-54rpx flex-row-center shrink-0 rounded-16rpx" :style="{ backgroundColor: action.iconBg }">
|
||||
<tm-icon :name="action.icon" :size="27" :color="action.disabled ? '#9ca3af' : action.iconColor" />
|
||||
</view>
|
||||
<view class="ml-14rpx min-w-0 flex-1">
|
||||
<view class="sub-text-25 font-700" :class="action.disabled ? 'color-#9ca3af!' : ''">{{ action.title }}</view>
|
||||
<view class="second-text-19 mt-2rpx show-line">{{ action.description }}</view>
|
||||
</view>
|
||||
<tm-icon name="arrow-right-s-line" :size="25" :color="action.disabled ? '#cbd5e1' : '#0094ff'" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="drawer-content flex-col-center px-30rpx text-center">
|
||||
<tm-icon name="user-line" :size="52" color="#94a3b8" />
|
||||
<view class="second-text-24 mt-14rpx">未选择被监护人</view>
|
||||
</view>
|
||||
</tm-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
|
||||
import { hooks, router } from "@@/index"
|
||||
import { filePath } from "@@/utils/file"
|
||||
import type { GuardianWardRechargeItem } from "@@/api/wallet"
|
||||
|
||||
type WardAction = {
|
||||
key: "face-upload" | "face-records" | "recharge"
|
||||
title: string
|
||||
description: string
|
||||
icon: string
|
||||
iconBg: string
|
||||
iconColor: string
|
||||
background: string
|
||||
borderColor: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
ward?: GuardianWardRechargeItem
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [boolean]
|
||||
}>()
|
||||
|
||||
const show = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value: boolean) => emit("update:modelValue", value),
|
||||
})
|
||||
|
||||
const avatarUrl = computed(() => filePath(props.ward?.wardAvatar) || "/static/icon/default-avatar.png")
|
||||
const statusClass = computed(() => (props.ward?.rechargeEnabled ? "bg-#ecfdf5 color-#047857" : "bg-#fff7ed color-#c2410c"))
|
||||
const countText = (value?: number) => (typeof value === "number" ? String(value) : "--")
|
||||
const faceCapacityReached = computed(() => {
|
||||
const available = props.ward?.availableFaceCount
|
||||
const pending = props.ward?.pendingFaceCount
|
||||
return typeof available === "number" && typeof pending === "number" && available + pending >= 3
|
||||
})
|
||||
|
||||
/**
|
||||
* 人脸状态放在统一业务分组中,避免“审核中”被误解为饭卡或预约订单的审核状态。
|
||||
*/
|
||||
const faceMetrics = computed(() => [
|
||||
{
|
||||
key: "available-face",
|
||||
label: "可用",
|
||||
value: props.ward?.availableFaceCount,
|
||||
color: "#007ad6",
|
||||
},
|
||||
{
|
||||
key: "pending-face",
|
||||
label: "审核中",
|
||||
value: props.ward?.pendingFaceCount,
|
||||
color: "#7c3aed",
|
||||
},
|
||||
])
|
||||
|
||||
/**
|
||||
* 操作项通过配置统一渲染,避免三类业务入口复制结构和交互状态。
|
||||
*/
|
||||
const actions = computed<WardAction[]>(() => [
|
||||
{
|
||||
key: "face-upload",
|
||||
title: "上传人脸",
|
||||
description: faceCapacityReached.value ? "审核中和可用人脸合计已达 3 张" : "拍摄并提交被监护人的人脸照片审核",
|
||||
icon: "camera-line",
|
||||
iconBg: "#eef8ff",
|
||||
iconColor: "#0094ff",
|
||||
background: "#f8fcff",
|
||||
borderColor: "#dcefff",
|
||||
disabled: faceCapacityReached.value,
|
||||
},
|
||||
{
|
||||
key: "face-records",
|
||||
title: "审核记录",
|
||||
description: "查看人脸审核进度、照片和拒绝原因",
|
||||
icon: "file-list-3-line",
|
||||
iconBg: "#f3f0ff",
|
||||
iconColor: "#7c3aed",
|
||||
background: "#fbfaff",
|
||||
borderColor: "#e9e2ff",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
key: "recharge",
|
||||
title: "饭卡充值",
|
||||
description: props.ward?.rechargeEnabled ? "使用目标食堂支持的支付方式为饭卡充值" : props.ward?.disabledReason || "当前饭卡暂不可充值",
|
||||
icon: "wallet-line",
|
||||
iconBg: "#ecfdf5",
|
||||
iconColor: "#059669",
|
||||
background: "#f5fdf9",
|
||||
borderColor: "#ccefe0",
|
||||
disabled: !props.ward?.rechargeEnabled,
|
||||
},
|
||||
])
|
||||
|
||||
/**
|
||||
* 所有业务跳转先关闭右侧抽屉,避免 App 页面切换时残留遮罩。
|
||||
*/
|
||||
const handleAction = (action: WardAction) => {
|
||||
if (!props.ward || action.disabled) return
|
||||
const wardUserId = String(props.ward.wardUserId || "")
|
||||
const wardName = encodeURIComponent(props.ward.wardName || "被监护人")
|
||||
show.value = false
|
||||
setTimeout(() => {
|
||||
if (action.key === "face-upload") {
|
||||
router.jump("/pages/human-face/guardian-ward-apply", { wardUserId, wardName })
|
||||
return
|
||||
}
|
||||
if (action.key === "face-records") {
|
||||
router.jump("/pages/human-face/guardian-ward-records", { wardUserId, wardName })
|
||||
return
|
||||
}
|
||||
router.jump("/pages/wallet/recharge", { wardUserId })
|
||||
}, 180)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.drawer-content {
|
||||
min-height: 100%;
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.drawer-hero {
|
||||
background: linear-gradient(118deg, #ffffff 0%, #ffffff 62%, #eef8ff 100%);
|
||||
}
|
||||
|
||||
.drawer-hero::after {
|
||||
position: absolute;
|
||||
right: -58rpx;
|
||||
top: -90rpx;
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(0, 148, 255, 0.07);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.drawer-hero__image {
|
||||
position: absolute;
|
||||
right: -20rpx;
|
||||
top: 2rpx;
|
||||
z-index: 1;
|
||||
width: 180rpx;
|
||||
height: 138rpx;
|
||||
opacity: 0.92;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<tm-drawer v-model:show="show" position="bottom" size="58%" title="用户关系详情" :show-close="true" :show-footer="false">
|
||||
<view v-if="node" class="detail-content">
|
||||
<view class="card flex items-center gap-16rpx border-1rpx border-#edf1f5 rounded-18rpx border-solid bg-white p-18rpx">
|
||||
<view
|
||||
class="card flex items-center gap-16rpx border-1rpx border-#edf1f5 rounded-18rpx border-solid bg-white p-18rpx">
|
||||
<view
|
||||
class="h-76rpx w-76rpx flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-#eef2f6 color-#64748b text-30rpx font-700"
|
||||
:class="genderClass"
|
||||
>
|
||||
:class="genderClass">
|
||||
<image v-if="avatarUrl" class="h-full w-full" :src="avatarUrl" mode="aspectFill" />
|
||||
<text v-else>{{ initial }}</text>
|
||||
</view>
|
||||
@@ -13,8 +13,11 @@
|
||||
<view class="show-line sub-text-28 font-700">{{ node.displayName }}</view>
|
||||
<view v-if="node.username" class="show-line second-text-20 mt-4rpx">@{{ node.username }}</view>
|
||||
</view>
|
||||
<view v-if="isEntry" class="shrink-0 rounded-full bg-#e8f5ff px-14rpx py-6rpx color-#0094ff text-20rpx font-700">当前用户</view>
|
||||
<view v-else-if="node.root" class="shrink-0 rounded-full bg-#eef2ff px-14rpx py-6rpx color-#4f46e5 text-20rpx font-700">
|
||||
<view v-if="isEntry"
|
||||
class="shrink-0 rounded-full bg-#e8f5ff px-14rpx py-6rpx color-#0094ff text-20rpx font-700">当前用户
|
||||
</view>
|
||||
<view v-else-if="node.root"
|
||||
class="shrink-0 rounded-full bg-#eef2ff px-14rpx py-6rpx color-#4f46e5 text-20rpx font-700">
|
||||
图谱根节点
|
||||
</view>
|
||||
</view>
|
||||
@@ -27,7 +30,8 @@
|
||||
|
||||
<template v-if="!node.root">
|
||||
<view class="mx-4rpx mt-24rpx sub-text-26 font-700">关系信息</view>
|
||||
<view class="sub-text-22 mt-10rpx border-1rpx border-#dbeafe rounded-14rpx border-solid bg-#eef8ff px-18rpx py-14rpx">
|
||||
<view
|
||||
class="sub-text-22 mt-10rpx border-1rpx border-#dbeafe rounded-14rpx border-solid bg-#eef8ff px-18rpx py-14rpx">
|
||||
{{ node.relationDescription || "暂无关系描述" }}
|
||||
</view>
|
||||
<view class="mt-12rpx grid grid-cols-2 gap-12rpx">
|
||||
@@ -42,7 +46,8 @@
|
||||
</template>
|
||||
|
||||
<view v-if="canUploadWardFace" class="mt-24rpx">
|
||||
<view class="mb-12rpx rounded-16rpx bg-#fff7ed px-18rpx py-14rpx color-#9a3412 text-22rpx leading-36rpx">
|
||||
<view
|
||||
class="mb-12rpx rounded-16rpx bg-#fff7ed px-18rpx py-14rpx color-#9a3412 text-22rpx leading-36rpx">
|
||||
您可以为该被监护人提交人脸照片,提交后需等待审核通过。
|
||||
</view>
|
||||
<view class="grid grid-cols-2 gap-12rpx">
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<crud-layout
|
||||
mainIsScroll
|
||||
title="图谱列表"
|
||||
bg="/static/page/blue-bg.png"
|
||||
navRightText="关系图谱"
|
||||
navRightIcon="records"
|
||||
@navRightClick="openRelationTree"
|
||||
>
|
||||
<z-paging
|
||||
ref="pageRef"
|
||||
v-model="wards"
|
||||
height="100%"
|
||||
:fixed="false"
|
||||
:default-page-size="10"
|
||||
safe-area-inset-bottom
|
||||
:loading-more-enabled="true"
|
||||
:show-scrollbar="false"
|
||||
@query="queryWards"
|
||||
:auto-clean-list-when-reload="false"
|
||||
:auto-scroll-to-top-when-reload="false"
|
||||
>
|
||||
<template #top>
|
||||
<view class="pt-16rpx">
|
||||
<view class="guardian-hero relative h-228rpx overflow-hidden rounded-24rpx bg-white shadow-main">
|
||||
<view class="relative z-2 h-full w-62% flex flex-col justify-center pl-22rpx">
|
||||
<view class="flex items-center gap-8rpx">
|
||||
<view class="main-text-28">被监护人服务台</view>
|
||||
<view v-if="queryCompleted" class="shrink-0 rounded-full bg-#eaf5ff px-10rpx py-4rpx text-19rpx color-#007ad6 font-700">
|
||||
{{ totalCount }} 人
|
||||
</view>
|
||||
</view>
|
||||
<view class="second-text-20 mt-4rpx leading-30rpx">饭卡、人脸与预约状态集中查看</view>
|
||||
<view class="mt-10rpx grid grid-cols-3 gap-6rpx">
|
||||
<view class="col-span-2 min-w-0 rounded-13rpx border-1rpx border-#d8ecfb border-solid bg-#f7fbff px-9rpx py-6rpx">
|
||||
<view class="flex items-center text-16rpx color-#475569 font-700">
|
||||
<tm-icon name="emotion-happy-line" :size="15" color="#007ad6" />
|
||||
<view class="ml-4rpx">人脸情况</view>
|
||||
</view>
|
||||
<view class="mt-2rpx grid grid-cols-2">
|
||||
<view
|
||||
v-for="item in heroFaceSummary"
|
||||
:key="item.key"
|
||||
class="flex min-w-0 items-baseline first:border-r-1rpx first:border-#d8ecfb first:border-r-solid last:pl-7rpx"
|
||||
>
|
||||
<view class="show-line text-15rpx color-#64748b">{{ item.label }}</view>
|
||||
<view class="ml-4rpx text-20rpx font-800" :style="{ color: item.color }">{{ item.value }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="min-w-0 rounded-13rpx border-1rpx border-#fed7aa border-solid bg-#fffaf3 px-8rpx py-6rpx">
|
||||
<view class="flex items-center text-15rpx color-#7c4a19 font-700">
|
||||
<tm-icon name="bowl-line" :size="15" color="#c2410c" />
|
||||
<view class="show-line ml-3rpx">预约订单</view>
|
||||
</view>
|
||||
<view class="mt-2rpx flex items-baseline">
|
||||
<view class="text-15rpx color-#9a5b22">待核销</view>
|
||||
<view class="ml-4rpx text-20rpx color-#c2410c font-800">{{ pendingReservationTotal }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<image
|
||||
class="guardian-hero__image"
|
||||
src="/static/illustration/guardian-service-hero.png"
|
||||
mode="aspectFit"
|
||||
aria-label="监护人陪同被监护人办理饭卡和人脸业务的插画"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view v-if="wards.length" class="mt-14rpx flex-col gap-12rpx pb-32rpx">
|
||||
<view
|
||||
v-for="ward in wards"
|
||||
:key="String(ward.wardUserId)"
|
||||
class="overflow-hidden rounded-24rpx border-1rpx border-#e8f0f8 border-solid bg-white px-20rpx pb-14rpx pt-13rpx shadow-main active:bg-#f6fbff"
|
||||
@tap="openActions(ward)"
|
||||
>
|
||||
<view class="flex items-center">
|
||||
<view class="h-72rpx w-72rpx shrink-0 overflow-hidden rounded-20rpx bg-#eef8ff">
|
||||
<image class="h-full w-full" :src="avatarUrl(ward)" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="ml-14rpx min-w-0 flex-1">
|
||||
<view class="flex items-center gap-8rpx">
|
||||
<view class="main-text-28 show-line min-w-0">{{ ward.wardName || "被监护人" }}</view>
|
||||
<view class="shrink-0 rounded-full px-10rpx py-4rpx text-18rpx font-700" :class="statusClass(ward)">
|
||||
{{ ward.cardStatusName || "状态异常" }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="second-text-20 mt-4rpx show-line">
|
||||
{{ ward.walletTypeName || "饭卡" }} · 卡号 {{ ward.maskedCardNumber || "--" }}
|
||||
</view>
|
||||
</view>
|
||||
<tm-icon name="arrow-right-s-line" :size="26" color="#94a3b8" />
|
||||
</view>
|
||||
<view class="mt-10rpx grid grid-cols-3 gap-8rpx">
|
||||
<view class="col-span-2 rounded-16rpx border-1rpx border-#dcecf9 border-solid bg-#f7fbff px-12rpx py-9rpx">
|
||||
<view class="flex items-center">
|
||||
<view class="h-30rpx w-30rpx flex-row-center rounded-full bg-#e3f3ff">
|
||||
<tm-icon name="emotion-happy-line" :size="17" color="#007ad6" />
|
||||
</view>
|
||||
<view class="ml-6rpx text-19rpx color-#334155 font-700">人脸情况</view>
|
||||
</view>
|
||||
<view class="mt-6rpx grid grid-cols-2">
|
||||
<view
|
||||
v-for="metric in faceMetrics(ward)"
|
||||
:key="metric.key"
|
||||
class="flex min-w-0 items-baseline first:border-r-1rpx first:border-#dcecf9 first:border-r-solid last:pl-10rpx"
|
||||
>
|
||||
<view class="show-line text-18rpx color-#64748b">{{ metric.label }}</view>
|
||||
<view class="ml-6rpx shrink-0 text-23rpx font-800" :style="{ color: metric.color }">
|
||||
{{ countText(metric.value) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-16rpx border-1rpx border-#fed7aa border-solid bg-#fffaf3 px-11rpx py-9rpx">
|
||||
<view class="flex items-center">
|
||||
<view class="h-30rpx w-30rpx flex-row-center rounded-full bg-#ffedd5">
|
||||
<tm-icon name="bowl-line" :size="17" color="#c2410c" />
|
||||
</view>
|
||||
<view class="show-line ml-5rpx text-18rpx color-#7c4a19 font-700">预约订单</view>
|
||||
</view>
|
||||
<view class="mt-6rpx flex items-baseline">
|
||||
<view class="text-18rpx color-#9a5b22">待核销</view>
|
||||
<view class="ml-6rpx text-23rpx color-#c2410c font-800">{{ countText(ward.pendingReservationCount) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-10rpx flex items-center justify-between border-t-1rpx border-#edf2f7 border-t-solid pt-10rpx">
|
||||
<view class="flex items-baseline">
|
||||
<view class="second-text-18 mr-8rpx">饭卡余额</view>
|
||||
<view class="text-30rpx color-#202938 font-800">¥{{ hooks.useMoney(ward.balance || 0) }}</view>
|
||||
</view>
|
||||
<view v-if="ward.rechargeEnabled" class="text-21rpx color-#059669 font-700">可充值</view>
|
||||
<view v-else class="show-line ml-16rpx max-w-330rpx text-20rpx color-#c2410c font-600">
|
||||
{{ ward.disabledReason || "暂不可充值" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template #empty>
|
||||
<view class="mt-18rpx rounded-24rpx bg-white px-28rpx py-64rpx shadow-main">
|
||||
<view class="flex-col-center text-center">
|
||||
<tm-icon name="user-line" :size="54" color="#94a3b8" />
|
||||
<view class="main-text-27 mt-14rpx">暂无有效被监护人</view>
|
||||
<view class="second-text-21 mt-6rpx leading-34rpx">这里只展示当前登录用户直接且有效的监护关系</view>
|
||||
<view
|
||||
class="mt-22rpx h-88rpx min-w-230rpx flex-row-center rounded-full border-1rpx border-blue border-solid bg-white px-30rpx color-#0094ff text-25rpx font-700 active:bg-#eef8ff"
|
||||
@tap="openRelationTree"
|
||||
>
|
||||
查看关系图谱
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</z-paging>
|
||||
|
||||
<guardian-ward-action-drawer v-model="drawerVisible" :ward="selectedWard" />
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue"
|
||||
import { onShow } from "@dcloudio/uni-app"
|
||||
|
||||
import { hooks, router } from "@@/index"
|
||||
import { walletApi } from "@@/api"
|
||||
import type { GuardianWardRechargeItem } from "@@/api/wallet"
|
||||
import { filePath } from "@@/utils/file"
|
||||
import GuardianWardActionDrawer from "./components/guardian-ward-action-drawer.vue"
|
||||
|
||||
const pageRef = ref<any>(null)
|
||||
const wards = ref<GuardianWardRechargeItem[]>([])
|
||||
const wardCache = ref<GuardianWardRechargeItem[]>([])
|
||||
const totalCount = ref(0)
|
||||
const queryCompleted = ref(false)
|
||||
const selectedWard = ref<GuardianWardRechargeItem>()
|
||||
const drawerVisible = ref(false)
|
||||
|
||||
type FaceMetric = {
|
||||
key: "available-face" | "pending-face"
|
||||
label: string
|
||||
value?: number
|
||||
color: string
|
||||
}
|
||||
|
||||
type HeroFaceSummary = {
|
||||
key: "available-face" | "pending-face"
|
||||
label: string
|
||||
value: string
|
||||
color: string
|
||||
}
|
||||
|
||||
const avatarUrl = (ward: GuardianWardRechargeItem) => filePath(ward.wardAvatar) || "/static/icon/default-avatar.png"
|
||||
const statusClass = (ward: GuardianWardRechargeItem) => (ward.rechargeEnabled ? "bg-#ecfdf5 color-#047857" : "bg-#fff7ed color-#c2410c")
|
||||
const countText = (value?: number) => (typeof value === "number" ? String(value) : "--")
|
||||
const sumMetric = (key: "availableFaceCount" | "pendingFaceCount" | "pendingReservationCount") => {
|
||||
if (!queryCompleted.value) return "--"
|
||||
const values = wardCache.value.map((item) => item[key])
|
||||
if (values.some((value) => typeof value !== "number")) return "--"
|
||||
return String(values.reduce<number>((total, value) => total + Number(value || 0), 0))
|
||||
}
|
||||
const heroFaceSummary = computed<HeroFaceSummary[]>(() => [
|
||||
{
|
||||
key: "available-face",
|
||||
label: "可用",
|
||||
value: sumMetric("availableFaceCount"),
|
||||
color: "#007ad6",
|
||||
},
|
||||
{
|
||||
key: "pending-face",
|
||||
label: "审核中",
|
||||
value: sumMetric("pendingFaceCount"),
|
||||
color: "#7c3aed",
|
||||
},
|
||||
])
|
||||
const pendingReservationTotal = computed(() => sumMetric("pendingReservationCount"))
|
||||
const faceMetrics = (ward: GuardianWardRechargeItem): FaceMetric[] => [
|
||||
{
|
||||
key: "available-face",
|
||||
label: "可用",
|
||||
value: ward.availableFaceCount,
|
||||
color: "#007ad6",
|
||||
},
|
||||
{
|
||||
key: "pending-face",
|
||||
label: "审核中",
|
||||
value: ward.pendingFaceCount,
|
||||
color: "#7c3aed",
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* 接口当前返回全部有效被监护人,页面按 z-paging 的页码切片回填,统一复用项目列表交互。
|
||||
*/
|
||||
const queryWards = async (page: number, pageSize: number) => {
|
||||
try {
|
||||
if (page === 1) {
|
||||
queryCompleted.value = false
|
||||
const response: any = await walletApi.guardianWardList()
|
||||
if (response?.code !== 0) throw new Error(response?.msg || "查询被监护人饭卡失败")
|
||||
wardCache.value = response?.data || []
|
||||
totalCount.value = wardCache.value.length
|
||||
queryCompleted.value = true
|
||||
if (selectedWard.value) {
|
||||
selectedWard.value = wardCache.value.find((item) => String(item.wardUserId) === String(selectedWard.value?.wardUserId))
|
||||
}
|
||||
}
|
||||
const start = (page - 1) * pageSize
|
||||
pageRef.value?.complete(wardCache.value.slice(start, start + pageSize))
|
||||
} catch (error) {
|
||||
console.error("查询被监护人饭卡列表失败", error)
|
||||
if (page === 1) {
|
||||
wardCache.value = []
|
||||
totalCount.value = 0
|
||||
}
|
||||
pageRef.value?.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
const openActions = (ward: GuardianWardRechargeItem) => {
|
||||
selectedWard.value = ward
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
const openRelationTree = () => router.jump("/pages/user/relation/tree")
|
||||
|
||||
onShow(() => {
|
||||
pageRef.value?.reload?.(false)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.guardian-hero {
|
||||
background: linear-gradient(112deg, #ffffff 0%, #ffffff 60%, #eef8ff 100%);
|
||||
}
|
||||
|
||||
.guardian-hero::after {
|
||||
position: absolute;
|
||||
right: -70rpx;
|
||||
top: -92rpx;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(0, 148, 255, 0.07);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.guardian-hero__image {
|
||||
position: absolute;
|
||||
right: -28rpx;
|
||||
bottom: 1rpx;
|
||||
z-index: 1;
|
||||
width: 41%;
|
||||
height: 198rpx;
|
||||
opacity: 0.95;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll title="关系图谱" bg="/static/page/blue-bg.png">
|
||||
<crud-layout mainIsScroll title="关系图谱" bg="/static/page/blue-bg.png" navRightText="图谱列表"
|
||||
navRightIcon="records" @navRightClick="openRelationList">
|
||||
<relation-stats v-if="graphRoot" :user-count="Number(graphRoot.userCount || layout.nodes.length || 1)"
|
||||
:relation-count="Number(graphRoot.relationCount || layout.edges.length)" :depth="layout.depth"
|
||||
:current-name="entryNode?.displayName || ''" />
|
||||
@@ -106,6 +107,11 @@ const handleNodeSelect = (node: RelationGraphNode) => {
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 图谱画布继续用于查看关系,业务操作统一进入独立的被监护人列表页。
|
||||
*/
|
||||
const openRelationList = () => router.jump("/pages/user/relation/list")
|
||||
|
||||
onLoad(async (options?: Record<string, any>) => {
|
||||
entryUserId.value = ensureUserId(options?.userId)
|
||||
await loadGraph()
|
||||
|
||||
+190
-107
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from "vue"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { onLoad, onUnload } from "@dcloudio/uni-app"
|
||||
|
||||
import { hooks, constant, router } from "@@/index"
|
||||
import { payApi } from "@@/api"
|
||||
import { payApi, walletApi } from "@@/api"
|
||||
import type { GuardianWardRechargeResult } from "@@/api/wallet"
|
||||
import AppLottiePlayer from "@@/components/common/app-lottie-player.vue"
|
||||
import InformationStreamAd from "@@/components/ad/information-stream-ad.vue"
|
||||
|
||||
@@ -11,133 +12,215 @@ import loadingAnimation from "@/static/lottie-json/loading.json"
|
||||
import successAnimation from "@/static/lottie-json/success.json"
|
||||
import errorAnimation from "@/static/lottie-json/fail.json"
|
||||
|
||||
type ResultPhase = "loading" | "success" | "error"
|
||||
type ResultPhase = "loading" | "confirming" | "success" | "error"
|
||||
|
||||
const { serviceEnum } = constant
|
||||
|
||||
const state = reactive<any>({
|
||||
phase: "loading" as ResultPhase,
|
||||
payNo: "",
|
||||
payOrder: {},
|
||||
failMessage: "",
|
||||
const state = reactive({
|
||||
phase: "loading" as ResultPhase,
|
||||
payNo: "",
|
||||
rechargeId: "",
|
||||
mode: "self",
|
||||
payOrder: {} as any,
|
||||
guardianResult: {} as GuardianWardRechargeResult,
|
||||
failMessage: "",
|
||||
querying: false,
|
||||
})
|
||||
|
||||
const MAX_AUTO_CONFIRM_ATTEMPTS = 3
|
||||
const AUTO_CONFIRM_INTERVAL = 1200
|
||||
let autoConfirmAttempts = 0
|
||||
let autoConfirmTimer: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const isGuardianMode = computed(() => state.mode === "guardianWard" && Boolean(state.rechargeId))
|
||||
const titleText = computed(() => {
|
||||
if (state.phase === "loading") return "充值结果确认中"
|
||||
return state.phase === "success" ? "充值成功" : "充值失败"
|
||||
if (state.phase === "loading") return "充值结果确认中"
|
||||
if (state.phase === "confirming") return "到账确认中"
|
||||
return state.phase === "success" ? "充值成功" : "充值未完成"
|
||||
})
|
||||
|
||||
const descText = computed(() => {
|
||||
if (state.phase === "loading") return "正在查询订单状态,请稍候..."
|
||||
if (state.phase === "success") return "充值金额已到账,可前往钱包查看最新余额"
|
||||
return state.failMessage || "未查询到本次充值结果,请稍后在充值记录中确认"
|
||||
if (state.phase === "loading") return "正在查询第三方支付订单,请稍候..."
|
||||
if (state.phase === "confirming") return "支付结果或饭卡到账回调仍在处理中,请稍后重试"
|
||||
if (state.phase === "success") {
|
||||
return isGuardianMode.value
|
||||
? `${state.guardianResult.wardName || "被监护人"}的饭卡余额已更新`
|
||||
: "充值金额已到账,可前往钱包查看最新余额"
|
||||
}
|
||||
return state.failMessage || "未查询到本次充值结果,请稍后在充值记录中确认"
|
||||
})
|
||||
|
||||
const currentAnimation = computed(() => {
|
||||
if (state.phase === "loading") return loadingAnimation
|
||||
return state.phase === "success" ? successAnimation : errorAnimation
|
||||
if (["loading", "confirming"].includes(state.phase)) return loadingAnimation
|
||||
return state.phase === "success" ? successAnimation : errorAnimation
|
||||
})
|
||||
|
||||
const currentLoop = computed(() => state.phase === "loading")
|
||||
|
||||
const currentAnimationSize = computed(() => {
|
||||
if (state.phase === "loading") {
|
||||
return { width: "140rpx", height: "140rpx" }
|
||||
}
|
||||
if (state.phase === "success") {
|
||||
return { width: "280rpx", height: "280rpx" }
|
||||
}
|
||||
return { width: "280rpx", height: "280rpx" }
|
||||
})
|
||||
|
||||
const payOrderAmount = computed(() => Number(state.payOrder?.price || 0))
|
||||
|
||||
const currentLoop = computed(() => ["loading", "confirming"].includes(state.phase))
|
||||
const currentAnimationSize = computed(() =>
|
||||
["loading", "confirming"].includes(state.phase)
|
||||
? { width: "140rpx", height: "140rpx" }
|
||||
: { width: "280rpx", height: "280rpx" },
|
||||
)
|
||||
const payOrderAmount = computed(() => Number(state.payOrder?.price || state.guardianResult?.payPrice || 0))
|
||||
const mchNameText = computed(() => state.payOrder?.mchName || "--")
|
||||
|
||||
const payTypeText = computed(() => {
|
||||
const text = serviceEnum.appPlusPlatformTypeEnum?.[state.payOrder?.payPlatformType]
|
||||
return text ? `${text}支付` : "--"
|
||||
const text = serviceEnum.appPlusPlatformTypeEnum?.[state.payOrder?.payPlatformType]
|
||||
return text ? `${text}支付` : "--"
|
||||
})
|
||||
const resultStatusText = computed(() => {
|
||||
if (state.phase === "success") return "已到账"
|
||||
if (state.phase === "confirming") return isGuardianMode.value ? "到账确认中" : "支付确认中"
|
||||
if (state.phase === "loading") return "查询中"
|
||||
return "未完成"
|
||||
})
|
||||
|
||||
const getPayOrder = async () => {
|
||||
try {
|
||||
const res: any = await payApi.getByPayNo({ payNo: state.payNo })
|
||||
state.payOrder = res?.data || {}
|
||||
if (!state.payOrder) {
|
||||
state.failMessage = "未查询到本次充值订单"
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (["20", "200"].includes(state.payOrder.status)) {
|
||||
state.phase = "success"
|
||||
} else {
|
||||
state.phase = "error"
|
||||
}
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.error("查询支付订单意外的错误:", error)
|
||||
}
|
||||
/**
|
||||
* 支付或到账回调延迟时做少量自动重试,超过次数后保留手动查询入口。
|
||||
*/
|
||||
function scheduleAutoConfirm() {
|
||||
if (autoConfirmAttempts >= MAX_AUTO_CONFIRM_ATTEMPTS) return
|
||||
autoConfirmAttempts += 1
|
||||
autoConfirmTimer = setTimeout(queryResult, AUTO_CONFIRM_INTERVAL)
|
||||
}
|
||||
const resultStatusClass = computed(() => {
|
||||
if (state.phase === "success") return "color-#047857"
|
||||
if (state.phase === "error") return "color-#dc2626"
|
||||
return "color-#007ad6"
|
||||
})
|
||||
|
||||
/**
|
||||
* 代充值必须同时满足第三方订单支付成功和钱包记录已到账,避免回调延迟时提前显示成功。
|
||||
*/
|
||||
async function queryResult() {
|
||||
if (state.querying) return
|
||||
state.querying = true
|
||||
state.phase = "loading"
|
||||
state.failMessage = ""
|
||||
try {
|
||||
const orderResponse: any = await payApi.getByPayNo({ payNo: state.payNo })
|
||||
if (orderResponse?.code !== 0 || !orderResponse?.data) {
|
||||
throw new Error(orderResponse?.msg || "未查询到本次充值订单")
|
||||
}
|
||||
state.payOrder = orderResponse.data
|
||||
const payStatus = String(state.payOrder.status)
|
||||
if (payStatus === "10") {
|
||||
state.phase = "confirming"
|
||||
scheduleAutoConfirm()
|
||||
return
|
||||
}
|
||||
if (payStatus !== "20") {
|
||||
state.phase = "error"
|
||||
state.failMessage = "本次支付未成功"
|
||||
return
|
||||
}
|
||||
if (!isGuardianMode.value) {
|
||||
state.phase = "success"
|
||||
return
|
||||
}
|
||||
|
||||
const walletResponse: any = await walletApi.guardianWardRechargeResult({ rechargeId: state.rechargeId })
|
||||
if (walletResponse?.code !== 0 || !walletResponse?.data) {
|
||||
throw new Error(walletResponse?.msg || "查询饭卡到账状态失败")
|
||||
}
|
||||
state.guardianResult = walletResponse.data
|
||||
state.phase = state.guardianResult.paid ? "success" : "confirming"
|
||||
if (state.phase === "confirming") scheduleAutoConfirm()
|
||||
} catch (error: any) {
|
||||
console.error("查询充值结果失败", error)
|
||||
state.phase = "error"
|
||||
state.failMessage = error?.message || error?.msg || "充值结果查询失败,请稍后重试"
|
||||
} finally {
|
||||
state.querying = false
|
||||
}
|
||||
}
|
||||
|
||||
const goTargetPage = () => {
|
||||
if (isGuardianMode.value) {
|
||||
router.jump("/pages/user/relation/list", {}, 2)
|
||||
return
|
||||
}
|
||||
router.jump("/pages/wallet/list", {}, 2)
|
||||
}
|
||||
|
||||
const retryResult = () => {
|
||||
if (autoConfirmTimer) clearTimeout(autoConfirmTimer)
|
||||
autoConfirmAttempts = 0
|
||||
queryResult()
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
state.payNo = options.payNo || ""
|
||||
if (state.payNo) {
|
||||
getPayOrder()
|
||||
}
|
||||
state.payNo = String(options?.payNo || "")
|
||||
state.rechargeId = String(options?.rechargeId || "")
|
||||
state.mode = String(options?.mode || "self")
|
||||
if (!state.payNo) {
|
||||
state.phase = "error"
|
||||
state.failMessage = "缺少支付订单号"
|
||||
return
|
||||
}
|
||||
queryResult()
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
if (autoConfirmTimer) clearTimeout(autoConfirmTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<crud-layout title="充值结果" bg="/static/page/blue-bg.png">
|
||||
<view class="h-30rpx" />
|
||||
<view class="bg-white rounded-24rpx shadow-main px-30rpx pt-10rpx pb-24rpx">
|
||||
<view class="flex-col-center pt-10rpx pb-20rpx h-500rpx">
|
||||
<view class="flex-row-center size-280rpx">
|
||||
<AppLottiePlayer :animationData="currentAnimation" :width="currentAnimationSize.width"
|
||||
:height="currentAnimationSize.height" :loop="currentLoop" />
|
||||
</view>
|
||||
<crud-layout mainIsScroll title="充值结果" bg="/static/page/blue-bg.png">
|
||||
<view class="pt-24rpx pb-250rpx">
|
||||
<view class="rounded-24rpx bg-white px-30rpx pb-24rpx pt-10rpx shadow-main">
|
||||
<view class="h-500rpx flex-col-center pb-20rpx pt-10rpx">
|
||||
<view class="h-280rpx w-280rpx flex-row-center">
|
||||
<AppLottiePlayer :animationData="currentAnimation" :width="currentAnimationSize.width"
|
||||
:height="currentAnimationSize.height" :loop="currentLoop" />
|
||||
</view>
|
||||
<view class="text-38rpx color-#222 font-700">{{ titleText }}</view>
|
||||
<view class="mt-10rpx max-w-590rpx text-center text-24rpx color-#657187 leading-38rpx">{{ descText }}</view>
|
||||
<view class="mt-10rpx text-58rpx color-#222 font-700">¥{{ hooks.useMoney(payOrderAmount) }}</view>
|
||||
</view>
|
||||
|
||||
<view class="text-38rpx font-700 color-#222">{{ titleText }}</view>
|
||||
<view class="mt-10rpx text-24rpx color-#8a94a6 text-center">{{ descText }}</view>
|
||||
<view class="text-58rpx font-700 color-#222">¥{{ hooks.useMoney(payOrderAmount) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="border-t-1rpx border-#edf2f7 border-t-solid pt-10rpx">
|
||||
<view class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#657187">商户</text>
|
||||
<text class="max-w-380rpx text-right text-28rpx color-#333 font-500">{{ mchNameText }}</text>
|
||||
</view>
|
||||
<view v-if="isGuardianMode" class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#657187">充值对象</text>
|
||||
<text class="max-w-380rpx text-right text-28rpx color-#333 font-500">{{ state.guardianResult.wardName || "被监护人" }}</text>
|
||||
</view>
|
||||
<view class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#657187">充值方式</text>
|
||||
<text class="max-w-380rpx text-right text-28rpx color-#333 font-500">{{ payTypeText }}</text>
|
||||
</view>
|
||||
<view v-if="isGuardianMode && state.guardianResult.paid" class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#657187">最新余额</text>
|
||||
<text class="max-w-380rpx text-right text-28rpx color-#333 font-700">¥{{ hooks.useMoney(state.guardianResult.balance || 0) }}</text>
|
||||
</view>
|
||||
<view class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#657187">订单状态</text>
|
||||
<text class="max-w-380rpx text-right text-28rpx font-700" :class="resultStatusClass">{{ resultStatusText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pt-10rpx">
|
||||
<view class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#8a94a6">商户</text>
|
||||
<text class="text-28rpx color-#333 font-500 max-w-380rpx text-right">{{ mchNameText }}</text>
|
||||
</view>
|
||||
<view class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#8a94a6">充值方式</text>
|
||||
<text class="text-28rpx color-#333 font-500 max-w-380rpx text-right">
|
||||
{{ payTypeText }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="flex items-center justify-between py-16rpx">
|
||||
<text class="text-26rpx color-#8a94a6">订单状态</text>
|
||||
<text class="text-28rpx font-500 max-w-380rpx text-right"
|
||||
:class="state.phase === 'success' ? 'color-#1dbb84' : state.phase === 'error' ? 'color-#ff5a5f' : 'color-#0094ff'">
|
||||
{{ state.phase === "loading" ? "确认中" : state.phase === "success" ? "已到账" : "未完成" }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="state.phase !== 'loading'"
|
||||
class="fixed left-30rpx right-30rpx bottom-30rpx z-20 flex flex-col items-center gap-20rpx">
|
||||
<!-- 广告 -->
|
||||
<information-stream-ad class="w-full" />
|
||||
<view v-if="state.phase === 'success'"
|
||||
class="w-full h-88rpx rounded-full flex items-center justify-center text-30rpx font-700 color-white! bg-gradient-to-r from-#0094ff to-#36b4ff shadow-[0_16rpx_32rpx_rgba(0,148,255,0.18)]"
|
||||
@tap="router.jump('/pages/wallet/list', {}, 2)">
|
||||
查看钱包
|
||||
</view>
|
||||
<view v-else
|
||||
class="w-full h-88rpx rounded-full flex items-center justify-center text-30rpx font-700 color-white! bg-gradient-to-r from-#0094ff to-#36b4ff shadow-[0_16rpx_32rpx_rgba(0,148,255,0.18)]"
|
||||
@tap="getPayOrder">
|
||||
重新查询
|
||||
</view>
|
||||
</view>
|
||||
</crud-layout>
|
||||
<view v-if="state.phase !== 'loading'"
|
||||
class="result-actions fixed bottom-0 left-0 right-0 z-20 flex flex-col items-center gap-16rpx bg-white px-30rpx pt-18rpx">
|
||||
<information-stream-ad class="w-full" />
|
||||
<view v-if="state.phase === 'success'"
|
||||
class="h-88rpx w-full flex-row-center rounded-full bg-blue text-30rpx color-white font-700 shadow-main active:opacity-80"
|
||||
@tap="goTargetPage">
|
||||
{{ isGuardianMode ? "返回图谱列表" : "查看钱包" }}
|
||||
</view>
|
||||
<view v-else
|
||||
class="h-88rpx w-full flex-row-center rounded-full bg-blue text-30rpx color-white font-700 shadow-main active:opacity-80"
|
||||
@tap="retryResult">
|
||||
{{ state.phase === "confirming" ? "重新确认到账" : "重新查询" }}
|
||||
</view>
|
||||
</view>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.result-actions {
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
box-shadow: 0 -8rpx 30rpx rgba(31, 41, 55, 0.08);
|
||||
}
|
||||
</style>
|
||||
+341
-263
@@ -1,280 +1,404 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from "vue"
|
||||
import { computed, reactive } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { isEmpty } from "radash"
|
||||
|
||||
import { hooks, constant, router } from "@@/index"
|
||||
import { walletApi, payApi, appPlusPlatformApi, type AppPlusPayDto } from "@@/api"
|
||||
import { hooks, router } from "@@/index"
|
||||
import { walletApi, appPlusPlatformApi, type AppPlusPayDto } from "@@/api"
|
||||
import type {
|
||||
GuardianWardRechargeContext,
|
||||
GuardianWardRechargeCreateResult,
|
||||
GuardianWardRechargePackage,
|
||||
} from "@@/api/wallet"
|
||||
|
||||
import ServiceText from "@@/components/common/service-text.vue"
|
||||
|
||||
const isPay = computed(() => {
|
||||
return state.payType && state.payType.length > 0
|
||||
type PayOption = {
|
||||
logoDir: string
|
||||
text: string
|
||||
desc: string
|
||||
disabled: boolean
|
||||
type: string
|
||||
value: "WECHAT" | "ALI_PAY"
|
||||
}
|
||||
|
||||
type NativePaymentProvider = "wxpay" | "alipay"
|
||||
|
||||
const state = reactive({
|
||||
payNo: "",
|
||||
rechargeId: "",
|
||||
payType: [] as PayOption[],
|
||||
walletId: "",
|
||||
wardUserId: "",
|
||||
currentPayType: "",
|
||||
currentRecharge: 0,
|
||||
detail: {} as any,
|
||||
rechargeList: [] as GuardianWardRechargePackage[],
|
||||
loading: false,
|
||||
loadError: "",
|
||||
submitting: false,
|
||||
})
|
||||
|
||||
|
||||
const state = reactive<any>({
|
||||
payNo: '',
|
||||
|
||||
payType: [],
|
||||
walletId: null,
|
||||
currentPayType: null,
|
||||
currentRecharge: 0,
|
||||
userInfo: {},
|
||||
detail: {},
|
||||
payTypeList: [],
|
||||
rechargeList: [],
|
||||
const isGuardianMode = computed(() => Boolean(state.wardUserId))
|
||||
const isH5Environment = computed(() => {
|
||||
let isH5 = false
|
||||
// #ifdef H5
|
||||
isH5 = true
|
||||
// #endif
|
||||
return isH5
|
||||
})
|
||||
const pageTitle = computed(() => (isGuardianMode.value ? "为被监护人充值" : "充值"))
|
||||
const isPay = computed(() => state.payType.length > 0 && state.rechargeList.length > 0)
|
||||
const summaryName = computed(() =>
|
||||
isGuardianMode.value ? state.detail.wardName || "被监护人" : state.detail.walletTypeName || "饭卡",
|
||||
)
|
||||
const summaryType = computed(() => state.detail.walletTypeName || "饭卡")
|
||||
const summaryIcon = computed(() => state.detail.walletIcon || "/static/icon/default-avatar.png")
|
||||
const environmentMessage = computed(() => {
|
||||
let message = ""
|
||||
// #ifdef H5
|
||||
message = "H5 暂不支持原生支付,请在 App 内完成充值"
|
||||
// #endif
|
||||
return message
|
||||
})
|
||||
const emptyMessage = computed(() => {
|
||||
if (state.loadError) return state.loadError
|
||||
if (!state.rechargeList.length) return "当前暂无可用充值套餐"
|
||||
return isGuardianMode.value ? "目标饭卡所属食堂暂不支持 App 支付" : "该商户暂不提供充值功能,请联系客服"
|
||||
})
|
||||
|
||||
onLoad((options: any) => {
|
||||
if (!isEmpty(options.id)) {
|
||||
state.walletId = options.id
|
||||
}
|
||||
})
|
||||
onShow(async () => {
|
||||
await getUserPayTypeList()
|
||||
getUserInfo()
|
||||
getDetail()
|
||||
getRechargeList()
|
||||
state.walletId = String(options?.id || "")
|
||||
state.wardUserId = String(options?.wardUserId || "")
|
||||
})
|
||||
|
||||
const getUserPayTypeList = async () => {
|
||||
const _pa = {}
|
||||
const { data } = await walletApi.getUserPaySettingList(_pa)
|
||||
state.payTypeList = data ?? []
|
||||
if (state.payTypeList.length) {
|
||||
const typeList = state.payType.map(v => v.type)
|
||||
for (const v of state.payTypeList) {
|
||||
if (!v.platformType) continue
|
||||
if (v.platformType == "10" && !typeList.includes("10")) {
|
||||
state.payType.push({
|
||||
logoDir: "/static/pay/wechat-pay.jpg",
|
||||
text: "微信支付",
|
||||
desc: "",
|
||||
disabled: false,
|
||||
type: "10",
|
||||
value: "WECHAT"
|
||||
},)
|
||||
}
|
||||
if (v.platformType == "20" && !typeList.includes("20")) {
|
||||
state.payType.push({
|
||||
logoDir: "/static/pay/ali-pay.png",
|
||||
text: "支付宝支付",
|
||||
desc: "10亿用户都在用,真安全,更放心",
|
||||
disabled: false,
|
||||
type: "20",
|
||||
value: "ALI_PAY"
|
||||
},)
|
||||
}
|
||||
}
|
||||
const defaultPayType = state.payType[0].type
|
||||
if (defaultPayType && !state.currentPayType) {
|
||||
state.currentPayType = defaultPayType
|
||||
}
|
||||
}
|
||||
onShow(loadPageData)
|
||||
|
||||
}
|
||||
|
||||
const getDetail = async () => {
|
||||
const _pa = { walletId: state.walletId }
|
||||
const { data } = await walletApi.detail(_pa)
|
||||
state.detail = data
|
||||
}
|
||||
const getUserInfo = () => {
|
||||
const userInfoKey = constant.storageConstant.USERINFO
|
||||
const userStorage = uni.getStorageSync(userInfoKey)
|
||||
if (!isEmpty(userStorage)) {
|
||||
state.userInfo = userStorage
|
||||
/**
|
||||
* 两种充值模式共用同一页面,代充值上下文由后端一次返回,避免使用监护人本人的商户支付配置。
|
||||
*/
|
||||
async function loadPageData() {
|
||||
if (state.loading) return
|
||||
state.loading = true
|
||||
state.loadError = ""
|
||||
state.payType = []
|
||||
state.rechargeList = []
|
||||
state.currentPayType = ""
|
||||
state.currentRecharge = 0
|
||||
try {
|
||||
if (isGuardianMode.value) {
|
||||
await loadGuardianContext()
|
||||
} else {
|
||||
await Promise.all([getUserPayTypeList(), getDetail(), getRechargeList()])
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("加载充值页面失败", error)
|
||||
state.loadError = error?.message || error?.msg || "充值信息加载失败,请稍后重试"
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
const getRechargeList = async () => {
|
||||
const _pa = {}
|
||||
const { data } = await walletApi.rechargeList(_pa)
|
||||
state.rechargeList = data
|
||||
|
||||
async function loadGuardianContext() {
|
||||
const response: any = await walletApi.guardianWardRechargeContext({ wardUserId: state.wardUserId })
|
||||
if (response?.code !== 0 || !response?.data) {
|
||||
throw new Error(response?.msg || "查询被监护人充值信息失败")
|
||||
}
|
||||
const context = response.data as GuardianWardRechargeContext
|
||||
state.detail = context.ward || {}
|
||||
state.rechargeList = context.packages || []
|
||||
setPayOptions(context.platformTypes || [])
|
||||
if (!state.detail.rechargeEnabled) {
|
||||
state.loadError = state.detail.disabledReason || "目标饭卡当前不可充值"
|
||||
}
|
||||
}
|
||||
// 充值
|
||||
const doPay = async () => {
|
||||
if (state.currentRecharge == -1) {
|
||||
|
||||
async function getUserPayTypeList() {
|
||||
const response: any = await walletApi.getUserPaySettingList({})
|
||||
if (response?.code !== 0) throw new Error(response?.msg || "查询支付方式失败")
|
||||
setPayOptions((response?.data || []).map((item: any) => String(item.platformType || "")))
|
||||
}
|
||||
|
||||
async function getDetail() {
|
||||
if (!state.walletId) throw new Error("缺少钱包信息")
|
||||
const response: any = await walletApi.detail({ walletId: state.walletId })
|
||||
if (response?.code !== 0 || !response?.data) throw new Error(response?.msg || "查询钱包失败")
|
||||
state.detail = response.data
|
||||
}
|
||||
|
||||
async function getRechargeList() {
|
||||
const response: any = await walletApi.rechargeList({})
|
||||
if (response?.code !== 0) throw new Error(response?.msg || "查询充值套餐失败")
|
||||
state.rechargeList = response?.data || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付方式统一由平台编码转换为页面配置,监护代充值和本人充值复用同一套支付交互。
|
||||
*/
|
||||
function setPayOptions(platformTypes: string[]) {
|
||||
const typeSet = new Set(platformTypes.map(String))
|
||||
const options: PayOption[] = []
|
||||
if (typeSet.has("10")) {
|
||||
options.push({
|
||||
logoDir: "/static/pay/wechat-pay.jpg",
|
||||
text: "微信支付",
|
||||
desc: "使用微信完成本次充值",
|
||||
disabled: false,
|
||||
type: "10",
|
||||
value: "WECHAT",
|
||||
})
|
||||
}
|
||||
if (typeSet.has("20")) {
|
||||
options.push({
|
||||
logoDir: "/static/pay/ali-pay.png",
|
||||
text: "支付宝支付",
|
||||
desc: "使用支付宝完成本次充值",
|
||||
disabled: false,
|
||||
type: "20",
|
||||
value: "ALI_PAY",
|
||||
})
|
||||
}
|
||||
state.payType = options
|
||||
state.currentPayType = options[0]?.type || ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付提交期间锁定按钮,防止重复创建代充值记录或第三方支付订单。
|
||||
*/
|
||||
async function doPay() {
|
||||
if (state.submitting) return
|
||||
const targetRecharge = state.rechargeList[state.currentRecharge]
|
||||
const targetPayType = state.payType.find((item) => item.type === state.currentPayType)
|
||||
if (!targetRecharge) {
|
||||
uni.showToast({ title: "请选择充值金额", icon: "none" })
|
||||
return
|
||||
}
|
||||
if (state.currentPayType == undefined) {
|
||||
if (!targetPayType) {
|
||||
uni.showToast({ title: "请选择支付方式", icon: "none" })
|
||||
return
|
||||
}
|
||||
if (isGuardianMode.value && !state.detail.rechargeEnabled) {
|
||||
uni.showToast({ title: state.detail.disabledReason || "目标饭卡当前不可充值", icon: "none" })
|
||||
return
|
||||
}
|
||||
if (isH5Environment.value) {
|
||||
uni.showToast({ title: "H5 暂不支持原生支付,请在 App 内完成充值", icon: "none", duration: 3500 })
|
||||
return
|
||||
}
|
||||
|
||||
state.submitting = true
|
||||
try {
|
||||
uni.showLoading({ title: "充值中...", mask: true })
|
||||
const targetRecharge = state.rechargeList[state.currentRecharge]
|
||||
const targetPayType = state.payType.find(v => v.type == state.currentPayType)
|
||||
if (isEmpty(targetRecharge)) {
|
||||
throw new Error()
|
||||
uni.showLoading({ title: "正在发起支付", mask: true })
|
||||
const provider = await requirePaymentProvider(targetPayType)
|
||||
const business = await createRechargeBusiness(targetRecharge)
|
||||
const payParams: AppPlusPayDto = {
|
||||
bizType: isGuardianMode.value ? "GUARDIAN_WARD_WALLET_RECHARGE" : "MINI_APP_WALLET_RECHARGE",
|
||||
bizId: business.bizId,
|
||||
miniPlatformType: targetPayType.value,
|
||||
openId: "",
|
||||
}
|
||||
// 检查用户是否有支付环境
|
||||
const pRes = await uni.getProvider({ service: 'payment' })
|
||||
const providers = (pRes?.provider ?? []) as string[]
|
||||
if (!providers.length) {
|
||||
throw new Error()
|
||||
const response: any = await appPlusPlatformApi.miniPay(payParams)
|
||||
if (response?.code !== 0 || !response?.data) {
|
||||
throw new Error(response?.msg || "充值支付发起失败")
|
||||
}
|
||||
if (targetPayType!.value == "WECHAT") {
|
||||
await disposeWxpay(targetRecharge.id)
|
||||
} else if (targetPayType!.value == "ALI_PAY") {
|
||||
await disposeAlipay(targetRecharge.id)
|
||||
}
|
||||
uni.hideLoading()
|
||||
router.jump("/pages/wallet/recharge-done", { payNo: state.payNo }, 2)
|
||||
state.payNo = String(response.data.payNo || "")
|
||||
await requestNativePayment(provider, targetPayType, response.data.appPlusPayParams || {})
|
||||
router.jump(
|
||||
"/pages/wallet/recharge-done",
|
||||
{
|
||||
payNo: state.payNo,
|
||||
rechargeId: state.rechargeId,
|
||||
mode: isGuardianMode.value ? "guardianWard" : "self",
|
||||
},
|
||||
2,
|
||||
)
|
||||
} catch (error: any) {
|
||||
console.error("充值意外的错误:", error)
|
||||
uni.hideLoading()
|
||||
if (error?.errMsg && error?.errMsg.includes("requestPayment:fail [paymentAlipay:62009]unknown error")) {
|
||||
uni.showToast({ title: "请先安装支付宝App", icon: "none" })
|
||||
} else if (error?.message) {
|
||||
uni.showToast({ title: error.message, icon: "none" })
|
||||
console.error("充值支付失败", error)
|
||||
if (error?.errMsg?.includes("paymentAlipay:62009")) {
|
||||
uni.showToast({ title: "请先安装支付宝 App", icon: "none" })
|
||||
} else if (error?.errMsg?.includes("cancel")) {
|
||||
uni.showToast({ title: "支付已取消", icon: "none" })
|
||||
} else {
|
||||
uni.showToast({ title: "充值失败", icon: "none" })
|
||||
uni.showToast({ title: error?.message || error?.msg || "充值失败", icon: "none" })
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
state.submitting = false
|
||||
}
|
||||
}
|
||||
|
||||
const disposeAlipay = async (bizId: any) => {
|
||||
const pRes = await uni.getProvider({ service: 'payment' })
|
||||
const providers = (pRes?.provider ?? []) as string[]
|
||||
if (!providers.length) {
|
||||
throw new Error("支付宝支付环境错误")
|
||||
async function createRechargeBusiness(targetRecharge: GuardianWardRechargePackage) {
|
||||
if (!isGuardianMode.value) {
|
||||
return { bizId: String(targetRecharge.id) }
|
||||
}
|
||||
if (!providers.includes("alipay")) {
|
||||
uni.showToast({ title: "请先安装支付宝App,确保支付宝支付环境", icon: "none" })
|
||||
return
|
||||
}
|
||||
const payParams: AppPlusPayDto = {
|
||||
bizType: "MINI_APP_WALLET_RECHARGE",
|
||||
bizId: bizId,
|
||||
miniPlatformType: "ALI_PAY",
|
||||
openId: "",
|
||||
}
|
||||
const { data: createData, code: createCode }: any = await appPlusPlatformApi.miniPay(payParams)
|
||||
if (createCode != 0) {
|
||||
throw new Error(createData?.msg ?? "支付宝充值发起失败")
|
||||
}
|
||||
state.payNo = createData?.payNo ?? ""
|
||||
const paymentResult = await uni.requestPayment({
|
||||
provider: 'alipay',
|
||||
...createData?.appPlusPayParams ?? {},
|
||||
});
|
||||
console.log("支付宝支付requestPayment返回:", paymentResult);
|
||||
if (paymentResult.errMsg && paymentResult.errMsg.includes("requestPayment:ok")) {
|
||||
uni.showToast({ title: "充值成功", icon: "none" })
|
||||
} else {
|
||||
uni.showToast({ title: "充值失败", icon: "none" })
|
||||
const response: any = await walletApi.createGuardianWardRecharge({
|
||||
wardUserId: state.wardUserId,
|
||||
packageId: targetRecharge.id,
|
||||
})
|
||||
if (response?.code !== 0 || !response?.data) {
|
||||
throw new Error(response?.msg || "创建代充值记录失败")
|
||||
}
|
||||
const result = response.data as GuardianWardRechargeCreateResult
|
||||
state.rechargeId = String(result.rechargeId || "")
|
||||
return { bizId: state.rechargeId }
|
||||
}
|
||||
|
||||
const disposeWxpay = async (bizId: any) => {
|
||||
const pRes = await uni.getProvider({ service: 'payment' })
|
||||
const providers = (pRes?.provider ?? []) as string[]
|
||||
if (!providers.length) {
|
||||
throw new Error("微信支付环境错误")
|
||||
async function requirePaymentProvider(targetPayType: PayOption) {
|
||||
const providerResult = await uni.getProvider({ service: "payment" })
|
||||
const providers = (providerResult?.provider || []) as string[]
|
||||
if (!providers.length) throw new Error("当前环境不支持原生支付,请在 App 内操作")
|
||||
const provider: NativePaymentProvider = targetPayType.value === "WECHAT" ? "wxpay" : "alipay"
|
||||
if (!providers.includes(provider)) {
|
||||
throw new Error(targetPayType.value === "WECHAT" ? "请先安装微信 App" : "请先安装支付宝 App")
|
||||
}
|
||||
if (!providers.includes("wxpay")) {
|
||||
uni.showToast({ title: "请先安装微信,确保微信支付环境", icon: "none" })
|
||||
return provider
|
||||
}
|
||||
|
||||
async function requestNativePayment(provider: NativePaymentProvider, targetPayType: PayOption, appPlusPayParams: Record<string, any>) {
|
||||
if (targetPayType.value === "ALI_PAY") {
|
||||
await uni.requestPayment({ provider, ...appPlusPayParams })
|
||||
return
|
||||
}
|
||||
const payParams: AppPlusPayDto = {
|
||||
bizType: "MINI_APP_WALLET_RECHARGE",
|
||||
bizId: bizId,
|
||||
miniPlatformType: "WECHAT",
|
||||
openId: "",
|
||||
}
|
||||
const { data: createData, code: createCode }: any = await appPlusPlatformApi.miniPay(payParams)
|
||||
console.log("微信支付createData:", createData);
|
||||
if (createCode != 0) {
|
||||
throw new Error(createData?.msg ?? "微信充值发起失败")
|
||||
}
|
||||
state.payNo = createData?.payNo ?? ""
|
||||
const paymentResult = await uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
await uni.requestPayment({
|
||||
provider,
|
||||
orderInfo: {
|
||||
"appid": createData?.appPlusPayParams?.appid,
|
||||
"noncestr": createData?.appPlusPayParams?.nonceStr,
|
||||
"package": createData?.appPlusPayParams?.packageVal,
|
||||
"partnerid": createData?.appPlusPayParams?.partnerId,
|
||||
"prepayid": createData?.appPlusPayParams?.prepayId,
|
||||
"timestamp": createData?.appPlusPayParams?.timestamp,
|
||||
"sign": createData?.appPlusPayParams?.sign,
|
||||
appid: appPlusPayParams.appid,
|
||||
noncestr: appPlusPayParams.nonceStr,
|
||||
package: appPlusPayParams.packageVal,
|
||||
partnerid: appPlusPayParams.partnerId,
|
||||
prepayid: appPlusPayParams.prepayId,
|
||||
timestamp: appPlusPayParams.timestamp,
|
||||
sign: appPlusPayParams.sign,
|
||||
},
|
||||
});
|
||||
console.log("微信支付requestPayment返回:", paymentResult);
|
||||
if (paymentResult.errMsg && paymentResult.errMsg.includes("requestPayment:ok")) {
|
||||
uni.showToast({ title: "充值成功", icon: "none" })
|
||||
} else {
|
||||
uni.showToast({ title: "充值失败", icon: "none" })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const toRechargeRecord = () => {
|
||||
if (isGuardianMode.value || !state.walletId) return
|
||||
router.jump("/pages/wallet/detail", { id: state.walletId })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<crud-layout title="充值" bg="/static/page/blue-bg.png">
|
||||
<view class="h-30rpx" />
|
||||
<view @tap="toRechargeRecord"
|
||||
class="relative flex-row items-center justify-between h-120rpx p-x-30rpx bg-#222222 rounded-20rpx">
|
||||
<view class="flex-row items-center">
|
||||
<view class="flex-row-center size-60rpx bg-white rounded-full">
|
||||
<image class="size-50rpx rounded-full" :src="state.detail.walletIcon" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="flex-row-center">
|
||||
<view class="m-l-25rpx main-text-32 color-#fbddb0!">
|
||||
{{ state.detail.walletTypeName }}
|
||||
</view>
|
||||
<crud-layout mainIsScroll :title="pageTitle" bg="/static/page/blue-bg.png">
|
||||
<view class="pt-24rpx pb-36rpx">
|
||||
<view v-if="state.loading" class="rounded-24rpx bg-white px-30rpx py-90rpx shadow-main">
|
||||
<view class="flex-col-center">
|
||||
<tm-icon name="loader-4-line" :size="48" color="#0094ff" spin />
|
||||
<view class="sub-text-26 mt-16rpx">正在加载充值信息</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="main-text-30 color-#fbddb0!"> ¥{{ hooks.useMoney(state.detail.balance) }} </view>
|
||||
<view class="recharge-record-tag">
|
||||
<text class="recharge-record-tag__text">充值记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="isPay">
|
||||
<view class="prefix_title m-t-35rpx">充值</view>
|
||||
<view class="recharge p-30rpx bg-white rounded-20rpx m-t-20rpx shadow-main">
|
||||
<view :class="[state.currentRecharge == i ? 'recharge_item_active' : '']" class="recharge_item"
|
||||
v-for="(v, i) in state.rechargeList" :key="i" @tap="state.currentRecharge = i">
|
||||
<text class="sub-text-30 text-center">¥{{ hooks.useMoney(v.payPrice) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="prefix_title m-t-35rpx">支付</view>
|
||||
<view class="px-30rpx py-10rpx bg-white rounded-20rpx m-t-20rpx shadow-main">
|
||||
<view v-for="(v, i) in state.payType" :key="i" class="flex items-center py-20rpx px-10rpx rounded-16rpx"
|
||||
@tap="!v.disabled && (state.currentPayType = v.type)">
|
||||
<image class="size-50rpx" :src="v.logoDir" mode="scaleToFill" />
|
||||
<view class="flex-1 flex justify-between items-center">
|
||||
<view class="flex-col justify-center p-l-30rpx">
|
||||
<view class="sub-text-24">{{ v.text }}</view>
|
||||
<view class="second-text-20">{{ v.desc }}</view>
|
||||
<template v-else>
|
||||
<view class="relative overflow-hidden rounded-24rpx bg-#222 px-26rpx py-24rpx" @tap="toRechargeRecord">
|
||||
<view
|
||||
class="absolute -right-24rpx -top-36rpx h-160rpx w-160rpx rounded-full bg-#0094ff opacity-18" />
|
||||
<view class="relative z-1 flex items-center">
|
||||
<view class="h-72rpx w-72rpx flex-row-center shrink-0 overflow-hidden rounded-20rpx bg-white">
|
||||
<image class="h-62rpx w-62rpx rounded-16rpx" :src="summaryIcon" mode="aspectFill" />
|
||||
</view>
|
||||
<tm-radio v-model="state.currentPayType" :disabled="v.disabled" :value="v.type" />
|
||||
<view class="ml-18rpx min-w-0 flex-1">
|
||||
<view class="show-line text-30rpx color-#fbddb0 font-800">{{ summaryName }}</view>
|
||||
<view class="mt-4rpx text-21rpx color-#d5c29f">{{ summaryType }}{{ isGuardianMode ? " · 代充值"
|
||||
: "" }}</view>
|
||||
</view>
|
||||
<view class="text-right">
|
||||
<view class="text-20rpx color-#d5c29f">当前余额</view>
|
||||
<view class="mt-3rpx text-32rpx color-#fbddb0 font-800">¥{{
|
||||
hooks.useMoney(state.detail.balance || 0) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isGuardianMode"
|
||||
class="relative z-1 mt-18rpx border-t-1rpx border-#ffffff20 border-t-solid pt-14rpx text-21rpx color-#d5c29f">
|
||||
饭卡卡号 {{ state.detail.maskedCardNumber || "--" }} · {{ state.detail.cardStatusName || "状态异常" }}
|
||||
</view>
|
||||
<view v-else class="recharge-record-tag">
|
||||
<text class="recharge-record-tag__text">充值记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-t-60rpx sub-text-30 color-white!"
|
||||
@tap="doPay">
|
||||
确认充值
|
||||
</view>
|
||||
|
||||
<view v-if="isPay && !state.loadError">
|
||||
<view v-if="environmentMessage"
|
||||
class="mt-22rpx flex items-start rounded-18rpx bg-#fff7ed px-18rpx py-15rpx">
|
||||
<tm-icon name="information-line" :size="24" color="#c2410c" />
|
||||
<view class="ml-8rpx flex-1 text-22rpx color-#9a3412 leading-34rpx">{{ environmentMessage }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="prefix_title mt-34rpx">选择充值金额</view>
|
||||
<view class="mt-18rpx grid grid-cols-3 gap-16rpx rounded-24rpx bg-white p-22rpx shadow-main">
|
||||
<view v-for="(item, index) in state.rechargeList" :key="String(item.id)"
|
||||
class="min-h-104rpx flex-col-center rounded-18rpx border-1rpx border-solid px-8rpx py-14rpx active:opacity-80"
|
||||
:class="state.currentRecharge === index ? 'border-blue bg-#eef8ff' : 'border-#dce3ea bg-white'"
|
||||
@tap="state.currentRecharge = index">
|
||||
<view class="text-29rpx font-800"
|
||||
:class="state.currentRecharge === index ? 'color-#0094ff' : 'color-#333'">
|
||||
¥{{ hooks.useMoney(item.payPrice) }}
|
||||
</view>
|
||||
<view v-if="item.bonusPrice > 0" class="mt-3rpx text-19rpx color-#f97316">赠 ¥{{
|
||||
hooks.useMoney(item.bonusPrice) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="prefix_title mt-34rpx">选择支付方式</view>
|
||||
<view class="mt-18rpx rounded-24rpx bg-white px-20rpx py-8rpx shadow-main">
|
||||
<view v-for="option in state.payType" :key="option.type"
|
||||
class="min-h-96rpx flex items-center rounded-18rpx px-10rpx py-14rpx active:bg-#f6fbff"
|
||||
@tap="!option.disabled && (state.currentPayType = option.type)">
|
||||
<image class="h-52rpx w-52rpx shrink-0 rounded-10rpx" :src="option.logoDir"
|
||||
mode="aspectFill" />
|
||||
<view class="ml-18rpx min-w-0 flex-1">
|
||||
<view class="sub-text-26 font-700">{{ option.text }}</view>
|
||||
<view class="second-text-20 mt-3rpx">{{ option.desc }}</view>
|
||||
</view>
|
||||
<tm-radio v-model="state.currentPayType" :disabled="option.disabled" :value="option.type" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="mt-48rpx h-92rpx flex-row-center rounded-full border-1rpx border-solid text-30rpx font-800"
|
||||
:class="state.submitting || environmentMessage ? 'recharge-submit--disabled' : 'recharge-submit--active active:opacity-80'"
|
||||
@tap="doPay">
|
||||
<tm-icon v-if="state.submitting" class="mr-10rpx" name="loader-4-line" :size="27"
|
||||
color="#ffffff" spin />
|
||||
{{ state.submitting ? "正在发起支付" : environmentMessage ? "请在 App 内完成充值" : `确认充值
|
||||
¥${hooks.useMoney(state.rechargeList[state.currentRecharge]?.payPrice || 0)}` }}
|
||||
</view>
|
||||
<view class="second-text-20 mt-14rpx text-center">支付金额及到账饭卡以服务端最终校验结果为准</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="mt-28rpx rounded-24rpx bg-white px-34rpx py-70rpx shadow-main">
|
||||
<view class="flex-col-center text-center">
|
||||
<tm-icon name="error-warning-line" :size="52" color="#f97316" />
|
||||
<view class="main-text-28 mt-16rpx">暂时无法充值</view>
|
||||
<view class="second-text-22 mt-8rpx max-w-560rpx leading-36rpx">{{ emptyMessage }}</view>
|
||||
<view
|
||||
class="mt-24rpx h-88rpx min-w-230rpx flex-row-center rounded-full border-1rpx border-blue border-solid px-30rpx color-#0094ff text-25rpx font-700 active:bg-#eef8ff"
|
||||
@tap="loadPageData">
|
||||
重新加载
|
||||
</view>
|
||||
<service-text v-if="!isGuardianMode" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view v-else class="wallet-recharge-empty bg-white rounded-20rpx shadow-main m-t-35rpx">
|
||||
<view class="wallet-recharge-empty__icon">!</view>
|
||||
<view class="sub-text-26 color-#333">该商户暂不提供充值功能,请联系客服</view>
|
||||
<service-text />
|
||||
</view>
|
||||
|
||||
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recharge-submit--active {
|
||||
border-color: #0094ff;
|
||||
background: #0094ff;
|
||||
color: #fff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 148, 255, 0.22);
|
||||
}
|
||||
|
||||
// H5 不具备原生支付能力,禁用态仍保留明确配色,避免白底白字导致按钮不可识别。
|
||||
.recharge-submit--disabled {
|
||||
border-color: #b7dcf5;
|
||||
background: #dceffd;
|
||||
color: #2f719d;
|
||||
box-shadow: 0 8rpx 20rpx rgba(0, 148, 255, 0.1);
|
||||
}
|
||||
|
||||
.recharge-record-tag {
|
||||
position: absolute;
|
||||
top: -10rpx;
|
||||
@@ -296,50 +420,4 @@ const toRechargeRecord = () => {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.wallet-recharge-empty {
|
||||
padding: 70rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wallet-recharge-empty__icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border-radius: 9999rpx;
|
||||
background: #e9f5fe;
|
||||
color: #0094ff;
|
||||
font-size: 42rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.recharge_item_active {
|
||||
background: #e9f5fe;
|
||||
border: 1px solid #e9f5fe !important;
|
||||
|
||||
&>text {
|
||||
color: #0094ff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.recharge {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-gap: 20rpx;
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
|
||||
&_item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,7 @@ export type GetUniqueCodeDto = {
|
||||
}
|
||||
|
||||
export type AppPlusPayDto = {
|
||||
bizType: "MINI_APP_WALLET_RECHARGE"
|
||||
bizType: "MINI_APP_WALLET_RECHARGE" | "GUARDIAN_WARD_WALLET_RECHARGE"
|
||||
bizId: string
|
||||
miniPlatformType: string
|
||||
amount?: number
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
import request from "@/sheep/utils/request"
|
||||
|
||||
export type GuardianWardRechargeItem = {
|
||||
wardUserId: string | number
|
||||
wardName: string
|
||||
wardAvatar?: string
|
||||
walletTypeName?: string
|
||||
balance: number
|
||||
maskedCardNumber?: string
|
||||
cardStatus?: string
|
||||
cardStatusName?: string
|
||||
rechargeEnabled: boolean
|
||||
disabledReason?: string
|
||||
/** 审核通过且当前可用的人脸数量,由被监护人列表接口聚合返回。 */
|
||||
availableFaceCount?: number
|
||||
/** 当前仍在审核中的人脸数量,由被监护人列表接口聚合返回。 */
|
||||
pendingFaceCount?: number
|
||||
/** 今天及以后已支付但尚未核销的预约订单数量,由被监护人列表接口聚合返回。 */
|
||||
pendingReservationCount?: number
|
||||
}
|
||||
|
||||
export type GuardianWardRechargePackage = {
|
||||
id: string | number
|
||||
name?: string
|
||||
payPrice: number
|
||||
bonusPrice: number
|
||||
totalPrice: number
|
||||
}
|
||||
|
||||
export type GuardianWardRechargeContext = {
|
||||
ward: GuardianWardRechargeItem
|
||||
packages: GuardianWardRechargePackage[]
|
||||
platformTypes: string[]
|
||||
}
|
||||
|
||||
export type GuardianWardRechargeCreateResult = {
|
||||
rechargeId: string | number
|
||||
payPrice: number
|
||||
wardName: string
|
||||
}
|
||||
|
||||
export type GuardianWardRechargeResult = {
|
||||
rechargeId: string | number
|
||||
wardName: string
|
||||
payPrice: number
|
||||
paid: boolean
|
||||
paidTime?: string
|
||||
balance: number
|
||||
}
|
||||
|
||||
export default {
|
||||
// 根据短码查询加密信息
|
||||
getByShortCode: (params: any): Promise<any> =>
|
||||
@@ -67,6 +115,33 @@ export default {
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
/** 查询当前登录用户全部有效被监护人的脱敏饭卡摘要。 */
|
||||
guardianWardList: (): Promise<any> =>
|
||||
request({
|
||||
url: "/wallet/wallet-recharge/app/guardian-ward/list",
|
||||
method: "GET",
|
||||
}),
|
||||
/** 查询目标饭卡、有效套餐和目标食堂支付平台。 */
|
||||
guardianWardRechargeContext: (params: { wardUserId: string | number }): Promise<any> =>
|
||||
request({
|
||||
url: "/wallet/wallet-recharge/app/guardian-ward/context",
|
||||
method: "GET",
|
||||
params,
|
||||
}),
|
||||
/** 创建绑定目标饭卡的待支付充值记录。 */
|
||||
createGuardianWardRecharge: (data: { wardUserId: string | number; packageId: string | number }): Promise<any> =>
|
||||
request({
|
||||
url: "/wallet/wallet-recharge/app/guardian-ward/create",
|
||||
method: "POST",
|
||||
data,
|
||||
}),
|
||||
/** 查询第三方支付成功后是否已经进入目标饭卡。 */
|
||||
guardianWardRechargeResult: (params: { rechargeId: string | number }): Promise<any> =>
|
||||
request({
|
||||
url: "/wallet/wallet-recharge/app/guardian-ward/result",
|
||||
method: "GET",
|
||||
params,
|
||||
}),
|
||||
/**
|
||||
* 是够能够使用智能柜
|
||||
*/
|
||||
|
||||
@@ -104,6 +104,7 @@ const thirdPayOrderStatusEnum = {
|
||||
|
||||
const thirdPayOrderBizTypeEnum = {
|
||||
"10": "小程序用户钱包充值",
|
||||
"40": "监护人代被监护人饭卡充值",
|
||||
}
|
||||
|
||||
const appPlusPlatformTypeEnum = {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 265 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Reference in new issue
Block a user