From 3e710e7ce3ca4e5a3a905a05f181f2325bb2ed2d Mon Sep 17 00:00:00 2001
From: dzc <2388224167@qq.com>
Date: Tue, 4 Aug 2026 17:10:59 +0800
Subject: [PATCH] =?UTF-8?q?feat:=E5=A4=9A=E9=A4=90=E6=AE=B5=E8=AE=A2?=
=?UTF-8?q?=E9=A4=90=E6=A8=A1=E5=BC=8F=20feat:=E9=AA=A8=E6=9E=B6=E5=B1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env | 8 +-
src/manifest.json | 4 +-
src/pages.json | 14 +
src/pages/merchant/rserve-total.vue | 94 +-
src/pages/order/components/confirm-tabs.vue | 2 +-
.../table-reservation-time-select.vue | 26 +-
src/pages/order/confirm.vue | 8 +-
src/pages/order/daily-menu-confirm.vue | 1027 +++++++++++++++++
src/pages/order/guardian-ward-menu.vue | 512 ++++++++
src/pages/pay/pay-complete.vue | 20 +-
src/pages/tabbar/canteen.vue | 66 +-
src/pages/tabbar/order.vue | 40 +-
.../guardian-ward-action-drawer-skeleton.vue | 93 ++
.../guardian-ward-action-drawer.vue | 103 +-
.../guardian-ward-list-skeleton.vue | 54 +
src/pages/user/relation/list.vue | 111 +-
src/sheep/api/order.ts | 18 +
src/sheep/api/pay.ts | 8 +
.../base-payment-drawer.vue | 620 ++++++++++
.../base/base-skeleton/base-skeleton.vue | 119 ++
.../order/daily-menu-cart-drawer.vue | 383 ++++++
.../components/order/daily-menu-food-card.vue | 197 ++++
.../order/daily-menu-food-skeleton.vue | 68 ++
.../components/order/daily-menu-types.ts | 45 +
.../components/order/order-list-skeleton.vue | 47 +
src/sheep/components/pay/pay-card.vue | 138 ++-
.../components/user/select-reserve-diners.vue | 77 +-
src/sheep/constant/service-enum.ts | 26 +
28 files changed, 3673 insertions(+), 255 deletions(-)
create mode 100644 src/pages/order/daily-menu-confirm.vue
create mode 100644 src/pages/order/guardian-ward-menu.vue
create mode 100644 src/pages/user/relation/components/guardian-ward-action-drawer-skeleton.vue
create mode 100644 src/pages/user/relation/components/guardian-ward-list-skeleton.vue
create mode 100644 src/sheep/components/base/base-payment-drawer/base-payment-drawer.vue
create mode 100644 src/sheep/components/base/base-skeleton/base-skeleton.vue
create mode 100644 src/sheep/components/order/daily-menu-cart-drawer.vue
create mode 100644 src/sheep/components/order/daily-menu-food-card.vue
create mode 100644 src/sheep/components/order/daily-menu-food-skeleton.vue
create mode 100644 src/sheep/components/order/daily-menu-types.ts
create mode 100644 src/sheep/components/order/order-list-skeleton.vue
diff --git a/.env b/.env
index 7612733..5d55bdf 100644
--- a/.env
+++ b/.env
@@ -33,12 +33,12 @@ VITE_OAUTH2_SOCIAL_CLIENT='social:social'
# 是否开启websocket 消息接受,
VITE_WEBSOCKET_ENABLE = true
# --------------------------- start 开发环境 start ----------------------------------
-VITE_APP_DEV_SERVICE = https://www.zianxin.com/api
-# VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc
+# VITE_APP_DEV_SERVICE = https://www.zianxin.com/api
+VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc
# VITE_APP_DEV_SERVICE = http://127.0.0.1:9999
-VITE_IMAGE_DEV_PATH = https://www.zianxin.com/api
-# VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc
+# VITE_IMAGE_DEV_PATH = https://www.zianxin.com/api
+VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc
# VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
# ----------------------------- end 开发环境 end ------------------------------------
diff --git a/src/manifest.json b/src/manifest.json
index 3bdc785..8f21990 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -2,8 +2,8 @@
"name" : "资安信",
"appid" : "__UNI__1BFE88F",
"description" : "资安信后勤管理系统",
- "versionName" : "1.5.06",
- "versionCode" : 1506,
+ "versionName" : "1.5.08",
+ "versionCode" : 1508,
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
diff --git a/src/pages.json b/src/pages.json
index 5a42dd1..c612e9e 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -265,6 +265,20 @@
"disableScroll": true
}
},
+ {
+ "path": "daily-menu-confirm",
+ "style": {
+ "navigationBarTitleText": "订餐",
+ "disableScroll": true
+ }
+ },
+ {
+ "path": "guardian-ward-menu",
+ "style": {
+ "navigationBarTitleText": "被监护人菜谱",
+ "disableScroll": true
+ }
+ },
{
"path": "confirm",
"style": {
diff --git a/src/pages/merchant/rserve-total.vue b/src/pages/merchant/rserve-total.vue
index be9f8cb..d8d574d 100644
--- a/src/pages/merchant/rserve-total.vue
+++ b/src/pages/merchant/rserve-total.vue
@@ -83,13 +83,13 @@
{{ dateGroup.payUserCount
- }}人次
+ }}人次
{{ dateGroup.timeCount
- }}次数
+ }}次数
{{ dateGroup.payCount
- }}份数
+ }}份数
{{
- formatPrice(dateGroup.totalPrice) }}金额
+ formatPrice(dateGroup.totalPrice) }}金额
@@ -117,7 +117,7 @@
{{ numberValue(row.payUserCount)
- }}
+ }}
人数
@@ -141,53 +141,49 @@
-
-
-
- 快捷日期
-
-
- {{ preset.label }}
-
-
-
- 自定义日期
-
-
- {{ filterDraft.startDate }}
-
- 至
-
- {{ filterDraft.endDate }}
-
-
- 单次最多查询 {{ MAX_RANGE_DAYS }} 天
-
- 用餐时间段
-
-
-
- {{ item.label }}
-
+
+
+
+ 快捷日期
+
+
+ {{ preset.label }}
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/pages/order/guardian-ward-menu.vue b/src/pages/order/guardian-ward-menu.vue
new file mode 100644
index 0000000..21193bc
--- /dev/null
+++ b/src/pages/order/guardian-ward-menu.vue
@@ -0,0 +1,512 @@
+
+
+
+
+
+
+
+
+ {{ state.wardName }}
+ 未来未核销预约菜谱
+
+
+
+
+
+
+
+
+ 菜谱概览
+ {{ state.loading ? "更新中" : `共 ${dateGroups.length} 天` }}
+
+
+
+
+
+
+ {{ item.value }}
+
+ {{ item.label }}
+
+
+
+
+
+
+ 预约菜谱
+ {{ state.orders.length }} 笔
+
+
+
+
+ 正在加载菜谱
+
+
+
+
+
+
+ 菜谱加载失败
+ 请检查网络或监护关系后重新加载
+ 重新加载
+
+
+
+
+
+
+ 暂无预约菜谱
+ 该被监护人目前没有今天及以后的未核销预约
+ 刷新看看
+
+
+
+
+
+
+
+
+
+
+ {{ formatDateTitle(dateGroup.date) }}
+ {{ formatDateSubTitle(dateGroup.date) }}
+
+
+
+ {{ dateGroup.orders.length }} 餐
+
+
+
+
+ {{ dateGroup.orders.length }}
+ 预约
+
+
+ {{ dateGroup.foodCount }}
+ 份数
+
+
+ ¥{{ hooks.useMoney(dateGroup.totalPrice) }}
+ 金额
+
+
+
+
+
+
+
+
+
+ {{ mealTitle(order) }}
+
+
+ {{ order.merchantName || "食堂商户" }}
+
+
+
+
+
+
+
+ {{ product.productName || "未命名菜品" }}
+ ¥{{ hooks.useMoney(product.productPrice || 0) }} / 份
+
+
+ ×{{ normalizedCount(product.productCount) }}
+ ¥{{ hooks.useMoney(productTotal(product)) }}
+
+
+
+
+ {{ order.productDesc || "暂无菜品明细" }}
+
+
+
+ {{ mealTime(order) }} · 共 {{ orderFoodCount(order) }} 份
+ 合计 ¥{{ hooks.useMoney(order.payPrice || 0) }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/pay/pay-complete.vue b/src/pages/pay/pay-complete.vue
index 29ea4c1..c452242 100644
--- a/src/pages/pay/pay-complete.vue
+++ b/src/pages/pay/pay-complete.vue
@@ -28,6 +28,10 @@ const state = reactive({
backBtnType: 'back',
orderDetail: {},
failMessage: "",
+ /** 批量订餐接口返回的整批实付金额,单位为分;其他支付场景保持为空并展示单笔订单金额。 */
+ batchTotalPayPrice: null as number | null,
+ /** 本次批量支付创建的订单数量,单订单入口保持为 1。 */
+ batchOrderCount: 1,
})
const titleText = computed(() => {
@@ -37,7 +41,11 @@ const titleText = computed(() => {
const descText = computed(() => {
if (state.phase === "loading") return "正在查询订单状态,请稍候..."
- if (state.phase === "success") return "支付金额已到账,可前往钱包查看最新余额"
+ if (state.phase === "success") {
+ return state.batchOrderCount > 1
+ ? `已完成 ${state.batchOrderCount} 笔订餐订单,可前往订单列表查看`
+ : "支付金额已到账,可前往钱包查看最新余额"
+ }
return state.failMessage || "未查询到本次支付结果,请稍后在支付记录中确认"
})
@@ -58,7 +66,7 @@ const currentAnimationSize = computed(() => {
return { width: "280rpx", height: "280rpx" }
})
-const payOrderAmount = computed(() => Number(state.orderDetail?.payPrice || 0))
+const payOrderAmount = computed(() => state.batchTotalPayPrice ?? Number(state.orderDetail?.payPrice || 0))
const mchNameText = computed(() => state.orderDetail?.merchantName || "--")
@@ -110,6 +118,14 @@ const getOrder = async (polling = false) => {
}
onLoad((options: any) => {
+ const batchTotalPayPrice = Number(options.totalPayPrice)
+ if (options.totalPayPrice !== undefined && Number.isFinite(batchTotalPayPrice) && batchTotalPayPrice >= 0) {
+ state.batchTotalPayPrice = batchTotalPayPrice
+ }
+ const batchOrderCount = Number(options.orderCount)
+ if (Number.isInteger(batchOrderCount) && batchOrderCount > 0) {
+ state.batchOrderCount = batchOrderCount
+ }
if (options.orderId) {
state.orderId = options.orderId || ""
state.mchId = options.mchId || ""
diff --git a/src/pages/tabbar/canteen.vue b/src/pages/tabbar/canteen.vue
index 1410d58..350c0c7 100644
--- a/src/pages/tabbar/canteen.vue
+++ b/src/pages/tabbar/canteen.vue
@@ -49,7 +49,12 @@ const query = (page: number, pagesize: number) => {
const toDetail = async (v: any) => {
const isLogin = await hooks.useCheckLogin()
if (isLogin) {
- router.jump("/pages/canteen/detail", { id: v.id })
+ const mchTableReservationModeCodeEnum = constant.serviceEnum.mchTableReservationModeCodeEnum
+ if (v.types == '1' && v.tableReservationConf.mode === mchTableReservationModeCodeEnum.DAILY_MENU) {
+ router.jump("/pages/order/daily-menu-confirm", { merchantId: v.id })
+ } else {
+ router.jump("/pages/canteen/detail", { id: v.id })
+ }
}
}
@@ -88,40 +93,17 @@ const inputClear = () => {
-
+
-
+
搜索
@@ -131,26 +113,16 @@ const inputClear = () => {
-
-
+
+
{{ v.name }}
- {{ line }}
+ {{ line
+ }}
{{ v.addressDes }}
diff --git a/src/pages/tabbar/order.vue b/src/pages/tabbar/order.vue
index cb79c1c..b39ae7c 100644
--- a/src/pages/tabbar/order.vue
+++ b/src/pages/tabbar/order.vue
@@ -9,6 +9,7 @@ import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import AppHotUpdate from "@/sheep/components/app-version/app-hot-update.vue"
import OrderCar from "@/sheep/components/order/order-car.vue"
import OrderConfirmReceipt from "@/sheep/components/order/order-confirm-receipt.vue"
+import OrderListSkeleton from "@/sheep/components/order/order-list-skeleton.vue"
const appHotUpdateRef = ref(null)
const orderConfirmReceiptRef = ref(null)
@@ -93,10 +94,25 @@ const pageHook = hooks.usePage(
null,
"data.records",
)
-const query = (page: number, pagesize: number) => {
- const isLogin = hooks.useStaticCheckLogin()
- if (!isLogin) return
- pageHook.startPage(page, pagesize, false, state.pa)
+/** 当前是否正在加载第一页订单,用于刷新期间以结构占位替换旧列表。 */
+const firstPageLoading = ref(true)
+/** 最新第一页请求序号,防止快速切换标签时旧请求提前关闭新请求的骨架。 */
+let firstPageRequestSequence = 0
+
+/**
+ * 查询订单分页数据,仅第一页控制整页骨架,后续分页继续沿用 z-paging 的加载状态。
+ */
+const query = async (page: number, pagesize: number) => {
+ const isFirstPage = page === 1
+ const requestSequence = isFirstPage ? ++firstPageRequestSequence : firstPageRequestSequence
+ if (isFirstPage) firstPageLoading.value = true
+ try {
+ const isLogin = hooks.useStaticCheckLogin()
+ if (!isLogin) return
+ await pageHook.startPage(page, pagesize, false, state.pa)
+ } finally {
+ if (isFirstPage && requestSequence === firstPageRequestSequence) firstPageLoading.value = false
+ }
}
@@ -104,7 +120,8 @@ const query = (page: number, pagesize: number) => {
+ :auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false"
+ :hide-empty-view="firstPageLoading" @scroll="pageHook.scroll">
@@ -123,11 +140,14 @@ const query = (page: number, pagesize: number) => {
-
-
-
+
+
+
+
+
+
diff --git a/src/pages/user/relation/components/guardian-ward-action-drawer-skeleton.vue b/src/pages/user/relation/components/guardian-ward-action-drawer-skeleton.vue
new file mode 100644
index 0000000..c7ec9e6
--- /dev/null
+++ b/src/pages/user/relation/components/guardian-ward-action-drawer-skeleton.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/user/relation/components/guardian-ward-action-drawer.vue b/src/pages/user/relation/components/guardian-ward-action-drawer.vue
index 53e2db2..56df37f 100644
--- a/src/pages/user/relation/components/guardian-ward-action-drawer.vue
+++ b/src/pages/user/relation/components/guardian-ward-action-drawer.vue
@@ -1,13 +1,26 @@
-
-
+
+
+
-
+
-
+
@@ -26,19 +39,16 @@
饭卡余额
- ¥{{ hooks.useMoney(ward.balance || 0) }}
-
+ ¥{{ hooks.useMoney(ward.balance || 0) }}
脱敏卡号
- {{ ward.maskedCardNumber || "暂无饭卡" }}
-
+ {{ ward.maskedCardNumber || "暂无饭卡" }}
-
+
@@ -46,8 +56,11 @@
人脸情况
-
+
{{ metric.label }}
{{ metric.value }}
@@ -71,8 +84,7 @@
-
+
{{ ward.disabledReason || "当前饭卡暂不可充值" }}
@@ -86,26 +98,25 @@
业务办理
选择一项服务,返回后列表自动刷新
- 3 项
+ {{ actions.length }} 项
-
-
-
+ @tap="handleAction(action)"
+ >
+
+
- {{
- action.title }}
+ {{ action.title }}
{{ action.description }}
-
+
@@ -123,21 +134,35 @@ import { computed } from "vue"
import { hooks, router } from "@@/index"
import { filePath } from "@@/utils/file"
import type { GuardianWardRechargeItem } from "@@/api/wallet"
+import GuardianWardActionDrawerSkeleton from "./guardian-ward-action-drawer-skeleton.vue"
type WardAction = {
- key: "face-upload" | "face-records" | "recharge"
+ /** 操作项唯一标识,仅用于前端分发对应页面跳转。 */
+ key: "menu" | "face-upload" | "face-records" | "recharge"
+ /** 操作项主标题。 */
title: string
+ /** 操作项补充说明,禁用时同时用于解释原因。 */
description: string
+ /** TMUI Remix 图标名称。 */
icon: string
+ /** 图标容器背景色。 */
iconBg: string
+ /** 图标前景色。 */
iconColor: string
+ /** 可操作状态下的卡片背景色。 */
background: string
+ /** 可操作状态下的卡片边框色。 */
borderColor: string
+ /** 是否禁止触发当前操作。 */
disabled: boolean
}
const props = defineProps<{
+ /** 右侧操作抽屉是否显示。 */
modelValue: boolean
+ /** 是否正在刷新被监护人数据;加载期间显示结构占位并阻止空态闪烁。 */
+ loading?: boolean
+ /** 当前选中的被监护人饭卡与预约摘要。 */
ward?: GuardianWardRechargeItem
}>()
@@ -177,9 +202,23 @@ const faceMetrics = computed(() => [
])
/**
- * 操作项通过配置统一渲染,避免三类业务入口复制结构和交互状态。
+ * 操作项通过配置统一渲染,避免各类业务入口复制结构和交互状态。
*/
const actions = computed(() => [
+ {
+ key: "menu",
+ title: "查看菜谱",
+ description:
+ Number(props.ward?.pendingReservationCount || 0) > 0
+ ? `查看 ${props.ward?.pendingReservationCount} 笔待核销预约的日期、餐段与菜品`
+ : "查看未来未核销预约的日期、餐段与菜品",
+ icon: "bowl-line",
+ iconBg: "#fff1e8",
+ iconColor: "#c2410c",
+ background: "#fffaf5",
+ borderColor: "#fed7aa",
+ disabled: false,
+ },
{
key: "face-upload",
title: "上传人脸",
@@ -224,6 +263,10 @@ const handleAction = (action: WardAction) => {
const wardName = encodeURIComponent(props.ward.wardName || "被监护人")
show.value = false
setTimeout(() => {
+ if (action.key === "menu") {
+ router.jump("/pages/order/guardian-ward-menu", { wardUserId, wardName })
+ return
+ }
if (action.key === "face-upload") {
router.jump("/pages/human-face/guardian-ward-apply", { wardUserId, wardName })
return
diff --git a/src/pages/user/relation/components/guardian-ward-list-skeleton.vue b/src/pages/user/relation/components/guardian-ward-list-skeleton.vue
new file mode 100644
index 0000000..9e8de0e
--- /dev/null
+++ b/src/pages/user/relation/components/guardian-ward-list-skeleton.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/user/relation/list.vue b/src/pages/user/relation/list.vue
index 104a7fc..38bac12 100644
--- a/src/pages/user/relation/list.vue
+++ b/src/pages/user/relation/list.vue
@@ -1,61 +1,89 @@
-
-
+
+
被监护人服务台
-
+
{{ totalCount }} 人
+
饭卡、人脸与预约状态集中查看
-
+
人脸情况
-
+
{{ item.label }}
- {{
- item.value }}
+ {{ item.value }}
+
-
+
预约订单
待核销
- {{
- pendingReservationTotal }}
+
+ {{ pendingReservationTotal }}
+
+
-
+
-
-
+
+
+
+ @tap="openActions(ward)"
+ >
@@ -63,8 +91,7 @@
{{ ward.wardName || "被监护人" }}
-
+
{{ ward.cardStatusName || "状态异常" }}
@@ -79,8 +106,7 @@
-
+
@@ -88,8 +114,11 @@
人脸情况
-
+
{{ metric.label }}
{{ metric.value }}
@@ -112,12 +141,10 @@
-
+
饭卡余额
- ¥{{ hooks.useMoney(ward.balance || 0) }}
-
+ ¥{{ hooks.useMoney(ward.balance || 0) }}
可充值
@@ -128,24 +155,24 @@
-
+
暂无有效被监护人
这里只展示当前登录用户直接且有效的监护关系
+ @tap="openRelationTree"
+ >
查看关系图谱
-
-
+
+
+
diff --git a/src/sheep/components/base/base-skeleton/base-skeleton.vue b/src/sheep/components/base/base-skeleton/base-skeleton.vue
new file mode 100644
index 0000000..a3cdc84
--- /dev/null
+++ b/src/sheep/components/base/base-skeleton/base-skeleton.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
diff --git a/src/sheep/components/order/daily-menu-cart-drawer.vue b/src/sheep/components/order/daily-menu-cart-drawer.vue
new file mode 100644
index 0000000..49e66ea
--- /dev/null
+++ b/src/sheep/components/order/daily-menu-cart-drawer.vue
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+ 购物车还是空的
+ 选择日期和餐段后添加菜品
+
+
+
+
+ {{ props.draftGroups.length }} 个餐段 · {{ props.totalFoodCount }} 份
+ 清空
+
+
+
+
+
+
+
+
+ {{ item.name || "未命名菜品" }}
+ ¥{{ hooks.useMoney(item.price || 0) }}
+
+
+
+
+
+
+
+ {{ item.quantity }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/sheep/components/order/daily-menu-food-card.vue b/src/sheep/components/order/daily-menu-food-card.vue
new file mode 100644
index 0000000..6fe3da3
--- /dev/null
+++ b/src/sheep/components/order/daily-menu-food-card.vue
@@ -0,0 +1,197 @@
+
+
+
+
+
+ {{ displayName }}
+ 已售罄
+
+
+ {{ props.ignoreStock ? "不限库存" : `库存 ${stock}` }}
+ |
+ 独立装餐
+
+
+ ¥
+ {{ hooks.useMoney(props.item.price || 0) }}
+ /份
+
+
+
+
+
+
+
+
+ {{ props.quantity }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/sheep/components/order/daily-menu-food-skeleton.vue b/src/sheep/components/order/daily-menu-food-skeleton.vue
new file mode 100644
index 0000000..e082b4d
--- /dev/null
+++ b/src/sheep/components/order/daily-menu-food-skeleton.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
diff --git a/src/sheep/components/order/daily-menu-types.ts b/src/sheep/components/order/daily-menu-types.ts
new file mode 100644
index 0000000..77d3897
--- /dev/null
+++ b/src/sheep/components/order/daily-menu-types.ts
@@ -0,0 +1,45 @@
+/** 每日菜谱接口返回的可选菜品。 */
+export type DailyMenuItem = {
+ /** 每日菜谱记录 ID,仅用于列表渲染。 */
+ id?: string | number
+ /** 菜品 SPU ID,是购物车与库存校验使用的业务标识。 */
+ spuId?: string | number
+ /** 菜品展示名称,接口为空时由界面显示兜底名称。 */
+ name?: string
+ /** 菜品单价,单位为分。 */
+ price?: number
+ /** 菜品图片相对路径或完整地址。 */
+ picUrl?: string
+ /** 当前可售库存,数量上限以本次查询结果为准。 */
+ stock?: number
+ /** 菜谱所属餐段编码,来源于每日菜谱接口。 */
+ hourType?: string | number
+}
+
+/** 购物车内某一日期餐段下的菜品快照。 */
+export type DraftFoodItem = DailyMenuItem & {
+ /** 用户为当前日期餐段选择的份数。 */
+ quantity: number
+}
+
+/** 按日期和餐段隔离的订餐草稿,切换筛选时仍保留其他餐段的选择。 */
+export type ReservationDraft = {
+ /** 日期与餐段拼成的页面内唯一键,不作为服务端业务编码。 */
+ key: string
+ /** 批量上传后端返回的父购物车 ID;重新上传时由最新响应覆盖。 */
+ carParentId?: string
+ /** 预约使用日期,格式为 yyyy-MM-dd。 */
+ apptDate: string
+ /** 预约餐段编码,沿用后端 hourType 协议值。 */
+ hourType: string
+ /** 餐段展示名称,来源于预约选项。 */
+ mealTitle: string
+ /** 动态预约 ID;周期预约时为空。 */
+ apptId: string
+ /** 周期预约规则 ID;动态预约时可能为空。 */
+ apptRuleId: string
+ /** 预约时间类型,提交时沿用预约列表返回值。 */
+ apptTimeType: string
+ /** 当前日期餐段选择的菜品列表。 */
+ items: DraftFoodItem[]
+}
diff --git a/src/sheep/components/order/order-list-skeleton.vue b/src/sheep/components/order/order-list-skeleton.vue
new file mode 100644
index 0000000..7afe060
--- /dev/null
+++ b/src/sheep/components/order/order-list-skeleton.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/sheep/components/pay/pay-card.vue b/src/sheep/components/pay/pay-card.vue
index fb57acd..565cac0 100644
--- a/src/sheep/components/pay/pay-card.vue
+++ b/src/sheep/components/pay/pay-card.vue
@@ -1,10 +1,30 @@
-
+
选择支付方式
-
+
+
+
+
+
+
+
+
+
+
+
+ !
+ 支付方式加载失败
+
+ 重新加载
+
+
+
@@ -35,7 +55,7 @@
-
+
@@ -61,8 +81,8 @@
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-t-60rpx sub-text-30 color-white!" @tap="doPay">
确认充值
-
-
+
@@ -98,25 +118,41 @@ import ServiceText from "@@/components/common/service-text.vue"
const emit = defineEmits(["selectPayType"])
export type Props = {
+ /** 支付卡片内“选择钱包”抽屉的层级,嵌入其他 Drawer 时由外层传入更高值。 */
+ walletDrawerZIndex?: string | number
visibleTitle?: boolean
visiblePayBtn?: boolean
visibleWalletPay?: boolean
+ /** 允许展示的平台支付类型编码;空数组表示展示接口返回的全部方式。 */
+ allowedPayTypes?: string[]
}
const props = withDefaults(defineProps(), {
+ walletDrawerZIndex: 1100,
visibleTitle: true,
visiblePayBtn: true,
visibleWalletPay: true,
+ allowedPayTypes: () => [],
+})
+
+/** 根据业务组件声明的能力过滤支付方式,避免展示当前下单接口无法处理的渠道。 */
+const visiblePayTypes = computed(() => {
+ if (!props.allowedPayTypes.length) return state.payType
+ return state.payType.filter((payType: any) => props.allowedPayTypes.includes(String(payType?.type || "")))
})
const isPay = computed(() => {
- return state.payType && state.payType.length > 0
+ return visiblePayTypes.value.length > 0
})
const walletPayDetail = computed(() => {
- return state.payType.find(v => v.type == "30") ?? null
+ return visiblePayTypes.value.find((v: any) => v.type == "30") ?? null
})
const state = reactive({
+ /** 支付方式配置是否正在加载,首次进入时用于控制等高骨架占位。 */
+ payTypeLoading: true,
+ /** 支付方式配置最近一次是否加载失败,用于提供明确的重试入口。 */
+ payTypeLoadFailed: false,
visibleSelectWallet: false,
walletList: [],
currentSelectWallet: null,
@@ -148,6 +184,7 @@ const getAllWalletList = async () => {
const { data, code }: any = await walletApi.all()
if (code != 0) throw new Error(data?.msg || "获取用户钱包列表失败")
const records = data?.records ?? []
+ if (!records.length) return
state.walletList = records
state.payType.push({
logoDir: "/static/logo/zax.png",
@@ -166,42 +203,53 @@ const getAllWalletList = async () => {
}
}
+/** 查询用户可用支付方式,并确保成功、空数据和异常路径都能结束骨架状态。 */
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 == "30" && !typeList.includes("30")) {
- await getAllWalletList()
+ state.payTypeLoading = true
+ state.payTypeLoadFailed = false
+ try {
+ 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 == "30" && !typeList.includes("30")) {
+ await getAllWalletList()
+ }
+ 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"
+ },)
+ }
}
- 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 = visiblePayTypes.value[0]?.type
+ const currentPayTypeAllowed = visiblePayTypes.value.some((payType: any) => payType.type == state.currentPayType)
+ if (defaultPayType && !currentPayTypeAllowed) {
+ state.currentPayType = defaultPayType
}
}
- const defaultPayType = state.payType[0].type
- if (defaultPayType && !state.currentPayType) {
- state.currentPayType = defaultPayType
- }
+ } catch (error) {
+ state.payTypeLoadFailed = true
+ console.error("获取用户支付方式失败", error)
+ } finally {
+ state.payTypeLoading = false
}
}
@@ -341,6 +389,18 @@ const disposeAlipay = async (bizId: any) => {