Compare commits
2 Commits
857f46ad17
...
dev-versio
| Author | SHA1 | Date | |
|---|---|---|---|
| 23e535562d | |||
| 6d725b51cb |
@@ -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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,8 +21,9 @@
|
|||||||
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-details">
|
||||||
<div class="product-info-sku" v-if="productInfo.sku">
|
<div class="product-info-sku" v-if="productInfo.sku">
|
||||||
<span class="sku-label">SKU:</span>
|
<span class="sku-label">SKU:</span>
|
||||||
<span class="sku-value">{{ productInfo.sku }}</span>
|
<span class="sku-value">{{ productInfo.sku }}</span>
|
||||||
@@ -37,7 +38,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-form
|
<el-form
|
||||||
@@ -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-form-item :label="t('order.postcode')" prop="shippingPostcode" required>
|
||||||
|
<div :class="isMobile ? 'mobile-postcode-group' : 'desktop-postcode-group'">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.shippingName"
|
v-model="form.shippingPostcode"
|
||||||
:placeholder="t('order.pleaseEnter') + t('order.shippingName') + '(' + t('order.mustMatchId') + ')'"
|
:placeholder="currentCountryConfig ? t('order.placeholderPostcode') + `(${currentCountryConfig.postcodeLength}${t('order.postcodeHint').replace('{0}', '')})` : t('order.placeholderPostcode')"
|
||||||
clearable
|
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-form-item v-if="form.shippingCountry !== 'VN'" :label="getCityLabel()" prop="shippingCity" required>
|
||||||
|
<div :class="isMobile ? 'mobile-input-group' : 'desktop-input-group'">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.shippingPhone"
|
v-model="form.shippingCity"
|
||||||
:placeholder="currentCountryConfig ? `${t('order.pleaseEnter')}${t('order.shippingPhone')}(${t('order.phoneCode')}:${currentCountryConfig.phoneCode})` : t('order.pleaseEnter') + t('order.shippingPhone') + '(' + t('order.phoneCode') + ')'"
|
:placeholder="getCityPlaceholder()"
|
||||||
|
:style="isMobile ? 'width: 100%' : form.shippingCountry === 'MY' ? 'width: 100%' : 'width: 48%'"
|
||||||
clearable
|
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-form-item :label="t('order.province')" prop="shippingProvince" required>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="currentCountryDisplayName"
|
v-model="form.shippingProvince"
|
||||||
disabled
|
:placeholder="t('order.placeholderProvinceVN')"
|
||||||
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
|
clearable
|
||||||
|
@input="updateAddressLine1"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('order.district')" prop="shippingDistrict" required>
|
||||||
<!-- 详细地址2(楼层、单元号,可选) -->
|
|
||||||
<el-form-item v-if="showField('shippingAddressLine2')" :label="t('order.addressLine2')" prop="shippingAddressLine2">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.shippingAddressLine2"
|
v-model="form.shippingDistrict"
|
||||||
:placeholder="t('order.placeholderAddressLine2')"
|
:placeholder="t('order.placeholderDistrictVN')"
|
||||||
clearable
|
clearable
|
||||||
|
@input="updateAddressLine1"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</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'">
|
<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,74 +193,55 @@
|
|||||||
/>
|
/>
|
||||||
</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
|
<el-input
|
||||||
v-model="form.shippingCity"
|
v-model="form.shippingStateMalaysia"
|
||||||
:placeholder="getCityPlaceholder()"
|
:placeholder="t('order.placeholderStateMalaysia')"
|
||||||
: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
|
|
||||||
v-model="form.shippingFloorUnit"
|
|
||||||
:placeholder="t('order.placeholderFloorUnit')"
|
|
||||||
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-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
|
<el-input
|
||||||
v-model="form.remark"
|
v-model="form.shippingAddressLine2"
|
||||||
type="textarea"
|
:placeholder="t('order.placeholderAddressLine2')"
|
||||||
:rows="3"
|
|
||||||
:placeholder="t('order.placeholderRemark')"
|
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</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">
|
||||||
@@ -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: ''
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前国家配置
|
// 当前国家配置
|
||||||
@@ -366,9 +337,10 @@ const currentCountryDisplayName = computed(() => {
|
|||||||
// 是否显示特定字段
|
// 是否显示特定字段
|
||||||
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,40 +530,19 @@ 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: [
|
||||||
],
|
|
||||||
shippingStreet: [
|
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.street')]), trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据国家配置添加必填字段验证
|
|
||||||
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' }
|
{ required: true, message: t('order.validationRequired', [t('order.addressLine1')]), trigger: 'blur' }
|
||||||
]
|
],
|
||||||
}
|
// 邮编始终必填(PayPal要求)
|
||||||
|
shippingPostcode: [
|
||||||
if (requiredFields.includes('shippingPostcode')) {
|
|
||||||
baseRules.shippingPostcode = [
|
|
||||||
{ required: true, message: t('order.validationRequired', [t('order.postcode')]), trigger: 'blur' },
|
{ required: true, message: t('order.validationRequired', [t('order.postcode')]), trigger: 'blur' },
|
||||||
{
|
{
|
||||||
validator: (rule, value, callback) => {
|
validator: (rule, value, callback) => {
|
||||||
if (value && !validatePostcode(form.shippingCountry, value)) {
|
if (value && currentCountryConfig.value && !validatePostcode(form.shippingCountry, value)) {
|
||||||
callback(new Error(t('order.validationPostcodeFormat', [currentCountryConfig.value.postcodeLength])))
|
callback(new Error(t('order.validationPostcodeFormat', [currentCountryConfig.value.postcodeLength])))
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
@@ -542,9 +550,17 @@ const getRules = () => {
|
|||||||
},
|
},
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
// 城市始终必填(PayPal要求,越南除外)
|
||||||
|
shippingCity: [
|
||||||
|
{ required: true, message: t('order.validationRequired', [t('order.city')]), trigger: 'blur' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据国家配置添加必填字段验证
|
||||||
|
if (currentCountryConfig.value) {
|
||||||
|
const requiredFields = getRequiredFields(form.shippingCountry)
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default defineConfig({
|
|||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://127.0.0.1:18082',
|
target: 'http://127.0.0.1:8082',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
ws: true,
|
ws: true,
|
||||||
@@ -35,7 +35,7 @@ export default defineConfig({
|
|||||||
configure: (proxy, options) => {
|
configure: (proxy, options) => {
|
||||||
proxy.on('error', (err, req, res) => {
|
proxy.on('error', (err, req, res) => {
|
||||||
console.error('代理错误:', err.message)
|
console.error('代理错误:', err.message)
|
||||||
console.error('请确保后端服务已启动在 http://127.0.0.1:18082')
|
console.error('请确保后端服务已启动在 http://127.0.0.1:8082')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user