feat(payment): 新增创建支付订单功能
- 添加创建订单页面,支持完整的支付信息填写 - 实现商品信息展示与自动填充功能 - 集成风控信息、收货地址和账单地址表单 - 支持自动生成商户订单号 - 实现表单验证和提交逻辑 - 添加订单创建成功后的跳转逻辑 - 集成Element Plus组件库优化界面交互 - 添加路由配置支持商品ID或链接码访问 - 实现价格格式化和数据显示优化 - 添加基础的错误处理和用户提示机制
This commit is contained in:
38
src/components/Loading.vue
Normal file
38
src/components/Loading.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div v-if="loading" class="loading-overlay">
|
||||
<el-loading
|
||||
:text="text"
|
||||
:spinner="spinner"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: '加载中...'
|
||||
},
|
||||
spinner: {
|
||||
type: String,
|
||||
default: 'el-icon-loading'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
</style>
|
||||
|
||||
32
src/main.js
Normal file
32
src/main.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
// 注册所有图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
|
||||
// 添加错误处理
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
console.error('Vue错误:', err)
|
||||
console.error('错误信息:', info)
|
||||
console.error('组件实例:', instance)
|
||||
}
|
||||
|
||||
// 挂载应用
|
||||
try {
|
||||
app.mount('#app')
|
||||
console.log('Vue应用已成功挂载')
|
||||
} catch (error) {
|
||||
console.error('应用挂载失败:', error)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ const routes = [
|
||||
{
|
||||
path: '/product/:id',
|
||||
name: 'ProductDetail',
|
||||
component: ProductDetail
|
||||
component: ProductDetail,
|
||||
// 支持商品ID(数字)或链接码(32位字符串)
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/create-order',
|
||||
|
||||
645
src/views/CreateOrder.vue
Normal file
645
src/views/CreateOrder.vue
Normal file
@@ -0,0 +1,645 @@
|
||||
<template>
|
||||
<div class="create-order">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>创建支付订单</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 商品信息展示卡片 -->
|
||||
<el-card v-if="productInfo" class="product-info-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="product-card-header">
|
||||
<span>商品信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="product-info-content">
|
||||
<div class="product-info-main">
|
||||
<el-image
|
||||
:src="productInfo.image"
|
||||
fit="cover"
|
||||
class="product-info-image"
|
||||
/>
|
||||
<div class="product-info-details">
|
||||
<div class="product-info-name">{{ productInfo.name }}</div>
|
||||
<div class="product-info-subtitle">{{ productInfo.subtitle }}</div>
|
||||
<!-- SKU信息 -->
|
||||
<div class="product-info-sku" v-if="productInfo.sku">
|
||||
<span class="sku-label">商品编码:</span>
|
||||
<span class="sku-value">{{ productInfo.sku }}</span>
|
||||
</div>
|
||||
<!-- 规格信息 -->
|
||||
<div class="product-info-specs" v-if="productInfo.specs && productInfo.specs.length > 0">
|
||||
<div
|
||||
v-for="(spec, index) in productInfo.specs"
|
||||
:key="index"
|
||||
class="product-spec-item"
|
||||
>
|
||||
<span class="spec-label">{{ spec.name }}:</span>
|
||||
<span class="spec-value">{{ spec.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 价格和数量 -->
|
||||
<div class="product-info-price">
|
||||
<span class="price-label">单价:</span>
|
||||
<span class="price-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price) }}</span>
|
||||
<span class="quantity-label">数量:</span>
|
||||
<span class="quantity-value">x{{ productInfo.quantity }}</span>
|
||||
<span class="total-label">小计:</span>
|
||||
<span class="total-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price * productInfo.quantity) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="150px"
|
||||
label-position="left"
|
||||
>
|
||||
<el-form-item label="商户订单号" prop="merchantTransactionId">
|
||||
<el-input
|
||||
v-model="form.merchantTransactionId"
|
||||
placeholder="请输入商户订单号(全局唯一)"
|
||||
clearable
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="generateOrderIdLocal"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
自动生成
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="交易金额" prop="amount">
|
||||
<el-input-number
|
||||
v-model="form.amount"
|
||||
:precision="2"
|
||||
:min="0.01"
|
||||
:max="999999.99"
|
||||
placeholder="请输入交易金额"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="交易币种" prop="currency">
|
||||
<el-select v-model="form.currency" placeholder="请选择币种" style="width: 100%">
|
||||
<el-option label="USD - 美元" value="USD" />
|
||||
<el-option label="EUR - 欧元" value="EUR" />
|
||||
<el-option label="GBP - 英镑" value="GBP" />
|
||||
<el-option label="CNY - 人民币" value="CNY" />
|
||||
<el-option label="JPY - 日元" value="JPY" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="交易类型" prop="paymentType">
|
||||
<el-radio-group v-model="form.paymentType">
|
||||
<el-radio label="SALE">直接付款</el-radio>
|
||||
<el-radio label="AUTH">预授权</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商户用户ID" prop="merchantUserId">
|
||||
<el-input
|
||||
v-model="form.merchantUserId"
|
||||
placeholder="请输入商户用户ID(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="结果重定向URL" prop="shopperResultUrl">
|
||||
<el-input
|
||||
v-model="form.shopperResultUrl"
|
||||
placeholder="支付完成后跳转的URL"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="取消重定向URL" prop="shopperCancelUrl">
|
||||
<el-input
|
||||
v-model="form.shopperCancelUrl"
|
||||
placeholder="取消支付后跳转的URL"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider>风控信息</el-divider>
|
||||
|
||||
<el-form-item label="客户姓名" prop="riskInfo.customer.firstName">
|
||||
<el-input
|
||||
v-model="form.riskInfo.customer.firstName"
|
||||
placeholder="名"
|
||||
style="width: 48%"
|
||||
/>
|
||||
<el-input
|
||||
v-model="form.riskInfo.customer.lastName"
|
||||
placeholder="姓"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="客户邮箱" prop="riskInfo.customer.email">
|
||||
<el-input
|
||||
v-model="form.riskInfo.customer.email"
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="客户电话" prop="riskInfo.customer.phone">
|
||||
<el-input
|
||||
v-model="form.riskInfo.customer.phone"
|
||||
placeholder="请输入电话"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider>商品信息</el-divider>
|
||||
|
||||
<el-form-item label="商品名称" prop="riskInfo.goods.0.name">
|
||||
<el-input
|
||||
v-model="form.riskInfo.goods[0].name"
|
||||
placeholder="请输入商品名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品描述" prop="riskInfo.goods.0.description">
|
||||
<el-input
|
||||
v-model="form.riskInfo.goods[0].description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入商品描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品单价" prop="riskInfo.goods.0.averageUnitPrice">
|
||||
<el-input-number
|
||||
v-model="form.riskInfo.goods[0].averageUnitPrice"
|
||||
:precision="2"
|
||||
:min="0.01"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="商品数量" prop="riskInfo.goods.0.number">
|
||||
<el-input-number
|
||||
v-model="form.riskInfo.goods[0].number"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider>收货地址</el-divider>
|
||||
|
||||
<el-form-item label="收货人姓名" prop="riskInfo.shipping.firstName">
|
||||
<el-input
|
||||
v-model="form.riskInfo.shipping.firstName"
|
||||
placeholder="名"
|
||||
style="width: 48%"
|
||||
/>
|
||||
<el-input
|
||||
v-model="form.riskInfo.shipping.lastName"
|
||||
placeholder="姓"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="街道地址" prop="riskInfo.shipping.street">
|
||||
<el-input
|
||||
v-model="form.riskInfo.shipping.street"
|
||||
placeholder="请输入街道地址"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="城市" prop="riskInfo.shipping.city">
|
||||
<el-input
|
||||
v-model="form.riskInfo.shipping.city"
|
||||
placeholder="请输入城市"
|
||||
style="width: 48%"
|
||||
/>
|
||||
<el-input
|
||||
v-model="form.riskInfo.shipping.state"
|
||||
placeholder="州/省(可选)"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮编" prop="riskInfo.shipping.postcode">
|
||||
<el-input
|
||||
v-model="form.riskInfo.shipping.postcode"
|
||||
placeholder="请输入邮编"
|
||||
style="width: 48%"
|
||||
/>
|
||||
<el-select
|
||||
v-model="form.riskInfo.shipping.country"
|
||||
placeholder="国家"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
>
|
||||
<el-option label="US - 美国" value="US" />
|
||||
<el-option label="CN - 中国" value="CN" />
|
||||
<el-option label="GB - 英国" value="GB" />
|
||||
<el-option label="DE - 德国" value="DE" />
|
||||
<el-option label="FR - 法国" value="FR" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider>账单地址</el-divider>
|
||||
|
||||
<el-form-item label="账单人姓名" prop="riskInfo.billing.firstName">
|
||||
<el-input
|
||||
v-model="form.riskInfo.billing.firstName"
|
||||
placeholder="名"
|
||||
style="width: 48%"
|
||||
/>
|
||||
<el-input
|
||||
v-model="form.riskInfo.billing.lastName"
|
||||
placeholder="姓"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单街道地址" prop="riskInfo.billing.street">
|
||||
<el-input
|
||||
v-model="form.riskInfo.billing.street"
|
||||
placeholder="请输入街道地址"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单城市" prop="riskInfo.billing.city">
|
||||
<el-input
|
||||
v-model="form.riskInfo.billing.city"
|
||||
placeholder="请输入城市"
|
||||
style="width: 48%"
|
||||
/>
|
||||
<el-input
|
||||
v-model="form.riskInfo.billing.state"
|
||||
placeholder="州/省(可选)"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单邮编" prop="riskInfo.billing.postcode">
|
||||
<el-input
|
||||
v-model="form.riskInfo.billing.postcode"
|
||||
placeholder="请输入邮编"
|
||||
style="width: 48%"
|
||||
/>
|
||||
<el-select
|
||||
v-model="form.riskInfo.billing.country"
|
||||
placeholder="国家"
|
||||
style="width: 48%; margin-left: 4%"
|
||||
>
|
||||
<el-option label="US - 美国" value="US" />
|
||||
<el-option label="CN - 中国" value="CN" />
|
||||
<el-option label="GB - 英国" value="GB" />
|
||||
<el-option label="DE - 德国" value="DE" />
|
||||
<el-option label="FR - 法国" value="FR" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm" :loading="loading">
|
||||
创建订单并支付
|
||||
</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { createPaymentOrder } from '../api/payment'
|
||||
import { generateOrderId, formatAmount } from '../utils/helpers'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const productInfo = ref(null)
|
||||
|
||||
const form = reactive({
|
||||
merchantTransactionId: '',
|
||||
amount: '',
|
||||
currency: 'USD',
|
||||
paymentType: 'SALE',
|
||||
merchantUserId: '',
|
||||
shopperResultUrl: `${window.location.origin}/result`,
|
||||
shopperCancelUrl: `${window.location.origin}/result`,
|
||||
riskInfo: {
|
||||
customer: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
registerTime: new Date().toISOString().replace(/[-:]/g, '').split('.')[0],
|
||||
registerIp: '',
|
||||
registerTerminal: 'PC',
|
||||
registerRange: '1',
|
||||
orderTime: new Date().toISOString().replace(/[-:]/g, '').split('.')[0],
|
||||
orderIp: '',
|
||||
orderCountry: 'US'
|
||||
},
|
||||
goods: [{
|
||||
name: '',
|
||||
description: '',
|
||||
averageUnitPrice: '',
|
||||
number: '1',
|
||||
virtualProduct: 'N'
|
||||
}],
|
||||
shipping: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
street: '',
|
||||
city: '',
|
||||
state: '',
|
||||
postcode: '',
|
||||
country: 'US'
|
||||
},
|
||||
billing: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
street: '',
|
||||
city: '',
|
||||
state: '',
|
||||
postcode: '',
|
||||
country: 'US'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const rules = {
|
||||
merchantTransactionId: [
|
||||
{ required: true, message: '请输入商户订单号', trigger: 'blur' }
|
||||
],
|
||||
amount: [
|
||||
{ required: true, message: '请输入交易金额', trigger: 'blur' }
|
||||
],
|
||||
currency: [
|
||||
{ required: true, message: '请选择交易币种', trigger: 'change' }
|
||||
],
|
||||
paymentType: [
|
||||
{ required: true, message: '请选择交易类型', trigger: 'change' }
|
||||
],
|
||||
shopperResultUrl: [
|
||||
{ required: true, message: '请输入结果重定向URL', trigger: 'blur' }
|
||||
],
|
||||
shopperCancelUrl: [
|
||||
{ required: true, message: '请输入取消重定向URL', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const generateOrderIdLocal = () => {
|
||||
form.merchantTransactionId = generateOrderId()
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
ElMessage.error('请填写完整信息')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 格式化数据
|
||||
const requestData = {
|
||||
...form,
|
||||
amount: form.amount.toFixed(2),
|
||||
signType: 'MD5',
|
||||
language: 'zh',
|
||||
threeDSecure: 'N',
|
||||
riskInfo: {
|
||||
...form.riskInfo,
|
||||
goods: form.riskInfo.goods.map(g => ({
|
||||
...g,
|
||||
averageUnitPrice: g.averageUnitPrice.toFixed(2),
|
||||
number: g.number.toString()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const response = await createPaymentOrder(requestData)
|
||||
|
||||
if (response.code === '0000') {
|
||||
ElMessage.success('订单创建成功')
|
||||
// 跳转到收银台页面
|
||||
router.push({
|
||||
path: '/checkout',
|
||||
query: { token: response.data.token }
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(response.message || '创建订单失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建订单失败:', error)
|
||||
ElMessage.error(error.response?.data?.message || '创建订单失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
if (!formRef.value) return
|
||||
formRef.value.resetFields()
|
||||
generateOrderIdLocal()
|
||||
}
|
||||
|
||||
// 格式化价格
|
||||
const formatPrice = (price) => {
|
||||
return formatAmount(price)
|
||||
}
|
||||
|
||||
// 从路由参数获取商品信息
|
||||
onMounted(() => {
|
||||
if (route.query.data) {
|
||||
try {
|
||||
const data = JSON.parse(decodeURIComponent(route.query.data))
|
||||
if (data.product) {
|
||||
productInfo.value = data.product
|
||||
|
||||
// 自动填充表单
|
||||
form.amount = (data.product.price * data.product.quantity).toFixed(2)
|
||||
form.currency = data.product.currency || 'USD'
|
||||
form.riskInfo.goods[0].name = data.product.name
|
||||
form.riskInfo.goods[0].description = `${data.product.subtitle} ${data.product.specs.map(s => `${s.name}:${s.label}`).join(';')}`
|
||||
form.riskInfo.goods[0].averageUnitPrice = data.product.price.toFixed(2)
|
||||
form.riskInfo.goods[0].number = data.product.quantity.toString()
|
||||
// 如果有SKU,添加到商品描述中
|
||||
if (data.product.sku) {
|
||||
form.riskInfo.goods[0].sku = data.product.sku
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析商品信息失败:', error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化时生成订单号
|
||||
generateOrderIdLocal()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.create-order {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 商品信息卡片 */
|
||||
.product-info-card {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.product-card-header {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.product-info-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.product-info-main {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.product-info-image {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-info-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.product-info-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.product-info-subtitle {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.product-info-sku {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.sku-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sku-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
font-family: 'Courier New', monospace;
|
||||
background: #fff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.product-info-specs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.product-spec-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.spec-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.spec-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.product-info-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 12px;
|
||||
background: #f9fafb;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.price-label,
|
||||
.quantity-label,
|
||||
.total-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.quantity-value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
margin-left: auto;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 18px;
|
||||
color: #f56c6c;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
152
src/views/OrderQuery.vue
Normal file
152
src/views/OrderQuery.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="order-query">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>订单查询</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :inline="true" :model="queryForm" class="query-form">
|
||||
<el-form-item label="商户订单号">
|
||||
<el-input
|
||||
v-model="queryForm.merchantTransactionId"
|
||||
placeholder="请输入商户订单号"
|
||||
clearable
|
||||
style="width: 300px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||
查询
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div v-if="orderData" class="order-detail">
|
||||
<el-descriptions title="订单详情" :column="2" border>
|
||||
<el-descriptions-item label="商户订单号">
|
||||
{{ orderData.merchantTransactionId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="PingPong交易流水号">
|
||||
{{ orderData.transactionId || '暂无' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="订单状态">
|
||||
<el-tag :type="getStatusTagType(orderData.status)">
|
||||
{{ getStatusText(orderData.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="交易金额">
|
||||
{{ orderData.amount }} {{ orderData.currency }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="交易类型">
|
||||
{{ orderData.paymentType === 'SALE' ? '直接付款' : '预授权' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ orderData.createTime }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="action-buttons" style="margin-top: 20px">
|
||||
<el-button type="primary" @click="goToPay" v-if="orderData.status === 'PENDING'">
|
||||
继续支付
|
||||
</el-button>
|
||||
<el-button @click="goToCreate">创建新订单</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-else-if="!loading" description="请输入订单号进行查询" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getOrderStatus } from '../api/payment'
|
||||
import { getStatusText, getStatusTagType } from '../utils/helpers'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const orderData = ref(null)
|
||||
|
||||
const queryForm = reactive({
|
||||
merchantTransactionId: ''
|
||||
})
|
||||
|
||||
const handleQuery = async () => {
|
||||
if (!queryForm.merchantTransactionId) {
|
||||
ElMessage.warning('请输入商户订单号')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
orderData.value = null
|
||||
|
||||
try {
|
||||
const response = await getOrderStatus(queryForm.merchantTransactionId)
|
||||
if (response.code === '0000' && response.data) {
|
||||
orderData.value = response.data
|
||||
ElMessage.success('查询成功')
|
||||
} else {
|
||||
ElMessage.error(response.message || '订单不存在')
|
||||
orderData.value = null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询订单失败:', error)
|
||||
ElMessage.error('查询失败,请稍后重试')
|
||||
orderData.value = null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const goToPay = () => {
|
||||
if (orderData.value && orderData.value.token) {
|
||||
router.push({
|
||||
path: '/checkout',
|
||||
query: { token: orderData.value.token }
|
||||
})
|
||||
} else {
|
||||
ElMessage.warning('该订单无法继续支付,请创建新订单')
|
||||
}
|
||||
}
|
||||
|
||||
const goToCreate = () => {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.order-query {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.query-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.order-detail {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.action-buttons .el-button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user