Compare commits
6 Commits
b2bbbf8c44
...
dev-versio
| Author | SHA1 | Date | |
|---|---|---|---|
| 23e535562d | |||
| 6d725b51cb | |||
| 857f46ad17 | |||
| e251153d14 | |||
| 3662ee072b | |||
| 3bdb2ff5f3 |
6
.env.production
Normal file
6
.env.production
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# 鐢熶骇鐜閰嶇疆
|
||||||
|
# 浣跨敤鐩稿璺緞锛岄€氳繃Nginx浠g悊鍒板悗绔?
|
||||||
|
# API鍩虹URL锛堢浉瀵硅矾寰勶紝閫氳繃Nginx浠g悊锛?# 閲嶈锛氬繀椤讳娇鐢ㄧ浉瀵硅矾寰?/api锛屼笉瑕佷娇鐢ㄥ畬鏁碪RL
|
||||||
|
VITE_API_BASE_URL=/api
|
||||||
|
|
||||||
|
# PingPong妯″紡锛坰andbox/production锛?VITE_PINGPONG_MODE=sandbox
|
||||||
@@ -73,7 +73,7 @@ MTKJPAY-FRONT/
|
|||||||
在 `src/api/request.js` 中配置后端API地址:
|
在 `src/api/request.js` 中配置后端API地址:
|
||||||
```javascript
|
```javascript
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
baseURL: 'http://localhost:8082/api',
|
baseURL: '/api', // 使用相对路径,通过Nginx代理
|
||||||
timeout: 10000
|
timeout: 10000
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
<h1>MT Pay 管理系统</h1>
|
<h1>MT Pay 管理系统</h1>
|
||||||
<div class="nav-menu">
|
<div class="nav-menu">
|
||||||
<router-link
|
<router-link
|
||||||
to="/query"
|
to="/manage/order"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ active: activeIndex === '/query' || activeIndex.startsWith('/query') }"
|
:class="{ active: activeIndex === '/manage/order' || activeIndex.startsWith('/manage/order') }"
|
||||||
>
|
>
|
||||||
订单查询
|
订单管理
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
to="/manage/product"
|
to="/manage/product"
|
||||||
|
|||||||
@@ -42,3 +42,14 @@ export function calculateCurrencyConversion(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询订单列表(支持分页和多条件查询)
|
||||||
|
*/
|
||||||
|
export function queryOrders(query) {
|
||||||
|
return request({
|
||||||
|
url: '/order/query',
|
||||||
|
method: 'post',
|
||||||
|
data: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const zh = {
|
|||||||
pleaseEnter: '请输入',
|
pleaseEnter: '请输入',
|
||||||
optional: '可选',
|
optional: '可选',
|
||||||
required: '必填',
|
required: '必填',
|
||||||
|
more: '更多',
|
||||||
|
collapse: '收起',
|
||||||
addressFormat: '地址格式',
|
addressFormat: '地址格式',
|
||||||
phoneCode: '国际区号',
|
phoneCode: '国际区号',
|
||||||
mustMatchId: '需与证件一致,支持当地语言+英文',
|
mustMatchId: '需与证件一致,支持当地语言+英文',
|
||||||
@@ -234,6 +236,8 @@ const en = {
|
|||||||
pleaseEnter: 'Please enter',
|
pleaseEnter: 'Please enter',
|
||||||
optional: 'Optional',
|
optional: 'Optional',
|
||||||
required: 'Required',
|
required: 'Required',
|
||||||
|
more: 'More',
|
||||||
|
collapse: 'Collapse',
|
||||||
addressFormat: 'Address Format',
|
addressFormat: 'Address Format',
|
||||||
phoneCode: 'Phone Code',
|
phoneCode: 'Phone Code',
|
||||||
mustMatchId: 'Must match ID, supports local language + English',
|
mustMatchId: 'Must match ID, supports local language + English',
|
||||||
@@ -436,6 +440,8 @@ const may = {
|
|||||||
pleaseEnter: 'Sila masukkan',
|
pleaseEnter: 'Sila masukkan',
|
||||||
optional: 'Pilihan',
|
optional: 'Pilihan',
|
||||||
required: 'Diperlukan',
|
required: 'Diperlukan',
|
||||||
|
more: 'Lebih Banyak',
|
||||||
|
collapse: 'Tutup',
|
||||||
addressFormat: 'Format Alamat',
|
addressFormat: 'Format Alamat',
|
||||||
phoneCode: 'Kod Telefon',
|
phoneCode: 'Kod Telefon',
|
||||||
mustMatchId: 'Mesti sepadan dengan ID, menyokong bahasa tempatan + Inggeris',
|
mustMatchId: 'Mesti sepadan dengan ID, menyokong bahasa tempatan + Inggeris',
|
||||||
@@ -633,9 +639,14 @@ const fil = {
|
|||||||
pleaseEnter: 'Mangyaring ipasok',
|
pleaseEnter: 'Mangyaring ipasok',
|
||||||
optional: 'Opsiyonal',
|
optional: 'Opsiyonal',
|
||||||
required: 'Kinakailangan',
|
required: 'Kinakailangan',
|
||||||
|
more: 'Higit Pa',
|
||||||
|
collapse: 'Itago',
|
||||||
addressFormat: 'Format ng Address',
|
addressFormat: 'Format ng Address',
|
||||||
phoneCode: 'Phone Code',
|
phoneCode: 'Phone Code',
|
||||||
mustMatchId: 'Dapat tumugma sa ID, sumusuporta sa lokal na wika + Ingles'
|
mustMatchId: 'Dapat tumugma sa ID, sumusuporta sa lokal na wika + Ingles',
|
||||||
|
// 占位符文本
|
||||||
|
placeholderAddressLine1: 'Mangyaring ipasok ang numero ng bahay, kalye, gusali',
|
||||||
|
placeholderAddressLine2: 'Mangyaring ipasok ang sahig, numero ng unit (opsyonal)'
|
||||||
},
|
},
|
||||||
product: {
|
product: {
|
||||||
selectCurrency: 'Pumili ng Currency at Wika',
|
selectCurrency: 'Pumili ng Currency at Wika',
|
||||||
@@ -779,6 +790,8 @@ const th = {
|
|||||||
pleaseEnter: 'กรุณากรอก',
|
pleaseEnter: 'กรุณากรอก',
|
||||||
optional: 'ไม่บังคับ',
|
optional: 'ไม่บังคับ',
|
||||||
required: 'จำเป็น',
|
required: 'จำเป็น',
|
||||||
|
more: 'เพิ่มเติม',
|
||||||
|
collapse: 'ย่อ',
|
||||||
addressFormat: 'รูปแบบที่อยู่',
|
addressFormat: 'รูปแบบที่อยู่',
|
||||||
phoneCode: 'รหัสโทรศัพท์',
|
phoneCode: 'รหัสโทรศัพท์',
|
||||||
mustMatchId: 'ต้องตรงกับบัตรประชาชน รองรับภาษาท้องถิ่น + อังกฤษ',
|
mustMatchId: 'ต้องตรงกับบัตรประชาชน รองรับภาษาท้องถิ่น + อังกฤษ',
|
||||||
@@ -979,6 +992,8 @@ const vie = {
|
|||||||
pleaseEnter: 'Vui lòng nhập',
|
pleaseEnter: 'Vui lòng nhập',
|
||||||
optional: 'Tùy chọn',
|
optional: 'Tùy chọn',
|
||||||
required: 'Bắt buộc',
|
required: 'Bắt buộc',
|
||||||
|
more: 'Thêm',
|
||||||
|
collapse: 'Thu gọn',
|
||||||
addressFormat: 'Định Dạng Địa Chỉ',
|
addressFormat: 'Định Dạng Địa Chỉ',
|
||||||
phoneCode: 'Mã Điện Thoại',
|
phoneCode: 'Mã Điện Thoại',
|
||||||
mustMatchId: 'Phải khớp với ID, hỗ trợ ngôn ngữ địa phương + tiếng Anh',
|
mustMatchId: 'Phải khớp với ID, hỗ trợ ngôn ngữ địa phương + tiếng Anh',
|
||||||
@@ -1176,9 +1191,14 @@ const id = {
|
|||||||
pleaseEnter: 'Silakan masukkan',
|
pleaseEnter: 'Silakan masukkan',
|
||||||
optional: 'Opsional',
|
optional: 'Opsional',
|
||||||
required: 'Diperlukan',
|
required: 'Diperlukan',
|
||||||
|
more: 'Lebih Banyak',
|
||||||
|
collapse: 'Tutup',
|
||||||
addressFormat: 'Format Alamat',
|
addressFormat: 'Format Alamat',
|
||||||
phoneCode: 'Kode Telepon',
|
phoneCode: 'Kode Telepon',
|
||||||
mustMatchId: 'Harus sesuai dengan ID, mendukung bahasa lokal + Inggris'
|
mustMatchId: 'Harus sesuai dengan ID, mendukung bahasa lokal + Inggris',
|
||||||
|
// 占位符文本
|
||||||
|
placeholderAddressLine1: 'Silakan masukkan nomor rumah, jalan, gedung',
|
||||||
|
placeholderAddressLine2: 'Silakan masukkan lantai, nomor unit (opsional)'
|
||||||
},
|
},
|
||||||
product: {
|
product: {
|
||||||
selectCurrency: 'Pilih Mata Uang dan Bahasa',
|
selectCurrency: 'Pilih Mata Uang dan Bahasa',
|
||||||
|
|||||||
16
src/main.js
16
src/main.js
@@ -17,18 +17,14 @@ app.use(router)
|
|||||||
app.use(ElementPlus)
|
app.use(ElementPlus)
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|
||||||
// 添加错误处理
|
// 添加错误处理(生产环境静默处理)
|
||||||
app.config.errorHandler = (err, instance, info) => {
|
app.config.errorHandler = (err, instance, info) => {
|
||||||
console.error('Vue错误:', err)
|
// 生产环境可以记录到错误追踪服务
|
||||||
console.error('错误信息:', info)
|
if (import.meta.env.DEV) {
|
||||||
console.error('组件实例:', instance)
|
console.error('Vue错误:', err, info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 挂载应用
|
// 挂载应用
|
||||||
try {
|
app.mount('#app')
|
||||||
app.mount('#app')
|
|
||||||
console.log('Vue应用已成功挂载')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('应用挂载失败:', error)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,12 +44,14 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/create-order',
|
path: '/create-order',
|
||||||
name: 'CreateOrder',
|
name: 'CreateOrder',
|
||||||
component: CreateOrder
|
component: CreateOrder,
|
||||||
|
meta: { isCustomerPage: true } // 标记为客户页面,不显示导航栏
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/order/confirm',
|
path: '/order/confirm',
|
||||||
name: 'OrderConfirm',
|
name: 'OrderConfirm',
|
||||||
component: () => import('../views/OrderConfirm.vue')
|
component: () => import('../views/OrderConfirm.vue'),
|
||||||
|
meta: { isCustomerPage: true } // 标记为客户页面,不显示导航栏
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/paypal/success',
|
path: '/paypal/success',
|
||||||
@@ -78,6 +80,12 @@ const routes = [
|
|||||||
name: 'OrderQuery',
|
name: 'OrderQuery',
|
||||||
component: OrderQuery
|
component: OrderQuery
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/manage/order',
|
||||||
|
name: 'OrderManage',
|
||||||
|
component: () => import('../views/OrderManage.vue'),
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/manage/product',
|
path: '/manage/product',
|
||||||
name: 'ProductManage',
|
name: 'ProductManage',
|
||||||
@@ -105,8 +113,6 @@ const router = createRouter({
|
|||||||
|
|
||||||
// 路由守卫
|
// 路由守卫
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
console.log('路由导航:', from.path, '->', to.path)
|
|
||||||
|
|
||||||
// 检查是否需要认证
|
// 检查是否需要认证
|
||||||
if (to.meta.requiresAuth) {
|
if (to.meta.requiresAuth) {
|
||||||
// 检查是否已登录
|
// 检查是否已登录
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ export const countryConfigs = {
|
|||||||
phoneCode: '+65',
|
phoneCode: '+65',
|
||||||
postcodeLength: 6,
|
postcodeLength: 6,
|
||||||
postcodePattern: /^\d{6}$/,
|
postcodePattern: /^\d{6}$/,
|
||||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
// 新加坡:城市和邮编是PayPal要求必填的
|
||||||
'shippingAddressLine1', 'shippingBlockNumber', 'shippingUnitNumber', 'shippingPostcode'],
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||||
|
'shippingCity', 'shippingAddressLine1', 'shippingBlockNumber', 'shippingUnitNumber', 'shippingPostcode'],
|
||||||
specialFields: ['shippingBlockNumber', 'shippingUnitNumber'],
|
specialFields: ['shippingBlockNumber', 'shippingUnitNumber'],
|
||||||
fieldLabels: {
|
fieldLabels: {
|
||||||
shippingBlockNumber: '组屋号 (Block Number)',
|
shippingBlockNumber: '组屋号 (Block Number)',
|
||||||
@@ -45,8 +46,9 @@ export const countryConfigs = {
|
|||||||
phoneCode: '+60',
|
phoneCode: '+60',
|
||||||
postcodeLength: 5,
|
postcodeLength: 5,
|
||||||
postcodePattern: /^\d{5}$/,
|
postcodePattern: /^\d{5}$/,
|
||||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
// 马来西亚:城市和邮编是PayPal要求必填的;州属放入更多按钮中,改为非必填
|
||||||
'shippingStateMalaysia', 'shippingAddressLine1', 'shippingPostcode'],
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||||
|
'shippingCity', 'shippingAddressLine1', 'shippingPostcode'],
|
||||||
specialFields: ['shippingStateMalaysia'],
|
specialFields: ['shippingStateMalaysia'],
|
||||||
fieldLabels: {
|
fieldLabels: {
|
||||||
shippingStateMalaysia: '州属 (State)',
|
shippingStateMalaysia: '州属 (State)',
|
||||||
@@ -64,8 +66,9 @@ export const countryConfigs = {
|
|||||||
phoneCode: '+63',
|
phoneCode: '+63',
|
||||||
postcodeLength: 4,
|
postcodeLength: 4,
|
||||||
postcodePattern: /^\d{4}$/,
|
postcodePattern: /^\d{4}$/,
|
||||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
// 菲律宾:城市和邮编是PayPal要求必填的;省放入更多按钮中,改为非必填
|
||||||
'shippingState', 'shippingBarangay', 'shippingAddressLine1', 'shippingPostcode'],
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||||
|
'shippingCity', 'shippingBarangay', 'shippingAddressLine1', 'shippingPostcode'],
|
||||||
specialFields: ['shippingBarangay'],
|
specialFields: ['shippingBarangay'],
|
||||||
fieldLabels: {
|
fieldLabels: {
|
||||||
shippingBarangay: 'Barangay(社区编号)',
|
shippingBarangay: 'Barangay(社区编号)',
|
||||||
@@ -84,8 +87,9 @@ export const countryConfigs = {
|
|||||||
phoneCode: '+66',
|
phoneCode: '+66',
|
||||||
postcodeLength: 5,
|
postcodeLength: 5,
|
||||||
postcodePattern: /^\d{5}$/,
|
postcodePattern: /^\d{5}$/,
|
||||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
// 泰国:城市和邮编是PayPal要求必填的;府放入更多按钮中,改为非必填
|
||||||
'shippingState', 'shippingAddressLine1', 'shippingPostcode', 'shippingAddressThai'],
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||||
|
'shippingCity', 'shippingAddressLine1', 'shippingAddressThai', 'shippingPostcode'],
|
||||||
specialFields: ['shippingAddressThai', 'shippingAdministrativeArea'],
|
specialFields: ['shippingAddressThai', 'shippingAdministrativeArea'],
|
||||||
fieldLabels: {
|
fieldLabels: {
|
||||||
shippingAddressThai: '泰文地址 (Thai Address)',
|
shippingAddressThai: '泰文地址 (Thai Address)',
|
||||||
@@ -105,8 +109,9 @@ export const countryConfigs = {
|
|||||||
phoneCode: '+84',
|
phoneCode: '+84',
|
||||||
postcodeLength: 5,
|
postcodeLength: 5,
|
||||||
postcodePattern: /^\d{5}$/,
|
postcodePattern: /^\d{5}$/,
|
||||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingProvince',
|
// 越南:使用省/市/郡/区/坊代替城市;邮编是PayPal要求必填的
|
||||||
'shippingDistrict', 'shippingWard', 'shippingAddressLine1', 'shippingPostcode'],
|
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||||
|
'shippingProvince', 'shippingDistrict', 'shippingWard', 'shippingAddressLine1', 'shippingPostcode'],
|
||||||
specialFields: ['shippingProvince', 'shippingDistrict', 'shippingWard'],
|
specialFields: ['shippingProvince', 'shippingDistrict', 'shippingWard'],
|
||||||
fieldLabels: {
|
fieldLabels: {
|
||||||
shippingProvince: '省 (Tỉnh)',
|
shippingProvince: '省 (Tỉnh)',
|
||||||
|
|||||||
@@ -21,20 +21,20 @@
|
|||||||
fit="cover"
|
fit="cover"
|
||||||
class="product-info-image"
|
class="product-info-image"
|
||||||
/>
|
/>
|
||||||
<div class="product-info-details">
|
<div class="product-info-name">{{ productInfo.name }}</div>
|
||||||
<div class="product-info-name">{{ productInfo.name }}</div>
|
</div>
|
||||||
<div class="product-info-sku" v-if="productInfo.sku">
|
<div class="product-info-details">
|
||||||
<span class="sku-label">SKU:</span>
|
<div class="product-info-sku" v-if="productInfo.sku">
|
||||||
<span class="sku-value">{{ productInfo.sku }}</span>
|
<span class="sku-label">SKU:</span>
|
||||||
</div>
|
<span class="sku-value">{{ productInfo.sku }}</span>
|
||||||
<div class="product-info-price">
|
</div>
|
||||||
<span class="price-label">{{ $t('product.unitPrice') }}:</span>
|
<div class="product-info-price">
|
||||||
<span class="price-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price) }}</span>
|
<span class="price-label">{{ $t('product.unitPrice') }}:</span>
|
||||||
<span class="quantity-label">{{ $t('product.quantity') }}:</span>
|
<span class="price-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price) }}</span>
|
||||||
<span class="quantity-value">x{{ productInfo.quantity }}</span>
|
<span class="quantity-label">{{ $t('product.quantity') }}:</span>
|
||||||
<span class="total-label">{{ $t('order.subtotal') }}:</span>
|
<span class="quantity-value">x{{ productInfo.quantity }}</span>
|
||||||
<span class="total-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price * productInfo.quantity) }}</span>
|
<span class="total-label">{{ $t('order.subtotal') }}:</span>
|
||||||
</div>
|
<span class="total-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price * productInfo.quantity) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,50 +76,74 @@
|
|||||||
<el-divider>{{ $t('order.shippingAddress') }}</el-divider>
|
<el-divider>{{ $t('order.shippingAddress') }}</el-divider>
|
||||||
<el-alert v-if="currentCountryConfig" :title="`${$t('order.addressFormat')}:${currentCountryConfig.addressFormat}`" type="info" :closable="false" style="margin-bottom: 20px" />
|
<el-alert v-if="currentCountryConfig" :title="`${$t('order.addressFormat')}:${currentCountryConfig.addressFormat}`" type="info" :closable="false" style="margin-bottom: 20px" />
|
||||||
|
|
||||||
<el-form-item :label="t('order.shippingName')" prop="shippingName">
|
<!-- 邮编(PayPal要求必填)- 常驻展示,必填 -->
|
||||||
<el-input
|
<el-form-item :label="t('order.postcode')" prop="shippingPostcode" required>
|
||||||
v-model="form.shippingName"
|
<div :class="isMobile ? 'mobile-postcode-group' : 'desktop-postcode-group'">
|
||||||
:placeholder="t('order.pleaseEnter') + t('order.shippingName') + '(' + t('order.mustMatchId') + ')'"
|
<el-input
|
||||||
clearable
|
v-model="form.shippingPostcode"
|
||||||
/>
|
:placeholder="currentCountryConfig ? t('order.placeholderPostcode') + `(${currentCountryConfig.postcodeLength}${t('order.postcodeHint').replace('{0}', '')})` : t('order.placeholderPostcode')"
|
||||||
|
clearable
|
||||||
|
:style="isMobile ? 'width: 100%' : 'width: 48%'"
|
||||||
|
>
|
||||||
|
<template #append v-if="postcodeMatching">
|
||||||
|
<el-icon class="is-loading"><el-icon-loading /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span v-if="currentCountryConfig" :style="isMobile ? 'display: block; margin-top: 8px; color: #909399; font-size: 12px' : 'margin-left: 10px; color: #909399; font-size: 12px'">
|
||||||
|
{{ currentCountryConfig.name }}{{ t('order.postcodeHint').replace('{0}', currentCountryConfig.postcodeLength) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="t('order.shippingPhone')" prop="shippingPhone">
|
<!-- 城市(PayPal要求必填)- 常驻展示,必填(越南不使用此字段,使用独立的省/市/郡/区/坊) -->
|
||||||
<el-input
|
<el-form-item v-if="form.shippingCountry !== 'VN'" :label="getCityLabel()" prop="shippingCity" required>
|
||||||
v-model="form.shippingPhone"
|
<div :class="isMobile ? 'mobile-input-group' : 'desktop-input-group'">
|
||||||
:placeholder="currentCountryConfig ? `${t('order.pleaseEnter')}${t('order.shippingPhone')}(${t('order.phoneCode')}:${currentCountryConfig.phoneCode})` : t('order.pleaseEnter') + t('order.shippingPhone') + '(' + t('order.phoneCode') + ')'"
|
<el-input
|
||||||
clearable
|
v-model="form.shippingCity"
|
||||||
/>
|
:placeholder="getCityPlaceholder()"
|
||||||
|
:style="isMobile ? 'width: 100%' : form.shippingCountry === 'MY' ? 'width: 100%' : 'width: 48%'"
|
||||||
|
clearable
|
||||||
|
@input="updateAddressLine1"
|
||||||
|
/>
|
||||||
|
<!-- 州/省字段:泰国显示府,菲律宾显示省,其他显示州/省(可选) -->
|
||||||
|
<el-input
|
||||||
|
v-if="form.shippingCountry !== 'VN' && form.shippingCountry !== 'MY'"
|
||||||
|
v-model="form.shippingState"
|
||||||
|
:placeholder="getStatePlaceholder()"
|
||||||
|
:style="isMobile ? 'width: 100%; margin-top: 10px' : 'width: 48%; margin-left: 4%'"
|
||||||
|
clearable
|
||||||
|
@input="updateAddressLine1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 收货国家(只读显示,根据货币自动确定) -->
|
<!-- 越南:省、市/郡、区/坊(PayPal要求必填)- 常驻展示,必填 -->
|
||||||
<el-form-item :label="t('order.shippingCountry')" prop="shippingCountry">
|
<template v-if="form.shippingCountry === 'VN'">
|
||||||
<el-input
|
<el-form-item :label="t('order.province')" prop="shippingProvince" required>
|
||||||
v-model="currentCountryDisplayName"
|
<el-input
|
||||||
disabled
|
v-model="form.shippingProvince"
|
||||||
style="width: 100%"
|
:placeholder="t('order.placeholderProvinceVN')"
|
||||||
/>
|
clearable
|
||||||
</el-form-item>
|
@input="updateAddressLine1"
|
||||||
|
/>
|
||||||
<!-- 详细地址1(门牌号、街道、楼栋)- 所有国家都显示 -->
|
</el-form-item>
|
||||||
<el-form-item v-if="showField('shippingAddressLine1')" :label="t('order.addressLine1')" prop="shippingAddressLine1">
|
<el-form-item :label="t('order.district')" prop="shippingDistrict" required>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.shippingAddressLine1"
|
v-model="form.shippingDistrict"
|
||||||
type="textarea"
|
:placeholder="t('order.placeholderDistrictVN')"
|
||||||
:rows="2"
|
clearable
|
||||||
:placeholder="t('order.placeholderAddressLine1')"
|
@input="updateAddressLine1"
|
||||||
clearable
|
/>
|
||||||
/>
|
</el-form-item>
|
||||||
</el-form-item>
|
<el-form-item :label="t('order.ward')" prop="shippingWard" required>
|
||||||
|
<el-input
|
||||||
<!-- 详细地址2(楼层、单元号,可选) -->
|
v-model="form.shippingWard"
|
||||||
<el-form-item v-if="showField('shippingAddressLine2')" :label="t('order.addressLine2')" prop="shippingAddressLine2">
|
:placeholder="t('order.placeholderWardVN')"
|
||||||
<el-input
|
clearable
|
||||||
v-model="form.shippingAddressLine2"
|
@input="updateAddressLine1"
|
||||||
:placeholder="t('order.placeholderAddressLine2')"
|
/>
|
||||||
clearable
|
</el-form-item>
|
||||||
/>
|
</template>
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 新加坡:组屋号和单元号 -->
|
<!-- 新加坡:组屋号和单元号 -->
|
||||||
<template v-if="form.shippingCountry === 'SG'">
|
<template v-if="form.shippingCountry === 'SG'">
|
||||||
@@ -159,39 +183,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 越南:省、市/郡、区/坊 -->
|
|
||||||
<template v-if="form.shippingCountry === 'VN'">
|
|
||||||
<el-form-item :label="t('order.province')" prop="shippingProvince">
|
|
||||||
<el-input
|
|
||||||
v-model="form.shippingProvince"
|
|
||||||
:placeholder="t('order.placeholderProvinceVN')"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="t('order.district')" prop="shippingDistrict">
|
|
||||||
<el-input
|
|
||||||
v-model="form.shippingDistrict"
|
|
||||||
:placeholder="t('order.placeholderDistrictVN')"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="t('order.ward')" prop="shippingWard">
|
|
||||||
<el-input
|
|
||||||
v-model="form.shippingWard"
|
|
||||||
:placeholder="t('order.placeholderWardVN')"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 马来西亚:州属 -->
|
|
||||||
<el-form-item v-if="form.shippingCountry === 'MY'" :label="t('order.stateMalaysia')" prop="shippingStateMalaysia">
|
|
||||||
<el-input
|
|
||||||
v-model="form.shippingStateMalaysia"
|
|
||||||
:placeholder="t('order.placeholderStateMalaysia')"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 泰国:行政区域(区/Tambon) -->
|
<!-- 泰国:行政区域(区/Tambon) -->
|
||||||
<el-form-item v-if="form.shippingCountry === 'TH' && showField('shippingAdministrativeArea')" :label="t('order.administrativeArea')" prop="shippingAdministrativeArea">
|
<el-form-item v-if="form.shippingCountry === 'TH' && showField('shippingAdministrativeArea')" :label="t('order.administrativeArea')" prop="shippingAdministrativeArea">
|
||||||
@@ -202,75 +193,56 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 城市和州/省(通用字段)- 越南不使用此字段,使用独立的省/市/郡/区/坊 -->
|
<!-- 马来西亚:州属 - 常驻展示 -->
|
||||||
<el-form-item v-if="form.shippingCountry !== 'VN'" :label="getCityLabel()" prop="shippingCity">
|
<el-form-item v-if="form.shippingCountry === 'MY'" :label="t('order.stateMalaysia')" prop="shippingStateMalaysia">
|
||||||
<div :class="isMobile ? 'mobile-input-group' : 'desktop-input-group'">
|
|
||||||
<el-input
|
|
||||||
v-model="form.shippingCity"
|
|
||||||
:placeholder="getCityPlaceholder()"
|
|
||||||
:style="isMobile ? 'width: 100%' : form.shippingCountry === 'MY' ? 'width: 100%' : 'width: 48%'"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
<!-- 州/省字段:泰国显示府,菲律宾显示省,其他显示州/省(可选) -->
|
|
||||||
<el-input
|
|
||||||
v-if="form.shippingCountry !== 'VN' && form.shippingCountry !== 'MY'"
|
|
||||||
v-model="form.shippingState"
|
|
||||||
:placeholder="getStatePlaceholder()"
|
|
||||||
:style="isMobile ? 'width: 100%; margin-top: 10px' : 'width: 48%; margin-left: 4%'"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 邮编 -->
|
|
||||||
<el-form-item :label="t('order.postcode')" prop="shippingPostcode">
|
|
||||||
<div :class="isMobile ? 'mobile-postcode-group' : 'desktop-postcode-group'">
|
|
||||||
<el-input
|
|
||||||
v-model="form.shippingPostcode"
|
|
||||||
:placeholder="currentCountryConfig ? t('order.placeholderPostcode') + `(${currentCountryConfig.postcodeLength}${t('order.postcodeHint').replace('{0}', '')})` : t('order.placeholderPostcode')"
|
|
||||||
clearable
|
|
||||||
:style="isMobile ? 'width: 100%' : 'width: 48%'"
|
|
||||||
>
|
|
||||||
<template #append v-if="postcodeMatching">
|
|
||||||
<el-icon class="is-loading"><el-icon-loading /></el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
<span v-if="currentCountryConfig" :style="isMobile ? 'display: block; margin-top: 8px; color: #909399; font-size: 12px' : 'margin-left: 10px; color: #909399; font-size: 12px'">
|
|
||||||
{{ currentCountryConfig.name }}{{ t('order.postcodeHint').replace('{0}', currentCountryConfig.postcodeLength) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 楼层/单元/代收点(补充信息) -->
|
|
||||||
<el-form-item :label="t('order.floorUnit')" prop="shippingFloorUnit">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.shippingFloorUnit"
|
v-model="form.shippingStateMalaysia"
|
||||||
:placeholder="t('order.placeholderFloorUnit')"
|
:placeholder="t('order.placeholderStateMalaysia')"
|
||||||
clearable
|
clearable
|
||||||
|
@input="updateAddressLine1"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 兼容旧字段:街道地址(如果新字段为空,使用旧字段) -->
|
<!-- 详细地址1(门牌号、街道、楼栋)- 所有国家都显示,必填,放在最下面 -->
|
||||||
<el-form-item v-if="!form.shippingAddressLine1" :label="t('order.street')" prop="shippingStreet">
|
<el-form-item v-if="showField('shippingAddressLine1')" :label="t('order.addressLine1')" prop="shippingAddressLine1" required>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.shippingStreet"
|
v-model="form.shippingAddressLine1"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="2"
|
:rows="2"
|
||||||
:placeholder="t('order.placeholderStreet')"
|
:placeholder="t('order.placeholderAddressLine1')"
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="t('order.remark')" prop="remark">
|
<!-- 更多地址信息按钮 -->
|
||||||
<el-input
|
<el-form-item>
|
||||||
v-model="form.remark"
|
<el-button
|
||||||
type="textarea"
|
type="text"
|
||||||
:rows="3"
|
@click="showMoreAddressFields = !showMoreAddressFields"
|
||||||
:placeholder="t('order.placeholderRemark')"
|
style="padding: 0; color: #409eff;"
|
||||||
clearable
|
>
|
||||||
/>
|
<el-icon style="margin-right: 4px;">
|
||||||
|
<ArrowDown v-if="!showMoreAddressFields" />
|
||||||
|
<ArrowUp v-else />
|
||||||
|
</el-icon>
|
||||||
|
{{ showMoreAddressFields ? t('order.collapse') : t('order.more') }}
|
||||||
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 更多地址信息 - 只包含详细地址2 -->
|
||||||
|
<template v-if="showMoreAddressFields">
|
||||||
|
<!-- 详细地址2(楼层、单元号,可选)- 放入更多按钮中 -->
|
||||||
|
<el-form-item v-if="showField('shippingAddressLine2')" :label="t('order.addressLine2')" prop="shippingAddressLine2">
|
||||||
|
<el-input
|
||||||
|
v-model="form.shippingAddressLine2"
|
||||||
|
:placeholder="t('order.placeholderAddressLine2')"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" size="large" @click="submitForm" :loading="loading" style="width: 200px">
|
<el-button type="primary" size="large" @click="submitForm" :loading="loading" style="width: 200px">
|
||||||
{{ $t('order.submit') }}
|
{{ $t('order.submit') }}
|
||||||
@@ -287,6 +259,7 @@ import { ref, reactive, computed, watch, onMounted } from 'vue'
|
|||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
|
||||||
import { createCustomerOrder } from '../api/order'
|
import { createCustomerOrder } from '../api/order'
|
||||||
import { formatAmount } from '../utils/helpers'
|
import { formatAmount } from '../utils/helpers'
|
||||||
import { getCountryConfig, getCountryByCurrency, validatePostcode, getRequiredFields } from '../utils/countryConfig'
|
import { getCountryConfig, getCountryByCurrency, validatePostcode, getRequiredFields } from '../utils/countryConfig'
|
||||||
@@ -301,13 +274,13 @@ const formRef = ref()
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const productInfo = ref(null)
|
const productInfo = ref(null)
|
||||||
const postcodeMatching = ref(false) // 邮编匹配中
|
const postcodeMatching = ref(false) // 邮编匹配中
|
||||||
|
const showMoreAddressFields = ref(false) // 控制更多地址字段的显示
|
||||||
|
const isAutoUpdating = ref(false) // 防止自动更新时的递归调用
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
customerName: '',
|
customerName: '',
|
||||||
customerPhone: '',
|
customerPhone: '',
|
||||||
customerEmail: '',
|
customerEmail: '',
|
||||||
shippingName: '',
|
|
||||||
shippingPhone: '',
|
|
||||||
shippingCountry: '',
|
shippingCountry: '',
|
||||||
shippingState: '',
|
shippingState: '',
|
||||||
shippingCity: '',
|
shippingCity: '',
|
||||||
@@ -324,9 +297,7 @@ const form = reactive({
|
|||||||
shippingProvince: '',
|
shippingProvince: '',
|
||||||
shippingDistrict: '',
|
shippingDistrict: '',
|
||||||
shippingWard: '',
|
shippingWard: '',
|
||||||
shippingStateMalaysia: '',
|
shippingStateMalaysia: ''
|
||||||
shippingFloorUnit: '',
|
|
||||||
remark: ''
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前国家配置
|
// 当前国家配置
|
||||||
@@ -363,12 +334,13 @@ const currentCountryDisplayName = computed(() => {
|
|||||||
return countryNameMap[lang] || countryNameMap.en || form.shippingCountry
|
return countryNameMap[lang] || countryNameMap.en || form.shippingCountry
|
||||||
})
|
})
|
||||||
|
|
||||||
// 是否显示特定字段
|
// 是否显示特定字段
|
||||||
const showField = (fieldName) => {
|
const showField = (fieldName) => {
|
||||||
if (!currentCountryConfig.value) {
|
if (!currentCountryConfig.value) {
|
||||||
// 默认显示基础字段
|
// 默认显示基础字段(包括详细地址)
|
||||||
return ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
return ['shippingCountry', 'shippingCity',
|
||||||
'shippingState', 'shippingStreet', 'shippingPostcode'].includes(fieldName)
|
'shippingState', 'shippingPostcode',
|
||||||
|
'shippingAddressLine1', 'shippingAddressLine2'].includes(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const specialFields = currentCountryConfig.value.specialFields || []
|
const specialFields = currentCountryConfig.value.specialFields || []
|
||||||
@@ -385,8 +357,8 @@ const showField = (fieldName) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 基础字段始终显示
|
// 基础字段始终显示
|
||||||
const baseFields = ['shippingName', 'shippingPhone', 'shippingCountry',
|
const baseFields = ['shippingCountry',
|
||||||
'shippingCity', 'shippingState', 'shippingStreet',
|
'shippingCity', 'shippingState',
|
||||||
'shippingPostcode', 'shippingAddressLine1', 'shippingAddressLine2']
|
'shippingPostcode', 'shippingAddressLine1', 'shippingAddressLine2']
|
||||||
if (baseFields.includes(fieldName)) {
|
if (baseFields.includes(fieldName)) {
|
||||||
return true
|
return true
|
||||||
@@ -408,17 +380,9 @@ const getFieldLabel = (fieldName) => {
|
|||||||
return fieldName
|
return fieldName
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取城市字段标签(根据国家动态显示)
|
// 获取城市字段标签(根据国家动态显示,只显示对应语言,不包含中文)
|
||||||
const getCityLabel = () => {
|
const getCityLabel = () => {
|
||||||
if (form.shippingCountry === 'TH') {
|
// 直接返回国际化翻译,i18n会根据当前语言环境自动返回对应语言的标签
|
||||||
return t('order.cityTown') + ' (县/Amphoe)'
|
|
||||||
} else if (form.shippingCountry === 'PH') {
|
|
||||||
return t('order.cityTown') + ' (市/City)'
|
|
||||||
} else if (form.shippingCountry === 'SG') {
|
|
||||||
return t('order.cityTown') + ' (城市/City)'
|
|
||||||
} else if (form.shippingCountry === 'MY') {
|
|
||||||
return t('order.cityTown') + ' (城市/City)'
|
|
||||||
}
|
|
||||||
return t('order.cityTown')
|
return t('order.cityTown')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,6 +404,66 @@ const getStatePlaceholder = () => {
|
|||||||
return t('order.placeholderStateOptional')
|
return t('order.placeholderStateOptional')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自动更新详细地址1(将省/州、市/郡、区/坊等信息拼接到详细地址1)
|
||||||
|
// 使用一个标志来跟踪是否应该自动更新(避免用户手动编辑时被覆盖)
|
||||||
|
const updateAddressLine1 = () => {
|
||||||
|
if (isAutoUpdating.value) return // 防止递归更新
|
||||||
|
|
||||||
|
isAutoUpdating.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建地址组件数组(省/州、市/郡、区/坊等)
|
||||||
|
const addressParts = []
|
||||||
|
|
||||||
|
// 根据国家添加不同的地址组件
|
||||||
|
if (form.shippingCountry === 'VN') {
|
||||||
|
// 越南:省、市/郡、区/坊
|
||||||
|
if (form.shippingProvince) addressParts.push(form.shippingProvince)
|
||||||
|
if (form.shippingDistrict) addressParts.push(form.shippingDistrict)
|
||||||
|
if (form.shippingWard) addressParts.push(form.shippingWard)
|
||||||
|
} else {
|
||||||
|
// 其他国家:城市、州/省
|
||||||
|
if (form.shippingCity) addressParts.push(form.shippingCity)
|
||||||
|
if (form.shippingState) addressParts.push(form.shippingState)
|
||||||
|
if (form.shippingCountry === 'MY' && form.shippingStateMalaysia) {
|
||||||
|
addressParts.push(form.shippingStateMalaysia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有地址组件,拼接到详细地址1
|
||||||
|
if (addressParts.length > 0) {
|
||||||
|
const addressSuffix = addressParts.join(', ')
|
||||||
|
const currentAddress = form.shippingAddressLine1 || ''
|
||||||
|
|
||||||
|
// 检查当前地址是否已包含这些组件(避免重复拼接)
|
||||||
|
const hasAllParts = addressParts.every(part => currentAddress.includes(part))
|
||||||
|
|
||||||
|
if (!hasAllParts) {
|
||||||
|
// 移除旧的地址组件(如果存在),保留用户手动输入的门牌号、街道、楼栋等信息
|
||||||
|
let cleanAddress = currentAddress
|
||||||
|
addressParts.forEach(part => {
|
||||||
|
// 移除该组件及其前后的逗号和空格
|
||||||
|
const escapedPart = part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
// 移除 ", 组件" 或 "组件, " 或单独的 "组件"
|
||||||
|
cleanAddress = cleanAddress.replace(new RegExp(`,\\s*${escapedPart}(?=\\s|,|$)`, 'g'), '')
|
||||||
|
cleanAddress = cleanAddress.replace(new RegExp(`${escapedPart}\\s*,`, 'g'), '')
|
||||||
|
cleanAddress = cleanAddress.replace(new RegExp(`^${escapedPart}\\s*$`, 'g'), '')
|
||||||
|
})
|
||||||
|
cleanAddress = cleanAddress.trim().replace(/^,\s*|,\s*$/g, '').replace(/,\s*,/g, ',')
|
||||||
|
|
||||||
|
// 追加新的地址组件
|
||||||
|
if (cleanAddress) {
|
||||||
|
form.shippingAddressLine1 = `${cleanAddress}, ${addressSuffix}`
|
||||||
|
} else {
|
||||||
|
form.shippingAddressLine1 = addressSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isAutoUpdating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 监听国家变化,清空相关字段
|
// 监听国家变化,清空相关字段
|
||||||
watch(() => form.shippingCountry, (newCountry, oldCountry) => {
|
watch(() => form.shippingCountry, (newCountry, oldCountry) => {
|
||||||
if (newCountry !== oldCountry) {
|
if (newCountry !== oldCountry) {
|
||||||
@@ -453,14 +477,19 @@ watch(() => form.shippingCountry, (newCountry, oldCountry) => {
|
|||||||
form.shippingDistrict = ''
|
form.shippingDistrict = ''
|
||||||
form.shippingWard = ''
|
form.shippingWard = ''
|
||||||
form.shippingAdministrativeArea = ''
|
form.shippingAdministrativeArea = ''
|
||||||
|
// 清空详细地址1(因为地址组件已清空)
|
||||||
// 更新电话区号提示
|
form.shippingAddressLine1 = ''
|
||||||
if (currentCountryConfig.value) {
|
|
||||||
form.shippingPhone = currentCountryConfig.value.phoneCode + ' '
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听地址组件变化,自动更新详细地址1
|
||||||
|
watch([() => form.shippingCity, () => form.shippingState, () => form.shippingStateMalaysia,
|
||||||
|
() => form.shippingProvince, () => form.shippingDistrict, () => form.shippingWard],
|
||||||
|
() => {
|
||||||
|
updateAddressLine1()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 监听邮编变化,自动匹配城市
|
// 监听邮编变化,自动匹配城市
|
||||||
watch(() => form.shippingPostcode, async (newPostcode) => {
|
watch(() => form.shippingPostcode, async (newPostcode) => {
|
||||||
if (!newPostcode || !form.shippingCountry) return
|
if (!newPostcode || !form.shippingCountry) return
|
||||||
@@ -501,21 +530,30 @@ const getRules = () => {
|
|||||||
customerEmail: [
|
customerEmail: [
|
||||||
{ type: 'email', message: t('order.validationInvalidEmail'), trigger: 'blur' }
|
{ type: 'email', message: t('order.validationInvalidEmail'), trigger: 'blur' }
|
||||||
],
|
],
|
||||||
shippingName: [
|
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.shippingName')]), trigger: 'blur' }
|
|
||||||
],
|
|
||||||
shippingPhone: [
|
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.shippingPhone')]), trigger: 'blur' },
|
|
||||||
{ pattern: /^[0-9+\-\s()]+$/, message: t('order.validationInvalidPhone'), trigger: 'blur' }
|
|
||||||
],
|
|
||||||
shippingCountry: [
|
shippingCountry: [
|
||||||
{ required: true, message: t('order.validationSelectCountry'), trigger: 'change' }
|
{ required: true, message: t('order.validationSelectCountry'), trigger: 'change' }
|
||||||
],
|
],
|
||||||
shippingCity: [
|
// 详细地址1始终必填
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.cityTown')]), trigger: 'blur' }
|
shippingAddressLine1: [
|
||||||
|
{ required: true, message: t('order.validationRequired', [t('order.addressLine1')]), trigger: 'blur' }
|
||||||
],
|
],
|
||||||
shippingStreet: [
|
// 邮编始终必填(PayPal要求)
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.street')]), trigger: 'blur' }
|
shippingPostcode: [
|
||||||
|
{ required: true, message: t('order.validationRequired', [t('order.postcode')]), trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value && currentCountryConfig.value && !validatePostcode(form.shippingCountry, value)) {
|
||||||
|
callback(new Error(t('order.validationPostcodeFormat', [currentCountryConfig.value.postcodeLength])))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 城市始终必填(PayPal要求,越南除外)
|
||||||
|
shippingCity: [
|
||||||
|
{ required: true, message: t('order.validationRequired', [t('order.city')]), trigger: 'blur' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,28 +561,6 @@ const getRules = () => {
|
|||||||
if (currentCountryConfig.value) {
|
if (currentCountryConfig.value) {
|
||||||
const requiredFields = getRequiredFields(form.shippingCountry)
|
const requiredFields = getRequiredFields(form.shippingCountry)
|
||||||
|
|
||||||
if (requiredFields.includes('shippingAddressLine1')) {
|
|
||||||
baseRules.shippingAddressLine1 = [
|
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.addressLine1')]), trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requiredFields.includes('shippingPostcode')) {
|
|
||||||
baseRules.shippingPostcode = [
|
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.postcode')]), trigger: 'blur' },
|
|
||||||
{
|
|
||||||
validator: (rule, value, callback) => {
|
|
||||||
if (value && !validatePostcode(form.shippingCountry, value)) {
|
|
||||||
callback(new Error(t('order.validationPostcodeFormat', [currentCountryConfig.value.postcodeLength])))
|
|
||||||
} else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trigger: 'blur'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requiredFields.includes('shippingBlockNumber')) {
|
if (requiredFields.includes('shippingBlockNumber')) {
|
||||||
baseRules.shippingBlockNumber = [
|
baseRules.shippingBlockNumber = [
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.blockNumber')]), trigger: 'blur' }
|
{ required: true, message: t('order.validationRequired', [t('order.blockNumber')]), trigger: 'blur' }
|
||||||
@@ -639,12 +655,12 @@ const submitForm = async () => {
|
|||||||
customerName: form.customerName,
|
customerName: form.customerName,
|
||||||
customerPhone: form.customerPhone,
|
customerPhone: form.customerPhone,
|
||||||
customerEmail: form.customerEmail || null,
|
customerEmail: form.customerEmail || null,
|
||||||
shippingName: form.shippingName,
|
shippingName: form.customerName, // 使用客户姓名作为收货人姓名
|
||||||
shippingPhone: form.shippingPhone,
|
shippingPhone: form.customerPhone, // 使用客户电话作为收货人电话
|
||||||
shippingCountry: form.shippingCountry,
|
shippingCountry: form.shippingCountry,
|
||||||
shippingState: form.shippingState || null,
|
shippingState: form.shippingState || null,
|
||||||
shippingCity: shippingCity, // 使用映射后的值,越南使用 shippingDistrict
|
shippingCity: shippingCity, // 使用映射后的值,越南使用 shippingDistrict
|
||||||
shippingStreet: form.shippingStreet || form.shippingAddressLine1, // 兼容旧字段
|
shippingStreet: form.shippingAddressLine1, // 使用详细地址1
|
||||||
shippingPostcode: form.shippingPostcode || null,
|
shippingPostcode: form.shippingPostcode || null,
|
||||||
// 东南亚地址扩展字段
|
// 东南亚地址扩展字段
|
||||||
shippingAddressLine1: form.shippingAddressLine1 || null,
|
shippingAddressLine1: form.shippingAddressLine1 || null,
|
||||||
@@ -658,8 +674,6 @@ const submitForm = async () => {
|
|||||||
shippingDistrict: form.shippingDistrict || null,
|
shippingDistrict: form.shippingDistrict || null,
|
||||||
shippingWard: form.shippingWard || null,
|
shippingWard: form.shippingWard || null,
|
||||||
shippingStateMalaysia: form.shippingStateMalaysia || null,
|
shippingStateMalaysia: form.shippingStateMalaysia || null,
|
||||||
shippingFloorUnit: form.shippingFloorUnit || null,
|
|
||||||
remark: form.remark || null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await createCustomerOrder(orderData)
|
const response = await createCustomerOrder(orderData)
|
||||||
@@ -703,10 +717,6 @@ onMounted(async () => {
|
|||||||
const countryCode = getCountryByCurrency(data.product.currency)
|
const countryCode = getCountryByCurrency(data.product.currency)
|
||||||
if (countryCode) {
|
if (countryCode) {
|
||||||
form.shippingCountry = countryCode
|
form.shippingCountry = countryCode
|
||||||
const config = getCountryConfig(countryCode)
|
|
||||||
if (config) {
|
|
||||||
form.shippingPhone = config.phoneCode + ' '
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -755,22 +765,18 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.product-info-main {
|
.product-info-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-image {
|
.product-info-image {
|
||||||
width: 120px;
|
width: 80px;
|
||||||
height: 120px;
|
height: 80px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
border: 1px solid #e4e7ed;
|
border: 1px solid #e4e7ed;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
object-fit: cover;
|
||||||
|
|
||||||
.product-info-details {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-name {
|
.product-info-name {
|
||||||
@@ -778,6 +784,13 @@ onMounted(async () => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-sku {
|
.product-info-sku {
|
||||||
@@ -830,39 +843,74 @@ onMounted(async () => {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端优化 */
|
/* 移动端优先设计 - 响应式优化 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.create-order {
|
.create-order {
|
||||||
padding: 10px;
|
padding: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 10px 0;
|
padding: 12px 16px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-card__body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品信息卡片优化 */
|
||||||
.product-info-card {
|
.product-info-card {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card-header {
|
.product-card-header {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info-content {
|
||||||
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-main {
|
.product-info-main {
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-image {
|
.product-info-image {
|
||||||
width: 100%;
|
width: 60px;
|
||||||
height: 200px;
|
height: 60px;
|
||||||
align-self: center;
|
flex-shrink: 0;
|
||||||
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-name {
|
.product-info-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-word;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info-details {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info-sku {
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-price {
|
.product-info-price {
|
||||||
@@ -870,33 +918,58 @@ onMounted(async () => {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-label,
|
||||||
|
.quantity-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-value,
|
||||||
|
.quantity-value {
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-label {
|
.total-label {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-value {
|
.total-value {
|
||||||
font-size: 20px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 表单优化 */
|
/* 表单优化 */
|
||||||
|
.el-form {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.el-form-item {
|
.el-form-item {
|
||||||
margin-bottom: 18px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input,
|
.el-input,
|
||||||
.el-select,
|
.el-select,
|
||||||
.el-textarea {
|
.el-textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner,
|
||||||
|
.el-textarea__inner {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 地址字段优化 */
|
/* 地址字段优化 */
|
||||||
@@ -911,39 +984,22 @@ onMounted(async () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 按钮优化 */
|
|
||||||
.el-form-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-top: 15px;
|
|
||||||
border-top: 1px solid #e4e7ed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item:last-child .el-button {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
height: 44px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item:last-child .el-button + .el-button {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 分隔线优化 */
|
/* 分隔线优化 */
|
||||||
.el-divider {
|
.el-divider {
|
||||||
margin: 20px 0;
|
margin: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-divider__text {
|
.el-divider__text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 0 15px;
|
padding: 0 12px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 提示信息优化 */
|
/* 提示信息优化 */
|
||||||
.el-alert {
|
.el-alert {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 输入组优化 */
|
/* 输入组优化 */
|
||||||
@@ -951,23 +1007,73 @@ onMounted(async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-input-group {
|
.desktop-input-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
gap: 4%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-postcode-group {
|
.mobile-postcode-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-postcode-group {
|
.desktop-postcode-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮优化 */
|
||||||
|
.el-form-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: white;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item:last-child .el-button {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item:last-child .el-button + .el-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 更多按钮优化 */
|
||||||
|
.el-button--text {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 国家选择器优化 */
|
||||||
|
.el-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文本域优化 */
|
||||||
|
.el-textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-textarea__inner {
|
||||||
|
min-height: 80px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -152,7 +152,7 @@
|
|||||||
:style="isMobile ? 'width: 100%' : 'width: 200px'"
|
:style="isMobile ? 'width: 100%' : 'width: 200px'"
|
||||||
>
|
>
|
||||||
<el-icon><Money /></el-icon>
|
<el-icon><Money /></el-icon>
|
||||||
{{ $t('confirm.payNow') }}
|
{{ countdown > 0 ? `${$t('confirm.payNow')} (${countdown}s)` : $t('confirm.payNow') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="goBack" :style="isMobile ? 'width: 100%; margin-top: 10px; margin-left: 0' : 'margin-left: 10px'">{{ $t('confirm.back') }}</el-button>
|
<el-button @click="goBack" :style="isMobile ? 'width: 100%; margin-top: 10px; margin-left: 0' : 'margin-left: 10px'">{{ $t('confirm.back') }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
@@ -185,6 +185,9 @@ const route = useRoute()
|
|||||||
const orderLoading = ref(true)
|
const orderLoading = ref(true)
|
||||||
const payLoading = ref(false)
|
const payLoading = ref(false)
|
||||||
const order = ref(null)
|
const order = ref(null)
|
||||||
|
const autoPayTimer = ref(null) // 自动支付定时器
|
||||||
|
const countdownTimer = ref(null) // 倒计时定时器
|
||||||
|
const countdown = ref(0) // 倒计时(秒)
|
||||||
|
|
||||||
// 移动端检测
|
// 移动端检测
|
||||||
const isMobile = computed(() => {
|
const isMobile = computed(() => {
|
||||||
@@ -363,6 +366,31 @@ const loadOrder = async () => {
|
|||||||
order.value = null
|
order.value = null
|
||||||
} finally {
|
} finally {
|
||||||
orderLoading.value = false
|
orderLoading.value = false
|
||||||
|
|
||||||
|
// 如果订单状态是未支付,3秒后自动触发支付
|
||||||
|
if (order.value && order.value.paymentStatus === 'UNPAID') {
|
||||||
|
countdown.value = 3
|
||||||
|
// 倒计时显示
|
||||||
|
countdownTimer.value = setInterval(() => {
|
||||||
|
countdown.value--
|
||||||
|
if (countdown.value <= 0) {
|
||||||
|
if (countdownTimer.value) {
|
||||||
|
clearInterval(countdownTimer.value)
|
||||||
|
countdownTimer.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
// 3秒后自动触发支付
|
||||||
|
autoPayTimer.value = setTimeout(() => {
|
||||||
|
if (countdownTimer.value) {
|
||||||
|
clearInterval(countdownTimer.value)
|
||||||
|
countdownTimer.value = null
|
||||||
|
}
|
||||||
|
countdown.value = 0
|
||||||
|
handlePay()
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +406,17 @@ const handlePay = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除自动支付定时器和倒计时(如果存在)
|
||||||
|
if (autoPayTimer.value) {
|
||||||
|
clearTimeout(autoPayTimer.value)
|
||||||
|
autoPayTimer.value = null
|
||||||
|
}
|
||||||
|
if (countdownTimer.value) {
|
||||||
|
clearInterval(countdownTimer.value)
|
||||||
|
countdownTimer.value = null
|
||||||
|
}
|
||||||
|
countdown.value = 0
|
||||||
|
|
||||||
payLoading.value = true
|
payLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -454,6 +493,18 @@ const handlePay = async () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadOrder()
|
loadOrder()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 组件卸载时清理定时器
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (autoPayTimer.value) {
|
||||||
|
clearTimeout(autoPayTimer.value)
|
||||||
|
autoPayTimer.value = null
|
||||||
|
}
|
||||||
|
if (countdownTimer.value) {
|
||||||
|
clearInterval(countdownTimer.value)
|
||||||
|
countdownTimer.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -628,18 +679,31 @@ onMounted(() => {
|
|||||||
border-top: 1px solid #ebeef5;
|
border-top: 1px solid #ebeef5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端优化 */
|
/* 移动端优先设计 - 响应式优化 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.order-confirm {
|
.order-confirm {
|
||||||
padding: 10px;
|
padding: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card__body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 12px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-info-section {
|
.order-info-section {
|
||||||
@@ -647,22 +711,47 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.order-info-section h3 {
|
.order-info-section h3 {
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 2px solid #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-amount {
|
.order-amount {
|
||||||
font-size: 18px;
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-amount-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-conversion-info {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion-alert-box {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion-alert-content {
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversion-title {
|
.conversion-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-icon {
|
.warning-icon {
|
||||||
@@ -670,35 +759,53 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.conversion-main-info {
|
.conversion-main-info {
|
||||||
padding: 12px;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.payment-amount-highlight {
|
.payment-amount-highlight {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.payment-amount-large {
|
.payment-amount-large {
|
||||||
font-size: 20px;
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exchange-rate-info {
|
.exchange-rate-info {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-locked-info {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding-top: 15px;
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: white;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons .el-button {
|
.action-buttons .el-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 44px;
|
height: 48px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons .el-button + .el-button {
|
.action-buttons .el-button + .el-button {
|
||||||
@@ -710,26 +817,49 @@ onMounted(() => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-descriptions--border {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions-item {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.el-descriptions-item__label {
|
.el-descriptions-item__label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
padding-right: 12px;
|
||||||
|
color: #606266;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-descriptions-item__content {
|
.el-descriptions-item__content {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
color: #303133;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shipping-address-detail {
|
.shipping-address-detail {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.address-line {
|
.address-line {
|
||||||
margin-bottom: 6px;
|
margin-bottom: 8px;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-line:last-child {
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.address-line strong {
|
.address-line strong {
|
||||||
font-size: 12px;
|
font-weight: 600;
|
||||||
|
color: #606266;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
545
src/views/OrderManage.vue
Normal file
545
src/views/OrderManage.vue
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
<template>
|
||||||
|
<div class="order-manage">
|
||||||
|
<!-- 查询表单 -->
|
||||||
|
<el-card class="search-card" style="margin-bottom: 20px">
|
||||||
|
<el-form :model="queryForm" :inline="true" class="search-form">
|
||||||
|
<el-form-item label="订单号">
|
||||||
|
<el-input
|
||||||
|
v-model="queryForm.orderNo"
|
||||||
|
placeholder="请输入订单号"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="PayPal订单ID">
|
||||||
|
<el-input
|
||||||
|
v-model="queryForm.paypalOrderId"
|
||||||
|
placeholder="请输入PayPal订单ID"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="订单状态">
|
||||||
|
<el-select
|
||||||
|
v-model="queryForm.status"
|
||||||
|
placeholder="请选择状态"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option label="待支付" value="PENDING" />
|
||||||
|
<el-option label="已支付" value="PAID" />
|
||||||
|
<el-option label="已发货" value="SHIPPED" />
|
||||||
|
<el-option label="已完成" value="COMPLETED" />
|
||||||
|
<el-option label="已取消" value="CANCELLED" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="支付状态">
|
||||||
|
<el-select
|
||||||
|
v-model="queryForm.paymentStatus"
|
||||||
|
placeholder="请选择支付状态"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option label="未支付" value="UNPAID" />
|
||||||
|
<el-option label="已支付" value="PAID" />
|
||||||
|
<el-option label="支付失败" value="FAILED" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="PayPal状态">
|
||||||
|
<el-select
|
||||||
|
v-model="queryForm.paypalStatus"
|
||||||
|
placeholder="请选择PayPal状态"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option label="已创建" value="CREATED" />
|
||||||
|
<el-option label="已批准" value="APPROVED" />
|
||||||
|
<el-option label="已完成" value="COMPLETED" />
|
||||||
|
<el-option label="已取消" value="VOIDED" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户姓名">
|
||||||
|
<el-input
|
||||||
|
v-model="queryForm.customerName"
|
||||||
|
placeholder="请输入客户姓名"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户电话">
|
||||||
|
<el-input
|
||||||
|
v-model="queryForm.customerPhone"
|
||||||
|
placeholder="请输入客户电话"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品名称">
|
||||||
|
<el-input
|
||||||
|
v-model="queryForm.productName"
|
||||||
|
placeholder="请输入商品名称"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery">
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="handleReset">
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 订单列表 -->
|
||||||
|
<el-card>
|
||||||
|
<el-table :data="orderList" v-loading="loading" style="width: 100%">
|
||||||
|
<!-- 订单号 -->
|
||||||
|
<el-table-column label="订单号" prop="orderNo" width="180" show-overflow-tooltip />
|
||||||
|
|
||||||
|
<!-- 商品信息 -->
|
||||||
|
<el-table-column label="商品信息" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>
|
||||||
|
<div class="product-name">{{ row.productName }}</div>
|
||||||
|
<div class="sku-name">SKU: {{ row.skuName }}</div>
|
||||||
|
<div class="quantity">数量: {{ row.quantity }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 订单金额 -->
|
||||||
|
<el-table-column label="订单金额" width="150" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.paymentCurrency && row.paymentCurrency !== row.originalCurrency">
|
||||||
|
<div class="original-amount">{{ row.originalAmount }} {{ row.originalCurrency }}</div>
|
||||||
|
<div class="payment-amount">{{ row.paymentAmount }} {{ row.paymentCurrency }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ row.totalAmount }} {{ row.originalCurrency || row.currency }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 客户信息 -->
|
||||||
|
<el-table-column label="客户信息" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>
|
||||||
|
<div>{{ row.customerName }}</div>
|
||||||
|
<div class="customer-phone">{{ row.customerPhone }}</div>
|
||||||
|
<div class="customer-email" v-if="row.customerEmail">{{ row.customerEmail }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 收货地址 -->
|
||||||
|
<el-table-column label="收货地址" min-width="200" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>
|
||||||
|
{{ row.shippingCountry }} {{ row.shippingCity }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 订单状态 -->
|
||||||
|
<el-table-column label="订单状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getStatusTagType(row.status)" size="small">
|
||||||
|
{{ getStatusText(row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 支付状态 -->
|
||||||
|
<el-table-column label="支付状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getPaymentStatusTagType(row.paymentStatus)" size="small">
|
||||||
|
{{ getPaymentStatusText(row.paymentStatus) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- PayPal信息 -->
|
||||||
|
<el-table-column label="PayPal信息" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.paypalOrderId">
|
||||||
|
<div class="paypal-order-id">订单ID: {{ row.paypalOrderId }}</div>
|
||||||
|
<el-tag v-if="row.paypalStatus" :type="getPaypalStatusTagType(row.paypalStatus)" size="small" style="margin-top: 4px">
|
||||||
|
{{ row.paypalStatus }}
|
||||||
|
</el-tag>
|
||||||
|
<div v-if="row.payerEmail" class="payer-email">{{ row.payerEmail }}</div>
|
||||||
|
</div>
|
||||||
|
<span v-else class="no-paypal">未关联PayPal订单</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 创建时间 -->
|
||||||
|
<el-table-column label="创建时间" width="180" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDateTime(row.createTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 操作 -->
|
||||||
|
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link size="small" @click="viewOrderDetail(row.id)">
|
||||||
|
查看详情
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页组件 -->
|
||||||
|
<div class="pagination-container" v-if="pagination.total > 0">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="pagination.pageNum"
|
||||||
|
v-model:page-size="pagination.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="pagination.total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Search, Refresh } from '@element-plus/icons-vue'
|
||||||
|
import { queryOrders } from '../api/order'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
const orderList = ref([])
|
||||||
|
|
||||||
|
// 查询表单
|
||||||
|
const queryForm = reactive({
|
||||||
|
orderNo: '',
|
||||||
|
paypalOrderId: '',
|
||||||
|
merchantOrderNo: '',
|
||||||
|
status: '',
|
||||||
|
paymentStatus: '',
|
||||||
|
paypalStatus: '',
|
||||||
|
paypalPaymentStatus: '',
|
||||||
|
customerName: '',
|
||||||
|
customerPhone: '',
|
||||||
|
customerEmail: '',
|
||||||
|
productName: '',
|
||||||
|
payerEmail: '',
|
||||||
|
payerName: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页信息
|
||||||
|
const pagination = ref({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询订单列表
|
||||||
|
const handleQuery = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const query = {}
|
||||||
|
if (queryForm.orderNo && queryForm.orderNo.trim()) {
|
||||||
|
query.orderNo = queryForm.orderNo.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.paypalOrderId && queryForm.paypalOrderId.trim()) {
|
||||||
|
query.paypalOrderId = queryForm.paypalOrderId.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.merchantOrderNo && queryForm.merchantOrderNo.trim()) {
|
||||||
|
query.merchantOrderNo = queryForm.merchantOrderNo.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.status) {
|
||||||
|
query.status = queryForm.status
|
||||||
|
}
|
||||||
|
if (queryForm.paymentStatus) {
|
||||||
|
query.paymentStatus = queryForm.paymentStatus
|
||||||
|
}
|
||||||
|
if (queryForm.paypalStatus) {
|
||||||
|
query.paypalStatus = queryForm.paypalStatus
|
||||||
|
}
|
||||||
|
if (queryForm.paypalPaymentStatus) {
|
||||||
|
query.paypalPaymentStatus = queryForm.paypalPaymentStatus
|
||||||
|
}
|
||||||
|
if (queryForm.customerName && queryForm.customerName.trim()) {
|
||||||
|
query.customerName = queryForm.customerName.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.customerPhone && queryForm.customerPhone.trim()) {
|
||||||
|
query.customerPhone = queryForm.customerPhone.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.customerEmail && queryForm.customerEmail.trim()) {
|
||||||
|
query.customerEmail = queryForm.customerEmail.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.productName && queryForm.productName.trim()) {
|
||||||
|
query.productName = queryForm.productName.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.payerEmail && queryForm.payerEmail.trim()) {
|
||||||
|
query.payerEmail = queryForm.payerEmail.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.payerName && queryForm.payerName.trim()) {
|
||||||
|
query.payerName = queryForm.payerName.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.startTime && queryForm.startTime.trim()) {
|
||||||
|
query.startTime = queryForm.startTime.trim()
|
||||||
|
}
|
||||||
|
if (queryForm.endTime && queryForm.endTime.trim()) {
|
||||||
|
query.endTime = queryForm.endTime.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加分页参数
|
||||||
|
query.pageNum = pagination.value.pageNum
|
||||||
|
query.pageSize = pagination.value.pageSize
|
||||||
|
|
||||||
|
const response = await queryOrders(query)
|
||||||
|
if (response.code === '0000' && response.data) {
|
||||||
|
const pageResult = response.data
|
||||||
|
orderList.value = pageResult.records || []
|
||||||
|
|
||||||
|
// 更新分页信息
|
||||||
|
pagination.value.total = pageResult.total || 0
|
||||||
|
pagination.value.pageNum = pageResult.current || 1
|
||||||
|
pagination.value.pageSize = pageResult.size || 10
|
||||||
|
|
||||||
|
if (orderList.value.length === 0) {
|
||||||
|
ElMessage.info('未找到符合条件的订单')
|
||||||
|
} else {
|
||||||
|
ElMessage.success(`查询到 ${pagination.value.total} 条订单,当前第 ${pagination.value.pageNum} 页`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error(response.message || '查询订单列表失败')
|
||||||
|
orderList.value = []
|
||||||
|
pagination.value.total = 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询订单列表失败:', error)
|
||||||
|
ElMessage.error('查询订单列表失败')
|
||||||
|
orderList.value = []
|
||||||
|
pagination.value.total = 0
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置查询条件
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.assign(queryForm, {
|
||||||
|
orderNo: '',
|
||||||
|
paypalOrderId: '',
|
||||||
|
merchantOrderNo: '',
|
||||||
|
status: '',
|
||||||
|
paymentStatus: '',
|
||||||
|
paypalStatus: '',
|
||||||
|
paypalPaymentStatus: '',
|
||||||
|
customerName: '',
|
||||||
|
customerPhone: '',
|
||||||
|
customerEmail: '',
|
||||||
|
productName: '',
|
||||||
|
payerEmail: '',
|
||||||
|
payerName: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: ''
|
||||||
|
})
|
||||||
|
pagination.value = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页大小改变
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
pagination.value.pageSize = size
|
||||||
|
pagination.value.pageNum = 1 // 重置到第一页
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页码改变
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
pagination.value.pageNum = page
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看订单详情
|
||||||
|
const viewOrderDetail = (orderId) => {
|
||||||
|
router.push(`/order/detail/${orderId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取订单状态文本
|
||||||
|
const getStatusText = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
'PENDING': '待支付',
|
||||||
|
'PAID': '已支付',
|
||||||
|
'SHIPPED': '已发货',
|
||||||
|
'COMPLETED': '已完成',
|
||||||
|
'CANCELLED': '已取消'
|
||||||
|
}
|
||||||
|
return statusMap[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取订单状态标签类型
|
||||||
|
const getStatusTagType = (status) => {
|
||||||
|
const typeMap = {
|
||||||
|
'PENDING': 'warning',
|
||||||
|
'PAID': 'success',
|
||||||
|
'SHIPPED': 'info',
|
||||||
|
'COMPLETED': 'success',
|
||||||
|
'CANCELLED': 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取支付状态文本
|
||||||
|
const getPaymentStatusText = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
'UNPAID': '未支付',
|
||||||
|
'PAID': '已支付',
|
||||||
|
'FAILED': '支付失败'
|
||||||
|
}
|
||||||
|
return statusMap[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取支付状态标签类型
|
||||||
|
const getPaymentStatusTagType = (status) => {
|
||||||
|
const typeMap = {
|
||||||
|
'UNPAID': 'warning',
|
||||||
|
'PAID': 'success',
|
||||||
|
'FAILED': 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取PayPal状态标签类型
|
||||||
|
const getPaypalStatusTagType = (status) => {
|
||||||
|
const typeMap = {
|
||||||
|
'CREATED': 'info',
|
||||||
|
'APPROVED': 'success',
|
||||||
|
'COMPLETED': 'success',
|
||||||
|
'VOIDED': 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期时间
|
||||||
|
const formatDateTime = (dateTime) => {
|
||||||
|
if (!dateTime) return ''
|
||||||
|
const date = new Date(dateTime)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载订单列表
|
||||||
|
onMounted(() => {
|
||||||
|
handleQuery()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.order-manage {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-name {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.original-amount {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-amount {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-phone {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-email {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paypal-order-id {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #606266;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payer-email {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-paypal {
|
||||||
|
color: #c0c4cc;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.pagination-container {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container :deep(.el-pagination) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -198,62 +198,6 @@
|
|||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 购买确认对话框 -->
|
|
||||||
<el-dialog
|
|
||||||
v-model="showConfirmDialog"
|
|
||||||
:title="t('product.confirmPurchaseInfo')"
|
|
||||||
:width="isMobile ? '95%' : '600px'"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
class="confirm-dialog"
|
|
||||||
>
|
|
||||||
<div class="confirm-dialog-content">
|
|
||||||
<!-- 商品信息 -->
|
|
||||||
<div class="confirm-product-info">
|
|
||||||
<el-image
|
|
||||||
:src="currentImage"
|
|
||||||
fit="cover"
|
|
||||||
class="confirm-product-image"
|
|
||||||
/>
|
|
||||||
<div class="confirm-product-details">
|
|
||||||
<div class="confirm-product-name">{{ product.name }}</div>
|
|
||||||
<div class="confirm-sku-info" v-if="selectedSku">
|
|
||||||
<span class="confirm-sku-label">{{ t('product.sku') }}:</span>
|
|
||||||
<span class="confirm-sku-value">{{ selectedSku.sku }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 价格和数量 -->
|
|
||||||
<el-divider />
|
|
||||||
<div class="confirm-price-section">
|
|
||||||
<div class="confirm-price-item">
|
|
||||||
<span class="confirm-price-label">{{ t('product.unitPrice') }}:</span>
|
|
||||||
<span class="confirm-price-value">
|
|
||||||
{{ selectedSku ? selectedSku.currency : '' }} {{ formatPrice(selectedSku ? selectedSku.price : 0) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="confirm-price-item">
|
|
||||||
<span class="confirm-price-label">{{ t('product.quantity') }}:</span>
|
|
||||||
<span class="confirm-price-value">{{ quantity }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="confirm-price-item confirm-total">
|
|
||||||
<span class="confirm-price-label">{{ t('product.total') }}:</span>
|
|
||||||
<span class="confirm-price-value confirm-total-price">
|
|
||||||
{{ selectedSku ? selectedSku.currency : '' }} {{ formatPrice(selectedSku ? (selectedSku.price * quantity) : 0) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="showConfirmDialog = false">{{ t('product.cancel') }}</el-button>
|
|
||||||
<el-button type="primary" @click="confirmBuy" :loading="loading">
|
|
||||||
{{ t('product.confirmPurchase') }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -281,7 +225,6 @@ const hoveredImage = ref(null)
|
|||||||
const quantity = ref(1)
|
const quantity = ref(1)
|
||||||
const activeTab = ref('detail')
|
const activeTab = ref('detail')
|
||||||
const selectedSku = ref(null)
|
const selectedSku = ref(null)
|
||||||
const showConfirmDialog = ref(false)
|
|
||||||
|
|
||||||
// 移动端检测
|
// 移动端检测
|
||||||
const isMobile = computed(() => {
|
const isMobile = computed(() => {
|
||||||
@@ -413,7 +356,7 @@ const hoverImage = (img) => {
|
|||||||
hoveredImage.value = img
|
hoveredImage.value = img
|
||||||
}
|
}
|
||||||
|
|
||||||
// 立即购买
|
// 立即购买(直接跳转,不再显示确认弹窗)
|
||||||
const handleBuyNow = () => {
|
const handleBuyNow = () => {
|
||||||
if (!selectedSku.value) {
|
if (!selectedSku.value) {
|
||||||
ElMessage.warning('请先选择商品SKU')
|
ElMessage.warning('请先选择商品SKU')
|
||||||
@@ -425,18 +368,6 @@ const handleBuyNow = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showConfirmDialog.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认购买
|
|
||||||
const confirmBuy = () => {
|
|
||||||
if (!selectedSku.value) {
|
|
||||||
ElMessage.warning('请先选择商品SKU')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
showConfirmDialog.value = false
|
|
||||||
|
|
||||||
// 构建订单数据
|
// 构建订单数据
|
||||||
const orderData = {
|
const orderData = {
|
||||||
product: {
|
product: {
|
||||||
@@ -1209,97 +1140,245 @@ onMounted(() => {
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式设计 */
|
/* 移动端优先设计 - 响应式设计 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.product-detail {
|
.product-detail {
|
||||||
padding: 10px;
|
padding: 0;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-main {
|
.product-main {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 15px;
|
padding: 12px;
|
||||||
gap: 20px;
|
gap: 16px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-images {
|
.product-images {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-image {
|
.main-image {
|
||||||
height: 300px;
|
height: auto;
|
||||||
|
min-height: 250px;
|
||||||
|
max-height: 400px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-list {
|
||||||
|
margin-top: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-item {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info {
|
.product-info {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.product-title {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sku-list {
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buy-now-btn,
|
|
||||||
.pay-now-btn {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-section {
|
.product-title {
|
||||||
flex-direction: column;
|
font-size: 18px;
|
||||||
gap: 15px;
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-selector-card {
|
.currency-selector-card {
|
||||||
padding: 10px 12px;
|
padding: 12px;
|
||||||
margin-top: 10px;
|
margin-top: 0;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-selector-header {
|
.currency-selector-header {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-icon {
|
.currency-icon {
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
margin-right: 5px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-selector-header .selector-label {
|
.currency-selector-header .selector-label {
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-selector-content {
|
.currency-selector-content {
|
||||||
padding: 8px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-radio-group {
|
.currency-radio-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-radio-button {
|
.currency-radio-button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: calc(50% - 3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-radio-button :deep(.el-radio-button__inner) {
|
.currency-radio-button :deep(.el-radio-button__inner) {
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
padding: 6px 4px;
|
padding: 8px 6px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sku-selection-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-list {
|
||||||
|
max-height: none;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-item {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-name {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-price-stock {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-price {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-stock {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-label {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-main {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-input :deep(.el-input-number) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-info {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-section {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-item {
|
||||||
|
font-size: 13px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: white;
|
||||||
|
padding: 12px 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-now-btn,
|
||||||
|
.pay-now-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-details-section {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-details-section h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-details-content {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
/* 旧样式兼容 */
|
/* 旧样式兼容 */
|
||||||
.currency-selector {
|
.currency-selector {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -12,11 +12,22 @@ export default defineConfig({
|
|||||||
'@': path.resolve(__dirname, 'src')
|
'@': path.resolve(__dirname, 'src')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 构建配置
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
assetsDir: 'assets',
|
||||||
|
// 确保资源路径正确
|
||||||
|
assetsInlineLimit: 4096,
|
||||||
|
// 生成source map(生产环境可以关闭)
|
||||||
|
sourcemap: false
|
||||||
|
},
|
||||||
|
// 基础路径(如果部署在子目录下需要配置)
|
||||||
|
base: '/',
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://127.0.0.1:8082', // 使用 127.0.0.1 而不是 localhost,避免 IPv6 问题
|
target: 'http://127.0.0.1:8082',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
ws: true,
|
ws: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user