修复bug
This commit is contained in:
@@ -54,9 +54,6 @@
|
|||||||
// 检查并更新语言(uni.reLaunch 会触发 onShow)
|
// 检查并更新语言(uni.reLaunch 会触发 onShow)
|
||||||
try {
|
try {
|
||||||
const savedLang = uni.getStorageSync('language')
|
const savedLang = uni.getStorageSync('language')
|
||||||
if(savedLang){
|
|
||||||
uni.removeStorageSync('language');
|
|
||||||
}
|
|
||||||
console.log('App onShow - 缓存中的语言:', savedLang)
|
console.log('App onShow - 缓存中的语言:', savedLang)
|
||||||
|
|
||||||
// 获取当前 i18n 实例并检查语言
|
// 获取当前 i18n 实例并检查语言
|
||||||
|
|||||||
@@ -55,10 +55,6 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="h5-bottom-actions">
|
<view class="h5-bottom-actions">
|
||||||
<view class="action-btn secondary" @click="$emit('buy')">
|
|
||||||
<image class="action-icon" src="/static/shop_icon.png" mode="aspectFit" />
|
|
||||||
<text class="action-label">{{ buyDeviceText }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="action-btn primary" @click="$emit('scan')">
|
<view class="action-btn primary" @click="$emit('scan')">
|
||||||
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
||||||
<text class="action-label primary-label">{{ scanText }}</text>
|
<text class="action-label primary-label">{{ scanText }}</text>
|
||||||
@@ -243,34 +239,36 @@
|
|||||||
right: 20rpx;
|
right: 20rpx;
|
||||||
bottom: 30rpx;
|
bottom: 30rpx;
|
||||||
z-index: 1200;
|
z-index: 1200;
|
||||||
padding: 14rpx;
|
padding: 12rpx;
|
||||||
background: rgba(255, 255, 255, 0.96);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
border-radius: 56rpx;
|
border-radius: 28rpx;
|
||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
gap: 12rpx;
|
gap: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
height: 86rpx;
|
min-height: 84rpx;
|
||||||
border-radius: 43rpx;
|
border-radius: 20rpx;
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.primary {
|
.action-btn.primary {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: #3EAB64;
|
background: #3EAB64;
|
||||||
max-width: 360rpx;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn.secondary {
|
.action-btn.secondary {
|
||||||
width: 150rpx;
|
width: 180rpx;
|
||||||
background: #f4f6f8;
|
background: #f4f6f8;
|
||||||
|
border: 2rpx solid #e7eaee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-icon {
|
.action-icon {
|
||||||
@@ -285,6 +283,7 @@
|
|||||||
.action-label {
|
.action-label {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary-label {
|
.primary-label {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import request from '../http'
|
import request from '../http'
|
||||||
|
|
||||||
// 获取系统配置(预留接口)
|
// 获取系统配置
|
||||||
// 期望后端返回形如:{ code: 200, data: { expressReturnCountdownSeconds: number } }
|
// 可传参示例:{ configKey: 'overseas_payment_dana_total' }
|
||||||
export const getSystemConfig = () => {
|
export const getSystemConfig = (data = {}) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/app/system/config',
|
url: '/system/config/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
data,
|
||||||
hideLoading: true
|
hideLoading: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
|
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
|
||||||
// export const URL = "https://manager.fdzpower.com/api" //正式国内服务器
|
// export const URL = "https://manager.fdzpower.com/api" //正式国内服务器
|
||||||
// export const URL = "https://ina.fdzpower.com/api" //正式国外服务器
|
export const URL = "https://ina.fdzpower.com/api" //正式国外服务器
|
||||||
// export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
|
// export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
|
||||||
export const URL = "http://192.168.0.158:8080" //本地调试
|
// export const URL = "http://192.168.0.158:8080" //本地调试
|
||||||
// export const URL = "http://127.0.0.1:8080" //本地调试
|
// export const URL = "http://127.0.0.1:8080" //本地调试
|
||||||
|
|
||||||
export const appid = "wx2165f0be356ae7a9" //微信小程序appid
|
export const appid = "wx2165f0be356ae7a9" //微信小程序appid
|
||||||
|
|||||||
@@ -7,10 +7,49 @@ import enUS from './locale/en-US.js'
|
|||||||
import idID from './locale/id-ID.js'
|
import idID from './locale/id-ID.js'
|
||||||
import uView from '@climblee/uv-ui'
|
import uView from '@climblee/uv-ui'
|
||||||
import { initConsoleControl } from './config/console.js'
|
import { initConsoleControl } from './config/console.js'
|
||||||
|
import { getSystemConfig } from './config/api/system.js'
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
// 兼容部分依赖/构建产物在浏览器环境访问 process.env 的场景
|
||||||
|
if (typeof globalThis !== 'undefined' && typeof globalThis.process === 'undefined') {
|
||||||
|
globalThis.process = { env: {} }
|
||||||
|
}
|
||||||
|
if (typeof globalThis !== 'undefined' && globalThis.process && !globalThis.process.env) {
|
||||||
|
globalThis.process.env = {}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
// 初始化 console 控制
|
// 初始化 console 控制
|
||||||
initConsoleControl()
|
initConsoleControl()
|
||||||
|
|
||||||
|
const LANGUAGE_STORAGE_KEY = 'language'
|
||||||
|
const SUPPORTED_LANGUAGES = ['zh-CN', 'en-US', 'id-ID']
|
||||||
|
|
||||||
|
const LANGUAGE_ALIASES = {
|
||||||
|
zh: 'zh-CN',
|
||||||
|
'zh-cn': 'zh-CN',
|
||||||
|
'zh_cn': 'zh-CN',
|
||||||
|
en: 'en-US',
|
||||||
|
'en-us': 'en-US',
|
||||||
|
'en_us': 'en-US',
|
||||||
|
id: 'id-ID',
|
||||||
|
'id-id': 'id-ID',
|
||||||
|
'id_id': 'id-ID',
|
||||||
|
in: 'id-ID',
|
||||||
|
'in-id': 'id-ID',
|
||||||
|
'in_id': 'id-ID'
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeLanguage = (lang) => {
|
||||||
|
if (!lang || typeof lang !== 'string') return ''
|
||||||
|
const cleaned = lang.trim()
|
||||||
|
if (!cleaned) return ''
|
||||||
|
const lower = cleaned.toLowerCase()
|
||||||
|
if (LANGUAGE_ALIASES[lower]) return LANGUAGE_ALIASES[lower]
|
||||||
|
if (SUPPORTED_LANGUAGES.includes(cleaned)) return cleaned
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
// 检测是否为 H5 环境
|
// 检测是否为 H5 环境
|
||||||
const isH5Platform = () => {
|
const isH5Platform = () => {
|
||||||
try {
|
try {
|
||||||
@@ -25,42 +64,69 @@ const isH5Platform = () => {
|
|||||||
|
|
||||||
// 获取系统语言
|
// 获取系统语言
|
||||||
const getSystemLanguage = () => {
|
const getSystemLanguage = () => {
|
||||||
// H5 环境默认使用印尼语
|
let language = 'zh-CN'
|
||||||
if (isH5Platform()) {
|
|
||||||
return 'id-ID'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 非 H5 环境根据系统语言判断
|
|
||||||
let language = 'en-US'
|
|
||||||
try {
|
try {
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
const systemInfo = uni.getSystemInfoSync() || {}
|
||||||
if (systemInfo && systemInfo.language) {
|
const systemLanguage = normalizeLanguage(systemInfo.language)
|
||||||
language = systemInfo.language === 'zh' || systemInfo.language.indexOf('zh') === 0
|
if (systemLanguage) {
|
||||||
? 'zh-CN'
|
language = systemLanguage
|
||||||
: 'en-US'
|
} else if (isH5Platform() && typeof navigator !== 'undefined') {
|
||||||
|
const browserLanguage = normalizeLanguage(navigator.language || '')
|
||||||
|
if (browserLanguage) language = browserLanguage
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取系统语言失败:', e)
|
console.error('获取系统语言失败:', e)
|
||||||
// 默认使用中文
|
|
||||||
language = 'zh-CN'
|
language = 'zh-CN'
|
||||||
}
|
}
|
||||||
return language
|
return language
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractLanguageFromConfig = (data) => {
|
||||||
|
if (!data) return ''
|
||||||
|
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
return normalizeLanguage(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
for (const item of data) {
|
||||||
|
const fromItem = extractLanguageFromConfig(item)
|
||||||
|
if (fromItem) return fromItem
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
const direct = normalizeLanguage(
|
||||||
|
data.language || data.lang || data.locale || data.defaultLanguage || data.defaultLang
|
||||||
|
)
|
||||||
|
if (direct) return direct
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
const keyLower = String(key).toLowerCase()
|
||||||
|
if (keyLower.includes('lang') || keyLower.includes('locale')) {
|
||||||
|
const parsed = extractLanguageFromConfig(value)
|
||||||
|
if (parsed) return parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
// 获取用户选择的语言
|
// 获取用户选择的语言
|
||||||
const getSavedLanguage = () => {
|
const getSavedLanguage = () => {
|
||||||
try {
|
try {
|
||||||
const savedLang = uni.getStorageSync('language')
|
const savedLang = normalizeLanguage(uni.getStorageSync(LANGUAGE_STORAGE_KEY))
|
||||||
if (savedLang) {
|
if (savedLang) {
|
||||||
return savedLang
|
return savedLang
|
||||||
}
|
}
|
||||||
const systemLang = getSystemLanguage()
|
const systemLang = getSystemLanguage()
|
||||||
uni.setStorageSync('language', systemLang)
|
uni.setStorageSync(LANGUAGE_STORAGE_KEY, systemLang)
|
||||||
return systemLang
|
return systemLang
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('语言设置出错:', e)
|
console.error('语言设置出错:', e)
|
||||||
// 出错时根据平台返回默认语言
|
return 'zh-CN'
|
||||||
return isH5Platform() ? 'id-ID' : 'zh-CN'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +170,27 @@ function getI18nInstance() {
|
|||||||
return i18nInstance
|
return i18nInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncLanguageFromRemoteConfig = async (i18n) => {
|
||||||
|
if (!isH5Platform()) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getSystemConfig()
|
||||||
|
if (!res || res.code !== 200) return
|
||||||
|
|
||||||
|
const remoteLang = extractLanguageFromConfig(res.data)
|
||||||
|
if (!remoteLang) return
|
||||||
|
|
||||||
|
const current = normalizeLanguage(i18n?.global?.locale || '')
|
||||||
|
if (current !== remoteLang) {
|
||||||
|
uni.setStorageSync(LANGUAGE_STORAGE_KEY, remoteLang)
|
||||||
|
i18n.global.locale = remoteLang
|
||||||
|
console.log('H5 语言已按系统配置更新为:', remoteLang)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('读取系统配置语言失败,使用本地语言设置:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
|
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
@@ -117,6 +204,9 @@ export function createApp() {
|
|||||||
// 使用 i18n
|
// 使用 i18n
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|
||||||
|
// H5 端通过系统配置同步语言(异步,不阻塞应用启动)
|
||||||
|
syncLanguageFromRemoteConfig(i18n)
|
||||||
|
|
||||||
// 手动注入 $i18n 到全局属性(确保组件可以访问)
|
// 手动注入 $i18n 到全局属性(确保组件可以访问)
|
||||||
app.config.globalProperties.$i18n = i18n.global
|
app.config.globalProperties.$i18n = i18n.global
|
||||||
|
|
||||||
|
|||||||
+31
-5
@@ -155,6 +155,12 @@
|
|||||||
import {
|
import {
|
||||||
useI18n
|
useI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
import {
|
||||||
|
fetchAndCacheDanaPaymentConfig,
|
||||||
|
DANA_TOTAL_STORAGE_KEY,
|
||||||
|
DANA_SINGLE_STORAGE_KEY,
|
||||||
|
parseDanaStorageNumber
|
||||||
|
} from '@/utils/danaPaymentConfig.js'
|
||||||
import DeviceDetailSkeleton from '@/components/DeviceDetailSkeleton.vue'
|
import DeviceDetailSkeleton from '@/components/DeviceDetailSkeleton.vue'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -179,11 +185,31 @@
|
|||||||
const isWechatMiniProgram = ref(false)
|
const isWechatMiniProgram = ref(false)
|
||||||
const isAlipayMiniProgram = ref(false)
|
const isAlipayMiniProgram = ref(false)
|
||||||
const isH5 = ref(false)
|
const isH5 = ref(false)
|
||||||
const IDR_DEPOSIT_DISPLAY = 99000
|
const IDR_DEPOSIT_DISPLAY = ref(99000)
|
||||||
const IDR_HOUR_PRICE_DISPLAY = 6000
|
const IDR_HOUR_PRICE_DISPLAY = ref(6000)
|
||||||
|
|
||||||
|
const loadDanaPricingCache = () => {
|
||||||
|
try {
|
||||||
|
const totalCached = parseDanaStorageNumber(uni.getStorageSync(DANA_TOTAL_STORAGE_KEY))
|
||||||
|
const singleCached = parseDanaStorageNumber(uni.getStorageSync(DANA_SINGLE_STORAGE_KEY))
|
||||||
|
if (totalCached !== null && totalCached > 0) {
|
||||||
|
IDR_DEPOSIT_DISPLAY.value = totalCached
|
||||||
|
}
|
||||||
|
if (singleCached !== null && singleCached > 0) {
|
||||||
|
IDR_HOUR_PRICE_DISPLAY.value = singleCached
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('读取 DANA 金额缓存失败,使用默认值:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 生命周期 onLoad 钩子
|
// 生命周期 onLoad 钩子
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
|
loadDanaPricingCache()
|
||||||
|
void fetchAndCacheDanaPaymentConfig()
|
||||||
|
.then(() => loadDanaPricingCache())
|
||||||
|
.catch((e) => console.warn('DANA 配置刷新失败:', e))
|
||||||
|
|
||||||
// 普通链接二维码进入时,参数通常在 options.q(且为编码后的完整 URL)
|
// 普通链接二维码进入时,参数通常在 options.q(且为编码后的完整 URL)
|
||||||
if (!options.deviceNo && options.q) {
|
if (!options.deviceNo && options.q) {
|
||||||
@@ -603,12 +629,12 @@
|
|||||||
const displayCurrencySymbol = computed(() => (isIdrCurrency.value ? 'Rp ' : '¥'))
|
const displayCurrencySymbol = computed(() => (isIdrCurrency.value ? 'Rp ' : '¥'))
|
||||||
|
|
||||||
const displayHourlyPrice = computed(() => {
|
const displayHourlyPrice = computed(() => {
|
||||||
if (isIdrCurrency.value) return `${IDR_HOUR_PRICE_DISPLAY}`
|
if (isIdrCurrency.value) return `${IDR_HOUR_PRICE_DISPLAY.value}`
|
||||||
return deviceFeeConfig.value.maxHourPrice || '5.00'
|
return deviceFeeConfig.value.maxHourPrice || '5.00'
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayDepositCap = computed(() => {
|
const displayDepositCap = computed(() => {
|
||||||
if (isIdrCurrency.value) return `${IDR_DEPOSIT_DISPLAY}`
|
if (isIdrCurrency.value) return `${IDR_DEPOSIT_DISPLAY.value}`
|
||||||
return deviceInfo.value.depositAmount || '99'
|
return deviceInfo.value.depositAmount || '99'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -647,7 +673,7 @@
|
|||||||
|
|
||||||
console.log(deviceId.value);
|
console.log(deviceId.value);
|
||||||
// 调用设备租借接口
|
// 调用设备租借接口
|
||||||
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value,payWay.value)
|
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value,payWay)
|
||||||
if (rentResult.code !== 200) {
|
if (rentResult.code !== 200) {
|
||||||
throw new Error(rentResult.msg || t('device.rentFailed'))
|
throw new Error(rentResult.msg || t('device.rentFailed'))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,6 +191,7 @@
|
|||||||
getCurrentAnnouncement,
|
getCurrentAnnouncement,
|
||||||
getCurrentAdvertisement
|
getCurrentAdvertisement
|
||||||
} from '../../config/api/system.js'
|
} from '../../config/api/system.js'
|
||||||
|
import { fetchAndCacheDanaPaymentConfig } from '../../utils/danaPaymentConfig.js'
|
||||||
import {
|
import {
|
||||||
getProductList
|
getProductList
|
||||||
} from '../../config/api/product.js'
|
} from '../../config/api/product.js'
|
||||||
@@ -332,7 +333,6 @@
|
|||||||
const noticeText = ref('')
|
const noticeText = ref('')
|
||||||
const bannerImages = ref([]) // 首页广告图片列表
|
const bannerImages = ref([]) // 首页广告图片列表
|
||||||
const bannerImageList = ref([]) // 完整的广告配置列表(包含链接信息)
|
const bannerImageList = ref([]) // 完整的广告配置列表(包含链接信息)
|
||||||
|
|
||||||
// 获取公告内容(支持多语言)
|
// 获取公告内容(支持多语言)
|
||||||
const getNoticeText = async () => {
|
const getNoticeText = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -619,7 +619,10 @@
|
|||||||
// 并行加载公告和广告(不依赖定位)
|
// 并行加载公告和广告(不依赖定位)
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getNoticeText(),
|
getNoticeText(),
|
||||||
getBannerImages()
|
getBannerImages(),
|
||||||
|
fetchAndCacheDanaPaymentConfig().catch((e) => {
|
||||||
|
console.warn('获取 DANA 配置失败,继续使用本地默认值:', e)
|
||||||
|
})
|
||||||
])
|
])
|
||||||
|
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
|
|||||||
+193
-53
@@ -44,7 +44,7 @@
|
|||||||
<view class="info-col">
|
<view class="info-col">
|
||||||
<view class="info-value-wrapper">
|
<view class="info-value-wrapper">
|
||||||
<text class="info-value-large">{{ getOrderFee() }}</text>
|
<text class="info-value-large">{{ getOrderFee() }}</text>
|
||||||
<text class="info-value-unit">{{ $t('unit.yuan') }}</text>
|
<text class="info-value-unit">{{ getCurrencyUnitText() }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-label">{{ $t('order.totalAmount') }}</view>
|
<view class="info-label">{{ $t('order.totalAmount') }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -116,8 +116,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="rent-paid" v-if="isOrderCompleted()">
|
<view class="rent-paid" v-if="isOrderCompleted()">
|
||||||
<text class="paid-label">{{ $t('order.paid') }}</text>
|
<text class="paid-label">{{ $t('order.paid') }}</text>
|
||||||
<text class="paid-value">{{ orderInfo.currentFee || orderInfo.payAmount || '10' }}</text>
|
<text class="paid-value">{{ formatAmountDisplay(orderInfo.currentFee || orderInfo.payAmount || '10') }}</text>
|
||||||
<text class="paid-unit">{{ $t('unit.yuan') }}</text>
|
<text class="paid-unit">{{ getCurrencyUnitText() }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="rent-service-tip">
|
<view class="rent-service-tip">
|
||||||
@@ -152,16 +152,16 @@
|
|||||||
<view v-if="showExpressAction" class="action-btn secondary" @click="expressRetrunOrder">
|
<view v-if="showExpressAction" class="action-btn secondary" @click="expressRetrunOrder">
|
||||||
{{ $t('express.title') }}
|
{{ $t('express.title') }}
|
||||||
</view>
|
</view>
|
||||||
<view v-if="showExpressAction" class="bottom-icon-btn" @click="quickReturn">
|
<!-- <view v-if="showExpressAction" class="bottom-icon-btn" @click="quickReturn">
|
||||||
<image src="/static/map.png" class="icon" mode="aspectFit" lazy-load="true"></image>
|
<image src="/static/map.png" class="icon" mode="aspectFit" lazy-load="true"></image>
|
||||||
<text>{{ $t('order.quickReturn') }}</text>
|
<text>{{ $t('order.quickReturn') }}</text>
|
||||||
</view>
|
</view> -->
|
||||||
</template>
|
</template>
|
||||||
<!-- 不支持快递归还时只显示快速归还(图标+小字) -->
|
<!-- 不支持快递归还时只显示快速归还(图标+小字) -->
|
||||||
<view v-else class="bottom-icon-btn" @click="quickReturn">
|
<!-- <view v-else class="bottom-icon-btn" @click="quickReturn">
|
||||||
<image src="/static/map.png" class="icon" mode="aspectFit" lazy-load="true"></image>
|
<image src="/static/map.png" class="icon" mode="aspectFit" lazy-load="true"></image>
|
||||||
<text>{{ $t('order.quickReturn') }}</text>
|
<text>{{ $t('order.quickReturn') }}</text>
|
||||||
</view>
|
</view> -->
|
||||||
<view
|
<view
|
||||||
v-if="showPauseBillingButton"
|
v-if="showPauseBillingButton"
|
||||||
class="action-btn secondary pause-billing-btn"
|
class="action-btn secondary pause-billing-btn"
|
||||||
@@ -264,6 +264,12 @@
|
|||||||
import {
|
import {
|
||||||
useI18n
|
useI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
import {
|
||||||
|
fetchAndCacheDanaPaymentConfig,
|
||||||
|
DANA_SINGLE_STORAGE_KEY,
|
||||||
|
DANA_TOTAL_STORAGE_KEY,
|
||||||
|
parseDanaStorageNumber
|
||||||
|
} from '@/utils/danaPaymentConfig.js'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
t
|
t
|
||||||
@@ -322,6 +328,43 @@
|
|||||||
const feeRuleText = ref('')
|
const feeRuleText = ref('')
|
||||||
const convertToOwnPopup = ref(null)
|
const convertToOwnPopup = ref(null)
|
||||||
const lastDeviceEjectTime = ref(0) // 上次点击"宝未弹出"的时间戳
|
const lastDeviceEjectTime = ref(0) // 上次点击"宝未弹出"的时间戳
|
||||||
|
const currencyCode = ref('CNY')
|
||||||
|
const danaPaymentTotal = ref(0)
|
||||||
|
const danaPaymentSingle = ref(6000)
|
||||||
|
|
||||||
|
const loadDanaPaymentCache = () => {
|
||||||
|
try {
|
||||||
|
const cachedTotal = parseDanaStorageNumber(uni.getStorageSync(DANA_TOTAL_STORAGE_KEY))
|
||||||
|
const cachedSingle = parseDanaStorageNumber(uni.getStorageSync(DANA_SINGLE_STORAGE_KEY))
|
||||||
|
if (cachedTotal !== null) {
|
||||||
|
danaPaymentTotal.value = cachedTotal
|
||||||
|
}
|
||||||
|
if (cachedSingle !== null && cachedSingle > 0) {
|
||||||
|
danaPaymentSingle.value = cachedSingle
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('读取 DANA 金额缓存失败,使用默认值:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveCurrencyCode = (orderData = {}) => {
|
||||||
|
const explicitCurrency = String(
|
||||||
|
orderData.currency || orderData.positionCurrency || orderData.position?.currency || ''
|
||||||
|
).toUpperCase()
|
||||||
|
if (explicitCurrency) return explicitCurrency
|
||||||
|
|
||||||
|
const payWay = String(orderData.payWay || '').toLowerCase()
|
||||||
|
const phone = String(orderData.phone || '')
|
||||||
|
const depositAmount = Number(orderData.depositAmount || 0)
|
||||||
|
const unitPrice = Number(orderData.unitPrice || 0)
|
||||||
|
|
||||||
|
// 海外 H5 订单接口偶发不返回 currency,按业务特征兜底识别 IDR
|
||||||
|
if (payWay.includes('antom') || phone.startsWith('+62') || depositAmount >= 1000 || unitPrice >= 1000) {
|
||||||
|
return 'IDR'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'CNY'
|
||||||
|
}
|
||||||
/** 是否允许点击暂停:null 拉取中,true 可暂停,false 不可暂停(按钮仍展示为禁用) */
|
/** 是否允许点击暂停:null 拉取中,true 可暂停,false 不可暂停(按钮仍展示为禁用) */
|
||||||
const pauseBillingEligible = ref(null)
|
const pauseBillingEligible = ref(null)
|
||||||
const pauseBillingLoading = ref(false)
|
const pauseBillingLoading = ref(false)
|
||||||
@@ -502,7 +545,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const orderTypeText = orderTypeMap[orderInfo.value.orderType] || orderInfo.value.orderType
|
const orderTypeText = orderTypeMap[orderInfo.value.orderType] || orderInfo.value.orderType
|
||||||
return `${orderInfo.value.unitPrice}${t('unit.yuan')}/${orderTypeText}`
|
if (currencyCode.value === 'IDR') {
|
||||||
|
return `Rp${formatAmountDisplay(danaPaymentSingle.value)}/${orderTypeText}`
|
||||||
|
}
|
||||||
|
return `${formatAmountDisplay(orderInfo.value.unitPrice)}${getCurrencyUnitText()}/${orderTypeText}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCurrencyUnitText = () => {
|
||||||
|
if (currencyCode.value === 'IDR') return 'Rp'
|
||||||
|
return t('unit.yuan')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化倒计时(显示为 HH:MM:SS 格式)
|
// 格式化倒计时(显示为 HH:MM:SS 格式)
|
||||||
@@ -619,23 +670,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatAmountDisplay = (amount) => {
|
||||||
|
if (amount === null || amount === undefined || amount === '') return '0'
|
||||||
|
const normalized = String(amount).replace(/[^\d.-]/g, '')
|
||||||
|
const num = Number(normalized)
|
||||||
|
if (Number.isFinite(num)) {
|
||||||
|
return String(Math.trunc(num))
|
||||||
|
}
|
||||||
|
return String(amount).split('.')[0]
|
||||||
|
}
|
||||||
|
|
||||||
// 获取使用时长标签文本
|
// 获取使用时长标签文本
|
||||||
const getUsedTimeLabel = () => {
|
const getUsedTimeLabel = () => {
|
||||||
// 使用中状态显示"已使用",已完成状态显示"使用时长"
|
// 使用中状态显示"已使用",已完成状态显示"使用时长"
|
||||||
return orderInfo.value.orderStatus === 'in_used' ? t('order.used') : t('order.duration')
|
return orderInfo.value.orderStatus === 'in_used' ? t('order.used') : t('order.duration')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取订单费用(不含单位)
|
// 总金额展示:使用本地缓存 danaPaymentTotal(结构如 { type: 'number', data: 99000 }),由 loadDanaPaymentCache 同步到 danaPaymentTotal
|
||||||
const getOrderFee = () => {
|
const getOrderFee = () => {
|
||||||
let fee;
|
return formatAmountDisplay(String(danaPaymentTotal.value))
|
||||||
if(orderInfo.value.originalFee){
|
|
||||||
fee = orderInfo.value.originalFee || orderInfo.value.originalFee || '0'
|
|
||||||
}else{
|
|
||||||
fee = orderInfo.value.currentFee;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除可能的"元"字符
|
|
||||||
return String(fee).replace(/[元¥]/g, '')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析开始时间
|
// 解析开始时间
|
||||||
@@ -969,6 +1022,7 @@
|
|||||||
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
|
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
|
||||||
orderInfo.value.originalFee = orderData.originalFee||''
|
orderInfo.value.originalFee = orderData.originalFee||''
|
||||||
orderInfo.value.returnMapImage = orderData.returnMapImage||''
|
orderInfo.value.returnMapImage = orderData.returnMapImage||''
|
||||||
|
currencyCode.value = resolveCurrencyCode(orderData)
|
||||||
|
|
||||||
// 保存快递归还开始时间(小时为单位)
|
// 保存快递归还开始时间(小时为单位)
|
||||||
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
|
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
|
||||||
@@ -1392,6 +1446,10 @@
|
|||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
|
loadDanaPaymentCache()
|
||||||
|
void fetchAndCacheDanaPaymentConfig()
|
||||||
|
.then(() => loadDanaPaymentCache())
|
||||||
|
.catch((e) => console.warn('DANA 配置刷新失败:', e))
|
||||||
console.log('订单详情页加载,参数:', JSON.stringify(options))
|
console.log('订单详情页加载,参数:', JSON.stringify(options))
|
||||||
|
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
@@ -1434,6 +1492,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
loadDanaPaymentCache()
|
||||||
isPageActive.value = true
|
isPageActive.value = true
|
||||||
if (orderInfo.value.orderStatus === 'in_used' && orderInfo.value.isSupportExpressReturn !== 'no') {
|
if (orderInfo.value.orderStatus === 'in_used' && orderInfo.value.isSupportExpressReturn !== 'no') {
|
||||||
startExpressCountdown()
|
startExpressCountdown()
|
||||||
@@ -1473,7 +1532,8 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
padding-bottom: 180rpx;
|
/* 底部操作栏为多行时,预留更大的安全滚动空间,避免遮挡详情内容 */
|
||||||
|
padding-bottom: calc(320rpx + env(safe-area-inset-bottom));
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
// 顶部标题
|
// 顶部标题
|
||||||
@@ -1524,6 +1584,10 @@
|
|||||||
.header-desc {
|
.header-desc {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-right {
|
.header-right {
|
||||||
@@ -1531,6 +1595,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
min-width: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-no-eject-btn {
|
.device-no-eject-btn {
|
||||||
@@ -1543,7 +1609,9 @@
|
|||||||
// background: #E8F5E9;
|
// background: #E8F5E9;
|
||||||
// border-radius: 12rpx;
|
// border-radius: 12rpx;
|
||||||
// border: 2rpx solid #07c160;
|
// border: 2rpx solid #07c160;
|
||||||
min-width: 120rpx;
|
min-width: 0;
|
||||||
|
max-width: 180rpx;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.device-no-eject-icon {
|
.device-no-eject-icon {
|
||||||
width: 68rpx;
|
width: 68rpx;
|
||||||
@@ -1555,6 +1623,13 @@
|
|||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -1591,12 +1666,15 @@
|
|||||||
|
|
||||||
.info-left {
|
.info-left {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
// justify-content: space-between;
|
// justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.info-col {
|
.info-col {
|
||||||
width: 200rpx;
|
width: auto;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
// flex: 1;
|
// flex: 1;
|
||||||
// text-align: center;
|
// text-align: center;
|
||||||
|
|
||||||
@@ -1631,7 +1709,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-shrink: 0;
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 48%;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
|
||||||
.return-reminder-btn {
|
.return-reminder-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1641,11 +1722,25 @@
|
|||||||
background: #3EAB64;
|
background: #3EAB64;
|
||||||
border-radius: 50rpx;
|
border-radius: 50rpx;
|
||||||
border: 2rpx solid #3EAB64;
|
border: 2rpx solid #3EAB64;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
flex-shrink: 1;
|
||||||
|
gap: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.return-reminder-text {
|
.return-reminder-text {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -1662,6 +1757,9 @@
|
|||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #4CAF50;
|
color: #4CAF50;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fee-rule-image {
|
.fee-rule-image {
|
||||||
@@ -1748,6 +1846,9 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
padding-top: 20rpx;
|
padding-top: 20rpx;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
|
||||||
.rent-service-link {
|
.rent-service-link {
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
@@ -1757,9 +1858,10 @@
|
|||||||
.rent-item {
|
.rent-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
padding: 16rpx 0;
|
padding: 16rpx 0;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
@@ -1768,13 +1870,22 @@
|
|||||||
.rent-label {
|
.rent-label {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 42%;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rent-value {
|
.rent-value {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
max-width: 400rpx;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
|
||||||
&.promotion-value {
|
&.promotion-value {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1826,27 +1937,36 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
padding: 20rpx 30rpx;
|
padding: 18rpx 24rpx;
|
||||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
padding-bottom: calc(18rpx + env(safe-area-inset-bottom));
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
border-top: 1rpx solid #f0f0f0;
|
||||||
|
box-shadow: 0 -6rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
align-items: stretch;
|
||||||
align-items: center;
|
gap: 12rpx;
|
||||||
gap: 20rpx;
|
|
||||||
|
|
||||||
.bottom-bar-in-use {
|
.bottom-bar-in-use {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
justify-content: space-between;
|
gap: 12rpx;
|
||||||
|
|
||||||
// gap: 20rpx;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
> .countdown-btn,
|
||||||
|
> .action-btn.secondary,
|
||||||
|
> .action-btn.pause-billing-btn {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .bottom-icon-btn {
|
||||||
|
flex: 0 0 122rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-icon-btn {
|
.bottom-icon-btn {
|
||||||
@@ -1854,17 +1974,27 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-width: 100rpx;
|
min-width: 108rpx;
|
||||||
|
min-height: 88rpx;
|
||||||
|
padding: 8rpx 8rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #f7f8fa;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 48rpx;
|
width: 42rpx;
|
||||||
height: 48rpx;
|
height: 42rpx;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
text {
|
text {
|
||||||
font-size: 24rpx;
|
font-size: 22rpx;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
line-height: 1.25;
|
||||||
|
text-align: center;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -1875,26 +2005,32 @@
|
|||||||
.countdown-btn {
|
.countdown-btn {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 200rpx;
|
min-width: 200rpx;
|
||||||
height: 88rpx;
|
min-height: 88rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 28rpx;
|
font-size: 26rpx;
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
background: #E8F5E9;
|
background: #E8F5E9;
|
||||||
border-radius: 44rpx;
|
border-radius: 20rpx;
|
||||||
border: 2rpx solid #07c160;
|
border: 2rpx solid #07c160;
|
||||||
|
padding: 0 18rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
height: 88rpx;
|
min-height: 88rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 30rpx;
|
font-size: 27rpx;
|
||||||
border-radius: 44rpx;
|
border-radius: 20rpx;
|
||||||
padding: 0 40rpx;
|
padding: 12rpx 20rpx;
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.35;
|
||||||
|
|
||||||
&.full-width {
|
&.full-width {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -1920,10 +2056,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.is-disabled {
|
&.is-disabled {
|
||||||
opacity: 0.45;
|
opacity: 1;
|
||||||
color: #999;
|
color: #b8b8b8;
|
||||||
background: #ebebeb;
|
background: #f3f3f3;
|
||||||
border-color: #ddd;
|
border-color: #e6e6e6;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
@@ -1945,8 +2081,12 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
border: 2rpx solid #07c160;
|
border: 2rpx solid #07c160;
|
||||||
flex: 0 1 auto;
|
flex: 1 1 100%;
|
||||||
max-width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|||||||
@@ -80,6 +80,9 @@
|
|||||||
<text class="amount-large">{{ totalAmount }}</text>
|
<text class="amount-large">{{ totalAmount }}</text>
|
||||||
<text class="pay-text">{{ $t('payment.payNow') }}</text>
|
<text class="pay-text">{{ $t('payment.payNow') }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="cancel-btn" @click="handleCancelOrder">
|
||||||
|
{{ $t('order.cancelOrder') }}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -96,6 +99,7 @@
|
|||||||
} from '@dcloudio/uni-app'
|
} from '@dcloudio/uni-app'
|
||||||
import {
|
import {
|
||||||
queryById,
|
queryById,
|
||||||
|
cancelOrder,
|
||||||
createWxPayment,
|
createWxPayment,
|
||||||
getWxPaymentStatus,
|
getWxPaymentStatus,
|
||||||
createAliPayment,
|
createAliPayment,
|
||||||
@@ -113,6 +117,12 @@
|
|||||||
import {
|
import {
|
||||||
useI18n
|
useI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
import {
|
||||||
|
fetchAndCacheDanaPaymentConfig,
|
||||||
|
DANA_TOTAL_STORAGE_KEY,
|
||||||
|
DANA_SINGLE_STORAGE_KEY,
|
||||||
|
parseDanaStorageNumber
|
||||||
|
} from '@/utils/danaPaymentConfig.js'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
t
|
t
|
||||||
@@ -125,7 +135,23 @@
|
|||||||
const passedTotalAmount = ref(null)
|
const passedTotalAmount = ref(null)
|
||||||
const passedDepositAmount = ref(null)
|
const passedDepositAmount = ref(null)
|
||||||
const currencyCode = ref('USD')
|
const currencyCode = ref('USD')
|
||||||
const IDR_DEPOSIT_DISPLAY = 99000
|
const IDR_DEPOSIT_DISPLAY = ref(99000)
|
||||||
|
const IDR_SINGLE_DISPLAY = ref(6000)
|
||||||
|
|
||||||
|
const loadDanaTotalCache = () => {
|
||||||
|
try {
|
||||||
|
const cachedTotal = parseDanaStorageNumber(uni.getStorageSync(DANA_TOTAL_STORAGE_KEY))
|
||||||
|
const cachedSingle = parseDanaStorageNumber(uni.getStorageSync(DANA_SINGLE_STORAGE_KEY))
|
||||||
|
if (cachedTotal !== null && cachedTotal > 0) {
|
||||||
|
IDR_DEPOSIT_DISPLAY.value = cachedTotal
|
||||||
|
}
|
||||||
|
if (cachedSingle !== null && cachedSingle > 0) {
|
||||||
|
IDR_SINGLE_DISPLAY.value = cachedSingle
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('读取 DANA 预支付缓存失败,使用默认值:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
||||||
const paymentMethods = ref([])
|
const paymentMethods = ref([])
|
||||||
@@ -152,7 +178,7 @@
|
|||||||
|
|
||||||
const totalAmount = computed(() => {
|
const totalAmount = computed(() => {
|
||||||
if (currencyCode.value === 'IDR') {
|
if (currencyCode.value === 'IDR') {
|
||||||
return `${IDR_DEPOSIT_DISPLAY}`
|
return `${IDR_DEPOSIT_DISPLAY.value}`
|
||||||
}
|
}
|
||||||
if (passedTotalAmount.value !== null) {
|
if (passedTotalAmount.value !== null) {
|
||||||
return parseFloat(passedTotalAmount.value).toFixed(2);
|
return parseFloat(passedTotalAmount.value).toFixed(2);
|
||||||
@@ -258,7 +284,7 @@
|
|||||||
orderInfo.value.deposit = deviceInfo.value.depositAmount;
|
orderInfo.value.deposit = deviceInfo.value.depositAmount;
|
||||||
}
|
}
|
||||||
if (currencyCode.value === 'IDR') {
|
if (currencyCode.value === 'IDR') {
|
||||||
orderInfo.value.deposit = `${IDR_DEPOSIT_DISPLAY}`
|
orderInfo.value.deposit = `${IDR_DEPOSIT_DISPLAY.value}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -509,6 +535,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCancelOrder = () => {
|
||||||
|
if (!orderId.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('order.orderNotExist'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: t('order.confirmCancel'),
|
||||||
|
content: t('order.confirmCancelContent'),
|
||||||
|
success: async (res) => {
|
||||||
|
if (!res.confirm) return;
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: t('common.processing')
|
||||||
|
});
|
||||||
|
const result = await cancelOrder({
|
||||||
|
orderId: orderId.value
|
||||||
|
});
|
||||||
|
if (result && result.code === 200) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: t('order.cancelSuccess'),
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
});
|
||||||
|
}, 800);
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.msg || t('order.cancelFailed'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || t('order.cancelFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 轮询定时器
|
// 轮询定时器
|
||||||
let pollingTimer = null;
|
let pollingTimer = null;
|
||||||
|
|
||||||
@@ -641,6 +713,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
|
loadDanaTotalCache()
|
||||||
|
void fetchAndCacheDanaPaymentConfig()
|
||||||
|
.then(() => loadDanaTotalCache())
|
||||||
|
.catch((e) => console.warn('DANA 配置刷新失败:', e))
|
||||||
// 设置导航栏标题为待支付
|
// 设置导航栏标题为待支付
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('payment.waitingForPayment')
|
title: t('payment.waitingForPayment')
|
||||||
@@ -909,6 +985,25 @@
|
|||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
width: 100%;
|
||||||
|
//height: 84rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
//border-radius: 42rpx;
|
||||||
|
//border: 2rpx solid #d9d9d9;
|
||||||
|
//background: #fff;
|
||||||
|
color: #666;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { getSystemConfig } from '@/config/api/system.js'
|
||||||
|
|
||||||
|
/** 系统配置项 key(与后台一致) */
|
||||||
|
export const DANA_TOTAL_CONFIG_KEY = 'overseas_payment_dana_total'
|
||||||
|
export const DANA_SINGLE_CONFIG_KEY = 'overseas_payment_dana_single'
|
||||||
|
|
||||||
|
/** 本地缓存 key */
|
||||||
|
export const DANA_TOTAL_STORAGE_KEY = 'danaPaymentTotal'
|
||||||
|
export const DANA_SINGLE_STORAGE_KEY = 'danaPaymentSingle'
|
||||||
|
|
||||||
|
/** 写入缓存的金额结构(与业务侧约定一致) */
|
||||||
|
const wrapStorageNumber = (n) => ({ type: 'number', data: n })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从本地缓存读取 DANA 金额;兼容 { type, data }、JSON 字符串、纯数字。
|
||||||
|
*/
|
||||||
|
export function parseDanaStorageNumber(raw) {
|
||||||
|
if (raw === null || raw === undefined || raw === '') return null
|
||||||
|
let value = raw
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
const trimmed = raw.trim()
|
||||||
|
if (trimmed.startsWith('{')) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(trimmed)
|
||||||
|
if (parsed && typeof parsed === 'object' && 'data' in parsed) {
|
||||||
|
value = parsed.data
|
||||||
|
} else {
|
||||||
|
value = parsed
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
value = raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof raw === 'object' && raw !== null && 'data' in raw) {
|
||||||
|
value = raw.data
|
||||||
|
}
|
||||||
|
const num = Number(value)
|
||||||
|
return Number.isFinite(num) ? num : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseConfigNumber = (rawValue) => {
|
||||||
|
if (rawValue === null || rawValue === undefined) return null
|
||||||
|
const cleaned = String(rawValue).replace(/[^0-9.]/g, '')
|
||||||
|
if (!cleaned) return null
|
||||||
|
const num = Number(cleaned)
|
||||||
|
return Number.isFinite(num) ? num : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractConfigValue = (res, configKey) => {
|
||||||
|
if (!res || res.code !== 200) return null
|
||||||
|
const row = (res.rows || []).find((item) => item && item.configKey === configKey)
|
||||||
|
if (row && row.configValue !== undefined) return row.configValue
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取 DANA 预扣款 / 单次扣款配置并写入本地缓存。
|
||||||
|
* 用于首页及「直达」设备详情、订单详情、支付页等场景,避免仅依赖首页拉取。
|
||||||
|
*/
|
||||||
|
export async function fetchAndCacheDanaPaymentConfig() {
|
||||||
|
const [totalRes, singleRes] = await Promise.all([
|
||||||
|
getSystemConfig({ configKey: DANA_TOTAL_CONFIG_KEY }),
|
||||||
|
getSystemConfig({ configKey: DANA_SINGLE_CONFIG_KEY })
|
||||||
|
])
|
||||||
|
|
||||||
|
const totalRaw = extractConfigValue(totalRes, DANA_TOTAL_CONFIG_KEY)
|
||||||
|
const singleRaw = extractConfigValue(singleRes, DANA_SINGLE_CONFIG_KEY)
|
||||||
|
const totalValue = parseConfigNumber(totalRaw)
|
||||||
|
const singleValue = parseConfigNumber(singleRaw)
|
||||||
|
|
||||||
|
if (totalValue !== null) {
|
||||||
|
uni.setStorageSync(DANA_TOTAL_STORAGE_KEY, wrapStorageNumber(totalValue))
|
||||||
|
}
|
||||||
|
if (singleValue !== null) {
|
||||||
|
uni.setStorageSync(DANA_SINGLE_STORAGE_KEY, wrapStorageNumber(singleValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
return { totalValue, singleValue }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user