hookehuyr

feat: 新增小程序支付测试页及路由配置

添加 weappPayBridge.vue 页面用于在小程序 WebView 中测试支付功能,该页面包含环境检测、订单参数输入及消息发送功能。同时,在路由配置中添加了对应的访问路径。
...@@ -128,4 +128,12 @@ export default [ ...@@ -128,4 +128,12 @@ export default [
128 title: '详情页', 128 title: '详情页',
129 }, 129 },
130 }, 130 },
131 + {
132 + path: '/weapp-pay-bridge',
133 + name: 'WeappPayBridge',
134 + component: () => import('@/views/weappPayBridge.vue'),
135 + meta: {
136 + title: '小程序支付测试',
137 + },
138 + },
131 ]; 139 ];
......
1 +<template>
2 + <div class="weapp-pay-bridge-page">
3 + <div class="hero-card">
4 + <div class="eyebrow">WebView 支付测试</div>
5 + <h1 class="title">从 H5 跳转到小程序支付测试页</h1>
6 + <p class="desc">
7 + 这个页面用于在小程序 WebView 中点击按钮后,直接跳到外层小程序的支付测试页,并自动尝试拉起微信支付弹框。
8 + </p>
9 + </div>
10 +
11 + <div class="panel">
12 + <div class="panel-head">
13 + <span class="panel-title">当前环境</span>
14 + <span :class="['env-tag', isMiniProgramWebView ? 'env-ok' : 'env-warn']">
15 + {{ isMiniProgramWebView ? '小程序 WebView 内' : '普通浏览器' }}
16 + </span>
17 + </div>
18 + <p class="hint">只有在小程序 WebView 中,点击下方按钮才会真正调用外层小程序页面。</p>
19 + </div>
20 +
21 + <div class="panel">
22 + <div class="panel-head">
23 + <span class="panel-title">支付参数</span>
24 + </div>
25 + <label class="field-label" for="orderId">测试订单 ID</label>
26 + <input
27 + id="orderId"
28 + v-model.trim="orderId"
29 + class="field-input"
30 + type="text"
31 + placeholder="请输入后端可生成支付参数的 order_id"
32 + />
33 + <p class="hint">建议使用未支付且可以正常返回支付参数的测试订单。</p>
34 + <div class="path-box">
35 + <div class="path-label">发给小程序 WebView 容器的消息</div>
36 + <div class="path-value">{{ payMessagePreview }}</div>
37 + </div>
38 + <button class="primary-btn" @click="sendPayMessage">点击触发小程序支付测试</button>
39 + </div>
40 +
41 + <div class="panel">
42 + <div class="panel-head">
43 + <span class="panel-title">最近结果</span>
44 + </div>
45 + <div class="result-text">{{ resultText }}</div>
46 + </div>
47 + </div>
48 +</template>
49 +
50 +<script setup>
51 +import { computed, ref } from 'vue';
52 +import { showToast } from 'vant';
53 +import wx from 'weixin-js-sdk';
54 +
55 +const orderId = ref('');
56 +const resultText = ref('等待开始测试');
57 +
58 +const isMiniProgramWebView = computed(() => {
59 + return navigator.userAgent.includes('miniProgram');
60 +});
61 +
62 +const payMessagePreview = computed(() => {
63 + return JSON.stringify({
64 + type: 'pay_test',
65 + order_id: orderId.value || '',
66 + source: 'weapp-pay-bridge',
67 + });
68 +});
69 +
70 +const ensureOrderId = () => {
71 + if (orderId.value) return true;
72 +
73 + showToast('请先输入订单 ID');
74 + resultText.value = '未填写订单 ID,无法继续触发支付测试。';
75 + return false;
76 +};
77 +
78 +const sendPayMessage = () => {
79 + if (!ensureOrderId()) return;
80 +
81 + if (!isMiniProgramWebView.value) {
82 + resultText.value = '当前不是小程序 WebView 环境,postMessage 不会发给小程序。';
83 + showToast('请在小程序 WebView 中打开此页');
84 + return;
85 + }
86 +
87 + try {
88 + wx.miniProgram.postMessage({
89 + data: {
90 + type: 'pay_test',
91 + order_id: orderId.value,
92 + source: 'weapp-pay-bridge',
93 + },
94 + });
95 + resultText.value = '已发送支付请求消息给当前小程序 WebView 容器。';
96 + } catch (error) {
97 + const message = error?.message || '发送 postMessage 失败';
98 + resultText.value = message;
99 + showToast(message);
100 + }
101 +};
102 +</script>
103 +
104 +<style scoped lang="less">
105 +.weapp-pay-bridge-page {
106 + height: 100vh;
107 + padding: 24px 18px 40px;
108 + box-sizing: border-box;
109 + overflow-y: auto;
110 + -webkit-overflow-scrolling: touch;
111 + background:
112 + radial-gradient(circle at top left, rgba(84, 171, 174, 0.18), transparent 28%),
113 + linear-gradient(180deg, #f7fbfb 0%, #eef4f6 100%);
114 + color: #16323a;
115 +}
116 +
117 +.hero-card,
118 +.panel {
119 + background: rgba(255, 255, 255, 0.94);
120 + border: 1px solid rgba(22, 50, 58, 0.08);
121 + border-radius: 22px;
122 + box-shadow: 0 18px 42px rgba(22, 50, 58, 0.08);
123 + padding: 22px 18px;
124 + box-sizing: border-box;
125 +}
126 +
127 +.hero-card {
128 + margin-bottom: 16px;
129 +}
130 +
131 +.eyebrow {
132 + display: inline-flex;
133 + align-items: center;
134 + padding: 6px 10px;
135 + border-radius: 999px;
136 + background: #e0f2f1;
137 + color: #0f766e;
138 + font-size: 12px;
139 + font-weight: 600;
140 + letter-spacing: 0.08em;
141 +}
142 +
143 +.title {
144 + margin: 14px 0 0;
145 + font-size: 28px;
146 + line-height: 1.25;
147 +}
148 +
149 +.desc,
150 +.hint,
151 +.result-text {
152 + margin: 12px 0 0;
153 + font-size: 14px;
154 + line-height: 1.7;
155 + color: #4f6470;
156 +}
157 +
158 +.panel {
159 + margin-bottom: 16px;
160 +}
161 +
162 +.panel-head {
163 + display: flex;
164 + align-items: center;
165 + justify-content: space-between;
166 + gap: 12px;
167 +}
168 +
169 +.panel-title {
170 + font-size: 18px;
171 + font-weight: 700;
172 + color: #16323a;
173 +}
174 +
175 +.env-tag {
176 + padding: 6px 10px;
177 + border-radius: 999px;
178 + font-size: 12px;
179 + font-weight: 600;
180 +}
181 +
182 +.env-ok {
183 + color: #166534;
184 + background: #dcfce7;
185 +}
186 +
187 +.env-warn {
188 + color: #9a3412;
189 + background: #ffedd5;
190 +}
191 +
192 +.field-label {
193 + display: block;
194 + margin-top: 16px;
195 + margin-bottom: 8px;
196 + font-size: 14px;
197 + color: #36505b;
198 +}
199 +
200 +.field-input {
201 + width: 100%;
202 + height: 46px;
203 + padding: 0 14px;
204 + border: 1px solid #cfd8dc;
205 + border-radius: 14px;
206 + box-sizing: border-box;
207 + background: #f9fbfb;
208 + font-size: 15px;
209 + color: #16323a;
210 +}
211 +
212 +.path-box {
213 + margin-top: 14px;
214 + padding: 14px;
215 + border-radius: 14px;
216 + background: #f2f7f8;
217 + border: 1px dashed #b6c8cf;
218 +}
219 +
220 +.path-label {
221 + font-size: 12px;
222 + color: #68808a;
223 +}
224 +
225 +.path-value {
226 + margin-top: 6px;
227 + font-size: 13px;
228 + line-height: 1.6;
229 + word-break: break-all;
230 + color: #16323a;
231 +}
232 +
233 +.primary-btn,
234 +.ghost-btn {
235 + width: 100%;
236 + height: 46px;
237 + border: none;
238 + border-radius: 999px;
239 + font-size: 15px;
240 + font-weight: 600;
241 + cursor: pointer;
242 +}
243 +
244 +.primary-btn {
245 + margin-top: 16px;
246 + color: #fff;
247 + background: linear-gradient(135deg, #0f766e, #155e75);
248 +}
249 +
250 +.ghost-btn {
251 + margin-top: 10px;
252 + color: #155e75;
253 + background: #e6f4f1;
254 +}
255 +</style>