Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 836cdaf2dc | |||
| af758a0ccc | |||
| ec05584bb7 | |||
| 2f0479ea05 | |||
| e1c9068ab0 | |||
| 6913d9266b |
@@ -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 实例并检查语言
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
lazy-load="true"
|
lazy-load="true"
|
||||||
></image>
|
></image>
|
||||||
<view class="product-info">
|
<view class="product-info">
|
||||||
<view class="product-name">{{ order.productName || order.deviceName || '风电者2026新款' }}</view>
|
<view class="product-name">{{ order.productName || order.deviceName || $t('goods.defaultProductNameShort') }}</view>
|
||||||
<view style="display: flex;justify-content: space-between;">
|
<view style="display: flex;justify-content: space-between;">
|
||||||
<view class="product-style">款式:{{ order.optionName || order.style || order.deviceStyle || '标准' }}</view>
|
<view class="product-style">款式:{{ order.optionName || order.style || order.deviceStyle || '标准' }}</view>
|
||||||
<view class="product-price">¥ {{ totalAmount }}</view>
|
<view class="product-price">¥ {{ totalAmount }}</view>
|
||||||
|
|||||||
@@ -0,0 +1,293 @@
|
|||||||
|
<template>
|
||||||
|
<view class="h5-home-wrap">
|
||||||
|
<view class="main-content" :style="mainContentStyle">
|
||||||
|
<view class="h5-banner-section" v-if="bannerImages && bannerImages.length > 0">
|
||||||
|
<swiper class="h5-banner-swiper" :indicator-dots="bannerImages.length > 1" :autoplay="true" :interval="4000"
|
||||||
|
:duration="500" circular>
|
||||||
|
<swiper-item v-for="(img, idx) in bannerImages" :key="idx">
|
||||||
|
<view class="h5-banner-item" @click="$emit('bannerClick', idx)">
|
||||||
|
<image :src="img" mode="aspectFill" class="h5-banner-image"></image>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
|
<view class="h5-banner-empty" v-else>
|
||||||
|
<text>{{ loadingText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="h5-cta-card">
|
||||||
|
<view class="h5-cta-header">
|
||||||
|
<text class="h5-cta-title">{{ scanText }}</text>
|
||||||
|
<text class="h5-cta-desc">{{ t('home.h5ScanDesc') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="h5-cta-btn" @click="$emit('scan')">
|
||||||
|
<image class="h5-cta-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
||||||
|
<text class="h5-cta-btn-text">{{ scanText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="h5-section">
|
||||||
|
<view class="h5-section-title">{{ t('home.h5HowItWorks') }}</view>
|
||||||
|
<view class="h5-steps">
|
||||||
|
<view class="h5-step-item">
|
||||||
|
<text class="h5-step-index">1</text>
|
||||||
|
<text class="h5-step-text">{{ t('home.h5StepScan') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="h5-step-item">
|
||||||
|
<text class="h5-step-index">2</text>
|
||||||
|
<text class="h5-step-text">{{ t('home.h5StepUnlock') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="h5-step-item">
|
||||||
|
<text class="h5-step-index">3</text>
|
||||||
|
<text class="h5-step-text">{{ t('home.h5StepReturn') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="h5-section">
|
||||||
|
<view class="h5-section-title">{{ t('home.h5ServiceTitle') }}</view>
|
||||||
|
<view class="h5-tags">
|
||||||
|
<text class="h5-tag">{{ t('home.h5ServiceSupport') }}</text>
|
||||||
|
<text class="h5-tag">{{ t('home.h5ServiceSafePayment') }}</text>
|
||||||
|
<text class="h5-tag">{{ t('home.h5ServiceEasyReturn') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="h5-bottom-actions">
|
||||||
|
<view class="action-btn primary" @click="$emit('scan')">
|
||||||
|
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
||||||
|
<text class="action-label primary-label">{{ scanText }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-btn secondary" @click="$emit('my')">
|
||||||
|
<image class="action-icon" src="/static/user.png" mode="aspectFit" />
|
||||||
|
<text class="action-label">{{ personalCenterText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from '../../utils/i18n.js'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
defineEmits(['bannerClick', 'scan', 'buy', 'my'])
|
||||||
|
defineProps({
|
||||||
|
mainContentStyle: { type: Object, default: () => ({}) },
|
||||||
|
bannerImages: { type: Array, default: () => [] },
|
||||||
|
loadingText: { type: String, default: '' },
|
||||||
|
buyDeviceText: { type: String, default: '' },
|
||||||
|
scanText: { type: String, default: '' },
|
||||||
|
personalCenterText: { type: String, default: '' }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.h5-home-wrap {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
padding-bottom: 220rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-section {
|
||||||
|
padding: 24rpx 20rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-swiper {
|
||||||
|
width: 100%;
|
||||||
|
height: 320rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-item,
|
||||||
|
.h5-banner-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-empty {
|
||||||
|
padding: 80rpx 20rpx 0;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-cta-card {
|
||||||
|
margin: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20rpx;
|
||||||
|
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-cta-header {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-cta-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f7d43;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-cta-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #4f7b61;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-cta-btn {
|
||||||
|
height: 76rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
background: #3EAB64;
|
||||||
|
border-radius: 38rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-cta-icon {
|
||||||
|
width: 30rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-cta-btn-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-section {
|
||||||
|
margin: 0 20rpx 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-section-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d2d2d;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-steps {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-step-item {
|
||||||
|
background: #f4f8f6;
|
||||||
|
border-radius: 14rpx;
|
||||||
|
padding: 16rpx 10rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-step-index {
|
||||||
|
width: 34rpx;
|
||||||
|
height: 34rpx;
|
||||||
|
background: #3EAB64;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-step-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #315c46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-tag {
|
||||||
|
padding: 10rpx 16rpx;
|
||||||
|
background: #eef8f1;
|
||||||
|
color: #2e7d4f;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-bottom-actions {
|
||||||
|
position: fixed;
|
||||||
|
left: 20rpx;
|
||||||
|
right: 20rpx;
|
||||||
|
bottom: 30rpx;
|
||||||
|
z-index: 1200;
|
||||||
|
padding: 12rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
border-radius: 28rpx;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
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;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.secondary {
|
||||||
|
width: 180rpx;
|
||||||
|
background: #f4f6f8;
|
||||||
|
border: 2rpx solid #e7eaee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.primary .action-icon {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-label {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="main-content" :style="mainContentStyle">
|
||||||
|
<!-- 支付宝小程序使用专用组件 -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<MapComponentAlipay v-if="!isLoading && userLocation && !locationPermissionDenied" ref="innerMapRef"
|
||||||
|
:userLocation="userLocation" :positionList="positionList" :filteredPositions="filteredPositions"
|
||||||
|
:searchKeyword="searchKeyword" :enableMarkers="true" :bannerImages="bannerImages"
|
||||||
|
:hideMapOverlays="hideMapOverlays" @relocate="$emit('relocate')" @scan="$emit('scan')"
|
||||||
|
@showList="$emit('showList')" @markerTap="$emit('markerTap', $event)"
|
||||||
|
@mapCenterChange="$emit('mapCenterChange', $event)" @bannerClick="$emit('bannerClick', $event)"
|
||||||
|
@guide="$emit('guide')" />
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 非支付宝小程序使用通用组件 -->
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
|
<MapComponent v-if="!isLoading && userLocation && !locationPermissionDenied" ref="innerMapRef"
|
||||||
|
:userLocation="userLocation" :positionList="positionList" :filteredPositions="filteredPositions"
|
||||||
|
:searchKeyword="searchKeyword" :enableMarkers="true" :bannerImages="bannerImages"
|
||||||
|
:hideMapOverlays="hideMapOverlays" @relocate="$emit('relocate')" @scan="$emit('scan')"
|
||||||
|
@showList="$emit('showList')" @markerTap="$emit('markerTap', $event)"
|
||||||
|
@mapCenterChange="$emit('mapCenterChange', $event)" @bannerClick="$emit('bannerClick', $event)"
|
||||||
|
@guide="$emit('guide')" />
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 地图加载状态 -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<view v-if="!userLocation" class="location-denied-placeholder">
|
||||||
|
<view class="denied-content">
|
||||||
|
<text class="denied-text">{{ locationPermissionText }}</text>
|
||||||
|
<view class="denied-enable-btn" @click="$emit('enableLocation')">
|
||||||
|
<text class="denied-enable-btn-text">{{ enableLocationText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
|
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
||||||
|
<view class="loading-content">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
<text>{{ loadingLocationText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="bottom-actions">
|
||||||
|
<view class="action-btn secondary small btn-nearby" @click="$emit('buy')">
|
||||||
|
<view class="icon-wrap">
|
||||||
|
<image src="/static/shop_icon.png" class="action-icon" mode="scaleToFill" lazy-load="true"></image>
|
||||||
|
</view>
|
||||||
|
<text class="action-label">{{ buyDeviceText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-btn primary btn-scan" @click="$emit('scan')">
|
||||||
|
<view class="icon-wrap">
|
||||||
|
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFill" lazy-load="true" />
|
||||||
|
</view>
|
||||||
|
<text class="primary-label">{{ scanText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-btn secondary small btn-my" @click="$emit('my')">
|
||||||
|
<view class="icon-wrap">
|
||||||
|
<image class="action-icon" src="/static/user.png" mode="aspectFit" lazy-load="true" />
|
||||||
|
</view>
|
||||||
|
<text class="action-label">{{ personalCenterText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import MapComponent from '../MapComponent.vue'
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
import MapComponentAlipay from '../MapComponentAlipay.vue'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
defineEmits(['relocate', 'scan', 'showList', 'markerTap', 'mapCenterChange', 'bannerClick', 'guide', 'enableLocation', 'buy', 'my'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
mainContentStyle: { type: Object, default: () => ({}) },
|
||||||
|
isLoading: { type: Boolean, default: false },
|
||||||
|
userLocation: { type: Object, default: null },
|
||||||
|
locationPermissionDenied: { type: Boolean, default: false },
|
||||||
|
positionList: { type: Array, default: () => [] },
|
||||||
|
filteredPositions: { type: Array, default: () => [] },
|
||||||
|
searchKeyword: { type: String, default: '' },
|
||||||
|
bannerImages: { type: Array, default: () => [] },
|
||||||
|
hideMapOverlays: { type: Boolean, default: false },
|
||||||
|
locationPermissionText: { type: String, default: '' },
|
||||||
|
enableLocationText: { type: String, default: '' },
|
||||||
|
loadingLocationText: { type: String, default: '' },
|
||||||
|
buyDeviceText: { type: String, default: '' },
|
||||||
|
scanText: { type: String, default: '' },
|
||||||
|
personalCenterText: { type: String, default: '' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const innerMapRef = ref(null)
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
mapCenter: computed(() => innerMapRef.value?.mapCenter || null),
|
||||||
|
moveToLocation: (location) => innerMapRef.value && innerMapRef.value.moveToLocation && innerMapRef.value.moveToLocation(location)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding-bottom: 180rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-loading-placeholder {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 180rpx;
|
||||||
|
background: #f6f7fb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
.loading-content {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border: 4rpx solid #f0f0f0;
|
||||||
|
border-top: 4rpx solid #2196F3;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-denied-placeholder {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 180rpx;
|
||||||
|
background: #f6f7fb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.denied-content {
|
||||||
|
width: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.denied-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.denied-enable-btn {
|
||||||
|
margin-top: 28rpx;
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
background: #3EAB64;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.denied-enable-btn-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-actions {
|
||||||
|
position: fixed;
|
||||||
|
left: 20rpx;
|
||||||
|
right: 20rpx;
|
||||||
|
bottom: 40rpx;
|
||||||
|
z-index: 1200;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.primary {
|
||||||
|
background: #3EAB64;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 56rpx;
|
||||||
|
height: 112rpx;
|
||||||
|
flex: 1;
|
||||||
|
max-width: 400rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.secondary {
|
||||||
|
color: #333;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
width: 140rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8rpx 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrap {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scan .icon-wrap {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scan .action-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-label {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-label {
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,7 +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://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.58: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
|
||||||
|
|||||||
+57
-10
@@ -71,18 +71,18 @@ export default {
|
|||||||
orders: 'Orders',
|
orders: 'Orders',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
title: 'FengDianZhe'
|
title: 'Isidaya'
|
||||||
},
|
},
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
name: 'FengDianZhe',
|
name: 'Isidaya',
|
||||||
slogan: 'Fan & Power Bank Rental',
|
slogan: 'Fan & Power Bank Rental',
|
||||||
fullName: 'FengDianZhe',
|
fullName: 'Isidaya',
|
||||||
welcome: 'Welcome'
|
welcome: 'Welcome to Isidaya'
|
||||||
},
|
},
|
||||||
|
|
||||||
home: {
|
home: {
|
||||||
title: 'FengDianZhe',
|
title: 'Isidaya',
|
||||||
nearbyDevices: 'Nearby',
|
nearbyDevices: 'Nearby',
|
||||||
scanToUse: 'Scan',
|
scanToUse: 'Scan',
|
||||||
personalCenter: 'Profile',
|
personalCenter: 'Profile',
|
||||||
@@ -92,6 +92,15 @@ export default {
|
|||||||
relocate: 'Relocate',
|
relocate: 'Relocate',
|
||||||
search: 'Search',
|
search: 'Search',
|
||||||
service: 'Service',
|
service: 'Service',
|
||||||
|
h5ScanDesc: 'Scan the device QR code to start',
|
||||||
|
h5HowItWorks: 'How It Works',
|
||||||
|
h5StepScan: 'Scan',
|
||||||
|
h5StepUnlock: 'Device Ejects',
|
||||||
|
h5StepReturn: 'Return',
|
||||||
|
h5ServiceTitle: 'Service',
|
||||||
|
h5ServiceSupport: '24/7 Support',
|
||||||
|
h5ServiceSafePayment: 'Safe Payment',
|
||||||
|
h5ServiceEasyReturn: 'Easy Return',
|
||||||
searchPlaceholder: 'Search locations',
|
searchPlaceholder: 'Search locations',
|
||||||
nearbyDeviceLocation: 'Nearby',
|
nearbyDeviceLocation: 'Nearby',
|
||||||
noNearbyDevice: 'No devices nearby',
|
noNearbyDevice: 'No devices nearby',
|
||||||
@@ -106,6 +115,38 @@ export default {
|
|||||||
enableLocation: 'Enable Location'
|
enableLocation: 'Enable Location'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
scan: {
|
||||||
|
title: 'Scan to Use',
|
||||||
|
album: 'Album',
|
||||||
|
manualInput: 'Enter Manually',
|
||||||
|
manualInputTitle: 'Enter Device No.',
|
||||||
|
deviceNoPlaceholder: 'Enter the number on the device',
|
||||||
|
initializing: 'Initializing...',
|
||||||
|
startingCamera: 'Starting camera...',
|
||||||
|
alignQRCode: 'Align the QR code within the frame',
|
||||||
|
initFailed: 'Initialization failed',
|
||||||
|
browserNotSupportCamera: 'Your browser does not support camera access',
|
||||||
|
containerNotFound: 'Scanner container not found',
|
||||||
|
noCameraFound: 'No camera found',
|
||||||
|
ensureCameraExists: 'Please make sure your device has a camera',
|
||||||
|
cameraPermissionDenied: 'Camera permission denied',
|
||||||
|
cameraPermissionHint: 'Please allow camera access in browser settings',
|
||||||
|
cameraInUse: 'Camera is in use',
|
||||||
|
closeOtherCameraApps: 'Please close other apps using the camera',
|
||||||
|
browserNotSupported: 'Browser not supported',
|
||||||
|
useModernBrowser: 'Please use a modern browser',
|
||||||
|
cameraStartFailed: 'Failed to start camera',
|
||||||
|
tryRefreshOrAlternative: 'Try refreshing the page or use another method',
|
||||||
|
errorFallbackHint: 'You can:',
|
||||||
|
errorFallbackAlbum: 'Select a QR code image from album',
|
||||||
|
errorFallbackManual: 'Enter device number manually',
|
||||||
|
recognizing: 'Recognizing...',
|
||||||
|
qrNotFound: 'QR code not detected',
|
||||||
|
recognizeFailed: 'Recognition failed',
|
||||||
|
h5Only: 'This feature is only available on H5',
|
||||||
|
deviceNoRequired: 'Please enter device number'
|
||||||
|
},
|
||||||
|
|
||||||
guide: {
|
guide: {
|
||||||
title: 'How to Use',
|
title: 'How to Use',
|
||||||
step1Title: 'Scan QR Code',
|
step1Title: 'Scan QR Code',
|
||||||
@@ -144,6 +185,8 @@ export default {
|
|||||||
offline: 'Offline',
|
offline: 'Offline',
|
||||||
pricingRules: 'Pricing Rules',
|
pricingRules: 'Pricing Rules',
|
||||||
capLimit: ' Cap',
|
capLimit: ' Cap',
|
||||||
|
detailBillingByUnit: 'Less than {unit} {minute} is billed as {unit} {minute}. Capped at ¥{cap}. Billing up to ¥{cap} counts as purchase.',
|
||||||
|
detailBillingIdr: 'Less than 1 {hour} is billed as 1 {hour}. Capped at {cap}. Billing up to {cap} counts as purchase.',
|
||||||
usageInstructions: 'Usage Instructions',
|
usageInstructions: 'Usage Instructions',
|
||||||
checkBeforeUse: 'Please check if the device is in good condition before use',
|
checkBeforeUse: 'Please check if the device is in good condition before use',
|
||||||
autoChargeOvertime: 'Overtime will be charged automatically by hour',
|
autoChargeOvertime: 'Overtime will be charged automatically by hour',
|
||||||
@@ -171,6 +214,7 @@ export default {
|
|||||||
noOrderRecord: 'No order records',
|
noOrderRecord: 'No order records',
|
||||||
getOrderListFailed: 'Failed to get order list',
|
getOrderListFailed: 'Failed to get order list',
|
||||||
confirmCancelContent: 'Are you sure to cancel this order?',
|
confirmCancelContent: 'Are you sure to cancel this order?',
|
||||||
|
confirmDeleteContent: 'Are you sure you want to delete this order?',
|
||||||
orderDetail: 'Order Detail',
|
orderDetail: 'Order Detail',
|
||||||
orderNo: 'Order No.',
|
orderNo: 'Order No.',
|
||||||
orderStatus: 'Order Status',
|
orderStatus: 'Order Status',
|
||||||
@@ -404,6 +448,7 @@ export default {
|
|||||||
alipay: 'Alipay',
|
alipay: 'Alipay',
|
||||||
alipayHk: 'Alipay (Hong Kong)',
|
alipayHk: 'Alipay (Hong Kong)',
|
||||||
alipayId: 'Alipay (Indonesia)',
|
alipayId: 'Alipay (Indonesia)',
|
||||||
|
ALIPAYDANA: 'Alipay DANA',
|
||||||
balance: 'Balance',
|
balance: 'Balance',
|
||||||
payNow: 'Pay',
|
payNow: 'Pay',
|
||||||
paying: 'Processing...',
|
paying: 'Processing...',
|
||||||
@@ -531,7 +576,7 @@ export default {
|
|||||||
received: 'Received',
|
received: 'Received',
|
||||||
detail: 'Detail',
|
detail: 'Detail',
|
||||||
recipientInfo: 'Ship To',
|
recipientInfo: 'Ship To',
|
||||||
recipientName: 'FengDianZhe 18163601305',
|
recipientName: 'Isidaya 18163601305',
|
||||||
recipientAddress: 'Rm 623, Bldg A2, Xinchanghai Park, Luogu St, Yuelu, Changsha, Hunan',
|
recipientAddress: 'Rm 623, Bldg A2, Xinchanghai Park, Luogu St, Yuelu, Changsha, Hunan',
|
||||||
copyAllInfo: 'Copy All',
|
copyAllInfo: 'Copy All',
|
||||||
recipient: 'To',
|
recipient: 'To',
|
||||||
@@ -611,7 +656,7 @@ export default {
|
|||||||
termsOfService: 'Terms of Service',
|
termsOfService: 'Terms of Service',
|
||||||
termsAndConditions: 'Terms & Conditions',
|
termsAndConditions: 'Terms & Conditions',
|
||||||
lastUpdate: 'Last Update',
|
lastUpdate: 'Last Update',
|
||||||
applicableToService: 'Applicable to "FengDianZhe" shared fan rental service',
|
applicableToService: 'Applicable to "Isidaya" shared fan rental service',
|
||||||
footerNotice: 'If you have questions about this agreement, please go to "My-Customer Service"',
|
footerNotice: 'If you have questions about this agreement, please go to "My-Customer Service"',
|
||||||
footerNoticePolicy: 'If you have questions about this policy, please go to "My-Customer Service"',
|
footerNoticePolicy: 'If you have questions about this policy, please go to "My-Customer Service"',
|
||||||
|
|
||||||
@@ -647,7 +692,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
share: {
|
share: {
|
||||||
title: 'FengDianZhe - Shared Fan & Power Bank',
|
title: 'Isidaya - Shared Fan & Power Bank',
|
||||||
path: '/pages/index/index'
|
path: '/pages/index/index'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -859,7 +904,9 @@ export default {
|
|||||||
goods: {
|
goods: {
|
||||||
title: 'Product Details',
|
title: 'Product Details',
|
||||||
goodsTitle: 'Customize Details',
|
goodsTitle: 'Customize Details',
|
||||||
productName: 'FengDianZhe Shared Fan + Power Bank + Hand Warmer Series - Cherry Blossom Pink',
|
defaultProductNameShort: 'Isidaya 2026',
|
||||||
|
defaultProductNameFull: 'Isidaya 2026 Fan, Power Bank & Hand Warmer All-in-One',
|
||||||
|
productName: 'Isidaya Shared Fan + Power Bank + Hand Warmer Series - Cherry Blossom Pink',
|
||||||
perUnit: '/pc',
|
perUnit: '/pc',
|
||||||
buyNow: 'Buy Now',
|
buyNow: 'Buy Now',
|
||||||
productDetail: 'Customize Details',
|
productDetail: 'Customize Details',
|
||||||
@@ -870,7 +917,7 @@ export default {
|
|||||||
temp: 'Smart Temperature',
|
temp: 'Smart Temperature',
|
||||||
charge: 'Fast Charging'
|
charge: 'Fast Charging'
|
||||||
},
|
},
|
||||||
description: 'FengDianZhe shared fan, integrating fan, power bank, and hand warmer functions. Equipped with 8000mAh large capacity battery for long-lasting use. Efficient fan design with 3-speed adjustment. Smart temperature control hand warmer, warm in winter and cool in summer. Fast charging technology supports multiple device charging. Cherry blossom pink color, fashionable and beautiful, your best travel companion.',
|
description: 'Isidaya shared fan, integrating fan, power bank, and hand warmer functions. Equipped with 8000mAh large capacity battery for long-lasting use. Efficient fan design with 3-speed adjustment. Smart temperature control hand warmer, warm in winter and cool in summer. Fast charging technology supports multiple device charging. Cherry blossom pink color, fashionable and beautiful, your best travel companion.',
|
||||||
confirmPurchase: 'Confirm Purchase',
|
confirmPurchase: 'Confirm Purchase',
|
||||||
confirmPurchaseContent: 'Confirm to purchase this product for ¥{price}?',
|
confirmPurchaseContent: 'Confirm to purchase this product for ¥{price}?',
|
||||||
purchaseSuccess: 'Purchase Successful',
|
purchaseSuccess: 'Purchase Successful',
|
||||||
|
|||||||
+57
-10
@@ -71,18 +71,18 @@ export default {
|
|||||||
orders: 'Pesanan',
|
orders: 'Pesanan',
|
||||||
settings: 'Pengaturan',
|
settings: 'Pengaturan',
|
||||||
back: 'Kembali',
|
back: 'Kembali',
|
||||||
title: 'Kipas Angin & Power Bank Berbagi FengDianZhe'
|
title: 'Kipas Angin & Power Bank Berbagi Isidaya'
|
||||||
},
|
},
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
name: 'FengDianZhe',
|
name: 'Isidaya',
|
||||||
slogan: 'Kipas Angin & Power Bank Berbagi',
|
slogan: 'Kipas Angin & Power Bank Berbagi',
|
||||||
fullName: 'FengDianZhe - Kipas Angin & Power Bank Berbagi',
|
fullName: 'Isidaya - Kipas Angin & Power Bank Berbagi',
|
||||||
welcome: 'Selamat datang menggunakan FengDianZhe'
|
welcome: 'Selamat datang menggunakan Isidaya'
|
||||||
},
|
},
|
||||||
|
|
||||||
home: {
|
home: {
|
||||||
title: 'Kipas Angin & Power Bank Berbagi FengDianZhe',
|
title: 'Kipas Angin & Power Bank Berbagi Isidaya',
|
||||||
nearbyDevices: 'Perangkat Terdekat',
|
nearbyDevices: 'Perangkat Terdekat',
|
||||||
scanToUse: 'Pindai untuk Menggunakan',
|
scanToUse: 'Pindai untuk Menggunakan',
|
||||||
personalCenter: 'Pusat Pribadi',
|
personalCenter: 'Pusat Pribadi',
|
||||||
@@ -92,6 +92,15 @@ export default {
|
|||||||
relocate: 'Lokasi Ulang',
|
relocate: 'Lokasi Ulang',
|
||||||
search: 'Cari',
|
search: 'Cari',
|
||||||
service: 'Layanan Pelanggan',
|
service: 'Layanan Pelanggan',
|
||||||
|
h5ScanDesc: 'Pindai kode QR perangkat untuk mulai menggunakan',
|
||||||
|
h5HowItWorks: 'Cara Penggunaan',
|
||||||
|
h5StepScan: 'Pindai',
|
||||||
|
h5StepUnlock: 'Perangkat Keluar',
|
||||||
|
h5StepReturn: 'Kembalikan',
|
||||||
|
h5ServiceTitle: 'Layanan',
|
||||||
|
h5ServiceSupport: 'Dukungan 24/7',
|
||||||
|
h5ServiceSafePayment: 'Pembayaran Aman',
|
||||||
|
h5ServiceEasyReturn: 'Pengembalian Mudah',
|
||||||
searchPlaceholder: 'Cari lokasi terdekat',
|
searchPlaceholder: 'Cari lokasi terdekat',
|
||||||
nearbyDeviceLocation: 'Lokasi Perangkat Terdekat',
|
nearbyDeviceLocation: 'Lokasi Perangkat Terdekat',
|
||||||
noNearbyDevice: 'Tidak ada perangkat terdekat',
|
noNearbyDevice: 'Tidak ada perangkat terdekat',
|
||||||
@@ -106,6 +115,38 @@ export default {
|
|||||||
enableLocation: 'Aktifkan Lokasi'
|
enableLocation: 'Aktifkan Lokasi'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
scan: {
|
||||||
|
title: 'Pindai untuk Menggunakan',
|
||||||
|
album: 'Album',
|
||||||
|
manualInput: 'Input Manual',
|
||||||
|
manualInputTitle: 'Masukkan Nomor Perangkat',
|
||||||
|
deviceNoPlaceholder: 'Masukkan nomor pada perangkat',
|
||||||
|
initializing: 'Menginisialisasi...',
|
||||||
|
startingCamera: 'Menghidupkan kamera...',
|
||||||
|
alignQRCode: 'Letakkan kode QR dalam bingkai',
|
||||||
|
initFailed: 'Inisialisasi gagal',
|
||||||
|
browserNotSupportCamera: 'Browser Anda tidak mendukung akses kamera',
|
||||||
|
containerNotFound: 'Kontainer pemindai tidak ditemukan',
|
||||||
|
noCameraFound: 'Kamera tidak ditemukan',
|
||||||
|
ensureCameraExists: 'Pastikan perangkat Anda memiliki kamera',
|
||||||
|
cameraPermissionDenied: 'Izin kamera ditolak',
|
||||||
|
cameraPermissionHint: 'Harap izinkan akses kamera di pengaturan browser',
|
||||||
|
cameraInUse: 'Kamera sedang digunakan',
|
||||||
|
closeOtherCameraApps: 'Harap tutup aplikasi lain yang menggunakan kamera',
|
||||||
|
browserNotSupported: 'Browser tidak didukung',
|
||||||
|
useModernBrowser: 'Harap gunakan browser modern',
|
||||||
|
cameraStartFailed: 'Gagal menghidupkan kamera',
|
||||||
|
tryRefreshOrAlternative: 'Coba segarkan halaman atau gunakan cara lain',
|
||||||
|
errorFallbackHint: 'Anda dapat:',
|
||||||
|
errorFallbackAlbum: 'Pilih gambar kode QR dari album',
|
||||||
|
errorFallbackManual: 'Masukkan nomor perangkat secara manual',
|
||||||
|
recognizing: 'Mengenali...',
|
||||||
|
qrNotFound: 'Kode QR tidak terdeteksi',
|
||||||
|
recognizeFailed: 'Pengenalan gagal',
|
||||||
|
h5Only: 'Fitur ini hanya tersedia di H5',
|
||||||
|
deviceNoRequired: 'Harap masukkan nomor perangkat'
|
||||||
|
},
|
||||||
|
|
||||||
guide: {
|
guide: {
|
||||||
title: 'Panduan Penggunaan',
|
title: 'Panduan Penggunaan',
|
||||||
step1Title: 'Pindai untuk Menggunakan',
|
step1Title: 'Pindai untuk Menggunakan',
|
||||||
@@ -145,6 +186,8 @@ export default {
|
|||||||
offline: 'Offline',
|
offline: 'Offline',
|
||||||
pricingRules: 'Aturan Penagihan',
|
pricingRules: 'Aturan Penagihan',
|
||||||
capLimit: 'Maksimum',
|
capLimit: 'Maksimum',
|
||||||
|
detailBillingByUnit: 'Kurang dari {unit} {minute} ditagih sebagai {unit} {minute}. Maksimum ¥{cap}. Penagihan hingga ¥{cap} dianggap sebagai pembelian.',
|
||||||
|
detailBillingIdr: 'Kurang dari 1 {hour} ditagih sebagai 1 {hour}. Maksimum {cap}. Penagihan hingga {cap} dianggap sebagai pembelian.',
|
||||||
usageInstructions: 'Instruksi Penggunaan',
|
usageInstructions: 'Instruksi Penggunaan',
|
||||||
checkBeforeUse: 'Harap periksa apakah perangkat dalam kondisi baik sebelum digunakan',
|
checkBeforeUse: 'Harap periksa apakah perangkat dalam kondisi baik sebelum digunakan',
|
||||||
autoChargeOvertime: 'Melebihi waktu penggunaan akan dikenakan biaya per jam secara otomatis',
|
autoChargeOvertime: 'Melebihi waktu penggunaan akan dikenakan biaya per jam secara otomatis',
|
||||||
@@ -172,6 +215,7 @@ export default {
|
|||||||
noOrderRecord: 'Tidak ada catatan pesanan',
|
noOrderRecord: 'Tidak ada catatan pesanan',
|
||||||
getOrderListFailed: 'Gagal mendapatkan daftar pesanan',
|
getOrderListFailed: 'Gagal mendapatkan daftar pesanan',
|
||||||
confirmCancelContent: 'Apakah Anda yakin ingin membatalkan pesanan ini?',
|
confirmCancelContent: 'Apakah Anda yakin ingin membatalkan pesanan ini?',
|
||||||
|
confirmDeleteContent: 'Apakah Anda yakin ingin menghapus pesanan ini?',
|
||||||
orderDetail: 'Detail Pesanan',
|
orderDetail: 'Detail Pesanan',
|
||||||
orderNo: 'Nomor Pesanan',
|
orderNo: 'Nomor Pesanan',
|
||||||
orderStatus: 'Status Pesanan',
|
orderStatus: 'Status Pesanan',
|
||||||
@@ -403,6 +447,7 @@ export default {
|
|||||||
alipay: 'Alipay',
|
alipay: 'Alipay',
|
||||||
alipayHk: 'Alipay Hong Kong',
|
alipayHk: 'Alipay Hong Kong',
|
||||||
alipayId: 'Alipay Indonesia',
|
alipayId: 'Alipay Indonesia',
|
||||||
|
ALIPAYDANA: 'Alipay DANA',
|
||||||
balance: 'Pembayaran Saldo',
|
balance: 'Pembayaran Saldo',
|
||||||
payNow: 'Bayar Sekarang',
|
payNow: 'Bayar Sekarang',
|
||||||
paying: 'Sedang membayar...',
|
paying: 'Sedang membayar...',
|
||||||
@@ -530,7 +575,7 @@ export default {
|
|||||||
received: 'Telah Diterima',
|
received: 'Telah Diterima',
|
||||||
detail: 'Detail',
|
detail: 'Detail',
|
||||||
recipientInfo: 'Informasi Penerima',
|
recipientInfo: 'Informasi Penerima',
|
||||||
recipientName: 'FengDianZhe 18163601305',
|
recipientName: 'Isidaya 18163601305',
|
||||||
recipientAddress: 'Gedung A2, Lantai 623, Taman Sains dan Teknologi Xinchanghaijian, Jalan Lugu, Distrik Yuelu, Changsha, Provinsi Hunan',
|
recipientAddress: 'Gedung A2, Lantai 623, Taman Sains dan Teknologi Xinchanghaijian, Jalan Lugu, Distrik Yuelu, Changsha, Provinsi Hunan',
|
||||||
copyAllInfo: 'Salin Semua Informasi',
|
copyAllInfo: 'Salin Semua Informasi',
|
||||||
recipient: 'Penerima',
|
recipient: 'Penerima',
|
||||||
@@ -610,7 +655,7 @@ export default {
|
|||||||
termsOfService: 'Ketentuan Layanan',
|
termsOfService: 'Ketentuan Layanan',
|
||||||
termsAndConditions: 'Syarat & Ketentuan',
|
termsAndConditions: 'Syarat & Ketentuan',
|
||||||
lastUpdate: 'Pembaruan Terakhir',
|
lastUpdate: 'Pembaruan Terakhir',
|
||||||
applicableToService: 'Berlaku untuk layanan sewa kipas angin berbagi "FengDianZhe"',
|
applicableToService: 'Berlaku untuk layanan sewa kipas angin berbagi "Isidaya"',
|
||||||
footerNotice: 'Jika ada pertanyaan tentang perjanjian ini, harap pergi ke "Saya-Layanan Pelanggan" untuk konsultasi',
|
footerNotice: 'Jika ada pertanyaan tentang perjanjian ini, harap pergi ke "Saya-Layanan Pelanggan" untuk konsultasi',
|
||||||
footerNoticePolicy: 'Jika ada pertanyaan tentang kebijakan ini, harap pergi ke "Saya-Layanan Pelanggan" untuk konsultasi',
|
footerNoticePolicy: 'Jika ada pertanyaan tentang kebijakan ini, harap pergi ke "Saya-Layanan Pelanggan" untuk konsultasi',
|
||||||
|
|
||||||
@@ -646,7 +691,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
share: {
|
share: {
|
||||||
title: 'FengDianZhe - Kipas Angin & Power Bank Berbagi',
|
title: 'Isidaya - Kipas Angin & Power Bank Berbagi',
|
||||||
path: '/pages/index/index'
|
path: '/pages/index/index'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -858,7 +903,9 @@ export default {
|
|||||||
goods: {
|
goods: {
|
||||||
title: 'Detail Produk',
|
title: 'Detail Produk',
|
||||||
goodsTitle: 'Detail Kustomisasi',
|
goodsTitle: 'Detail Kustomisasi',
|
||||||
productName: 'FengDianZhe Kipas Angin Berbagi + Power Bank + Seri Hand Warmer - Pink Sakura',
|
defaultProductNameShort: 'Isidaya 2026',
|
||||||
|
defaultProductNameFull: 'Isidaya 2026 Kipas Angin, Power Bank & Hand Warmer 3-in-1',
|
||||||
|
productName: 'Isidaya Kipas Angin Berbagi + Power Bank + Seri Hand Warmer - Pink Sakura',
|
||||||
perUnit: '/buah',
|
perUnit: '/buah',
|
||||||
buyNow: 'Beli Sekarang',
|
buyNow: 'Beli Sekarang',
|
||||||
productDetail: 'Detail Kustomisasi',
|
productDetail: 'Detail Kustomisasi',
|
||||||
@@ -869,7 +916,7 @@ export default {
|
|||||||
temp: 'Kontrol Suhu Pintar',
|
temp: 'Kontrol Suhu Pintar',
|
||||||
charge: 'Pengisian Cepat'
|
charge: 'Pengisian Cepat'
|
||||||
},
|
},
|
||||||
description: 'FengDianZhe kipas angin berbagi, mengintegrasikan tiga fungsi dalam satu: kipas angin, power bank, dan hand warmer. Menggunakan baterai kapasitas besar 8000mAh, daya tahan lama. Desain kipas angin efisien, tiga tingkat angin dapat disesuaikan. Hand warmer kontrol suhu pintar, hangat di musim dingin dan sejuk di musim panas. Teknologi pengisian cepat, mendukung pengisian multi-perangkat. Warna pink sakura, modis dan indah, adalah teman perjalanan terbaik Anda.',
|
description: 'Isidaya kipas angin berbagi, mengintegrasikan tiga fungsi dalam satu: kipas angin, power bank, dan hand warmer. Menggunakan baterai kapasitas besar 8000mAh, daya tahan lama. Desain kipas angin efisien, tiga tingkat angin dapat disesuaikan. Hand warmer kontrol suhu pintar, hangat di musim dingin dan sejuk di musim panas. Teknologi pengisian cepat, mendukung pengisian multi-perangkat. Warna pink sakura, modis dan indah, adalah teman perjalanan terbaik Anda.',
|
||||||
confirmPurchase: 'Konfirmasi Pembelian',
|
confirmPurchase: 'Konfirmasi Pembelian',
|
||||||
confirmPurchaseContent: 'Konfirmasi membeli produk ini, perlu membayar ¥{price}?',
|
confirmPurchaseContent: 'Konfirmasi membeli produk ini, perlu membayar ¥{price}?',
|
||||||
purchaseSuccess: 'Pembelian berhasil',
|
purchaseSuccess: 'Pembelian berhasil',
|
||||||
|
|||||||
+57
-10
@@ -70,18 +70,18 @@ export default {
|
|||||||
orders: '订单',
|
orders: '订单',
|
||||||
settings: '设置',
|
settings: '设置',
|
||||||
back: '返回',
|
back: '返回',
|
||||||
title: '风电者共享风扇&暖手充电宝'
|
title: 'Isidaya共享风扇&暖手充电宝'
|
||||||
},
|
},
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
name: '风电者',
|
name: 'Isidaya',
|
||||||
slogan: '共享风扇暖手充电宝',
|
slogan: '共享风扇暖手充电宝',
|
||||||
fullName: '风电者 - 共享风扇暖手充电宝',
|
fullName: 'Isidaya - 共享风扇暖手充电宝',
|
||||||
welcome: '欢迎使用风电者'
|
welcome: '欢迎使用Isidaya'
|
||||||
},
|
},
|
||||||
|
|
||||||
home: {
|
home: {
|
||||||
title: '风电者共享风扇&暖手充电宝',
|
title: 'Isidaya共享风扇&暖手充电宝',
|
||||||
nearbyDevices: '附近设备',
|
nearbyDevices: '附近设备',
|
||||||
scanToUse: '扫码使用',
|
scanToUse: '扫码使用',
|
||||||
personalCenter: '个人中心',
|
personalCenter: '个人中心',
|
||||||
@@ -91,6 +91,15 @@ export default {
|
|||||||
relocate: '重新定位',
|
relocate: '重新定位',
|
||||||
search: '搜索',
|
search: '搜索',
|
||||||
service: '客服',
|
service: '客服',
|
||||||
|
h5ScanDesc: '扫描设备二维码,立即开始使用',
|
||||||
|
h5HowItWorks: '使用流程',
|
||||||
|
h5StepScan: '扫码',
|
||||||
|
h5StepUnlock: '设备弹出',
|
||||||
|
h5StepReturn: '归还',
|
||||||
|
h5ServiceTitle: '服务保障',
|
||||||
|
h5ServiceSupport: '24小时在线客服',
|
||||||
|
h5ServiceSafePayment: '安全支付',
|
||||||
|
h5ServiceEasyReturn: '便捷归还',
|
||||||
searchPlaceholder: '搜索附近场地',
|
searchPlaceholder: '搜索附近场地',
|
||||||
nearbyDeviceLocation: '附近设备场地',
|
nearbyDeviceLocation: '附近设备场地',
|
||||||
noNearbyDevice: '附近暂无设备',
|
noNearbyDevice: '附近暂无设备',
|
||||||
@@ -105,6 +114,38 @@ export default {
|
|||||||
enableLocation: '开启定位'
|
enableLocation: '开启定位'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
scan: {
|
||||||
|
title: '扫码使用',
|
||||||
|
album: '相册',
|
||||||
|
manualInput: '手动输入',
|
||||||
|
manualInputTitle: '手动输入设备号',
|
||||||
|
deviceNoPlaceholder: '请输入设备上的编号',
|
||||||
|
initializing: '正在初始化...',
|
||||||
|
startingCamera: '正在启动摄像头...',
|
||||||
|
alignQRCode: '将二维码放入框内扫描',
|
||||||
|
initFailed: '初始化失败',
|
||||||
|
browserNotSupportCamera: '您的浏览器不支持摄像头访问',
|
||||||
|
containerNotFound: '扫码容器未找到',
|
||||||
|
noCameraFound: '未找到可用的摄像头',
|
||||||
|
ensureCameraExists: '请确保设备有摄像头',
|
||||||
|
cameraPermissionDenied: '摄像头权限被拒绝',
|
||||||
|
cameraPermissionHint: '请在浏览器设置中允许访问摄像头',
|
||||||
|
cameraInUse: '摄像头被占用',
|
||||||
|
closeOtherCameraApps: '请关闭其他使用摄像头的应用',
|
||||||
|
browserNotSupported: '浏览器不支持',
|
||||||
|
useModernBrowser: '请使用现代浏览器访问',
|
||||||
|
cameraStartFailed: '摄像头启动失败',
|
||||||
|
tryRefreshOrAlternative: '请尝试刷新页面或使用其他方式',
|
||||||
|
errorFallbackHint: '您可以:',
|
||||||
|
errorFallbackAlbum: '从相册选择二维码图片',
|
||||||
|
errorFallbackManual: '手动输入设备号',
|
||||||
|
recognizing: '正在识别...',
|
||||||
|
qrNotFound: '未识别到二维码',
|
||||||
|
recognizeFailed: '识别失败',
|
||||||
|
h5Only: '该功能仅在H5环境可用',
|
||||||
|
deviceNoRequired: '请输入设备号'
|
||||||
|
},
|
||||||
|
|
||||||
guide: {
|
guide: {
|
||||||
title: '使用指南',
|
title: '使用指南',
|
||||||
step1Title: '扫码使用',
|
step1Title: '扫码使用',
|
||||||
@@ -143,6 +184,8 @@ export default {
|
|||||||
offline: '离线',
|
offline: '离线',
|
||||||
pricingRules: '计费规则',
|
pricingRules: '计费规则',
|
||||||
capLimit: '元封顶',
|
capLimit: '元封顶',
|
||||||
|
detailBillingByUnit: '不足{unit}{minute}按{unit}{minute}计费,封顶{cap}元,持续计费至{cap}元视为买断',
|
||||||
|
detailBillingIdr: '不足1{hour}按1{hour}计费,封顶{cap},持续计费至{cap}视为买断',
|
||||||
usageInstructions: '使用说明',
|
usageInstructions: '使用说明',
|
||||||
checkBeforeUse: '请在使用前检查设备是否完好',
|
checkBeforeUse: '请在使用前检查设备是否完好',
|
||||||
autoChargeOvertime: '超出使用时间将自动按小时计费',
|
autoChargeOvertime: '超出使用时间将自动按小时计费',
|
||||||
@@ -170,6 +213,7 @@ export default {
|
|||||||
noOrderRecord: '暂无订单记录',
|
noOrderRecord: '暂无订单记录',
|
||||||
getOrderListFailed: '获取订单列表失败',
|
getOrderListFailed: '获取订单列表失败',
|
||||||
confirmCancelContent: '确定要取消此订单吗?',
|
confirmCancelContent: '确定要取消此订单吗?',
|
||||||
|
confirmDeleteContent: '确定要删除这个订单吗?',
|
||||||
orderDetail: '订单详情',
|
orderDetail: '订单详情',
|
||||||
orderNo: '订单号',
|
orderNo: '订单号',
|
||||||
orderStatus: '订单状态',
|
orderStatus: '订单状态',
|
||||||
@@ -403,6 +447,7 @@ export default {
|
|||||||
alipay: '支付宝',
|
alipay: '支付宝',
|
||||||
alipayHk: 'Alipay 香港',
|
alipayHk: 'Alipay 香港',
|
||||||
alipayId: 'Alipay 印尼',
|
alipayId: 'Alipay 印尼',
|
||||||
|
ALIPAYDANA:'Alipay DANA',
|
||||||
balance: '余额支付',
|
balance: '余额支付',
|
||||||
payNow: '立即支付',
|
payNow: '立即支付',
|
||||||
paying: '支付中...',
|
paying: '支付中...',
|
||||||
@@ -530,7 +575,7 @@ export default {
|
|||||||
received: '已签收',
|
received: '已签收',
|
||||||
detail: '详情',
|
detail: '详情',
|
||||||
recipientInfo: '收件信息',
|
recipientInfo: '收件信息',
|
||||||
recipientName: '风电者 18163601305',
|
recipientName: 'Isidaya 18163601305',
|
||||||
recipientAddress: '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623',
|
recipientAddress: '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623',
|
||||||
copyAllInfo: '一键复制全部信息',
|
copyAllInfo: '一键复制全部信息',
|
||||||
recipient: '收件人',
|
recipient: '收件人',
|
||||||
@@ -610,7 +655,7 @@ export default {
|
|||||||
termsOfService: '服务条款',
|
termsOfService: '服务条款',
|
||||||
termsAndConditions: '条款与细则',
|
termsAndConditions: '条款与细则',
|
||||||
lastUpdate: '最后更新',
|
lastUpdate: '最后更新',
|
||||||
applicableToService: '适用于"风电者"共享风扇租借服务',
|
applicableToService: '适用于"Isidaya"共享风扇租借服务',
|
||||||
footerNotice: '如对本协议有疑问,请前往"我的-客服"咨询',
|
footerNotice: '如对本协议有疑问,请前往"我的-客服"咨询',
|
||||||
footerNoticePolicy: '如对本政策有疑问,请前往"我的-客服"咨询',
|
footerNoticePolicy: '如对本政策有疑问,请前往"我的-客服"咨询',
|
||||||
|
|
||||||
@@ -646,7 +691,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
share: {
|
share: {
|
||||||
title: '风电者 - 共享风扇暖手充电宝',
|
title: 'Isidaya - 共享风扇暖手充电宝',
|
||||||
path: '/pages/index/index'
|
path: '/pages/index/index'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -858,7 +903,9 @@ export default {
|
|||||||
goods: {
|
goods: {
|
||||||
title: '商品详情',
|
title: '商品详情',
|
||||||
goodsTitle: '定制详情',
|
goodsTitle: '定制详情',
|
||||||
productName: '风电者共享风扇 + 充电宝 + 暖手宝系列-樱花粉',
|
defaultProductNameShort: 'Isidaya 2026新款',
|
||||||
|
defaultProductNameFull: 'Isidaya 2026新款风扇、充电宝、暖手宝三合一',
|
||||||
|
productName: 'Isidaya共享风扇 + 充电宝 + 暖手宝系列-樱花粉',
|
||||||
perUnit: '/个',
|
perUnit: '/个',
|
||||||
buyNow: '立即购买',
|
buyNow: '立即购买',
|
||||||
productDetail: '定制详情',
|
productDetail: '定制详情',
|
||||||
@@ -869,7 +916,7 @@ export default {
|
|||||||
temp: '智能控温',
|
temp: '智能控温',
|
||||||
charge: '快速充电'
|
charge: '快速充电'
|
||||||
},
|
},
|
||||||
description: '风电者共享风扇,集风扇、充电宝、暖手宝三合一功能。采用8000mAh大容量电池,续航持久。高效风扇设计,三档风力可调。智能控温暖手宝,冬暖夏凉。快速充电技术,支持多设备充电。樱花粉配色,时尚美观,是您出行的最佳伴侣。',
|
description: 'Isidaya共享风扇,集风扇、充电宝、暖手宝三合一功能。采用8000mAh大容量电池,续航持久。高效风扇设计,三档风力可调。智能控温暖手宝,冬暖夏凉。快速充电技术,支持多设备充电。樱花粉配色,时尚美观,是您出行的最佳伴侣。',
|
||||||
confirmPurchase: '确认购买',
|
confirmPurchase: '确认购买',
|
||||||
confirmPurchaseContent: '确认购买该商品,需支付 ¥{price}?',
|
confirmPurchaseContent: '确认购买该商品,需支付 ¥{price}?',
|
||||||
purchaseSuccess: '购买成功',
|
purchaseSuccess: '购买成功',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@
|
|||||||
"mode" : "history",
|
"mode" : "history",
|
||||||
"base" : "/"
|
"base" : "/"
|
||||||
},
|
},
|
||||||
"title" : "FDZPower"
|
"title" : "Isidaya"
|
||||||
},
|
},
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : false
|
"enable" : false
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/scan/index",
|
"path": "pages/scan/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "扫码使用",
|
"navigationBarTitleText": "",
|
||||||
"navigationBarBackgroundColor": "#000000",
|
"navigationBarBackgroundColor": "#000000",
|
||||||
"navigationBarTextStyle": "white"
|
"navigationBarTextStyle": "white"
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-20
@@ -34,12 +34,12 @@
|
|||||||
|
|
||||||
<view class="pricing-banner">
|
<view class="pricing-banner">
|
||||||
<view class="pricing-main">
|
<view class="pricing-main">
|
||||||
<text class="price-symbol">¥</text>
|
<text class="price-symbol">{{ displayCurrencySymbol }}</text>
|
||||||
<text class="price">{{ deviceFeeConfig.maxHourPrice || '5.00' }}</text>
|
<text class="price">{{ displayHourlyPrice }}</text>
|
||||||
<text class="unit">/{{ getPriceUnit() }}</text>
|
<text class="unit">/{{ getPriceUnit() }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="cap-badge">
|
<view class="cap-badge">
|
||||||
<text class="cap-text">{{ deviceInfo.depositAmount || '99' }}{{ $t('device.capLimit') }}</text>
|
<text class="cap-text">{{ displayDepositCap }}{{ $t('device.capLimit') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -129,6 +129,7 @@
|
|||||||
import {
|
import {
|
||||||
ref,
|
ref,
|
||||||
reactive,
|
reactive,
|
||||||
|
computed,
|
||||||
onMounted
|
onMounted
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import {
|
import {
|
||||||
@@ -152,7 +153,8 @@
|
|||||||
getUserPhoneNumber
|
getUserPhoneNumber
|
||||||
} from '@/util/index.js'
|
} from '@/util/index.js'
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n,
|
||||||
|
showModalI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
import DeviceDetailSkeleton from '@/components/DeviceDetailSkeleton.vue'
|
import DeviceDetailSkeleton from '@/components/DeviceDetailSkeleton.vue'
|
||||||
|
|
||||||
@@ -181,7 +183,6 @@
|
|||||||
|
|
||||||
// 生命周期 onLoad 钩子
|
// 生命周期 onLoad 钩子
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
|
|
||||||
// 普通链接二维码进入时,参数通常在 options.q(且为编码后的完整 URL)
|
// 普通链接二维码进入时,参数通常在 options.q(且为编码后的完整 URL)
|
||||||
if (!options.deviceNo && options.q) {
|
if (!options.deviceNo && options.q) {
|
||||||
const fullUrl = decodeURIComponent(options.q)
|
const fullUrl = decodeURIComponent(options.q)
|
||||||
@@ -297,7 +298,7 @@
|
|||||||
// 不立即抛出错误,而是记录问题并继续处理
|
// 不立即抛出错误,而是记录问题并继续处理
|
||||||
if (!res) {
|
if (!res) {
|
||||||
console.error('API返回数据为空')
|
console.error('API返回数据为空')
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: '数据异常',
|
title: '数据异常',
|
||||||
content: 'API返回为空',
|
content: 'API返回为空',
|
||||||
showCancel: false
|
showCancel: false
|
||||||
@@ -320,7 +321,7 @@
|
|||||||
} else {
|
} else {
|
||||||
// 记录详细信息,不抛出错误
|
// 记录详细信息,不抛出错误
|
||||||
console.warn('获取手机号响应异常:', res.msg || '未知错误')
|
console.warn('获取手机号响应异常:', res.msg || '未知错误')
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('auth.phoneError'),
|
title: t('auth.phoneError'),
|
||||||
content: `${t('common.statusCode')}: ${res.code}, ${t('common.message')}: ${res.msg || t('common.none')}`,
|
content: `${t('common.statusCode')}: ${res.code}, ${t('common.message')}: ${res.msg || t('common.none')}`,
|
||||||
showCancel: false
|
showCancel: false
|
||||||
@@ -333,7 +334,7 @@
|
|||||||
|
|
||||||
// 显示更详细的错误信息
|
// 显示更详细的错误信息
|
||||||
let errMsg = err.message || err.toString()
|
let errMsg = err.message || err.toString()
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('auth.phoneGetFailed'),
|
title: t('auth.phoneGetFailed'),
|
||||||
content: t('common.errorInfo') + ': ' + errMsg,
|
content: t('common.errorInfo') + ': ' + errMsg,
|
||||||
showCancel: false
|
showCancel: false
|
||||||
@@ -342,7 +343,7 @@
|
|||||||
} catch (outerError) {
|
} catch (outerError) {
|
||||||
// uni.hideLoading()
|
// uni.hideLoading()
|
||||||
console.error('获取手机号外部错误:', outerError)
|
console.error('获取手机号外部错误:', outerError)
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('common.unexpectedError'),
|
title: t('common.unexpectedError'),
|
||||||
content: t('common.processException') + ': ' + (outerError.message || outerError),
|
content: t('common.processException') + ': ' + (outerError.message || outerError),
|
||||||
showCancel: false
|
showCancel: false
|
||||||
@@ -413,7 +414,7 @@
|
|||||||
|
|
||||||
// 显示登录提示
|
// 显示登录提示
|
||||||
const showLoginTip = () => {
|
const showLoginTip = () => {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('common.tips'),
|
title: t('common.tips'),
|
||||||
content: t('common.loginRequired'),
|
content: t('common.loginRequired'),
|
||||||
confirmText: t('auth.goToLogin'),
|
confirmText: t('auth.goToLogin'),
|
||||||
@@ -499,6 +500,7 @@
|
|||||||
|
|
||||||
// 获取价格单位文本
|
// 获取价格单位文本
|
||||||
const getPriceUnit = () => {
|
const getPriceUnit = () => {
|
||||||
|
if (isIdrCurrency.value) return t('time.hour')
|
||||||
console.log(deviceInfo.value);
|
console.log(deviceInfo.value);
|
||||||
// 按分钟计费
|
// 按分钟计费
|
||||||
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
|
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
|
||||||
@@ -547,6 +549,9 @@
|
|||||||
|
|
||||||
// 生成计费说明文本
|
// 生成计费说明文本
|
||||||
const getPricingInfoText = () => {
|
const getPricingInfoText = () => {
|
||||||
|
if (isIdrCurrency.value) {
|
||||||
|
return `${displayCurrencySymbol.value}${displayHourlyPrice.value}/${t('time.hour')}`
|
||||||
|
}
|
||||||
const unitPrice = getBillingUnitPrice()
|
const unitPrice = getBillingUnitPrice()
|
||||||
const maxHourPrice = deviceFeeConfig.value.maxHourPrice || '5'
|
const maxHourPrice = deviceFeeConfig.value.maxHourPrice || '5'
|
||||||
|
|
||||||
@@ -562,17 +567,20 @@
|
|||||||
|
|
||||||
// 生成详细说明文本
|
// 生成详细说明文本
|
||||||
const getDetailInfoText = () => {
|
const getDetailInfoText = () => {
|
||||||
const freeMinutes = getFreeMinutes()
|
if (isIdrCurrency.value) {
|
||||||
|
const cap = `${displayCurrencySymbol.value}${displayDepositCap.value}`
|
||||||
|
return t('device.detailBillingIdr', {
|
||||||
|
hour: t('time.hour'),
|
||||||
|
cap
|
||||||
|
})
|
||||||
|
}
|
||||||
const unitMinutes = getBillingUnitMinutes()
|
const unitMinutes = getBillingUnitMinutes()
|
||||||
const depositAmount = deviceInfo.value.depositAmount || '99'
|
const depositAmount = deviceInfo.value.depositAmount || '99'
|
||||||
|
return t('device.detailBillingByUnit', {
|
||||||
// 按分钟计费
|
unit: unitMinutes,
|
||||||
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
|
minute: t('time.minute'),
|
||||||
return `不足${unitMinutes}分钟按${unitMinutes}分钟计费,封顶${depositAmount}元,持续计费至${depositAmount}元视为买断`
|
cap: depositAmount
|
||||||
}
|
})
|
||||||
|
|
||||||
// 按小时计费
|
|
||||||
return `不足${unitMinutes}分钟按${unitMinutes}分钟计费,封顶${depositAmount}元,持续计费至${depositAmount}元视为买断`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取租借按钮文本
|
// 获取租借按钮文本
|
||||||
@@ -584,6 +592,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currencyCode = computed(() => {
|
||||||
|
return (positionInfo.value?.currency || '').toUpperCase()
|
||||||
|
})
|
||||||
|
|
||||||
|
const isIdrCurrency = computed(() => currencyCode.value === 'IDR')
|
||||||
|
|
||||||
|
const displayCurrencySymbol = computed(() => (isIdrCurrency.value ? 'Rp ' : '¥'))
|
||||||
|
|
||||||
|
const displayHourlyPrice = computed(() => {
|
||||||
|
return deviceFeeConfig.value.maxHourPrice || '5.00'
|
||||||
|
})
|
||||||
|
|
||||||
|
const displayDepositCap = computed(() => {
|
||||||
|
return deviceInfo.value.depositAmount || '99'
|
||||||
|
})
|
||||||
|
|
||||||
// 提交租借订单
|
// 提交租借订单
|
||||||
const submitRentOrder = async (payWay) => {
|
const submitRentOrder = async (payWay) => {
|
||||||
try {
|
try {
|
||||||
@@ -619,7 +643,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'))
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-88
@@ -17,81 +17,26 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<view class="main-content" :style="mainContentStyle">
|
<!-- #ifndef H5 -->
|
||||||
<!-- 全屏地图组件 -->
|
<HomeMainNonH5 ref="mapRef" :mainContentStyle="mainContentStyle" :isLoading="isLoading" :userLocation="userLocation"
|
||||||
<!-- 支付宝小程序使用专用组件 -->
|
:locationPermissionDenied="locationPermissionDenied" :positionList="positionList"
|
||||||
<!-- #ifdef MP-ALIPAY -->
|
:filteredPositions="filteredPositions" :searchKeyword="searchKeyword" :bannerImages="bannerImages"
|
||||||
<MapComponentAlipay v-if="!isLoading && userLocation && !locationPermissionDenied" ref="mapRef"
|
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup"
|
||||||
:userLocation="userLocation" :positionList="positionList" :filteredPositions="filteredPositions"
|
:locationPermissionText="$t('home.locationPermissionOffTip')" :enableLocationText="$t('home.enableLocation')"
|
||||||
:searchKeyword="searchKeyword" :enableMarkers="true" :bannerImages="bannerImages"
|
:loadingLocationText="$t('common.loadingLocation')" :buyDeviceText="$t('home.buyDevice')"
|
||||||
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
:scanText="$t('home.scanToUse')" :personalCenterText="$t('home.personalCenter')" @relocate="handleRelocate"
|
||||||
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition" @mapCenterChange="onMapCenterChange"
|
||||||
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
|
@bannerClick="handleBannerClick" @guide="openGuidePopup" @enableLocation="handleEnableLocation" @buy="goToBuy"
|
||||||
<!-- #endif -->
|
@my="goMy" />
|
||||||
<!-- 非支付宝小程序使用通用组件 -->
|
|
||||||
<!-- #ifndef MP-ALIPAY -->
|
|
||||||
<MapComponent v-if="!isLoading && userLocation && !locationPermissionDenied" ref="mapRef"
|
|
||||||
:userLocation="userLocation" :positionList="positionList" :filteredPositions="filteredPositions"
|
|
||||||
:searchKeyword="searchKeyword" :enableMarkers="true" :bannerImages="bannerImages"
|
|
||||||
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
|
||||||
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
|
||||||
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
|
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 地图加载状态 -->
|
<!-- #ifdef H5 -->
|
||||||
<!-- #ifdef MP-ALIPAY -->
|
<HomeMainH5 :mainContentStyle="mainContentStyle" :bannerImages="bannerImages" :loadingText="$t('common.loading')"
|
||||||
<view v-if="!userLocation" class="location-denied-placeholder">
|
:buyDeviceText="$t('home.buyDevice')" :scanText="$t('home.scanToUse')"
|
||||||
<view class="denied-content">
|
:personalCenterText="$t('home.personalCenter')" @bannerClick="handleBannerClick" @scan="handleScan"
|
||||||
<text class="denied-text">{{ $t('home.locationPermissionOffTip') }}</text>
|
@buy="goToBuy" @my="goMy" />
|
||||||
<view class="denied-enable-btn" @click="handleEnableLocation">
|
|
||||||
<text class="denied-enable-btn-text">{{ $t('home.enableLocation') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 支付宝端:拒绝定位后直接展示提示;不展示 common.loadingLocation -->
|
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- #ifndef MP-ALIPAY -->
|
|
||||||
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
|
||||||
<view class="loading-content">
|
|
||||||
<view class="loading-spinner"></view>
|
|
||||||
<text>{{ $t('common.loadingLocation') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部操作栏:附近设备 / 扫码使用 / 我的 -->
|
|
||||||
<view class="bottom-actions">
|
|
||||||
<!-- <view class="action-btn secondary small btn-nearby" @click="showLocationList">
|
|
||||||
<view class="icon-wrap">
|
|
||||||
<image class="action-icon" src="/static/map.png" mode="aspectFit" />
|
|
||||||
</view>
|
|
||||||
<text class="action-label">{{ $t('home.nearbyDevices') }}</text>
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<view class="action-btn secondary small btn-nearby" @click="goToBuy">
|
|
||||||
<view class="icon-wrap">
|
|
||||||
<image src="/static/shop_icon.png" class="action-icon" mode="scaleToFill" lazy-load="true"></image>
|
|
||||||
</view>
|
|
||||||
<text class="action-label">{{ $t('home.buyDevice') }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="action-btn primary btn-scan" @click="handleScan">
|
|
||||||
<view class="icon-wrap">
|
|
||||||
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFill" lazy-load="true" />
|
|
||||||
</view>
|
|
||||||
<text class="primary-label">{{ $t('home.scanToUse') }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="action-btn secondary small btn-my" @click="goMy">
|
|
||||||
<view class="icon-wrap">
|
|
||||||
<image class="action-icon" src="/static/user.png" mode="aspectFit" lazy-load="true" />
|
|
||||||
</view>
|
|
||||||
<text class="action-label">{{ $t('home.personalCenter') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 场地列表弹窗组件 -->
|
<!-- 场地列表弹窗组件 -->
|
||||||
<LocationListSheet :show="showLocationPopup" :expanded="isExpanded" :positions="filteredPositions"
|
<LocationListSheet :show="showLocationPopup" :expanded="isExpanded" :positions="filteredPositions"
|
||||||
:isLoading="isLoading" :title="$t('home.nearbyDeviceLocation')" @close="hideLocationList"
|
:isLoading="isLoading" :title="$t('home.nearbyDeviceLocation')" @close="hideLocationList"
|
||||||
@@ -256,15 +201,12 @@
|
|||||||
calculateDistanceSync,
|
calculateDistanceSync,
|
||||||
testDistanceCalculation
|
testDistanceCalculation
|
||||||
} from '../../utils/mapUtils.js'
|
} from '../../utils/mapUtils.js'
|
||||||
// 同样需要使用相对路径引入组件
|
import HomeMainNonH5 from '../../components/home/HomeMainNonH5.vue'
|
||||||
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
|
import HomeMainH5 from '../../components/home/HomeMainH5.vue'
|
||||||
import MapComponent from '../../components/MapComponent.vue'
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
import MapComponentAlipay from '../../components/MapComponentAlipay.vue'
|
|
||||||
// #endif
|
|
||||||
import LocationListSheet from '../../components/LocationListSheet.vue'
|
import LocationListSheet from '../../components/LocationListSheet.vue'
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n,
|
||||||
|
showModalI18n
|
||||||
} from '../../utils/i18n.js'
|
} from '../../utils/i18n.js'
|
||||||
|
|
||||||
// 开启右上角分享菜单(仅 mp-weixin 有效)
|
// 开启右上角分享菜单(仅 mp-weixin 有效)
|
||||||
@@ -391,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 {
|
||||||
@@ -441,27 +382,40 @@
|
|||||||
appPlatform = 'ali'
|
appPlatform = 'ali'
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
const platformCandidates = []
|
||||||
|
if (appPlatform) {
|
||||||
|
platformCandidates.push(appPlatform)
|
||||||
|
}
|
||||||
|
// #ifdef H5
|
||||||
|
// H5 无固定 appPlatform,依次尝试常见值,兼容后端按平台分组配置广告位
|
||||||
|
platformCandidates.push('h5', 'wechat', 'ali')
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
let loaded = false
|
||||||
|
for (const platform of Array.from(new Set(platformCandidates))) {
|
||||||
// 调用接口获取广告内容
|
// 调用接口获取广告内容
|
||||||
const res = await getCurrentAdvertisement({
|
const res = await getCurrentAdvertisement({
|
||||||
appPlatform: appPlatform, // 微信平台
|
appPlatform: platform,
|
||||||
appType: 'user', // 用户端
|
appType: 'user',
|
||||||
pictureLocation: 'home_banner'
|
pictureLocation: 'home_banner'
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('首页广告响应:', res)
|
console.log('首页广告响应:', platform, res)
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
// 使用 imageList 字段(包含图片和链接信息)
|
|
||||||
const imageList = res.data.imageList || []
|
const imageList = res.data.imageList || []
|
||||||
if (imageList.length > 0) {
|
if (imageList.length > 0) {
|
||||||
bannerImageList.value = imageList
|
bannerImageList.value = imageList
|
||||||
// 提取图片URL用于展示
|
// 提取图片URL用于展示
|
||||||
bannerImages.value = imageList.map(item => item.imageUrl)
|
bannerImages.value = imageList.map(item => item.imageUrl)
|
||||||
} else {
|
loaded = true
|
||||||
console.warn('未获取到广告图片')
|
break
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
console.warn('获取首页广告失败:', res?.msg || '未知错误')
|
}
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
console.warn('未获取到首页广告图片')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取首页广告失败:', error)
|
console.error('获取首页广告失败:', error)
|
||||||
@@ -668,6 +622,11 @@
|
|||||||
getBannerImages()
|
getBannerImages()
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
// H5 首页不展示地图,跳过定位与附近设备加载
|
||||||
|
return
|
||||||
|
// #endif
|
||||||
|
|
||||||
// 1. 先获取用户位置
|
// 1. 先获取用户位置
|
||||||
await getUserLocationAndAddress()
|
await getUserLocationAndAddress()
|
||||||
|
|
||||||
@@ -1459,7 +1418,7 @@
|
|||||||
|
|
||||||
const expected = expectedUserPhone.value
|
const expected = expectedUserPhone.value
|
||||||
if (expected && normalizePhone(aliPhone) !== normalizePhone(expected)) {
|
if (expected && normalizePhone(aliPhone) !== normalizePhone(expected)) {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('common.tips'),
|
title: t('common.tips'),
|
||||||
content: '当前支付宝授权手机号与账号绑定手机号不一致,无法扫码租借,请更换账号或联系管理员。',
|
content: '当前支付宝授权手机号与账号绑定手机号不一致,无法扫码租借,请更换账号或联系管理员。',
|
||||||
showCancel: false
|
showCancel: false
|
||||||
@@ -1626,6 +1585,36 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h5-banner-section {
|
||||||
|
padding: 24rpx 20rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-swiper {
|
||||||
|
width: 100%;
|
||||||
|
height: 320rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h5-banner-empty {
|
||||||
|
padding: 80rpx 20rpx 0;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
/* 顶部Logo和通知栏 */
|
/* 顶部Logo和通知栏 */
|
||||||
.header-section {
|
.header-section {
|
||||||
width: 92%;
|
width: 92%;
|
||||||
|
|||||||
+175
-52
@@ -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"
|
||||||
@@ -262,7 +262,8 @@
|
|||||||
URL
|
URL
|
||||||
} from "@/config/url.js"
|
} from "@/config/url.js"
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n,
|
||||||
|
showModalI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -322,6 +323,26 @@
|
|||||||
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 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 +523,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(orderInfo.value.unitPrice)}/${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,6 +648,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = () => {
|
||||||
// 使用中状态显示"已使用",已完成状态显示"使用时长"
|
// 使用中状态显示"已使用",已完成状态显示"使用时长"
|
||||||
@@ -627,15 +666,17 @@
|
|||||||
|
|
||||||
// 获取订单费用(不含单位)
|
// 获取订单费用(不含单位)
|
||||||
const getOrderFee = () => {
|
const getOrderFee = () => {
|
||||||
let fee;
|
let fee
|
||||||
if(orderInfo.value.originalFee){
|
if (orderInfo.value.originalFee) {
|
||||||
fee = orderInfo.value.originalFee || orderInfo.value.originalFee || '0'
|
fee = orderInfo.value.originalFee || '0'
|
||||||
}else{
|
} else if (orderInfo.value.payAmount) {
|
||||||
fee = orderInfo.value.currentFee;
|
fee = orderInfo.value.payAmount
|
||||||
|
} else {
|
||||||
|
fee = orderInfo.value.currentFee
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除可能的"元"字符
|
// 移除小数位,仅显示整数金额
|
||||||
return String(fee).replace(/[元¥]/g, '')
|
return formatAmountDisplay(String(fee).replace(/[元¥]/g, ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析开始时间
|
// 解析开始时间
|
||||||
@@ -969,6 +1010,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
|
||||||
@@ -1153,7 +1195,7 @@
|
|||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
const handleCancelOrder = () => {
|
const handleCancelOrder = () => {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('order.confirmCancel'),
|
title: t('order.confirmCancel'),
|
||||||
content: t('order.confirmCancelContent'),
|
content: t('order.confirmCancelContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
@@ -1473,7 +1515,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 +1567,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 +1578,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 +1592,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 +1606,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 +1649,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 +1692,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 +1705,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 +1740,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 +1829,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 +1841,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 +1853,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 +1920,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 +1957,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 +1988,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 +2039,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 +2064,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;
|
||||||
|
|||||||
+45
-37
@@ -24,36 +24,36 @@
|
|||||||
<view class="action-item" @click.stop="chooseImage">
|
<view class="action-item" @click.stop="chooseImage">
|
||||||
<!-- <view class="action-icon">📷</view> -->
|
<!-- <view class="action-icon">📷</view> -->
|
||||||
<uv-icon name="photo" size="24" color="#fff"></uv-icon>
|
<uv-icon name="photo" size="24" color="#fff"></uv-icon>
|
||||||
<text>相册</text>
|
<text>{{ $t('scan.album') }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="action-item" @click.stop="toggleInput">
|
<view class="action-item" @click.stop="toggleInput">
|
||||||
<!-- <view class="action-icon">✏️</view> -->
|
<!-- <view class="action-icon">✏️</view> -->
|
||||||
<uv-icon name="edit-pen" size="24" color="#fff"></uv-icon>
|
<uv-icon name="edit-pen" size="24" color="#fff"></uv-icon>
|
||||||
<text>手动输入</text>
|
<text>{{ $t('scan.manualInput') }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
<view class="action-item" @click.stop="goBack">
|
<view class="action-item" @click.stop="goBack">
|
||||||
<!-- <view class="action-icon">←</view> -->
|
<!-- <view class="action-icon">←</view> -->
|
||||||
<uv-icon name="arrow-left" size="24" color="#fff"></uv-icon>
|
<uv-icon name="arrow-left" size="24" color="#fff"></uv-icon>
|
||||||
<text>返回</text>
|
<text>{{ $t('common.back') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 手动输入弹窗 -->
|
<!-- 手动输入弹窗 -->
|
||||||
<uv-popup ref="inputPopup" mode="center" round="16" :closeOnClickOverlay="true">
|
<uv-popup ref="inputPopup" mode="center" round="16" :closeOnClickOverlay="true">
|
||||||
<view class="input-dialog">
|
<view class="input-dialog">
|
||||||
<view class="dialog-title">手动输入设备号</view>
|
<view class="dialog-title">{{ $t('scan.manualInputTitle') }}</view>
|
||||||
<input
|
<input
|
||||||
v-model="manualDeviceNo"
|
v-model="manualDeviceNo"
|
||||||
placeholder="请输入设备上的编号"
|
:placeholder="$t('scan.deviceNoPlaceholder')"
|
||||||
class="device-input"
|
class="device-input"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
<view class="dialog-btns">
|
<view class="dialog-btns">
|
||||||
<button class="cancel-btn" @click="closeInput">取消</button>
|
<button class="cancel-btn" @click="closeInput">{{ $t('common.cancel') }}</button>
|
||||||
<button class="confirm-btn" @click="confirmManualInput">确定</button>
|
<button class="confirm-btn" @click="confirmManualInput">{{ $t('common.confirm') }}</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</uv-popup>
|
</uv-popup>
|
||||||
@@ -64,10 +64,13 @@
|
|||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
import { getQueryString } from '../../util/index.js';
|
import { getQueryString } from '../../util/index.js';
|
||||||
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode';
|
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode';
|
||||||
|
import { useI18n, showModalI18n } from '@/utils/i18n.js';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const inputPopup = ref(null);
|
const inputPopup = ref(null);
|
||||||
const manualDeviceNo = ref('');
|
const manualDeviceNo = ref('');
|
||||||
const tipText = ref('正在初始化...');
|
const tipText = ref(t('scan.initializing'));
|
||||||
const scanning = ref(false);
|
const scanning = ref(false);
|
||||||
const hasFlash = ref(false);
|
const hasFlash = ref(false);
|
||||||
const flashOn = ref(false);
|
const flashOn = ref(false);
|
||||||
@@ -93,12 +96,12 @@ const getScanConfig = () => ({
|
|||||||
// 初始化扫码
|
// 初始化扫码
|
||||||
const initScan = async () => {
|
const initScan = async () => {
|
||||||
try {
|
try {
|
||||||
tipText.value = '正在初始化...';
|
tipText.value = t('scan.initializing');
|
||||||
console.log('=== 开始初始化扫码 ===');
|
console.log('=== 开始初始化扫码 ===');
|
||||||
|
|
||||||
// 检查浏览器支持
|
// 检查浏览器支持
|
||||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||||
throw new Error('您的浏览器不支持摄像头访问');
|
throw new Error(t('scan.browserNotSupportCamera'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待 DOM 渲染
|
// 等待 DOM 渲染
|
||||||
@@ -107,7 +110,7 @@ const initScan = async () => {
|
|||||||
// 检查容器元素
|
// 检查容器元素
|
||||||
const readerElement = document.getElementById('qr-reader');
|
const readerElement = document.getElementById('qr-reader');
|
||||||
if (!readerElement) {
|
if (!readerElement) {
|
||||||
throw new Error('扫码容器元素未找到');
|
throw new Error(t('scan.containerNotFound'));
|
||||||
}
|
}
|
||||||
console.log('✓ 扫码容器元素已找到');
|
console.log('✓ 扫码容器元素已找到');
|
||||||
|
|
||||||
@@ -130,10 +133,10 @@ const initScan = async () => {
|
|||||||
const startScanning = async () => {
|
const startScanning = async () => {
|
||||||
try {
|
try {
|
||||||
if (!html5QrCode) {
|
if (!html5QrCode) {
|
||||||
throw new Error('Html5Qrcode 实例不存在');
|
throw new Error(t('scan.initFailed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
tipText.value = '正在启动摄像头...';
|
tipText.value = t('scan.startingCamera');
|
||||||
console.log('=== 开始启动扫描 ===');
|
console.log('=== 开始启动扫描 ===');
|
||||||
console.log('html5QrCode 实例:', html5QrCode);
|
console.log('html5QrCode 实例:', html5QrCode);
|
||||||
console.log('html5QrCode.start 方法:', typeof html5QrCode.start);
|
console.log('html5QrCode.start 方法:', typeof html5QrCode.start);
|
||||||
@@ -155,7 +158,7 @@ const startScanning = async () => {
|
|||||||
console.log('start() 调用结果:', startResult);
|
console.log('start() 调用结果:', startResult);
|
||||||
|
|
||||||
scanning.value = true;
|
scanning.value = true;
|
||||||
tipText.value = '将二维码放入框内扫描';
|
tipText.value = t('scan.alignQRCode');
|
||||||
console.log('✅ 扫描已成功启动');
|
console.log('✅ 扫描已成功启动');
|
||||||
|
|
||||||
// 延迟隐藏默认UI
|
// 延迟隐藏默认UI
|
||||||
@@ -184,7 +187,7 @@ const startScanning = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
scanning.value = true;
|
scanning.value = true;
|
||||||
tipText.value = '将二维码放入框内扫描';
|
tipText.value = t('scan.alignQRCode');
|
||||||
console.log('✅ 使用前置摄像头启动成功');
|
console.log('✅ 使用前置摄像头启动成功');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -215,14 +218,14 @@ const startScanning = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
scanning.value = true;
|
scanning.value = true;
|
||||||
tipText.value = '将二维码放入框内扫描';
|
tipText.value = t('scan.alignQRCode');
|
||||||
console.log('✅ 使用默认摄像头启动成功');
|
console.log('✅ 使用默认摄像头启动成功');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hideDefaultUI();
|
hideDefaultUI();
|
||||||
}, 200);
|
}, 200);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('未找到可用的摄像头');
|
throw new Error(t('scan.noCameraFound'));
|
||||||
}
|
}
|
||||||
} catch (err3) {
|
} catch (err3) {
|
||||||
console.error('❌ 所有方式都失败:', err3);
|
console.error('❌ 所有方式都失败:', err3);
|
||||||
@@ -324,43 +327,45 @@ const stopScan = async () => {
|
|||||||
const handleInitError = (err) => {
|
const handleInitError = (err) => {
|
||||||
console.error('处理初始化错误:', err);
|
console.error('处理初始化错误:', err);
|
||||||
|
|
||||||
let errMsg = '初始化失败';
|
let errMsg = t('scan.initFailed');
|
||||||
let errDetail = '';
|
let errDetail = '';
|
||||||
|
|
||||||
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
||||||
errMsg = '摄像头权限被拒绝';
|
errMsg = t('scan.cameraPermissionDenied');
|
||||||
errDetail = '请在浏览器设置中允许访问摄像头';
|
errDetail = t('scan.cameraPermissionHint');
|
||||||
}
|
}
|
||||||
else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
|
else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
|
||||||
errMsg = '未找到可用的摄像头';
|
errMsg = t('scan.noCameraFound');
|
||||||
errDetail = '请确保设备有摄像头';
|
errDetail = t('scan.ensureCameraExists');
|
||||||
}
|
}
|
||||||
else if (err.name === 'NotReadableError' || err.name === 'TrackStartError') {
|
else if (err.name === 'NotReadableError' || err.name === 'TrackStartError') {
|
||||||
errMsg = '摄像头被占用';
|
errMsg = t('scan.cameraInUse');
|
||||||
errDetail = '请关闭其他使用摄像头的应用';
|
errDetail = t('scan.closeOtherCameraApps');
|
||||||
}
|
}
|
||||||
else if (err.name === 'NotSupportedError') {
|
else if (err.name === 'NotSupportedError') {
|
||||||
errMsg = '浏览器不支持';
|
errMsg = t('scan.browserNotSupported');
|
||||||
errDetail = '请使用现代浏览器访问';
|
errDetail = t('scan.useModernBrowser');
|
||||||
}
|
}
|
||||||
// else if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
|
// else if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
|
||||||
// errMsg = '需要 HTTPS 环境';
|
// errMsg = '需要 HTTPS 环境';
|
||||||
// errDetail = '摄像头功能需要在安全环境下使用';
|
// errDetail = '摄像头功能需要在安全环境下使用';
|
||||||
// }
|
// }
|
||||||
else {
|
else {
|
||||||
errMsg = err.message || '摄像头启动失败';
|
errMsg = err.message || t('scan.cameraStartFailed');
|
||||||
errDetail = '请尝试刷新页面或使用其他方式';
|
errDetail = t('scan.tryRefreshOrAlternative');
|
||||||
}
|
}
|
||||||
|
|
||||||
tipText.value = errMsg;
|
tipText.value = errMsg;
|
||||||
|
|
||||||
|
const fallbackContent = `${errDetail}\n\n${t('scan.errorFallbackHint')}\n1. ${t('scan.errorFallbackAlbum')}\n2. ${t('scan.errorFallbackManual')}`;
|
||||||
|
|
||||||
// 显示错误提示,提供备选方案
|
// 显示错误提示,提供备选方案
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: errMsg,
|
title: errMsg,
|
||||||
content: errDetail + '\n\n您可以:\n1. 从相册选择二维码图片\n2. 手动输入设备号',
|
content: fallbackContent,
|
||||||
showCancel: true,
|
showCancel: true,
|
||||||
cancelText: '返回',
|
cancelText: t('common.back'),
|
||||||
confirmText: '手动输入',
|
confirmText: t('scan.manualInput'),
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
toggleInput();
|
toggleInput();
|
||||||
@@ -383,7 +388,7 @@ const chooseImage = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showLoading({ title: '正在识别...' });
|
uni.showLoading({ title: t('scan.recognizing') });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 先停止摄像头扫描
|
// 先停止摄像头扫描
|
||||||
@@ -422,7 +427,7 @@ const chooseImage = async () => {
|
|||||||
// 不要立即返回,等待首页处理完成
|
// 不要立即返回,等待首页处理完成
|
||||||
console.log('图片识别结果已发送,等待首页处理...');
|
console.log('图片识别结果已发送,等待首页处理...');
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '未识别到二维码', icon: 'none' });
|
uni.showToast({ title: t('scan.qrNotFound'), icon: 'none' });
|
||||||
// 识别失败,重新启动摄像头扫描
|
// 识别失败,重新启动摄像头扫描
|
||||||
if (wasScanning) {
|
if (wasScanning) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
@@ -433,7 +438,7 @@ const chooseImage = async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('图片识别失败:', err);
|
console.error('图片识别失败:', err);
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({ title: '识别失败', icon: 'none' });
|
uni.showToast({ title: t('scan.recognizeFailed'), icon: 'none' });
|
||||||
// 识别失败,重新启动摄像头扫描
|
// 识别失败,重新启动摄像头扫描
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -452,7 +457,7 @@ const chooseImage = async () => {
|
|||||||
count: 1,
|
count: 1,
|
||||||
sourceType: ['album'],
|
sourceType: ['album'],
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.showToast({ title: '该功能仅在H5环境可用', icon: 'none' });
|
uni.showToast({ title: t('scan.h5Only'), icon: 'none' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// #endif
|
// #endif
|
||||||
@@ -477,7 +482,7 @@ const confirmManualInput = () => {
|
|||||||
const deviceNo = manualDeviceNo.value.trim();
|
const deviceNo = manualDeviceNo.value.trim();
|
||||||
|
|
||||||
if (!deviceNo) {
|
if (!deviceNo) {
|
||||||
uni.showToast({ title: '请输入设备号', icon: 'none' });
|
uni.showToast({ title: t('scan.deviceNoRequired'), icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,6 +513,9 @@ const goBack = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('扫码页面已挂载');
|
console.log('扫码页面已挂载');
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: t('scan.title')
|
||||||
|
});
|
||||||
|
|
||||||
// 延迟初始化,确保 DOM 已渲染
|
// 延迟初始化,确保 DOM 已渲染
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 91 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -143,7 +143,7 @@
|
|||||||
|
|
||||||
<view class="popup-footer">
|
<view class="popup-footer">
|
||||||
<view class="confirm-btn" @click="confirmSku">
|
<view class="confirm-btn" @click="confirmSku">
|
||||||
<text>确定</text>
|
<text>{{ $t('common.confirm') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
|
|
||||||
<view class="popup-footer">
|
<view class="popup-footer">
|
||||||
<view class="confirm-btn" @click="confirmAliRegion">
|
<view class="confirm-btn" @click="confirmAliRegion">
|
||||||
<text>确定</text>
|
<text>{{ $t('common.confirm') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
class="product-image"
|
class="product-image"
|
||||||
></image>
|
></image>
|
||||||
<view class="product-info">
|
<view class="product-info">
|
||||||
<view class="product-name">{{ orderDetail.productName || orderDetail.deviceName || '风电者2026新款风扇、充电宝、暖手宝三合一' }}</view>
|
<view class="product-name">{{ orderDetail.productName || orderDetail.deviceName || $t('goods.defaultProductNameFull') }}</view>
|
||||||
<view class="product-style">款式:{{ orderDetail.optionName || orderDetail.style || '标准' }}</view>
|
<view class="product-style">款式:{{ orderDetail.optionName || orderDetail.style || '标准' }}</view>
|
||||||
<view class="product-price">¥{{ orderDetail.price || orderDetail.totalAmount }}</view>
|
<view class="product-price">¥{{ orderDetail.price || orderDetail.totalAmount }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
createWxPayment
|
createWxPayment
|
||||||
} from '../../config/api/order.js';
|
} from '../../config/api/order.js';
|
||||||
// import { getSystemParamByKey } from '../../config/api/system.js';
|
// import { getSystemParamByKey } from '../../config/api/system.js';
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -409,9 +409,9 @@
|
|||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
const onCancelOrder = () => {
|
const onCancelOrder = () => {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: '提示',
|
title: t('common.tips'),
|
||||||
content: '确定要取消这个订单吗?',
|
content: t('order.confirmCancelContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
try {
|
try {
|
||||||
@@ -450,9 +450,9 @@
|
|||||||
|
|
||||||
// 删除订单
|
// 删除订单
|
||||||
const onDeleteOrder = () => {
|
const onDeleteOrder = () => {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: '提示',
|
title: t('common.tips'),
|
||||||
content: '确定要删除这个订单吗?',
|
content: t('order.confirmDeleteContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
import {
|
import {
|
||||||
URL
|
URL
|
||||||
} from '../../config/url.js';
|
} from '../../config/url.js';
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -494,7 +494,7 @@
|
|||||||
// 取消订单
|
// 取消订单
|
||||||
const handleCancelOrder = async (order) => {
|
const handleCancelOrder = async (order) => {
|
||||||
try {
|
try {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('order.confirmCancel'),
|
title: t('order.confirmCancel'),
|
||||||
content: t('order.confirmCancelContent'),
|
content: t('order.confirmCancelContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
@@ -540,9 +540,9 @@
|
|||||||
|
|
||||||
// 处理删除订单
|
// 处理删除订单
|
||||||
const handleDeleteOrder = (order) => {
|
const handleDeleteOrder = (order) => {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: '提示',
|
title: t('common.tips'),
|
||||||
content: '确定要删除这个订单吗?',
|
content: t('order.confirmDeleteContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+66
-44
@@ -45,17 +45,14 @@
|
|||||||
import {
|
import {
|
||||||
getOrderList,
|
getOrderList,
|
||||||
queryById,
|
queryById,
|
||||||
|
getOrderByOrderNo,
|
||||||
getOrderByOrderNoScorePayStatus,
|
getOrderByOrderNoScorePayStatus,
|
||||||
cancelOrder,
|
cancelOrder
|
||||||
createWxPayment
|
|
||||||
} from '../../config/api/order.js';
|
} from '../../config/api/order.js';
|
||||||
import {
|
import {
|
||||||
updateUserBalance
|
getDeviceInfo
|
||||||
} from '../../config/api/user.js';
|
} from '../../config/api/device.js';
|
||||||
import {
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
URL
|
|
||||||
} from '../../config/url.js';
|
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -283,61 +280,86 @@
|
|||||||
navigateToOrderDetail(order);
|
navigateToOrderDetail(order);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 立即支付
|
/**
|
||||||
|
* 待支付订单:与设备租借押金流程一致,进入支付详情页(微信/支付宝/H5-Antom/DANA 等在支付页内选择并完成)
|
||||||
|
*/
|
||||||
const handlePayment = async (order) => {
|
const handlePayment = async (order) => {
|
||||||
|
const orderNo = order.orderNo
|
||||||
|
const orderId = order.orderId || order.orderNo
|
||||||
|
if (!orderId && !orderNo) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('order.orderInfoMissing'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.processing')
|
title: t('common.loading')
|
||||||
});
|
})
|
||||||
|
|
||||||
// 调用后端创建微信支付订单接口
|
let od = null
|
||||||
const res = await createWxPayment(order.orderNo);
|
if (orderNo) {
|
||||||
|
const res = await getOrderByOrderNo(orderNo)
|
||||||
|
if (res && res.code === 200 && res.data) {
|
||||||
|
od = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!od && orderId) {
|
||||||
|
const res = await queryById(orderId)
|
||||||
|
if (res && res.code === 200 && res.data) {
|
||||||
|
od = res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (res && res.code === 200) {
|
const idForPay = String(od?.orderId ?? orderId ?? orderNo ?? '')
|
||||||
const payParams = res.data;
|
const qsParts = [`orderId=${encodeURIComponent(idForPay)}`]
|
||||||
|
|
||||||
// 调用微信支付
|
if (od) {
|
||||||
await uni.requestPayment({
|
const deposit = parseFloat(od.depositAmount)
|
||||||
...payParams,
|
const packagePrice = parseFloat(od.unitPrice)
|
||||||
success: async () => {
|
if (Number.isFinite(packagePrice)) {
|
||||||
uni.showToast({
|
qsParts.push(`packagePrice=${packagePrice}`)
|
||||||
title: t('payment.paymentSuccess'),
|
}
|
||||||
icon: 'success'
|
if (Number.isFinite(deposit)) {
|
||||||
});
|
const totalAmount = deposit.toFixed(2)
|
||||||
|
qsParts.push(`totalAmount=${encodeURIComponent(totalAmount)}`)
|
||||||
|
qsParts.push(`depositAmount=${encodeURIComponent(String(deposit))}`)
|
||||||
|
}
|
||||||
|
|
||||||
// 更新用户余额
|
const deviceNo = od.deviceNo || order.deviceId
|
||||||
|
if (deviceNo) {
|
||||||
try {
|
try {
|
||||||
await updateUserBalance(order.orderId || order.orderNo);
|
const devRes = await getDeviceInfo(deviceNo)
|
||||||
} catch (error) {
|
const feeCfg = devRes?.data?.device?.feeConfig
|
||||||
console.warn('更新用户余额失败:', error);
|
if (feeCfg) {
|
||||||
|
qsParts.push(`feeConfig=${encodeURIComponent(feeCfg)}`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取设备 feeConfig 失败,支付页将仅依赖订单信息:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新订单列表
|
uni.hideLoading()
|
||||||
await loadOrderList(orderStatusTabs[currentTab.value].status);
|
uni.navigateTo({
|
||||||
},
|
url: `/subPackages/order/payment?${qsParts.join('&')}`
|
||||||
fail: (err) => {
|
})
|
||||||
console.error('支付失败:', err);
|
|
||||||
throw new Error(t('payment.paymentFailedRetry'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(res?.msg || '创建支付订单失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.hideLoading();
|
uni.hideLoading()
|
||||||
|
console.error('跳转支付页失败:', error)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: error.message || t('payment.paymentFailed'),
|
title: error?.message || t('payment.paymentFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
const handleCancelOrder = async (order) => {
|
const handleCancelOrder = async (order) => {
|
||||||
try {
|
try {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('order.confirmCancel'),
|
title: t('order.confirmCancel'),
|
||||||
content: t('order.confirmCancelContent'),
|
content: t('order.confirmCancelContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -111,7 +115,8 @@
|
|||||||
updateUserBalance
|
updateUserBalance
|
||||||
} from '@/config/api/user.js'
|
} from '@/config/api/user.js'
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n,
|
||||||
|
showModalI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -124,6 +129,7 @@
|
|||||||
const deviceInfo = ref(null)
|
const deviceInfo = ref(null)
|
||||||
const passedTotalAmount = ref(null)
|
const passedTotalAmount = ref(null)
|
||||||
const passedDepositAmount = ref(null)
|
const passedDepositAmount = ref(null)
|
||||||
|
const currencyCode = ref('USD')
|
||||||
|
|
||||||
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
||||||
const paymentMethods = ref([])
|
const paymentMethods = ref([])
|
||||||
@@ -149,6 +155,14 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
const totalAmount = computed(() => {
|
const totalAmount = computed(() => {
|
||||||
|
if (currencyCode.value === 'IDR') {
|
||||||
|
const raw =
|
||||||
|
passedTotalAmount.value != null && passedTotalAmount.value !== ''
|
||||||
|
? passedTotalAmount.value
|
||||||
|
: orderInfo.value.deposit
|
||||||
|
const num = Number(String(raw ?? '').replace(/[^\d.-]/g, ''))
|
||||||
|
return Number.isFinite(num) ? String(Math.trunc(num)) : String(raw || '0')
|
||||||
|
}
|
||||||
if (passedTotalAmount.value !== null) {
|
if (passedTotalAmount.value !== null) {
|
||||||
return parseFloat(passedTotalAmount.value).toFixed(2);
|
return parseFloat(passedTotalAmount.value).toFixed(2);
|
||||||
}
|
}
|
||||||
@@ -156,7 +170,10 @@
|
|||||||
return deposit.toFixed(2)
|
return deposit.toFixed(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
const currencySymbol = computed(() => 'USD')
|
const currencySymbol = computed(() => {
|
||||||
|
if (currencyCode.value === 'IDR') return 'Rp'
|
||||||
|
return 'USD'
|
||||||
|
})
|
||||||
|
|
||||||
// 加载订单信息
|
// 加载订单信息
|
||||||
const loadOrderInfo = async () => {
|
const loadOrderInfo = async () => {
|
||||||
@@ -244,9 +261,10 @@
|
|||||||
const res = await getDeviceInfo(deviceNo.value);
|
const res = await getDeviceInfo(deviceNo.value);
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
deviceInfo.value = res.data.device;
|
deviceInfo.value = res.data.device;
|
||||||
|
currencyCode.value = (res.data?.position?.currency || currencyCode.value || 'USD').toUpperCase()
|
||||||
|
|
||||||
if (deviceInfo.value && deviceInfo.value.depositAmount) {
|
if (deviceInfo.value && deviceInfo.value.depositAmount) {
|
||||||
orderInfo.value.deposit = deviceInfo.value.depositAmount;
|
orderInfo.value.deposit = deviceInfo.value.depositAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -309,13 +327,18 @@
|
|||||||
// }
|
// }
|
||||||
// 每条选项的 paymentMethodType 必须唯一下发到 Antom 的 paymentType 参数,否则 v-for 的 key 与单选态会异常
|
// 每条选项的 paymentMethodType 必须唯一下发到 Antom 的 paymentType 参数,否则 v-for 的 key 与单选态会异常
|
||||||
methods.push({
|
methods.push({
|
||||||
paymentMethodType: 'ALIPAY_HK',
|
paymentMethodType: 'ALIPAY_DANA',
|
||||||
paymentMethodName: t('payment.alipayHk')
|
paymentMethodName: t('payment.ALIPAYDANA')
|
||||||
})
|
|
||||||
methods.push({
|
|
||||||
paymentMethodType: 'ALIPAY_ID',
|
|
||||||
paymentMethodName: t('payment.alipayId')
|
|
||||||
})
|
})
|
||||||
|
// methods.push({
|
||||||
|
// paymentMethodType: 'ALIPAY_HK',
|
||||||
|
// paymentMethodName: t('payment.alipayHk')
|
||||||
|
// })
|
||||||
|
// methods.push({
|
||||||
|
// paymentMethodType: 'ALIPAY_ID',
|
||||||
|
// paymentMethodName: t('payment.alipayId')
|
||||||
|
// })
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取 Antom 支付方式失败:', error);
|
console.error('获取 Antom 支付方式失败:', error);
|
||||||
}
|
}
|
||||||
@@ -492,6 +515,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCancelOrder = () => {
|
||||||
|
if (!orderId.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('order.orderNotExist'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showModalI18n({
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -892,6 +961,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>
|
||||||
@@ -44,7 +44,7 @@ import { onShow } from '@dcloudio/uni-app'
|
|||||||
import { getUserInfo } from '@/util/index.js'
|
import { getUserInfo } from '@/util/index.js'
|
||||||
import { withdrawDeposit } from '@/config/api/user.js'
|
import { withdrawDeposit } from '@/config/api/user.js'
|
||||||
import { queryById } from '@/config/api/order.js'
|
import { queryById } from '@/config/api/order.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ const handleWithdraw = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('deposit.confirmWithdraw'),
|
title: t('deposit.confirmWithdraw'),
|
||||||
content: t('deposit.withdrawDesc'),
|
content: t('deposit.withdrawDesc'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
@@ -158,7 +158,7 @@ const handleWithdraw = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('deposit.withdrawFailed'),
|
title: t('deposit.withdrawFailed'),
|
||||||
content: errorMessage,
|
content: errorMessage,
|
||||||
showCancel: false
|
showCancel: false
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = 'WindPower'
|
const brandName = 'Isidaya'
|
||||||
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
const disputeVenue = 'the location of the platform'
|
const disputeVenue = 'the location of the platform'
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = '风电者'
|
const brandName = 'Isidaya'
|
||||||
const companyName = '深圳乐慕智云科技有限公司'
|
const companyName = '深圳乐慕智云科技有限公司'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
const disputeVenue = '平台所在地'
|
const disputeVenue = '平台所在地'
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = 'WindPower'
|
const brandName = 'Isidaya'
|
||||||
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = '风电者'
|
const brandName = 'Isidaya'
|
||||||
const companyName = '深圳乐慕智云科技有限公司'
|
const companyName = '深圳乐慕智云科技有限公司'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Terms & Conditions
|
||||||
|
|
||||||
|
**Last Update**: 2025-02-05
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Applicable Law
|
||||||
|
|
||||||
|
These Terms of Service are governed by the laws of the People's Republic of China. By using this service, you agree to be bound by Chinese law. Any disputes arising from this service shall first be resolved through friendly negotiation; if negotiation fails, either party may file a lawsuit with the People's Court having jurisdiction over the location of the service provider.
|
||||||
|
|
||||||
|
## Payment Methods
|
||||||
|
|
||||||
|
We support multiple payment methods, including but not limited to: WeChat Pay, Alipay, WeChat Pay Score deposit-free, etc. Users need to complete the payment process before using the service. After successful payment, the system will automatically unlock the device for user access. All payment transactions are conducted through secure encrypted channels to ensure user fund security.
|
||||||
|
|
||||||
|
## Refund Policy
|
||||||
|
|
||||||
|
1. **Deposit Refund**: After returning the device, the deposit will be automatically refunded to the original payment account after deducting the corresponding rental fee, expected to arrive within 0-7 business days.
|
||||||
|
2. **Order Cancellation**: Unused orders can be cancelled before use begins, and the deposit will be fully refunded.
|
||||||
|
3. **Exception Refund**: In case of special circumstances such as device failure, users can apply for a refund, which we will process within 3-5 business days after verification.
|
||||||
|
4. **Membership Cards/Coupons**: Purchased membership cards and coupons generally do not support refunds. Please contact customer service for special cases.
|
||||||
|
|
||||||
|
## Service Terms
|
||||||
|
|
||||||
|
When using this service, users should comply with the following regulations:
|
||||||
|
|
||||||
|
1. Take good care of the rented equipment and do not intentionally damage or privately occupy it;
|
||||||
|
2. Return the equipment on time to avoid additional charges;
|
||||||
|
3. Do not use the equipment for illegal purposes;
|
||||||
|
4. If equipment failure is found, contact customer service promptly.
|
||||||
|
|
||||||
|
Violation of the above regulations may result in service termination and liability.
|
||||||
|
|
||||||
|
## Liability Limitation
|
||||||
|
|
||||||
|
To the maximum extent permitted by law, we are not liable for any indirect, incidental, special, or consequential damages arising from the use or inability to use this service. Our total liability shall not exceed the fees paid by users for using this service. We are not responsible for service interruptions or delays caused by force majeure, network failures, third-party reasons, etc.
|
||||||
|
|
||||||
|
## Dispute Resolution
|
||||||
|
|
||||||
|
If users have any questions or disputes about the service, please first contact us through customer service channels. We will respond within 24 hours of receiving feedback and negotiate a resolution as soon as possible. If negotiation fails, both parties agree to submit the dispute to the People's Court with jurisdiction over the location of the service provider for resolution through litigation. During the dispute resolution period, both parties should continue to perform the undisputed terms of this agreement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you have questions about this agreement, please go to **My → Customer Service**.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Syarat & Ketentuan
|
||||||
|
|
||||||
|
**Pembaruan Terakhir**: 2025-02-05
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hukum yang Berlaku
|
||||||
|
|
||||||
|
Ketentuan Layanan ini diatur oleh hukum Republik Rakyat Tiongkok. Dengan menggunakan layanan ini, Anda setuju untuk terikat oleh hukum Tiongkok. Setiap perselisihan yang timbul dari layanan ini harus diselesaikan terlebih dahulu melalui negosiasi bersahabat; jika negosiasi gagal, salah satu pihak dapat mengajukan gugatan ke Pengadilan Rakyat yang memiliki yurisdiksi atas lokasi penyedia layanan.
|
||||||
|
|
||||||
|
## Metode Pembayaran
|
||||||
|
|
||||||
|
Kami mendukung berbagai metode pembayaran, termasuk namun tidak terbatas pada: WeChat Pay, Alipay, WeChat Pay Score tanpa deposit, dll. Pengguna perlu menyelesaikan proses pembayaran sebelum menggunakan layanan. Setelah pembayaran berhasil, sistem akan secara otomatis membuka kunci perangkat untuk akses pengguna. Semua transaksi pembayaran dilakukan melalui saluran terenkripsi yang aman untuk memastikan keamanan dana pengguna.
|
||||||
|
|
||||||
|
## Kebijakan Pengembalian Dana
|
||||||
|
|
||||||
|
1. **Pengembalian Deposit**: Setelah mengembalikan perangkat, deposit akan secara otomatis dikembalikan ke akun pembayaran asli setelah dikurangi biaya sewa yang sesuai, diperkirakan tiba dalam 0-7 hari kerja.
|
||||||
|
2. **Pembatalan Pesanan**: Pesanan yang tidak digunakan dapat dibatalkan sebelum penggunaan dimulai, dan deposit akan dikembalikan sepenuhnya.
|
||||||
|
3. **Pengembalian Dana Pengecualian**: Dalam kasus keadaan khusus seperti kegagalan perangkat, pengguna dapat mengajukan pengembalian dana, yang akan kami proses dalam 3-5 hari kerja setelah verifikasi.
|
||||||
|
4. **Kartu Keanggotaan/Kupon**: Kartu keanggotaan dan kupon yang dibeli umumnya tidak mendukung pengembalian dana. Silakan hubungi layanan pelanggan untuk kasus khusus.
|
||||||
|
|
||||||
|
## Ketentuan Layanan
|
||||||
|
|
||||||
|
Saat menggunakan layanan ini, pengguna harus mematuhi peraturan berikut:
|
||||||
|
|
||||||
|
1. Jaga peralatan yang disewa dengan baik dan jangan sengaja merusak atau memilikinya secara pribadi;
|
||||||
|
2. Kembalikan peralatan tepat waktu untuk menghindari biaya tambahan;
|
||||||
|
3. Jangan gunakan peralatan untuk tujuan ilegal;
|
||||||
|
4. Jika ditemukan kegagalan peralatan, hubungi layanan pelanggan segera.
|
||||||
|
|
||||||
|
Pelanggaran terhadap peraturan di atas dapat mengakibatkan penghentian layanan dan tanggung jawab.
|
||||||
|
|
||||||
|
## Batasan Tanggung Jawab
|
||||||
|
|
||||||
|
Sejauh diizinkan oleh hukum, kami tidak bertanggung jawab atas kerusakan tidak langsung, insidental, khusus, atau konsekuensial yang timbul dari penggunaan atau ketidakmampuan menggunakan layanan ini. Total tanggung jawab kami tidak akan melebihi biaya yang dibayarkan oleh pengguna untuk menggunakan layanan ini. Kami tidak bertanggung jawab atas gangguan atau penundaan layanan yang disebabkan oleh force majeure, kegagalan jaringan, alasan pihak ketiga, dll.
|
||||||
|
|
||||||
|
## Penyelesaian Sengketa
|
||||||
|
|
||||||
|
Jika pengguna memiliki pertanyaan atau perselisihan tentang layanan, silakan hubungi kami terlebih dahulu melalui saluran layanan pelanggan. Kami akan merespons dalam 24 jam setelah menerima umpan balik dan bernegosiasi untuk penyelesaian sesegera mungkin. Jika negosiasi gagal, kedua belah pihak setuju untuk menyerahkan perselisihan ke Pengadilan Rakyat dengan yurisdiksi atas lokasi penyedia layanan untuk penyelesaian melalui litigasi. Selama periode penyelesaian sengketa, kedua belah pihak harus terus melaksanakan ketentuan yang tidak dipersengketakan dari perjanjian ini.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Jika ada pertanyaan tentang perjanjian ini, harap pergi ke **Saya → Layanan Pelanggan** untuk konsultasi.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# 条款与细则
|
||||||
|
|
||||||
|
**最后更新**: 2025-02-05
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 适用法律
|
||||||
|
|
||||||
|
本服务条款受中华人民共和国法律管辖。用户使用本服务即表示同意接受中国法律的约束。任何因本服务引起的争议,应首先通过友好协商解决;协商不成的,任何一方均可向服务提供方所在地有管辖权的人民法院提起诉讼。
|
||||||
|
|
||||||
|
## 支付方式
|
||||||
|
|
||||||
|
我们支持多种支付方式,包括但不限于:微信支付、支付宝、微信支付分免押金等。用户在使用服务前需完成支付流程。支付成功后,系统将自动开启设备供用户使用。所有支付交易均通过安全加密通道进行,确保用户资金安全。
|
||||||
|
|
||||||
|
## 退款介绍
|
||||||
|
|
||||||
|
1. 押金退款:归还设备后,押金将在扣除相应租金后自动退还至原支付账户,预计0-7个工作日到账。
|
||||||
|
2. 订单取消:未使用的订单可在开始使用前取消,押金将全额退还。
|
||||||
|
3. 异常退款:如遇设备故障等特殊情况,用户可申请退款,我们将在核实后3-5个工作日内处理。
|
||||||
|
4. 会员卡/优惠券:已购买的会员卡和优惠券一般不支持退款,特殊情况请联系客服处理。
|
||||||
|
|
||||||
|
## 服务条款
|
||||||
|
|
||||||
|
用户在使用本服务时,应遵守以下规定:
|
||||||
|
|
||||||
|
1. 妥善保管租借的设备,不得故意损坏或私自占有;
|
||||||
|
2. 按时归还设备,避免产生额外费用;
|
||||||
|
3. 不得将设备用于非法用途;
|
||||||
|
4. 如发现设备故障,应及时联系客服处理。
|
||||||
|
|
||||||
|
违反上述规定的,我们有权终止服务并追究相应责任。
|
||||||
|
|
||||||
|
## 责任限制
|
||||||
|
|
||||||
|
在法律允许的最大范围内,我们对因使用或无法使用本服务而导致的任何间接、偶然、特殊或后果性损害不承担责任。我们的总责任不超过用户为使用本服务所支付的费用。对于因不可抗力、网络故障、第三方原因等导致的服务中断或延迟,我们不承担责任。
|
||||||
|
|
||||||
|
## 争议解决
|
||||||
|
|
||||||
|
如用户对服务有任何疑问或争议,请首先通过客服渠道联系我们,我们将在收到反馈后24小时内响应,并尽快协商解决。如协商不成,双方同意将争议提交至服务提供方所在地有管辖权的人民法院通过诉讼方式解决。在争议解决期间,双方应继续履行本协议中无争议的条款。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
如对本协议有疑问,请前往「我的 - 客服」咨询。
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
getExpressReturnDetail,
|
getExpressReturnDetail,
|
||||||
fillExpressTrackingNumber
|
fillExpressTrackingNumber
|
||||||
} from '@/config/api/expressReturn.js'
|
} from '@/config/api/expressReturn.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
const rec = res.data
|
const rec = res.data
|
||||||
if (rec.status === 0) {
|
if (rec.status === 0) {
|
||||||
recordId.value = rec.id
|
recordId.value = rec.id
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('common.tips'),
|
title: t('common.tips'),
|
||||||
content: t('express.existingReturnNotice'),
|
content: t('express.existingReturnNotice'),
|
||||||
confirmText: t('express.goToFill'),
|
confirmText: t('express.goToFill'),
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { getExpressReturnDetail } from '@/config/api/expressReturn.js'
|
import { getExpressReturnDetail } from '@/config/api/expressReturn.js'
|
||||||
import { getCustomerPhone } from '@/util/index.js'
|
import { getCustomerPhone } from '@/util/index.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ const handleCopyTracking = () => {
|
|||||||
// 联系客服
|
// 联系客服
|
||||||
const handleContactService = () => {
|
const handleContactService = () => {
|
||||||
const customerPhone = getCustomerPhone()
|
const customerPhone = getCustomerPhone()
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('user.customerService'),
|
title: t('user.customerService'),
|
||||||
content: `${t('help.phone')}:${customerPhone}\n${t('help.workingHours')}:${t('express.workingHours')}`,
|
content: `${t('help.phone')}:${customerPhone}\n${t('help.workingHours')}:${t('express.workingHours')}`,
|
||||||
confirmText: t('express.call'),
|
confirmText: t('express.call'),
|
||||||
|
|||||||
@@ -67,8 +67,7 @@ onMounted(() => {
|
|||||||
loadList()
|
loadList()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 收件信息
|
// 收件信息(名称走多语言,地址暂为固定配置)
|
||||||
const recipientName = '风电者 18163601305'
|
|
||||||
const recipientAddress = '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623'
|
const recipientAddress = '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623'
|
||||||
|
|
||||||
const loadList = async () => {
|
const loadList = async () => {
|
||||||
@@ -131,7 +130,7 @@ const getStatusBadge = (status) => ({
|
|||||||
|
|
||||||
// 一键复制全部信息
|
// 一键复制全部信息
|
||||||
const copyAllInfo = () => {
|
const copyAllInfo = () => {
|
||||||
const allInfo = `${t('express.recipient')}:${recipientName}\n${t('express.recipientAddressLabel')}:${recipientAddress}`
|
const allInfo = `${t('express.recipient')}:${t('express.recipientName')}\n${t('express.recipientAddressLabel')}:${recipientAddress}`
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: allInfo,
|
data: allInfo,
|
||||||
success: () => {
|
success: () => {
|
||||||
|
|||||||
@@ -152,6 +152,7 @@
|
|||||||
import {
|
import {
|
||||||
URL
|
URL
|
||||||
} from "@/config/url.js"
|
} from "@/config/url.js"
|
||||||
|
import { showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -411,7 +412,7 @@
|
|||||||
this.clearExpressCountdown()
|
this.clearExpressCountdown()
|
||||||
|
|
||||||
// 显示归还成功弹窗
|
// 显示归还成功弹窗
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: this.$t('order.returnSuccess'),
|
title: this.$t('order.returnSuccess'),
|
||||||
content: this.$t('order.returnSuccessMessage'),
|
content: this.$t('order.returnSuccessMessage'),
|
||||||
confirmText: this.$t('order.viewDetails'),
|
confirmText: this.$t('order.viewDetails'),
|
||||||
@@ -793,7 +794,7 @@
|
|||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
handleCancelOrder() {
|
handleCancelOrder() {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: this.$t('order.confirmCancel'),
|
title: this.$t('order.confirmCancel'),
|
||||||
content: this.$t('order.confirmCancelContent'),
|
content: this.$t('order.confirmCancelContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { wxLogin, alipayLogin, getUserPhoneNumber, getUserInfo } from '@/util/index.js'
|
import { wxLogin, alipayLogin, getUserPhoneNumber, getUserInfo } from '@/util/index.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -95,11 +95,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 未勾选,弹窗提示
|
// 未勾选,弹窗提示
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('common.tips'),
|
title: t('common.tips'),
|
||||||
content: t('auth.pleaseAgreeToTerms'),
|
content: t('auth.pleaseAgreeToTerms'),
|
||||||
confirmText: t('common.confirm'),
|
|
||||||
cancelText: t('common.cancel'),
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
// 用户点击同意,自动勾选
|
// 用户点击同意,自动勾选
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 验证码输入 -->
|
<!-- 验证码输入 -->
|
||||||
|
<!-- #ifndef H5 -->
|
||||||
<view class="form-group">
|
<view class="form-group">
|
||||||
<view class="code-input-wrapper">
|
<view class="code-input-wrapper">
|
||||||
<input
|
<input
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<!-- 区域提示 -->
|
<!-- 区域提示 -->
|
||||||
<view class="region-notice">
|
<view class="region-notice">
|
||||||
@@ -50,6 +52,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 协议勾选 -->
|
<!-- 协议勾选 -->
|
||||||
|
<!-- #ifndef H5 -->
|
||||||
<view class="agreement-box">
|
<view class="agreement-box">
|
||||||
<checkbox-group @change="onAgreementChange">
|
<checkbox-group @change="onAgreementChange">
|
||||||
<label class="agreement-label">
|
<label class="agreement-label">
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</checkbox-group>
|
</checkbox-group>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -171,15 +175,19 @@
|
|||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!validatePhone()) return
|
if (!validatePhone()) return
|
||||||
|
|
||||||
|
// #ifndef H5
|
||||||
if (!verifyCode.value) {
|
if (!verifyCode.value) {
|
||||||
uni.showToast({ title: t('auth.codeRequired'), icon: 'none' })
|
uni.showToast({ title: t('auth.codeRequired'), icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef H5
|
||||||
if (!isAgreed.value) {
|
if (!isAgreed.value) {
|
||||||
uni.showToast({ title: t('auth.pleaseAgreeToTerms'), icon: 'none' })
|
uni.showToast({ title: t('auth.pleaseAgreeToTerms'), icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uni.showLoading({ title: t('common.loggingIn') })
|
uni.showLoading({ title: t('common.loggingIn') })
|
||||||
@@ -187,7 +195,8 @@
|
|||||||
loginType: 'SMS',
|
loginType: 'SMS',
|
||||||
appid,
|
appid,
|
||||||
phonenumber: getSubmitPhoneNumber(),
|
phonenumber: getSubmitPhoneNumber(),
|
||||||
smsCode: verifyCode.value
|
// H5 按产品要求不做验证码发送与校验,小程序端保持原流程
|
||||||
|
smsCode: verifyCode.value || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res && res.code !== 200) {
|
if (res && res.code !== 200) {
|
||||||
|
|||||||
@@ -137,8 +137,8 @@
|
|||||||
<view class="auth-title">授权登录</view>
|
<view class="auth-title">授权登录</view>
|
||||||
<view class="auth-desc">获取您的微信头像、昵称等公开信息</view>
|
<view class="auth-desc">获取您的微信头像、昵称等公开信息</view>
|
||||||
<view class="auth-buttons">
|
<view class="auth-buttons">
|
||||||
<button class="cancel-btn" @click="closeAuthPopup">取消</button>
|
<button class="cancel-btn" @click="closeAuthPopup">{{ $t('common.cancel') }}</button>
|
||||||
<button class="confirm-btn" @click="getUserProfile">确定</button>
|
<button class="confirm-btn" @click="getUserProfile">{{ $t('common.confirm') }}</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, getCurrentInstance } from 'vue'
|
import { ref, computed, onMounted, getCurrentInstance } from 'vue'
|
||||||
import { userLogout } from '@/config/api/user.js'
|
import { userLogout } from '@/config/api/user.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ const showLanguageSelector = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: t('common.tips'),
|
title: t('common.tips'),
|
||||||
content: t('user.confirmLogout'),
|
content: t('user.confirmLogout'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
import {
|
import {
|
||||||
URL
|
URL
|
||||||
} from '@/config/url'
|
} from '@/config/url'
|
||||||
|
import { showModalI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleLogout() {
|
handleLogout() {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: this.$t('common.tips'),
|
title: this.$t('common.tips'),
|
||||||
content: this.$t('user.confirmLogout'),
|
content: this.$t('user.confirmLogout'),
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
|
|||||||
+1
-4
@@ -290,10 +290,7 @@ export const fetchAndCacheCustomerPhone = async () => {
|
|||||||
try {
|
try {
|
||||||
// 获取当前语言
|
// 获取当前语言
|
||||||
const locale = uni.getLocale ? uni.getLocale() : uni.getSystemInfoSync().language
|
const locale = uni.getLocale ? uni.getLocale() : uni.getSystemInfoSync().language
|
||||||
const isEnglish = locale && (locale.toLowerCase().startsWith('en') || locale.toLowerCase().includes('en'))
|
const brandName = 'Isidaya'
|
||||||
|
|
||||||
// 根据语言设置品牌名称
|
|
||||||
const brandName = isEnglish ? 'fdzpower' : '风电者'
|
|
||||||
|
|
||||||
console.log('获取客服电话,当前语言:', locale, '品牌名称:', brandName)
|
console.log('获取客服电话,当前语言:', locale, '品牌名称:', brandName)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,59 @@
|
|||||||
// i18n工具函数 - 用于在 Vue 3 setup 中安全获取 $t
|
// i18n工具函数 - 用于在 Vue 3 setup 中安全获取 $t
|
||||||
import { getCurrentInstance } from 'vue'
|
import { getCurrentInstance } from 'vue'
|
||||||
|
import zhCN from '../locale/zh-CN.js'
|
||||||
|
import enUS from '../locale/en-US.js'
|
||||||
|
import idID from '../locale/id-ID.js'
|
||||||
|
|
||||||
|
const MESSAGE_MAP = {
|
||||||
|
'zh-CN': zhCN,
|
||||||
|
'en-US': enUS,
|
||||||
|
'id-ID': idID
|
||||||
|
}
|
||||||
|
const LANGUAGE_STORAGE_KEY = 'language'
|
||||||
|
|
||||||
|
export function getAppLocale() {
|
||||||
|
try {
|
||||||
|
const lang = uni.getStorageSync(LANGUAGE_STORAGE_KEY)
|
||||||
|
if (lang && MESSAGE_MAP[lang]) return lang
|
||||||
|
} catch (_) {}
|
||||||
|
return 'zh-CN'
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookupMessage(locale, key) {
|
||||||
|
const segments = String(key).split('.')
|
||||||
|
let node = MESSAGE_MAP[locale]
|
||||||
|
for (const seg of segments) {
|
||||||
|
if (node == null || typeof node !== 'object') return null
|
||||||
|
node = node[seg]
|
||||||
|
}
|
||||||
|
return typeof node === 'string' ? node : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 非 Vue 组件场景下的文案翻译 */
|
||||||
|
export function translate(key, values) {
|
||||||
|
const locale = getAppLocale()
|
||||||
|
let text = lookupMessage(locale, key) ?? lookupMessage('zh-CN', key) ?? key
|
||||||
|
if (values && typeof values === 'object') {
|
||||||
|
Object.entries(values).forEach(([k, v]) => {
|
||||||
|
text = text.split(`{${k}}`).join(String(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
/** uni.showModal 封装:默认使用多语言取消/确认按钮 */
|
||||||
|
export function showModalI18n(options = {}) {
|
||||||
|
const { cancelText, confirmText, showCancel = true, ...rest } = options
|
||||||
|
const modalOptions = {
|
||||||
|
...rest,
|
||||||
|
showCancel,
|
||||||
|
confirmText: confirmText ?? translate('common.confirm')
|
||||||
|
}
|
||||||
|
if (showCancel !== false) {
|
||||||
|
modalOptions.cancelText = cancelText ?? translate('common.cancel')
|
||||||
|
}
|
||||||
|
return uni.showModal(modalOptions)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 setup 中使用 i18n
|
* 在 setup 中使用 i18n
|
||||||
|
|||||||
@@ -872,6 +872,7 @@ function getUserLocation() {
|
|||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: getPermissionText('locationTitle'),
|
title: getPermissionText('locationTitle'),
|
||||||
content: getPermissionText('locationNeed'),
|
content: getPermissionText('locationNeed'),
|
||||||
|
confirmText: getPermissionText('gotIt'),
|
||||||
showCancel: false
|
showCancel: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { queryById } from '@/config/api/order.js'
|
import { queryById } from '@/config/api/order.js'
|
||||||
|
import { showModalI18n, translate } from '@/utils/i18n.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单监控服务
|
* 订单监控服务
|
||||||
@@ -198,11 +199,11 @@ class OrderMonitor {
|
|||||||
// 如果在订单详情页,页面自己会处理状态变化
|
// 如果在订单详情页,页面自己会处理状态变化
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.currentPage !== 'detail') {
|
if (this.currentPage !== 'detail') {
|
||||||
uni.showModal({
|
showModalI18n({
|
||||||
title: '归还成功',
|
title: translate('order.returnSuccess'),
|
||||||
content: '风扇已归还成功,剩余押金将退还到您的账户',
|
content: translate('order.returnSuccessMessage'),
|
||||||
confirmText: '查看详情',
|
confirmText: translate('order.viewDetails'),
|
||||||
cancelText: '我知道了',
|
cancelText: translate('permission.gotIt'),
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
// 跳转到订单详情页面查看详情
|
// 跳转到订单详情页面查看详情
|
||||||
|
|||||||
Reference in New Issue
Block a user