|
|
@@ -0,0 +1,559 @@
|
|
|
+<template>
|
|
|
+ <div class="payment-container">
|
|
|
+ <h2>💰 支付管理 (一键生成)</h2>
|
|
|
+
|
|
|
+ <div class="quick-action-section">
|
|
|
+ <div class="action-group">
|
|
|
+ <h3>快速操作 - 自动生成随机数据</h3>
|
|
|
+ <div class="button-grid">
|
|
|
+ <button @click="handleCreateAndPay" class="btn btn-success btn-large">
|
|
|
+ <div class="btn-icon">💳</div>
|
|
|
+ <div class="btn-text">创建+支付</div>
|
|
|
+ <div class="btn-desc">创建订单并支付</div>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <button @click="handleBatchPay" class="btn btn-primary btn-large">
|
|
|
+ <div class="btn-icon">📊</div>
|
|
|
+ <div class="btn-text">批量支付</div>
|
|
|
+ <div class="btn-desc">批量创建并支付10个</div>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <button @click="handlePaymentFailures" class="btn btn-warning btn-large">
|
|
|
+ <div class="btn-icon">⚠️</div>
|
|
|
+ <div class="btn-text">测试失败场景</div>
|
|
|
+ <div class="btn-desc">测试支付失败重试</div>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <span v-if="message" :class="['message', message.type]">{{ message.text }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="result-section" v-if="latestPayment">
|
|
|
+ <h3>最新支付记录</h3>
|
|
|
+ <div class="payment-info">
|
|
|
+ <p><strong>支付ID:</strong> <code>{{ latestPayment.paymentId }}</code></p>
|
|
|
+ <p><strong>订单ID:</strong> <code>{{ latestPayment.orderId }}</code></p>
|
|
|
+ <p><strong>金额:</strong> ¥{{ latestPayment.amount }}</p>
|
|
|
+ <p><strong>状态:</strong> <span :class="['status-badge', latestPayment.status.toLowerCase()]">{{ latestPayment.status }}</span></p>
|
|
|
+ <p><strong>支付方式:</strong> <span class="method-badge">{{ latestPayment.paymentMethod }}</span></p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stats-section" v-if="operationCount > 0">
|
|
|
+ <h3>支付统计</h3>
|
|
|
+ <div class="stats-grid">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ operationCount }}</div>
|
|
|
+ <div class="stat-label">总操作数</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ successCount }}</div>
|
|
|
+ <div class="stat-label">成功</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ failureCount }}</div>
|
|
|
+ <div class="stat-label">失败</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-number">{{ totalAmount }}</div>
|
|
|
+ <div class="stat-label">总金额 (¥)</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { orderAPI, paymentAPI } from '../api/index.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'PaymentComponent',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ message: null,
|
|
|
+ latestPayment: null,
|
|
|
+ operationCount: 0,
|
|
|
+ successCount: 0,
|
|
|
+ failureCount: 0,
|
|
|
+ totalAmount: 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 生成随机用户ID
|
|
|
+ generateUserId() {
|
|
|
+ const names = ['alice', 'bob', 'charlie', 'david', 'emma', 'frank', 'grace', 'henry']
|
|
|
+ const name = names[Math.floor(Math.random() * names.length)]
|
|
|
+ return `${name}-${Math.floor(Math.random() * 10000)}`
|
|
|
+ },
|
|
|
+
|
|
|
+ // 生成随机价格
|
|
|
+ generatePrice() {
|
|
|
+ return (Math.random() * 500 + 10).toFixed(2)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 生成随机描述
|
|
|
+ generateDescription() {
|
|
|
+ const descriptions = ['回收电子产品', '二手家电处理', '旧家具回收', '废金属回收', '包装材料回收']
|
|
|
+ return descriptions[Math.floor(Math.random() * descriptions.length)]
|
|
|
+ },
|
|
|
+
|
|
|
+ // 生成随机支付方式
|
|
|
+ generatePaymentMethod() {
|
|
|
+ return ['CARD', 'WECHAT', 'ALIPAY'][Math.floor(Math.random() * 3)]
|
|
|
+ },
|
|
|
+
|
|
|
+ async handleCreateAndPay() {
|
|
|
+ try {
|
|
|
+ // 创建订单
|
|
|
+ const createRequest = {
|
|
|
+ userId: this.generateUserId(),
|
|
|
+ price: parseFloat(this.generatePrice()),
|
|
|
+ description: this.generateDescription()
|
|
|
+ }
|
|
|
+
|
|
|
+ const createResponse = await orderAPI.create(createRequest)
|
|
|
+ if (createResponse.data.code !== 0) {
|
|
|
+ throw new Error(createResponse.data.msg)
|
|
|
+ }
|
|
|
+
|
|
|
+ const orderId = createResponse.data.data.orderId
|
|
|
+ const amount = parseFloat(createResponse.data.data.price)
|
|
|
+
|
|
|
+ // 支付订单
|
|
|
+ const paymentRequest = {
|
|
|
+ orderId: orderId,
|
|
|
+ amount: amount,
|
|
|
+ paymentMethod: this.generatePaymentMethod()
|
|
|
+ }
|
|
|
+
|
|
|
+ const payResponse = await paymentAPI.pay(paymentRequest)
|
|
|
+ this.operationCount++
|
|
|
+
|
|
|
+ if (payResponse.data.code === 0) {
|
|
|
+ this.latestPayment = payResponse.data.data
|
|
|
+ this.successCount++
|
|
|
+ this.totalAmount += amount
|
|
|
+ this.message = {
|
|
|
+ type: 'success',
|
|
|
+ text: `✅ 支付成功! 支付ID: ${payResponse.data.data.paymentId}`
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.failureCount++
|
|
|
+ this.message = {
|
|
|
+ type: 'error',
|
|
|
+ text: `❌ 支付失败: ${payResponse.data.msg}`
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.operationCount++
|
|
|
+ this.failureCount++
|
|
|
+ this.message = { type: 'error', text: `❌ ${error.message}` }
|
|
|
+ }
|
|
|
+
|
|
|
+ setTimeout(() => { this.message = null }, 3000)
|
|
|
+ },
|
|
|
+
|
|
|
+ async handleBatchPay() {
|
|
|
+ const startTime = Date.now()
|
|
|
+ this.message = { type: 'info', text: '⏳ 正在批量创建并支付10个订单...' }
|
|
|
+
|
|
|
+ let successCount = 0
|
|
|
+ let failureCount = 0
|
|
|
+ let totalAmount = 0
|
|
|
+
|
|
|
+ for (let i = 0; i < 10; i++) {
|
|
|
+ try {
|
|
|
+ // 创建订单
|
|
|
+ const createRequest = {
|
|
|
+ userId: this.generateUserId(),
|
|
|
+ price: parseFloat(this.generatePrice()),
|
|
|
+ description: this.generateDescription()
|
|
|
+ }
|
|
|
+
|
|
|
+ const createResponse = await orderAPI.create(createRequest)
|
|
|
+ if (createResponse.data.code !== 0) {
|
|
|
+ failureCount++
|
|
|
+ this.failureCount++
|
|
|
+ this.operationCount++
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ const orderId = createResponse.data.data.orderId
|
|
|
+ const amount = parseFloat(createResponse.data.data.price)
|
|
|
+
|
|
|
+ // 支付订单
|
|
|
+ const paymentRequest = {
|
|
|
+ orderId: orderId,
|
|
|
+ amount: amount,
|
|
|
+ paymentMethod: this.generatePaymentMethod()
|
|
|
+ }
|
|
|
+
|
|
|
+ const payResponse = await paymentAPI.pay(paymentRequest)
|
|
|
+ this.operationCount++
|
|
|
+
|
|
|
+ if (payResponse.data.code === 0) {
|
|
|
+ this.latestPayment = payResponse.data.data
|
|
|
+ successCount++
|
|
|
+ this.successCount++
|
|
|
+ totalAmount += amount
|
|
|
+ } else {
|
|
|
+ failureCount++
|
|
|
+ this.failureCount++
|
|
|
+ }
|
|
|
+
|
|
|
+ // 避免请求过快,每个请求间隔150ms
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 150))
|
|
|
+ } catch (error) {
|
|
|
+ failureCount++
|
|
|
+ this.failureCount++
|
|
|
+ this.operationCount++
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const duration = Date.now() - startTime
|
|
|
+ this.totalAmount += totalAmount
|
|
|
+ this.message = {
|
|
|
+ type: successCount > 0 ? 'success' : 'error',
|
|
|
+ text: `✅ 批量支付完成! 成功: ${successCount}, 失败: ${failureCount}, 总金额: ¥${totalAmount.toFixed(2)}, 耗时: ${duration}ms`
|
|
|
+ }
|
|
|
+
|
|
|
+ setTimeout(() => { this.message = null }, 5000)
|
|
|
+ },
|
|
|
+
|
|
|
+ async handlePaymentFailures() {
|
|
|
+ const startTime = Date.now()
|
|
|
+ this.message = { type: 'info', text: '⏳ 测试支付失败重试...' }
|
|
|
+
|
|
|
+ let finalSuccess = false
|
|
|
+ let retryCount = 0
|
|
|
+ const maxRetries = 5
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 创建订单
|
|
|
+ const createRequest = {
|
|
|
+ userId: this.generateUserId(),
|
|
|
+ price: parseFloat(this.generatePrice()),
|
|
|
+ description: '测试失败重试'
|
|
|
+ }
|
|
|
+
|
|
|
+ const createResponse = await orderAPI.create(createRequest)
|
|
|
+ if (createResponse.data.code !== 0) {
|
|
|
+ throw new Error(createResponse.data.msg)
|
|
|
+ }
|
|
|
+
|
|
|
+ const orderId = createResponse.data.data.orderId
|
|
|
+ const amount = parseFloat(createResponse.data.data.price)
|
|
|
+
|
|
|
+ // 重试支付,直到成功或达到最大重试次数
|
|
|
+ while (retryCount < maxRetries && !finalSuccess) {
|
|
|
+ try {
|
|
|
+ const paymentRequest = {
|
|
|
+ orderId: orderId,
|
|
|
+ amount: amount,
|
|
|
+ paymentMethod: this.generatePaymentMethod()
|
|
|
+ }
|
|
|
+
|
|
|
+ const payResponse = await paymentAPI.pay(paymentRequest)
|
|
|
+ this.operationCount++
|
|
|
+ retryCount++
|
|
|
+
|
|
|
+ if (payResponse.data.code === 0) {
|
|
|
+ this.latestPayment = payResponse.data.data
|
|
|
+ this.successCount++
|
|
|
+ this.totalAmount += amount
|
|
|
+ finalSuccess = true
|
|
|
+ } else {
|
|
|
+ this.failureCount++
|
|
|
+ }
|
|
|
+
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 300))
|
|
|
+ } catch (error) {
|
|
|
+ this.failureCount++
|
|
|
+ this.operationCount++
|
|
|
+ retryCount++
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const duration = Date.now() - startTime
|
|
|
+ if (finalSuccess) {
|
|
|
+ this.message = {
|
|
|
+ type: 'success',
|
|
|
+ text: `✅ 第${retryCount}次重试成功! 耗时: ${duration}ms`
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.message = {
|
|
|
+ type: 'error',
|
|
|
+ text: `❌ 连续失败${maxRetries}次,放弃重试。耗时: ${duration}ms`
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.operationCount++
|
|
|
+ this.failureCount++
|
|
|
+ this.message = { type: 'error', text: `❌ ${error.message}` }
|
|
|
+ }
|
|
|
+
|
|
|
+ setTimeout(() => { this.message = null }, 5000)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.payment-container {
|
|
|
+ padding: 20px;
|
|
|
+ max-width: 900px;
|
|
|
+ margin: 20px auto;
|
|
|
+}
|
|
|
+
|
|
|
+h2 {
|
|
|
+ color: #333;
|
|
|
+ border-bottom: 2px solid #67C26A;
|
|
|
+ padding-bottom: 10px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+h3 {
|
|
|
+ color: #555;
|
|
|
+ margin-top: 20px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 快速操作区域 */
|
|
|
+.quick-action-section {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ padding: 30px;
|
|
|
+ border-radius: 12px;
|
|
|
+ margin: 20px 0;
|
|
|
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.action-group h3 {
|
|
|
+ color: white;
|
|
|
+ margin-top: 0;
|
|
|
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.button-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
|
+ gap: 15px;
|
|
|
+ margin: 20px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.btn {
|
|
|
+ padding: 12px 20px;
|
|
|
+ border: none;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-large {
|
|
|
+ padding: 20px;
|
|
|
+ min-height: 120px;
|
|
|
+ justify-content: center;
|
|
|
+ font-weight: 600;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-large:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-icon {
|
|
|
+ font-size: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-text {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ opacity: 0.9;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-primary {
|
|
|
+ background-color: #409EFF;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-primary:hover {
|
|
|
+ background-color: #66b1ff;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-success {
|
|
|
+ background-color: #67C26A;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-success:hover {
|
|
|
+ background-color: #85CE61;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-warning {
|
|
|
+ background-color: #E6A23C;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-warning:hover {
|
|
|
+ background-color: #ebb563;
|
|
|
+}
|
|
|
+
|
|
|
+.message {
|
|
|
+ display: block;
|
|
|
+ margin-top: 15px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ animation: slideIn 0.3s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+.message.success {
|
|
|
+ background-color: #f0f9ff;
|
|
|
+ color: #67C26A;
|
|
|
+ border: 1px solid #67C26A;
|
|
|
+}
|
|
|
+
|
|
|
+.message.error {
|
|
|
+ background-color: #fef0f0;
|
|
|
+ color: #F56C6C;
|
|
|
+ border: 1px solid #F56C6C;
|
|
|
+}
|
|
|
+
|
|
|
+.message.warning {
|
|
|
+ background-color: #fdf6ec;
|
|
|
+ color: #E6A23C;
|
|
|
+ border: 1px solid #E6A23C;
|
|
|
+}
|
|
|
+
|
|
|
+.message.info {
|
|
|
+ background-color: #f0f2f5;
|
|
|
+ color: #909399;
|
|
|
+ border: 1px solid #909399;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-10px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 结果展示区域 */
|
|
|
+.result-section {
|
|
|
+ background: #f5f7fa;
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-top: 20px;
|
|
|
+ border-left: 4px solid #67C26A;
|
|
|
+}
|
|
|
+
|
|
|
+.payment-info {
|
|
|
+ background: white;
|
|
|
+ padding: 16px;
|
|
|
+ border-radius: 6px;
|
|
|
+ line-height: 1.8;
|
|
|
+}
|
|
|
+
|
|
|
+.payment-info p {
|
|
|
+ margin: 8px 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.payment-info code {
|
|
|
+ background: #f0f0f0;
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 3px;
|
|
|
+ font-family: 'Courier New', monospace;
|
|
|
+ color: #d63384;
|
|
|
+}
|
|
|
+
|
|
|
+.status-badge {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 4px 12px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+ text-transform: uppercase;
|
|
|
+}
|
|
|
+
|
|
|
+.status-badge.pending {
|
|
|
+ background-color: #fff7e6;
|
|
|
+ color: #b37200;
|
|
|
+}
|
|
|
+
|
|
|
+.status-badge.success {
|
|
|
+ background-color: #f6ffed;
|
|
|
+ color: #135200;
|
|
|
+}
|
|
|
+
|
|
|
+.status-badge.failed {
|
|
|
+ background-color: #fff1f0;
|
|
|
+ color: #92400e;
|
|
|
+}
|
|
|
+
|
|
|
+.status-badge.refunded {
|
|
|
+ background-color: #e6f7ff;
|
|
|
+ color: #003a8c;
|
|
|
+}
|
|
|
+
|
|
|
+.method-badge {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 3px 10px;
|
|
|
+ background-color: #e6f7ff;
|
|
|
+ color: #0050b3;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* 统计区域 */
|
|
|
+.stats-section {
|
|
|
+ background: white;
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-top: 20px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
|
+ gap: 15px;
|
|
|
+ margin-top: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-item {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ text-align: center;
|
|
|
+ color: white;
|
|
|
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.stat-number {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ font-size: 14px;
|
|
|
+ opacity: 0.9;
|
|
|
+}
|
|
|
+</style>
|