feat:饭卡充值

This commit is contained in:
dzc committed 2026-07-28 18:30:02 +08:00
1 parent f30da78c80
commit 0b8dd47764
16 files changed
+1678 -958

No files matched your search

+8 -1
View File
@@ -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 @@
}
]
}
}
}
+24 -85
View File
@@ -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>
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">
+301
View File
@@ -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>
+7 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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
+75
View File
@@ -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,
}),
/**
* 是够能够使用智能柜
*/
+1
View File
@@ -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