Merge remote-tracking branch 'origin/master'

This commit is contained in:
zjf committed 2026-09-08 11:03:19 +08:00
commit 3cbb8fb6df
11 files changed
+161 -89

No files matched your search

+3 -3
View File
@@ -33,11 +33,11 @@ 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_APP_DEV_SERVICE = https://www.zianxin.com/api
# VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc
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
+5
View File
@@ -20,3 +20,8 @@ dist
*.sln
*.sw?
unpackage
# AI assistant artifacts
.video_agent
.mimosa
.v2c
-15
View File
@@ -1,9 +1,6 @@
export default {
mobile: {
pay: {
selectWallet: "Select wallet:",
balance: "Balance:",
@@ -297,7 +294,6 @@ export default {
duplicateDinerWallet: "Duplicate meal card data for {name}. Contact admin.",
refundApply: {
pageTitle: "Request refund",
loadFail: "Failed to load refund info",
@@ -417,11 +413,9 @@ export default {
increaseQtyAria: "Increase refund qty for {productName}",
qtyAria: "{productName} refund qty {count}",
},
refundHistory: {
pageTitle: "Refund progress",
loadFail: "Failed to load refund progress",
@@ -467,7 +461,6 @@ export default {
statusUpdated: "Refund status updated",
status: {
pendingReview: { title: "Refund under review", desc: "Merchant is processing. See updates below.", label: "Reviewing" },
completed: { title: "Refund completed", desc: "Confirmed. Arrival per payment channel.", label: "Completed" },
@@ -477,24 +470,16 @@ export default {
rejected: { title: "Refund rejected", desc: "Not approved. See records below.", label: "Rejected" },
updated: { title: "Progress updated", desc: "Status reflects latest server records", label: "Updated" },
},
auto: {
completed: { title: "Refund completed", desc: "Auto refund done. Check meal card balance.", label: "Completed" },
waitRetry: { title: "Waiting for auto refund", desc: "Will retry automatically. No resubmit needed.", label: "Retry pending" },
processing: { title: "Auto refund in progress", desc: "Processing without merchant review.", label: "Auto processing" },
},
},
},
},
}
+2 -2
View File
@@ -2,8 +2,8 @@
"name" : "资安信",
"appid" : "__UNI__1BFE88F",
"description" : "资安信后勤管理系统",
"versionName" : "1.5.33",
"versionCode" : 1533,
"versionName" : "1.5.36",
"versionCode" : 1536,
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
@@ -75,6 +75,7 @@ import { useI18n } from "vue-i18n"
import dayjs from "dayjs"
import { orderApi } from "@@/api"
import { hooks } from "@@/index"
import ServiceText from "@@/components/common/service-text.vue"
@@ -129,9 +130,10 @@ const getOptionId = (option: ApptOption) => option.reserveMode === WEEKLY_CYCLE
? `cycle:${option.apptRuleId ?? ""}:${option.hourType ?? ""}`
: `appt:${option.id ?? ""}`
const getOptionTitle = (option: ApptOption) => option.reserveMode === WEEKLY_CYCLE
? option.hourTypeName || option.title || t("mobile.tableReservation.mealDefault")
: option.title || option.hourTypeName || t("mobile.tableReservation.mealDefault")
/** 已知餐段按当前语言展示,未知协议值保留服务端标题以兼容自定义餐段。 */
const getOptionTitle = (option: ApptOption) => hooks.useEnum("hardHourTypeEnum", option.hourType)
|| (option.reserveMode === WEEKLY_CYCLE ? option.hourTypeName || option.title : option.title || option.hourTypeName)
|| t("mobile.tableReservation.mealDefault")
const formatMonthDay = (value: string) => dayjs(value).format("MM/DD")
+30 -11
View File
@@ -98,8 +98,8 @@
t('mobile.dailyMenu.bookedEmpty') }}
</view>
<view v-else class="mt-2rpx text-20rpx color-#667085 show-line">
{{ formatReservationDate(bookedMenus[0]?.apptUseDate) }} · {{ bookedMenus[0]?.apptTitle ||
t('mobile.dailyMenu.mealSlot') }}
{{ formatReservationDate(bookedMenus[0]?.apptUseDate) }} · {{
resolveMealTitle(bookedMenus[0]) }}
·
{{ bookedFoodSummary(bookedMenus[0]) }}
</view>
@@ -118,8 +118,7 @@
<view class="flex items-center gap-8rpx">
<text class="sub-text-23">{{ formatReservationDate(reservation.apptUseDate) }}</text>
<text class="meal-tag show-line">
{{ hooks.useEnum("hardHourTypeEnum", reservation.hourType) ||
t("mobile.dailyMenu.mealSlot") }}
{{ resolveMealTitle(reservation) }}
</text>
</view>
<view class="mt-4rpx text-20rpx color-#667085 show-line">
@@ -498,12 +497,31 @@ const bookedMenus = computed(() =>
*/
const resolveDailyMenuCartUserId = () => String(userStore.userId || userStore.userInfo?.userId || "")
/**
* 清理预约日期严格早于当前本地自然日的购物车草稿。
* 页面引用的是 Pinia Store 内的持久化对象,逐项删除会同步更新统一缓存;日期缺失或无效时保留原数据,避免误删。
*/
const removeExpiredDrafts = () => {
const today = dayjs().startOf("day")
Object.entries(state.draftMap).forEach(([draftKey, draft]) => {
const apptDate = dayjs(draft?.apptDate)
if (apptDate.isValid() && apptDate.startOf("day").isBefore(today)) {
delete state.draftMap[draftKey]
}
})
}
/** 使用日期和餐段生成仅供页面草稿索引的稳定键。 */
const createDraftKey = (apptDate: string, hourType: string | number) =>
apptDate && String(hourType) ? `${apptDate}::${String(hourType)}` : ""
/** 解析预约选项的餐段名称,优先使用服务端展示文案。 */
const resolveMealTitle = (apptObj: any) => apptObj?.hourTypeName || apptObj?.title || t("mobile.dailyMenu.mealSlot")
/** 已知餐段按当前语言展示,未知协议值保留服务端标题以兼容自定义餐段和历史订单。 */
const resolveMealTitle = (apptObj: any) => hooks.useEnum("hardHourTypeEnum", apptObj?.hourType)
|| apptObj?.hourTypeName
|| apptObj?.title
|| apptObj?.apptTitle
|| apptObj?.mealTitle
|| t("mobile.dailyMenu.mealSlot")
/** 将预约日期格式化为适合移动端阅读的日期和星期。 */
const formatReservationDate = (value?: string) => {
@@ -611,7 +629,7 @@ const removeDraftGroup = (draftKey: string) => {
title: t("mobile.dailyMenu.modal.removeMealTitle"),
content: t("mobile.dailyMenu.modal.removeMealContent", {
date: formatReservationDate(draft.apptDate),
meal: draft.mealTitle,
meal: resolveMealTitle(draft),
}),
confirmColor: "#d92d20",
success: (result) => {
@@ -778,7 +796,7 @@ const formatDraftFoodSelection = (draft: ReservationDraft, item: DailyMenuItem)
const foodName = String(item.name || "").trim() || t("mobile.dailyMenu.reconcile.foodIdFallback", {
id: String(item.spuId || item.id || t("mobile.dailyMenu.reconcile.unknownId")),
})
return `${formatReservationDate(draft.apptDate)} ${draft.mealTitle}:${foodName}`
return `${formatReservationDate(draft.apptDate)} ${resolveMealTitle(draft)}:${foodName}`
}
/**
@@ -947,7 +965,7 @@ const showSameMealReservationConflict = (conflicts: ReturnType<typeof findSameMe
const summaries = conflicts
.slice(0, 2)
.map(({ draft, dinerUserIds }) =>
`${formatReservationDate(draft.apptDate)} ${draft.mealTitle}${t("mobile.dailyMenu.modal.dupPeople", { count: dinerUserIds.length })}`,
`${formatReservationDate(draft.apptDate)} ${resolveMealTitle(draft)}${t("mobile.dailyMenu.modal.dupPeople", { count: dinerUserIds.length })}`,
)
const remainingCount = Math.max(conflicts.length - summaries.length, 0)
viewState.bookedMenusExpanded = true
@@ -1118,7 +1136,7 @@ const submitDrafts = async () => {
uni.showToast({
title: t("mobile.dailyMenu.toast.singleMealOnly", {
date: formatReservationDate(invalidDraft.apptDate),
meal: invalidDraft.mealTitle,
meal: resolveMealTitle(invalidDraft),
}),
icon: "none",
})
@@ -1163,8 +1181,9 @@ onLoad((options: any) => {
state.draftMap = dailyMenuCartStore.getOrCreateDraftMap(resolveDailyMenuCartUserId(), state.merchantId)
})
/** 页面每次显示时刷新商户配置和已预订菜谱。 */
/** 页面每次显示时先清理过期购物车,再刷新商户配置和已预订菜谱。 */
onShow(async () => {
removeExpiredDrafts()
await loadMch()
await loadBookedMenus()
})
+49 -23
View File
@@ -26,9 +26,11 @@
<tm-icon name="error-warning-line" :size="42" color="#d97706" />
</view>
<view class="sub-text-28 font-700">{{ t('mobile.pay.refundApply.loadFail') }}</view>
<view class="mt-8rpx text-22rpx leading-36rpx color-#667085">{{ t('mobile.pay.refundApply.loadFailHint') }}</view>
<view class="mt-8rpx text-22rpx leading-36rpx color-#667085">{{ t('mobile.pay.refundApply.loadFailHint')
}}</view>
<view class="retry-button mt-24rpx" hover-class="tap-feedback" role="button" tabindex="0"
:aria-label="t('mobile.pay.refundApply.reloadAria')" @tap="loadPage">{{ t('mobile.pay.reload') }}</view>
:aria-label="t('mobile.pay.refundApply.reloadAria')" @tap="loadPage">{{ t('mobile.pay.reload') }}
</view>
</view>
<template v-else-if="refundDetail">
@@ -38,16 +40,18 @@
<view class="panel-head">
<view>
<view class="main-text-28">{{ t('mobile.pay.refundApply.selectProducts') }}</view>
<view class="mt-3rpx text-20rpx color-#667085">{{ t('mobile.pay.refundApply.selectProductsHint') }}</view>
<view class="mt-3rpx text-20rpx color-#667085">{{
t('mobile.pay.refundApply.selectProductsHint') }}</view>
</view>
<view v-if="canEditForm" class="all-refund-action"
:class="{ 'all-refund-action--active': allRemainingSelected }"
:hover-class="allRemainingSelected ? 'none' : 'tap-feedback'" role="button"
:tabindex="allRemainingSelected ? -1 : 0" :aria-disabled="allRemainingSelected"
:tabindex="allRemainingSelected ? -1 : 0" :aria-disabled="allRemainingSelected"
:aria-label="t('mobile.pay.refundApply.selectAllAria')" @tap="selectAllRemaining">
<tm-icon :name="allRemainingSelected ? 'checkbox-circle-line' : 'check-double-line'"
:size="24" color="#0094ff" />
<text class="ml-5rpx">{{ allRemainingSelected ? t('mobile.pay.refundApply.allSelected') : t('mobile.pay.refundApply.selectAll') }}</text>
<text class="ml-5rpx">{{ allRemainingSelected ? t('mobile.pay.refundApply.allSelected') :
t('mobile.pay.refundApply.selectAll') }}</text>
</view>
</view>
@@ -56,7 +60,8 @@
<tm-icon name="shopping-bag-3-line" :size="42" color="#667085" />
</view>
<view class="sub-text-28 font-700">{{ t('mobile.pay.refundApply.noRefundableProducts') }}</view>
<view class="mt-8rpx text-22rpx color-#667085">{{ t('mobile.pay.refundApply.contactMerchant') }}</view>
<view class="mt-8rpx text-22rpx color-#667085">{{ t('mobile.pay.refundApply.contactMerchant') }}
</view>
</view>
<view v-else class="product-list">
@@ -72,26 +77,36 @@
<text class="text-19rpx font-700">¥</text>
<text class="ml-2rpx text-28rpx font-800">{{ money(product.productPrice)
}}</text>
<text class="ml-4rpx text-19rpx color-#667085">{{ t('mobile.pay.refundApply.perUnit') }}</text>
<text class="ml-4rpx text-19rpx color-#667085">{{
t('mobile.pay.refundApply.perUnit') }}</text>
</view>
<view v-if="product.onlineRefundSupported"
class="mt-5rpx flex flex-wrap items-center gap-x-12rpx gap-y-3rpx text-19rpx color-#667085">
<text>{{ t('mobile.pay.refundApply.purchased', { count: product.originalCount }) }}</text>
<text>{{ t('mobile.pay.refundApply.refunded', { count: product.refundedCount }) }}</text>
<text class="font-700 color-#0094ff">{{ t('mobile.pay.refundApply.remaining', { count: product.refundableCount }) }}</text>
<text>{{ t('mobile.pay.refundApply.purchased', { count: product.originalCount })
}}</text>
<text>{{ t('mobile.pay.refundApply.refunded', { count: product.refundedCount })
}}</text>
<text class="font-700 color-#0094ff">{{ t('mobile.pay.refundApply.remaining', {
count: product.refundableCount
}) }}</text>
</view>
<view v-else class="weighted-tag mt-9rpx">{{ t('mobile.pay.refundApply.weightedProduct') }}</view>
<view v-else class="weighted-tag mt-9rpx">{{
t('mobile.pay.refundApply.weightedProduct') }}</view>
</view>
</view>
<view v-if="product.onlineRefundSupported" class="product-control">
<view class="min-w-0 flex-1 pr-10rpx">
<view v-if="product.selectedCount > 0" class="text-21rpx font-700 color-#e5484d">
{{ t('mobile.pay.refundApply.estimatedRefund', { amount: money(estimateProductRefund(product)) }) }}
{{ t('mobile.pay.refundApply.estimatedRefund', {
amount:
money(estimateProductRefund(product))
}) }}
</view>
<view v-else-if="product.refundableCount > 0" class="text-20rpx color-#667085">
{{ t('mobile.pay.refundApply.selectRefundQty') }}</view>
<view v-else class="text-20rpx color-#98a2b3">{{ t('mobile.pay.refundApply.noRefundableQty') }}</view>
<view v-else class="text-20rpx color-#98a2b3">{{
t('mobile.pay.refundApply.noRefundableQty') }}</view>
</view>
<view class="quantity-control"
:aria-label="t('mobile.pay.refundApply.qtyAria', { productName: product.productName, count: product.selectedCount })">
@@ -134,7 +149,8 @@
<tm-icon name="scales-3-line" :size="42" color="#b54708" />
</view>
<view class="sub-text-28 font-700">{{ t('mobile.pay.refundApply.weightedOnly') }}</view>
<view class="mt-8rpx text-center text-22rpx leading-36rpx color-#667085">{{ t('mobile.pay.refundApply.weightedOnlyHint') }}
<view class="mt-8rpx text-center text-22rpx leading-36rpx color-#667085">{{
t('mobile.pay.refundApply.weightedOnlyHint') }}
</view>
</view>
@@ -151,9 +167,11 @@
v-model:model-str="applyForm.reasonText" :list="reasonOptions">
<view class="form-row" hover-class="row-tap-feedback" role="button" tabindex="0"
:aria-label="t('mobile.pay.refundApply.selectReasonAria')">
<view class="form-label"><text class="required-mark">*</text>{{ t('mobile.pay.refundApply.refundReason') }}</view>
<view class="form-label"><text class="required-mark">*</text>{{
t('mobile.pay.refundApply.refundReason') }}</view>
<view class="flex min-w-0 flex-1 items-center justify-end text-23rpx color-#475467">
<text class="show-line">{{ applyForm.reasonText || t('mobile.pay.refundApply.selectPlaceholder') }}</text>
<text class="show-line">{{ applyForm.reasonText ||
t('mobile.pay.refundApply.selectPlaceholder') }}</text>
<tm-icon class="ml-6rpx flex-shrink-0" name="arrow-right-s-line" :size="27"
color="#98a2b3" />
</view>
@@ -164,11 +182,13 @@
<view class="mt-12rpx refund-panel p-24rpx">
<view class="flex items-center justify-between">
<view class="main-text-26">{{ t('mobile.pay.refundApply.proofTitle') }}</view>
<view class="text-20rpx color-#667085">{{ t('mobile.pay.refundApply.proofOptional') }}</view>
<view class="text-20rpx color-#667085">{{ t('mobile.pay.refundApply.proofOptional') }}
</view>
</view>
<view class="mt-18rpx">
<tm-upload-photo v-model="uploadFiles" :maxFileSize="1024 * 1024 * 5" :url="uploadActionUrl"
:header="uploadHeader" :maxCount="6" :column="4" img-height="142" :permissionFeature="t('mobile.pay.refundApply.proofPermission')" />
:header="uploadHeader" :maxCount="6" :column="4" img-height="142"
:permissionFeature="t('mobile.pay.refundApply.proofPermission')" />
</view>
<view class="mt-16rpx flex items-start text-20rpx leading-32rpx color-#667085">
<tm-icon name="information-line" :size="22" color="#667085" />
@@ -198,20 +218,25 @@
<view v-if="refundDetail && !requestState.failed" class="refund-submit-bar">
<view class="min-w-0 flex-1">
<view class="flex items-baseline gap-6rpx">
<text class="text-20rpx color-#667085">{{ t('mobile.pay.refundApply.estimatedRefundLabel') }}</text>
<text class="text-20rpx color-#667085">{{ t('mobile.pay.refundApply.estimatedRefundLabel')
}}</text>
<text class="text-25rpx font-800 color-#e5484d">¥</text>
<text class="text-34rpx font-800 color-#e5484d refund-number">{{ money(selectedRefundPrice)
}}</text>
</view>
<view class="mt-2rpx show-line text-20rpx color-#667085">
{{ selectedProductCount > 0 ? t('mobile.pay.refundApply.selectedSummary', { productCount: selectedProductCount, unitCount: selectedUnitCount }) :
{{ selectedProductCount > 0 ? t('mobile.pay.refundApply.selectedSummary', {
productCount:
selectedProductCount, unitCount: selectedUnitCount
}) :
submitHint }}
</view>
</view>
<view class="submit-button" :class="{ 'submit-button--disabled': submitDisabled }"
:hover-class="submitDisabled ? 'none' : 'tap-feedback'" role="button"
:tabindex="submitDisabled ? -1 : 0" :aria-disabled="submitDisabled" :aria-label="t('mobile.pay.refundApply.submitAria')"
@tap="!submitDisabled && submitRefund()">{{ submitButtonText }}</view>
:tabindex="submitDisabled ? -1 : 0" :aria-disabled="submitDisabled"
:aria-label="t('mobile.pay.refundApply.submitAria')" @tap="!submitDisabled && submitRefund()">{{
submitButtonText }}</view>
</view>
</template>
</crud-layout>
@@ -536,6 +561,7 @@ function confirmSubmit(): Promise<boolean> {
amount: money(selectedRefundPrice.value),
processingText,
}),
cancelText: t('common.cancel'),
confirmText: t('mobile.pay.refundApply.confirmSubmit'),
confirmColor: "#0094ff",
success: (result) => resolve(Boolean(result.confirm)),
@@ -827,7 +853,7 @@ async function submitRefund() {
.submit-button {
display: flex;
min-width: 184rpx;
min-width: 220rpx;
height: 88rpx;
flex-shrink: 0;
align-items: center;
+23 -9
View File
@@ -144,17 +144,31 @@ const doTab = (e: any) => {
<view class="flex-row items-center justify-between card p-x-20rpx p-y-30rpx m-b-20rpx"
v-for="(v, i) in pageHook.list" :key="i">
<view class="flex">
<view class="w-200rpx flex justify-left p-r-25rpx">
<tm-tag v-if="v.title == '更新余额'" size="m">{{ t('mobile.wallet.txUpdateBalance') }}</tm-tag>
<tm-tag v-if="v.title == '批量更新余额'" size="m">{{ t('mobile.wallet.txBatchUpdateBalance') }}</tm-tag>
<tm-tag v-if="v.title == '充值'" size="m">{{ t('mobile.wallet.txRecharge') }}</tm-tag>
<tm-tag v-if="v.title == '支付'" color="error" size="m">{{ t('mobile.wallet.txPay') }}</tm-tag>
<tm-tag v-if="v.title == '退款'" color="info" size="m">{{ t('mobile.wallet.txRefund') }}</tm-tag>
<tm-tag v-if="v.title == '转账'" color="success" size="m">{{ t('mobile.wallet.txTransfer') }}</tm-tag>
<tm-tag v-if="v.title == '转账退款'" color="info" size="m">{{ t('mobile.wallet.txTransferRefund') }}</tm-tag>
<view class="w-260rpx flex justify-left p-r-25rpx">
<tm-tag v-if="v.title == '更新余额'" size="xs">{{ t('mobile.wallet.txUpdateBalance') }}</tm-tag>
<tm-tag v-if="v.title == '批量更新余额'" size="xs">
{{ t('mobile.wallet.txBatchUpdateBalance') }}
</tm-tag>
<tm-tag v-if="v.title == '充值'" size="xs">
{{ t('mobile.wallet.txRecharge') }}
</tm-tag>
<tm-tag v-if="v.title == '支付'" color="error" size="xs">
{{ t('mobile.wallet.txPay') }}
</tm-tag>
<tm-tag v-if="v.title == '退款'" color="info" size="xs">
{{ t('mobile.wallet.txRefund') }}
</tm-tag>
<tm-tag v-if="v.title == '转账'" color="success" size="xs">
{{ t('mobile.wallet.txTransfer') }}
</tm-tag>
<tm-tag v-if="v.title == '转账退款'" color="info" size="xs">
{{ t('mobile.wallet.txTransferRefund') }}
</tm-tag>
</view>
<view class="flex-row-center">
<text class="sub-text-30">{{ hooks.useMoney(v.price) }}{{ t('mobile.wallet.currencyUnit') }}</text>
<text class="sub-text-30">
{{ hooks.useMoney(v.price) }}{{ t('mobile.wallet.currencyUnit') }}
</text>
</view>
</view>
<view class="sub-text-24 text-right">
+24 -16
View File
@@ -41,7 +41,7 @@ onLoad(() => { })
onShow(async () => {
const isLogin = await hooks.useCheckLogin()
if (!isLogin) return
await userStore._getUserZsMch()
await userStore._getUserZsMch()
if (pageRef.value) {
pageRef.value.reload(false)
}
@@ -151,7 +151,7 @@ const doTransferMoney = (v: any) => {
* 后端返回的 identityTypes 是钱包所属用户身份,不能使用当前登录用户身份替代。
*/
const canRecharge = (wallet: any) => wallet?.walletTypeUniqueCode == 1
&& userStore.isTopUpIdentityAllowed(wallet?.identityTypes)
&& userStore.isTopUpIdentityAllowed(wallet?.identityTypes)
</script>
<template>
@@ -165,7 +165,8 @@ const canRecharge = (wallet: any) => wallet?.walletTypeUniqueCode == 1
<view class="flex-col">
<view class="flex-row items-center justify-between">
<view class="flex-row-center">
<text v-if="v.cardNumber" class="second-text-28">{{ t('mobile.wallet.cardNumber') }} {{ v.cardNumber }}</text>
<text v-if="v.cardNumber" class="second-text-28">{{ t('mobile.wallet.cardNumber') }} {{
v.cardNumber }}</text>
</view>
<view class="second-text-50">
<text v-if="v.cardNumber" class="iconfont icon-fuzhi"
@@ -184,8 +185,10 @@ const canRecharge = (wallet: any) => wallet?.walletTypeUniqueCode == 1
</text>
</view>
<view v-if="v.cardNumber">
<tm-tag v-if="v.isFreeze == 0" size="s" color="success">{{ t('mobile.wallet.statusNormal') }}</tm-tag>
<tm-tag v-if="v.isFreeze == 1" size="s" color="error">{{ t('mobile.wallet.statusFrozen') }}</tm-tag>
<tm-tag v-if="v.isFreeze == 0" size="s" color="success">{{ t('mobile.wallet.statusNormal')
}}</tm-tag>
<tm-tag v-if="v.isFreeze == 1" size="s" color="error">{{ t('mobile.wallet.statusFrozen')
}}</tm-tag>
</view>
</view>
<view class="flex-row items-center justify-between m-t-10rpx">
@@ -193,7 +196,8 @@ const canRecharge = (wallet: any) => wallet?.walletTypeUniqueCode == 1
</view>
<view class="flex-row items-center justify-end space-x-20rpx m-t-10rpx">
<!-- <view v-if="v.walletTypeUniqueCode == 1 && !v.cardNumber" class="botder_btn" @tap.stop="openBindDialog(v)">{{ t('mobile.wallet.bind') }}</view> -->
<view v-if="v.isRule == '1'" class="botder_btn" @tap.stop="doTransferMoney(v)">{{ t('mobile.wallet.transfer') }}</view>
<view v-if="v.isRule == '1'" class="botder_btn" @tap.stop="doTransferMoney(v)">{{
t('mobile.wallet.transfer') }}</view>
<view v-if="v.walletTypeUniqueCode == 1 && v.cardNumber && v.isFreeze != 1" class="botder_btn"
@tap.stop="openFreezeDialog(v)">
{{ t('mobile.wallet.applyFreeze') }}
@@ -210,30 +214,34 @@ const canRecharge = (wallet: any) => wallet?.walletTypeUniqueCode == 1
</z-paging>
</crud-layout>
<tm-drawer z-index="100" v-model:show="state.showBindDialog" :overlayClick="false" :title="t('mobile.wallet.bindMealCard')" :show-close="true"
position="bottom" size="40%">
<tm-drawer z-index="100" v-model:show="state.showBindDialog" :overlayClick="false"
:title="t('mobile.wallet.bindMealCard')" :show-close="true" position="bottom" size="40%">
<view class="h-50rpx" />
<tm-form ref="bindFormRef" v-model="bindForm" :rules="bindFormRules">
<tm-form-item name="cardNumber" :showLabel="false" :showBottom="false">
<tm-input v-model="bindForm.cardNumber" showClear :placeholder="t('mobile.wallet.cardNumberPlaceholder')" />
<tm-input v-model="bindForm.cardNumber" showClear
:placeholder="t('mobile.wallet.cardNumberPlaceholder')" />
</tm-form-item>
</tm-form>
<view class="h-50rpx" />
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doBind">{{ t('mobile.wallet.bind') }}</view>
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doBind">{{
t('mobile.wallet.bind') }}</view>
</tm-drawer>
<tm-drawer z-index="100" v-model:show="state.showFreezeDialog" :overlayClick="true" :title="t('mobile.wallet.applyFreezeTitle')" :show-close="true"
position="bottom" size="40%">
<tm-drawer z-index="100" v-model:show="state.showFreezeDialog" :overlayClick="true"
:title="t('mobile.wallet.applyFreezeTitle')" :show-close="true" position="bottom" size="40%">
<view class="h-50rpx" />
<tm-form ref="bindFormRef" v-model="freezeForm">
<tm-form-item name="remark" :showLabel="false" :showBottom="false">
<tm-input v-model="freezeForm.remark" type="textarea" showClear :placeholder="t('mobile.wallet.remarkPlaceholder')" />
<tm-input v-model="freezeForm.remark" type="textarea" showClear
:placeholder="t('mobile.wallet.remarkPlaceholder')" />
</tm-form-item>
</tm-form>
<view class="h-50rpx" />
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doFreeze">{{ t('mobile.wallet.freeze') }}</view>
<view class="bg-#0094ff p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap="doFreeze">{{
t('mobile.wallet.freeze') }}</view>
</tm-drawer>
<tm-drawer z-index="100" v-model:show="state.showQrCodeDialog" :overlayClick="false" :title="t('mobile.wallet.qrCodeTitle')"
:show-close="true" position="bottom" size="50%">
<tm-drawer z-index="100" v-model:show="state.showQrCodeDialog" :overlayClick="false"
:title="t('mobile.wallet.qrCodeTitle')" :show-close="true" position="bottom" size="50%">
<view class="flex-col-center">
<view class="h-80rpx" />
<canvas canvas-id="qrcodeId" class="size-200px" />
@@ -12,19 +12,23 @@
<view v-else class="pb-12rpx">
<view class="cart-toolbar">
<view class="text-21rpx color-#667085">
{{ t('mobile.dailyMenu.cart.segmentCount', { meals: props.draftGroups.length, foods: props.totalFoodCount }) }}
{{ t('mobile.dailyMenu.cart.segmentCount', {
meals: props.draftGroups.length, foods:
props.totalFoodCount
}) }}
</view>
<view class="clear-button" hover-class="tap-feedback" @tap="emitClear">{{ t('mobile.dailyMenu.cart.clear') }}</view>
<view class="clear-button" hover-class="tap-feedback" @tap="emitClear">{{
t('mobile.dailyMenu.cart.clear') }}</view>
</view>
<view v-for="draft in props.draftGroups" :key="draft.key" class="cart-group">
<view class="cart-group__header">
<view class="cart-group__meta">
<text class="cart-group__date">{{ formatReservationDate(draft.apptDate) }}</text>
<text class="meal-tag show-line">{{ draft.mealTitle }}</text>
<text class="meal-tag show-line">{{ resolveMealTitle(draft) }}</text>
</view>
<view class="remove-hit-area" hover-class="tap-feedback"
:aria-label="t('mobile.dailyMenu.cart.removeGroupAria', { meal: draft.mealTitle })"
:aria-label="t('mobile.dailyMenu.cart.removeGroupAria', { meal: resolveMealTitle(draft) })"
@tap="emitRemoveGroup(draft.key)">
<view class="remove-button">
<tm-icon name="delete-bin-line" :size="23" color="#d92d20" />
@@ -35,7 +39,8 @@
<view v-for="item in draft.items" :key="item.spuId || item.id" class="cart-food-row">
<image class="cart-food-row__image" :src="foodImage(item)" mode="aspectFill" />
<view class="ml-12rpx min-w-0 flex-1">
<view class="show-line sub-text-24">{{ item.name || t('mobile.dailyMenu.foodCard.unnamed') }}</view>
<view class="show-line sub-text-24">{{ item.name || t('mobile.dailyMenu.foodCard.unnamed') }}
</view>
<view class="mt-3rpx text-23rpx font-700 color-#e5484d">¥{{ hooks.useMoney(item.price || 0) }}
</view>
</view>
@@ -63,7 +68,10 @@
<template #footer>
<view class="drawer-footer">
<view class="min-w-0 flex-1">
<view class="text-20rpx color-#667085">{{ t('mobile.dailyMenu.cart.dinerTotal', { count: props.selectedDinerCount }) }}</view>
<view class="text-20rpx color-#667085">{{ t('mobile.dailyMenu.cart.dinerTotal', {
count:
props.selectedDinerCount
}) }}</view>
<view class="mt-1rpx text-32rpx font-800 color-#e5484d">¥{{ hooks.useMoney(props.totalPayableAmount)
}}</view>
</view>
@@ -138,6 +146,11 @@ const drawerShow = computed({
set: (show: boolean) => emit("update:show", show),
})
/** 已知餐段按当前语言展示,并为旧缓存草稿及未知协议值保留原始标题回退。 */
const resolveMealTitle = (draft: ReservationDraft) => hooks.useEnum("hardHourTypeEnum", draft.hourType)
|| draft.mealTitle
|| t("mobile.dailyMenu.mealSlot")
/** 将预约日期格式化为抽屉标题使用的“月日 + 星期”,避免依赖各端 dayjs locale。 */
const formatReservationDate = (value?: string) => {
if (!value || !dayjs(value).isValid()) return t("mobile.dailyMenu.datePending")
@@ -14,7 +14,7 @@
<base-skeleton class="mt-18rpx" width="126" height="44" round="22" color="#dcecff" dark-color="#323232" />
</template>
<template v-else>
<view class="flex items-center justify-between gap-8rpx">
<view class="flex items-center justify-between">
<view>
<view class="sub-text-30">{{ t("mobile.order.diners.title") }}</view>
<view class="second-text-22 mt-6rpx color-#999">