@2kog/pkg-widget 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +1 -0
- package/package.json +6 -3
- package/postcss.config.js +6 -0
- package/src/App.vue +5 -1
- package/src/assets/css/external/tailwind.css +3 -0
- package/src/components/common/payment.vue +578 -0
- package/src/locale/en-us/payment.js +14 -0
- package/src/locale/zh-cn/payment.js +14 -0
- package/src/main.js +1 -0
- package/tailwind.config.js +11 -0
- package/vite.config.js +4 -4
package/index.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<link rel="icon" href="/favicon.ico" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Iruxu Pkg Widget</title>
|
|
8
|
+
<link href="https://static.2kog.com/lib/css/font-awesome/7.1.0/css/all.min.css" rel="stylesheet" />
|
|
8
9
|
</head>
|
|
9
10
|
<body>
|
|
10
11
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kog/pkg-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -22,10 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@vitejs/plugin-vue": "^5.1.2",
|
|
25
|
+
"autoprefixer": "^10.4.23",
|
|
25
26
|
"less": "^4.2.0",
|
|
27
|
+
"postcss": "^8.5.6",
|
|
28
|
+
"tailwindcss": "^3.4.19",
|
|
26
29
|
"vite": "^5.4.1",
|
|
27
|
-
"vite-
|
|
28
|
-
"vite-
|
|
30
|
+
"vite-plugin-require": "^1.2.14",
|
|
31
|
+
"vite-svg-loader": "^5.1.0"
|
|
29
32
|
},
|
|
30
33
|
"repository": {
|
|
31
34
|
"type": "git",
|
package/src/App.vue
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
:show-name="true"
|
|
10
10
|
:optionsWithFlag="false"></langSelect>
|
|
11
11
|
<pay></pay>
|
|
12
|
+
|
|
13
|
+
<payment ></payment>
|
|
12
14
|
</div>
|
|
13
15
|
</template>
|
|
14
16
|
|
|
@@ -18,13 +20,15 @@ import UploadImage from "./components/common/upload-image.vue";
|
|
|
18
20
|
import {upload} from "./assets/data/upload";
|
|
19
21
|
import langSelect from "./components/common/lang-select.vue";
|
|
20
22
|
import pay from "./demo/pay-dialog-demo.vue";
|
|
23
|
+
import payment from "./components/common/payment.vue";
|
|
21
24
|
export default {
|
|
22
25
|
name: "App",
|
|
23
26
|
components: {
|
|
24
27
|
Logo,
|
|
25
28
|
UploadImage,
|
|
26
29
|
langSelect,
|
|
27
|
-
pay
|
|
30
|
+
pay,
|
|
31
|
+
payment
|
|
28
32
|
},
|
|
29
33
|
data() {
|
|
30
34
|
return {
|
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="c-payment-component">
|
|
3
|
+
<div class="text-center my-8 c-payment__header">
|
|
4
|
+
<slot name="header"></slot>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<!-- 右侧:支付操作区 -->
|
|
8
|
+
<div class="modal-main m-purchase-pay-main">
|
|
9
|
+
<!-- 支付方式切换 -->
|
|
10
|
+
<div class="grid grid-cols-2 gap-4 mb-8">
|
|
11
|
+
<slot name="payment-methods" :methods="paymentMethods" :active="activeMethod" :switch="switchPay">
|
|
12
|
+
<button
|
|
13
|
+
v-for="method in paymentMethods"
|
|
14
|
+
:key="method.value"
|
|
15
|
+
@click="switchPay(method.value)"
|
|
16
|
+
class="pay-method-btn rounded-2xl p-4 flex flex-col items-center gap-2 cursor-pointer"
|
|
17
|
+
:class="[
|
|
18
|
+
activeMethod === method.value ? 'active ' + method.activeClass : '',
|
|
19
|
+
method.customClass || '',
|
|
20
|
+
]"
|
|
21
|
+
>
|
|
22
|
+
<i :class="method.icon" class="text-2xl"></i>
|
|
23
|
+
<span class="text-xs font-medium">{{ method.label }}</span>
|
|
24
|
+
</button>
|
|
25
|
+
</slot>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- 二维码区 -->
|
|
29
|
+
<div class="flex flex-col items-center justify-center relative">
|
|
30
|
+
<div class="qr-wrapper">
|
|
31
|
+
<slot name="badge"></slot>
|
|
32
|
+
<div
|
|
33
|
+
class="w-44 h-44 bg-gray-50 rounded-xl flex items-center justify-center p-2 relative overflow-hidden"
|
|
34
|
+
>
|
|
35
|
+
<!-- Loading 状态 -->
|
|
36
|
+
<div
|
|
37
|
+
v-if="loading"
|
|
38
|
+
class="absolute inset-0 flex items-center justify-center bg-white rounded-xl"
|
|
39
|
+
>
|
|
40
|
+
<slot name="loading">
|
|
41
|
+
<i class="fas fa-spinner fa-spin text-3xl text-gray-400"></i>
|
|
42
|
+
</slot>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- 显示二维码 -->
|
|
46
|
+
<QrcodeVue
|
|
47
|
+
v-else-if="qrcodeUrl"
|
|
48
|
+
:value="qrcodeUrl"
|
|
49
|
+
class="w-full h-full"
|
|
50
|
+
:size="160"
|
|
51
|
+
level="H"
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<!-- 默认图标 -->
|
|
55
|
+
<slot v-else name="placeholder" :activeMethod="activeMethod">
|
|
56
|
+
<i
|
|
57
|
+
class="text-7xl"
|
|
58
|
+
:class="currentPaymentMethod?.icon || 'fas fa-qrcode text-gray-400'"
|
|
59
|
+
></i>
|
|
60
|
+
</slot>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="mt-6 flex items-center gap-2 text-gray-400 text-xs bg-white/5 px-4 py-2 rounded-full">
|
|
64
|
+
<i class="fas fa-lock text-emerald-500"></i>
|
|
65
|
+
{{ t("pay.secureChannel") }}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- 错误提示 -->
|
|
69
|
+
<div v-if="errorMsg" class="mt-4 text-red-400 text-xs text-center">
|
|
70
|
+
<slot name="error" :message="errorMsg">
|
|
71
|
+
{{ errorMsg }}
|
|
72
|
+
</slot>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- 底部插槽 -->
|
|
77
|
+
<div class="mt-8 text-center">
|
|
78
|
+
<slot name="footer" :texts="texts"></slot>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<script>
|
|
85
|
+
import QrcodeVue from "qrcode.vue";
|
|
86
|
+
import { createI18n } from "vue-i18n";
|
|
87
|
+
import enUs from "../../locale/en-us/payment";
|
|
88
|
+
import zhCn from "../../locale/zh-cn/payment";
|
|
89
|
+
|
|
90
|
+
// 创建独立的 i18n 实例
|
|
91
|
+
const getLocale = () => {
|
|
92
|
+
const storedLanguage = localStorage.getItem('lang');
|
|
93
|
+
if (!storedLanguage) {
|
|
94
|
+
return navigator.language;
|
|
95
|
+
} else {
|
|
96
|
+
return storedLanguage;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const payI18n = createI18n({
|
|
101
|
+
locale: getLocale(),
|
|
102
|
+
fallbackLocale: "zh-CN",
|
|
103
|
+
messages: {
|
|
104
|
+
"en-US": enUs,
|
|
105
|
+
"zh-CN": zhCn,
|
|
106
|
+
},
|
|
107
|
+
legacy: false, // 使用 Composition API 模式
|
|
108
|
+
warnHtmlInMessage: "off",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export default {
|
|
112
|
+
name: "PaymentComponent",
|
|
113
|
+
components: {
|
|
114
|
+
QrcodeVue,
|
|
115
|
+
},
|
|
116
|
+
emits: ["close", "success", "payment-type-change", "error"],
|
|
117
|
+
props: {
|
|
118
|
+
// ===== API 配置 =====
|
|
119
|
+
api: {
|
|
120
|
+
type: Object,
|
|
121
|
+
required: true,
|
|
122
|
+
validator: (value) => {
|
|
123
|
+
return (
|
|
124
|
+
typeof value.createOrder === "function" &&
|
|
125
|
+
typeof value.getOrderStatus === "function" &&
|
|
126
|
+
typeof value.getPaymentQRCode === "function" &&
|
|
127
|
+
typeof value.queryPaymentStatus === "function"
|
|
128
|
+
);
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// ===== 订单参数 =====
|
|
133
|
+
orderParams: {
|
|
134
|
+
type: Object,
|
|
135
|
+
required: true,
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// ===== 默认支付方式 =====
|
|
139
|
+
defaultPaymentType: {
|
|
140
|
+
type: String,
|
|
141
|
+
default: "wepay",
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// ===== 自动开始 =====
|
|
145
|
+
autoStart: {
|
|
146
|
+
type: Boolean,
|
|
147
|
+
default: false,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
data() {
|
|
151
|
+
return {
|
|
152
|
+
// 支付方式配置
|
|
153
|
+
paymentMethods: [
|
|
154
|
+
{
|
|
155
|
+
value: "wepay",
|
|
156
|
+
label: payI18n.global.t("pay.wechat"),
|
|
157
|
+
icon: "fab fa-weixin text-emerald-500",
|
|
158
|
+
activeClass: "text-emerald-500",
|
|
159
|
+
customClass: "wechat",
|
|
160
|
+
channel: "wepay",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
value: "alipay",
|
|
164
|
+
label: payI18n.global.t("pay.alipay"),
|
|
165
|
+
icon: "fab fa-alipay text-blue-500",
|
|
166
|
+
activeClass: "text-blue-500",
|
|
167
|
+
customClass: "alipay",
|
|
168
|
+
channel: {
|
|
169
|
+
mobile: "alipay_wap",
|
|
170
|
+
pc: "alipay_pc",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
|
|
175
|
+
// 文本配置
|
|
176
|
+
texts: {
|
|
177
|
+
createOrderFailed: payI18n.global.t("pay.createOrderFailed"),
|
|
178
|
+
orderTimeout: payI18n.global.t("pay.orderTimeout"),
|
|
179
|
+
orderFailed: payI18n.global.t("pay.orderFailed"),
|
|
180
|
+
getQrcodeFailed: payI18n.global.t("pay.getQrcodeFailed"),
|
|
181
|
+
paymentTimeout: payI18n.global.t("pay.paymentTimeout"),
|
|
182
|
+
paymentFailed: payI18n.global.t("pay.paymentFailed"),
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
// 轮询配置
|
|
186
|
+
pollingConfig: {
|
|
187
|
+
orderInterval: 2000, // 每次轮询订单状态的间隔(毫秒),2000ms = 2 秒
|
|
188
|
+
paymentInterval: 3000, // 每次轮询支付状态的间隔(毫秒),3000ms = 3 秒
|
|
189
|
+
maxRetries: 60, // 最多轮询次数,超过后视为超时/失败
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// 订单和支付信息
|
|
193
|
+
pendingOrderId: null,
|
|
194
|
+
paymentId: null,
|
|
195
|
+
price: null,
|
|
196
|
+
activeMethod: this.defaultPaymentType,
|
|
197
|
+
qrcode: null,
|
|
198
|
+
longUrl: null,
|
|
199
|
+
skipUrl: null,
|
|
200
|
+
paymentSuccess: false,
|
|
201
|
+
|
|
202
|
+
// UI 状态
|
|
203
|
+
loading: false,
|
|
204
|
+
errorMsg: "",
|
|
205
|
+
warningVisible: false,
|
|
206
|
+
checking: false,
|
|
207
|
+
|
|
208
|
+
// 轮询定时器
|
|
209
|
+
orderPollingTimer: null,
|
|
210
|
+
paymentPollingTimer: null,
|
|
211
|
+
orderPollingRetries: 0,
|
|
212
|
+
paymentPollingRetries: 0,
|
|
213
|
+
};
|
|
214
|
+
},
|
|
215
|
+
computed: {
|
|
216
|
+
isMobile() {
|
|
217
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
currentPaymentMethod() {
|
|
221
|
+
return this.paymentMethods.find((m) => m.value === this.activeMethod);
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
actualPayChannel() {
|
|
225
|
+
const method = this.currentPaymentMethod;
|
|
226
|
+
if (!method) return this.activeMethod;
|
|
227
|
+
|
|
228
|
+
if (typeof method.channel === "string") {
|
|
229
|
+
return method.channel;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (typeof method.channel === "object") {
|
|
233
|
+
return this.isMobile ? method.channel.mobile : method.channel.pc;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return this.activeMethod;
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
qrcodeUrl() {
|
|
240
|
+
return this.qrcode || this.longUrl || null;
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
watch: {
|
|
244
|
+
defaultPaymentType: {
|
|
245
|
+
handler(val) {
|
|
246
|
+
this.activeMethod = val;
|
|
247
|
+
},
|
|
248
|
+
immediate: true,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
mounted() {
|
|
252
|
+
if (this.autoStart) {
|
|
253
|
+
this.startPaymentProcess();
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
beforeUnmount() {
|
|
257
|
+
this.clearAllTimers();
|
|
258
|
+
},
|
|
259
|
+
methods: {
|
|
260
|
+
// ===== 公共方法 =====
|
|
261
|
+
// 翻译函数
|
|
262
|
+
t(key) {
|
|
263
|
+
return payI18n.global.t(key);
|
|
264
|
+
},
|
|
265
|
+
async startPaymentProcess() {
|
|
266
|
+
this.loading = true;
|
|
267
|
+
this.errorMsg = "";
|
|
268
|
+
await this.createOrder();
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
async switchPay(method) {
|
|
272
|
+
if (this.activeMethod === method) return;
|
|
273
|
+
|
|
274
|
+
this.activeMethod = method;
|
|
275
|
+
this.errorMsg = "";
|
|
276
|
+
this.$emit("payment-type-change", method);
|
|
277
|
+
|
|
278
|
+
if (this.paymentId) {
|
|
279
|
+
await this.getPaymentQrcode();
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
resetPaymentState() {
|
|
284
|
+
this.pendingOrderId = null;
|
|
285
|
+
this.paymentId = null;
|
|
286
|
+
this.price = null;
|
|
287
|
+
this.qrcode = null;
|
|
288
|
+
this.longUrl = null;
|
|
289
|
+
this.skipUrl = null;
|
|
290
|
+
this.paymentSuccess = false;
|
|
291
|
+
this.loading = false;
|
|
292
|
+
this.errorMsg = "";
|
|
293
|
+
this.warningVisible = false;
|
|
294
|
+
this.orderPollingRetries = 0;
|
|
295
|
+
this.paymentPollingRetries = 0;
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// ===== 支付流程 =====
|
|
299
|
+
async createOrder() {
|
|
300
|
+
try {
|
|
301
|
+
const res = await this.api.createOrder(this.orderParams);
|
|
302
|
+
if (res && res.id) {
|
|
303
|
+
this.pendingOrderId = res.id;
|
|
304
|
+
this.startOrderPolling();
|
|
305
|
+
} else {
|
|
306
|
+
throw new Error(this.texts.createOrderFailed);
|
|
307
|
+
}
|
|
308
|
+
} catch (err) {
|
|
309
|
+
this.handleError(this.texts.createOrderFailed, err);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
startOrderPolling() {
|
|
313
|
+
this.orderPollingRetries = 0;
|
|
314
|
+
|
|
315
|
+
this.orderPollingTimer = setInterval(async () => {
|
|
316
|
+
try {
|
|
317
|
+
this.orderPollingRetries++;
|
|
318
|
+
|
|
319
|
+
if (this.orderPollingRetries > this.pollingConfig.maxRetries) {
|
|
320
|
+
this.clearTimer("order");
|
|
321
|
+
this.handleError(this.texts.orderTimeout);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const response = await this.api.getOrderStatus(this.pendingOrderId);
|
|
326
|
+
|
|
327
|
+
if (response && response.status === 2) {
|
|
328
|
+
this.clearTimer("order");
|
|
329
|
+
this.paymentId = response.payment_id;
|
|
330
|
+
this.price = response.total_amount;
|
|
331
|
+
await this.getPaymentQrcode();
|
|
332
|
+
} else if (response && response.status === -1) {
|
|
333
|
+
this.clearTimer("order");
|
|
334
|
+
this.handleError(response.message || this.texts.orderFailed);
|
|
335
|
+
}
|
|
336
|
+
} catch (err) {
|
|
337
|
+
console.error("Failed to poll order status:", err);
|
|
338
|
+
}
|
|
339
|
+
}, this.pollingConfig.orderInterval);
|
|
340
|
+
},
|
|
341
|
+
async getPaymentQrcode() {
|
|
342
|
+
if (!this.paymentId) return;
|
|
343
|
+
|
|
344
|
+
this.loading = true;
|
|
345
|
+
this.errorMsg = "";
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const QR_PAY_MODE_ALIPAY_PC = 4;
|
|
349
|
+
const params = {};
|
|
350
|
+
if (this.actualPayChannel === "alipay_pc") {
|
|
351
|
+
params.qr_pay_mode = QR_PAY_MODE_ALIPAY_PC;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const response = await this.api.getPaymentQRCode(this.paymentId, this.actualPayChannel, params);
|
|
355
|
+
|
|
356
|
+
if (response) {
|
|
357
|
+
this.qrcode = response.qrcode || response;
|
|
358
|
+
this.longUrl = response.long_url || response.longUrl;
|
|
359
|
+
this.skipUrl = response.skip_url || response.skipUrl;
|
|
360
|
+
this.loading = false;
|
|
361
|
+
|
|
362
|
+
if (!this.paymentPollingTimer) {
|
|
363
|
+
this.startPaymentPolling();
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
throw new Error(this.texts.getQrcodeFailed);
|
|
367
|
+
}
|
|
368
|
+
} catch (err) {
|
|
369
|
+
this.handleError(this.texts.getQrcodeFailed, err);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
startPaymentPolling() {
|
|
374
|
+
this.paymentPollingRetries = 0;
|
|
375
|
+
|
|
376
|
+
this.paymentPollingTimer = setInterval(async () => {
|
|
377
|
+
try {
|
|
378
|
+
this.paymentPollingRetries++;
|
|
379
|
+
|
|
380
|
+
if (this.paymentPollingRetries > this.pollingConfig.maxRetries) {
|
|
381
|
+
this.clearTimer("payment");
|
|
382
|
+
this.warningVisible = true;
|
|
383
|
+
this.handleError(this.texts.paymentTimeout);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const response = await this.api.queryPaymentStatus(this.paymentId);
|
|
388
|
+
|
|
389
|
+
if (response && response.status === 1) {
|
|
390
|
+
this.clearTimer("payment");
|
|
391
|
+
this.paymentSuccess = true;
|
|
392
|
+
setTimeout(() => {
|
|
393
|
+
this.$emit("success", {
|
|
394
|
+
paymentId: this.paymentId,
|
|
395
|
+
orderId: this.pendingOrderId,
|
|
396
|
+
});
|
|
397
|
+
}, 500);
|
|
398
|
+
} else if (response && response.status === "failed") {
|
|
399
|
+
this.clearTimer("payment");
|
|
400
|
+
this.warningVisible = true;
|
|
401
|
+
this.handleError(this.texts.paymentFailed);
|
|
402
|
+
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
console.error("Failed to poll payment status:", err);
|
|
405
|
+
}
|
|
406
|
+
}, this.pollingConfig.paymentInterval);
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
// ===== 工具方法 =====
|
|
410
|
+
handleError(message, error) {
|
|
411
|
+
this.loading = false;
|
|
412
|
+
this.errorMsg = message;
|
|
413
|
+
this.$emit("error", { message, error });
|
|
414
|
+
if (error) {
|
|
415
|
+
console.error(message, error);
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
clearTimer(type) {
|
|
420
|
+
if (type === "order" && this.orderPollingTimer) {
|
|
421
|
+
clearInterval(this.orderPollingTimer);
|
|
422
|
+
this.orderPollingTimer = null;
|
|
423
|
+
} else if (type === "payment" && this.paymentPollingTimer) {
|
|
424
|
+
clearInterval(this.paymentPollingTimer);
|
|
425
|
+
this.paymentPollingTimer = null;
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
clearAllTimers() {
|
|
430
|
+
this.clearTimer("order");
|
|
431
|
+
this.clearTimer("payment");
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
</script>
|
|
436
|
+
|
|
437
|
+
<style lang="less">
|
|
438
|
+
.c-payment-component {
|
|
439
|
+
// max-width: 28rem;
|
|
440
|
+
flex: 1;
|
|
441
|
+
width: 100%;
|
|
442
|
+
background-color: #12141a;
|
|
443
|
+
color: white;
|
|
444
|
+
// border-radius: 1rem;
|
|
445
|
+
padding: 2rem;
|
|
446
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
447
|
+
border: 1px solid rgb(31 41 55);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.m-purchase-pay-main {
|
|
451
|
+
/* 左侧特权侧边栏 - 增加了图片背景风格 */
|
|
452
|
+
.modal-sidebar {
|
|
453
|
+
width: 35%;
|
|
454
|
+
position: relative;
|
|
455
|
+
padding: 40px 30px;
|
|
456
|
+
border-right: 1px solid rgba(255, 255, 255, 0.05);
|
|
457
|
+
overflow: hidden;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* 背景压暗与渐变层 */
|
|
461
|
+
.modal-sidebar::before {
|
|
462
|
+
content: "";
|
|
463
|
+
position: absolute;
|
|
464
|
+
inset: 0;
|
|
465
|
+
background: linear-gradient(180deg, rgba(15, 23, 42, 0.85) 0%, rgba(15, 23, 42, 0.95) 100%);
|
|
466
|
+
z-index: 1;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.modal-sidebar > * {
|
|
470
|
+
position: relative;
|
|
471
|
+
z-index: 2;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* 右侧支付主区 */
|
|
475
|
+
.modal-main {
|
|
476
|
+
width: 65%;
|
|
477
|
+
padding: 40px;
|
|
478
|
+
background: #0f172a;
|
|
479
|
+
position: relative;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.pay-method-btn {
|
|
483
|
+
border: 2px solid rgba(255, 255, 255, 0.05);
|
|
484
|
+
transition: all 0.3s;
|
|
485
|
+
}
|
|
486
|
+
.pay-method-btn.active.wechat {
|
|
487
|
+
border-color: #10b981;
|
|
488
|
+
background: rgba(16, 185, 129, 0.05);
|
|
489
|
+
}
|
|
490
|
+
.pay-method-btn.active.alipay {
|
|
491
|
+
border-color: #3b82f6;
|
|
492
|
+
background: rgba(59, 130, 246, 0.05);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.qr-wrapper {
|
|
496
|
+
background: white;
|
|
497
|
+
padding: 12px;
|
|
498
|
+
border-radius: 24px;
|
|
499
|
+
position: relative;
|
|
500
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.badge-discount {
|
|
504
|
+
position: absolute;
|
|
505
|
+
top: -10px;
|
|
506
|
+
right: -10px;
|
|
507
|
+
background: #ef4444;
|
|
508
|
+
color: white;
|
|
509
|
+
padding: 4px 10px;
|
|
510
|
+
border-radius: 10px;
|
|
511
|
+
font-size: 10px;
|
|
512
|
+
font-weight: 800;
|
|
513
|
+
transform: rotate(10deg);
|
|
514
|
+
box-shadow: 0 4px 10px rgba(239, 68, 68, 0.4);
|
|
515
|
+
animation: pulse 2s infinite;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.u-title {
|
|
519
|
+
color: #6a7282;
|
|
520
|
+
}
|
|
521
|
+
.m-purchase-feature-list {
|
|
522
|
+
.u-icon {
|
|
523
|
+
.size(32px);
|
|
524
|
+
flex-shrink: 0;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
.u-avatar {
|
|
528
|
+
.size(24px);
|
|
529
|
+
overflow: hidden;
|
|
530
|
+
.r(50%);
|
|
531
|
+
}
|
|
532
|
+
.u-mode {
|
|
533
|
+
// background: rgba(255, 255, 255, 0.1);
|
|
534
|
+
background-color: @primary;
|
|
535
|
+
color: #fff;
|
|
536
|
+
font-size: 10px;
|
|
537
|
+
font-weight: 600;
|
|
538
|
+
padding: 2px 6px;
|
|
539
|
+
border-radius: 8px;
|
|
540
|
+
margin-left: 8px;
|
|
541
|
+
vertical-align: middle;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
@keyframes pulse {
|
|
545
|
+
0%,
|
|
546
|
+
100% {
|
|
547
|
+
transform: rotate(10deg) scale(1);
|
|
548
|
+
}
|
|
549
|
+
50% {
|
|
550
|
+
transform: rotate(10deg) scale(1.1);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
@media (max-width: 640px) {
|
|
555
|
+
.modal-container {
|
|
556
|
+
flex-direction: column;
|
|
557
|
+
}
|
|
558
|
+
.modal-sidebar {
|
|
559
|
+
width: 100%;
|
|
560
|
+
padding: 20px;
|
|
561
|
+
}
|
|
562
|
+
.modal-main {
|
|
563
|
+
width: 100%;
|
|
564
|
+
padding: 20px;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* 如果需要微调动画,可以在这里添加 */
|
|
570
|
+
.fade-enter-active,
|
|
571
|
+
.fade-leave-active {
|
|
572
|
+
transition: opacity 0.3s ease;
|
|
573
|
+
}
|
|
574
|
+
.fade-enter-from,
|
|
575
|
+
.fade-leave-to {
|
|
576
|
+
opacity: 0;
|
|
577
|
+
}
|
|
578
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
pay: {
|
|
3
|
+
wechat: "WeChat Pay",
|
|
4
|
+
alipay: "Alipay",
|
|
5
|
+
secureChannel: "Official Encrypted Payment Channel, Active Immediately",
|
|
6
|
+
|
|
7
|
+
createOrderFailed: "Failed to create order, please try again",
|
|
8
|
+
orderTimeout: "Order processing timed out, please refresh and try again",
|
|
9
|
+
orderFailed: "Order processing failed, please try again",
|
|
10
|
+
getQrcodeFailed: "Failed to get payment QR code, please try again",
|
|
11
|
+
paymentTimeout: "Payment timed out, please contact customer service if you have paid",
|
|
12
|
+
paymentFailed: "Payment failed, please try again",
|
|
13
|
+
},
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
pay: {
|
|
3
|
+
wechat: "微信支付",
|
|
4
|
+
alipay: "支付宝",
|
|
5
|
+
secureChannel: "官方加密支付通道,即开即用",
|
|
6
|
+
|
|
7
|
+
createOrderFailed: "创建订单失败,请重试",
|
|
8
|
+
orderTimeout: "订单处理超时,请刷新重试",
|
|
9
|
+
orderFailed: "订单处理失败,请重试",
|
|
10
|
+
getQrcodeFailed: "获取支付二维码失败,请重试",
|
|
11
|
+
paymentTimeout: "支付超时,如已支付请联系客服",
|
|
12
|
+
paymentFailed: "支付失败,请重试",
|
|
13
|
+
},
|
|
14
|
+
}
|
package/src/main.js
CHANGED
package/vite.config.js
CHANGED
|
@@ -8,13 +8,13 @@ export default defineConfig({
|
|
|
8
8
|
plugins: [vue(), svgLoader(), vitePluginRequire.default()],
|
|
9
9
|
server: {
|
|
10
10
|
proxy: {
|
|
11
|
-
|
|
12
|
-
target:
|
|
11
|
+
"/api": {
|
|
12
|
+
target: "https://pay.dpying.com",
|
|
13
13
|
changeOrigin: true,
|
|
14
14
|
secure: false,
|
|
15
15
|
// 不需要 rewrite,保持完整路径
|
|
16
|
-
}
|
|
17
|
-
}
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
18
|
},
|
|
19
19
|
css: {
|
|
20
20
|
preprocessorOptions: {
|