Compare commits
1 Commits
dev
...
dev-versio
| Author | SHA1 | Date | |
|---|---|---|---|
| 23e535562d |
@@ -30,6 +30,8 @@ const zh = {
|
||||
pleaseEnter: '请输入',
|
||||
optional: '可选',
|
||||
required: '必填',
|
||||
more: '更多',
|
||||
collapse: '收起',
|
||||
addressFormat: '地址格式',
|
||||
phoneCode: '国际区号',
|
||||
mustMatchId: '需与证件一致,支持当地语言+英文',
|
||||
@@ -234,6 +236,8 @@ const en = {
|
||||
pleaseEnter: 'Please enter',
|
||||
optional: 'Optional',
|
||||
required: 'Required',
|
||||
more: 'More',
|
||||
collapse: 'Collapse',
|
||||
addressFormat: 'Address Format',
|
||||
phoneCode: 'Phone Code',
|
||||
mustMatchId: 'Must match ID, supports local language + English',
|
||||
@@ -436,6 +440,8 @@ const may = {
|
||||
pleaseEnter: 'Sila masukkan',
|
||||
optional: 'Pilihan',
|
||||
required: 'Diperlukan',
|
||||
more: 'Lebih Banyak',
|
||||
collapse: 'Tutup',
|
||||
addressFormat: 'Format Alamat',
|
||||
phoneCode: 'Kod Telefon',
|
||||
mustMatchId: 'Mesti sepadan dengan ID, menyokong bahasa tempatan + Inggeris',
|
||||
@@ -633,9 +639,14 @@ const fil = {
|
||||
pleaseEnter: 'Mangyaring ipasok',
|
||||
optional: 'Opsiyonal',
|
||||
required: 'Kinakailangan',
|
||||
more: 'Higit Pa',
|
||||
collapse: 'Itago',
|
||||
addressFormat: 'Format ng Address',
|
||||
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: {
|
||||
selectCurrency: 'Pumili ng Currency at Wika',
|
||||
@@ -779,6 +790,8 @@ const th = {
|
||||
pleaseEnter: 'กรุณากรอก',
|
||||
optional: 'ไม่บังคับ',
|
||||
required: 'จำเป็น',
|
||||
more: 'เพิ่มเติม',
|
||||
collapse: 'ย่อ',
|
||||
addressFormat: 'รูปแบบที่อยู่',
|
||||
phoneCode: 'รหัสโทรศัพท์',
|
||||
mustMatchId: 'ต้องตรงกับบัตรประชาชน รองรับภาษาท้องถิ่น + อังกฤษ',
|
||||
@@ -979,6 +992,8 @@ const vie = {
|
||||
pleaseEnter: 'Vui lòng nhập',
|
||||
optional: 'Tùy chọn',
|
||||
required: 'Bắt buộc',
|
||||
more: 'Thêm',
|
||||
collapse: 'Thu gọn',
|
||||
addressFormat: 'Định Dạng Địa Chỉ',
|
||||
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',
|
||||
@@ -1176,9 +1191,14 @@ const id = {
|
||||
pleaseEnter: 'Silakan masukkan',
|
||||
optional: 'Opsional',
|
||||
required: 'Diperlukan',
|
||||
more: 'Lebih Banyak',
|
||||
collapse: 'Tutup',
|
||||
addressFormat: 'Format Alamat',
|
||||
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: {
|
||||
selectCurrency: 'Pilih Mata Uang dan Bahasa',
|
||||
|
||||
@@ -44,12 +44,14 @@ const routes = [
|
||||
{
|
||||
path: '/create-order',
|
||||
name: 'CreateOrder',
|
||||
component: CreateOrder
|
||||
component: CreateOrder,
|
||||
meta: { isCustomerPage: true } // 标记为客户页面,不显示导航栏
|
||||
},
|
||||
{
|
||||
path: '/order/confirm',
|
||||
name: 'OrderConfirm',
|
||||
component: () => import('../views/OrderConfirm.vue')
|
||||
component: () => import('../views/OrderConfirm.vue'),
|
||||
meta: { isCustomerPage: true } // 标记为客户页面,不显示导航栏
|
||||
},
|
||||
{
|
||||
path: '/paypal/success',
|
||||
|
||||
@@ -25,8 +25,9 @@ export const countryConfigs = {
|
||||
phoneCode: '+65',
|
||||
postcodeLength: 6,
|
||||
postcodePattern: /^\d{6}$/,
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||
'shippingAddressLine1', 'shippingBlockNumber', 'shippingUnitNumber', 'shippingPostcode'],
|
||||
// 新加坡:城市和邮编是PayPal要求必填的
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||
'shippingCity', 'shippingAddressLine1', 'shippingBlockNumber', 'shippingUnitNumber', 'shippingPostcode'],
|
||||
specialFields: ['shippingBlockNumber', 'shippingUnitNumber'],
|
||||
fieldLabels: {
|
||||
shippingBlockNumber: '组屋号 (Block Number)',
|
||||
@@ -45,8 +46,9 @@ export const countryConfigs = {
|
||||
phoneCode: '+60',
|
||||
postcodeLength: 5,
|
||||
postcodePattern: /^\d{5}$/,
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||
'shippingStateMalaysia', 'shippingAddressLine1', 'shippingPostcode'],
|
||||
// 马来西亚:城市和邮编是PayPal要求必填的;州属放入更多按钮中,改为非必填
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||
'shippingCity', 'shippingAddressLine1', 'shippingPostcode'],
|
||||
specialFields: ['shippingStateMalaysia'],
|
||||
fieldLabels: {
|
||||
shippingStateMalaysia: '州属 (State)',
|
||||
@@ -64,8 +66,9 @@ export const countryConfigs = {
|
||||
phoneCode: '+63',
|
||||
postcodeLength: 4,
|
||||
postcodePattern: /^\d{4}$/,
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||
'shippingState', 'shippingBarangay', 'shippingAddressLine1', 'shippingPostcode'],
|
||||
// 菲律宾:城市和邮编是PayPal要求必填的;省放入更多按钮中,改为非必填
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||
'shippingCity', 'shippingBarangay', 'shippingAddressLine1', 'shippingPostcode'],
|
||||
specialFields: ['shippingBarangay'],
|
||||
fieldLabels: {
|
||||
shippingBarangay: 'Barangay(社区编号)',
|
||||
@@ -84,8 +87,9 @@ export const countryConfigs = {
|
||||
phoneCode: '+66',
|
||||
postcodeLength: 5,
|
||||
postcodePattern: /^\d{5}$/,
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||
'shippingState', 'shippingAddressLine1', 'shippingPostcode', 'shippingAddressThai'],
|
||||
// 泰国:城市和邮编是PayPal要求必填的;府放入更多按钮中,改为非必填
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||
'shippingCity', 'shippingAddressLine1', 'shippingAddressThai', 'shippingPostcode'],
|
||||
specialFields: ['shippingAddressThai', 'shippingAdministrativeArea'],
|
||||
fieldLabels: {
|
||||
shippingAddressThai: '泰文地址 (Thai Address)',
|
||||
@@ -105,8 +109,9 @@ export const countryConfigs = {
|
||||
phoneCode: '+84',
|
||||
postcodeLength: 5,
|
||||
postcodePattern: /^\d{5}$/,
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingProvince',
|
||||
'shippingDistrict', 'shippingWard', 'shippingAddressLine1', 'shippingPostcode'],
|
||||
// 越南:使用省/市/郡/区/坊代替城市;邮编是PayPal要求必填的
|
||||
requiredFields: ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||
'shippingProvince', 'shippingDistrict', 'shippingWard', 'shippingAddressLine1', 'shippingPostcode'],
|
||||
specialFields: ['shippingProvince', 'shippingDistrict', 'shippingWard'],
|
||||
fieldLabels: {
|
||||
shippingProvince: '省 (Tỉnh)',
|
||||
|
||||
@@ -21,20 +21,20 @@
|
||||
fit="cover"
|
||||
class="product-info-image"
|
||||
/>
|
||||
<div class="product-info-details">
|
||||
<div class="product-info-name">{{ productInfo.name }}</div>
|
||||
<div class="product-info-sku" v-if="productInfo.sku">
|
||||
<span class="sku-label">SKU:</span>
|
||||
<span class="sku-value">{{ productInfo.sku }}</span>
|
||||
</div>
|
||||
<div class="product-info-price">
|
||||
<span class="price-label">{{ $t('product.unitPrice') }}:</span>
|
||||
<span class="price-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price) }}</span>
|
||||
<span class="quantity-label">{{ $t('product.quantity') }}:</span>
|
||||
<span class="quantity-value">x{{ productInfo.quantity }}</span>
|
||||
<span class="total-label">{{ $t('order.subtotal') }}:</span>
|
||||
<span class="total-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price * productInfo.quantity) }}</span>
|
||||
</div>
|
||||
<div class="product-info-name">{{ productInfo.name }}</div>
|
||||
</div>
|
||||
<div class="product-info-details">
|
||||
<div class="product-info-sku" v-if="productInfo.sku">
|
||||
<span class="sku-label">SKU:</span>
|
||||
<span class="sku-value">{{ productInfo.sku }}</span>
|
||||
</div>
|
||||
<div class="product-info-price">
|
||||
<span class="price-label">{{ $t('product.unitPrice') }}:</span>
|
||||
<span class="price-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price) }}</span>
|
||||
<span class="quantity-label">{{ $t('product.quantity') }}:</span>
|
||||
<span class="quantity-value">x{{ productInfo.quantity }}</span>
|
||||
<span class="total-label">{{ $t('order.subtotal') }}:</span>
|
||||
<span class="total-value">{{ productInfo.currency }} {{ formatPrice(productInfo.price * productInfo.quantity) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,50 +76,74 @@
|
||||
<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-form-item :label="t('order.shippingName')" prop="shippingName">
|
||||
<el-input
|
||||
v-model="form.shippingName"
|
||||
:placeholder="t('order.pleaseEnter') + t('order.shippingName') + '(' + t('order.mustMatchId') + ')'"
|
||||
clearable
|
||||
/>
|
||||
<!-- 邮编(PayPal要求必填)- 常驻展示,必填 -->
|
||||
<el-form-item :label="t('order.postcode')" prop="shippingPostcode" required>
|
||||
<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.shippingPhone')" prop="shippingPhone">
|
||||
<el-input
|
||||
v-model="form.shippingPhone"
|
||||
:placeholder="currentCountryConfig ? `${t('order.pleaseEnter')}${t('order.shippingPhone')}(${t('order.phoneCode')}:${currentCountryConfig.phoneCode})` : t('order.pleaseEnter') + t('order.shippingPhone') + '(' + t('order.phoneCode') + ')'"
|
||||
clearable
|
||||
/>
|
||||
<!-- 城市(PayPal要求必填)- 常驻展示,必填(越南不使用此字段,使用独立的省/市/郡/区/坊) -->
|
||||
<el-form-item v-if="form.shippingCountry !== 'VN'" :label="getCityLabel()" prop="shippingCity" required>
|
||||
<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
|
||||
@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 :label="t('order.shippingCountry')" prop="shippingCountry">
|
||||
<el-input
|
||||
v-model="currentCountryDisplayName"
|
||||
disabled
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 详细地址1(门牌号、街道、楼栋)- 所有国家都显示 -->
|
||||
<el-form-item v-if="showField('shippingAddressLine1')" :label="t('order.addressLine1')" prop="shippingAddressLine1">
|
||||
<el-input
|
||||
v-model="form.shippingAddressLine1"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:placeholder="t('order.placeholderAddressLine1')"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 详细地址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>
|
||||
<!-- 越南:省、市/郡、区/坊(PayPal要求必填)- 常驻展示,必填 -->
|
||||
<template v-if="form.shippingCountry === 'VN'">
|
||||
<el-form-item :label="t('order.province')" prop="shippingProvince" required>
|
||||
<el-input
|
||||
v-model="form.shippingProvince"
|
||||
:placeholder="t('order.placeholderProvinceVN')"
|
||||
clearable
|
||||
@input="updateAddressLine1"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('order.district')" prop="shippingDistrict" required>
|
||||
<el-input
|
||||
v-model="form.shippingDistrict"
|
||||
:placeholder="t('order.placeholderDistrictVN')"
|
||||
clearable
|
||||
@input="updateAddressLine1"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('order.ward')" prop="shippingWard" required>
|
||||
<el-input
|
||||
v-model="form.shippingWard"
|
||||
:placeholder="t('order.placeholderWardVN')"
|
||||
clearable
|
||||
@input="updateAddressLine1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 新加坡:组屋号和单元号 -->
|
||||
<template v-if="form.shippingCountry === 'SG'">
|
||||
@@ -159,39 +183,6 @@
|
||||
/>
|
||||
</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) -->
|
||||
<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 v-if="form.shippingCountry !== 'VN'" :label="getCityLabel()" prop="shippingCity">
|
||||
<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-form-item v-if="form.shippingCountry === 'MY'" :label="t('order.stateMalaysia')" prop="shippingStateMalaysia">
|
||||
<el-input
|
||||
v-model="form.shippingFloorUnit"
|
||||
:placeholder="t('order.placeholderFloorUnit')"
|
||||
v-model="form.shippingStateMalaysia"
|
||||
:placeholder="t('order.placeholderStateMalaysia')"
|
||||
clearable
|
||||
@input="updateAddressLine1"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 兼容旧字段:街道地址(如果新字段为空,使用旧字段) -->
|
||||
<el-form-item v-if="!form.shippingAddressLine1" :label="t('order.street')" prop="shippingStreet">
|
||||
<!-- 详细地址1(门牌号、街道、楼栋)- 所有国家都显示,必填,放在最下面 -->
|
||||
<el-form-item v-if="showField('shippingAddressLine1')" :label="t('order.addressLine1')" prop="shippingAddressLine1" required>
|
||||
<el-input
|
||||
v-model="form.shippingStreet"
|
||||
v-model="form.shippingAddressLine1"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:placeholder="t('order.placeholderStreet')"
|
||||
:placeholder="t('order.placeholderAddressLine1')"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('order.remark')" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
:placeholder="t('order.placeholderRemark')"
|
||||
clearable
|
||||
/>
|
||||
<!-- 更多地址信息按钮 -->
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="text"
|
||||
@click="showMoreAddressFields = !showMoreAddressFields"
|
||||
style="padding: 0; color: #409eff;"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- 更多地址信息 - 只包含详细地址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-button type="primary" size="large" @click="submitForm" :loading="loading" style="width: 200px">
|
||||
{{ $t('order.submit') }}
|
||||
@@ -287,6 +259,7 @@ import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
|
||||
import { createCustomerOrder } from '../api/order'
|
||||
import { formatAmount } from '../utils/helpers'
|
||||
import { getCountryConfig, getCountryByCurrency, validatePostcode, getRequiredFields } from '../utils/countryConfig'
|
||||
@@ -301,13 +274,13 @@ const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const productInfo = ref(null)
|
||||
const postcodeMatching = ref(false) // 邮编匹配中
|
||||
const showMoreAddressFields = ref(false) // 控制更多地址字段的显示
|
||||
const isAutoUpdating = ref(false) // 防止自动更新时的递归调用
|
||||
|
||||
const form = reactive({
|
||||
customerName: '',
|
||||
customerPhone: '',
|
||||
customerEmail: '',
|
||||
shippingName: '',
|
||||
shippingPhone: '',
|
||||
shippingCountry: '',
|
||||
shippingState: '',
|
||||
shippingCity: '',
|
||||
@@ -324,9 +297,7 @@ const form = reactive({
|
||||
shippingProvince: '',
|
||||
shippingDistrict: '',
|
||||
shippingWard: '',
|
||||
shippingStateMalaysia: '',
|
||||
shippingFloorUnit: '',
|
||||
remark: ''
|
||||
shippingStateMalaysia: ''
|
||||
})
|
||||
|
||||
// 当前国家配置
|
||||
@@ -363,12 +334,13 @@ const currentCountryDisplayName = computed(() => {
|
||||
return countryNameMap[lang] || countryNameMap.en || form.shippingCountry
|
||||
})
|
||||
|
||||
// 是否显示特定字段
|
||||
// 是否显示特定字段
|
||||
const showField = (fieldName) => {
|
||||
if (!currentCountryConfig.value) {
|
||||
// 默认显示基础字段
|
||||
return ['shippingName', 'shippingPhone', 'shippingCountry', 'shippingCity',
|
||||
'shippingState', 'shippingStreet', 'shippingPostcode'].includes(fieldName)
|
||||
// 默认显示基础字段(包括详细地址)
|
||||
return ['shippingCountry', 'shippingCity',
|
||||
'shippingState', 'shippingPostcode',
|
||||
'shippingAddressLine1', 'shippingAddressLine2'].includes(fieldName)
|
||||
}
|
||||
|
||||
const specialFields = currentCountryConfig.value.specialFields || []
|
||||
@@ -385,8 +357,8 @@ const showField = (fieldName) => {
|
||||
}
|
||||
|
||||
// 基础字段始终显示
|
||||
const baseFields = ['shippingName', 'shippingPhone', 'shippingCountry',
|
||||
'shippingCity', 'shippingState', 'shippingStreet',
|
||||
const baseFields = ['shippingCountry',
|
||||
'shippingCity', 'shippingState',
|
||||
'shippingPostcode', 'shippingAddressLine1', 'shippingAddressLine2']
|
||||
if (baseFields.includes(fieldName)) {
|
||||
return true
|
||||
@@ -408,17 +380,9 @@ const getFieldLabel = (fieldName) => {
|
||||
return fieldName
|
||||
}
|
||||
|
||||
// 获取城市字段标签(根据国家动态显示)
|
||||
// 获取城市字段标签(根据国家动态显示,只显示对应语言,不包含中文)
|
||||
const getCityLabel = () => {
|
||||
if (form.shippingCountry === 'TH') {
|
||||
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)'
|
||||
}
|
||||
// 直接返回国际化翻译,i18n会根据当前语言环境自动返回对应语言的标签
|
||||
return t('order.cityTown')
|
||||
}
|
||||
|
||||
@@ -440,6 +404,66 @@ const getStatePlaceholder = () => {
|
||||
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) => {
|
||||
if (newCountry !== oldCountry) {
|
||||
@@ -453,14 +477,19 @@ watch(() => form.shippingCountry, (newCountry, oldCountry) => {
|
||||
form.shippingDistrict = ''
|
||||
form.shippingWard = ''
|
||||
form.shippingAdministrativeArea = ''
|
||||
|
||||
// 更新电话区号提示
|
||||
if (currentCountryConfig.value) {
|
||||
form.shippingPhone = currentCountryConfig.value.phoneCode + ' '
|
||||
}
|
||||
// 清空详细地址1(因为地址组件已清空)
|
||||
form.shippingAddressLine1 = ''
|
||||
}
|
||||
})
|
||||
|
||||
// 监听地址组件变化,自动更新详细地址1
|
||||
watch([() => form.shippingCity, () => form.shippingState, () => form.shippingStateMalaysia,
|
||||
() => form.shippingProvince, () => form.shippingDistrict, () => form.shippingWard],
|
||||
() => {
|
||||
updateAddressLine1()
|
||||
}
|
||||
)
|
||||
|
||||
// 监听邮编变化,自动匹配城市
|
||||
watch(() => form.shippingPostcode, async (newPostcode) => {
|
||||
if (!newPostcode || !form.shippingCountry) return
|
||||
@@ -501,21 +530,30 @@ const getRules = () => {
|
||||
customerEmail: [
|
||||
{ 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: [
|
||||
{ required: true, message: t('order.validationSelectCountry'), trigger: 'change' }
|
||||
],
|
||||
shippingCity: [
|
||||
{ required: true, message: t('order.validationRequired', [t('order.cityTown')]), trigger: 'blur' }
|
||||
// 详细地址1始终必填
|
||||
shippingAddressLine1: [
|
||||
{ required: true, message: t('order.validationRequired', [t('order.addressLine1')]), trigger: 'blur' }
|
||||
],
|
||||
shippingStreet: [
|
||||
{ required: true, message: t('order.validationRequired', [t('order.street')]), trigger: 'blur' }
|
||||
// 邮编始终必填(PayPal要求)
|
||||
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) {
|
||||
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')) {
|
||||
baseRules.shippingBlockNumber = [
|
||||
{ required: true, message: t('order.validationRequired', [t('order.blockNumber')]), trigger: 'blur' }
|
||||
@@ -639,12 +655,12 @@ const submitForm = async () => {
|
||||
customerName: form.customerName,
|
||||
customerPhone: form.customerPhone,
|
||||
customerEmail: form.customerEmail || null,
|
||||
shippingName: form.shippingName,
|
||||
shippingPhone: form.shippingPhone,
|
||||
shippingName: form.customerName, // 使用客户姓名作为收货人姓名
|
||||
shippingPhone: form.customerPhone, // 使用客户电话作为收货人电话
|
||||
shippingCountry: form.shippingCountry,
|
||||
shippingState: form.shippingState || null,
|
||||
shippingCity: shippingCity, // 使用映射后的值,越南使用 shippingDistrict
|
||||
shippingStreet: form.shippingStreet || form.shippingAddressLine1, // 兼容旧字段
|
||||
shippingStreet: form.shippingAddressLine1, // 使用详细地址1
|
||||
shippingPostcode: form.shippingPostcode || null,
|
||||
// 东南亚地址扩展字段
|
||||
shippingAddressLine1: form.shippingAddressLine1 || null,
|
||||
@@ -658,8 +674,6 @@ const submitForm = async () => {
|
||||
shippingDistrict: form.shippingDistrict || null,
|
||||
shippingWard: form.shippingWard || null,
|
||||
shippingStateMalaysia: form.shippingStateMalaysia || null,
|
||||
shippingFloorUnit: form.shippingFloorUnit || null,
|
||||
remark: form.remark || null
|
||||
}
|
||||
|
||||
const response = await createCustomerOrder(orderData)
|
||||
@@ -703,10 +717,6 @@ onMounted(async () => {
|
||||
const countryCode = getCountryByCurrency(data.product.currency)
|
||||
if (countryCode) {
|
||||
form.shippingCountry = countryCode
|
||||
const config = getCountryConfig(countryCode)
|
||||
if (config) {
|
||||
form.shippingPhone = config.phoneCode + ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,22 +765,18 @@ onMounted(async () => {
|
||||
|
||||
.product-info-main {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.product-info-image {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e4e7ed;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-info-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.product-info-name {
|
||||
@@ -778,6 +784,13 @@ onMounted(async () => {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-info-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.product-info-sku {
|
||||
@@ -830,39 +843,74 @@ onMounted(async () => {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 移动端优化 */
|
||||
/* 移动端优先设计 - 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.create-order {
|
||||
padding: 10px;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
padding: 10px 0;
|
||||
padding: 12px 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* 商品信息卡片优化 */
|
||||
.product-info-card {
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.product-card-header {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.product-info-content {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.product-info-main {
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.product-info-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
align-self: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
|
||||
.product-info-name {
|
||||
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 {
|
||||
@@ -870,33 +918,58 @@ onMounted(async () => {
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.price-label,
|
||||
.quantity-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.price-value,
|
||||
.quantity-value {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
margin-left: 0;
|
||||
margin-top: 8px;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 表单优化 */
|
||||
.el-form {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 18px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
padding-bottom: 5px;
|
||||
padding-bottom: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.el-input,
|
||||
.el-select,
|
||||
.el-textarea {
|
||||
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%;
|
||||
}
|
||||
|
||||
/* 按钮优化 */
|
||||
.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 {
|
||||
margin: 20px 0;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.el-divider__text {
|
||||
font-size: 14px;
|
||||
padding: 0 15px;
|
||||
padding: 0 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 提示信息优化 */
|
||||
.el-alert {
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
/* 输入组优化 */
|
||||
@@ -951,23 +1007,73 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.desktop-input-group {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 4%;
|
||||
}
|
||||
|
||||
.mobile-postcode-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.desktop-postcode-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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>
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
:style="isMobile ? 'width: 100%' : 'width: 200px'"
|
||||
>
|
||||
<el-icon><Money /></el-icon>
|
||||
{{ $t('confirm.payNow') }}
|
||||
{{ countdown > 0 ? `${$t('confirm.payNow')} (${countdown}s)` : $t('confirm.payNow') }}
|
||||
</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>
|
||||
@@ -168,7 +168,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage } from 'element-plus'
|
||||
@@ -185,6 +185,9 @@ const route = useRoute()
|
||||
const orderLoading = ref(true)
|
||||
const payLoading = ref(false)
|
||||
const order = ref(null)
|
||||
const autoPayTimer = ref(null) // 自动支付定时器
|
||||
const countdownTimer = ref(null) // 倒计时定时器
|
||||
const countdown = ref(0) // 倒计时(秒)
|
||||
|
||||
// 移动端检测
|
||||
const isMobile = computed(() => {
|
||||
@@ -363,6 +366,31 @@ const loadOrder = async () => {
|
||||
order.value = null
|
||||
} finally {
|
||||
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
|
||||
}
|
||||
|
||||
// 清除自动支付定时器和倒计时(如果存在)
|
||||
if (autoPayTimer.value) {
|
||||
clearTimeout(autoPayTimer.value)
|
||||
autoPayTimer.value = null
|
||||
}
|
||||
if (countdownTimer.value) {
|
||||
clearInterval(countdownTimer.value)
|
||||
countdownTimer.value = null
|
||||
}
|
||||
countdown.value = 0
|
||||
|
||||
payLoading.value = true
|
||||
|
||||
try {
|
||||
@@ -454,6 +493,18 @@ const handlePay = async () => {
|
||||
onMounted(() => {
|
||||
loadOrder()
|
||||
})
|
||||
|
||||
// 组件卸载时清理定时器
|
||||
onUnmounted(() => {
|
||||
if (autoPayTimer.value) {
|
||||
clearTimeout(autoPayTimer.value)
|
||||
autoPayTimer.value = null
|
||||
}
|
||||
if (countdownTimer.value) {
|
||||
clearInterval(countdownTimer.value)
|
||||
countdownTimer.value = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -628,18 +679,31 @@ onMounted(() => {
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
/* 移动端优化 */
|
||||
/* 移动端优先设计 - 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.order-confirm {
|
||||
padding: 10px;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.order-info-section {
|
||||
@@ -647,22 +711,47 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.order-info-section h3 {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.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 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
@@ -670,35 +759,53 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.conversion-main-info {
|
||||
padding: 12px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.payment-amount-highlight {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.payment-amount-large {
|
||||
font-size: 20px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.exchange-rate-info {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rate-locked-info {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
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 {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
height: 44px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.action-buttons .el-button + .el-button {
|
||||
@@ -710,26 +817,49 @@ onMounted(() => {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.el-descriptions--border {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-descriptions-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.el-descriptions-item__label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
width: 100px;
|
||||
padding-right: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.el-descriptions-item__content {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.shipping-address-detail {
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.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 {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,62 +198,6 @@
|
||||
</el-tabs>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
@@ -281,7 +225,6 @@ const hoveredImage = ref(null)
|
||||
const quantity = ref(1)
|
||||
const activeTab = ref('detail')
|
||||
const selectedSku = ref(null)
|
||||
const showConfirmDialog = ref(false)
|
||||
|
||||
// 移动端检测
|
||||
const isMobile = computed(() => {
|
||||
@@ -413,7 +356,7 @@ const hoverImage = (img) => {
|
||||
hoveredImage.value = img
|
||||
}
|
||||
|
||||
// 立即购买
|
||||
// 立即购买(直接跳转,不再显示确认弹窗)
|
||||
const handleBuyNow = () => {
|
||||
if (!selectedSku.value) {
|
||||
ElMessage.warning('请先选择商品SKU')
|
||||
@@ -425,18 +368,6 @@ const handleBuyNow = () => {
|
||||
return
|
||||
}
|
||||
|
||||
showConfirmDialog.value = true
|
||||
}
|
||||
|
||||
// 确认购买
|
||||
const confirmBuy = () => {
|
||||
if (!selectedSku.value) {
|
||||
ElMessage.warning('请先选择商品SKU')
|
||||
return
|
||||
}
|
||||
|
||||
showConfirmDialog.value = false
|
||||
|
||||
// 构建订单数据
|
||||
const orderData = {
|
||||
product: {
|
||||
@@ -1209,97 +1140,245 @@ onMounted(() => {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
/* 移动端优先设计 - 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.product-detail {
|
||||
padding: 10px;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.product-main {
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
gap: 20px;
|
||||
padding: 12px;
|
||||
gap: 16px;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.product-images {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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%;
|
||||
}
|
||||
|
||||
.service-section {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
.product-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.currency-selector-card {
|
||||
padding: 10px 12px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
padding: 12px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.currency-selector-header {
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.currency-icon {
|
||||
font-size: 16px;
|
||||
margin-right: 5px;
|
||||
font-size: 18px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.currency-selector-header .selector-label {
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.currency-selector-content {
|
||||
padding: 8px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.currency-radio-group {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.currency-radio-button {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-width: calc(50% - 3px);
|
||||
}
|
||||
|
||||
.currency-radio-button :deep(.el-radio-button__inner) {
|
||||
font-size: 11px;
|
||||
padding: 6px 4px;
|
||||
font-size: 12px;
|
||||
padding: 8px 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
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 {
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user