diff --git a/App.vue b/App.vue
index d89da34..b00e382 100644
--- a/App.vue
+++ b/App.vue
@@ -54,9 +54,6 @@
// 检查并更新语言(uni.reLaunch 会触发 onShow)
try {
const savedLang = uni.getStorageSync('language')
- if(savedLang){
- uni.removeStorageSync('language');
- }
console.log('App onShow - 缓存中的语言:', savedLang)
// 获取当前 i18n 实例并检查语言
diff --git a/components/home/HomeMainH5.vue b/components/home/HomeMainH5.vue
index f749b4b..4b193dc 100644
--- a/components/home/HomeMainH5.vue
+++ b/components/home/HomeMainH5.vue
@@ -55,10 +55,6 @@
-
-
- {{ buyDeviceText }}
-
{{ scanText }}
@@ -243,34 +239,36 @@
right: 20rpx;
bottom: 30rpx;
z-index: 1200;
- padding: 14rpx;
+ padding: 12rpx;
background: rgba(255, 255, 255, 0.96);
- border-radius: 56rpx;
+ border-radius: 28rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
display: flex;
- align-items: center;
+ align-items: stretch;
gap: 12rpx;
}
.action-btn {
- height: 86rpx;
- border-radius: 43rpx;
+ min-height: 84rpx;
+ border-radius: 20rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
+ box-sizing: border-box;
}
.action-btn.primary {
flex: 1;
background: #3EAB64;
- max-width: 360rpx;
+ min-width: 0;
}
.action-btn.secondary {
- width: 150rpx;
+ width: 180rpx;
background: #f4f6f8;
+ border: 2rpx solid #e7eaee;
}
.action-icon {
@@ -285,6 +283,7 @@
.action-label {
font-size: 24rpx;
color: #333;
+ text-align: center;
}
.primary-label {
diff --git a/config/api/system.js b/config/api/system.js
index 4ade900..c7bd7a7 100644
--- a/config/api/system.js
+++ b/config/api/system.js
@@ -1,11 +1,12 @@
import request from '../http'
-// 获取系统配置(预留接口)
-// 期望后端返回形如:{ code: 200, data: { expressReturnCountdownSeconds: number } }
-export const getSystemConfig = () => {
+// 获取系统配置
+// 可传参示例:{ configKey: 'overseas_payment_dana_total' }
+export const getSystemConfig = (data = {}) => {
return request({
- url: '/app/system/config',
+ url: '/system/config/list',
method: 'get',
+ data,
hideLoading: true
})
}
diff --git a/config/url.js b/config/url.js
index 2dba386..106577d 100644
--- a/config/url.js
+++ b/config/url.js
@@ -1,8 +1,8 @@
// export const URL = "https://my.gxfs123.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 = "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 appid = "wx2165f0be356ae7a9" //微信小程序appid
diff --git a/main.js b/main.js
index be2748e..cdbbb31 100644
--- a/main.js
+++ b/main.js
@@ -7,10 +7,49 @@ import enUS from './locale/en-US.js'
import idID from './locale/id-ID.js'
import uView from '@climblee/uv-ui'
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 控制
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 环境
const isH5Platform = () => {
try {
@@ -25,42 +64,69 @@ const isH5Platform = () => {
// 获取系统语言
const getSystemLanguage = () => {
- // H5 环境默认使用印尼语
- if (isH5Platform()) {
- return 'id-ID'
- }
-
- // 非 H5 环境根据系统语言判断
- let language = 'en-US'
+ let language = 'zh-CN'
try {
- const systemInfo = uni.getSystemInfoSync()
- if (systemInfo && systemInfo.language) {
- language = systemInfo.language === 'zh' || systemInfo.language.indexOf('zh') === 0
- ? 'zh-CN'
- : 'en-US'
+ const systemInfo = uni.getSystemInfoSync() || {}
+ const systemLanguage = normalizeLanguage(systemInfo.language)
+ if (systemLanguage) {
+ language = systemLanguage
+ } else if (isH5Platform() && typeof navigator !== 'undefined') {
+ const browserLanguage = normalizeLanguage(navigator.language || '')
+ if (browserLanguage) language = browserLanguage
}
} catch (e) {
console.error('获取系统语言失败:', e)
- // 默认使用中文
language = 'zh-CN'
}
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 = () => {
try {
- const savedLang = uni.getStorageSync('language')
+ const savedLang = normalizeLanguage(uni.getStorageSync(LANGUAGE_STORAGE_KEY))
if (savedLang) {
return savedLang
}
const systemLang = getSystemLanguage()
- uni.setStorageSync('language', systemLang)
+ uni.setStorageSync(LANGUAGE_STORAGE_KEY, systemLang)
return systemLang
} catch (e) {
console.error('语言设置出错:', e)
- // 出错时根据平台返回默认语言
- return isH5Platform() ? 'id-ID' : 'zh-CN'
+ return 'zh-CN'
}
}
@@ -104,6 +170,27 @@ function getI18nInstance() {
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() {
const app = createSSRApp(App)
@@ -116,6 +203,9 @@ export function createApp() {
// 使用 i18n
app.use(i18n)
+
+ // H5 端通过系统配置同步语言(异步,不阻塞应用启动)
+ syncLanguageFromRemoteConfig(i18n)
// 手动注入 $i18n 到全局属性(确保组件可以访问)
app.config.globalProperties.$i18n = i18n.global
diff --git a/pages/device/detail.vue b/pages/device/detail.vue
index c5c807e..4d6927d 100644
--- a/pages/device/detail.vue
+++ b/pages/device/detail.vue
@@ -155,6 +155,12 @@
import {
useI18n
} 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'
const {
@@ -179,11 +185,31 @@
const isWechatMiniProgram = ref(false)
const isAlipayMiniProgram = ref(false)
const isH5 = ref(false)
- const IDR_DEPOSIT_DISPLAY = 99000
- const IDR_HOUR_PRICE_DISPLAY = 6000
+ const IDR_DEPOSIT_DISPLAY = ref(99000)
+ 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(async (options) => {
+ loadDanaPricingCache()
+ void fetchAndCacheDanaPaymentConfig()
+ .then(() => loadDanaPricingCache())
+ .catch((e) => console.warn('DANA 配置刷新失败:', e))
// 普通链接二维码进入时,参数通常在 options.q(且为编码后的完整 URL)
if (!options.deviceNo && options.q) {
@@ -603,12 +629,12 @@
const displayCurrencySymbol = computed(() => (isIdrCurrency.value ? 'Rp ' : '¥'))
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'
})
const displayDepositCap = computed(() => {
- if (isIdrCurrency.value) return `${IDR_DEPOSIT_DISPLAY}`
+ if (isIdrCurrency.value) return `${IDR_DEPOSIT_DISPLAY.value}`
return deviceInfo.value.depositAmount || '99'
})
@@ -647,7 +673,7 @@
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) {
throw new Error(rentResult.msg || t('device.rentFailed'))
}
diff --git a/pages/index/index.vue b/pages/index/index.vue
index fc184f4..df0edca 100644
--- a/pages/index/index.vue
+++ b/pages/index/index.vue
@@ -191,6 +191,7 @@
getCurrentAnnouncement,
getCurrentAdvertisement
} from '../../config/api/system.js'
+ import { fetchAndCacheDanaPaymentConfig } from '../../utils/danaPaymentConfig.js'
import {
getProductList
} from '../../config/api/product.js'
@@ -332,7 +333,6 @@
const noticeText = ref('')
const bannerImages = ref([]) // 首页广告图片列表
const bannerImageList = ref([]) // 完整的广告配置列表(包含链接信息)
-
// 获取公告内容(支持多语言)
const getNoticeText = async () => {
try {
@@ -577,7 +577,7 @@
navBarHeight.value = 44
}
}
-
+
// 生命周期
onMounted(() => {
initNavBarHeight()
@@ -619,7 +619,10 @@
// 并行加载公告和广告(不依赖定位)
await Promise.all([
getNoticeText(),
- getBannerImages()
+ getBannerImages(),
+ fetchAndCacheDanaPaymentConfig().catch((e) => {
+ console.warn('获取 DANA 配置失败,继续使用本地默认值:', e)
+ })
])
// #ifdef H5
diff --git a/pages/order/detail.vue b/pages/order/detail.vue
index a34012f..6bca9a9 100644
--- a/pages/order/detail.vue
+++ b/pages/order/detail.vue
@@ -44,7 +44,7 @@
{{ getOrderFee() }}
- {{ $t('unit.yuan') }}
+ {{ getCurrencyUnitText() }}
{{ $t('order.totalAmount') }}
@@ -116,8 +116,8 @@
{{ $t('order.paid') }}
- {{ orderInfo.currentFee || orderInfo.payAmount || '10' }}
- {{ $t('unit.yuan') }}
+ {{ formatAmountDisplay(orderInfo.currentFee || orderInfo.payAmount || '10') }}
+ {{ getCurrencyUnitText() }}
@@ -152,16 +152,16 @@
{{ $t('express.title') }}
-
+
-
+
{
+ 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 不可暂停(按钮仍展示为禁用) */
const pauseBillingEligible = ref(null)
const pauseBillingLoading = ref(false)
@@ -502,7 +545,15 @@
}
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 格式)
@@ -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 = () => {
// 使用中状态显示"已使用",已完成状态显示"使用时长"
return orderInfo.value.orderStatus === 'in_used' ? t('order.used') : t('order.duration')
}
- // 获取订单费用(不含单位)
+ // 总金额展示:使用本地缓存 danaPaymentTotal(结构如 { type: 'number', data: 99000 }),由 loadDanaPaymentCache 同步到 danaPaymentTotal
const getOrderFee = () => {
- let fee;
- if(orderInfo.value.originalFee){
- fee = orderInfo.value.originalFee || orderInfo.value.originalFee || '0'
- }else{
- fee = orderInfo.value.currentFee;
- }
-
- // 移除可能的"元"字符
- return String(fee).replace(/[元¥]/g, '')
+ return formatAmountDisplay(String(danaPaymentTotal.value))
}
// 解析开始时间
@@ -969,6 +1022,7 @@
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
orderInfo.value.originalFee = orderData.originalFee||''
orderInfo.value.returnMapImage = orderData.returnMapImage||''
+ currencyCode.value = resolveCurrencyCode(orderData)
// 保存快递归还开始时间(小时为单位)
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
@@ -1392,6 +1446,10 @@
// 生命周期钩子
onLoad((options) => {
+ loadDanaPaymentCache()
+ void fetchAndCacheDanaPaymentConfig()
+ .then(() => loadDanaPaymentCache())
+ .catch((e) => console.warn('DANA 配置刷新失败:', e))
console.log('订单详情页加载,参数:', JSON.stringify(options))
// 设置页面标题
@@ -1434,6 +1492,7 @@
})
onShow(() => {
+ loadDanaPaymentCache()
isPageActive.value = true
if (orderInfo.value.orderStatus === 'in_used' && orderInfo.value.isSupportExpressReturn !== 'no') {
startExpressCountdown()
@@ -1473,7 +1532,8 @@
min-height: 100vh;
background: #f7f8fa;
padding: 30rpx;
- padding-bottom: 180rpx;
+ /* 底部操作栏为多行时,预留更大的安全滚动空间,避免遮挡详情内容 */
+ padding-bottom: calc(320rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
// 顶部标题
@@ -1524,6 +1584,10 @@
.header-desc {
font-size: 28rpx;
color: #999;
+ white-space: normal;
+ word-break: break-word;
+ overflow-wrap: anywhere;
+ line-height: 1.5;
}
.header-right {
@@ -1531,6 +1595,8 @@
align-items: center;
height: 100%;
text-align: center;
+ min-width: 0;
+ flex-shrink: 1;
}
.device-no-eject-btn {
@@ -1543,7 +1609,9 @@
// background: #E8F5E9;
// border-radius: 12rpx;
// border: 2rpx solid #07c160;
- min-width: 120rpx;
+ min-width: 0;
+ max-width: 180rpx;
+ width: 100%;
.device-no-eject-icon {
width: 68rpx;
@@ -1555,6 +1623,13 @@
font-size: 26rpx;
color: #07c160;
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 {
@@ -1591,12 +1666,15 @@
.info-left {
flex: 1;
+ min-width: 0;
display: flex;
// justify-content: space-between;
align-items: center;
.info-col {
- width: 200rpx;
+ width: auto;
+ flex: 1;
+ min-width: 0;
// flex: 1;
// text-align: center;
@@ -1631,7 +1709,10 @@
display: flex;
align-items: center;
justify-content: flex-end;
- flex-shrink: 0;
+ flex-shrink: 1;
+ min-width: 0;
+ max-width: 48%;
+ margin-left: 12rpx;
.return-reminder-btn {
display: flex;
@@ -1641,11 +1722,25 @@
background: #3EAB64;
border-radius: 50rpx;
border: 2rpx solid #3EAB64;
+ width: 100%;
+ max-width: 100%;
+ flex-shrink: 1;
+ gap: 8rpx;
+ overflow: hidden;
+ box-sizing: border-box;
.return-reminder-text {
font-size: 24rpx;
color: #fff;
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 {
@@ -1662,6 +1757,9 @@
font-size: 24rpx;
color: #4CAF50;
line-height: 1.6;
+ white-space: normal;
+ word-break: break-word;
+ overflow-wrap: anywhere;
}
.fee-rule-image {
@@ -1748,6 +1846,9 @@
text-align: center;
line-height: 1.6;
padding-top: 20rpx;
+ white-space: normal;
+ word-break: break-word;
+ overflow-wrap: anywhere;
.rent-service-link {
color: #07c160;
@@ -1757,9 +1858,10 @@
.rent-item {
display: flex;
justify-content: space-between;
- align-items: center;
+ align-items: flex-start;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
+ gap: 16rpx;
&:last-of-type {
border-bottom: none;
@@ -1768,13 +1870,22 @@
.rent-label {
font-size: 28rpx;
color: #999;
+ flex-shrink: 0;
+ max-width: 42%;
+ line-height: 1.5;
}
.rent-value {
font-size: 28rpx;
color: #333;
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 {
display: flex;
@@ -1826,27 +1937,36 @@
left: 0;
right: 0;
bottom: 0;
- padding: 20rpx 30rpx;
- padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+ padding: 18rpx 24rpx;
+ padding-bottom: calc(18rpx + env(safe-area-inset-bottom));
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;
display: flex;
flex-wrap: wrap;
- justify-content: space-between;
- align-items: center;
- gap: 20rpx;
+ align-items: stretch;
+ gap: 12rpx;
.bottom-bar-in-use {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
- align-items: center;
- justify-content: space-between;
-
- // gap: 20rpx;
+ align-items: stretch;
+ gap: 12rpx;
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 {
@@ -1854,17 +1974,27 @@
flex-direction: column;
align-items: 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 {
- width: 48rpx;
- height: 48rpx;
- margin-bottom: 8rpx;
+ width: 42rpx;
+ height: 42rpx;
+ margin-bottom: 6rpx;
}
text {
- font-size: 24rpx;
+ font-size: 22rpx;
color: #666;
+ line-height: 1.25;
+ text-align: center;
+ white-space: normal;
+ word-break: break-word;
+ overflow-wrap: anywhere;
}
&:active {
@@ -1875,26 +2005,32 @@
.countdown-btn {
flex: 1 1 auto;
min-width: 200rpx;
- height: 88rpx;
+ min-height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
- font-size: 28rpx;
+ font-size: 26rpx;
color: #07c160;
background: #E8F5E9;
- border-radius: 44rpx;
+ border-radius: 20rpx;
border: 2rpx solid #07c160;
+ padding: 0 18rpx;
+ box-sizing: border-box;
+ text-align: center;
+ line-height: 1.3;
}
.action-btn {
- height: 88rpx;
+ min-height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
- font-size: 30rpx;
- border-radius: 44rpx;
- padding: 0 40rpx;
- white-space: nowrap;
+ font-size: 27rpx;
+ border-radius: 20rpx;
+ padding: 12rpx 20rpx;
+ white-space: normal;
+ text-align: center;
+ line-height: 1.35;
&.full-width {
flex: 1;
@@ -1920,10 +2056,10 @@
}
&.is-disabled {
- opacity: 0.45;
- color: #999;
- background: #ebebeb;
- border-color: #ddd;
+ opacity: 1;
+ color: #b8b8b8;
+ background: #f3f3f3;
+ border-color: #e6e6e6;
&:active {
opacity: 0.45;
@@ -1945,8 +2081,12 @@
background: #fff;
color: #07c160;
border: 2rpx solid #07c160;
- flex: 0 1 auto;
- max-width: 100%;
+ flex: 1 1 100%;
+ width: 100%;
+ min-width: 0;
+ white-space: normal;
+ word-break: break-word;
+ overflow-wrap: anywhere;
&:active {
opacity: 0.8;
diff --git a/subPackages/order/payment.vue b/subPackages/order/payment.vue
index 8175694..49daf5c 100644
--- a/subPackages/order/payment.vue
+++ b/subPackages/order/payment.vue
@@ -80,6 +80,9 @@
{{ totalAmount }}
{{ $t('payment.payNow') }}
+
+ {{ $t('order.cancelOrder') }}
+
@@ -96,6 +99,7 @@
} from '@dcloudio/uni-app'
import {
queryById,
+ cancelOrder,
createWxPayment,
getWxPaymentStatus,
createAliPayment,
@@ -113,6 +117,12 @@
import {
useI18n
} from '@/utils/i18n.js'
+ import {
+ fetchAndCacheDanaPaymentConfig,
+ DANA_TOTAL_STORAGE_KEY,
+ DANA_SINGLE_STORAGE_KEY,
+ parseDanaStorageNumber
+ } from '@/utils/danaPaymentConfig.js'
const {
t
@@ -125,7 +135,23 @@
const passedTotalAmount = ref(null)
const passedDepositAmount = ref(null)
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 多平台)
const paymentMethods = ref([])
@@ -152,7 +178,7 @@
const totalAmount = computed(() => {
if (currencyCode.value === 'IDR') {
- return `${IDR_DEPOSIT_DISPLAY}`
+ return `${IDR_DEPOSIT_DISPLAY.value}`
}
if (passedTotalAmount.value !== null) {
return parseFloat(passedTotalAmount.value).toFixed(2);
@@ -258,7 +284,7 @@
orderInfo.value.deposit = deviceInfo.value.depositAmount;
}
if (currencyCode.value === 'IDR') {
- orderInfo.value.deposit = `${IDR_DEPOSIT_DISPLAY}`
+ orderInfo.value.deposit = `${IDR_DEPOSIT_DISPLAY.value}`
}
}
} 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;
@@ -641,6 +713,10 @@
}
onLoad((options) => {
+ loadDanaTotalCache()
+ void fetchAndCacheDanaPaymentConfig()
+ .then(() => loadDanaTotalCache())
+ .catch((e) => console.warn('DANA 配置刷新失败:', e))
// 设置导航栏标题为待支付
uni.setNavigationBarTitle({
title: t('payment.waitingForPayment')
@@ -909,6 +985,25 @@
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;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/utils/danaPaymentConfig.js b/utils/danaPaymentConfig.js
new file mode 100644
index 0000000..69fce79
--- /dev/null
+++ b/utils/danaPaymentConfig.js
@@ -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 }
+}