11
This commit is contained in:
1 parent
116b46033d
commit
9269298ec3
6 files changed
+70
-9
No files matched your search
@@ -10,3 +10,7 @@ VITE_APP_DEV_SERVICE=https://dzc-test-1.fy.takin.cc
|
||||
# 生产环境后端地址;配置为空时由线上部署提供同源接口。
|
||||
# VITE_APP_PROD_SERVICE=https://www.zianxin.com/api
|
||||
VITE_APP_PROD_SERVICE=/api
|
||||
|
||||
# 图片基址沿用后台 VITE_IMAGE_BASE_PATH;相对 logoPic 通过同源 /api 访问文件网关。
|
||||
# 使用独立图片服务时可改成该服务完整地址,已是 HTTP(S) 的图片链接不会重复拼接。
|
||||
VITE_IMAGE_BASE_PATH=/api
|
||||
@@ -86,3 +86,9 @@ tm-ui `3.2.0` 从参考项目的 `src/uni_modules/tm-ui` 原样引入 `tm-sheet`
|
||||
结果页参考 App 的 `pay-complete.vue`,使用同版 `lottie-web 5.13.0` 和原始 `success/fail/loading` 矢量动画:成功、失败播放一次,查询中循环;减少动态效果时显示最终帧,后台暂停,销毁时清理。动画不可用时显示本地状态图标,文字与金额保持可读。
|
||||
|
||||
取消、网络错误和确认超时不会显示“支付失败”,而是保留待确认说明及只读重新查询入口。返回收银台仍使用同一票据,携带 `returned=1、resultViewed=1` 抑制再次自动付款及结果页往返跳转;该标记只影响导航,不改变服务端状态。确认成功或关闭后,线下商户入口可继续沿用原“再付一笔/重新输入金额”流程。
|
||||
|
||||
## 商户标识地址
|
||||
|
||||
收银页和付款结果页共用 `merchant-logo.vue`,原始 `logoPic` 保持后端返回值。相对图片路径(例如 `/admin/sys-file/oss/file?fileName=...`)按后台同名的 `.env` 配置 `VITE_IMAGE_BASE_PATH` 拼接,默认 `/api`,不追加 `/third`。生产同源 `/api` 代理应同时承接文件接口的 `/admin` 路径;也可把图片基址设为独立文件网关的完整地址。
|
||||
|
||||
完整 HTTP(S) 图片链接及其查询参数原样保留;缺少图片或加载失败时使用店铺占位图形。切换图片地址后重置失败状态,不影响商户信息或付款状态。修改 `.env` 后需重启开发服务,线上需要重新构建发布。
|
||||
@@ -5,8 +5,7 @@
|
||||
<tm-sheet :margin="[0]" :padding="[32]" :round="32" color="white" class="amount-card">
|
||||
<view class="flex items-center gap-24rpx min-w-0">
|
||||
<view class="merchant-logo flex-row-center size-96rpx shrink-0 rounded-28rpx bg-soft overflow-hidden" aria-hidden="true">
|
||||
<image v-if="merchant?.logoPic && !merchantLogoFailed" :src="merchant.logoPic" class="size-full" mode="aspectFill" @error="handleLogoError" />
|
||||
<tm-icon v-else name="store-2-line" :size="52" :color="themeColors.primary" />
|
||||
<merchant-logo :src="merchant?.logoPic" />
|
||||
</view>
|
||||
<view class="flex-1 min-w-0">
|
||||
<text class="block text-24rpx text-muted mb-8rpx">收款商户</text>
|
||||
@@ -59,7 +58,7 @@ import { computed, nextTick, ref, watch } from "vue"
|
||||
import PaymentKeyboard from "@@/components/payment-keyboard.vue"
|
||||
import PaymentUnavailable from "@@/components/payment-unavailable.vue"
|
||||
import { useMerchantPayment } from "@@/hooks/use-merchant-payment"
|
||||
import { themeColors } from "@@/config/theme"
|
||||
import MerchantLogo from "@@/components/merchant-logo.vue"
|
||||
import { hasViewedPaymentResult, paymentResultRoute, resolvePaymentResultPhase } from "@@/utils/payment-result"
|
||||
|
||||
/** 页面与数字键盘共享真实收银状态,任何付款入口都经过同一个请求和金额守卫。 */
|
||||
@@ -92,14 +91,9 @@ watch([context, ticket], () => {
|
||||
if (!hasViewedPaymentResult()) showResult()
|
||||
})
|
||||
|
||||
/** 商户图片失败只切换占位标识,不影响真实商户名称和付款对象。 */
|
||||
const merchantLogoFailed = ref(false)
|
||||
/** 长金额按展示长度缩小字号,始终保留完整金额。 */
|
||||
const amountSizeClass = computed(() => amount.value.length > 10 ? "text-56rpx" : amount.value.length > 7 ? "text-72rpx" : "text-104rpx")
|
||||
|
||||
/** 标识失败时使用 tm-ui 店铺图形。 */
|
||||
function handleLogoError() { merchantLogoFailed.value = true }
|
||||
|
||||
/** 保留 Tab 和按钮原生键盘语义;锁定金额时 Hook 会拒绝数字输入。 */
|
||||
function handleKeyboard(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey || event.isComposing) return
|
||||
|
||||
@@ -25,7 +25,12 @@
|
||||
<view v-if="context" class="result-details border-t-1px border-t-solid border-border pt-20rpx">
|
||||
<view class="result-detail-row flex justify-between items-start gap-24rpx py-18rpx">
|
||||
<text class="text-26rpx text-muted shrink-0 leading-relaxed">收款商户</text>
|
||||
<text class="result-detail-value min-w-0 text-28rpx text-text font-500 text-right leading-relaxed">{{ merchantName }}</text>
|
||||
<view class="flex flex-1 min-w-0 items-center justify-end gap-12rpx">
|
||||
<view class="size-48rpx shrink-0 rounded-12rpx overflow-hidden">
|
||||
<merchant-logo :src="context.merchant.logoPic" :icon-size="32" />
|
||||
</view>
|
||||
<text class="result-detail-value min-w-0 text-28rpx text-text font-500 text-right leading-relaxed">{{ merchantName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="result-detail-row flex justify-between items-start gap-24rpx py-18rpx">
|
||||
<text class="text-26rpx text-muted shrink-0 leading-relaxed">支付方式</text>
|
||||
@@ -58,6 +63,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, watch } from "vue"
|
||||
import PaymentResultAnimation from "@@/components/payment-result-animation.vue"
|
||||
import MerchantLogo from "@@/components/merchant-logo.vue"
|
||||
import { usePaymentResult } from "@@/hooks/use-payment-result"
|
||||
import { themeColors } from "@@/config/theme"
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { themeColors } from "@@/config/theme"
|
||||
import { resolveImageUrl } from "@@/utils/image"
|
||||
|
||||
/** 商户标识共用原文件地址解析和失败占位,尺寸与圆角由调用页面的容器控制。 */
|
||||
const props = withDefaults(defineProps<{
|
||||
/** 后端 logoPic,可为相对文件路径或完整 HTTP(S) 地址;为空时显示店铺图形。 */
|
||||
src?: string | null
|
||||
/** 占位图标尺寸,单位为 rpx,不影响真实图片的容器尺寸。 */
|
||||
iconSize?: number
|
||||
}>(), { iconSize: 52 })
|
||||
/** 已拼接图片网关前缀的实际展示地址,保留原图的签名和缩放查询参数。 */
|
||||
const imageUrl = computed(() => resolveImageUrl(props.src))
|
||||
/** 仅当前地址加载失败时使用占位,不能影响后续切换到的新商户图片。 */
|
||||
const failed = ref(false)
|
||||
|
||||
/** 图片地址变化后允许重新加载,修复一次错误导致后续真实标识始终被隐藏的问题。 */
|
||||
watch(imageUrl, () => { failed.value = false })
|
||||
|
||||
/** 无效或不可访问的标识只降级显示,不影响真实商户、金额和支付结果。 */
|
||||
function handleError() { failed.value = true }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="size-full flex-row-center bg-soft overflow-hidden" aria-hidden="true">
|
||||
<image v-if="imageUrl && !failed" :key="imageUrl" :src="imageUrl" class="size-full" mode="aspectFill" @error="handleError" />
|
||||
<tm-icon v-else name="store-2-line" :size="iconSize" :color="themeColors.primary" />
|
||||
</view>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
/** 图片基址沿用后台同名配置;未单独配置时复用当前环境的 API 前缀,不附加 /third。 */
|
||||
const imageBase = (import.meta.env.VITE_IMAGE_BASE_PATH
|
||||
|| (import.meta.env.DEV ? import.meta.env.VITE_API_URL || "/api" : import.meta.env.VITE_APP_PROD_SERVICE || ""))
|
||||
.trim().replace(/\/+$/, "")
|
||||
|
||||
/**
|
||||
* 将后端 logoPic 相对文件路径拼成可访问地址,完整 HTTP(S) 链接保留原查询参数。
|
||||
* 图片地址不携带登录 Token;缺失或不支持的协议交给商户标识组件显示占位图形。
|
||||
*/
|
||||
export function resolveImageUrl(source?: string | null): string {
|
||||
if (typeof source !== "string") return ""
|
||||
const path = source.trim()
|
||||
if (!path) return ""
|
||||
if (/^https?:\/\//i.test(path) || path.startsWith("//")) return path
|
||||
if (/^[a-z][a-z0-9+.-]*:/i.test(path)) return ""
|
||||
const relative = path.replace(/^\/+/, "")
|
||||
if (!imageBase) return `/${relative}`
|
||||
// 同源前缀已包含在文件地址时不再次拼接,例如 /api/admin/...。
|
||||
if (imageBase.startsWith("/") && (path === imageBase || path.startsWith(`${imageBase}/`))) return path
|
||||
return `${imageBase}/${relative}`
|
||||
}
|
||||
Reference in new issue
Block a user