diff --git a/src/pages.json b/src/pages.json
index 961139d..5a42dd1 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -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 @@
}
]
}
-}
\ No newline at end of file
+}
diff --git a/src/pages/admin/wallet/list.vue b/src/pages/admin/wallet/list.vue
index 22b5bba..afb0418 100644
--- a/src/pages/admin/wallet/list.vue
+++ b/src/pages/admin/wallet/list.vue
@@ -1,50 +1,23 @@
-
+
-
+
-
-
清空
@@ -58,8 +31,7 @@
-
@@ -80,7 +52,8 @@
饭卡类型
{{ row.walletTypeName || "-" }}
- {{ row.walletTypeUniqueCode }}
+ {{ row.walletTypeUniqueCode
+ }}
@@ -98,10 +71,7 @@
-
充值/扣款
@@ -109,30 +79,14 @@
-
+
-
-
{{ item.name || "-" }}
@@ -145,20 +99,9 @@
- (state.balanceShow = v)">
+ (state.balanceShow = v)">
@@ -182,15 +125,11 @@
变动类型
-
充值
-
扣款
diff --git a/src/pages/canteen/detail.vue b/src/pages/canteen/detail.vue
index 6cb635e..516f5cc 100644
--- a/src/pages/canteen/detail.vue
+++ b/src/pages/canteen/detail.vue
@@ -15,30 +15,29 @@ import ProductRepetitionAlert from "./components/product-repetition-alert.vue"
const productRepetitionAlertRef = ref()
const state = reactive({
- detailId: null,
- canteenDetail: {},
- takeOutDetail: {},
+ detailId: null,
+ canteenDetail: {},
+ takeOutDetail: {},
- foodList: [],
+ foodList: [],
- cartList: [],
- totPrice: 0,
- totCount: 0,
+ cartList: [],
+ totPrice: 0,
+ totCount: 0,
- classifyList: [],
- currentClassifyIndex: 0,
+ classifyList: [],
+ currentClassifyIndex: 0,
- showCartDialog: false,
- bookingTimeList: [],
- selectedBookingTime: "",
+ showCartDialog: false,
+ bookingTimeList: [],
- // 必选提示 & 定位
- requiredHintMap: {} as Record,
- scrollIntoView: "",
+ // 必选提示 & 定位
+ requiredHintMap: {} as Record,
+ scrollIntoView: "",
- // 规则预加载:进入页面即请求
- choiceRulesVo: null as any,
- hasChoiceRules: false,
+ // 规则预加载:进入页面即请求
+ choiceRulesVo: null as any,
+ hasChoiceRules: false,
// 套餐组成弹层
showComboDialog: false,
@@ -46,183 +45,183 @@ const state = reactive({
})
onLoad((options: any) => {
- if (!isEmpty(options.id)) {
- state.detailId = options.id
- }
+ if (!isEmpty(options.id)) {
+ state.detailId = options.id
+ }
})
onShow(() => {
- // 首次进入若缺少 id,会导致商户相关接口 merchantId 为空而报错
- if (isEmpty(state.detailId)) return
- getDetail()
- getFoodCart()
- getClassify()
- loadChoiceRules()
+ // 首次进入若缺少 id,会导致商户相关接口 merchantId 为空而报错
+ if (isEmpty(state.detailId)) return
+ getDetail()
+ getFoodCart()
+ getClassify()
+ loadChoiceRules()
})
const loadChoiceRules = async () => {
- state.choiceRulesVo = null
- state.hasChoiceRules = false
- if (isEmpty(state.detailId)) return
- try {
- const res: any = await foodApi.getChoiceGroups({ merchantId: String(state.detailId), status: 0 })
- if (res?.code !== 0) return
- const vo = res?.data
- state.choiceRulesVo = vo
- updateChoiceTipStatus()
- } catch {
- // 不阻断正常点餐流程
- }
+ state.choiceRulesVo = null
+ state.hasChoiceRules = false
+ if (isEmpty(state.detailId)) return
+ try {
+ const res: any = await foodApi.getChoiceGroups({ merchantId: String(state.detailId), status: 0 })
+ if (res?.code !== 0) return
+ const vo = res?.data
+ state.choiceRulesVo = vo
+ updateChoiceTipStatus()
+ } catch {
+ // 不阻断正常点餐流程
+ }
}
const updateChoiceTipStatus = () => {
- const vo = state.choiceRulesVo
- if (!vo) {
- state.hasChoiceRules = false
- return
- }
- const groups = Array.isArray(vo?.groups) ? vo.groups : []
- const requiredSpus = Array.isArray(vo?.requiredSpus) ? vo.requiredSpus : []
+ const vo = state.choiceRulesVo
+ if (!vo) {
+ state.hasChoiceRules = false
+ return
+ }
+ const groups = Array.isArray(vo?.groups) ? vo.groups : []
+ const requiredSpus = Array.isArray(vo?.requiredSpus) ? vo.requiredSpus : []
- // 没有任何规则时不显示
- if (!groups.length && !requiredSpus.length) {
- state.hasChoiceRules = false
- return
- }
+ // 没有任何规则时不显示
+ if (!groups.length && !requiredSpus.length) {
+ state.hasChoiceRules = false
+ return
+ }
- const cartQtyMap = buildCartQtyMap()
- const cartSpuIds = new Set(Object.keys(cartQtyMap).filter(Boolean))
+ const cartQtyMap = buildCartQtyMap()
+ const cartSpuIds = new Set(Object.keys(cartQtyMap).filter(Boolean))
- // 1) requiredSpus:任何一个缺失就显示“未选必选品”
- for (const rs of requiredSpus) {
- const spuId = rs?.spuId != null ? String(rs.spuId) : ""
- if (spuId && !cartSpuIds.has(spuId)) {
- state.hasChoiceRules = true
- return
- }
- }
+ // 1) requiredSpus:任何一个缺失就显示“未选必选品”
+ for (const rs of requiredSpus) {
+ const spuId = rs?.spuId != null ? String(rs.spuId) : ""
+ if (spuId && !cartSpuIds.has(spuId)) {
+ state.hasChoiceRules = true
+ return
+ }
+ }
- // 2) required group:只要存在 required=true 且未达标就显示
- for (const g of groups) {
- if (!Boolean(g?.required)) continue
- const minSelect = Number(g?.minSelect || 0)
- const minTotalQty = Number(g?.minTotalQty || 0)
- const items = Array.isArray(g?.items) ? g.items : []
+ // 2) required group:只要存在 required=true 且未达标就显示
+ for (const g of groups) {
+ if (!Boolean(g?.required)) continue
+ const minSelect = Number(g?.minSelect || 0)
+ const minTotalQty = Number(g?.minTotalQty || 0)
+ const items = Array.isArray(g?.items) ? g.items : []
- let selectedKinds = 0
- let selectedTotalQty = 0
- for (const it of items) {
- const spuId = it?.spuId != null ? String(it.spuId) : ""
- if (!spuId) continue
- const qty = Number(cartQtyMap[spuId] || 0)
- if (qty > 0) {
- selectedKinds += 1
- selectedTotalQty += qty
- }
- }
+ let selectedKinds = 0
+ let selectedTotalQty = 0
+ for (const it of items) {
+ const spuId = it?.spuId != null ? String(it.spuId) : ""
+ if (!spuId) continue
+ const qty = Number(cartQtyMap[spuId] || 0)
+ if (qty > 0) {
+ selectedKinds += 1
+ selectedTotalQty += qty
+ }
+ }
- if (minSelect > 0 && selectedKinds < minSelect) {
- state.hasChoiceRules = true
- return
- }
- if (minTotalQty > 0 && selectedTotalQty < minTotalQty) {
- state.hasChoiceRules = true
- return
- }
- }
+ if (minSelect > 0 && selectedKinds < minSelect) {
+ state.hasChoiceRules = true
+ return
+ }
+ if (minTotalQty > 0 && selectedTotalQty < minTotalQty) {
+ state.hasChoiceRules = true
+ return
+ }
+ }
- // 规则存在但已满足,不显示“未选必选品”
- state.hasChoiceRules = false
+ // 规则存在但已满足,不显示“未选必选品”
+ state.hasChoiceRules = false
}
const classifyLoading = ref(false)
/** functionScope 在部分接口/端上可能是 JSON 字符串 */
const normalizeFunctionScope = (raw: unknown): Record => {
- if (!raw) return {}
- if (typeof raw === "string") {
- try {
- const parsed = JSON.parse(raw)
- return parsed && typeof parsed === "object" ? parsed : {}
- } catch {
- return {}
- }
- }
- if (typeof raw === "object") return { ...(raw as Record) }
- return {}
+ if (!raw) return {}
+ if (typeof raw === "string") {
+ try {
+ const parsed = JSON.parse(raw)
+ return parsed && typeof parsed === "object" ? parsed : {}
+ } catch {
+ return {}
+ }
+ }
+ if (typeof raw === "object") return { ...(raw as Record) }
+ return {}
}
const isTruthyFlag = (v: unknown) => v === true || v === 1 || v === "1" || v === "true"
// 获取食堂详情信息
const getDetail = async () => {
- if (isEmpty(state.detailId)) {
- uni.showToast({ title: "商户信息缺失,请从食堂列表进入", icon: "none" })
- return
- }
- uni.showLoading({ title: "加载中...", mask: true })
- const { code, data } = await merchantApi.detail({ id: state.detailId })
- if (code == 0 && data) {
- state.canteenDetail = {
- ...data,
- functionScope: normalizeFunctionScope(data.functionScope),
- }
- }
- await getTakeOutDetail()
- uni.hideLoading()
+ if (isEmpty(state.detailId)) {
+ uni.showToast({ title: "商户信息缺失,请从食堂列表进入", icon: "none" })
+ return
+ }
+ uni.showLoading({ title: "加载中...", mask: true })
+ const { code, data } = await merchantApi.detail({ id: state.detailId })
+ if (code == 0 && data) {
+ state.canteenDetail = {
+ ...data,
+ functionScope: normalizeFunctionScope(data.functionScope),
+ }
+ }
+ await getTakeOutDetail()
+ uni.hideLoading()
}
const getTakeOutDetail = async () => {
- if (isEmpty(state.detailId)) return
- const { data } = await merchantApi.getOrCreateTakeOut({ merchId: state.detailId })
- state.takeOutDetail = data
+ if (isEmpty(state.detailId)) return
+ const { data } = await merchantApi.getOrCreateTakeOut({ merchId: state.detailId })
+ state.takeOutDetail = data
}
// 获取购物车信息
const getFoodCart = async () => {
- if (isEmpty(state.detailId)) return
- const { data } = await orderApi.getShopCar({ merchantId: state.detailId, hardType: "CANTEEN" })
- state.cartList = data ?? []
- if (isEmpty(state.cartList)) {
- state.foodList.forEach((v: any) => {
- v.productCount = 0
- })
- } else {
- syncCartCount(state.foodList)
- }
- state.totPrice = state.cartList.map((v) => Number(v.totalPrice)).reduce((pre, next) => pre + next, 0)
- state.totCount = state.cartList.map((v) => v.productCount).reduce((pre, next) => pre + next, 0)
- // 购物车变化后刷新底部“未选必选品”提示
- updateChoiceTipStatus()
+ if (isEmpty(state.detailId)) return
+ const { data } = await orderApi.getShopCar({ merchantId: state.detailId, hardType: "CANTEEN" })
+ state.cartList = data ?? []
+ if (isEmpty(state.cartList)) {
+ state.foodList.forEach((v: any) => {
+ v.productCount = 0
+ })
+ } else {
+ syncCartCount(state.foodList)
+ }
+ state.totPrice = state.cartList.map((v) => Number(v.totalPrice)).reduce((pre, next) => pre + next, 0)
+ state.totCount = state.cartList.map((v) => v.productCount).reduce((pre, next) => pre + next, 0)
+ // 购物车变化后刷新底部“未选必选品”提示
+ updateChoiceTipStatus()
}
// 同步购物车购买数量
const syncCartCount = (foodList: any) => {
- if (state.cartList && state.cartList.length) {
- for (const i of foodList) {
- for (const v of state.cartList) {
- if (i.id == v.productId) {
- i.productCount = v.productCount
- }
- }
- }
- }
- state.foodList = foodList
+ if (state.cartList && state.cartList.length) {
+ for (const i of foodList) {
+ for (const v of state.cartList) {
+ if (i.id == v.productId) {
+ i.productCount = v.productCount
+ }
+ }
+ }
+ }
+ state.foodList = foodList
}
// 获取菜品分类
const getClassify = async () => {
- if (isEmpty(state.detailId)) return
- const { data } = await foodApi.classify({ merchantId: state.detailId })
- state.classifyList = data ?? []
- if (!isEmpty(state.classifyList)) {
- await getFoodList()
- }
+ if (isEmpty(state.detailId)) return
+ const { data } = await foodApi.classify({ merchantId: state.detailId })
+ state.classifyList = data ?? []
+ if (!isEmpty(state.classifyList)) {
+ await getFoodList()
+ }
}
// 获取食物列表
const getFoodList = async (opts: { withLoading?: boolean } = {}) => {
- const target = state.classifyList[state.currentClassifyIndex]
- if (isEmpty(target)) return
+ const target = state.classifyList[state.currentClassifyIndex]
+ if (isEmpty(target)) return
const withLoading = !!opts.withLoading
if (withLoading) {
classifyLoading.value = true
@@ -235,10 +234,10 @@ const getFoodList = async (opts: { withLoading?: boolean } = {}) => {
time: hooks.useTime(new Date(), "{y}-{m}-{d}"),
}
const { data } = await foodApi.product(_pa)
- ;(data || []).forEach((i: any) => {
- i.productCount = 0
- i.productId = i.id
- })
+ ; (data || []).forEach((i: any) => {
+ i.productCount = 0
+ i.productId = i.id
+ })
syncCartCount(data || [])
} finally {
if (withLoading) {
@@ -251,59 +250,59 @@ const getFoodList = async (opts: { withLoading?: boolean } = {}) => {
// 切换选中的分类
const doClassify = async (i: any) => {
if (i === state.currentClassifyIndex || classifyLoading.value) return
- state.currentClassifyIndex = i
- await getFoodList({ withLoading: true })
+ state.currentClassifyIndex = i
+ await getFoodList({ withLoading: true })
}
// 设置购物车商品的数量
const chentProductCartCount = async (item: any) => {
- try {
- uni.showLoading({ title: "加载中", mask: true })
- // 检查商品库存
- const { code, msg }: any = await foodApi.checkSpuStock({ spuId: item.productId, incrCount: item.productCount })
- if (code != 0) {
- item.productCount--
- return uni.showToast({ title: msg, icon: "none" })
- }
+ try {
+ uni.showLoading({ title: "加载中", mask: true })
+ // 检查商品库存
+ const { code, msg }: any = await foodApi.checkSpuStock({ spuId: item.productId, incrCount: item.productCount })
+ if (code != 0) {
+ item.productCount--
+ return uni.showToast({ title: msg, icon: "none" })
+ }
- const targetIndex = state.cartList.findIndex((v) => v.productId == item.productId)
- if (targetIndex !== -1) {
- state.cartList[targetIndex].productCount = item.productCount
- } else {
- state.cartList.push({ ...item, productId: item.id, productCount: item.productCount })
- }
- nextTick(async () => {
- await updateFoodCart()
- })
- } catch (e) {
- console.error(e)
- } finally {
- uni.hideLoading()
- }
+ const targetIndex = state.cartList.findIndex((v) => v.productId == item.productId)
+ if (targetIndex !== -1) {
+ state.cartList[targetIndex].productCount = item.productCount
+ } else {
+ state.cartList.push({ ...item, productId: item.id, productCount: item.productCount })
+ }
+ nextTick(async () => {
+ await updateFoodCart()
+ })
+ } catch (e) {
+ console.error(e)
+ } finally {
+ uni.hideLoading()
+ }
}
// 保存购物车商品
const updateFoodCart = async ({ saveTakeOutSetting = false } = {}) => {
- if (isEmpty(state.cartList)) return
- const list = state.cartList
- .filter((v) => v.productCount !== undefined && v.productCount >= 0)
- .map((v) => {
- return {
- productCount: v.productCount,
- productId: v.productId,
- }
- })
- if (isEmpty(list)) return
- const _pa: any = { merchantId: state.detailId, list: list, hardType: "CANTEEN" }
- if (saveTakeOutSetting && state.takeOutDetail) {
- _pa.takeOutSetting = {
- minDeliveryAmt: state.takeOutDetail.minDeliveryAmt,
- mchTakeOutAmt: state.takeOutDetail.mchTakeOutAmt,
- userTakeOutAmt: state.takeOutDetail.userTakeOutAmt,
- }
- }
- const { data, code }: any = await orderApi.updateShopCar(_pa)
- if (code == 0) await getFoodCart()
+ if (isEmpty(state.cartList)) return
+ const list = state.cartList
+ .filter((v) => v.productCount !== undefined && v.productCount >= 0)
+ .map((v) => {
+ return {
+ productCount: v.productCount,
+ productId: v.productId,
+ }
+ })
+ if (isEmpty(list)) return
+ const _pa: any = { merchantId: state.detailId, list: list, hardType: "CANTEEN" }
+ if (saveTakeOutSetting && state.takeOutDetail) {
+ _pa.takeOutSetting = {
+ minDeliveryAmt: state.takeOutDetail.minDeliveryAmt,
+ mchTakeOutAmt: state.takeOutDetail.mchTakeOutAmt,
+ userTakeOutAmt: state.takeOutDetail.userTakeOutAmt,
+ }
+ }
+ const { data, code }: any = await orderApi.updateShopCar(_pa)
+ if (code == 0) await getFoodCart()
}
// 打开购物车
@@ -344,201 +343,195 @@ const addComboFromDialog = () => {
}
const pickCarParentId = () => {
- const row = state.cartList?.[0]
- return row?.parentCarId ?? row?.carParentId
+ const row = state.cartList?.[0]
+ return row?.parentCarId ?? row?.carParentId
}
// 购买:不再弹出预订时间段选择,尽量带 apptId 跳转订单确认页;预约列表失败仍允许进入结算
const doBuy = async () => {
- if (isEmpty(state.cartList)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
- // 下单前必选/组合校验:不通过则提示并跳转到第一个缺项
- const ok = await validateChoiceBeforeBuy()
- if (!ok) return
- // 这里要再判断一下,如果购物车里的商品大于1
- const isRemind = state.totCount > 1
- // 订餐购物车是否只允许有一个商品
- const singleProductOnlyInCart = state.canteenDetail?.tableReservationConf?.singleProductOnlyInCart
- if (isRemind && singleProductOnlyInCart && singleProductOnlyInCart === true) {
- uni.showToast({ title: "订餐购物车只允许有一个商品", icon: "none" })
- }
- // 商品数量大于1时,弹出确认弹窗
- else if (isRemind) {
- productRepetitionAlertRef.value.open({
- totalCount: state.totCount,
- mchName: state.canteenDetail?.name || "",
- carList: state.cartList,
- takeOutSetting: state.takeOutDetail || {},
- })
- }
- // 创建订单
- else {
- await doCreateOrder()
- }
+ if (isEmpty(state.cartList)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
+ // 下单前必选/组合校验:不通过则提示并跳转到第一个缺项
+ const ok = await validateChoiceBeforeBuy()
+ if (!ok) return
+ // 这里要再判断一下,如果购物车里的商品大于1
+ const isRemind = state.totCount > 1
+ // 订餐购物车是否只允许有一个商品
+ const singleProductOnlyInCart = state.canteenDetail?.tableReservationConf?.singleProductOnlyInCart
+ if (isRemind && singleProductOnlyInCart && singleProductOnlyInCart === true) {
+ uni.showToast({ title: "订餐购物车只允许有一个商品", icon: "none" })
+ }
+ // 商品数量大于1时,弹出确认弹窗
+ else if (isRemind) {
+ productRepetitionAlertRef.value.open({
+ totalCount: state.totCount,
+ mchName: state.canteenDetail?.name || "",
+ carList: state.cartList,
+ takeOutSetting: state.takeOutDetail || {},
+ })
+ }
+ // 创建订单
+ else {
+ await doCreateOrder()
+ }
}
const doCreateOrder = async () => {
- let carParentId = pickCarParentId()
- if (!carParentId) {
- await getFoodCart()
- carParentId = pickCarParentId()
- }
- if (!carParentId) return uni.showToast({ title: "订单信息缺失(carParentId)", icon: "none" })
- try {
- uni.showLoading({ title: "加载中...", mask: true })
- let apptId = ""
- try {
- // 先更新一下购物车,把最新的商品数量和配送配置保存到后端
- await updateFoodCart({ saveTakeOutSetting: true })
- const res: any = await orderApi.apptList({})
- if (res?.code == 0 && Array.isArray(res.data) && res.data.length > 0) {
- state.bookingTimeList = res.data
- apptId = res.data[0].id ?? ""
- state.selectedBookingTime = apptId
- }
- } catch {
- // 预约接口异常不阻断结算页
- }
- state.showCartDialog = false
- router.jump("/pages/order/confirm", {
- merchantId: String(state.detailId ?? ""),
- carParentId: String(carParentId),
- apptId: String(apptId),
- })
- } catch (error: any) {
- uni.showToast({ title: error?.msg || "加载失败", icon: "none" })
- } finally {
- uni.hideLoading()
- }
+ let carParentId = pickCarParentId()
+ if (!carParentId) {
+ await getFoodCart()
+ carParentId = pickCarParentId()
+ }
+ if (!carParentId) return uni.showToast({ title: "订单信息缺失(carParentId)", icon: "none" })
+ try {
+ uni.showLoading({ title: "加载中...", mask: true })
+ let apptId = ""
+ try {
+ // 先更新一下购物车,把最新的商品数量和配送配置保存到后端
+ await updateFoodCart({ saveTakeOutSetting: true })
+ } catch {
+ // 预约接口异常不阻断结算页
+ }
+ state.showCartDialog = false
+ router.jump("/pages/order/confirm", {
+ merchantId: String(state.detailId ?? ""),
+ carParentId: String(carParentId),
+ apptId: String(apptId),
+ })
+ } catch (error: any) {
+ uni.showToast({ title: error?.msg || "加载失败", icon: "none" })
+ } finally {
+ uni.hideLoading()
+ }
}
const buildCartQtyMap = () => {
- const map: Record = {}
- for (const row of state.cartList || []) {
- const spuId = row?.productId // productId == spuId(后端约定)
- const qty = Number(row?.productCount || 0)
- if (!spuId || qty <= 0) continue
- map[String(spuId)] = (map[String(spuId)] || 0) + qty
- }
- return map
+ const map: Record = {}
+ for (const row of state.cartList || []) {
+ const spuId = row?.productId // productId == spuId(后端约定)
+ const qty = Number(row?.productCount || 0)
+ if (!spuId || qty <= 0) continue
+ map[String(spuId)] = (map[String(spuId)] || 0) + qty
+ }
+ return map
}
const validateChoiceBeforeBuy = async () => {
- state.requiredHintMap = {}
- state.scrollIntoView = ""
+ state.requiredHintMap = {}
+ state.scrollIntoView = ""
- const merchantId = state.detailId
- if (!merchantId) return true
+ const merchantId = state.detailId
+ if (!merchantId) return true
- // 购物车里已有的 spuId 集合/数量
- const cartQtyMap = buildCartQtyMap()
- const cartSpuIds = new Set(Object.keys(cartQtyMap).filter(Boolean))
+ // 购物车里已有的 spuId 集合/数量
+ const cartQtyMap = buildCartQtyMap()
+ const cartSpuIds = new Set(Object.keys(cartQtyMap).filter(Boolean))
- // 优先使用进入页面时预加载的规则,避免重复请求
- let vo: any = state.choiceRulesVo
- if (!vo) {
- try {
- const res: any = await foodApi.getChoiceGroups({ merchantId: String(merchantId), status: 0 })
- if (res?.code === 0) vo = res?.data
- } catch {
- // 规则接口失败时,不阻断购买(避免线上因接口波动无法下单)
- return true
- }
- if (vo) state.choiceRulesVo = vo
- }
- if (!vo) return true
+ // 优先使用进入页面时预加载的规则,避免重复请求
+ let vo: any = state.choiceRulesVo
+ if (!vo) {
+ try {
+ const res: any = await foodApi.getChoiceGroups({ merchantId: String(merchantId), status: 0 })
+ if (res?.code === 0) vo = res?.data
+ } catch {
+ // 规则接口失败时,不阻断购买(避免线上因接口波动无法下单)
+ return true
+ }
+ if (vo) state.choiceRulesVo = vo
+ }
+ if (!vo) return true
- // 1) 单品必选:requiredSpus(requiredChoice=1 且 choiceGroupId 为空)
- const requiredSpus = Array.isArray(vo.requiredSpus) ? vo.requiredSpus : []
- for (const rs of requiredSpus) {
- const spuId = rs?.spuId != null ? String(rs.spuId) : ""
- if (!spuId) continue
- if (!cartSpuIds.has(spuId)) {
- state.requiredHintMap[spuId] = "未选必选品"
- state.scrollIntoView = `spu_${spuId}`
- uni.showToast({ title: "未选必选品", icon: "none" })
- return false
- }
- }
+ // 1) 单品必选:requiredSpus(requiredChoice=1 且 choiceGroupId 为空)
+ const requiredSpus = Array.isArray(vo.requiredSpus) ? vo.requiredSpus : []
+ for (const rs of requiredSpus) {
+ const spuId = rs?.spuId != null ? String(rs.spuId) : ""
+ if (!spuId) continue
+ if (!cartSpuIds.has(spuId)) {
+ state.requiredHintMap[spuId] = "未选必选品"
+ state.scrollIntoView = `spu_${spuId}`
+ uni.showToast({ title: "未选必选品", icon: "none" })
+ return false
+ }
+ }
- // 2) 分组:groups(choiceGroupId 聚合)
- const groups = Array.isArray(vo.groups) ? vo.groups : []
- for (const g of groups) {
- const groupName = String(g?.groupName || "必选项")
- const required = Boolean(g?.required)
- const minSelect = Number(g?.minSelect || 0)
- const maxSelect = Number(g?.maxSelect || 0) // 0 不限制
- const minTotalQty = Number(g?.minTotalQty || 0)
- const maxTotalQty = Number(g?.maxTotalQty || 0) // 0 不限制
- const items = Array.isArray(g?.items) ? g.items : []
+ // 2) 分组:groups(choiceGroupId 聚合)
+ const groups = Array.isArray(vo.groups) ? vo.groups : []
+ for (const g of groups) {
+ const groupName = String(g?.groupName || "必选项")
+ const required = Boolean(g?.required)
+ const minSelect = Number(g?.minSelect || 0)
+ const maxSelect = Number(g?.maxSelect || 0) // 0 不限制
+ const minTotalQty = Number(g?.minTotalQty || 0)
+ const maxTotalQty = Number(g?.maxTotalQty || 0) // 0 不限制
+ const items = Array.isArray(g?.items) ? g.items : []
- let selectedKinds = 0
- let selectedTotalQty = 0
- const itemSpuIds: string[] = []
- for (const it of items) {
- const spuId = it?.spuId != null ? String(it.spuId) : ""
- if (!spuId) continue
- itemSpuIds.push(spuId)
- const qty = Number(cartQtyMap[spuId] || 0)
- if (qty > 0) {
- selectedKinds += 1
- selectedTotalQty += qty
- }
- }
- if (!itemSpuIds.length) continue
+ let selectedKinds = 0
+ let selectedTotalQty = 0
+ const itemSpuIds: string[] = []
+ for (const it of items) {
+ const spuId = it?.spuId != null ? String(it.spuId) : ""
+ if (!spuId) continue
+ itemSpuIds.push(spuId)
+ const qty = Number(cartQtyMap[spuId] || 0)
+ if (qty > 0) {
+ selectedKinds += 1
+ selectedTotalQty += qty
+ }
+ }
+ if (!itemSpuIds.length) continue
- // 上限校验(比如二选一选了两个)
- if (maxSelect > 0 && selectedKinds > maxSelect) {
- const firstSpuId = itemSpuIds[0]
- for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = `选择超出限制(最多选${maxSelect}种)`
- state.scrollIntoView = `spu_${firstSpuId}`
- uni.showToast({ title: `【${groupName}】选择超出限制`, icon: "none" })
- return false
- }
- if (maxTotalQty > 0 && selectedTotalQty > maxTotalQty) {
- const firstSpuId = itemSpuIds[0]
- for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = `数量超出限制(最多${maxTotalQty}件)`
- state.scrollIntoView = `spu_${firstSpuId}`
- uni.showToast({ title: `【${groupName}】数量超出限制`, icon: "none" })
- return false
- }
+ // 上限校验(比如二选一选了两个)
+ if (maxSelect > 0 && selectedKinds > maxSelect) {
+ const firstSpuId = itemSpuIds[0]
+ for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = `选择超出限制(最多选${maxSelect}种)`
+ state.scrollIntoView = `spu_${firstSpuId}`
+ uni.showToast({ title: `【${groupName}】选择超出限制`, icon: "none" })
+ return false
+ }
+ if (maxTotalQty > 0 && selectedTotalQty > maxTotalQty) {
+ const firstSpuId = itemSpuIds[0]
+ for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = `数量超出限制(最多${maxTotalQty}件)`
+ state.scrollIntoView = `spu_${firstSpuId}`
+ uni.showToast({ title: `【${groupName}】数量超出限制`, icon: "none" })
+ return false
+ }
- // 下限校验:required=true 才强制(按后端 VO 注释)
- if (required) {
- if (minSelect > 0 && selectedKinds < minSelect) {
- const firstSpuId = itemSpuIds[0]
- for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = "未选必选品"
- state.scrollIntoView = `spu_${firstSpuId}`
- uni.showToast({ title: `【${groupName}】未选必选品`, icon: "none" })
- return false
- }
- if (minTotalQty > 0 && selectedTotalQty < minTotalQty) {
- const firstSpuId = itemSpuIds[0]
- for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = `未满足必选数量(至少${minTotalQty}件)`
- state.scrollIntoView = `spu_${firstSpuId}`
- uni.showToast({ title: `【${groupName}】未满足必选数量`, icon: "none" })
- return false
- }
- }
- }
+ // 下限校验:required=true 才强制(按后端 VO 注释)
+ if (required) {
+ if (minSelect > 0 && selectedKinds < minSelect) {
+ const firstSpuId = itemSpuIds[0]
+ for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = "未选必选品"
+ state.scrollIntoView = `spu_${firstSpuId}`
+ uni.showToast({ title: `【${groupName}】未选必选品`, icon: "none" })
+ return false
+ }
+ if (minTotalQty > 0 && selectedTotalQty < minTotalQty) {
+ const firstSpuId = itemSpuIds[0]
+ for (const spuId of itemSpuIds) state.requiredHintMap[spuId] = `未满足必选数量(至少${minTotalQty}件)`
+ state.scrollIntoView = `spu_${firstSpuId}`
+ uni.showToast({ title: `【${groupName}】未满足必选数量`, icon: "none" })
+ return false
+ }
+ }
+ }
- return true
+ return true
}
const doPhone = () => {
- if (isEmpty(state.canteenDetail.phone)) {
- uni.showToast({ title: "该商户没有预留电话", icon: "none" })
- return
- }
- hooks.useMakePhoneCall(state.canteenDetail.phone)
+ if (isEmpty(state.canteenDetail.phone)) {
+ uni.showToast({ title: "该商户没有预留电话", icon: "none" })
+ return
+ }
+ hooks.useMakePhoneCall(state.canteenDetail.phone)
}
const doFood = () => {
- router.jump("/pages/canteen/food", { merchantId: state.detailId })
+ router.jump("/pages/canteen/food", { merchantId: state.detailId })
}
const orderingFoodEnabled = computed(() =>
- isTruthyFlag(state.canteenDetail?.functionScope?.orderingFoodEnable),
+ isTruthyFlag(state.canteenDetail?.functionScope?.orderingFoodEnable),
)
const mealSlots = computed(() =>
@@ -558,24 +551,17 @@ const swiperPics = computed(() => {
})
const doOrderingFood = () => {
- if (!orderingFoodEnabled.value) return
- router.jump("/pages/ordering-food/submit", { merchantId: String(state.detailId ?? "") })
+ if (!orderingFoodEnabled.value) return
+ router.jump("/pages/ordering-food/submit", { merchantId: String(state.detailId ?? "") })
}
-
+
-
+
@@ -587,11 +573,7 @@ const doOrderingFood = () => {
+
+
+
+
+
+
- chentProductCartCount(v)"
- @remove="(v) => chentProductCartCount(v)"
- @submit="doBuy"
- />
-
+ chentProductCartCount(v)"
+ @remove="(v) => chentProductCartCount(v)" @submit="doBuy" />
+
@@ -727,11 +675,7 @@ const doOrderingFood = () => {
包含菜品
-
+
{{ Number(i) + 1 }}
{{ c.name }}
@@ -750,9 +694,9 @@ const doOrderingFood = () => {
diff --git a/src/pages/user/relation/components/relation-detail-drawer.vue b/src/pages/user/relation/components/relation-detail-drawer.vue
index b0ee2d8..3db8b14 100644
--- a/src/pages/user/relation/components/relation-detail-drawer.vue
+++ b/src/pages/user/relation/components/relation-detail-drawer.vue
@@ -1,11 +1,11 @@
-
+
+ :class="genderClass">
{{ initial }}
@@ -13,8 +13,11 @@
{{ node.displayName }}
@{{ node.username }}
- 当前用户
-
+ 当前用户
+
+
图谱根节点
@@ -27,7 +30,8 @@
关系信息
-
+
{{ node.relationDescription || "暂无关系描述" }}
@@ -42,7 +46,8 @@
-
+
您可以为该被监护人提交人脸照片,提交后需等待审核通过。
diff --git a/src/pages/user/relation/list.vue b/src/pages/user/relation/list.vue
new file mode 100644
index 0000000..86a2d20
--- /dev/null
+++ b/src/pages/user/relation/list.vue
@@ -0,0 +1,301 @@
+
+
+
+
+
+
+
+
+ 被监护人服务台
+
+ {{ totalCount }} 人
+
+
+ 饭卡、人脸与预约状态集中查看
+
+
+
+
+ 人脸情况
+
+
+
+ {{ item.label }}
+ {{ item.value }}
+
+
+
+
+
+
+ 预约订单
+
+
+ 待核销
+ {{ pendingReservationTotal }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ ward.wardName || "被监护人" }}
+
+ {{ ward.cardStatusName || "状态异常" }}
+
+
+
+ {{ ward.walletTypeName || "饭卡" }} · 卡号 {{ ward.maskedCardNumber || "--" }}
+
+
+
+
+
+
+
+
+
+
+ 人脸情况
+
+
+
+ {{ metric.label }}
+
+ {{ countText(metric.value) }}
+
+
+
+
+
+
+
+
+
+ 预约订单
+
+
+ 待核销
+ {{ countText(ward.pendingReservationCount) }}
+
+
+
+
+
+ 饭卡余额
+ ¥{{ hooks.useMoney(ward.balance || 0) }}
+
+ 可充值
+
+ {{ ward.disabledReason || "暂不可充值" }}
+
+
+
+
+
+
+
+
+
+ 暂无有效被监护人
+ 这里只展示当前登录用户直接且有效的监护关系
+
+ 查看关系图谱
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/user/relation/tree.vue b/src/pages/user/relation/tree.vue
index ea43ae2..77bd0c6 100644
--- a/src/pages/user/relation/tree.vue
+++ b/src/pages/user/relation/tree.vue
@@ -1,5 +1,6 @@
-
+
@@ -106,6 +107,11 @@ const handleNodeSelect = (node: RelationGraphNode) => {
detailVisible.value = true
}
+/**
+ * 图谱画布继续用于查看关系,业务操作统一进入独立的被监护人列表页。
+ */
+const openRelationList = () => router.jump("/pages/user/relation/list")
+
onLoad(async (options?: Record) => {
entryUserId.value = ensureUserId(options?.userId)
await loadGraph()
diff --git a/src/pages/wallet/recharge-done.vue b/src/pages/wallet/recharge-done.vue
index 8f2a2d8..f520895 100644
--- a/src/pages/wallet/recharge-done.vue
+++ b/src/pages/wallet/recharge-done.vue
@@ -1,9 +1,10 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {{ titleText }}
+ {{ descText }}
+ ¥{{ hooks.useMoney(payOrderAmount) }}
+
- {{ titleText }}
- {{ descText }}
- ¥{{ hooks.useMoney(payOrderAmount) }}
-
-
+
+
+ 商户
+ {{ mchNameText }}
+
+
+ 充值对象
+ {{ state.guardianResult.wardName || "被监护人" }}
+
+
+ 充值方式
+ {{ payTypeText }}
+
+
+ 最新余额
+ ¥{{ hooks.useMoney(state.guardianResult.balance || 0) }}
+
+
+ 订单状态
+ {{ resultStatusText }}
+
+
+
+
-
-
- 商户
- {{ mchNameText }}
-
-
- 充值方式
-
- {{ payTypeText }}
-
-
-
- 订单状态
-
- {{ state.phase === "loading" ? "确认中" : state.phase === "success" ? "已到账" : "未完成" }}
-
-
-
-
-
-
-
-
-
- 查看钱包
-
-
- 重新查询
-
-
-
+
+
+
+ {{ isGuardianMode ? "返回图谱列表" : "查看钱包" }}
+
+
+ {{ state.phase === "confirming" ? "重新确认到账" : "重新查询" }}
+
+
+
+
+
diff --git a/src/pages/wallet/recharge.vue b/src/pages/wallet/recharge.vue
index ce578aa..f0fa995 100644
--- a/src/pages/wallet/recharge.vue
+++ b/src/pages/wallet/recharge.vue
@@ -1,280 +1,404 @@
-
-
-
-
-
-
-
-
-
- {{ state.detail.walletTypeName }}
-
+
+
+
+
+
+ 正在加载充值信息
- ¥{{ hooks.useMoney(state.detail.balance) }}
-
- 充值记录
-
-
-
- 充值
-
-
- ¥{{ hooks.useMoney(v.payPrice) }}
-
-
- 支付
-
-
-
-
-
- {{ v.text }}
- {{ v.desc }}
+
+
+
+
+
+
-
+
+ {{ summaryName }}
+ {{ summaryType }}{{ isGuardianMode ? " · 代充值"
+ : "" }}
+
+
+ 当前余额
+ ¥{{
+ hooks.useMoney(state.detail.balance || 0) }}
+
+
+
+ 饭卡卡号 {{ state.detail.maskedCardNumber || "--" }} · {{ state.detail.cardStatusName || "状态异常" }}
+
+
+ 充值记录
-
-
- 确认充值
-
+
+
+
+
+ {{ environmentMessage }}
+
+
+ 选择充值金额
+
+
+
+ ¥{{ hooks.useMoney(item.payPrice) }}
+
+ 赠 ¥{{
+ hooks.useMoney(item.bonusPrice) }}
+
+
+
+ 选择支付方式
+
+
+
+
+ {{ option.text }}
+ {{ option.desc }}
+
+
+
+
+
+
+
+ {{ state.submitting ? "正在发起支付" : environmentMessage ? "请在 App 内完成充值" : `确认充值
+ ¥${hooks.useMoney(state.rechargeList[state.currentRecharge]?.payPrice || 0)}` }}
+
+ 支付金额及到账饭卡以服务端最终校验结果为准
+
+
+
+
+
+ 暂时无法充值
+ {{ emptyMessage }}
+
+ 重新加载
+
+
+
+
+
-
-
- !
- 该商户暂不提供充值功能,请联系客服
-
-
-
-
diff --git a/src/sheep/api/app-plus-platform.ts b/src/sheep/api/app-plus-platform.ts
index f578a19..a9bcbc8 100644
--- a/src/sheep/api/app-plus-platform.ts
+++ b/src/sheep/api/app-plus-platform.ts
@@ -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
diff --git a/src/sheep/api/wallet.ts b/src/sheep/api/wallet.ts
index a28f9fb..93c18c2 100644
--- a/src/sheep/api/wallet.ts
+++ b/src/sheep/api/wallet.ts
@@ -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 =>
@@ -67,6 +115,33 @@ export default {
method: "POST",
data,
}),
+ /** 查询当前登录用户全部有效被监护人的脱敏饭卡摘要。 */
+ guardianWardList: (): Promise =>
+ request({
+ url: "/wallet/wallet-recharge/app/guardian-ward/list",
+ method: "GET",
+ }),
+ /** 查询目标饭卡、有效套餐和目标食堂支付平台。 */
+ guardianWardRechargeContext: (params: { wardUserId: string | number }): Promise =>
+ request({
+ url: "/wallet/wallet-recharge/app/guardian-ward/context",
+ method: "GET",
+ params,
+ }),
+ /** 创建绑定目标饭卡的待支付充值记录。 */
+ createGuardianWardRecharge: (data: { wardUserId: string | number; packageId: string | number }): Promise =>
+ request({
+ url: "/wallet/wallet-recharge/app/guardian-ward/create",
+ method: "POST",
+ data,
+ }),
+ /** 查询第三方支付成功后是否已经进入目标饭卡。 */
+ guardianWardRechargeResult: (params: { rechargeId: string | number }): Promise =>
+ request({
+ url: "/wallet/wallet-recharge/app/guardian-ward/result",
+ method: "GET",
+ params,
+ }),
/**
* 是够能够使用智能柜
*/
diff --git a/src/sheep/constant/service-enum.ts b/src/sheep/constant/service-enum.ts
index 2209b5c..12d5dcb 100644
--- a/src/sheep/constant/service-enum.ts
+++ b/src/sheep/constant/service-enum.ts
@@ -104,6 +104,7 @@ const thirdPayOrderStatusEnum = {
const thirdPayOrderBizTypeEnum = {
"10": "小程序用户钱包充值",
+ "40": "监护人代被监护人饭卡充值",
}
const appPlusPlatformTypeEnum = {
diff --git a/src/static/illustration/guardian-action-hero.png b/src/static/illustration/guardian-action-hero.png
new file mode 100644
index 0000000..0e6849f
Binary files /dev/null and b/src/static/illustration/guardian-action-hero.png differ
diff --git a/src/static/illustration/guardian-service-hero.png b/src/static/illustration/guardian-service-hero.png
new file mode 100644
index 0000000..5b48a6b
Binary files /dev/null and b/src/static/illustration/guardian-service-hero.png differ
diff --git a/tmp/imagegen/guardian-action-hero-source.png b/tmp/imagegen/guardian-action-hero-source.png
new file mode 100644
index 0000000..2bd90ba
Binary files /dev/null and b/tmp/imagegen/guardian-action-hero-source.png differ
diff --git a/tmp/imagegen/guardian-service-hero-source.png b/tmp/imagegen/guardian-service-hero-source.png
new file mode 100644
index 0000000..b949853
Binary files /dev/null and b/tmp/imagegen/guardian-service-hero-source.png differ