Ver Fonte

fix(统一支付前端): 收银台评审修复(R72 展示门禁、R76/R77)

alphaH há 1 semana atrás
pai
commit
65b5103bcf
1 ficheiros alterados com 43 adições e 14 exclusões
  1. 43 14
      frontend/src/views/module_payment/unified/cashier/index.vue

+ 43 - 14
frontend/src/views/module_payment/unified/cashier/index.vue

@@ -193,15 +193,15 @@
           :loading="h5LinkLoading"
           @click="generateH5Link"
         >
-          复制 H5 支付链接
+          生成 H5 支付链接
         </el-button>
-        <template v-if="h5Link">
+        <template v-if="safeH5Link">
           <div class="link-line">
             <span class="link-text">{{ h5Link }}</span>
-            <CopyButton v-if="safeH5Link" :text="h5Link" />
+            <CopyButton :text="h5Link" />
           </div>
-          <div v-if="!safeH5Link" class="warn">该链接不是 http/https 地址,只展示不复制</div>
         </template>
+        <div v-else-if="h5Link" class="warn">该链接不是 http/https 地址,已隐藏</div>
       </div>
 
       <div class="footer">
@@ -237,6 +237,8 @@ import UnifiedAPI, {
   type UnifiedOrderCreateParams,
   type UnifiedOrderVO,
 } from "@/api/module_payment/unified";
+import { useUserStore } from "@/store";
+import { ROLE_ROOT } from "@/constants";
 import {
   UNIFIED_ORDER_STATUS_TEXT,
   UNIFIED_WAY_CODE_OPTIONS,
@@ -328,7 +330,7 @@ const qrDataUrl = ref("");
 const h5Link = ref("");
 const h5LinkLoading = ref(false);
 const pollTimedOut = ref(false);
-/** R75:快照接口判定订单不存在(body code=404 / HTTP 404)—— 停止态,不再接表 */
+/** R75:快照接口判定订单不存在(body code=404)—— 停止态,不再接表 */
 const pollGone = ref(false);
 
 let pollTimer: number | undefined;
@@ -340,6 +342,8 @@ let pendingOutTradeNo = "";
 /** 二维码异步生成的代次:迟到的 A 单结果不能渲染到 B 单上 */
 let qrSeq = 0;
 
+const userStore = useUserStore();
+
 const wayCodeOptions = computed(() =>
   UNIFIED_WAY_CODE_OPTIONS.filter((o) => WAY_CODES_BY_MODE[mode.value].includes(o.value))
 );
@@ -355,6 +359,19 @@ const submitDisabled = computed(
     (merchants.value.length > 1 && !form.mch_no)
 );
 
+/**
+ * 与 `v-hasPerm` 同口径(`src/directives/permission/index.ts:20-40`):超管角色直接放行,
+ * 其余看权限串。按钮会被指令摘掉,但输入框里按回车仍会调用 `submit()` ——
+ * 这里必须再拦一次,否则无权限的人按回车只会收到一条 403 toast。
+ */
+const canCreate = computed(() => {
+  const roles = userStore.basicInfo.roles;
+  if (roles && roles.map((r) => r.code).includes(ROLE_ROOT)) {
+    return true;
+  }
+  return userStore.prems.includes("module_payment:unified:cashier:create");
+});
+
 const payKind = computed(() => (order.value?.pay_data_type || "").toLowerCase());
 const payValue = computed(() => order.value?.pay_data || "");
 
@@ -391,7 +408,7 @@ const statusTagType = computed<TagType>(() => {
   return (st && ORDER_STATUS_TAG[st]) || "info";
 });
 
-/** R72:未终态且该方式能被公开 H5 页受理时,才提供「复制 H5 支付链接」;已失效的单不给入口 */
+/** R72:未终态且该方式能被公开 H5 页受理时,才提供「生成 H5 支付链接」;已失效的单不给入口 */
 const canGenerateH5Link = computed(() => {
   const o = order.value;
   return (
@@ -465,6 +482,9 @@ function applyOrder(vo: UnifiedOrderVO) {
 }
 
 async function submit() {
+  if (!canCreate.value) {
+    return;
+  }
   if (creating.value) {
     return;
   }
@@ -540,8 +560,15 @@ async function submit() {
     }
     pendingOutTradeNo = "";
     applyOrder(vo);
-  } catch {
+  } catch (err) {
     // 拦截器已用后端原话弹过 toast(例如「商户已停用」)。输入保留,直接重试。
+    // R77:409 = 这个 out_trade_no 已存在且本次参数不一致(后端 UnifiedOrderService 判定),
+    // 继续复用旧号永远建不出来 —— 丢掉幂等键,下一次点击换新号。
+    const e = err as { data?: { code?: number | string } };
+    if (String(e?.data?.code) === "409") {
+      pendingOutTradeNo = "";
+      inputHint.value = "订单参数已变化,已更换新的单号,请再试一次";
+    }
   } finally {
     creating.value = false;
   }
@@ -587,11 +614,10 @@ async function poll() {
     // R70:静默轮询失败保留上次已知状态,不弹 toast。
     // R75:订单不存在不是抖动 —— 后端以 HTTP 200 + body code=404 应答(GlobalExceptionHandler
     // 不设状态码),再问也不会有结果。停表并把结果区切到「订单不存在或已失效」。
-    const e = err as {
-      data?: { code?: number | string };
-      response?: { status?: number };
-    };
-    if (String(e?.data?.code) === "404" || e?.response?.status === 404) {
+    // R76:HTTP 层的 404 到不了这里 —— 拦截器把它压成裸 `new Error("请求处理失败")`
+    // (`utils/request.ts:169-173`),页面只能看到 body code,故只判它。
+    const e = err as { data?: { code?: number | string } };
+    if (String(e?.data?.code) === "404") {
       // 迟到的 404 不能停掉新单的表:先确认屏幕上还是同一单,再停表/置失效。
       if (order.value?.order_no === orderNo) {
         stopPolling();
@@ -665,8 +691,10 @@ watch(mode, (m) => {
 
 // 主扫:codeUrl / payUrl 生成二维码,codeImgUrl 交给 <img>(computed)。
 // seq 用来丢弃迟到的异步结果 —— A 单的码不能渲染到 B 单上。
+// 多个标量源(而不是一个返回新数组的 getter):轮询每次替换 order 对象时,
+// 只要这三个值没变就不重复编码/重复 warn。
 watch(
-  () => [order.value?.order_no, order.value?.pay_data, order.value?.pay_data_type] as const,
+  [() => order.value?.order_no, () => order.value?.pay_data, () => order.value?.pay_data_type],
   async () => {
     const seq = ++qrSeq;
     qrDataUrl.value = "";
@@ -698,8 +726,9 @@ watch(
 );
 
 // H5:非 http/https 的 pay_data 只按文本展示,并在控制台留一条 WARN
+// 同样用多个标量源,避免每 3 秒轮询都重打一次 warn。
 watch(
-  () => [order.value?.order_no, order.value?.way_code, order.value?.pay_data] as const,
+  [() => order.value?.order_no, () => order.value?.way_code, () => order.value?.pay_data],
   () => {
     const o = order.value;
     if (!o || o.way_code !== "WX_H5") {