feat:国际化多语言适配

This commit is contained in:
2025-10-29 15:48:40 +08:00
parent 985d739324
commit 3d67dc928d
41 changed files with 2636 additions and 2801 deletions
+31 -3
View File
@@ -8,11 +8,39 @@
export default { export default {
onLaunch: function() { onLaunch: function() {
console.log('App Launch') console.log('App Launch')
// 注意:语言初始化已移至 main.js,确保每次 reLaunch 都能正确加载新语言
}, },
onShow: async function() { onShow: async function() {
// 手动登录模式:不再自动登录 console.log('========================================')
// 如需保留可开关逻辑,可在此读取配置决定是否执行 autoLogin console.log('=== App onShow 被调用 ===')
console.log('时间戳:', new Date().toLocaleTimeString())
// 检查并更新语言(uni.reLaunch 会触发 onShow
try {
const savedLang = uni.getStorageSync('language')
console.log('App onShow - 缓存中的语言:', savedLang)
// 获取当前 i18n 实例并检查语言
if (this.$i18n) {
const currentLocale = this.$i18n.locale
console.log('App onShow - 当前 i18n locale:', currentLocale)
if (savedLang && savedLang !== currentLocale) {
console.log('=== App onShow 检测到语言变化 ===')
console.log('旧语言:', currentLocale)
console.log('新语言:', savedLang)
// 强制更新语言
this.$i18n.locale = savedLang
console.log('App onShow - 语言已更新为:', this.$i18n.locale)
console.log('App onShow - 测试翻译:', this.$t('common.loading'))
}
}
} catch (e) {
console.error('App onShow - 语言检查失败:', e)
}
console.log('========================================')
}, },
onHide: function() { onHide: function() {
console.log('App Hide') console.log('App Hide')
-300
View File
@@ -1,300 +0,0 @@
# Uni-Fans API 接口文档
本文档详细说明了 Uni-Fans 应用中使用的所有接口,包括参数说明和使用示例。
## 目录
1. [订单查询接口](#1-订单查询接口)
2. [设备信息查询接口](#2-设备信息查询接口)
3. [订单套餐更新接口](#3-订单套餐更新接口)
4. [用户余额更新接口](#4-用户余额更新接口)
5. [微信支付订单创建接口](#5-微信支付订单创建接口)
6. [设备租借指令接口](#6-设备租借指令接口)
## 1. 订单查询接口
### 描述
根据订单ID查询订单详细信息。
### 接口信息
- **方法名**: `queryById`
- **请求方式**: GET
- **URL**: `/app/order/query/{orderId}`
### 请求参数
| 参数名 | 类型 | 必须 | 描述 |
| ----- | ---- | ---- | ---- |
| orderId | String | 是 | 订单ID |
### 响应参数
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"orderId": "订单ID",
"orderNo": "订单编号",
"deviceNo": "设备编号",
"createTime": "创建时间",
"phone": "联系电话",
"depositAmount": "押金金额",
"packageTime": "套餐时间(分钟)",
"packagePrice": "套餐价格"
}
}
```
### 使用示例
```javascript
const orderInfo = await queryById('12345');
if (orderInfo.code === 200) {
// 处理订单信息
console.log(orderInfo.data);
}
```
## 2. 设备信息查询接口
### 描述
根据设备编号查询设备详细信息。
### 接口信息
- **方法名**: `getDeviceInfo`
- **请求方式**: GET
- **URL**: `/app/device/info/{deviceNo}`
### 请求参数
| 参数名 | 类型 | 必须 | 描述 |
| ----- | ---- | ---- | ---- |
| deviceNo | String | 是 | 设备编号 |
### 响应参数
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"device": {
"deviceNo": "设备编号",
"deviceName": "设备名称",
"deviceStatus": "设备状态",
"depositAmount": "押金金额",
"feeType": "收费类型(hour/times)",
"feeConfig": "费用配置JSON字符串"
}
}
}
```
### 使用示例
```javascript
const deviceInfo = await getDeviceInfo('D001');
if (deviceInfo.code === 200) {
// 处理设备信息
console.log(deviceInfo.data.device);
}
```
## 3. 订单套餐更新接口
### 描述
更新订单的套餐信息。
### 接口信息
- **方法名**: `updateOrderPackage`
- **请求方式**: POST
- **URL**: `/app/order/update-package`
### 请求参数
| 参数名 | 类型 | 必须 | 描述 |
| ----- | ---- | ---- | ---- |
| orderId | String | 是 | 订单ID |
| packageTime | Number | 是 | 套餐时间(分钟) |
| packagePrice | Number | 是 | 套餐价格 |
### 响应参数
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
### 使用示例
```javascript
const result = await updateOrderPackage({
orderId: '12345',
packageTime: 360, // 6小时(分钟)
packagePrice: 30 // 30元
});
if (result.code === 200) {
console.log('套餐更新成功');
}
```
## 4. 用户余额更新接口
### 描述
支付成功后更新用户余额信息。
### 接口信息
- **方法名**: `updateUserBalance`
- **请求方式**: POST
- **URL**: `/app/user/update-balance`
### 请求参数
| 参数名 | 类型 | 必须 | 描述 |
| ----- | ---- | ---- | ---- |
| orderId | String | 是 | 订单ID |
### 响应参数
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"userId": "用户ID",
"balance": "更新后余额"
}
}
```
### 使用示例
```javascript
const result = await updateUserBalance('12345');
if (result.code === 200) {
console.log('用户余额更新成功');
}
```
## 5. 微信支付订单创建接口
### 描述
创建微信支付订单。
### 接口信息
- **请求方式**: GET
- **URL**: `/app/wx-payment/create/{orderNo}`
### 请求参数
| 参数名 | 类型 | 必须 | 描述 |
| ----- | ---- | ---- | ---- |
| orderNo | String | 是 | 订单编号 |
### 请求头
| 参数名 | 必须 | 描述 |
| ----- | ---- | ---- |
| Authorization | 是 | Bearer 认证令牌 |
| Clientid | 是 | 客户端ID |
### 响应参数
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"appId": "微信应用ID",
"timeStamp": "时间戳",
"nonceStr": "随机字符串",
"package": "预支付交易会话标识",
"signType": "签名类型",
"paySign": "签名"
}
}
```
### 使用示例
```javascript
const res = await uni.request({
url: `${URL}/app/wx-payment/create/${orderNo}`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
});
if (res.statusCode === 200 && res.data.code === 200) {
const payParams = res.data.data;
await uni.requestPayment({
...payParams,
success: () => {
console.log('支付成功');
},
fail: (err) => {
console.error('支付失败:', err);
}
});
}
```
## 6. 设备租借指令接口
### 描述
发送设备租借指令。
### 接口信息
- **请求方式**: POST
- **URL**: `/app/device/sendRentCommand`
### 请求参数
| 参数名 | 类型 | 必须 | 描述 |
| ----- | ---- | ---- | ---- |
| orderId | String | 是 | 订单ID |
### 请求头
| 参数名 | 必须 | 描述 |
| ----- | ---- | ---- |
| Content-Type | 是 | application/x-www-form-urlencoded |
| Authorization | 是 | Bearer 认证令牌 |
| Clientid | 是 | 客户端ID |
### 响应参数
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
### 使用示例
```javascript
const res = await uni.request({
url: `${URL}/app/device/sendRentCommand`,
method: 'POST',
data: {
orderId: '12345'
},
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
});
if (res.statusCode === 200 && res.data.code === 200) {
console.log('租借指令发送成功');
}
```
+20 -15
View File
@@ -14,23 +14,23 @@
@click="$emit('select', item)"> @click="$emit('select', item)">
<view class="position-info"> <view class="position-info">
<view class="position-name">{{ item.name }}</view> <view class="position-name">{{ item.name }}</view>
<view class="tag-row"> <view class="tag-row">
<view class="status-tag rent" v-if="isRentable(item)"><text>可租借</text></view> <view class="status-tag rent" v-if="isRentable(item)"><text>{{ $t('location.rent') }}</text></view>
<view class="status-tag return" v-if="isReturnable(item)"><text>可归还</text></view> <view class="status-tag return" v-if="isReturnable(item)"><text>{{ $t('location.return') }}</text></view>
</view> </view>
<view class="position-time" v-if="item.workTime && item.workTime !== '0'"><text>营业时间{{ item.workTime }}</text> <view class="position-time" v-if="item.workTime && item.workTime !== '0'"><text>{{ $t('location.businessHours') }}{{ item.workTime }}</text>
</view> </view>
</view> </view>
<view class="position-actions"> <view class="position-actions">
<view class="distance-info" v-if="item.distance"><text>{{ item.distance }}</text></view> <view class="distance-info" v-if="item.distance"><text>{{ item.distance }}</text></view>
<view class="nav-btn" @click.stop="$emit('navigate', item)"><text>导航</text></view> <view class="nav-btn" @click.stop="$emit('navigate', item)"><text>{{ $t('location.navigate') }}</text></view>
</view> </view>
</view> </view>
<view class="empty-state" v-if="!isLoading && (!positions || positions.length === 0)"> <view class="empty-state" v-if="!isLoading && (!positions || positions.length === 0)">
<image class="empty-icon" src="/static/scan-icon.png" mode="aspectFit" /> <image class="empty-icon" src="/static/scan-icon.png" mode="aspectFit" />
<text class="empty-text">附近暂无设备</text> <text class="empty-text">{{ $t('home.noNearbyDevice') }}</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
@@ -39,12 +39,17 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
const $t = instance?.proxy?.$t || ((key) => key)
const props = defineProps({ const props = defineProps({
show: { type: Boolean, default: false }, show: { type: Boolean, default: false },
expanded: { type: Boolean, default: false }, expanded: { type: Boolean, default: false },
positions: { type: Array, default: () => [] }, positions: { type: Array, default: () => [] },
isLoading: { type: Boolean, default: false }, isLoading: { type: Boolean, default: false },
title: { type: String, default: '附近设备场地' } title: { type: String, default: '' }
}) })
const isRentable = (item) => { const isRentable = (item) => {
+8 -22
View File
@@ -36,7 +36,7 @@
<view class="map-loading" v-if="isLoading"> <view class="map-loading" v-if="isLoading">
<view class="loading-content"> <view class="loading-content">
<view class="loading-spinner"></view> <view class="loading-spinner"></view>
<text>地图加载中...</text> <text>{{ $t('common.loadingMap') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -58,28 +58,13 @@
calculateDistanceSync calculateDistanceSync
} from '../utils/mapUtils.js' } from '../utils/mapUtils.js'
// 获取 i18n 实例
const instance = getCurrentInstance()
const $t = instance?.proxy?.$t || ((key) => key)
// 引用折叠面板组件的ref // 引用折叠面板组件的ref
const collapseRef = ref(null) const collapseRef = ref(null)
// 使用指南步骤
const guideSteps = ref([{
title: '扫码使用',
desc: '找到附近设备,扫描设备上的二维码'
},
{
title: '免押金支付',
desc: '无需支付押金,使用支付分免押即可完成租借'
},
{
title: '开始使用',
desc: '设备自动解锁,风扇弹出后取出即可开始使用'
},
{
title: '归还设备',
desc: '使用完毕后,按照设备规格要求将风扇还入即可结束订单'
}
])
// Props // Props
const props = defineProps({ const props = defineProps({
userLocation: { userLocation: {
@@ -436,8 +421,9 @@ const handleSearch = () => {
right: 0; right: 0;
bottom: 0; bottom: 0;
width: 94vw; width: 94vw;
height: var(--map-height, 78vh); height: calc(100% - 20rpx); /* 减少高度,避免覆盖底部按钮 */
margin: 20rpx; margin: 20rpx;
margin-bottom: 0; /* 底部不需要边距 */
border-radius: 20rpx; border-radius: 20rpx;
overflow: hidden; overflow: hidden;
@@ -520,7 +506,7 @@ const handleSearch = () => {
.map-side-controls { .map-side-controls {
position: absolute; position: absolute;
right: 20rpx; right: 20rpx;
bottom: 20rpx; bottom: 160rpx; /* 向上移动,避免被底部按钮遮挡 */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
+19 -16
View File
@@ -11,18 +11,18 @@
<view class="payment-badge wx-score" v-if="order.payWay == 'wx_score_pay'"> <view class="payment-badge wx-score" v-if="order.payWay == 'wx_score_pay'">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image> <image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image>
<view class="badge-text"> <view class="badge-text">
<text>微信支付分</text> <text>{{ $t('order.wxPayScore') }}</text>
<text class="divider">|</text> <text class="divider">|</text>
<text class="highlight">免押租借</text> <text class="highlight">{{ $t('order.depositFree') }}</text>
</view> </view>
</view> </view>
<view class="payment-badge member" v-else-if="order.payWay == 'wx_member_pay'"> <view class="payment-badge member" v-else-if="order.payWay == 'wx_member_pay'">
<text class="badge-text">会员订单</text> <text class="badge-text">{{ $t('order.memberOrder') }}</text>
</view> </view>
<view class="payment-badge deposit" v-else> <view class="payment-badge deposit" v-else>
<text class="badge-text">微信支付</text> <text class="badge-text">{{ $t('order.wxPay') }}</text>
<text class="divider">|</text> <text class="divider">|</text>
<text class="badge-text">押金租借</text> <text class="badge-text">{{ $t('order.depositPay') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -39,11 +39,11 @@
<!-- 订单时间信息 --> <!-- 订单时间信息 -->
<view class="order-times"> <view class="order-times">
<view class="time-row"> <view class="time-row">
<text class="time-label">租借地点</text> <text class="time-label">{{ $t('order.rentLocation') }}</text>
<text class="time-value">{{ order.deviceName || order.positionName }}</text> <text class="time-value">{{ order.deviceName || order.positionName }}</text>
</view> </view>
<view class="time-row"> <view class="time-row">
<text class="time-label">租借时间</text> <text class="time-label">{{ $t('order.rentTime') }}</text>
<text class="time-value">{{ order.startTime }}</text> <text class="time-value">{{ order.startTime }}</text>
</view> </view>
<view class="arrow" @click="onDetails"> <view class="arrow" @click="onDetails">
@@ -59,7 +59,7 @@
<!-- 订单底部 --> <!-- 订单底部 -->
<view class="order-footer"> <view class="order-footer">
<view class="footer-left"> <view class="footer-left">
<view v-if="isInUse" class="renting"><text class="dot"></text>租借中</view> <view v-if="isInUse" class="renting"><text class="dot"></text>{{ $t('order.renting') }}</view>
<view v-else-if="isFinished" class="meta"> <view v-else-if="isFinished" class="meta">
<view class="meta-item"><text class="dot"></text>{{ usedDurationText }}</view> <view class="meta-item"><text class="dot"></text>{{ usedDurationText }}</view>
<view class="meta-item"><text class="currency"></text>{{ displayAmount }}</view> <view class="meta-item"><text class="currency"></text>{{ displayAmount }}</view>
@@ -68,19 +68,22 @@
<view class="actions"> <view class="actions">
<!-- 待支付状态显示支付和取消按钮 --> <!-- 待支付状态显示支付和取消按钮 -->
<view v-if="isWaitingForPayment" class="action-item primary" @click="onPay">立即支付</view> <view v-if="isWaitingForPayment" class="action-item primary" @click="onPay">{{ $t('order.payNow') }}</view>
<view v-if="isWaitingForPayment" class="action-item secondary" @click="onCancel">取消订单</view> <view v-if="isWaitingForPayment" class="action-item secondary" @click="onCancel">{{ $t('order.cancelOrder') }}</view>
<!-- 使用中状态显示归还设备按钮 --> <!-- 使用中状态显示归还设备按钮 -->
<view v-if="isInUse" class="action-item primary" @click="onReturn">快速归还</view> <view v-if="isInUse" class="action-item primary" @click="onReturn">{{ $t('order.quickReturn') }}</view>
<!-- 查看详情按钮对所有订单都显示 --> <!-- 查看详情按钮对所有订单都显示 -->
<!-- <view class="action-item secondary" >查看详情</view> --> <!-- <view class="action-item secondary" >{{ $t('order.viewDetails') }}</view> -->
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'; import { computed, getCurrentInstance } from 'vue';
const instance = getCurrentInstance()
const $t = instance?.proxy?.$t || ((key) => key)
const props = defineProps({ const props = defineProps({
order: { type: Object, required: true }, order: { type: Object, required: true },
@@ -125,7 +128,7 @@
const isInUse = computed(() => normalizedStatus.value === 'in_used'); const isInUse = computed(() => normalizedStatus.value === 'in_used');
const isFinished = computed(() => normalizedStatus.value === 'used_done'); const isFinished = computed(() => normalizedStatus.value === 'used_done');
const titleText = computed(() => props.order.deviceName ? '租借风扇' : '租借风扇'); const titleText = computed(() => $t('order.rentFan'));
// 显示金额(优先后端给定字段) // 显示金额(优先后端给定字段)
const displayAmount = computed(() => props.order.amount || props.order.payAmount || props.order.actualDeviceAmount || props.order.currentFee || '0.00'); const displayAmount = computed(() => props.order.amount || props.order.payAmount || props.order.actualDeviceAmount || props.order.currentFee || '0.00');
@@ -139,8 +142,8 @@
const minutes = Math.floor(diffMs / 60000); const minutes = Math.floor(diffMs / 60000);
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
const mins = minutes % 60; const mins = minutes % 60;
if (hours > 0) return `${hours}小时${mins}分钟`; if (hours > 0) return `${hours}${$t('time.hour')}${mins}${$t('time.minute')}`;
return `${mins}分钟`; return `${mins}${$t('time.minute')}`;
}); });
function parseDate(str) { function parseDate(str) {
+2 -2
View File
@@ -1,6 +1,6 @@
// export const URL = "https://my.gxfs123.com/api" //正式服务器 // export const URL = "https://my.gxfs123.com/api" //正式服务器
// export const URL = "https://fansdev.gxfs123.com/api" //测试服务器 export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
export const URL = "http://192.168.5.120:8080" //本地调试 // export const URL = "http://192.168.5.120: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
+608
View File
@@ -0,0 +1,608 @@
export default {
common: {
confirm: 'Confirm',
cancel: 'Cancel',
and: 'and',
submit: 'Submit',
processing: 'Processing',
submitting: 'Submitting',
save: 'Save',
loadFailed: 'Load failed',
statusCode: 'Status Code',
message: 'Message',
none: 'None',
unexpectedError: 'Unexpected Error',
processException: 'Process exception',
errorInfo: 'Error Info',
edit: 'Edit',
delete: 'Delete',
search: 'Search',
loading: 'Loading...',
loadingData: 'Loading data...',
loadingLocation: 'Getting location...',
loadingMap: 'Loading map...',
loadingPosition: 'Loading locations...',
noData: 'No data',
success: 'Success',
failed: 'Failed',
retry: 'Retry',
back: 'Back',
next: 'Next',
complete: 'Complete',
more: 'More',
close: 'Close',
yes: 'Yes',
no: 'No',
all: 'All',
tips: 'Tips',
notice: 'Notice',
warning: 'Warning',
error: 'Error',
networkError: 'Network Error',
systemError: 'System Error',
authFailed: 'Authentication Failed',
unauthorized: 'Unauthorized',
loginRequired: 'Please login first',
operationSuccess: 'Operation successful',
operationFailed: 'Operation failed',
refresh: 'Refresh',
pull: 'Pull to refresh',
release: 'Release to refresh'
},
nav: {
home: 'Home',
my: 'Me',
orders: 'Orders',
settings: 'Settings',
back: 'Back',
title: 'FengDianZhe'
},
app: {
name: 'FengDianZhe',
slogan: 'Fan & Power Bank Rental',
fullName: 'FengDianZhe',
welcome: 'Welcome'
},
home: {
title: 'FengDianZhe',
nearbyDevices: 'Nearby',
scanToUse: 'Scan',
personalCenter: 'Profile',
useGuide: 'Guide',
navigate: 'Navigate',
relocate: 'Relocate',
search: 'Search',
service: 'Service',
searchPlaceholder: 'Search locations',
nearbyDeviceLocation: 'Nearby',
noNearbyDevice: 'No devices nearby',
relocating: 'Locating...',
locateSuccess: 'Located',
locateFailed: 'Location failed',
invalidQRCode: 'Invalid QR code',
scanFailed: 'Scan failed',
noticeTitle: 'Notice',
getLocationFailed: 'Location unavailable'
},
guide: {
title: 'How to Use',
step1Title: 'Scan QR Code',
step1Desc: 'Find a device and scan its QR code',
step2Title: 'No Deposit',
step2Desc: 'Rent with WeChat Pay Score, no deposit needed',
step3Title: 'Start Using',
step3Desc: 'Device unlocks, take out the fan',
step4Title: 'Return',
step4Desc: 'Insert fan back when done'
},
location: {
rent: 'Available',
return: 'Returnable',
navigate: 'Navigate',
distance: 'Distance',
businessHours: 'Business Hours: ',
navigateHere: 'Navigate Here',
coordinateError: 'Invalid location coordinates',
notExist: 'Location does not exist'
},
device: {
reportError: 'Report Error',
scanToUse: 'Scan to Use',
deviceInfo: 'Device Info',
deviceNo: 'Device No.',
location: 'Location',
businessHours: 'Business Hours',
pricing: 'Pricing',
pricingText: '$0.7/hour, $5/24 hours, Max $125',
getDeviceInfoFailed: 'Failed to get device info',
available: 'Available',
offline: 'Offline',
pricingRules: 'Pricing Rules',
capLimit: ' Cap',
usageInstructions: 'Usage Instructions',
checkBeforeUse: 'Please check if the device is in good condition before use',
autoChargeOvertime: 'Overtime will be charged automatically by hour',
useInDesignatedArea: 'Please use the device in designated area',
rentDepositFree: 'Rent Deposit-free',
wxPayScoreDesc: 'WeChat Pay Score | 550+ points enjoy',
checking: 'Checking',
deviceNoNotRecognized: 'Device number not recognized',
processFailed: 'Process failed, please try again later',
sharedFan: 'Shared Fan',
deviceNoRequired: 'Device number is required',
rentFailed: 'Device rent failed',
rentSuccess: 'Rent successful',
rentFailedRetry: 'Rent failed, please retry',
getPayParamsFailed: 'Failed to get payment parameters',
payScoreFailedCancelled: 'Pay score call failed, order cancelled'
},
order: {
myOrders: 'My Orders',
noOrderRecord: 'No order records',
getOrderListFailed: 'Failed to get order list',
confirmCancelContent: 'Are you sure to cancel this order?',
orderDetail: 'Order Detail',
orderNo: 'Order No.',
orderStatus: 'Order Status',
deviceNo: 'Device No.',
rentLocation: 'Rent Location',
rentTime: 'Rent Time',
returnTime: 'Return Time',
startTime: 'Start Time',
endTime: 'End Time',
duration: 'Duration',
amount: 'Amount',
totalAmount: 'Total Amount',
payAmount: 'Pay Amount',
deposit: 'Deposit',
rentFee: 'Rent Fee',
payNow: 'Pay Now',
cancelOrder: 'Cancel Order',
quickReturn: 'Quick Return',
returnDevice: 'Return Device',
viewDetails: 'View Details',
orderCompleted: 'Order Completed',
orderCancelled: 'Order Cancelled',
waitingForPayment: 'Pending',
inUse: 'In Use',
finished: 'Finished',
cancelled: 'Cancelled',
renting: 'Renting',
rentFan: 'Rent Fan',
noOrder: 'No orders in use',
getOrderFailed: 'Failed to get order',
paymentSuccess: 'Payment successful',
paymentFailed: 'Payment failed',
cancelSuccess: 'Cancelled successfully',
cancelFailed: 'Cancel failed',
returnSuccess: 'Returned successfully',
returnFailed: 'Return failed',
confirmCancel: 'Confirm to cancel order?',
confirmReturn: 'Confirm to return device?',
wxPayScore: 'WeChat Pay Score',
depositFree: 'Deposit-free',
memberOrder: 'Member Order',
wxPay: 'WeChat Pay',
depositPay: 'Deposit Pay',
paymentInProgress: 'Payment in Progress',
paymentFailedRetry: 'Payment failed, please try again',
pleasePaySoon: 'Please complete payment soon',
pleaseReturnInTime: 'Please take good care of the device and return it in time',
returnedThankYou: 'Your fan has been returned, thank you for using',
used: 'Used',
rentInfo: 'Rent Information',
fanNo: 'Fan No.',
rentMethod: 'Rent Method',
returnLocation: 'Return Location',
paid: 'Paid',
canExpressReturn: ' later for express return',
pauseBilling: 'Pause Billing',
rentAgain: 'Rent Again',
backToHome: 'Back to Home',
feeAppeal: 'Fee Appeal',
orderIdRequired: 'Order ID is required',
refundSuccess: 'Refund request successful',
refundFailed: 'Refund request failed',
orderNotExist: 'Order info does not exist',
currentFee: 'Current Fee',
returnInstructions: 'Return Instructions',
ensureDeviceIntact: 'Please ensure the device is intact',
insertFanBack: 'Insert the fan back to original or empty slot',
autoDetectReturn: 'System will auto-detect return and process refund',
autoJumpAfterReturn: 'Will auto-jump to success page after return',
refreshStatus: 'Refresh Status',
countdown: 'Countdown',
pauseAndExpress: 'Pause billing, express return',
orderInfoMissing: 'Order info missing',
returnSuccessMessage: 'Fan returned successfully, remaining deposit will be refunded',
noOrderInUse: 'No order in use found',
pleaseRefreshManually: 'Please refresh manually to check return status',
cancelling: 'Cancelling order',
cancelFailedContactService: 'Cancel failed, please contact customer service',
getOrderStatusFailed: 'Failed to get order status',
syncSuccess: 'Status synced successfully',
syncFailed: 'Sync failed'
},
user: {
clickToLogin: 'Login',
loginPrompt: 'Login to continue',
personalCenter: 'Profile',
depositBalance: 'Balance',
withdraw: 'Withdraw',
commonServices: 'Services',
quickReturn: 'Quick Return',
quickReturnDesc: '(View active orders)',
expressReturn: 'Express Return',
myOrders: 'Orders',
customerService: 'Support',
feedback: 'Feedback',
businessLicense: 'License',
cooperation: 'Partner',
settings: 'Settings',
userAgreement: 'Terms',
privacyPolicy: 'Privacy',
version: 'v',
logout: 'Logout',
confirmLogout: 'Logout?',
logoutSuccess: 'Logged out',
getUserInfoFailed: 'Failed',
updateSuccess: 'Updated',
updateFailed: 'Failed',
avatarUpdated: 'Avatar updated',
avatarUploadFailed: 'Upload failed',
noAvatar: 'No avatar',
noAvatarUrl: 'Failed',
avatarDownloadFailed: 'Download failed',
notLoggedIn: 'Not logged in',
phoneNotBound: 'No phone',
balanceDesc: 'Available for rental'
},
auth: {
authTitle: 'Phone Login',
authDesc: 'We need your phone for service and contact',
getPhoneNumber: 'Login with Phone',
notNow: 'Skip',
authRequired: 'Login Required',
authSuccess: 'Success',
authFailed: 'Failed',
loginTitle: 'Login',
loginDesc: 'Login for better experience',
getUserInfoSuccess: 'Success',
getUserInfoFailed: 'Failed',
pleaseUseInWechat: 'Use in WeChat',
agreeToTerms: 'I agree to',
pleaseAgreeToTerms: 'Please agree to terms',
loginSuccess: 'Login successful',
loginFailed: 'Login failed',
phoneCancelled: 'Cancelled',
goToLogin: 'Login',
authDescShort: 'Phone number required for service',
phoneRequired: 'Phone required',
getting: 'Loading...',
phoneSuccess: 'Success',
phoneError: 'Error',
phoneGetFailed: 'Failed',
authCodeFailed: 'Auth failed'
},
payment: {
paymentAmount: 'Amount',
paymentMethod: 'Method',
wechatPay: 'WeChat',
alipay: 'Alipay',
balance: 'Balance',
payNow: 'Pay',
paying: 'Processing...',
paymentSuccess: 'Success',
paymentFailed: 'Failed',
paymentCancelled: 'Cancelled',
orderPayment: 'Payment',
waitingForPayment: 'Pending',
pleasePayIn15Min: 'Pay within 15 min',
orderInfo: 'Order',
createTime: 'Created',
contactPhone: 'Phone',
feeInfo: 'Fee',
deposit: 'Deposit',
package: 'Package',
total: 'Total',
paymentFailedRetry: 'Payment failed, retry?',
createPayOrderFailed: 'Failed'
},
feedback: {
title: 'Feedback',
placeholder: 'Describe the issue',
submit: 'Submit',
submitSuccess: 'Submitted',
submitFailed: 'Failed',
contentRequired: 'Enter details',
issueType: 'Type',
issueDescription: 'Description',
imageUpload: 'Photo (Optional)',
uploadImage: 'Upload',
contactInfo: 'Contact',
contactPlaceholder: 'Your phone',
pleaseSelectType: 'Select type',
pleaseDescribe: 'Describe issue',
pleaseContact: 'Leave contact',
imageUploadFailed: 'Upload failed',
deviceFault: 'Device Fault',
chargingIssue: 'Charging',
usageSuggestion: 'Suggestion',
other: 'Other'
},
help: {
title: 'Customer Service',
commonQuestions: 'Common Questions',
contactUs: 'Contact Us',
phone: 'Phone',
email: 'Email',
workingHours: 'Working Hours',
functionDeveloping: 'Feature in development'
},
settings: {
title: 'Settings',
language: 'Language',
languageSetting: 'Language Setting',
chinese: '简体中文',
english: 'English',
notification: 'Notification',
privacy: 'Privacy',
about: 'About',
clearCache: 'Clear Cache',
cacheCleared: 'Cache cleared',
logout: 'Logout',
confirmLogout: 'Confirm to logout?',
logoutSuccess: 'Logout successful'
},
express: {
title: 'Express Return',
addReturn: 'New Return',
returnRecord: 'Records',
expressNo: 'Tracking No.',
expressCompany: 'Courier',
sendTime: 'Sent',
receivedTime: 'Received',
status: 'Status',
pending: 'Pending',
shipped: 'Shipped',
received: 'Received',
detail: 'Detail',
recipientInfo: 'Ship To',
recipientName: 'FengDianZhe 18163601305',
recipientAddress: 'Rm 623, Bldg A2, Xinchanghai Park, Luogu St, Yuelu, Changsha, Hunan',
copyAllInfo: 'Copy All',
recipient: 'To',
recipientAddressLabel: 'Address',
copySuccess: 'Copied',
copyFailed: 'Failed',
noReturnRecord: 'No records',
toFill: 'Fill',
userPhone: 'Phone',
billingPaused: 'Paused',
completed: 'Done',
processing: 'Processing',
getListFailed: 'Load failed',
loadFailed: 'Failed',
returnCompleted: 'Return Completed',
returnCompletedDesc: 'Your express has been successfully returned',
processingDesc: 'Processing your return request',
pendingDesc: 'Waiting to process return request',
expressInfo: 'Express Info',
trackingNo: 'Tracking No.',
packageType: 'Package Type',
packageWeight: 'Package Weight',
returnInfo: 'Return Info',
returnAddress: 'Return Address',
returnTime: 'Return Time',
processTime: 'Process Time',
completeTime: 'Complete Time',
remarkInfo: 'Remark Info',
copyTrackingNo: 'Copy Tracking No.',
trackingNoCopied: 'Tracking number copied',
workingHours: 'Mon-Sun 09:00-22:00',
call: 'Call',
returnDetail: 'Return Detail',
getDetailFailed: 'Failed to get detail',
fillExpress: 'Express Return',
openTime: 'Start Time',
fillExpressInfo: 'Fill Express Return Info',
contactPhone: 'Contact Phone',
fillTrackingPlaceholder: 'Enter tracking number to fill',
trackingPlaceholder: 'Enter tracking number (optional)',
confirmFill: 'Confirm Fill',
submitInfo: 'Submit Info',
orderNoMissing: 'Order number missing',
getRecordFailed: 'Failed to get record',
existingReturnNotice: 'Express return request exists, go to fill tracking number?',
goToFill: 'Go to Fill',
alreadyHasRecord: 'Return record already exists',
pleaseEnterValidPhone: 'Please enter valid contact phone',
pleaseEnterTrackingNo: 'Please enter tracking number',
filling: 'Filling',
fillSuccess: 'Fill successful',
fillFailed: 'Fill failed',
submitSuccess: 'Submit successful',
submitFailed: 'Submit failed'
},
join: {
title: 'Cooperation',
cooperationTitle: 'Cooperation Method',
contactUs: 'Contact Us',
phone: 'Phone',
email: 'Email',
submit: 'Submit Application',
name: 'Name',
contactPhone: 'Contact',
city: 'City',
intention: 'Intention',
placeholder: 'Please briefly describe your cooperation intention...',
submitSuccess: 'Submitted successfully, we will contact you soon',
submitFailed: 'Submit failed, please try again later',
pageLoadFailed: 'Page load failed'
},
legal: {
agreement: 'User Agreement',
privacy: 'Privacy Policy',
termsOfService: 'Terms of Service',
lastUpdate: 'Last Update',
applicableToService: 'Applicable to "FengDianZhe" shared fan rental 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"'
},
search: {
title: 'Find Device',
placeholder: 'Enter location name or address',
history: 'Search History',
clear: 'Clear History',
noResult: 'No results found',
searching: 'Searching...',
invalidCoordinate: 'Invalid coordinates',
positionInfoError: 'Location info error'
},
share: {
title: 'FengDianZhe - Shared Fan & Power Bank',
path: '/pages/index/index'
},
error: {
networkError: 'Network connection failed',
serverError: 'Server error',
timeout: 'Request timeout',
unknown: 'Unknown error',
tryAgain: 'Please try again later'
},
time: {
hour: 'hour',
minute: 'minute',
second: 'second',
day: 'day',
week: 'week',
month: 'month',
year: 'year',
justNow: 'Just now',
minutesAgo: 'minutes ago',
hoursAgo: 'hours ago',
daysAgo: 'days ago',
yesterday: 'Yesterday',
today: 'Today',
tomorrow: 'Tomorrow'
},
unit: {
yuan: 'CNY',
meter: 'm',
km: 'km',
piece: 'pc',
times: 'times'
},
waiting: {
title: 'Ejecting',
preparing: 'Preparing...',
longTimeNotice: 'Taking too long? Contact staff',
deviceEjecting: 'Ejecting...',
rentFailed: 'Rental failed',
timeout: 'Timeout'
},
success: {
paymentSuccess: 'Payment Successful',
paymentSuccessDesc: 'Your order has been paid successfully',
orderInfo: 'Order Info',
paymentAmount: 'Payment Amount',
paymentTime: 'Payment Time',
deviceStatus: 'Device Status',
preparingDevice: 'Preparing your device, please wait...',
deviceReady: 'Device ready, please take your fan',
deviceFailed: 'Device ejection failed, please contact customer service',
backToHome: 'Back to Home',
viewOrder: 'View Order',
returnSuccess: 'Return Successful',
returnSuccessDesc: 'Your fan has been returned, fee deducted from deposit',
usedTime: 'Used Time',
packageTime: 'Package Time',
extraTime: 'Extra Time',
returnTime: 'Return Time',
packageFee: 'Package Fee',
extraFee: 'Extra Fee',
totalFee: 'Total Fee',
depositAmount: 'Deposit',
refundAmount: 'Refund Amount',
refundStatus: 'Refund Status',
refundNotice: 'Refund Notice',
refundNotice1: 'Deposit balance needs to be manually withdrawn',
refundNotice2: 'Withdrawal will be refunded to original payment account within 1-3 business days',
refundNotice3: 'If you have questions, please contact customer service',
applyRefund: 'Apply Refund',
refundWaiting: 'Pending',
refundProcessing: 'Processing',
refundSuccess: 'Refunded',
refundFailed: 'Failed'
},
deposit: {
title: 'Deposit Management',
depositBalance: 'Deposit Balance',
withdraw: 'Withdraw',
withdrawRecord: 'Withdraw Record',
withdrawAmount: 'Withdraw Amount',
withdrawStatus: 'Withdraw Status',
applyWithdraw: 'Apply Withdraw',
withdrawSuccess: 'Withdraw successful',
withdrawFailed: 'Withdraw failed',
noBalance: 'No balance to withdraw',
confirmWithdraw: 'Confirm Withdraw',
withdrawDesc: 'Deposit will be refunded to original account within 0-7 business days',
withdrawing: 'Withdrawing...',
withdrawSubmitted: 'Withdraw request submitted',
withdrawNotice: 'Withdraw Notice',
withdrawNotice1: 'Withdrawal will be refunded to original payment account',
withdrawNotice2: 'Withdrawal expected to arrive within 0-7 business days',
withdrawNotice3: 'If delayed, please contact customer service',
depositRecord: 'Deposit Record',
orderNotReturned: 'Current order not returned, please return before withdraw',
alreadyRefunded: 'Deposit already refunded',
refundProcessing: 'Refund processing, please wait'
},
userProfile: {
title: 'Personal Info',
avatar: 'Avatar',
nickname: 'Nickname',
phone: 'Phone',
edit: 'Edit',
save: 'Save',
cancel: 'Cancel',
clickToChange: 'Click to change avatar',
notSet: 'Not set',
notBound: 'Not bound',
balance: 'Balance',
enterNickname: 'Enter new nickname',
nicknameRequired: 'Nickname cannot be empty',
saving: 'Saving...',
nicknameUpdated: 'Nickname updated successfully',
updateFailed: 'Update failed',
uploading: 'Uploading...'
}
}
+8
View File
@@ -0,0 +1,8 @@
import zhCN from './zh-CN.js'
import enUS from './en-US.js'
export default {
'zh-CN': zhCN,
'en-US': enUS
}
+608
View File
@@ -0,0 +1,608 @@
export default {
common: {
confirm: '确定',
cancel: '取消',
and: '和',
submit: '提交',
processing: '处理中',
submitting: '提交中',
save: '保存',
loadFailed: '加载失败',
statusCode: '状态码',
message: '消息',
none: '无',
unexpectedError: '意外错误',
processException: '处理过程发生异常',
errorInfo: '错误信息',
edit: '编辑',
delete: '删除',
search: '搜索',
loading: '加载中...',
loadingData: '正在获取数据...',
loadingLocation: '正在获取位置信息...',
loadingMap: '地图加载中...',
loadingPosition: '正在获取场地信息...',
noData: '暂无数据',
success: '成功',
failed: '失败',
retry: '重试',
back: '返回',
next: '下一步',
complete: '完成',
more: '更多',
close: '关闭',
yes: '是',
no: '否',
all: '全部',
tips: '提示',
notice: '通知',
warning: '警告',
error: '错误',
networkError: '网络错误',
systemError: '系统错误',
authFailed: '认证失败',
unauthorized: '未授权',
loginRequired: '请先登录',
operationSuccess: '操作成功',
operationFailed: '操作失败',
refresh: '刷新',
pull: '下拉刷新',
release: '释放刷新'
},
nav: {
home: '首页',
my: '我的',
orders: '订单',
settings: '设置',
back: '返回',
title: '风电者共享风扇&暖手充电宝'
},
app: {
name: '风电者',
slogan: '共享风扇暖手充电宝',
fullName: '风电者 - 共享风扇暖手充电宝',
welcome: '欢迎使用风电者'
},
home: {
title: '风电者共享风扇&暖手充电宝',
nearbyDevices: '附近设备',
scanToUse: '扫码使用',
personalCenter: '个人中心',
useGuide: '使用指南',
navigate: '导航',
relocate: '重新定位',
search: '搜索',
service: '客服',
searchPlaceholder: '搜索附近场地',
nearbyDeviceLocation: '附近设备场地',
noNearbyDevice: '附近暂无设备',
relocating: '重新定位中...',
locateSuccess: '定位成功',
locateFailed: '定位失败,请检查定位权限',
invalidQRCode: '无效的设备二维码',
scanFailed: '扫码失败',
noticeTitle: '通知公告',
getLocationFailed: '获取位置失败,显示默认地图'
},
guide: {
title: '使用指南',
step1Title: '扫码使用',
step1Desc: '找到附近设备,扫描设备上的二维码',
step2Title: '免押金支付',
step2Desc: '无需支付押金,使用支付分免押即可完成租借',
step3Title: '开始使用',
step3Desc: '设备自动解锁,风扇弹出后取出即可开始使用',
step4Title: '归还设备',
step4Desc: '使用完毕后,按照设备规格要求将风扇还入即可结束订单'
},
location: {
rent: '可租借',
return: '可归还',
navigate: '导航',
distance: '距离',
businessHours: '营业时间:',
navigateHere: '导航去这',
coordinateError: '该场地坐标信息异常',
notExist: '场地不存在'
},
device: {
reportError: '设备报错',
scanToUse: '扫码使用',
deviceInfo: '设备信息',
deviceNo: '设备号',
location: '地点',
businessHours: '营业时间',
pricing: '计费',
pricingText: '5元/小时,36元/24小时,总计¥899元',
getDeviceInfoFailed: '获取设备信息失败',
available: '可使用',
offline: '离线',
pricingRules: '计费规则',
capLimit: '元封顶',
usageInstructions: '使用说明',
checkBeforeUse: '请在使用前检查设备是否完好',
autoChargeOvertime: '超出使用时间将自动按小时计费',
useInDesignatedArea: '请在指定区域内使用设备',
rentDepositFree: '免押金租借',
wxPayScoreDesc: '微信支付分 | 550分以上优享',
checking: '检查中',
deviceNoNotRecognized: '未识别到设备编号',
processFailed: '处理失败,请稍后重试',
sharedFan: '共享风扇',
deviceNoRequired: '设备号不能为空',
rentFailed: '设备租借失败',
rentSuccess: '租借成功',
rentFailedRetry: '租借失败,请重试',
getPayParamsFailed: '获取支付参数失败',
payScoreFailedCancelled: '支付分调用失败,订单已取消'
},
order: {
myOrders: '我的订单',
noOrderRecord: '暂无订单记录',
getOrderListFailed: '获取订单列表失败',
confirmCancelContent: '确定要取消此订单吗?',
orderDetail: '订单详情',
orderNo: '订单号',
orderStatus: '订单状态',
deviceNo: '设备号',
rentLocation: '租借地点',
rentTime: '租借时间',
returnTime: '归还时间',
startTime: '开始时间',
endTime: '结束时间',
duration: '使用时长',
amount: '金额',
totalAmount: '总金额',
payAmount: '支付金额',
deposit: '押金',
rentFee: '租金',
payNow: '立即支付',
cancelOrder: '取消订单',
quickReturn: '快速归还',
returnDevice: '归还设备',
viewDetails: '查看详情',
orderCompleted: '订单已完成',
orderCancelled: '订单已取消',
waitingForPayment: '待支付',
inUse: '使用中',
finished: '已完成',
cancelled: '已取消',
renting: '租借中',
rentFan: '租借风扇',
noOrder: '暂无使用中的订单',
getOrderFailed: '获取订单失败',
paymentSuccess: '支付成功',
paymentFailed: '支付失败',
cancelSuccess: '取消成功',
cancelFailed: '取消失败',
returnSuccess: '归还成功',
returnFailed: '归还失败',
confirmCancel: '确认取消订单?',
confirmReturn: '确认归还设备?',
wxPayScore: '微信支付分',
depositFree: '免押租借',
memberOrder: '会员订单',
wxPay: '微信支付',
depositPay: '押金租借',
paymentInProgress: '支付中',
paymentFailedRetry: '支付失败,请重新支付',
pleasePaySoon: '请尽快完成支付',
pleaseReturnInTime: '请妥善保管设备,使用完毕后及时归还',
returnedThankYou: '您的风扇已归还,感谢使用',
used: '已使用',
rentInfo: '租借信息',
fanNo: '风扇编号',
rentMethod: '租借方式',
returnLocation: '归还地点',
paid: '已支付',
canExpressReturn: '后可快递归还',
pauseBilling: '暂停计费',
rentAgain: '再次租借',
backToHome: '返回首页',
feeAppeal: '费用申诉',
orderIdRequired: '订单ID不能为空',
refundSuccess: '退款申请成功',
refundFailed: '退款申请失败',
orderNotExist: '订单信息不存在',
currentFee: '当前费用',
returnInstructions: '归还说明',
ensureDeviceIntact: '请确保设备完好无损',
insertFanBack: '将风扇插入原位置或空闲插口',
autoDetectReturn: '系统将自动检测归还并处理退款',
autoJumpAfterReturn: '归还成功后将自动跳转到成功页面',
refreshStatus: '刷新状态',
countdown: '倒计时',
pauseAndExpress: '暂停计费,快递归还',
orderInfoMissing: '缺少订单信息',
returnSuccessMessage: '风扇已归还成功,剩余押金将退还到您的账户',
noOrderInUse: '未找到使用中的订单',
pleaseRefreshManually: '请手动刷新查看归还状态',
cancelling: '取消订单中',
cancelFailedContactService: '取消订单失败,请联系客服',
getOrderStatusFailed: '订单状态查询失败',
syncSuccess: '状态同步成功',
syncFailed: '同步状态失败'
},
user: {
clickToLogin: '点击登录',
loginPrompt: '授权登录后可查看订单与资产',
personalCenter: '个人中心',
depositBalance: '押金余额',
withdraw: '提现',
commonServices: '常用服务',
quickReturn: '快速归还',
quickReturnDesc: '(直接查看使用中的订单)',
expressReturn: '快递归还记录',
myOrders: '我的订单',
customerService: '客服中心',
feedback: '投诉与建议',
businessLicense: '营业资质',
cooperation: '合作加盟',
settings: '设置',
userAgreement: '《用户协议》',
privacyPolicy: '《隐私政策》',
version: 'v',
logout: '退出登录',
confirmLogout: '确认退出登录?',
logoutSuccess: '退出成功',
getUserInfoFailed: '获取用户信息失败',
updateSuccess: '信息更新成功',
updateFailed: '更新用户信息失败',
avatarUpdated: '头像已更新',
avatarUploadFailed: '头像更新失败',
noAvatar: '未选择头像',
noAvatarUrl: '未获取到头像地址',
avatarDownloadFailed: '头像下载失败',
notLoggedIn: '未登录',
phoneNotBound: '未绑定手机号',
balanceDesc: '可用于租借设备'
},
auth: {
authTitle: '授权获取手机号',
authDesc: '为了提供更好的服务和紧急联系,需要授权获取您的手机号',
getPhoneNumber: '手机号快捷登录',
notNow: '暂不授权',
authRequired: '需要授权',
authSuccess: '授权成功',
authFailed: '授权失败',
loginTitle: '登录',
loginDesc: '为保障使用体验,请先完成登录',
getUserInfoSuccess: '获取用户信息成功',
getUserInfoFailed: '获取用户信息失败',
pleaseUseInWechat: '请在微信小程序中使用此功能',
agreeToTerms: '我已阅读并同意',
pleaseAgreeToTerms: '请先阅读并同意《用户协议》和《隐私政策》',
loginSuccess: '登录成功',
loginFailed: '登录失败',
phoneCancelled: '已取消手机号授权',
goToLogin: '去登录',
authDescShort: '为了提供更好的服务,需要授权获取您的手机号',
phoneRequired: '需要授权手机号才能使用设备',
getting: '获取中...',
phoneSuccess: '手机号获取成功',
phoneError: '获取手机号异常',
phoneGetFailed: '获取手机号失败',
authCodeFailed: '获取授权码失败'
},
payment: {
paymentAmount: '支付金额',
paymentMethod: '支付方式',
wechatPay: '微信支付',
alipay: '支付宝',
balance: '余额支付',
payNow: '立即支付',
paying: '支付中...',
paymentSuccess: '支付成功',
paymentFailed: '支付失败',
paymentCancelled: '支付已取消',
orderPayment: '订单支付',
waitingForPayment: '等待支付',
pleasePayIn15Min: '请在15分钟内完成支付',
orderInfo: '订单信息',
createTime: '创建时间',
contactPhone: '联系电话',
feeInfo: '费用信息',
deposit: '押金',
package: '套餐',
total: '合计',
paymentFailedRetry: '支付失败,请重试',
createPayOrderFailed: '创建支付订单失败'
},
feedback: {
title: '投诉与建议',
placeholder: '请详细描述您遇到的问题,以便我们更好地为您解决',
submit: '提交反馈',
submitSuccess: '反馈成功',
submitFailed: '反馈失败',
contentRequired: '请输入内容',
issueType: '问题类型',
issueDescription: '问题描述',
imageUpload: '图片上传(选填)',
uploadImage: '上传图片',
contactInfo: '联系方式',
contactPlaceholder: '请留下您的手机号,方便我们联系您',
pleaseSelectType: '请选择问题类型',
pleaseDescribe: '请描述您的问题',
pleaseContact: '请留下联系方式',
imageUploadFailed: '图片上传失败',
deviceFault: '设备故障',
chargingIssue: '收费问题',
usageSuggestion: '使用建议',
other: '其他'
},
help: {
title: '客服中心',
commonQuestions: '常见问题',
contactUs: '联系我们',
phone: '电话',
email: '邮箱',
workingHours: '工作时间',
functionDeveloping: '功能开发中'
},
settings: {
title: '设置',
language: '语言',
languageSetting: '语言设置',
chinese: '简体中文',
english: 'English',
notification: '通知',
privacy: '隐私',
about: '关于',
clearCache: '清除缓存',
cacheCleared: '缓存已清除',
logout: '退出登录',
confirmLogout: '确认退出登录?',
logoutSuccess: '退出成功'
},
express: {
title: '快递归还',
addReturn: '添加归还',
returnRecord: '快递归还记录',
expressNo: '快递单号',
expressCompany: '快递公司',
sendTime: '寄出时间',
receivedTime: '签收时间',
status: '状态',
pending: '待处理',
shipped: '已寄出',
received: '已签收',
detail: '详情',
recipientInfo: '收件信息',
recipientName: '风电者 18163601305',
recipientAddress: '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623',
copyAllInfo: '一键复制全部信息',
recipient: '收件人',
recipientAddressLabel: '收件地址',
copySuccess: '全部信息已复制',
copyFailed: '复制失败',
noReturnRecord: '暂无归还记录',
toFill: '待填写',
userPhone: '用户电话',
billingPaused: '暂停计费中',
completed: '已完成',
processing: '处理中',
getListFailed: '获取列表失败',
loadFailed: '加载失败',
returnCompleted: '归还完成',
returnCompletedDesc: '您的快递已成功归还',
processingDesc: '正在处理您的归还请求',
pendingDesc: '等待处理归还申请',
expressInfo: '快递信息',
trackingNo: '运单号',
packageType: '包裹类型',
packageWeight: '包裹重量',
returnInfo: '归还信息',
returnAddress: '归还地址',
returnTime: '归还时间',
processTime: '处理时间',
completeTime: '完成时间',
remarkInfo: '备注信息',
copyTrackingNo: '复制运单号',
trackingNoCopied: '运单号已复制',
workingHours: '周一至周日 09:00-22:00',
call: '拨打',
returnDetail: '归还详情',
getDetailFailed: '获取详情失败',
fillExpress: '快递归还',
openTime: '开始时间',
fillExpressInfo: '填写快递归还信息',
contactPhone: '联系电话',
fillTrackingPlaceholder: '请输入需要补填的快递单号',
trackingPlaceholder: '请输入快递单号(可先留空)',
confirmFill: '确认补填',
submitInfo: '提交信息',
orderNoMissing: '缺少订单号',
getRecordFailed: '获取记录失败',
existingReturnNotice: '已存在快递归还申请,是否前往补填快递单号?',
goToFill: '去补填',
alreadyHasRecord: '已有归还记录',
pleaseEnterValidPhone: '请填写有效联系电话',
pleaseEnterTrackingNo: '请填写快递单号',
filling: '补填中',
fillSuccess: '补填成功',
fillFailed: '补填失败',
submitSuccess: '提交成功',
submitFailed: '提交失败'
},
join: {
title: '合作加盟',
cooperationTitle: '合作方式',
contactUs: '联系我们',
phone: '联系电话',
email: '联系邮箱',
submit: '提交申请',
name: '姓名',
contactPhone: '联系方式',
city: '所在城市',
intention: '合作意向',
placeholder: '请简要说明您的合作意向...',
submitSuccess: '提交成功,我们会尽快联系您',
submitFailed: '提交失败,请稍后重试',
pageLoadFailed: '页面加载失败'
},
legal: {
agreement: '用户协议',
privacy: '隐私政策',
termsOfService: '服务条款',
lastUpdate: '最后更新',
applicableToService: '适用于"风电者"共享风扇租借服务',
footerNotice: '如对本协议有疑问,请前往"我的-客服"咨询',
footerNoticePolicy: '如对本政策有疑问,请前往"我的-客服"咨询'
},
search: {
title: '查找设备',
placeholder: '请输入场地名称或地址',
history: '搜索历史',
clear: '清除历史',
noResult: '暂无搜索结果',
searching: '搜索中...',
invalidCoordinate: '该位置坐标无效',
positionInfoError: '场地信息异常'
},
share: {
title: '风电者 - 共享风扇暖手充电宝',
path: '/pages/index/index'
},
error: {
networkError: '网络连接失败',
serverError: '服务器错误',
timeout: '请求超时',
unknown: '未知错误',
tryAgain: '请稍后重试'
},
time: {
hour: '小时',
minute: '分钟',
second: '秒',
day: '天',
week: '周',
month: '月',
year: '年',
justNow: '刚刚',
minutesAgo: '分钟前',
hoursAgo: '小时前',
daysAgo: '天前',
yesterday: '昨天',
today: '今天',
tomorrow: '明天'
},
unit: {
yuan: '元',
meter: '米',
km: '公里',
piece: '个',
times: '次'
},
waiting: {
title: '设备弹出中',
preparing: '正在为您弹出设备',
longTimeNotice: '若长时间未弹出,请联系现场工作人员或稍后重试',
deviceEjecting: '设备弹出中,请稍候',
rentFailed: '设备租借失败,订单已取消',
timeout: '等待超时,请稍后重试'
},
success: {
paymentSuccess: '支付成功',
paymentSuccessDesc: '您的订单已支付成功',
orderInfo: '订单信息',
paymentAmount: '支付金额',
paymentTime: '支付时间',
deviceStatus: '设备状态',
preparingDevice: '正在准备您的设备,请稍候...',
deviceReady: '设备已弹出,请取走您的风扇',
deviceFailed: '弹出设备失败,请联系客服',
backToHome: '返回首页',
viewOrder: '查看订单',
returnSuccess: '归还成功',
returnSuccessDesc: '您的风扇已归还,费用已从押金中扣除',
usedTime: '使用时长',
packageTime: '套餐时长',
extraTime: '超出时长',
returnTime: '归还时间',
packageFee: '套餐费用',
extraFee: '超时费用',
totalFee: '总费用',
depositAmount: '押金',
refundAmount: '退还金额',
refundStatus: '退还状态',
refundNotice: '退款说明',
refundNotice1: '押金剩余金额需要您手动申请提现',
refundNotice2: '提现申请提交后将在1-3个工作日内退还到原支付账户',
refundNotice3: '如有疑问,请联系客服',
applyRefund: '申请退款',
refundWaiting: '待申请',
refundProcessing: '处理中',
refundSuccess: '已退款',
refundFailed: '退款失败'
},
deposit: {
title: '押金管理',
depositBalance: '押金余额',
withdraw: '提现',
withdrawRecord: '提现记录',
withdrawAmount: '提现金额',
withdrawStatus: '提现状态',
applyWithdraw: '申请提现',
withdrawSuccess: '提现成功',
withdrawFailed: '提现失败',
noBalance: '无可提现余额',
confirmWithdraw: '确认提现',
withdrawDesc: '押金将原路退回,预计0-7个工作日到账',
withdrawing: '提现中...',
withdrawSubmitted: '提现申请已提交',
withdrawNotice: '提现说明',
withdrawNotice1: '提现金额将原路退回支付账户',
withdrawNotice2: '提现申请提交后预计0-7个工作日到账',
withdrawNotice3: '如超时未收到,请联系客服处理',
depositRecord: '押金记录',
orderNotReturned: '当前订单尚未归还,请归还后再提现',
alreadyRefunded: '押金已退还,无需重复提现',
refundProcessing: '押金退还处理中,请耐心等待'
},
userProfile: {
title: '个人信息',
avatar: '头像',
nickname: '昵称',
phone: '手机号',
edit: '编辑',
save: '保存',
cancel: '取消',
clickToChange: '点击头像更换',
notSet: '未设置',
notBound: '未绑定',
balance: '余额',
enterNickname: '请输入新昵称',
nicknameRequired: '昵称不能为空',
saving: '保存中...',
nicknameUpdated: '昵称修改成功',
updateFailed: '修改失败',
uploading: '上传中...'
}
}
+127 -23
View File
@@ -1,35 +1,139 @@
import App from './App' import App from './App'
import { orderMonitor } from './utils/orderMonitor.js' import { orderMonitor } from './utils/orderMonitor.js'
import uView from "uview-ui";
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
// 注册全局订单监控服务
Vue.prototype.$orderMonitor = orderMonitor
App.mpType = 'app'
Vue.use(uView)
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue' import { createSSRApp } from 'vue'
import { createI18n } from 'vue-i18n'
import zhCN from './locale/zh-CN.js'
import enUS from './locale/en-US.js'
import uView from "uview-ui"
// 获取系统语言
const getSystemLanguage = () => {
let language = 'zh-CN'
try {
const systemInfo = uni.getSystemInfoSync()
if (systemInfo && systemInfo.language) {
language = systemInfo.language === 'zh' || systemInfo.language.indexOf('zh') === 0
? 'zh-CN'
: 'en-US'
}
} catch (e) {
console.error('获取系统语言失败:', e)
}
return language
}
// 获取用户选择的语言
const getSavedLanguage = () => {
try {
const savedLang = uni.getStorageSync('language')
if (savedLang) {
return savedLang
}
const systemLang = getSystemLanguage()
uni.setStorageSync('language', systemLang)
return systemLang
} catch (e) {
console.error('语言设置出错:', e)
return 'zh-CN'
}
}
// 创建 i18n 实例(Vue 3
let i18nInstance = null
function getI18nInstance() {
// 每次都重新读取当前语言
const currentLang = getSavedLanguage()
console.log('=== getI18nInstance 被调用 ===')
console.log('缓存中的语言:', currentLang)
console.log('当前 i18n 实例存在?', !!i18nInstance)
console.log('当前 i18n.global.locale:', i18nInstance?.global?.locale)
// 检查是否需要更新语言
if (i18nInstance && i18nInstance.global.locale !== currentLang) {
console.log('=== 检测到语言变化,强制更新 ===')
console.log('旧语言:', i18nInstance.global.locale)
console.log('新语言:', currentLang)
// 直接更新 locale(这应该会触发所有组件重新渲染)
i18nInstance.global.locale = currentLang
console.log('i18n.global.locale 已更新为:', i18nInstance.global.locale)
console.log('测试翻译 (common.loading):', i18nInstance.global.t('common.loading'))
console.log('测试翻译 (home.title):', i18nInstance.global.t('home.title'))
console.log('===================================')
return i18nInstance
}
// 首次创建实例
if (!i18nInstance) {
console.log('=== 首次创建 i18n 实例 ===')
i18nInstance = createI18n({
legacy: true, // 使用 Legacy API 模式,支持全局 $t
locale: currentLang,
fallbackLocale: 'zh-CN',
messages: {
'zh-CN': zhCN,
'en-US': enUS
},
silentTranslationWarn: true,
silentFallbackWarn: true
})
console.log('i18n 实例已创建,语言:', currentLang)
console.log('测试翻译 (common.loading):', i18nInstance.global.t('common.loading'))
console.log('测试翻译 (home.title):', i18nInstance.global.t('home.title'))
console.log('============================')
}
return i18nInstance
}
export function createApp() { export function createApp() {
console.log('========================================')
console.log('=== createApp 被调用 ===')
console.log('时间戳:', new Date().toLocaleTimeString())
console.log('========================================')
const app = createSSRApp(App) const app = createSSRApp(App)
// 注册全局订单监控服务到VUE3 // 使用 uView
app.use(uView)
// 获取或更新 i18n 实例
const i18n = getI18nInstance()
// 使用 i18n
app.use(i18n)
// 手动注入 $i18n 到全局属性(确保组件可以访问)
app.config.globalProperties.$i18n = i18n.global
// 注册全局订单监控服务
app.config.globalProperties.$orderMonitor = orderMonitor app.config.globalProperties.$orderMonitor = orderMonitor
// 注册全局语言切换方法(注意:建议使用 reLaunch 来切换语言以确保完全刷新)
app.config.globalProperties.$setLanguage = (lang) => {
console.log('$setLanguage 被调用,语言:', lang)
uni.setStorageSync('language', lang)
// 更新 i18n 实例的语言
if (i18n && i18n.global) {
i18n.global.locale = lang
console.log('i18n.global.locale 已更新为:', i18n.global.locale)
}
}
console.log('=== Vue 3 应用创建完成 ===')
console.log('最终 locale:', i18n.global.locale)
console.log('app.config.globalProperties.$t 存在?', !!app.config.globalProperties.$t)
console.log('app.config.globalProperties.$i18n 存在?', !!app.config.globalProperties.$i18n)
console.log('测试 $t 调用:', i18n.global.t('common.loading'))
console.log('===========================')
return { return {
app app
} }
} }
// #endif
-1764
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -4,7 +4,8 @@
"axios": "^1.7.9", "axios": "^1.7.9",
"axios-miniprogram-adapter": "0.3.4", "axios-miniprogram-adapter": "0.3.4",
"uniapp-axios-adapter": "^0.3.2", "uniapp-axios-adapter": "^0.3.2",
"uview-ui": "1.8.8" "uview-ui": "1.8.8",
"vue-i18n": "9"
}, },
"devDependencies": { "devDependencies": {
"sass": "^1.57.1", "sass": "^1.57.1",
+46 -46
View File
@@ -16,34 +16,34 @@
"enableShareTimeline": true "enableShareTimeline": true
} }
}, },
{ {
"path": "pages/login/index", "path": "pages/login/index",
"style": { "style": {
"navigationBarTitleText": "登录", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
}, },
{ {
"path": "pages/legal/agreement", "path": "pages/legal/agreement",
"style": { "style": {
"navigationBarTitleText": "用户协议", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
}, },
{ {
"path": "pages/legal/privacy", "path": "pages/legal/privacy",
"style": { "style": {
"navigationBarTitleText": "隐私政策", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
}, },
{ {
"path": "pages/my/index", "path": "pages/my/index",
"style": { "style": {
"navigationBarTitleText": "个人中心", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#D1FFE1", "navigationBarBackgroundColor": "#D1FFE1",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -51,7 +51,7 @@
{ {
"path": "pages/userProfile/index", "path": "pages/userProfile/index",
"style": { "style": {
"navigationBarTitleText": "个人信息", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#D1FFE1", "navigationBarBackgroundColor": "#D1FFE1",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -59,7 +59,7 @@
{ {
"path": "pages/setting/index", "path": "pages/setting/index",
"style": { "style": {
"navigationBarTitleText": "设置", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -67,19 +67,19 @@
{ {
"path": "pages/deposit/index", "path": "pages/deposit/index",
"style": { "style": {
"navigationBarTitleText": "押金管理" "navigationBarTitleText": ""
} }
}, },
{ {
"path": "pages/order/index", "path": "pages/order/index",
"style": { "style": {
"navigationBarTitleText": "租借记录" "navigationBarTitleText": ""
} }
}, },
{ {
"path": "pages/order/payment", "path": "pages/order/payment",
"style": { "style": {
"navigationBarTitleText": "订单支付", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -87,7 +87,7 @@
{ {
"path": "pages/expressReturn/addExpressReturn", "path": "pages/expressReturn/addExpressReturn",
"style": { "style": {
"navigationBarTitleText": "快递归还", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -95,19 +95,19 @@
{ {
"path": "pages/feedback/index", "path": "pages/feedback/index",
"style": { "style": {
"navigationBarTitleText": "投诉与建议" "navigationBarTitleText": ""
} }
}, },
{ {
"path": "pages/help/index", "path": "pages/help/index",
"style": { "style": {
"navigationBarTitleText": "帮助中心" "navigationBarTitleText": ""
} }
}, },
{ {
"path": "pages/device/detail", "path": "pages/device/detail",
"style": { "style": {
"navigationBarTitleText": "设备详情", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -116,13 +116,13 @@
{ {
"path": "pages/serve/bagCheck/index", "path": "pages/serve/bagCheck/index",
"style": { "style": {
"navigationBarTitleText": "共享风扇" "navigationBarTitleText": ""
} }
}, },
{ {
"path": "pages/return/index", "path": "pages/return/index",
"style": { "style": {
"navigationBarTitleText": "订单详情", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -130,7 +130,7 @@
{ {
"path": "pages/order/success", "path": "pages/order/success",
"style": { "style": {
"navigationBarTitleText": "支付成功", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -138,7 +138,7 @@
{ {
"path": "pages/order/return-success", "path": "pages/order/return-success",
"style": { "style": {
"navigationBarTitleText": "归还成功", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -146,7 +146,7 @@
{ {
"path": "pages/order/detail", "path": "pages/order/detail",
"style": { "style": {
"navigationBarTitleText": "订单详情", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -154,14 +154,14 @@
{ {
"path": "pages/expressReturn/index", "path": "pages/expressReturn/index",
"style": { "style": {
"navigationBarTitleText": "快递归还列表", "navigationBarTitleText": "",
"navigationStyle": "default" "navigationStyle": "default"
} }
}, },
{ {
"path": "pages/expressReturn/detail", "path": "pages/expressReturn/detail",
"style": { "style": {
"navigationBarTitleText": "归还详情", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -169,7 +169,7 @@
{ {
"path": "pages/search/index", "path": "pages/search/index",
"style": { "style": {
"navigationBarTitleText": "查找设备", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -177,7 +177,7 @@
{ {
"path": "pages/position/detail", "path": "pages/position/detail",
"style": { "style": {
"navigationBarTitleText": "附近设备详情", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#D1FFE1", "navigationBarBackgroundColor": "#D1FFE1",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -185,7 +185,7 @@
{ {
"path": "pages/join/index", "path": "pages/join/index",
"style": { "style": {
"navigationBarTitleText": "合作加盟", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -193,7 +193,7 @@
{ {
"path": "pages/waiting/index", "path": "pages/waiting/index",
"style": { "style": {
"navigationBarTitleText": "设备弹出中", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff", "navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
@@ -201,7 +201,7 @@
], ],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarTitleText": "共享风扇", "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#F8F8F8", "navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F8F8"
} }
+24 -20
View File
@@ -2,27 +2,27 @@
<view class="deposit-container"> <view class="deposit-container">
<!-- 押金金额卡片 --> <!-- 押金金额卡片 -->
<view class="deposit-card"> <view class="deposit-card">
<view class="title">押金余额</view> <view class="title">{{ $t('deposit.depositBalance') }}</view>
<view class="amount">¥{{ depositAmount }}</view> <view class="amount">¥{{ depositAmount }}</view>
<button class="withdraw-btn" @click="handleWithdraw" :disabled="depositAmount <= 0">提现</button> <button class="withdraw-btn" @click="handleWithdraw" :disabled="depositAmount <= 0">{{ $t('deposit.withdraw') }}</button>
</view> </view>
<!-- 提现说明 --> <!-- 提现说明 -->
<view class="notice-card"> <view class="notice-card">
<view class="notice-title"> <view class="notice-title">
<view class="dot"></view> <view class="dot"></view>
<text>提现说明</text> <text>{{ $t('deposit.withdrawNotice') }}</text>
</view> </view>
<view class="notice-content"> <view class="notice-content">
<view class="notice-item">1. 提现金额将原路退回支付账户</view> <view class="notice-item">1. {{ $t('deposit.withdrawNotice1') }}</view>
<view class="notice-item">2. 提现申请提交后预计0-7个工作日到账</view> <view class="notice-item">2. {{ $t('deposit.withdrawNotice2') }}</view>
<view class="notice-item">3. 如超时未收到请联系客服处理</view> <view class="notice-item">3. {{ $t('deposit.withdrawNotice3') }}</view>
</view> </view>
</view> </view>
<!-- 押金记录 --> <!-- 押金记录 -->
<view class="record-card" v-if="records.length > 0"> <view class="record-card" v-if="records.length > 0">
<view class="record-title">押金记录</view> <view class="record-title">{{ $t('deposit.depositRecord') }}</view>
<view class="record-list"> <view class="record-list">
<view class="record-item" v-for="(item, index) in records" :key="index"> <view class="record-item" v-for="(item, index) in records" :key="index">
<view class="record-info"> <view class="record-info">
@@ -53,6 +53,10 @@ export default {
} }
}, },
onLoad() { onLoad() {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('deposit.title')
})
// this.loadUserInfo() // this.loadUserInfo()
}, },
onShow() { onShow() {
@@ -84,7 +88,7 @@ export default {
} catch (error) { } catch (error) {
console.error('获取用户信息失败:', error) console.error('获取用户信息失败:', error)
uni.showToast({ uni.showToast({
title: '获取用户信息失败', title: this.$t('user.getUserInfoFailed'),
icon: 'none' icon: 'none'
}) })
} }
@@ -92,7 +96,7 @@ export default {
async handleWithdraw() { async handleWithdraw() {
if (parseFloat(this.depositAmount) <= 0) { if (parseFloat(this.depositAmount) <= 0) {
uni.showToast({ uni.showToast({
title: '无可提现余额', title: this.$t('deposit.noBalance'),
icon: 'none' icon: 'none'
}) })
return return
@@ -111,12 +115,12 @@ export default {
// } // }
uni.showModal({ uni.showModal({
title: '确认提现', title: this.$t('deposit.confirmWithdraw'),
content: '押金将原路退回,预计0-7个工作日到账', content: this.$t('deposit.withdrawDesc'),
success: async (res) => { success: async (res) => {
if (res.confirm) { if (res.confirm) {
uni.showLoading({ uni.showLoading({
title: '提现中...' title: this.$t('deposit.withdrawing')
}) })
try { try {
@@ -127,7 +131,7 @@ export default {
if (result.code === 200) { if (result.code === 200) {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: '提现申请已提交', title: this.$t('deposit.withdrawSubmitted'),
icon: 'success' icon: 'success'
}) })
@@ -144,26 +148,26 @@ export default {
this.loadUserInfo() this.loadUserInfo()
}, 1500) }, 1500)
} else { } else {
throw new Error(result.msg || '提现失败') throw new Error(result.msg || this.$t('deposit.withdrawFailed'))
} }
} catch (error) { } catch (error) {
console.error('提现失败:', error) console.error('提现失败:', error)
uni.hideLoading() uni.hideLoading()
// 更详细的错误处理 // 更详细的错误处理
let errorMessage = '提现失败,请稍后再试'; let errorMessage = this.$t('deposit.withdrawFailed');
// 如果有具体错误信息,使用它 // 如果有具体错误信息,使用它
if (error.message) { if (error.message) {
// 常见错误消息处理 // 常见错误消息处理
if (error.message.includes('尚未归还')) { if (error.message.includes('尚未归还')) {
errorMessage = '当前订单尚未归还,请归还后再提现'; errorMessage = this.$t('deposit.orderNotReturned');
} else if (error.message.includes('已退还')) { } else if (error.message.includes('已退还')) {
errorMessage = '押金已退还,无需重复提现'; errorMessage = this.$t('deposit.alreadyRefunded');
} else if (error.message.includes('处理中')) { } else if (error.message.includes('处理中')) {
errorMessage = '押金退还处理中,请耐心等待'; errorMessage = this.$t('deposit.refundProcessing');
} else if (error.message.includes('余额为0')) { } else if (error.message.includes('余额为0')) {
errorMessage = '账户余额为0,无法提现'; errorMessage = this.$t('deposit.noBalance');
} else { } else {
// 使用后端返回的具体错误消息 // 使用后端返回的具体错误消息
errorMessage = error.message; errorMessage = error.message;
@@ -172,7 +176,7 @@ export default {
// 显示错误提示 // 显示错误提示
uni.showModal({ uni.showModal({
title: '提现失败', title: this.$t('deposit.withdrawFailed'),
content: errorMessage, content: errorMessage,
showCancel: false showCancel: false
}) })
+55 -49
View File
@@ -12,7 +12,7 @@
</view> </view>
</view> </view>
<view class="device-id"> <view class="device-id">
<text class="id-label">设备号</text> <text class="id-label">{{ $t('device.deviceNo') }}</text>
<text class="id-value">{{ deviceId }}</text> <text class="id-value">{{ deviceId }}</text>
</view> </view>
</view> </view>
@@ -20,17 +20,17 @@
<!-- 计费规则 --> <!-- 计费规则 -->
<view class="card pricing-card"> <view class="card pricing-card">
<view class="card-header"> <view class="card-header">
<text class="card-title">计费规则</text> <text class="card-title">{{ $t('device.pricingRules') }}</text>
</view> </view>
<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">¥</text>
<text class="price">{{ deviceFeeConfig.maxHourPrice || '5.00' }}</text> <text class="price">{{ deviceFeeConfig.maxHourPrice || '5.00' }}</text>
<text class="unit">/小时</text> <text class="unit">/{{ $t('time.hour') }}</text>
</view> </view>
<view class="cap-badge"> <view class="cap-badge">
<text class="cap-text">{{ deviceInfo.depositAmount || '99' }}元封顶</text> <text class="cap-text">{{ deviceInfo.depositAmount || '99' }}{{ $t('device.capLimit') }}</text>
</view> </view>
</view> </view>
@@ -51,20 +51,20 @@
<!-- 使用说明 --> <!-- 使用说明 -->
<view class="card notice-card"> <view class="card notice-card">
<view class="card-header"> <view class="card-header">
<text class="card-title">使用说明</text> <text class="card-title">{{ $t('device.usageInstructions') }}</text>
</view> </view>
<view class="notice-items"> <view class="notice-items">
<view class="notice-item"> <view class="notice-item">
<view class="notice-dot"></view> <view class="notice-dot"></view>
<text class="notice-text">请在使用前检查设备是否完好</text> <text class="notice-text">{{ $t('device.checkBeforeUse') }}</text>
</view> </view>
<view class="notice-item"> <view class="notice-item">
<view class="notice-dot"></view> <view class="notice-dot"></view>
<text class="notice-text">超出使用时间将自动按小时计费</text> <text class="notice-text">{{ $t('device.autoChargeOvertime') }}</text>
</view> </view>
<view class="notice-item"> <view class="notice-item">
<view class="notice-dot"></view> <view class="notice-dot"></view>
<text class="notice-text">请在指定区域内使用设备</text> <text class="notice-text">{{ $t('device.useInDesignatedArea') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -73,11 +73,11 @@
<view class="footer"> <view class="footer">
<button class="rent-button" :class="{ 'return-button': hasActiveOrder }" <button class="rent-button" :class="{ 'return-button': hasActiveOrder }"
@click="handleRent('wx-score-pay')"> @click="handleRent('wx-score-pay')">
<text>{{ hasActiveOrder ? '归还设备' : '免押金租借' }}</text> <text>{{ hasActiveOrder ? $t('order.returnDevice') : $t('device.rentDepositFree') }}</text>
</button> </button>
<view class="wechat-credit"> <view class="wechat-credit">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="wx-icon"></image> <image src="/static/images/wxpayflag.png" mode="aspectFit" class="wx-icon"></image>
<text class="credit-text">微信支付分 <text class="divider">|</text> 550分以上优享</text> <text class="credit-text">{{ $t('device.wxPayScoreDesc') }}</text>
</view> </view>
</view> </view>
@@ -86,17 +86,17 @@
<view class="popup-mask" @click.stop></view> <view class="popup-mask" @click.stop></view>
<view class="popup-content"> <view class="popup-content">
<view class="popup-header"> <view class="popup-header">
<text class="popup-title">授权获取手机号</text> <text class="popup-title">{{ $t('auth.authTitle') }}</text>
</view> </view>
<view class="popup-body"> <view class="popup-body">
<view class="auth-desc"> <view class="auth-desc">
<text>为了提供更好的服务需要授权获取您的手机号</text> <text>{{ $t('auth.authDescShort') }}</text>
</view> </view>
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber"> <button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
一键获取手机号 {{ $t('auth.getPhoneNumber') }}
</button> </button>
<view class="auth-cancel" @click="showPhoneAuthPopup = false"> <view class="auth-cancel" @click="showPhoneAuthPopup = false">
<text>暂不授权</text> <text>{{ $t('auth.notNow') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -133,6 +133,9 @@
getUserInfo, getUserInfo,
getUserPhoneNumber getUserPhoneNumber
} from '@/util/index.js' } from '@/util/index.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
// 响应式状态 // 响应式状态
const deviceInfo = ref({}) const deviceInfo = ref({})
@@ -143,7 +146,7 @@
const batteryLevel = ref(95) const batteryLevel = ref(95)
const hasActiveOrder = ref(false) const hasActiveOrder = ref(false)
const deviceStatus = reactive({ const deviceStatus = reactive({
text: '可使用', get text() { return $t('device.available') },
class: 'available' class: 'available'
}) })
const isLoggedIn = ref(true) const isLoggedIn = ref(true)
@@ -164,6 +167,9 @@
}) })
onMounted(async () => { onMounted(async () => {
uni.setNavigationBarTitle({
title: $t('device.deviceInfo')
})
await checkUserPhone() await checkUserPhone()
await fetchDeviceInfo() await fetchDeviceInfo()
}) })
@@ -195,7 +201,7 @@
// 用户拒绝授权的情况 // 用户拒绝授权的情况
if (e.detail.errMsg && e.detail.errMsg.includes('deny')) { if (e.detail.errMsg && e.detail.errMsg.includes('deny')) {
uni.showToast({ uni.showToast({
title: '需要授权手机号才能使用设备', title: $t('auth.phoneRequired'),
icon: 'none' icon: 'none'
}) })
return return
@@ -204,7 +210,7 @@
// 获取到授权code // 获取到授权code
if (e.detail.code) { if (e.detail.code) {
uni.showLoading({ uni.showLoading({
title: '获取中...' title: $t('auth.getting')
}) })
console.log('获取到的授权code:', e.detail.code) console.log('获取到的授权code:', e.detail.code)
@@ -236,15 +242,15 @@
showPhoneAuthPopup.value = false showPhoneAuthPopup.value = false
uni.showToast({ uni.showToast({
title: '手机号获取成功', title: $t('auth.phoneSuccess'),
icon: 'success' icon: 'success'
}) })
} else { } else {
// 记录详细信息,不抛出错误 // 记录详细信息,不抛出错误
console.warn('获取手机号响应异常:', res.msg || '未知错误') console.warn('获取手机号响应异常:', res.msg || '未知错误')
uni.showModal({ uni.showModal({
title: '获取手机号异常', title: $t('auth.phoneError'),
content: `状态码: ${res.code}, 消息: ${res.msg || '无'}`, content: `${$t('common.statusCode')}: ${res.code}, ${$t('common.message')}: ${res.msg || $t('common.none')}`,
showCancel: false showCancel: false
}) })
} }
@@ -256,8 +262,8 @@
// 显示更详细的错误信息 // 显示更详细的错误信息
let errMsg = err.message || err.toString() let errMsg = err.message || err.toString()
uni.showModal({ uni.showModal({
title: '获取手机号失败', title: $t('auth.phoneGetFailed'),
content: '错误信息: ' + errMsg, content: $t('common.errorInfo') + ': ' + errMsg,
showCancel: false showCancel: false
}) })
}) })
@@ -265,14 +271,14 @@
uni.hideLoading() uni.hideLoading()
console.error('获取手机号外部错误:', outerError) console.error('获取手机号外部错误:', outerError)
uni.showModal({ uni.showModal({
title: '意外错误', title: $t('common.unexpectedError'),
content: '处理过程发生异常: ' + (outerError.message || outerError), content: $t('common.processException') + ': ' + (outerError.message || outerError),
showCancel: false showCancel: false
}) })
} }
} else { } else {
uni.showToast({ uni.showToast({
title: '获取授权码失败', title: $t('auth.authCodeFailed'),
icon: 'none' icon: 'none'
}) })
} }
@@ -296,16 +302,16 @@
deviceLocation.value = res.data.position.name deviceLocation.value = res.data.position.name
} }
// 更新设备状态 // 更新设备状态
if (deviceInfo.value.status) { if (deviceInfo.value.status) {
if (deviceInfo.value.status === 'online') { if (deviceInfo.value.status === 'online') {
deviceStatus.text = '可使用' deviceStatus.text = $t('device.available')
deviceStatus.class = 'available' deviceStatus.class = 'available'
} else if (deviceInfo.value.status === 'offline') { } else if (deviceInfo.value.status === 'offline') {
deviceStatus.text = '离线' deviceStatus.text = $t('device.offline')
deviceStatus.class = 'offline' deviceStatus.class = 'offline'
}
} }
}
if (deviceInfo.value.feeConfig) { if (deviceInfo.value.feeConfig) {
deviceFeeConfig.value = JSON.parse(deviceInfo.value.feeConfig)[0] || {} deviceFeeConfig.value = JSON.parse(deviceInfo.value.feeConfig)[0] || {}
} else { } else {
@@ -321,9 +327,9 @@
// 显示登录提示 // 显示登录提示
const showLoginTip = () => { const showLoginTip = () => {
uni.showModal({ uni.showModal({
title: '提示', title: $t('common.tips'),
content: '请先登录后再操作', content: $t('common.loginRequired'),
confirmText: '去登录', confirmText: $t('auth.goToLogin'),
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
uni.navigateTo({ uni.navigateTo({
@@ -358,7 +364,7 @@
} }
} catch (error) { } catch (error) {
uni.showToast({ uni.showToast({
title: '订单状态查询失败', title: $t('order.getOrderStatusFailed'),
icon: 'none' icon: 'none'
}) })
} }
@@ -444,6 +450,9 @@
// 提交租借订单 // 提交租借订单
const submitRentOrder = async (payWay) => { const submitRentOrder = async (payWay) => {
try { try {
uni.showLoading({
title: $t('common.processing')
})
// --- 第一步:先请求订阅消息(必须在用户点击的同步上下文中)--- // --- 第一步:先请求订阅消息(必须在用户点击的同步上下文中)---
if (payWay === 'wx-score-pay') { if (payWay === 'wx-score-pay') {
console.log('准备请求订阅消息(在异步操作之前),时间:', new Date().toLocaleTimeString()); console.log('准备请求订阅消息(在异步操作之前),时间:', new Date().toLocaleTimeString());
@@ -471,14 +480,11 @@
} }
// --- 订阅消息请求完成 --- // --- 订阅消息请求完成 ---
uni.showLoading({
title: '处理中'
})
console.log(deviceId.value); console.log(deviceId.value);
// 调用设备租借接口 // 调用设备租借接口
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value) const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value)
if (rentResult.code !== 200) { if (rentResult.code !== 200) {
throw new Error(rentResult.msg || '设备租借失败') throw new Error(rentResult.msg || $t('device.rentFailed'))
} }
// 获取后端返回的订单信息 // 获取后端返回的订单信息
@@ -556,14 +562,14 @@
// 用户取消授权,需要取消订单 // 用户取消授权,需要取消订单
try { try {
uni.showLoading({ uni.showLoading({
title: '取消订单中' title: $t('order.cancelling')
}); });
const cancelRes = await cancelOrder({ orderId: order.orderNo }); const cancelRes = await cancelOrder({ orderId: order.orderNo });
console.log('订单取消结果:', cancelRes); console.log('订单取消结果:', cancelRes);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '已取消订单', title: $t('order.orderCancelled'),
icon: 'none', icon: 'none',
duration: 2000 duration: 2000
}); });
@@ -578,7 +584,7 @@
console.error('取消订单失败:', cancelError); console.error('取消订单失败:', cancelError);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '取消订单失败,请联系客服', title: $t('order.cancelFailedContactService'),
icon: 'none' icon: 'none'
}); });
} }
@@ -589,7 +595,7 @@
// 支付分调用异常,也需要取消订单 // 支付分调用异常,也需要取消订单
try { try {
uni.showLoading({ uni.showLoading({
title: '取消订单中' title: $t('order.cancelling')
}); });
const cancelRes = await cancelOrder({ orderId: order.orderNo }); const cancelRes = await cancelOrder({ orderId: order.orderNo });
console.log('订单取消结果:', cancelRes); console.log('订单取消结果:', cancelRes);
@@ -600,7 +606,7 @@
} }
uni.showToast({ uni.showToast({
title: '支付分调用失败,订单已取消', title: $t('device.payScoreFailedCancelled'),
icon: 'none' icon: 'none'
}); });
@@ -612,7 +618,7 @@
} }
} else { } else {
uni.showToast({ uni.showToast({
title: res?.msg || '获取支付参数失败', title: res?.msg || $t('device.getPayParamsFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -620,7 +626,7 @@
} catch (error) { } catch (error) {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: error.message || '租借失败,请重试', title: error.message || $t('device.rentFailedRetry'),
icon: 'none' icon: 'none'
}) })
} }
+34 -23
View File
@@ -3,32 +3,32 @@
<!-- 订单摘要卡片 --> <!-- 订单摘要卡片 -->
<view class="order-summary-card" v-if="orderInfo.orderNo"> <view class="order-summary-card" v-if="orderInfo.orderNo">
<view class="summary-row"> <view class="summary-row">
<view class="label">订单号</view> <view class="label">{{ $t('order.orderNo') }}</view>
<view class="value">{{ orderInfo.orderNo }}</view> <view class="value">{{ orderInfo.orderNo }}</view>
</view> </view>
<view class="summary-row" v-if="orderInfo.deviceNo"> <view class="summary-row" v-if="orderInfo.deviceNo">
<view class="label">设备号</view> <view class="label">{{ $t('order.deviceNo') }}</view>
<view class="value">{{ orderInfo.deviceNo }}</view> <view class="value">{{ orderInfo.deviceNo }}</view>
</view> </view>
<view class="summary-row" v-if="orderInfo.startTime"> <view class="summary-row" v-if="orderInfo.startTime">
<view class="label">开放时间</view> <view class="label">{{ $t('express.openTime') }}</view>
<view class="value">{{ orderInfo.startTime }}</view> <view class="value">{{ orderInfo.startTime }}</view>
</view> </view>
</view> </view>
<!-- 表单区域 --> <!-- 表单区域 -->
<view class="form-section"> <view class="form-section">
<view class="form-title">填写快递归还信息</view> <view class="form-title">{{ $t('express.fillExpressInfo') }}</view>
<view class="input-wrapper"> <view class="input-wrapper">
<view class="input-label">联系电话</view> <view class="input-label">{{ $t('express.contactPhone') }}</view>
<input class="input-field" type="number" v-model="phone" maxlength="20" /> <input class="input-field" type="number" v-model="phone" maxlength="20" />
</view> </view>
<view class="input-wrapper"> <view class="input-wrapper">
<view class="input-label">快递单号</view> <view class="input-label">{{ $t('express.expressNo') }}</view>
<input class="input-field" type="text" v-model="trackingNumber" <input class="input-field" type="text" v-model="trackingNumber"
:placeholder="isFillMode ? '请输入需要补填的快递单号' : '请输入快递单号(可先留空)'" maxlength="40" /> :placeholder="isFillMode ? $t('express.fillTrackingPlaceholder') : $t('express.trackingPlaceholder')" maxlength="40" />
</view> </view>
<view class="tips" v-if="tipsText">{{ tipsText }}</view> <view class="tips" v-if="tipsText">{{ tipsText }}</view>
@@ -36,7 +36,7 @@
<!-- 提交按钮 --> <!-- 提交按钮 -->
<view class="submit-btn" :class="{ disabled: submitting }" @click="!submitting && handleSubmit()"> <view class="submit-btn" :class="{ disabled: submitting }" @click="!submitting && handleSubmit()">
{{ isFillMode ? '确认补填' : '提交信息' }} {{ isFillMode ? $t('express.confirmFill') : $t('express.submitInfo') }}
</view> </view>
</view> </view>
</template> </template>
@@ -44,7 +44,9 @@
<script setup> <script setup>
import { import {
ref, ref,
reactive reactive,
getCurrentInstance,
onMounted
} from 'vue' } from 'vue'
import { import {
onLoad onLoad
@@ -58,6 +60,15 @@
getExpressReturnDetail, getExpressReturnDetail,
fillExpressTrackingNumber fillExpressTrackingNumber
} from '@/config/api/expressReturn.js' } from '@/config/api/expressReturn.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('express.fillExpress')
})
})
const orderId = ref('') const orderId = ref('')
const recordId = ref('') const recordId = ref('')
@@ -85,7 +96,7 @@
if (!orderId.value) { if (!orderId.value) {
uni.showToast({ uni.showToast({
title: '缺少订单号', title: $t('express.orderNoMissing'),
icon: 'none' icon: 'none'
}) })
setTimeout(() => { setTimeout(() => {
@@ -100,7 +111,7 @@
const loadOrder = async () => { const loadOrder = async () => {
try { try {
uni.showLoading({ uni.showLoading({
title: '加载中' title: $t('common.loading')
}) })
const res = await queryById(orderId.value) const res = await queryById(orderId.value)
if (res?.code === 200 && res.data) { if (res?.code === 200 && res.data) {
@@ -110,11 +121,11 @@
// 默认联系电话可回填订单上的手机号(若有) // 默认联系电话可回填订单上的手机号(若有)
if (res.data.phone && !phone.value) phone.value = res.data.phone if (res.data.phone && !phone.value) phone.value = res.data.phone
} else { } else {
throw new Error(res?.msg || '获取订单失败') throw new Error(res?.msg || $t('order.getOrderFailed'))
} }
} catch (e) { } catch (e) {
uni.showToast({ uni.showToast({
title: e.message || '加载失败', title: e.message || $t('express.loadFailed'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -125,7 +136,7 @@
const loadRecordAndOrderByRecord = async () => { const loadRecordAndOrderByRecord = async () => {
try { try {
uni.showLoading({ uni.showLoading({
title: '加载中' title: $t('common.loading')
}) })
const res = await getExpressReturnDetail(recordId.value) const res = await getExpressReturnDetail(recordId.value)
if (res?.code === 200 && res.data) { if (res?.code === 200 && res.data) {
@@ -135,11 +146,11 @@
} }
if (res.data.userPhone && !phone.value) phone.value = res.data.userPhone if (res.data.userPhone && !phone.value) phone.value = res.data.userPhone
} else { } else {
throw new Error(res?.msg || '获取记录失败') throw new Error(res?.msg || $t('express.getRecordFailed'))
} }
} catch (e) { } catch (e) {
uni.showToast({ uni.showToast({
title: e.message || '加载失败', title: e.message || $t('express.loadFailed'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -155,10 +166,10 @@
if (rec.status === 0) { if (rec.status === 0) {
recordId.value = rec.id recordId.value = rec.id
uni.showModal({ uni.showModal({
title: '提示', title: $t('common.tips'),
content: '已存在快递归还申请,是否前往补填快递单号?', content: $t('express.existingReturnNotice'),
confirmText: '去补填', confirmText: $t('express.goToFill'),
cancelText: '取消', cancelText: $t('common.cancel'),
success: (r) => { success: (r) => {
if (r.confirm) { if (r.confirm) {
uni.redirectTo({ uni.redirectTo({
@@ -170,7 +181,7 @@
return return
} else { } else {
uni.showToast({ uni.showToast({
title: '已有归还记录', title: $t('express.alreadyHasRecord'),
icon: 'none' icon: 'none'
}) })
setTimeout(() => { setTimeout(() => {
@@ -190,14 +201,14 @@
const digits = (phone.value || '').replace(/\D/g, '') const digits = (phone.value || '').replace(/\D/g, '')
if (!digits || digits.length < 5) { if (!digits || digits.length < 5) {
uni.showToast({ uni.showToast({
title: '请填写有效联系电话', title: $t('express.pleaseEnterValidPhone'),
icon: 'none' icon: 'none'
}) })
return false return false
} }
if (isFillMode.value && !trackingNumber.value) { if (isFillMode.value && !trackingNumber.value) {
uni.showToast({ uni.showToast({
title: '请填写快递单号', title: $t('express.pleaseEnterTrackingNo'),
icon: 'none' icon: 'none'
}) })
return false return false
+36 -29
View File
@@ -16,23 +16,23 @@
<!-- 快递信息卡片 --> <!-- 快递信息卡片 -->
<view class="info-card"> <view class="info-card">
<view class="card-title"> <view class="card-title">
<text class="title-text">快递信息</text> <text class="title-text">{{ $t('express.expressInfo') }}</text>
</view> </view>
<view class="info-list"> <view class="info-list">
<view class="info-item"> <view class="info-item">
<text class="label">快递公司</text> <text class="label">{{ $t('express.expressCompany') }}</text>
<text class="value">{{ detailData.expressCompany }}</text> <text class="value">{{ detailData.expressCompany }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">运单号</text> <text class="label">{{ $t('express.trackingNo') }}</text>
<text class="value tracking-number">{{ detailData.trackingNumber }}</text> <text class="value tracking-number">{{ detailData.trackingNumber }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">包裹类型</text> <text class="label">{{ $t('express.packageType') }}</text>
<text class="value">{{ detailData.packageType }}</text> <text class="value">{{ detailData.packageType }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">包裹重量</text> <text class="label">{{ $t('express.packageWeight') }}</text>
<text class="value">{{ detailData.weight }}</text> <text class="value">{{ detailData.weight }}</text>
</view> </view>
</view> </view>
@@ -41,23 +41,23 @@
<!-- 归还信息卡片 --> <!-- 归还信息卡片 -->
<view class="info-card"> <view class="info-card">
<view class="card-title"> <view class="card-title">
<text class="title-text">归还信息</text> <text class="title-text">{{ $t('express.returnInfo') }}</text>
</view> </view>
<view class="info-list"> <view class="info-list">
<view class="info-item"> <view class="info-item">
<text class="label">归还地址</text> <text class="label">{{ $t('express.returnAddress') }}</text>
<text class="value address">{{ detailData.returnAddress }}</text> <text class="value address">{{ detailData.returnAddress }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">归还时间</text> <text class="label">{{ $t('express.returnTime') }}</text>
<text class="value">{{ detailData.returnTime }}</text> <text class="value">{{ detailData.returnTime }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">处理时间</text> <text class="label">{{ $t('express.processTime') }}</text>
<text class="value">{{ detailData.processTime || '--' }}</text> <text class="value">{{ detailData.processTime || '--' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">完成时间</text> <text class="label">{{ $t('express.completeTime') }}</text>
<text class="value">{{ detailData.completeTime || '--' }}</text> <text class="value">{{ detailData.completeTime || '--' }}</text>
</view> </view>
</view> </view>
@@ -66,7 +66,7 @@
<!-- 备注信息卡片 --> <!-- 备注信息卡片 -->
<view class="info-card" v-if="detailData.remark"> <view class="info-card" v-if="detailData.remark">
<view class="card-title"> <view class="card-title">
<text class="title-text">备注信息</text> <text class="title-text">{{ $t('express.remarkInfo') }}</text>
</view> </view>
<view class="remark-content"> <view class="remark-content">
<text class="remark-text">{{ detailData.remark }}</text> <text class="remark-text">{{ detailData.remark }}</text>
@@ -76,10 +76,10 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="action-buttons"> <view class="action-buttons">
<button class="action-btn primary" @click="handleCopyTracking"> <button class="action-btn primary" @click="handleCopyTracking">
<text class="btn-text">复制运单号</text> <text class="btn-text">{{ $t('express.copyTrackingNo') }}</text>
</button> </button>
<button class="action-btn secondary" @click="handleContactService"> <button class="action-btn secondary" @click="handleContactService">
<text class="btn-text">联系客服</text> <text class="btn-text">{{ $t('user.customerService') }}</text>
</button> </button>
</view> </view>
</view> </view>
@@ -89,6 +89,9 @@
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'
const { t: $t } = useI18n()
// 详情数据 // 详情数据
const detailData = ref({ const detailData = ref({
@@ -128,21 +131,21 @@ const getStatusIcon = (status) => {
// 获取状态文本 // 获取状态文本
const getStatusText = (status) => { const getStatusText = (status) => {
const textMap = { const textMap = {
'completed': '归还完成', 'completed': $t('express.returnCompleted'),
'processing': '处理中', 'processing': $t('express.processing'),
'pending': '待处理' 'pending': $t('express.pending')
} }
return textMap[status] || '待处理' return textMap[status] || $t('express.pending')
} }
// 获取状态描述 // 获取状态描述
const getStatusDesc = (status) => { const getStatusDesc = (status) => {
const descMap = { const descMap = {
'completed': '您的快递已成功归还', 'completed': $t('express.returnCompletedDesc'),
'processing': '正在处理您的归还请求', 'processing': $t('express.processingDesc'),
'pending': '等待处理归还申请' 'pending': $t('express.pendingDesc')
} }
return descMap[status] || '等待处理归还申请' return descMap[status] || $t('express.pendingDesc')
} }
// 复制运单号 // 复制运单号
@@ -151,7 +154,7 @@ const handleCopyTracking = () => {
data: detailData.value.trackingNumber, data: detailData.value.trackingNumber,
success: () => { success: () => {
uni.showToast({ uni.showToast({
title: '运单号已复制', title: $t('express.trackingNoCopied'),
icon: 'success' icon: 'success'
}) })
} }
@@ -162,10 +165,10 @@ const handleCopyTracking = () => {
const handleContactService = () => { const handleContactService = () => {
const customerPhone = getCustomerPhone() const customerPhone = getCustomerPhone()
uni.showModal({ uni.showModal({
title: '联系客服', title: $t('user.customerService'),
content: `客服电话:${customerPhone}\n工作时间:周一至周日 09:00-22:00`, content: `${$t('help.phone')}${customerPhone}\n${$t('help.workingHours')}${$t('express.workingHours')}`,
confirmText: '拨打', confirmText: $t('express.call'),
cancelText: '取消', cancelText: $t('common.cancel'),
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
uni.makePhoneCall({ uni.makePhoneCall({
@@ -178,12 +181,16 @@ const handleContactService = () => {
// 页面加载时获取详情数据 // 页面加载时获取详情数据
onMounted(async () => { onMounted(async () => {
uni.setNavigationBarTitle({
title: $t('express.returnDetail')
})
const pages = getCurrentPages() const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] const currentPage = pages[pages.length - 1]
const options = currentPage.options || {} const options = currentPage.options || {}
if (!options.id) return if (!options.id) return
try { try {
uni.showLoading({ title: '加载中' }) uni.showLoading({ title: $t('common.loading') })
const res = await getExpressReturnDetail(options.id) const res = await getExpressReturnDetail(options.id)
if (res && res.code === 200 && res.data) { if (res && res.code === 200 && res.data) {
const r = res.data const r = res.data
@@ -201,10 +208,10 @@ onMounted(async () => {
remark: r.remark || '' remark: r.remark || ''
} }
} else { } else {
throw new Error(res?.msg || '获取详情失败') throw new Error(res?.msg || $t('express.getDetailFailed'))
} }
} catch (e) { } catch (e) {
uni.showToast({ title: e.message || '加载失败', icon: 'none' }) uni.showToast({ title: e.message || $t('express.loadFailed'), icon: 'none' })
} finally { } finally {
uni.hideLoading() uni.hideLoading()
} }
+31 -22
View File
@@ -2,13 +2,13 @@
<view class="express-return-container"> <view class="express-return-container">
<!-- 收件信息卡片 --> <!-- 收件信息卡片 -->
<view class="recipient-info-card"> <view class="recipient-info-card">
<view class="info-header">收件信息</view> <view class="info-header">{{ $t('express.recipientInfo') }}</view>
<view class="info-content"> <view class="info-content">
<text class="recipient-name">风电者 18163601305</text> <text class="recipient-name">{{ $t('express.recipientName') }}</text>
<text class="recipient-address">湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623</text> <text class="recipient-address">{{ $t('express.recipientAddress') }}</text>
</view> </view>
<view class="copy-all-btn" @click="copyAllInfo"> <view class="copy-all-btn" @click="copyAllInfo">
<text class="btn-text">一键复制全部信息</text> <text class="btn-text">{{ $t('express.copyAllInfo') }}</text>
</view> </view>
</view> </view>
@@ -26,9 +26,9 @@
<text class="status-label">{{ getStatusText(item.status) }}</text> <text class="status-label">{{ getStatusText(item.status) }}</text>
</view> </view>
<view class="content-body"> <view class="content-body">
<text class="info-text">订单号{{ item.orderId || '' }}</text> <text class="info-text">{{ $t('order.orderNo') }}{{ item.orderId || '' }}</text>
<text class="info-text">快递单号{{ item.trackingNumber || '待填写' }}</text> <text class="info-text">{{ $t('express.expressNo') }}{{ item.trackingNumber || $t('express.toFill') }}</text>
<text class="info-text">用户电话{{ item.userPhone || '' }}</text> <text class="info-text">{{ $t('express.userPhone') }}{{ item.userPhone || '' }}</text>
</view> </view>
</view> </view>
@@ -44,7 +44,7 @@
<!-- 空状态 --> <!-- 空状态 -->
<view class="empty-state" v-if="returnList.length === 0"> <view class="empty-state" v-if="returnList.length === 0">
<view class="empty-icon">📦</view> <view class="empty-icon">📦</view>
<text class="empty-text">暂无归还记录</text> <text class="empty-text">{{ $t('express.noReturnRecord') }}</text>
</view> </view>
</view> </view>
</template> </template>
@@ -52,11 +52,21 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { getExpressReturnList } from '@/config/api/expressReturn.js' import { getExpressReturnList } from '@/config/api/expressReturn.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const returnList = ref([]) const returnList = ref([])
const loading = ref(false) const loading = ref(false)
const query = ref({ pageNum: 1, pageSize: 20 }) const query = ref({ pageNum: 1, pageSize: 20 })
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('express.returnRecord')
})
loadList()
})
// 收件信息 // 收件信息
const recipientName = '风电者 18163601305' const recipientName = '风电者 18163601305'
const recipientAddress = '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623' const recipientAddress = '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623'
@@ -83,10 +93,10 @@ const loadList = async () => {
remark: r.remark remark: r.remark
})) }))
} else { } else {
throw new Error(res?.msg || '获取列表失败') throw new Error(res?.msg || $t('express.getListFailed'))
} }
} catch (e) { } catch (e) {
uni.showToast({ title: e.message || '加载失败', icon: 'none' }) uni.showToast({ title: e.message || $t('express.loadFailed'), icon: 'none' })
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -108,27 +118,27 @@ const getStatusClass = (status) => ({
}[status] || 'status-pending') }[status] || 'status-pending')
const getStatusText = (status) => ({ const getStatusText = (status) => ({
'completed': '暂停计费中', 'completed': $t('express.billingPaused'),
'processing': '暂停计费中', 'processing': $t('express.billingPaused'),
'pending': '暂停计费中' 'pending': $t('express.billingPaused')
}[status] || '暂停计费中') }[status] || $t('express.billingPaused'))
const getStatusBadge = (status) => ({ const getStatusBadge = (status) => ({
'completed': '已完成', 'completed': $t('express.completed'),
'processing': '处理中', 'processing': $t('express.processing'),
'pending': '待处理' 'pending': $t('express.pending')
}[status] || '待处理') }[status] || $t('express.pending'))
// 一键复制全部信息 // 一键复制全部信息
const copyAllInfo = () => { const copyAllInfo = () => {
const allInfo = `收件人:${recipientName}\n收件地址${recipientAddress}` const allInfo = `${$t('express.recipient')}${recipientName}\n${$t('express.recipientAddressLabel')}${recipientAddress}`
uni.setClipboardData({ uni.setClipboardData({
data: allInfo, data: allInfo,
success: () => { success: () => {
uni.showToast({ title: '全部信息已复制', icon: 'success' }) uni.showToast({ title: $t('express.copySuccess'), icon: 'success' })
}, },
fail: () => { fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' }) uni.showToast({ title: $t('express.copyFailed'), icon: 'none' })
} }
}) })
} }
@@ -144,7 +154,6 @@ const handleItemClick = (item) => {
} }
} }
onMounted(loadList)
</script> </script>
<style lang="scss"> <style lang="scss">
+51 -41
View File
@@ -1,58 +1,59 @@
<template> <template>
<view class="feedback-container"> <view class="feedback-container">
<!-- <form> --> <!-- <form> -->
<!-- 问题类型选择 --> <!-- 问题类型选择 -->
<view class="type-section"> <view class="type-section">
<view class="section-title">问题类型</view> <view class="section-title">{{ $t('feedback.issueType') }}</view>
<view class="type-grid"> <view class="type-grid">
<view v-for="(type, index) in types" :key="index" class="type-item" <view v-for="(type, index) in types" :key="index" class="type-item"
:class="{ active: selectedType === index }" @click="selectType(index)"> :class="{ active: selectedType === index }" @click="selectType(index)">
{{ type }} {{ type }}
</view>
</view> </view>
</view> </view>
</view>
<!-- 问题描述 --> <!-- 问题描述 -->
<view class="description-section"> <view class="description-section">
<view class="section-title">问题描述</view> <view class="section-title">{{ $t('feedback.issueDescription') }}</view>
<textarea class="description-input" v-model="description" placeholder="请详细描述您遇到的问题,以便我们更好地为您解决" <textarea class="description-input" v-model="description" :placeholder="$t('feedback.placeholder')"
maxlength="500" name="description" /> maxlength="500" name="description" />
<view class="word-count">{{ description.length }}/500</view> <view class="word-count">{{ description.length }}/500</view>
</view> </view>
<!-- 图片上传 --> <!-- 图片上传 -->
<view class="upload-section"> <view class="upload-section">
<view class="section-title">图片上传选填</view> <view class="section-title">{{ $t('feedback.imageUpload') }}</view>
<view class="upload-grid"> <view class="upload-grid">
<view class="upload-item" v-for="(img, index) in images" :key="index"> <view class="upload-item" v-for="(img, index) in images" :key="index">
<image :src="img" mode="aspectFill" /> <image :src="img" mode="aspectFill" />
<view class="delete-btn" @click="deleteImage(index)">×</view> <view class="delete-btn" @click="deleteImage(index)">×</view>
</view> </view>
<view class="upload-btn" @click="chooseImage" v-if="images.length < 3"> <view class="upload-btn" @click="chooseImage" v-if="images.length < 3">
<text class="plus">+</text> <text class="plus">+</text>
<text class="tip">上传图片</text> <text class="tip">{{ $t('feedback.uploadImage') }}</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 联系方式 --> <!-- 联系方式 -->
<view class="contact-section"> <view class="contact-section">
<view class="section-title">联系方式</view> <view class="section-title">{{ $t('feedback.contactInfo') }}</view>
<input class="contact-input" v-model="contact" placeholder="请留下您的手机号,方便我们联系您" type="number" <input class="contact-input" v-model="contact" :placeholder="$t('feedback.contactPlaceholder')" type="number"
maxlength="11" name="contact" /> maxlength="11" name="contact" />
</view> </view>
<!-- 提交按钮 --> <!-- 提交按钮 -->
<view class="submit-section"> <view class="submit-section">
<view class="submit-btn" @click="submitFeedback">提交反馈</view> <view class="submit-btn" @click="submitFeedback">{{ $t('feedback.submit') }}</view>
</view> </view>
<!-- </form> --> <!-- </form> -->
</view> </view>
</template> </template>
<script setup> <script setup>
import { import {
ref ref,
onMounted
} from 'vue' } from 'vue'
import { import {
URL, URL,
@@ -64,9 +65,18 @@
import { import {
addUserFeedback addUserFeedback
} from '../../config/api/feedback' } from '../../config/api/feedback'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('feedback.title')
})
})
// 响应式数据 // 响应式数据
const types = ref(['设备故障', '收费问题', '使用建议', '其他']) const types = ref([$t('feedback.deviceFault'), $t('feedback.chargingIssue'), $t('feedback.usageSuggestion'), $t('feedback.other')])
const selectedType = ref(-1) const selectedType = ref(-1)
const paramsType = ref('') const paramsType = ref('')
const description = ref('') const description = ref('')
@@ -113,7 +123,7 @@
const submitFeedback = async () => { const submitFeedback = async () => {
if (selectedType.value === -1) { if (selectedType.value === -1) {
uni.showToast({ uni.showToast({
title: '请选择问题类型', title: $t('feedback.pleaseSelectType'),
icon: 'none' icon: 'none'
}) })
return return
@@ -121,7 +131,7 @@
if (!description.value.trim()) { if (!description.value.trim()) {
uni.showToast({ uni.showToast({
title: '请描述您的问题', title: $t('feedback.pleaseDescribe'),
icon: 'none' icon: 'none'
}) })
return return
@@ -129,13 +139,13 @@
if (!contact.value) { if (!contact.value) {
uni.showToast({ uni.showToast({
title: '请留下联系方式', title: $t('feedback.pleaseContact'),
icon: 'none' icon: 'none'
}) })
return return
} }
if (types.value[selectedType.value] == '设备故障' || types.value[selectedType.value] == '收费问题') { if (types.value[selectedType.value] == $t('feedback.deviceFault') || types.value[selectedType.value] == $t('feedback.chargingIssue')) {
paramsType.value = 'complain' paramsType.value = 'complain'
} else { } else {
paramsType.value = 'suggestion' paramsType.value = 'suggestion'
@@ -164,7 +174,7 @@
// 兼容后端返回 { code: 200 } 或 HTTP 200 情况 // 兼容后端返回 { code: 200 } 或 HTTP 200 情况
if ((res.statusCode === 200) && ((res.data && res.data.code === 200) || res.data === true || res.data?.success === true)) { if ((res.statusCode === 200) && ((res.data && res.data.code === 200) || res.data === true || res.data?.success === true)) {
uni.showToast({ uni.showToast({
title: '反馈成功', title: $t('feedback.submitSuccess'),
icon: 'success' icon: 'success'
}) })
setTimeout(() => { setTimeout(() => {
@@ -173,14 +183,14 @@
return return
} }
uni.showToast({ uni.showToast({
title: (res.data && (res.data.msg || res.data.message)) || '反馈失败', title: (res.data && (res.data.msg || res.data.message)) || $t('feedback.submitFailed'),
icon: 'none' icon: 'none'
}) })
}, },
fail: (err) => { fail: (err) => {
console.error('feedback request failed:', err) console.error('feedback request failed:', err)
uni.showToast({ uni.showToast({
title: '网络错误,请稍后重试', title: $t('error.networkError'),
icon: 'none' icon: 'none'
}) })
} }
+7 -3
View File
@@ -20,14 +20,14 @@
<!-- 联系客服 --> <!-- 联系客服 -->
<view class="contact-card"> <view class="contact-card">
<view class="contact-title">{{ HELP_CONTENT.CONTACT.TITLE }}</view> <view class="contact-title">{{ $t('help.contactUs') }}</view>
<view class="contact-content"> <view class="contact-content">
<view class="contact-item"> <view class="contact-item">
<text class="label">{{ HELP_CONTENT.CONTACT.PHONE.LABEL }}</text> <text class="label">{{ $t('help.phone') }}</text>
<text class="value" @click="makePhoneCall">{{ customerPhone }}</text> <text class="value" @click="makePhoneCall">{{ customerPhone }}</text>
</view> </view>
<view class="contact-item"> <view class="contact-item">
<text class="label">{{ HELP_CONTENT.CONTACT.SERVICE_TIME.LABEL }}</text> <text class="label">{{ $t('help.workingHours') }}</text>
<text class="value">{{ HELP_CONTENT.CONTACT.SERVICE_TIME.VALUE }}</text> <text class="value">{{ HELP_CONTENT.CONTACT.SERVICE_TIME.VALUE }}</text>
</view> </view>
</view> </view>
@@ -51,6 +51,10 @@ export default {
} }
}, },
onLoad() { onLoad() {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('help.title')
})
// 从缓存读取客服电话 // 从缓存读取客服电话
this.customerPhone = getCustomerPhone() this.customerPhone = getCustomerPhone()
}, },
+59 -33
View File
@@ -1,19 +1,23 @@
<template> <template>
<view class="container fullscreen"> <view class="container fullscreen">
<!-- 自定义导航栏 --> <!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }"> <view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content" :style="{ height: navBarHeight + 'px' }"> <view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
<text class="navbar-title">风电者共享风扇&暖手充电宝</text> <text class="navbar-title">{{ $t('home.title') }}</text>
</view>
</view> </view>
</view>
<view class="map-notice" v-if="noticeText" @click="openNoticePopup"> <!-- 顶部信息区域通知招商等 -->
<view class="top-info-section" :style="{ top: (statusBarHeight + navBarHeight) + 'px' }">
<!-- 通知栏 -->
<view class="notice-wrapper" v-if="noticeText" @click="openNoticePopup">
<uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF" <uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
icon="volume"></uv-notice-bar> icon="volume"></uv-notice-bar>
</view> </view>
</view>
<!-- 内容区域 --> <!-- 内容区域 -->
<view class="main-content" :style="{ paddingTop: (statusBarHeight) + 'px' }"> <view class="main-content" :style="{ paddingTop: (statusBarHeight + navBarHeight + noticeHeight) + 'px' }">
<!-- 全屏地图组件 --> <!-- 全屏地图组件 -->
<MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation" <MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword" :positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
@@ -25,7 +29,7 @@
<view v-if="isLoading || !userLocation" class="map-loading-placeholder"> <view v-if="isLoading || !userLocation" class="map-loading-placeholder">
<view class="loading-content"> <view class="loading-content">
<view class="loading-spinner"></view> <view class="loading-spinner"></view>
<text>正在获取位置信息...</text> <text>{{ $t('common.loadingLocation') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -36,28 +40,28 @@
<view class="icon-wrap"> <view class="icon-wrap">
<image class="action-icon" src="/static/map.png" mode="aspectFit" /> <image class="action-icon" src="/static/map.png" mode="aspectFit" />
</view> </view>
<text class="action-label">附近设备</text> <text class="action-label">{{ $t('home.nearbyDevices') }}</text>
</view> --> </view> -->
<view class="action-btn secondary small btn-nearby" @click="openPopup"> <view class="action-btn secondary small btn-nearby" @click="openPopup">
<view class="icon-wrap"> <view class="icon-wrap">
<image src="/static/use_help.png" class="action-icon" mode="aspectFit"></image> <image src="/static/use_help.png" class="action-icon" mode="aspectFit"></image>
</view> </view>
<text class="action-label">使用指南</text> <text class="action-label">{{ $t('home.useGuide') }}</text>
</view> </view>
<view class="action-btn primary btn-scan" @click="handleScan"> <view class="action-btn primary btn-scan" @click="handleScan">
<view class="icon-wrap"> <view class="icon-wrap">
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFill" /> <image class="action-icon" src="/static/scan-icon.png" mode="aspectFill" />
</view> </view>
<text class="primary-label">扫码使用</text> <text class="primary-label">{{ $t('home.scanToUse') }}</text>
</view> </view>
<view class="action-btn secondary small btn-my" @click="goMy"> <view class="action-btn secondary small btn-my" @click="goMy">
<view class="icon-wrap"> <view class="icon-wrap">
<image class="action-icon" src="/static/user.png" mode="aspectFit" /> <image class="action-icon" src="/static/user.png" mode="aspectFit" />
</view> </view>
<text class="action-label">个人中心</text> <text class="action-label">{{ $t('home.personalCenter') }}</text>
</view> </view>
</view> </view>
@@ -67,7 +71,7 @@
:expanded="isExpanded" :expanded="isExpanded"
:positions="filteredPositions" :positions="filteredPositions"
:isLoading="isLoading" :isLoading="isLoading"
title="附近设备场地" :title="$t('home.nearbyDeviceLocation')"
@close="hideLocationList" @close="hideLocationList"
@select="selectPositionFromPopup" @select="selectPositionFromPopup"
@navigate="navigateToPosition" @navigate="navigateToPosition"
@@ -77,7 +81,7 @@
<view class="loading-overlay" v-if="isLoading"> <view class="loading-overlay" v-if="isLoading">
<view class="loading-content"> <view class="loading-content">
<view class="loading-spinner"></view> <view class="loading-spinner"></view>
<text>正在获取场地信息...</text> <text>{{ $t('common.loadingPosition') }}</text>
</view> </view>
</view> </view>
@@ -86,17 +90,17 @@
<view class="popup-mask" @click.stop="showPhoneAuthPopup = false"></view> <view class="popup-mask" @click.stop="showPhoneAuthPopup = false"></view>
<view class="popup-content"> <view class="popup-content">
<view class="popup-header"> <view class="popup-header">
<text class="popup-title">授权获取手机号</text> <text class="popup-title">{{ $t('auth.authTitle') }}</text>
</view> </view>
<view class="popup-body"> <view class="popup-body">
<view class="auth-desc"> <view class="auth-desc">
<text>为了提供更好的服务和紧急联系需要授权获取您的手机号</text> <text>{{ $t('auth.authDesc') }}</text>
</view> </view>
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber"> <button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
<text>一键获取手机号</text> <text>{{ $t('auth.getPhoneNumber') }}</text>
</button> </button>
<view class="auth-cancel" @click="showPhoneAuthPopup = false"> <view class="auth-cancel" @click="showPhoneAuthPopup = false">
<text>暂不授权</text> <text>{{ $t('auth.notNow') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -106,7 +110,7 @@
<uv-popup ref="guidePopup" mode="center" round="24" :overlay="true" :closeOnClickOverlay="false" :safeAreaInsetBottom="false"> <uv-popup ref="guidePopup" mode="center" round="24" :overlay="true" :closeOnClickOverlay="false" :safeAreaInsetBottom="false">
<view class="guide-popup"> <view class="guide-popup">
<view class="guide-header"> <view class="guide-header">
<text class="guide-title">使用指南</text> <text class="guide-title">{{ $t('guide.title') }}</text>
<!-- <view class="guide-close" @click="closeGuidePopup"> <!-- <view class="guide-close" @click="closeGuidePopup">
<uv-icon name="close" size="20"></uv-icon> <uv-icon name="close" size="20"></uv-icon>
</view> --> </view> -->
@@ -115,8 +119,8 @@
<view class="guide-step" v-for="(step, idx) in guideSteps" :key="idx"> <view class="guide-step" v-for="(step, idx) in guideSteps" :key="idx">
<view class="step-index">{{ idx + 1 }}</view> <view class="step-index">{{ idx + 1 }}</view>
<view class="step-info"> <view class="step-info">
<view class="step-title">{{ step.title }}</view> <view class="step-title">{{ $t('guide.step' + (idx + 1) + 'Title') }}</view>
<view class="step-desc">{{ step.desc }}</view> <view class="step-desc">{{ $t('guide.step' + (idx + 1) + 'Desc') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -133,7 +137,7 @@
<uv-popup ref="noticePopup" mode="center" round="24" :overlay="true" :closeOnClickOverlay="true" :safeAreaInsetBottom="false"> <uv-popup ref="noticePopup" mode="center" round="24" :overlay="true" :closeOnClickOverlay="true" :safeAreaInsetBottom="false">
<view class="notice-popup"> <view class="notice-popup">
<view class="notice-header"> <view class="notice-header">
<text class="notice-title">通知公告</text> <text class="notice-title">{{ $t('home.noticeTitle') }}</text>
</view> </view>
<view class="notice-content"> <view class="notice-content">
<text class="notice-text">{{ noticeText }}</text> <text class="notice-text">{{ noticeText }}</text>
@@ -182,6 +186,8 @@
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径 // 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
import MapComponent from '../../components/MapComponent.vue' import MapComponent from '../../components/MapComponent.vue'
import LocationListSheet from '../../components/LocationListSheet.vue' import LocationListSheet from '../../components/LocationListSheet.vue'
import { useI18n } from '../../utils/i18n.js'
// 开启右上角分享菜单(仅 mp-weixin 有效) // 开启右上角分享菜单(仅 mp-weixin 有效)
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
wx.showShareMenu({ wx.showShareMenu({
@@ -190,6 +196,8 @@
}) })
// #endif // #endif
const { t: $t } = useI18n()
// 响应式数据 // 响应式数据
const searchKeyword = ref('') const searchKeyword = ref('')
const userLocation = ref(null) const userLocation = ref(null)
@@ -205,6 +213,7 @@
// 导航栏高度相关 // 导航栏高度相关
const statusBarHeight = ref(0) const statusBarHeight = ref(0)
const navBarHeight = ref(44) // 默认导航栏内容高度 const navBarHeight = ref(44) // 默认导航栏内容高度
const noticeHeight = ref(0) // 通知栏高度
// 使用指南步骤 // 使用指南步骤
const guideSteps = ref([ const guideSteps = ref([
@@ -240,6 +249,11 @@
const res = await getNoticeTextData(parasm); const res = await getNoticeTextData(parasm);
noticeText.value = res.data.noticeContent; noticeText.value = res.data.noticeContent;
// 设置通知栏高度
if (res.data.noticeContent) {
noticeHeight.value = 50 // 通知栏高度约50px
}
// 将通知内容存储到本地缓存 // 将通知内容存储到本地缓存
try { try {
uni.setStorageSync('noticeContent', res.data.noticeContent); uni.setStorageSync('noticeContent', res.data.noticeContent);
@@ -400,7 +414,7 @@ const noticePopup = ref(null)
} catch (error) { } catch (error) {
console.error('获取位置失败:', error) console.error('获取位置失败:', error)
uni.showToast({ uni.showToast({
title: '获取位置失败,显示默认地图', title: $t('home.getLocationFailed'),
icon: 'none' icon: 'none'
}) })
} }
@@ -578,7 +592,7 @@ const noticePopup = ref(null)
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: '定位成功', title: $t('home.locateSuccess'),
icon: 'success', icon: 'success',
duration: 1500 duration: 1500
}) })
@@ -587,7 +601,7 @@ const noticePopup = ref(null)
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: e.errMsg || '定位失败,请检查定位权限', title: e.errMsg || $t('home.locateFailed'),
icon: 'none', icon: 'none',
duration: 2000 duration: 2000
}) })
@@ -691,7 +705,7 @@ const noticePopup = ref(null)
if (!deviceNo) { if (!deviceNo) {
uni.showToast({ uni.showToast({
title: '无效的设备二维码', title: $t('home.invalidQRCode'),
icon: 'none' icon: 'none'
}) })
return return
@@ -830,16 +844,18 @@ const closeNoticePopup = () => {
export default { export default {
// 分享给朋友 // 分享给朋友
onShareAppMessage() { onShareAppMessage() {
const $t = this.$t || ((key) => key)
return { return {
title: '风电者 - 共享风扇暖手充电宝', title: $t('share.title'),
path: '/pages/index/index', path: '/pages/index/index',
// imageUrl: '/static/logo.png' // imageUrl: '/static/logo.png'
} }
}, },
// 朋友圈 // 朋友圈
onShareTimeline() { onShareTimeline() {
const $t = this.$t || ((key) => key)
return { return {
title: '风电者 - 共享风扇暖手充电宝', title: $t('share.title'),
query: '', query: '',
// imageUrl: '/static/logo.png' // imageUrl: '/static/logo.png'
} }
@@ -889,6 +905,8 @@ const closeNoticePopup = () => {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding-bottom: 180rpx; /* 为底部按钮留出空间 */
box-sizing: border-box;
} }
/* 顶部Logo和通知栏 */ /* 顶部Logo和通知栏 */
@@ -1376,7 +1394,7 @@ const closeNoticePopup = () => {
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 180rpx; /* 为底部按钮留出空间 */
background: #f6f7fb; background: #f6f7fb;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1502,11 +1520,19 @@ const closeNoticePopup = () => {
line-height: 1; line-height: 1;
} }
.map-notice { /* 顶部信息区域 */
.top-info-section {
position: fixed;
left: 0;
right: 0;
z-index: 998;
background: transparent;
}
.notice-wrapper {
margin: 0 20rpx; margin: 0 20rpx;
margin-top: 10rpx; padding: 10rpx 0;
border-radius: 20rpx; border-radius: 20rpx;
z-index: 15;
} }
/* 使用指南弹窗样式 */ /* 使用指南弹窗样式 */
+7 -1
View File
@@ -10,6 +10,9 @@
ref, ref,
onMounted onMounted
} from 'vue' } from 'vue'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
// 外部网页地址 // 外部网页地址
const webUrl = ref('https://joininvestment.gxfs123.com/') const webUrl = ref('https://joininvestment.gxfs123.com/')
@@ -24,13 +27,16 @@
const handleError = (e) => { const handleError = (e) => {
console.error('web-view 加载错误:', e) console.error('web-view 加载错误:', e)
uni.showToast({ uni.showToast({
title: '页面加载失败', title: $t('join.pageLoadFailed'),
icon: 'none', icon: 'none',
duration: 2000 duration: 2000
}) })
} }
onMounted(() => { onMounted(() => {
uni.setNavigationBarTitle({
title: $t('join.title')
})
console.log('招商页面加载,外部网址:', webUrl.value) console.log('招商页面加载,外部网址:', webUrl.value)
}) })
</script> </script>
+13 -4
View File
@@ -1,8 +1,8 @@
<template> <template>
<view class="legal-page"> <view class="legal-page">
<view class="header"> <view class="header">
<view class="title">用户协议</view> <view class="title">{{ $t('legal.agreement') }}</view>
<view class="subtitle">适用于风电者共享风扇租借服务最后更新{{ effectiveDate }}</view> <view class="subtitle">{{ $t('legal.applicableToService') }}{{ $t('legal.lastUpdate') }}{{ effectiveDate }}</view>
</view> </view>
<scroll-view class="content" scroll-y> <scroll-view class="content" scroll-y>
@@ -71,12 +71,21 @@
<view class="p">15.2 协议条款如被认定无效或不可执行不影响其他条款的效力与执行</view> <view class="p">15.2 协议条款如被认定无效或不可执行不影响其他条款的效力与执行</view>
</scroll-view> </scroll-view>
<view class="footer">如对本协议有疑问请前往我的-客服咨询</view> <view class="footer">{{ $t('legal.footerNotice') }}</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('legal.agreement')
})
})
const brandName = '风电者' const brandName = '风电者'
const companyName = '深圳乐慕智云科技有限公司' const companyName = '深圳乐慕智云科技有限公司'
+14 -4
View File
@@ -1,8 +1,8 @@
<template> <template>
<view class="legal-page"> <view class="legal-page">
<view class="header"> <view class="header">
<view class="title">隐私政策</view> <view class="title">{{ $t('legal.privacy') }}</view>
<view class="subtitle">适用于风电者共享风扇租借服务最后更新{{ effectiveDate }}</view> <view class="subtitle">{{ $t('legal.applicableToService') }}{{ $t('legal.lastUpdate') }}{{ effectiveDate }}</view>
</view> </view>
<view class="card notice"> <view class="card notice">
@@ -56,14 +56,24 @@
<view class="p">10.1 您可通过我的-客服与我们联系以行使前述权利或就本政策提出疑问</view> <view class="p">10.1 您可通过我的-客服与我们联系以行使前述权利或就本政策提出疑问</view>
</scroll-view> </scroll-view>
<view class="footer">如对本政策有疑问请前往我的-客服咨询</view> <view class="footer">{{ $t('legal.footerNoticePolicy') }}</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { import {
ref ref,
onMounted
} from 'vue' } from 'vue'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('legal.privacy')
})
})
const brandName = '风电者' const brandName = '风电者'
const companyName = '深圳乐慕智云科技有限公司' const companyName = '深圳乐慕智云科技有限公司'
+41 -31
View File
@@ -2,18 +2,18 @@
<view class="login-container"> <view class="login-container">
<view class="logo"> <view class="logo">
<image src="/static/logo.png" mode="aspectFit" /> <image src="/static/logo.png" mode="aspectFit" />
<text class="app-name">风电者共享风扇&充电宝</text> <text class="app-name">{{ $t('app.slogan') }}</text>
</view> </view>
<view class="title">登录您的账号</view> <view class="title">{{ $t('auth.loginTitle') }}</view>
<view class="subtitle">为保障使用体验请先完成登录</view> <view class="subtitle">{{ $t('auth.loginDesc') }}</view>
<!-- 微信一键手机号快捷登录推荐 --> <!-- 微信一键手机号快捷登录推荐 -->
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick"> <button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
手机号快捷登录 {{ $t('auth.getPhoneNumber') }}
</button> </button>
<button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber"> <button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
手机号快捷登录 {{ $t('auth.getPhoneNumber') }}
</button> </button>
<!-- 仅微信登录不授权手机号时使用 --> <!-- 仅微信登录不授权手机号时使用 -->
@@ -24,10 +24,10 @@
<label class="agreement-label"> <label class="agreement-label">
<checkbox value="agreed" :checked="isAgreed" color="#07c160" class="agreement-checkbox" /> <checkbox value="agreed" :checked="isAgreed" color="#07c160" class="agreement-checkbox" />
<text class="agreement-text"> <text class="agreement-text">
我已阅读并同意 {{ $t('auth.agreeToTerms') }}
<text class="link" @tap.stop="go('/pages/legal/agreement')">用户协议</text> <text class="link" @tap.stop="go('/pages/legal/agreement')">{{ $t('user.userAgreement') }}</text>
{{ $t('common.and') }}
<text class="link" @tap.stop="go('/pages/legal/privacy')">隐私政策</text> <text class="link" @tap.stop="go('/pages/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
</text> </text>
</label> </label>
</checkbox-group> </checkbox-group>
@@ -36,9 +36,19 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { wxLogin, getUserPhoneNumber, getUserInfo } from '../../util/index.js' import { wxLogin, getUserPhoneNumber, getUserInfo } from '../../util/index.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('auth.loginTitle')
})
})
const redirect = ref('/pages/index/index') const redirect = ref('/pages/index/index')
const isAgreed = ref(false) // 是否同意协议 const isAgreed = ref(false) // 是否同意协议
@@ -67,23 +77,23 @@
return return
} }
// 未勾选,弹窗提示 // 未勾选,弹窗提示
uni.showModal({ uni.showModal({
title: '温馨提示', title: $t('common.tips'),
content: '请先阅读并同意《用户协议》和《隐私政策》', content: $t('auth.pleaseAgreeToTerms'),
confirmText: '同意', confirmText: $t('common.confirm'),
cancelText: '取消', cancelText: $t('common.cancel'),
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
// 用户点击同意,自动勾选 // 用户点击同意,自动勾选
isAgreed.value = true isAgreed.value = true
resolve() resolve()
} else { } else {
// 用户点击取消 // 用户点击取消
reject(new Error('需要同意协议才能登录')) reject(new Error('需要同意协议才能登录'))
}
} }
}) }
})
}) })
} }
@@ -108,8 +118,8 @@
// 先检查是否同意协议 // 先检查是否同意协议
await checkAgreement() await checkAgreement()
await wxLogin() await wxLogin()
uni.showToast({ title: '登录成功', icon: 'success' }) uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin() await navigateAfterLogin()
} catch (error) { } catch (error) {
if (error.message !== '需要同意协议才能登录') { if (error.message !== '需要同意协议才能登录') {
@@ -120,7 +130,7 @@
const onGetPhoneNumber = async (e) => { const onGetPhoneNumber = async (e) => {
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') { if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
uni.showToast({ title: '已取消手机号授权', icon: 'none' }) uni.showToast({ title: $t('auth.phoneCancelled'), icon: 'none' })
return return
} }
@@ -129,10 +139,10 @@
await wxLogin() await wxLogin()
// 再用微信返回的临时 code 换取手机号 // 再用微信返回的临时 code 换取手机号
await getUserPhoneNumber(e.detail.code) await getUserPhoneNumber(e.detail.code)
uni.showToast({ title: '登录成功', icon: 'success' }) uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin() await navigateAfterLogin()
} catch (error) { } catch (error) {
uni.showToast({ title: error.message || '登录失败', icon: 'none' }) uni.showToast({ title: error.message || $t('auth.loginFailed'), icon: 'none' })
} }
} }
+28 -22
View File
@@ -6,8 +6,8 @@
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill"></image> <image v-else class="avatar" src="@/static/head.png" mode="aspectFill"></image>
</view> </view>
<view class="user-text"> <view class="user-text">
<view class="nickname">{{ userInfo.nickName || '点击登录' }}</view> <view class="nickname">{{ userInfo.nickName || $t('user.clickToLogin') }}</view>
<view class="subtext">{{ userInfo.phone ? maskPhone(userInfo.phone) : '授权登录后可查看订单与资产' }}</view> <view class="subtext">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('user.loginPrompt') }}</view>
</view> </view>
<uv-icon type="right" size="16" color="#999"></uv-icon> <uv-icon type="right" size="16" color="#999"></uv-icon>
</view> </view>
@@ -32,56 +32,56 @@
<view class="list-item" @click="handleQuickReturn"> <view class="list-item" @click="handleQuickReturn">
<view class="left"> <view class="left">
<image class="icon" src="/static/express_return.png" mode="aspectFit"></image> <image class="icon" src="/static/express_return.png" mode="aspectFit"></image>
<text class="title">快速归还<text style="font-size: 18rpx;">直接查看使用中的订单</text></text> <text class="title">{{ $t('user.quickReturn') }}<text style="font-size: 18rpx;">{{ $t('user.quickReturnDesc') }}</text></text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
<view class="list-item" @click="navigateTo('/pages/expressReturn/index')" v-if="showMenuItem"> <view class="list-item" @click="navigateTo('/pages/expressReturn/index')" v-if="showMenuItem">
<view class="left"> <view class="left">
<image class="icon" src="/static/express.png" mode="aspectFit"></image> <image class="icon" src="/static/express.png" mode="aspectFit"></image>
<text class="title">快递归还记录</text> <text class="title">{{ $t('user.expressReturn') }}</text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
<view class="list-item" @click="navigateTo('/pages/order/index')"> <view class="list-item" @click="navigateTo('/pages/order/index')">
<view class="left"> <view class="left">
<image class="icon" src="/static/orderList.png" mode="aspectFit"></image> <image class="icon" src="/static/orderList.png" mode="aspectFit"></image>
<text class="title">我的订单</text> <text class="title">{{ $t('user.myOrders') }}</text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
<view class="list-item" @click="navigateTo('/pages/help/index')"> <view class="list-item" @click="navigateTo('/pages/help/index')">
<view class="left"> <view class="left">
<image class="icon" src="/static/customer-service.png" mode="aspectFit"></image> <image class="icon" src="/static/customer-service.png" mode="aspectFit"></image>
<text class="title">客服中心</text> <text class="title">{{ $t('user.customerService') }}</text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
<view class="list-item" @click="navigateTo('/pages/feedback/index')"> <view class="list-item" @click="navigateTo('/pages/feedback/index')">
<view class="left"> <view class="left">
<image class="icon" src="/static/suggess.png" mode="aspectFit"></image> <image class="icon" src="/static/suggess.png" mode="aspectFit"></image>
<text class="title">投诉与建议</text> <text class="title">{{ $t('user.feedback') }}</text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
<!-- <view class="list-item" @click="navigateTo('/pages/legal/agreement')"> <!-- <view class="list-item" @click="navigateTo('/pages/legal/agreement')">
<view class="left"> <view class="left">
<image class="icon" src="/static/business-licence.png" mode="aspectFit"></image> <image class="icon" src="/static/business-licence.png" mode="aspectFit"></image>
<text class="title">营业资质</text> <text class="title">{{ $t('user.businessLicense') }}</text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> --> </view> -->
<view class="list-item" @click="navigateTo('/pages/join/index')"> <view class="list-item" @click="navigateTo('/pages/join/index')">
<view class="left"> <view class="left">
<image class="icon" src="/static/peopleInWork.png" mode="aspectFit"></image> <image class="icon" src="/static/peopleInWork.png" mode="aspectFit"></image>
<text class="title">合作加盟</text> <text class="title">{{ $t('user.cooperation') }}</text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
<view class="list-item" @click="navigateTo('/pages/setting/index')"> <view class="list-item" @click="navigateTo('/pages/setting/index')">
<view class="left"> <view class="left">
<image class="icon" src="/static/setting.png" mode="aspectFit"></image> <image class="icon" src="/static/setting.png" mode="aspectFit"></image>
<text class="title">设置</text> <text class="title">{{ $t('user.settings') }}</text>
</view> </view>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
@@ -90,11 +90,11 @@
<view class="footer-agreements"> <view class="footer-agreements">
<view class="link-box"> <view class="link-box">
<text class="link" @click="navigateTo('/pages/legal/agreement')">用户协议</text> <text class="link" @click="navigateTo('/pages/legal/agreement')">{{ $t('user.userAgreement') }}</text>
<text class="sep"></text> <text class="sep"></text>
<text class="link" @click="navigateTo('/pages/legal/privacy')">隐私政策</text> <text class="link" @click="navigateTo('/pages/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
</view> </view>
<view class="version">v{{ appVersion }}</view> <view class="version">{{ $t('user.version') }}{{ appVersion }}</view>
</view> </view>
<!-- 保留授权弹窗暂不启用 --> <!-- 保留授权弹窗暂不启用 -->
@@ -133,8 +133,11 @@ import {
import { import {
URL URL
} from '../../config/url.js' } from '../../config/url.js'
import { useI18n } from '@/utils/i18n.js'
// 设置页执行退出登录,此页不再直接调用 // 设置页执行退出登录,此页不再直接调用
const { t: $t } = useI18n()
// 响应式状态 // 响应式状态
const userInfo = ref({}); const userInfo = ref({});
const deposit = ref('0.00'); const deposit = ref('0.00');
@@ -147,6 +150,9 @@ import {
// 页面加载时初始化 // 页面加载时初始化
onMounted(() => { onMounted(() => {
uni.setNavigationBarTitle({
title: $t('user.personalCenter')
})
getInfo(); getInfo();
initVersion(); initVersion();
}); });
@@ -186,7 +192,7 @@ import {
} catch (error) { } catch (error) {
console.error('获取用户信息失败:', error); console.error('获取用户信息失败:', error);
uni.showToast({ uni.showToast({
title: '获取用户信息失败', title: $t('user.getUserInfoFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -269,7 +275,7 @@ import {
}); });
} else { } else {
uni.showToast({ uni.showToast({
title: '暂无使用中的订单', title: $t('order.noOrder'),
icon: 'none' icon: 'none'
}); });
} }
@@ -277,7 +283,7 @@ import {
uni.hideLoading(); uni.hideLoading();
console.error('获取使用中订单失败:', error); console.error('获取使用中订单失败:', error);
uni.showToast({ uni.showToast({
title: '获取订单失败', title: $t('order.getOrderFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -300,7 +306,7 @@ import {
// #endif // #endif
// #ifndef MP-WEIXIN // #ifndef MP-WEIXIN
uni.showToast({ uni.showToast({
title: '请在微信小程序中使用此功能', title: $t('auth.pleaseUseInWechat'),
icon: 'none' icon: 'none'
}) })
// #endif // #endif
@@ -404,7 +410,7 @@ import {
// #ifndef MP-WEIXIN // #ifndef MP-WEIXIN
uni.showToast({ uni.showToast({
title: '请在微信小程序中使用此功能', title: $t('auth.pleaseUseInWechat'),
icon: 'none' icon: 'none'
}); });
closeAuthPopup(); closeAuthPopup();
@@ -433,7 +439,7 @@ import {
// }); // });
uni.showToast({ uni.showToast({
title: '信息更新成功', title: $t('user.updateSuccess'),
icon: 'success' icon: 'success'
}); });
@@ -442,7 +448,7 @@ import {
} catch (error) { } catch (error) {
console.error('更新用户信息失败:', error); console.error('更新用户信息失败:', error);
uni.showToast({ uni.showToast({
title: '更新用户信息失败', title: $t('user.updateFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -511,7 +517,7 @@ import {
// 关于我们 // 关于我们
const handleAboutUs = () => { const handleAboutUs = () => {
uni.showToast({ uni.showToast({
title: '功能开发中', title: $t('help.functionDeveloping'),
icon: 'none' icon: 'none'
}); });
}; };
@@ -519,7 +525,7 @@ import {
// 隐私政策 // 隐私政策
const handlePrivacyPolicy = () => { const handlePrivacyPolicy = () => {
uni.showToast({ uni.showToast({
title: '功能开发中', title: $t('help.functionDeveloping'),
icon: 'none' icon: 'none'
}); });
}; };
+58 -53
View File
@@ -19,9 +19,9 @@
<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"></text> <text class="info-value-unit">{{ $t('unit.yuan') }}</text>
</view> </view>
<view class="info-label">订单金额</view> <view class="info-label">{{ $t('order.totalAmount') }}</view>
</view> </view>
</view> </view>
<view class="fee-rule"> <view class="fee-rule">
@@ -31,39 +31,39 @@
<!-- 租借信息卡片 --> <!-- 租借信息卡片 -->
<view class="rent-card"> <view class="rent-card">
<view class="rent-title">租借信息</view> <view class="rent-title">{{ $t('order.rentInfo') }}</view>
<view class="rent-item"> <view class="rent-item">
<view class="rent-label">订单编号</view> <view class="rent-label">{{ $t('order.orderNo') }}</view>
<view class="rent-value">{{ orderInfo.orderNo || '-' }}</view> <view class="rent-value">{{ orderInfo.orderNo || '-' }}</view>
</view> </view>
<view class="rent-item"> <view class="rent-item">
<view class="rent-label">风扇编号</view> <view class="rent-label">{{ $t('order.fanNo') }}</view>
<view class="rent-value">{{ deviceId || '-' }}</view> <view class="rent-value">{{ deviceId || '-' }}</view>
</view> </view>
<view class="rent-item"> <view class="rent-item">
<view class="rent-label">租借时间</view> <view class="rent-label">{{ $t('order.rentTime') }}</view>
<view class="rent-value">{{ orderInfo.startTime || '-' }}</view> <view class="rent-value">{{ orderInfo.startTime || '-' }}</view>
</view> </view>
<view class="rent-item"> <view class="rent-item">
<view class="rent-label">租借地点</view> <view class="rent-label">{{ $t('order.rentLocation') }}</view>
<view class="rent-value">{{ orderInfo.positionName || '新佳宜(九天银河店)' }}</view> <view class="rent-value">{{ orderInfo.positionName || '-' }}</view>
</view> </view>
<view class="rent-item"> <view class="rent-item">
<view class="rent-label">租借方式</view> <view class="rent-label">{{ $t('order.rentMethod') }}</view>
<view class="rent-value">{{ getPayWayText() }}</view> <view class="rent-value">{{ getPayWayText() }}</view>
</view> </view>
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.endTime"> <view class="rent-item" v-if="isOrderCompleted() && orderInfo.endTime">
<view class="rent-label">归还时间</view> <view class="rent-label">{{ $t('order.returnTime') }}</view>
<view class="rent-value">{{ orderInfo.endTime }}</view> <view class="rent-value">{{ orderInfo.endTime }}</view>
</view> </view>
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.returnPosition"> <view class="rent-item" v-if="isOrderCompleted() && orderInfo.returnPosition">
<view class="rent-label">归还地点</view> <view class="rent-label">{{ $t('order.returnLocation') }}</view>
<view class="rent-value">{{ orderInfo.returnPosition || '新佳宜(九天银河店)' }}</view> <view class="rent-value">{{ orderInfo.returnPosition || '-' }}</view>
</view> </view>
<view class="rent-paid" v-if="isOrderCompleted()"> <view class="rent-paid" v-if="isOrderCompleted()">
<text class="paid-label">已支付</text> <text class="paid-label">{{ $t('order.paid') }}</text>
<text class="paid-value">{{ orderInfo.currentFee || orderInfo.payAmount || '10' }}</text> <text class="paid-value">{{ orderInfo.currentFee || orderInfo.payAmount || '10' }}</text>
<text class="paid-unit"></text> <text class="paid-unit">{{ $t('unit.yuan') }}</text>
</view> </view>
</view> </view>
@@ -73,16 +73,16 @@
<template v-if="orderInfo.orderStatus === 'in_used'"> <template v-if="orderInfo.orderStatus === 'in_used'">
<view class="bottom-icon-btn" @click="contactService"> <view class="bottom-icon-btn" @click="contactService">
<image src="/static/customer-service.png" class="icon" mode="aspectFit"></image> <image src="/static/customer-service.png" class="icon" mode="aspectFit"></image>
<text>客服中心</text> <text>{{ $t('user.customerService') }}</text>
</view> </view>
<view v-if="!showExpressAction" class="countdown-btn"> <view v-if="!showExpressAction" class="countdown-btn">
{{ formatCountdown(countdownRemaining) }}后可快递归还 {{ formatCountdown(countdownRemaining) }}{{ $t('order.canExpressReturn') }}
</view> </view>
<view v-if="showExpressAction" class="action-btn secondary" @click="expressRetrunOrder"> <view v-if="showExpressAction" class="action-btn secondary" @click="expressRetrunOrder">
暂停计费 {{ $t('order.pauseBilling') }}
</view> </view>
<view v-if="showExpressAction" class="action-btn primary" @click="quickReturn"> <view v-if="showExpressAction" class="action-btn primary" @click="quickReturn">
快速归还 {{ $t('order.quickReturn') }}
</view> </view>
</template> </template>
@@ -90,26 +90,26 @@
<template v-if="isOrderCompleted()"> <template v-if="isOrderCompleted()">
<view class="bottom-icon-btn" @click="handleWithdraw" v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0"> <view class="bottom-icon-btn" @click="handleWithdraw" v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0">
<image src="/static/suggess.png" class="icon" mode="aspectFit"></image> <image src="/static/suggess.png" class="icon" mode="aspectFit"></image>
<text>费用申诉</text> <text>{{ $t('order.feeAppeal') }}</text>
</view> </view>
<view class="bottom-icon-btn" @click="contactService"> <view class="bottom-icon-btn" @click="contactService">
<image src="/static/customer-service.png" class="icon" mode="aspectFit"></image> <image src="/static/customer-service.png" class="icon" mode="aspectFit"></image>
<text>客服中心</text> <text>{{ $t('user.customerService') }}</text>
</view> </view>
<view class="action-btn primary" @click="rentAgain"> <view class="action-btn primary" @click="rentAgain">
再次租借 {{ $t('order.rentAgain') }}
</view> </view>
</template> </template>
<!-- 待支付状态 --> <!-- 待支付状态 -->
<template v-if="orderInfo.orderStatus === 'waiting_for_payment'"> <template v-if="orderInfo.orderStatus === 'waiting_for_payment'">
<view class="action-btn secondary" @click="handleCancelOrder">取消订单</view> <view class="action-btn secondary" @click="handleCancelOrder">{{ $t('order.cancelOrder') }}</view>
<view class="action-btn primary" @click="handlePayment">立即支付</view> <view class="action-btn primary" @click="handlePayment">{{ $t('order.payNow') }}</view>
</template> </template>
<!-- 已取消状态 --> <!-- 已取消状态 -->
<template v-if="orderInfo.orderStatus === 'order_cancelled'"> <template v-if="orderInfo.orderStatus === 'order_cancelled'">
<view class="action-btn primary full-width" @click="goToHome">返回首页</view> <view class="action-btn primary full-width" @click="goToHome">{{ $t('order.backToHome') }}</view>
</template> </template>
</view> </view>
</view> </view>
@@ -168,6 +168,11 @@
onLoad(options) { onLoad(options) {
console.log('订单详情页加载,参数:', JSON.stringify(options)) console.log('订单详情页加载,参数:', JSON.stringify(options))
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('order.orderDetail')
})
this.isPageActive = true this.isPageActive = true
// 从缓存读取通知内容(计费规则) // 从缓存读取通知内容(计费规则)
@@ -241,27 +246,27 @@
// 获取订单状态文字 // 获取订单状态文字
getOrderStatusText() { getOrderStatusText() {
const statusMap = { const statusMap = {
'waiting_for_payment': '待支付', 'waiting_for_payment': this.$t('order.waitingForPayment'),
'payment_in_progress': '支付中', 'payment_in_progress': this.$t('order.paymentInProgress'),
'payment_successful': '支付成功', 'payment_successful': this.$t('order.paymentSuccess'),
'in_used': '使用中', 'in_used': this.$t('order.inUse'),
'payment_failed': '支付失败', 'payment_failed': this.$t('order.paymentFailed'),
'order_cancelled': '已取消', 'order_cancelled': this.$t('order.cancelled'),
'used_done': '已完成', 'used_done': this.$t('order.finished'),
'used_down': '已完成' 'used_down': this.$t('order.finished')
} }
return statusMap[this.orderInfo.orderStatus] || '订单详情' return statusMap[this.orderInfo.orderStatus] || this.$t('order.orderDetail')
}, },
// 获取状态描述 // 获取状态描述
getStatusDesc() { getStatusDesc() {
const descMap = { const descMap = {
'waiting_for_payment': '请尽快完成支付', 'waiting_for_payment': this.$t('order.pleasePaySoon'),
'in_used': '请妥善保管设备,使用完毕后及时归还', 'in_used': this.$t('order.pleaseReturnInTime'),
'used_done': '您的风扇已归还,感谢使用', 'used_done': this.$t('order.returnedThankYou'),
'used_down': '您的风扇已归还,感谢使用', 'used_down': this.$t('order.returnedThankYou'),
'order_cancelled': '订单已取消', 'order_cancelled': this.$t('order.orderCancelled'),
'payment_failed': '支付失败,请重新支付' 'payment_failed': this.$t('order.paymentFailedRetry')
} }
return descMap[this.orderInfo.orderStatus] || '' return descMap[this.orderInfo.orderStatus] || ''
}, },
@@ -293,11 +298,11 @@
// 获取支付方式文本 // 获取支付方式文本
getPayWayText() { getPayWayText() {
const payWayMap = { const payWayMap = {
'wx_score_pay': '免押租借', 'wx_score_pay': this.$t('order.depositFree'),
'wx_member_pay': '会员订单', 'wx_member_pay': this.$t('order.memberOrder'),
'wx_pay': '押金租借' 'wx_pay': this.$t('order.depositPay')
} }
return payWayMap[this.orderInfo.payWay] || '免押租借' return payWayMap[this.orderInfo.payWay] || this.$t('order.depositFree')
}, },
// 格式化倒计时(显示为 HH:MM:SS 格式) // 格式化倒计时(显示为 HH:MM:SS 格式)
@@ -393,7 +398,7 @@
// 获取使用时长标签文本 // 获取使用时长标签文本
getUsedTimeLabel() { getUsedTimeLabel() {
// 使用中状态显示"已使用",已完成状态显示"使用时长" // 使用中状态显示"已使用",已完成状态显示"使用时长"
return this.orderInfo.orderStatus === 'in_used' ? '已使用' : '使用时长' return this.orderInfo.orderStatus === 'in_used' ? this.$t('order.used') : this.$t('order.duration')
}, },
// 获取订单费用(不含单位) // 获取订单费用(不含单位)
@@ -549,7 +554,7 @@
try { try {
if (!this.orderInfo.orderId) { if (!this.orderInfo.orderId) {
throw new Error('订单ID不能为空') throw new Error(this.$t('order.orderIdRequired'))
} }
const result = await queryById(this.orderInfo.orderId) const result = await queryById(this.orderInfo.orderId)
@@ -762,13 +767,13 @@
// 取消订单 // 取消订单
handleCancelOrder() { handleCancelOrder() {
uni.showModal({ uni.showModal({
title: '确认取消', title: this.$t('order.confirmCancel'),
content: '确定要取消此订单吗?', content: this.$t('order.confirmCancelContent'),
success: async (res) => { success: async (res) => {
if (res.confirm) { if (res.confirm) {
try { try {
uni.showLoading({ uni.showLoading({
title: '处理中' title: this.$t('common.processing')
}) })
const result = await cancelOrder({ const result = await cancelOrder({
orderId: this.orderInfo.orderId orderId: this.orderInfo.orderId
@@ -776,17 +781,17 @@
if (result.code === 200) { if (result.code === 200) {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: '订单已取消', title: this.$t('order.cancelSuccess'),
icon: 'success' icon: 'success'
}) })
await this.getOrderDetails() await this.getOrderDetails()
} else { } else {
throw new Error(result.msg || '取消订单失败') throw new Error(result.msg || this.$t('order.cancelFailed'))
} }
} catch (error) { } catch (error) {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: error.message || '取消订单失败', title: error.message || this.$t('order.cancelFailed'),
icon: 'none' icon: 'none'
}) })
} }
@@ -821,7 +826,7 @@
if (res.statusCode === 200 && res.data.code === 200) { if (res.statusCode === 200 && res.data.code === 200) {
uni.showToast({ uni.showToast({
title: '退款申请成功', title: this.$t('order.refundSuccess'),
icon: 'success' icon: 'success'
}) })
@@ -837,7 +842,7 @@
} catch (error) { } catch (error) {
console.error('退款申请错误:', error) console.error('退款申请错误:', error)
uni.showToast({ uni.showToast({
title: error.message || '退款申请失败', title: error.message || this.$t('order.refundFailed'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
+42 -32
View File
@@ -10,12 +10,12 @@
<!-- 订单列表 --> <!-- 订单列表 -->
<view class="order-list"> <view class="order-list">
<view class="empty-state" v-if="orderList.length === 0"> <view class="empty-state" v-if="orderList.length === 0">
<view class="empty-icon"> <view class="empty-icon">
<image src="/static/orderList.png" mode="aspectFill" class="empty-icon"></image> <image src="/static/orderList.png" mode="aspectFill" class="empty-icon"></image>
</view>
<text class="empty-text">暂无订单记录</text>
</view> </view>
<text class="empty-text">{{ $t('order.noOrderRecord') }}</text>
</view>
<OrderItemCard <OrderItemCard
v-for="(order, index) in orderList" v-for="(order, index) in orderList"
@@ -56,6 +56,16 @@
import { import {
URL URL
} from '../../config/url.js'; } from '../../config/url.js';
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('order.myOrders')
})
})
// 初始化状态 // 初始化状态
const currentTab = ref(0); const currentTab = ref(0);
@@ -64,62 +74,62 @@
// 订单状态映射 // 订单状态映射
const orderStatusMap = reactive({ const orderStatusMap = reactive({
'0': { '0': {
text: '待支付', get text() { return $t('order.waitingForPayment') },
class: 'status-waiting' class: 'status-waiting'
}, },
'1': { '1': {
text: '使用中', get text() { return $t('order.inUse') },
class: 'status-using' class: 'status-using'
}, },
'2': { '2': {
text: '已完成', get text() { return $t('order.finished') },
class: 'status-finished' class: 'status-finished'
}, },
'3': { '3': {
text: '已取消', get text() { return $t('order.cancelled') },
class: 'status-cancelled' class: 'status-cancelled'
}, },
'waiting_for_payment': { 'waiting_for_payment': {
text: '待支付', get text() { return $t('order.waitingForPayment') },
class: 'status-waiting' class: 'status-waiting'
}, },
'in_used': { 'in_used': {
text: '使用中', get text() { return $t('order.inUse') },
class: 'status-using' class: 'status-using'
}, },
'used_done': { 'used_done': {
text: '已完成', get text() { return $t('order.finished') },
class: 'status-finished' class: 'status-finished'
}, },
'order_cancelled': { 'order_cancelled': {
text: '已取消', get text() { return $t('order.cancelled') },
class: 'status-cancelled' class: 'status-cancelled'
}, },
'express_return': { 'express_return': {
text: '快递归还', get text() { return $t('express.title') },
class: 'status-express-return' class: 'status-express-return'
} }
}); });
// 订单状态标签 // 订单状态标签
const orderStatusTabs = reactive([{ const orderStatusTabs = reactive([{
text: '全部', get text() { return $t('common.all') },
status: [] status: []
}, },
{ {
text: '待付款', get text() { return $t('order.waitingForPayment') },
status: ['waiting_for_payment'] status: ['waiting_for_payment']
}, },
{ {
text: '使用中', get text() { return $t('order.inUse') },
status: ['in_used'] status: ['in_used']
}, },
{ {
text: '已完成', get text() { return $t('order.finished') },
status: ['used_done'] status: ['used_done']
}, },
{ {
text: '已取消', get text() { return $t('order.cancelled') },
status: ['order_cancelled'] status: ['order_cancelled']
} }
]); ]);
@@ -212,7 +222,7 @@
} catch (error) { } catch (error) {
console.error('获取订单列表失败:', error); console.error('获取订单列表失败:', error);
uni.showToast({ uni.showToast({
title: '获取订单列表失败', title: $t('order.getOrderListFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -224,14 +234,14 @@
const res = await getOrderByOrderNoScorePayStatus(order.orderNo); const res = await getOrderByOrderNoScorePayStatus(order.orderNo);
if (res.code === 200) { if (res.code === 200) {
uni.showToast({ uni.showToast({
title: '状态同步成功', title: $t('order.syncSuccess'),
icon: 'success' icon: 'success'
}); });
await loadOrderList(orderStatusTabs[currentTab.value].status); await loadOrderList(orderStatusTabs[currentTab.value].status);
} }
} catch (error) { } catch (error) {
uni.showToast({ uni.showToast({
title: '同步状态失败', title: $t('order.syncFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -258,7 +268,7 @@
const handlePayment = async (order) => { const handlePayment = async (order) => {
try { try {
uni.showLoading({ uni.showLoading({
title: '处理中' title: $t('common.processing')
}); });
// 调用后端创建微信支付订单接口 // 调用后端创建微信支付订单接口
@@ -279,7 +289,7 @@
...payParams, ...payParams,
success: async () => { success: async () => {
uni.showToast({ uni.showToast({
title: '支付成功', title: $t('payment.paymentSuccess'),
icon: 'success' icon: 'success'
}); });
@@ -295,7 +305,7 @@
}, },
fail: (err) => { fail: (err) => {
console.error('支付失败:', err); console.error('支付失败:', err);
throw new Error('支付失败,请重试'); throw new Error($t('payment.paymentFailedRetry'));
} }
}); });
} else { } else {
@@ -306,7 +316,7 @@
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: error.message || '支付失败', title: error.message || $t('payment.paymentFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -316,12 +326,12 @@
const handleCancelOrder = async (order) => { const handleCancelOrder = async (order) => {
try { try {
uni.showModal({ uni.showModal({
title: '确认取消', title: $t('order.confirmCancel'),
content: '确定要取消此订单吗?', content: $t('order.confirmCancelContent'),
success: async (res) => { success: async (res) => {
if (res.confirm) { if (res.confirm) {
uni.showLoading({ uni.showLoading({
title: '处理中' title: $t('common.processing')
}); });
const result = await cancelOrder({ const result = await cancelOrder({
@@ -331,14 +341,14 @@
if (result) { if (result) {
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '订单已取消', title: $t('order.cancelSuccess'),
icon: 'success' icon: 'success'
}); });
// 刷新订单列表 // 刷新订单列表
await loadOrderList(); await loadOrderList();
} else { } else {
throw new Error(result.msg || '取消订单失败'); throw new Error(result.msg || $t('order.cancelFailed'));
} }
} }
} }
@@ -346,7 +356,7 @@
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: error.message || '取消订单失败', title: error.message || $t('order.cancelFailed'),
icon: 'none' icon: 'none'
}); });
} }
+29 -24
View File
@@ -9,38 +9,38 @@
<!-- 订单信息 --> <!-- 订单信息 -->
<view class="order-card"> <view class="order-card">
<view class="card-title">订单信息</view> <view class="card-title">{{ $t('payment.orderInfo') }}</view>
<view class="info-item"> <view class="info-item">
<text class="label">订单号</text> <text class="label">{{ $t('order.orderNo') }}</text>
<text class="value">{{ orderInfo.orderNo || '-' }}</text> <text class="value">{{ orderInfo.orderNo || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">设备号</text> <text class="label">{{ $t('order.deviceNo') }}</text>
<text class="value">{{ orderInfo.deviceNo || '-' }}</text> <text class="value">{{ orderInfo.deviceNo || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">创建时间</text> <text class="label">{{ $t('payment.createTime') }}</text>
<text class="value">{{ orderInfo.createTime || '-' }}</text> <text class="value">{{ orderInfo.createTime || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">联系电话</text> <text class="label">{{ $t('payment.contactPhone') }}</text>
<text class="value">{{ orderInfo.phone || '-' }}</text> <text class="value">{{ orderInfo.phone || '-' }}</text>
</view> </view>
</view> </view>
<!-- 费用信息 --> <!-- 费用信息 -->
<view class="price-card"> <view class="price-card">
<view class="card-title">费用信息</view> <view class="card-title">{{ $t('payment.feeInfo') }}</view>
<view class="price-item"> <view class="price-item">
<text class="label">押金</text> <text class="label">{{ $t('payment.deposit') }}</text>
<text class="value">{{ orderInfo.deposit || '99.00' }}</text> <text class="value">{{ orderInfo.deposit || '99.00' }}</text>
</view> </view>
<view class="price-item"> <view class="price-item">
<text class="label">套餐</text> <text class="label">{{ $t('payment.package') }}</text>
<text class="value">{{ packageInfo.price }}/{{ packageInfo.time }}小时</text> <text class="value">{{ packageInfo.price }}{{ $t('unit.yuan') }}/{{ packageInfo.time }}{{ $t('time.hour') }}</text>
</view> </view>
<view class="price-item total"> <view class="price-item total">
<text class="label">合计</text> <text class="label">{{ $t('payment.total') }}</text>
<text class="value">{{ totalAmount }}</text> <text class="value">{{ totalAmount }}</text>
</view> </view>
</view> </view>
@@ -51,10 +51,10 @@
<!-- 底部操作栏 --> <!-- 底部操作栏 -->
<view class="bottom-bar"> <view class="bottom-bar">
<view class="total-amount"> <view class="total-amount">
<text>合计</text> <text>{{ $t('payment.total') }}</text>
<text class="amount">{{ totalAmount }}</text> <text class="amount">{{ totalAmount }}</text>
</view> </view>
<view class="pay-btn" @click="handlePayment">立即支付</view> <view class="pay-btn" @click="handlePayment">{{ $t('payment.payNow') }}</view>
</view> </view>
</view> </view>
</template> </template>
@@ -81,8 +81,8 @@ export default {
passedTotalAmount: null, passedTotalAmount: null,
passedDepositAmount: null, passedDepositAmount: null,
orderStatus: { orderStatus: {
text: '等待支付', get text() { return this.$t('payment.waitingForPayment') },
desc: '请在15分钟内完成支付', get desc() { return this.$t('payment.pleasePayIn15Min') },
class: 'waiting' class: 'waiting'
} }
} }
@@ -117,6 +117,11 @@ export default {
} }
}, },
onLoad(options) { onLoad(options) {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('payment.orderPayment')
})
if (options && options.orderId) { if (options && options.orderId) {
this.orderId = options.orderId this.orderId = options.orderId
@@ -141,9 +146,9 @@ export default {
} }
this.loadOrderInfo() this.loadOrderInfo()
} else { } else {
uni.showToast({ uni.showToast({
title: '订单信息不存在', title: this.$t('order.orderNotExist'),
icon: 'none' icon: 'none'
}) })
setTimeout(() => { setTimeout(() => {
@@ -158,7 +163,7 @@ export default {
async loadOrderInfo() { async loadOrderInfo() {
try { try {
uni.showLoading({ uni.showLoading({
title: '加载中' title: this.$t('common.loading')
}) })
const res = await queryById(this.orderId) const res = await queryById(this.orderId)
@@ -238,7 +243,7 @@ export default {
async handlePayment() { async handlePayment() {
try { try {
uni.showLoading({ uni.showLoading({
title: '处理中' title: this.$t('common.processing')
}) })
// 调用后端创建微信支付订单接口 // 调用后端创建微信支付订单接口
@@ -259,7 +264,7 @@ export default {
...payParams, ...payParams,
success: async () => { success: async () => {
uni.showToast({ uni.showToast({
title: '支付成功', title: this.$t('payment.paymentSuccess'),
icon: 'success' icon: 'success'
}); });
@@ -279,7 +284,7 @@ export default {
}, },
fail: (err) => { fail: (err) => {
console.error('支付失败:', err) console.error('支付失败:', err)
throw new Error('支付失败,请重试') throw new Error(this.$t('payment.paymentFailedRetry'))
} }
}) })
} else { } else {
@@ -298,7 +303,7 @@ export default {
async sendRentCommand() { async sendRentCommand() {
try { try {
uni.showLoading({ uni.showLoading({
title: '处理中' title: this.$t('common.processing')
}) })
// 调用发送租借指令的接口 // 调用发送租借指令的接口
@@ -307,7 +312,7 @@ export default {
if (res.code === 200) { if (res.code === 200) {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: '租借成功', title: this.$t('device.rentSuccess'),
icon: 'success' icon: 'success'
}) })
@@ -318,12 +323,12 @@ export default {
}) })
}, 1500) }, 1500)
} else { } else {
throw new Error(res.msg || '租借失败') throw new Error(res.msg || this.$t('device.rentFailed'))
} }
} catch (error) { } catch (error) {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: error.message || '租借失败', title: error.message || this.$t('device.rentFailed'),
icon: 'none' icon: 'none'
}) })
} }
+41 -36
View File
@@ -3,82 +3,82 @@
<!-- 支付成功状态 --> <!-- 支付成功状态 -->
<view class="status-card"> <view class="status-card">
<view class="status-icon success"></view> <view class="status-icon success"></view>
<view class="status-text">归还成功</view> <view class="status-text">{{ $t('success.returnSuccess') }}</view>
<view class="status-desc">您的风扇已归还费用已从押金中扣除</view> <view class="status-desc">{{ $t('success.returnSuccessDesc') }}</view>
</view> </view>
<!-- 订单信息 --> <!-- 订单信息 -->
<view class="order-card"> <view class="order-card">
<view class="card-title">订单信息</view> <view class="card-title">{{ $t('success.orderInfo') }}</view>
<view class="info-item"> <view class="info-item">
<text class="label">订单号</text> <text class="label">{{ $t('order.orderNo') }}</text>
<text class="value">{{ orderInfo.orderNo || '-' }}</text> <text class="value">{{ orderInfo.orderNo || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">设备号</text> <text class="label">{{ $t('order.deviceNo') }}</text>
<text class="value">{{ orderInfo.deviceNo || '-' }}</text> <text class="value">{{ orderInfo.deviceNo || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">使用时长</text> <text class="label">{{ $t('success.usedTime') }}</text>
<text class="value">{{ orderInfo.usedTime || '-' }}</text> <text class="value">{{ orderInfo.usedTime || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">套餐时长</text> <text class="label">{{ $t('success.packageTime') }}</text>
<text class="value">{{ orderInfo.packageTime || '1小时' }}</text> <text class="value">{{ orderInfo.packageTime || '1' + $t('time.hour') }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">超出时长</text> <text class="label">{{ $t('success.extraTime') }}</text>
<text class="value">{{ orderInfo.extraTime || '0分钟' }}</text> <text class="value">{{ orderInfo.extraTime || '0' + $t('time.minute') }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">归还时间</text> <text class="label">{{ $t('success.returnTime') }}</text>
<text class="value">{{ orderInfo.endTime || '-' }}</text> <text class="value">{{ orderInfo.endTime || '-' }}</text>
</view> </view>
</view> </view>
<!-- 费用信息 --> <!-- 费用信息 -->
<view class="refund-card"> <view class="refund-card">
<view class="card-title">费用信息</view> <view class="card-title">{{ $t('payment.feeInfo') }}</view>
<view class="info-item"> <view class="info-item">
<text class="label">套餐费用</text> <text class="label">{{ $t('success.packageFee') }}</text>
<text class="value">{{ orderInfo.packagePrice || '0.00' }}</text> <text class="value">{{ orderInfo.packagePrice || '0.00' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">超时费用</text> <text class="label">{{ $t('success.extraFee') }}</text>
<text class="value">{{ orderInfo.extraFee || '0.00' }}</text> <text class="value">{{ orderInfo.extraFee || '0.00' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">总费用</text> <text class="label">{{ $t('success.totalFee') }}</text>
<text class="value">{{ orderInfo.currentFee || '0.00' }}</text> <text class="value">{{ orderInfo.currentFee || '0.00' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">押金</text> <text class="label">{{ $t('success.depositAmount') }}</text>
<text class="value">{{ orderInfo.deposit || '99.00' }}</text> <text class="value">{{ orderInfo.deposit || '99.00' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">退还金额</text> <text class="label">{{ $t('success.refundAmount') }}</text>
<text class="value highlight">{{ orderInfo.refundAmount || '99.00' }}</text> <text class="value highlight">{{ orderInfo.refundAmount || '99.00' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">退还状态</text> <text class="label">{{ $t('success.refundStatus') }}</text>
<text class="value" :class="orderInfo.withdrawStatus || 'waiting'">{{ getWithdrawStatusText() }}</text> <text class="value" :class="orderInfo.withdrawStatus || 'waiting'">{{ getWithdrawStatusText() }}</text>
</view> </view>
</view> </view>
<!-- 退款说明卡片 --> <!-- 退款说明卡片 -->
<view class="notice-card"> <view class="notice-card">
<view class="card-title">退款说明</view> <view class="card-title">{{ $t('success.refundNotice') }}</view>
<view class="notice-content"> <view class="notice-content">
<view>1. 押金剩余金额需要您手动申请提现</view> <view>1. {{ $t('success.refundNotice1') }}</view>
<view>2. 提现申请提交后将在1-3个工作日内退还到原支付账户</view> <view>2. {{ $t('success.refundNotice2') }}</view>
<view>3. 如有疑问请联系客服</view> <view>3. {{ $t('success.refundNotice3') }}</view>
</view> </view>
</view> </view>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="button-group"> <view class="button-group">
<button class="primary-btn" @click="handleWithdraw" v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0">申请退款</button> <button class="primary-btn" @click="handleWithdraw" v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0">{{ $t('success.applyRefund') }}</button>
<button class="primary-btn" @click="goToHome">返回首页</button> <button class="primary-btn" @click="goToHome">{{ $t('success.backToHome') }}</button>
</view> </view>
</view> </view>
</template> </template>
@@ -107,12 +107,17 @@ export default {
} }
}, },
onLoad(options) { onLoad(options) {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('success.returnSuccess')
})
if (options && options.orderId) { if (options && options.orderId) {
this.orderId = options.orderId; this.orderId = options.orderId;
this.loadOrderInfo(); this.loadOrderInfo();
} else { } else {
uni.showToast({ uni.showToast({
title: '订单ID不能为空', title: this.$t('order.orderIdRequired'),
icon: 'none' icon: 'none'
}); });
setTimeout(() => { setTimeout(() => {
@@ -124,18 +129,18 @@ export default {
// 获取退款状态文本 // 获取退款状态文本
getWithdrawStatusText() { getWithdrawStatusText() {
const statusMap = { const statusMap = {
'waiting': '待申请', 'waiting': this.$t('success.refundWaiting'),
'processing': '处理中', 'processing': this.$t('success.refundProcessing'),
'success': '已退款', 'success': this.$t('success.refundSuccess'),
'failed': '退款失败' 'failed': this.$t('success.refundFailed')
}; };
return statusMap[this.orderInfo.withdrawStatus] || '待申请'; return statusMap[this.orderInfo.withdrawStatus] || this.$t('success.refundWaiting');
}, },
// 加载订单信息 // 加载订单信息
async loadOrderInfo() { async loadOrderInfo() {
try { try {
uni.showLoading({ title: '加载中' }); uni.showLoading({ title: this.$t('common.loading') });
const result = await queryById(this.orderId); const result = await queryById(this.orderId);
if (result.code === 200 && result.data) { if (result.code === 200 && result.data) {
@@ -216,7 +221,7 @@ export default {
} catch (error) { } catch (error) {
console.error('加载订单信息错误:', error); console.error('加载订单信息错误:', error);
uni.showToast({ uni.showToast({
title: error.message || '获取订单信息失败', title: error.message || this.$t('order.getOrderFailed'),
icon: 'none' icon: 'none'
}); });
} finally { } finally {
@@ -227,7 +232,7 @@ export default {
// 申请退款 // 申请退款
async handleWithdraw() { async handleWithdraw() {
try { try {
uni.showLoading({ title: '处理中' }); uni.showLoading({ title: this.$t('common.processing') });
const res = await uni.request({ const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/withdraw/add/${this.orderInfo.orderNo}`, url: `${URL || 'http://127.0.0.1:8080'}/app/withdraw/add/${this.orderInfo.orderNo}`,
@@ -241,7 +246,7 @@ export default {
if (res.statusCode === 200 && res.data.code === 200) { if (res.statusCode === 200 && res.data.code === 200) {
uni.showToast({ uni.showToast({
title: '退款申请成功', title: this.$t('order.refundSuccess'),
icon: 'success' icon: 'success'
}); });
@@ -254,12 +259,12 @@ export default {
this.loadOrderInfo(); this.loadOrderInfo();
}, 1500); }, 1500);
} else { } else {
throw new Error(res.data.msg || '退款申请失败'); throw new Error(res.data.msg || this.$t('order.refundFailed'));
} }
} catch (error) { } catch (error) {
console.error('退款申请错误:', error); console.error('退款申请错误:', error);
uni.showToast({ uni.showToast({
title: error.message || '退款申请失败', title: error.message || this.$t('order.refundFailed'),
icon: 'none' icon: 'none'
}); });
} finally { } finally {
+26 -19
View File
@@ -3,27 +3,27 @@
<!-- 支付成功状态 --> <!-- 支付成功状态 -->
<view class="status-card"> <view class="status-card">
<view class="status-icon success"></view> <view class="status-icon success"></view>
<view class="status-text">支付成功</view> <view class="status-text">{{ $t('success.paymentSuccess') }}</view>
<view class="status-desc">您的订单已支付成功</view> <view class="status-desc">{{ $t('success.paymentSuccessDesc') }}</view>
</view> </view>
<!-- 订单信息 --> <!-- 订单信息 -->
<view class="order-card"> <view class="order-card">
<view class="card-title">订单信息</view> <view class="card-title">{{ $t('success.orderInfo') }}</view>
<view class="info-item"> <view class="info-item">
<text class="label">订单号</text> <text class="label">{{ $t('order.orderNo') }}</text>
<text class="value">{{ orderInfo.orderNo || '-' }}</text> <text class="value">{{ orderInfo.orderNo || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">设备号</text> <text class="label">{{ $t('order.deviceNo') }}</text>
<text class="value">{{ orderInfo.deviceNo || '-' }}</text> <text class="value">{{ orderInfo.deviceNo || '-' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">支付金额</text> <text class="label">{{ $t('success.paymentAmount') }}</text>
<text class="value">{{ orderInfo.amount || '0.00' }}</text> <text class="value">{{ orderInfo.amount || '0.00' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="label">支付时间</text> <text class="label">{{ $t('success.paymentTime') }}</text>
<text class="value">{{ orderInfo.payTime || '-' }}</text> <text class="value">{{ orderInfo.payTime || '-' }}</text>
</view> </view>
</view> </view>
@@ -38,8 +38,8 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="button-group"> <view class="button-group">
<button class="primary-btn" @click="goToHome">返回首页</button> <button class="primary-btn" @click="goToHome">{{ $t('success.backToHome') }}</button>
<button class="secondary-btn" @click="goToOrderList">查看订单</button> <button class="secondary-btn" @click="goToOrderList">{{ $t('success.viewOrder') }}</button>
</view> </view>
</view> </view>
</template> </template>
@@ -54,11 +54,18 @@ export default {
orderId: '', orderId: '',
orderInfo: {}, orderInfo: {},
isLoading: true, isLoading: true,
deviceMessage: '正在准备您的设备,请稍候...', deviceMessage: '',
hasTriggeredDevice: false hasTriggeredDevice: false
} }
}, },
onLoad(options) { onLoad(options) {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('success.paymentSuccess')
})
this.deviceMessage = this.$t('success.preparingDevice')
if (options && options.orderId) { if (options && options.orderId) {
this.orderId = options.orderId this.orderId = options.orderId
this.loadOrderInfo() this.loadOrderInfo()
@@ -70,7 +77,7 @@ export default {
}) })
} else { } else {
uni.showToast({ uni.showToast({
title: '订单信息不存在', title: this.$t('order.orderNotExist'),
icon: 'none' icon: 'none'
}) })
setTimeout(() => { setTimeout(() => {
@@ -82,7 +89,7 @@ export default {
async loadOrderInfo() { async loadOrderInfo() {
try { try {
uni.showLoading({ uni.showLoading({
title: '加载中' title: this.$t('common.loading')
}) })
const res = await queryById(this.orderId) const res = await queryById(this.orderId)
@@ -118,7 +125,7 @@ export default {
} catch (error) { } catch (error) {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: error.message || '获取订单信息失败', title: error.message || this.$t('order.getOrderFailed'),
icon: 'none' icon: 'none'
}) })
} }
@@ -134,7 +141,7 @@ export default {
this.hasTriggeredDevice = true this.hasTriggeredDevice = true
uni.$emit('orderSuccess:' + this.orderId) uni.$emit('orderSuccess:' + this.orderId)
this.isLoading = true this.isLoading = true
this.deviceMessage = '正在准备您的设备,请稍候...' this.deviceMessage = this.$t('success.preparingDevice')
try { try {
console.log(`准备触发弹出风扇,orderId: ${this.orderId}`) console.log(`准备触发弹出风扇,orderId: ${this.orderId}`)
@@ -144,19 +151,19 @@ export default {
console.log('确认支付并弹出风扇结果:', JSON.stringify(result)) console.log('确认支付并弹出风扇结果:', JSON.stringify(result))
if (result && result.code === 200) { if (result && result.code === 200) {
this.deviceMessage = '设备已弹出,请取走您的风扇' this.deviceMessage = this.$t('success.deviceReady')
uni.showToast({ uni.showToast({
title: '风扇已弹出', title: this.$t('success.deviceReady'),
icon: 'success' icon: 'success'
}) })
} else { } else {
throw new Error((result && result.msg) || '弹出风扇失败') throw new Error((result && result.msg) || this.$t('success.deviceFailed'))
} }
} catch (error) { } catch (error) {
console.error('弹出风扇错误:', error) console.error('弹出风扇错误:', error)
this.deviceMessage = '弹出设备失败,请联系客服' this.deviceMessage = this.$t('success.deviceFailed')
uni.showToast({ uni.showToast({
title: error.message || '弹出风扇失败,请联系客服', title: error.message || this.$t('success.deviceFailed'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
+20 -20
View File
@@ -8,7 +8,7 @@
<!-- 场地信息卡片 --> <!-- 场地信息卡片 -->
<view class="info-card"> <view class="info-card">
<!-- 场地名称 --> <!-- 场地名称 -->
<view class="position-name">{{ positionInfo.name || '加载中...' }}</view> <view class="position-name">{{ positionInfo.name || $t('common.loading') }}</view>
<!-- 地址信息 --> <!-- 地址信息 -->
<view class="info-item" v-if="positionInfo.location"> <view class="info-item" v-if="positionInfo.location">
@@ -19,30 +19,30 @@
<!-- 营业时间 --> <!-- 营业时间 -->
<view class="info-item" v-if="positionInfo.workTime && positionInfo.workTime !== '0'"> <view class="info-item" v-if="positionInfo.workTime && positionInfo.workTime !== '0'">
<image src="/static/device-time.png" class="item-icon" mode="aspectFit"></image> <image src="/static/device-time.png" class="item-icon" mode="aspectFit"></image>
<text class="item-text">营业时间{{ positionInfo.workTime }}</text> <text class="item-text">{{ $t('location.businessHours') }}{{ positionInfo.workTime }}</text>
</view> </view>
<!-- 计费信息 --> <!-- 计费信息 -->
<view class="info-item"> <view class="info-item">
<image src="/static/device-price.png" class="item-icon" mode="aspectFit"></image> <image src="/static/device-price.png" class="item-icon" mode="aspectFit"></image>
<text class="item-text">计费{{ pricingText }}</text> <text class="item-text">{{ $t('device.pricing') }}{{ pricingText }}</text>
</view> </view>
<!-- 按钮组 --> <!-- 按钮组 -->
<view class="button-group"> <view class="button-group">
<view style="display: flex;flex-direction: row;gap: 10rpx;"> <view style="display: flex;flex-direction: row;gap: 10rpx;">
<view class="status-btn" v-if="isRentable">可租借</view> <view class="status-btn" v-if="isRentable">{{ $t('location.rent') }}</view>
<view class="status-btn" v-if="isReturnable">可归还</view> <view class="status-btn" v-if="isReturnable">{{ $t('location.return') }}</view>
</view> </view>
<view class="nav-btn" @click.stop="navigateToPosition">导航去这</view> <view class="nav-btn" @click.stop="navigateToPosition">{{ $t('location.navigateHere') }}</view>
</view> </view>
</view> </view>
<!-- 底部操作按钮 --> <!-- 底部操作按钮 -->
<view class="footer-actions"> <view class="footer-actions">
<button class="action-btn btn-outline" @click="reportError">设备报错</button> <button class="action-btn btn-outline" @click="reportError">{{ $t('device.reportError') }}</button>
<button class="action-btn btn-primary" @click="scanCode">扫码使用</button> <button class="action-btn btn-primary" @click="scanCode">{{ $t('device.scanToUse') }}</button>
</view> </view>
</view> </view>
</template> </template>
@@ -93,7 +93,7 @@
const loadPositionDetail = async () => { const loadPositionDetail = async () => {
try { try {
uni.showLoading({ uni.showLoading({
title: '加载中...' title: $t('common.loading')
}) })
const res = await uni.request({ const res = await uni.request({
@@ -111,10 +111,10 @@
if (position) { if (position) {
positionInfo.value = position positionInfo.value = position
} else { } else {
uni.showToast({ uni.showToast({
title: '场地不存在', title: this.$t('location.notExist'),
icon: 'none' icon: 'none'
}) })
} }
} else if (res.statusCode === 401 || res.data?.code === 401 || res.data?.code === 40101) { } else if (res.statusCode === 401 || res.data?.code === 401 || res.data?.code === 40101) {
uni.reLaunch({ uni.reLaunch({
@@ -124,7 +124,7 @@
} catch (e) { } catch (e) {
console.error('加载场地详情失败:', e) console.error('加载场地详情失败:', e)
uni.showToast({ uni.showToast({
title: '加载失败', title: $t('common.loadFailed'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -135,7 +135,7 @@
const navigateToPosition = () => { const navigateToPosition = () => {
if (!positionInfo.value.latitude || !positionInfo.value.longitude) { if (!positionInfo.value.latitude || !positionInfo.value.longitude) {
uni.showToast({ uni.showToast({
title: '该场地坐标信息异常', title: $t('location.coordinateError'),
icon: 'none' icon: 'none'
}) })
return return
@@ -150,7 +150,7 @@
longitude < -180 || longitude > 180 || longitude < -180 || longitude > 180 ||
(latitude === 0 && longitude === 0)) { (latitude === 0 && longitude === 0)) {
uni.showToast({ uni.showToast({
title: '该场地坐标信息异常', title: $t('location.coordinateError'),
icon: 'none' icon: 'none'
}) })
return return
@@ -185,10 +185,10 @@
}, },
fail: (err) => { fail: (err) => {
console.error('扫码失败:', err) console.error('扫码失败:', err)
uni.showToast({ uni.showToast({
title: '扫码失败', title: this.$t('home.scanFailed'),
icon: 'none' icon: 'none'
}) })
} }
}) })
} }
+63 -58
View File
@@ -4,13 +4,13 @@
<view class="order-card"> <view class="order-card">
<view class="order-header"> <view class="order-header">
<text class="title">{{ getOrderStatusText() }}</text> <text class="title">{{ getOrderStatusText() }}</text>
<text class="order-no">订单号{{ orderInfo.orderNo || '-' }}</text> <text class="order-no">{{ $t('order.orderNo') }}{{ orderInfo.orderNo || '-' }}</text>
</view> </view>
<view class="device-info"> <view class="device-info">
<view class="device-left"> <view class="device-left">
<view class="device-name">共享风扇</view> <view class="device-name">{{ $t('device.sharedFan') }}</view>
<view class="device-id">设备号{{ deviceId }}</view> <view class="device-id">{{ $t('order.deviceNo') }}{{ deviceId }}</view>
</view> </view>
<!-- 支付方式标识 --> <!-- 支付方式标识 -->
@@ -19,41 +19,41 @@
<view class="payment-badge wx-score" v-if="orderInfo.payWay == 'wx_score_pay'"> <view class="payment-badge wx-score" v-if="orderInfo.payWay == 'wx_score_pay'">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image> <image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image>
<view class="badge-text"> <view class="badge-text">
<text>微信支付分</text> <text>{{ $t('order.wxPayScore') }}</text>
<text class="divider">|</text> <text class="divider">|</text>
<text class="highlight">免押租借</text> <text class="highlight">{{ $t('order.depositFree') }}</text>
</view> </view>
</view> </view>
<!-- 会员订单标识 --> <!-- 会员订单标识 -->
<view class="payment-badge member" v-else-if="orderInfo.payWay == 'wx_member_pay'"> <view class="payment-badge member" v-else-if="orderInfo.payWay == 'wx_member_pay'">
<text class="badge-text">会员订单</text> <text class="badge-text">{{ $t('order.memberOrder') }}</text>
</view> </view>
<!-- 微信支付押金标识 --> <!-- 微信支付押金标识 -->
<view class="payment-badge deposit" v-else-if="orderInfo.payWay == 'wx_pay'"> <view class="payment-badge deposit" v-else-if="orderInfo.payWay == 'wx_pay'">
<text class="badge-text">押金租借</text> <text class="badge-text">{{ $t('order.depositPay') }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="time-info"> <view class="time-info">
<view class="time-item"> <view class="time-item">
<text class="label">开始时间</text> <text class="label">{{ $t('order.startTime') }}</text>
<text class="value">{{ orderInfo.startTime }}</text> <text class="value">{{ orderInfo.startTime }}</text>
</view> </view>
<view class="time-item" v-if="orderInfo.endTime"> <view class="time-item" v-if="orderInfo.endTime">
<text class="label">结束时间</text> <text class="label">{{ $t('order.endTime') }}</text>
<text class="value">{{ orderInfo.endTime }}</text> <text class="value">{{ orderInfo.endTime }}</text>
</view> </view>
<view class="time-item" v-if="orderInfo.orderStatus === 'in_used'"> <view class="time-item" v-if="orderInfo.orderStatus === 'in_used'">
<text class="label">已使用时长</text> <text class="label">{{ $t('order.used') }}</text>
<text class="value highlight">{{ orderInfo.usedTime }}</text> <text class="value highlight">{{ orderInfo.usedTime }}</text>
</view> </view>
<view class="time-item" v-if="orderInfo.orderStatus === 'in_used'"> <view class="time-item" v-if="orderInfo.orderStatus === 'in_used'">
<text class="label">当前费用</text> <text class="label">{{ $t('order.currentFee') }}</text>
<text class="value">{{ orderInfo.currentFee }}</text> <text class="value">{{ orderInfo.currentFee }}</text>
</view> </view>
<view class="time-item" v-if="orderInfo.phone"> <view class="time-item" v-if="orderInfo.phone">
<text class="label">联系电话</text> <text class="label">{{ $t('payment.contactPhone') }}</text>
<text class="value">{{ orderInfo.phone }}</text> <text class="value">{{ orderInfo.phone }}</text>
</view> </view>
</view> </view>
@@ -69,42 +69,42 @@
<!-- 费用信息卡片 --> <!-- 费用信息卡片 -->
<view class="notice-card" v-if="orderInfo.depositAmount || orderInfo.packageTime"> <view class="notice-card" v-if="orderInfo.depositAmount || orderInfo.packageTime">
<view class="notice-title">费用信息</view> <view class="notice-title">{{ $t('payment.feeInfo') }}</view>
<view class="notice-list"> <view class="notice-list">
<view class="notice-item" v-if="orderInfo.depositAmount"> <view class="notice-item" v-if="orderInfo.depositAmount">
<view class="dot"></view> <view class="dot"></view>
<text>押金{{ orderInfo.depositAmount }}</text> <text>{{ $t('payment.deposit') }}{{ orderInfo.depositAmount }}</text>
</view> </view>
<view class="notice-item" v-if="orderInfo.packageTime && orderInfo.packagePrice"> <view class="notice-item" v-if="orderInfo.packageTime && orderInfo.packagePrice">
<view class="dot"></view> <view class="dot"></view>
<text>套餐{{ orderInfo.packagePrice }} / {{ formatTime(orderInfo.packageTime) }}</text> <text>{{ $t('payment.package') }}{{ orderInfo.packagePrice }}{{ $t('unit.yuan') }} / {{ formatTime(orderInfo.packageTime) }}</text>
</view> </view>
<view class="notice-item"> <view class="notice-item">
<view class="dot"></view> <view class="dot"></view>
<text>合计{{ orderInfo.payAmount || 0 }}</text> <text>{{ $t('payment.total') }}{{ orderInfo.payAmount || 0 }}</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 归还说明 --> <!-- 归还说明 -->
<view class="notice-card" v-if="orderInfo.orderStatus === 'in_used'"> <view class="notice-card" v-if="orderInfo.orderStatus === 'in_used'">
<view class="notice-title">归还说明</view> <view class="notice-title">{{ $t('order.returnInstructions') }}</view>
<view class="notice-list"> <view class="notice-list">
<view class="notice-item"> <view class="notice-item">
<view class="dot"></view> <view class="dot"></view>
<text>请确保设备完好无损</text> <text>{{ $t('order.ensureDeviceIntact') }}</text>
</view> </view>
<view class="notice-item"> <view class="notice-item">
<view class="dot"></view> <view class="dot"></view>
<text>将风扇插入原位置或空闲插口</text> <text>{{ $t('order.insertFanBack') }}</text>
</view> </view>
<view class="notice-item"> <view class="notice-item">
<view class="dot"></view> <view class="dot"></view>
<text>系统将自动检测归还并处理退款</text> <text>{{ $t('order.autoDetectReturn') }}</text>
</view> </view>
<view class="notice-item"> <view class="notice-item">
<view class="dot"></view> <view class="dot"></view>
<text>归还成功后将自动跳转到成功页面</text> <text>{{ $t('order.autoJumpAfterReturn') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -117,25 +117,25 @@
<view class="bottom-bar"> <view class="bottom-bar">
<!-- 使用中状态显示归还相关操作 --> <!-- 使用中状态显示归还相关操作 -->
<view v-if="orderInfo.orderStatus === 'in_used'" class="action-item secondary" @click="checkReturnStatus"> <view v-if="orderInfo.orderStatus === 'in_used'" class="action-item secondary" @click="checkReturnStatus">
刷新状态</view> {{ $t('order.refreshStatus') }}</view>
<view v-if="orderInfo.orderStatus === 'in_used' && !showExpressAction" class="action-item primary"> <view v-if="orderInfo.orderStatus === 'in_used' && !showExpressAction" class="action-item primary">
倒计时{{ formatHms(countdownRemaining) }} {{ $t('order.countdown') }}{{ formatHms(countdownRemaining) }}
</view> </view>
<view v-if="orderInfo.orderStatus === 'in_used' && showExpressAction" class="action-item primary" @click="expressRetrunOrder"> <view v-if="orderInfo.orderStatus === 'in_used' && showExpressAction" class="action-item primary" @click="expressRetrunOrder">
暂停计费快递归还 {{ $t('order.pauseAndExpress') }}
</view> </view>
<!-- 已完成状态显示查看详情和返回首页 --> <!-- 已完成状态显示查看详情和返回首页 -->
<view v-if="orderInfo.orderStatus === 'used_done' || orderInfo.orderStatus === 'used_down'" <view v-if="orderInfo.orderStatus === 'used_done' || orderInfo.orderStatus === 'used_down'"
class="action-item secondary" @click="goToHome">返回首页</view> class="action-item secondary" @click="goToHome">{{ $t('order.backToHome') }}</view>
<view v-if="orderInfo.orderStatus === 'used_done' || orderInfo.orderStatus === 'used_down'" <view v-if="orderInfo.orderStatus === 'used_done' || orderInfo.orderStatus === 'used_down'"
class="action-item primary" @click="viewOrderDetails">查看详情</view> class="action-item primary" @click="viewOrderDetails">{{ $t('order.viewDetails') }}</view>
<!-- 待支付状态显示支付和取消操作 --> <!-- 待支付状态显示支付和取消操作 -->
<view v-if="orderInfo.orderStatus === 'waiting_for_payment'" class="action-item secondary" <view v-if="orderInfo.orderStatus === 'waiting_for_payment'" class="action-item secondary"
@click="handleCancelOrder">取消订单</view> @click="handleCancelOrder">{{ $t('order.cancelOrder') }}</view>
<view v-if="orderInfo.orderStatus === 'waiting_for_payment'" class="action-item primary" <view v-if="orderInfo.orderStatus === 'waiting_for_payment'" class="action-item primary"
@click="handlePayment">立即支付</view> @click="handlePayment">{{ $t('order.payNow') }}</view>
</view> </view>
</view> </view>
</template> </template>
@@ -190,6 +190,11 @@
onLoad(options) { onLoad(options) {
console.log('Return page loaded with options:', JSON.stringify(options)) console.log('Return page loaded with options:', JSON.stringify(options))
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('order.orderDetail')
})
// 标记页面为活跃状态 // 标记页面为活跃状态
this.isPageActive = true this.isPageActive = true
@@ -209,7 +214,7 @@
} else { } else {
// 缺少必要参数 // 缺少必要参数
uni.showToast({ uni.showToast({
title: '缺少订单信息', title: this.$t('order.orderInfoMissing'),
icon: 'none' icon: 'none'
}) })
@@ -406,9 +411,9 @@
// 显示归还成功弹窗 // 显示归还成功弹窗
uni.showModal({ uni.showModal({
title: '归还成功', title: this.$t('order.returnSuccess'),
content: '风扇已归还成功,剩余押金将退还到您的账户', content: this.$t('order.returnSuccessMessage'),
confirmText: '查看详情', confirmText: this.$t('order.viewDetails'),
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
// 跳转到归还成功页面查看详情 // 跳转到归还成功页面查看详情
@@ -428,17 +433,17 @@
// 根据订单状态获取对应的文字显示 // 根据订单状态获取对应的文字显示
getOrderStatusText() { getOrderStatusText() {
const statusMap = { const statusMap = {
'waiting_for_payment': '待支付', 'waiting_for_payment': this.$t('order.waitingForPayment'),
'payment_in_progress': '支付中', 'payment_in_progress': this.$t('order.paymentInProgress'),
'payment_successful': '支付成功', 'payment_successful': this.$t('order.paymentSuccess'),
'in_used': '使用中', 'in_used': this.$t('order.inUse'),
'payment_failed': '支付失败', 'payment_failed': this.$t('order.paymentFailed'),
'order_cancelled': '已取消', 'order_cancelled': this.$t('order.cancelled'),
'used_done': '已完成', 'used_done': this.$t('order.finished'),
'used_down': '已完成' 'used_down': this.$t('order.finished')
} }
return statusMap[this.orderInfo.orderStatus] || '使用中' return statusMap[this.orderInfo.orderStatus] || this.$t('order.inUse')
}, },
// 获取订单详情 // 获取订单详情
@@ -453,7 +458,7 @@
// uni.showLoading({ title: '加载中' }) // uni.showLoading({ title: '加载中' })
if (!this.orderInfo.orderId) { if (!this.orderInfo.orderId) {
throw new Error('订单ID不能为空') throw new Error(this.$t('order.orderIdRequired'))
} }
const result = await queryById(this.orderInfo.orderId) const result = await queryById(this.orderInfo.orderId)
@@ -527,7 +532,7 @@
} catch (error) { } catch (error) {
console.error('获取订单详情错误:', error) console.error('获取订单详情错误:', error)
uni.showToast({ uni.showToast({
title: error.message || '获取订单信息失败', title: error.message || this.$t('order.getOrderFailed'),
icon: 'none' icon: 'none'
}) })
@@ -679,12 +684,12 @@
if (this.currentStatusChecks >= this.maxStatusChecks) { if (this.currentStatusChecks >= this.maxStatusChecks) {
this.clearStatusCheckTimer() this.clearStatusCheckTimer()
// 提示用户手动刷新 // 提示用户手动刷新
uni.showToast({ uni.showToast({
title: '请手动刷新查看归还状态', title: this.$t('order.pleaseRefreshManually'),
icon: 'none', icon: 'none',
duration: 3000 duration: 3000
}) })
} }
} else { } else {
console.log('页面已不活跃,停止状态检查计时器') console.log('页面已不活跃,停止状态检查计时器')
@@ -701,7 +706,7 @@
// uni.showLoading({ title: '加载中' }) // uni.showLoading({ title: '加载中' })
if (!this.deviceId) { if (!this.deviceId) {
throw new Error('设备号不能为空') throw new Error(this.$t('device.deviceNoRequired'))
} }
// 这里调用API查询该设备的使用中订单 // 这里调用API查询该设备的使用中订单
@@ -742,12 +747,12 @@
// 获取详细订单信息 // 获取详细订单信息
this.getOrderDetails() this.getOrderDetails()
} else { } else {
throw new Error('未找到使用中的订单') throw new Error(this.$t('order.noOrderInUse'))
} }
} catch (error) { } catch (error) {
console.error('通过设备号查询订单失败:', error) console.error('通过设备号查询订单失败:', error)
uni.showToast({ uni.showToast({
title: error.message || '获取订单信息失败', title: error.message || this.$t('order.getOrderFailed'),
icon: 'none' icon: 'none'
}) })
@@ -795,13 +800,13 @@
// 取消订单 // 取消订单
handleCancelOrder() { handleCancelOrder() {
uni.showModal({ uni.showModal({
title: '确认取消', title: this.$t('order.confirmCancel'),
content: '确定要取消此订单吗?', content: this.$t('order.confirmCancelContent'),
success: async (res) => { success: async (res) => {
if (res.confirm) { if (res.confirm) {
try { try {
uni.showLoading({ uni.showLoading({
title: '处理中' title: this.$t('common.processing')
}); });
const result = await cancelOrder({ const result = await cancelOrder({
orderId: this.orderInfo.orderId orderId: this.orderInfo.orderId
@@ -809,17 +814,17 @@
if (result.code === 200) { if (result.code === 200) {
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '订单已取消', title: this.$t('order.cancelSuccess'),
icon: 'success' icon: 'success'
}); });
await this.getOrderDetails(); await this.getOrderDetails();
} else { } else {
throw new Error(result.msg || '取消订单失败'); throw new Error(result.msg || this.$t('order.cancelFailed'));
} }
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: error.message || '取消订单失败', title: error.message || this.$t('order.cancelFailed'),
icon: 'none' icon: 'none'
}); });
} }
+28 -21
View File
@@ -12,8 +12,8 @@
<view class="list-wrap"> <view class="list-wrap">
<view class="panel"> <view class="panel">
<view class="filter-tabs"> <view class="filter-tabs">
<view class="tab" :class="{ active: activeTab === 'rent' }" @click="setTab('rent')">可租借</view> <view class="tab" :class="{ active: activeTab === 'rent' }" @click="setTab('rent')">{{ $t('location.rent') }}</view>
<view class="tab" :class="{ active: activeTab === 'return' }" @click="setTab('return')">可归还</view> <view class="tab" :class="{ active: activeTab === 'return' }" @click="setTab('return')">{{ $t('location.return') }}</view>
</view> </view>
<scroll-view class="list-scroll" scroll-y="true"> <scroll-view class="list-scroll" scroll-y="true">
<view class="card" :class="{ available: isRentable(item), invalid: !isValidCoordinate(item.latitude, item.longitude) }" <view class="card" :class="{ available: isRentable(item), invalid: !isValidCoordinate(item.latitude, item.longitude) }"
@@ -28,16 +28,16 @@
<view class="row sub" v-if="item.location"> <view class="row sub" v-if="item.location">
<text class="addr">{{ item.location }}</text> <text class="addr">{{ item.location }}</text>
</view> </view>
<view class="row meta" v-if="item.workTime && item.workTime !== '0'"> <view class="row meta" v-if="item.workTime && item.workTime !== '0'">
<text class="time">营业时间{{ item.workTime }}</text> <text class="time">{{ $t('location.businessHours') }}{{ item.workTime }}</text>
</view> </view>
<view class="row meta" v-if="!isValidCoordinate(item.latitude, item.longitude)"> <view class="row meta" v-if="!isValidCoordinate(item.latitude, item.longitude)">
<text class="time" style="color: #ff6b6b;">坐标信息异常</text> <text class="time" style="color: #ff6b6b;">{{ $t('location.coordinateError') }}</text>
</view> </view>
<view class="tags"> <view class="tags">
<view class="tag rent" v-if="isRentable(item)">可租借</view> <view class="tag rent" v-if="isRentable(item)">{{ $t('location.rent') }}</view>
<view class="tag return" v-if="isReturnable(item)">可归还</view> <view class="tag return" v-if="isReturnable(item)">{{ $t('location.return') }}</view>
</view> </view>
</view> </view>
<view class="actions"> <view class="actions">
@@ -50,10 +50,10 @@
</view> </view>
</view> </view>
<view class="empty-state" v-if="!isLoading && (!positionList || positionList.length === 0)"> <view class="empty-state" v-if="!isLoading && (!positionList || positionList.length === 0)">
<image class="empty-icon" src="/static/scan-icon.png" mode="aspectFit" /> <image class="empty-icon" src="/static/scan-icon.png" mode="aspectFit" />
<text class="empty-text">附近暂无设备</text> <text class="empty-text">{{ $t('home.noNearbyDevice') }}</text>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
</view> </view>
@@ -75,6 +75,16 @@
getRegeo, getRegeo,
calculateDistanceSync calculateDistanceSync
} from '../../utils/mapUtils.js' } from '../../utils/mapUtils.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('search.title')
})
init()
})
const userLocation = ref(null) const userLocation = ref(null)
const positionList = ref([]) const positionList = ref([])
@@ -208,7 +218,7 @@
const navigateToPosition = (position) => { const navigateToPosition = (position) => {
if (!isValidCoordinate(position.latitude, position.longitude)) { if (!isValidCoordinate(position.latitude, position.longitude)) {
uni.showToast({ uni.showToast({
title: '该位置坐标无效', title: $t('search.invalidCoordinate'),
icon: 'none' icon: 'none'
}) })
return return
@@ -226,7 +236,7 @@
const goToPositionDetail = (position) => { const goToPositionDetail = (position) => {
if (!position.positionId) { if (!position.positionId) {
uni.showToast({ uni.showToast({
title: '场地信息异常', title: $t('search.positionInfoError'),
icon: 'none' icon: 'none'
}) })
return return
@@ -236,9 +246,6 @@
}) })
} }
onMounted(() => {
init()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+10 -4
View File
@@ -25,15 +25,21 @@
}, },
async onLoad(option) { async onLoad(option) {
console.log('bagCheck onLoad option:', option); console.log('bagCheck onLoad option:', option);
//
uni.setNavigationBarTitle({
title: this.$t('device.checking')
})
try { try {
uni.showLoading({ uni.showLoading({
title: '处理中...', title: this.$t('common.processing'),
mask: true mask: true
}); });
// //
if (!option || !option.deviceNo) { if (!option || !option.deviceNo) {
throw new Error('未识别到设备编号'); throw new Error(this.$t('device.deviceNoNotRecognized'));
} }
const deviceNo = option.deviceNo; const deviceNo = option.deviceNo;
@@ -97,10 +103,10 @@
error.message.includes('未识别到设备编号') || error.message.includes('未识别到设备编号') ||
error.message.includes('网络请求失败') || error.message.includes('网络请求失败') ||
error.message.includes('服务器错误') error.message.includes('服务器错误')
)) { ) ) {
console.error('扫码检查订单失败:', error); console.error('扫码检查订单失败:', error);
uni.showToast({ uni.showToast({
title: error.message || '处理失败,请稍后重试', title: error.message || this.$t('device.processFailed'),
icon: 'none', icon: 'none',
duration: 2000 duration: 2000
}); });
+110 -6
View File
@@ -1,18 +1,27 @@
<template> <template>
<view class="setting-page"> <view class="setting-page">
<view class="group">
<view class="item" @click="showLanguageSelector">
<text class="label">{{ $t('settings.language') }}</text>
<view class="right">
<text class="value">{{ currentLanguageText }}</text>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view>
</view>
</view>
<view class="group"> <view class="group">
<view class="item" @click="navigateTo('/pages/legal/agreement')"> <view class="item" @click="navigateTo('/pages/legal/agreement')">
<text class="label">用户协议</text> <text class="label">{{ $t('user.userAgreement') }}</text>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
<view class="item" @click="navigateTo('/pages/legal/privacy')"> <view class="item" @click="navigateTo('/pages/legal/privacy')">
<text class="label">隐私政策</text> <text class="label">{{ $t('user.privacyPolicy') }}</text>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
</view> </view>
<view class="group"> <view class="group">
<view class="item" @click="handleLogout"> <view class="item" @click="handleLogout">
<text class="label">退出登录</text> <text class="label">{{ $t('user.logout') }}</text>
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon> <uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
</view> </view>
</view> </view>
@@ -20,21 +29,105 @@
</template> </template>
<script setup> <script setup>
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'
const { t: $t } = useI18n()
// i18n
const instance = getCurrentInstance()
const globalI18n = instance?.appContext?.config?.globalProperties?.$i18n
//
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('settings.title')
})
})
//
const currentLanguage = ref(uni.getStorageSync('language') || 'zh-CN')
//
const currentLanguageText = computed(() => {
return currentLanguage.value === 'zh-CN' ? '简体中文' : 'English'
})
const navigateTo = (url) => { const navigateTo = (url) => {
uni.navigateTo({ url }) uni.navigateTo({ url })
} }
//
const showLanguageSelector = () => {
uni.showActionSheet({
itemList: ['简体中文', 'English'],
success: (res) => {
const lang = res.tapIndex === 0 ? 'zh-CN' : 'en-US'
if (lang !== currentLanguage.value) {
console.log('========================================')
console.log('=== 用户选择切换语言 ===')
console.log('旧语言:', currentLanguage.value)
console.log('新语言:', lang)
// 1.
uni.setStorageSync('language', lang)
console.log('✓ 语言已保存到缓存')
// 2. i18n
if (globalI18n) {
console.log('✓ 正在更新 globalI18n.locale...')
console.log(' 更新前:', globalI18n.locale)
globalI18n.locale = lang
console.log(' 更新后:', globalI18n.locale)
console.log('✓ 测试翻译:', globalI18n.t('common.loading'))
} else {
console.warn('✗ globalI18n 不存在!')
console.warn(' instance:', !!instance)
console.warn(' appContext:', !!instance?.appContext)
console.warn(' globalProperties:', !!instance?.appContext?.config?.globalProperties)
}
// 3.
currentLanguage.value = lang
console.log('========================================')
// 4.
uni.showToast({
title: lang === 'zh-CN' ? '语言已切换,正在刷新...' : 'Language switched, refreshing...',
icon: 'none',
duration: 800
})
// 5. i18n
setTimeout(() => {
console.log('=== 准备 reLaunch 到首页 ===')
// 使 reLaunch
uni.reLaunch({
url: '/pages/index/index',
success: () => {
console.log('✓ 页面已重新加载')
},
fail: (err) => {
console.error('✗ 页面重载失败:', err)
}
})
}, 800)
}
}
})
}
const handleLogout = async () => { const handleLogout = async () => {
uni.showModal({ uni.showModal({
title: '提示', title: $t('common.tips'),
content: '确定要退出登录吗?', content: $t('user.confirmLogout'),
success: async (res) => { success: async (res) => {
if (res.confirm) { if (res.confirm) {
const response = await userLogout(); const response = await userLogout();
if (response.code == 200) { if (response.code == 200) {
uni.showToast({ title: '退出成功', icon: 'none' }) uni.showToast({ title: $t('user.logoutSuccess'), icon: 'none' })
setTimeout(() => { setTimeout(() => {
uni.removeStorageSync('token') uni.removeStorageSync('token')
uni.removeStorageSync('userInfo') uni.removeStorageSync('userInfo')
@@ -77,4 +170,15 @@ const handleLogout = async () => {
font-size: 30rpx; font-size: 30rpx;
color: #333333; color: #333333;
} }
.right {
display: flex;
align-items: center;
gap: 8rpx;
}
.value {
font-size: 28rpx;
color: #999999;
}
</style> </style>
+27 -15
View File
@@ -6,40 +6,40 @@
<image :src="userInfo.avatar || '/static/images/default-avatar.png'" mode="aspectFill"></image> <image :src="userInfo.avatar || '/static/images/default-avatar.png'" mode="aspectFill"></image>
</view> </view>
<view class="user-info"> <view class="user-info">
<text class="nickname">{{ userInfo.nickName || '未登录' }}</text> <text class="nickname">{{ userInfo.nickName || $t('user.notLoggedIn') }}</text>
<text class="phone">{{ userInfo.phone || '未绑定手机号' }}</text> <text class="phone">{{ userInfo.phone || $t('user.phoneNotBound') }}</text>
</view> </view>
</view> </view>
<!-- 余额卡片 --> <!-- 余额卡片 -->
<view class="balance-card"> <view class="balance-card">
<view class="balance-title">余额</view> <view class="balance-title">{{ $t('userProfile.balance') }}</view>
<view class="balance-amount">{{ userInfo.balanceAmount || '0.00' }}</view> <view class="balance-amount">{{ userInfo.balanceAmount || '0.00' }}</view>
<view class="balance-desc">可用于租借设备</view> <view class="balance-desc">{{ $t('user.balanceDesc') }}</view>
</view> </view>
<!-- 功能菜单 --> <!-- 功能菜单 -->
<view class="menu-list"> <view class="menu-list">
<view class="menu-item" @click="navigateTo('/pages/order/list')"> <view class="menu-item" @click="navigateTo('/pages/order/index')">
<text class="menu-icon">📋</text> <text class="menu-icon">📋</text>
<text class="menu-text">我的订单</text> <text class="menu-text">{{ $t('user.myOrders') }}</text>
<text class="menu-arrow">></text> <text class="menu-arrow">></text>
</view> </view>
<view class="menu-item" @click="navigateTo('/pages/user/feedback')"> <view class="menu-item" @click="navigateTo('/pages/feedback/index')">
<text class="menu-icon">💬</text> <text class="menu-icon">💬</text>
<text class="menu-text">意见反馈</text> <text class="menu-text">{{ $t('user.feedback') }}</text>
<text class="menu-arrow">></text> <text class="menu-arrow">></text>
</view> </view>
<view class="menu-item" @click="navigateTo('/pages/user/about')"> <view class="menu-item" @click="navigateTo('/pages/help/index')">
<text class="menu-icon"></text> <text class="menu-icon"></text>
<text class="menu-text">关于我们</text> <text class="menu-text">{{ $t('help.title') }}</text>
<text class="menu-arrow">></text> <text class="menu-arrow">></text>
</view> </view>
</view> </view>
<!-- 退出登录按钮 --> <!-- 退出登录按钮 -->
<view class="logout-btn" @click="handleLogout" v-if="isLogin"> <view class="logout-btn" @click="handleLogout" v-if="isLogin">
<text>退出登录</text> <text>{{ $t('user.logout') }}</text>
</view> </view>
</view> </view>
</template> </template>
@@ -55,6 +55,12 @@ export default {
isLogin: false isLogin: false
} }
}, },
onLoad() {
//
uni.setNavigationBarTitle({
title: this.$t('user.personalCenter')
})
},
onShow() { onShow() {
this.loadUserInfo() this.loadUserInfo()
}, },
@@ -90,16 +96,22 @@ export default {
}, },
handleLogout() { handleLogout() {
uni.showModal({ uni.showModal({
title: '提示', title: this.$t('common.tips'),
content: '确定要退出登录吗?', content: this.$t('user.confirmLogout'),
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
uni.removeStorageSync('token') uni.removeStorageSync('token')
uni.removeStorageSync('userInfo') uni.removeStorageSync('userInfo')
this.isLogin = false this.isLogin = false
uni.redirectTo({ uni.showToast({
url: '/pages/login/index' title: this.$t('user.logoutSuccess'),
icon: 'success'
}) })
setTimeout(() => {
uni.redirectTo({
url: '/pages/login/index'
})
}, 500)
} }
} }
}) })
+26 -20
View File
@@ -9,15 +9,15 @@
<button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button> <button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
<!-- #endif --> <!-- #endif -->
</view> </view>
<view class="avatar-tip">点击头像更换</view> <view class="avatar-tip">{{ $t('userProfile.clickToChange') }}</view>
</view> </view>
<view class="form-section"> <view class="form-section">
<!-- 昵称编辑区域 --> <!-- 昵称编辑区域 -->
<view class="form-item nickname-item" :class="{ editing: isEditingNickname }"> <view class="form-item nickname-item" :class="{ editing: isEditingNickname }">
<view class="label">昵称</view> <view class="label">{{ $t('userProfile.nickname') }}</view>
<view class="value" v-if="!isEditingNickname" @click="startEditNickname"> <view class="value" v-if="!isEditingNickname" @click="startEditNickname">
<text class="value-text">{{ userInfo.nickName || '未设置' }}</text> <text class="value-text">{{ userInfo.nickName || $t('userProfile.notSet') }}</text>
<uv-icon name="edit-pen" size="16" color="#999999"></uv-icon> <uv-icon name="edit-pen" size="16" color="#999999"></uv-icon>
</view> </view>
</view> </view>
@@ -27,25 +27,25 @@
<input <input
class="nickname-input" class="nickname-input"
v-model="newNickname" v-model="newNickname"
placeholder="请输入新昵称" :placeholder="$t('userProfile.enterNickname')"
maxlength="20" maxlength="20"
:focus="true" :focus="true"
/> />
<view class="edit-buttons"> <view class="edit-buttons">
<button class="cancel-btn" @click="cancelEditNickname">取消</button> <button class="cancel-btn" @click="cancelEditNickname">{{ $t('common.cancel') }}</button>
<button class="save-btn" @click="saveNickname">保存</button> <button class="save-btn" @click="saveNickname">{{ $t('common.save') }}</button>
</view> </view>
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="label">手机号</view> <view class="label">{{ $t('userProfile.phone') }}</view>
<view class="value"> <view class="value">
<text class="value-text">{{ userInfo.phone ? maskPhone(userInfo.phone) : '未绑定' }}</text> <text class="value-text">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('userProfile.notBound') }}</text>
</view> </view>
</view> </view>
<view class="form-item" v-if="userInfo.balanceAmount !== undefined"> <view class="form-item" v-if="userInfo.balanceAmount !== undefined">
<view class="label">余额</view> <view class="label">{{ $t('userProfile.balance') }}</view>
<view class="value"> <view class="value">
<text class="value-text amount">¥{{ userInfo.balanceAmount || '0.00' }}</text> <text class="value-text amount">¥{{ userInfo.balanceAmount || '0.00' }}</text>
</view> </view>
@@ -57,6 +57,9 @@
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive, onMounted } from 'vue';
import { getMyIndexInfo, uploadUserAvatar, updateUserInfo } from '../../config/api/user.js'; import { getMyIndexInfo, uploadUserAvatar, updateUserInfo } from '../../config/api/user.js';
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
// //
const userInfo = ref({ const userInfo = ref({
@@ -71,6 +74,9 @@ const isEditingNickname = ref(false);
// //
onMounted(() => { onMounted(() => {
uni.setNavigationBarTitle({
title: $t('userProfile.title')
})
loadUserInfo(); loadUserInfo();
}); });
@@ -96,7 +102,7 @@ const loadUserInfo = async () => {
} catch (error) { } catch (error) {
console.error('获取用户信息失败:', error); console.error('获取用户信息失败:', error);
uni.showToast({ uni.showToast({
title: '获取用户信息失败', title: $t('user.getUserInfoFailed'),
icon: 'none' icon: 'none'
}); });
} }
@@ -132,13 +138,13 @@ const onChooseAvatar = async (e) => {
const avatarLocalPath = e?.detail?.avatarUrl; const avatarLocalPath = e?.detail?.avatarUrl;
if (!avatarLocalPath) { if (!avatarLocalPath) {
uni.showToast({ uni.showToast({
title: '未选择头像', title: $t('user.noAvatar'),
icon: 'none' icon: 'none'
}); });
return; return;
} }
uni.showLoading({ uni.showLoading({
title: '上传中...', title: $t('userProfile.uploading'),
mask: true mask: true
}); });
const uploadRes = await uploadUserAvatar(avatarLocalPath); const uploadRes = await uploadUserAvatar(avatarLocalPath);
@@ -151,14 +157,14 @@ const onChooseAvatar = async (e) => {
uni.setStorageSync('userInfo', userInfo.value); uni.setStorageSync('userInfo', userInfo.value);
} }
uni.showToast({ uni.showToast({
title: '头像更新成功', title: $t('user.avatarUpdated'),
icon: 'success' icon: 'success'
}); });
await loadUserInfo(); await loadUserInfo();
} catch (err) { } catch (err) {
console.error('选择/上传头像失败:', err); console.error('选择/上传头像失败:', err);
uni.showToast({ uni.showToast({
title: '头像更新失败', title: $t('user.avatarUploadFailed'),
icon: 'none' icon: 'none'
}); });
} finally { } finally {
@@ -182,7 +188,7 @@ const cancelEditNickname = () => {
const saveNickname = async () => { const saveNickname = async () => {
if (!newNickname.value || !newNickname.value.trim()) { if (!newNickname.value || !newNickname.value.trim()) {
uni.showToast({ uni.showToast({
title: '昵称不能为空', title: $t('userProfile.nicknameRequired'),
icon: 'none' icon: 'none'
}); });
return; return;
@@ -190,7 +196,7 @@ const saveNickname = async () => {
try { try {
uni.showLoading({ uni.showLoading({
title: '保存中...', title: $t('userProfile.saving'),
mask: true mask: true
}); });
@@ -222,19 +228,19 @@ const saveNickname = async () => {
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: '昵称修改成功', title: $t('userProfile.nicknameUpdated'),
icon: 'success' icon: 'success'
}); });
isEditingNickname.value = false; isEditingNickname.value = false;
} else { } else {
throw new Error(res.message || '修改失败'); throw new Error(res.message || $t('userProfile.updateFailed'));
} }
} catch (error) { } catch (error) {
console.error('修改昵称失败:', error); console.error('修改昵称失败:', error);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: error.message || '修改失败', title: error.message || $t('userProfile.updateFailed'),
icon: 'none' icon: 'none'
}); });
} }
+11 -5
View File
@@ -1,6 +1,6 @@
<template> <template>
<view class="waiting-container"> <view class="waiting-container">
<view class="title">设备弹出中请稍候</view> <view class="title">{{ $t('waiting.title') }}</view>
<view class="progress-wrapper"> <view class="progress-wrapper">
<view class="progress-circle"> <view class="progress-circle">
<view class="progress-left"> <view class="progress-left">
@@ -11,12 +11,12 @@
</view> </view>
<view class="progress-inner"> <view class="progress-inner">
<text class="percent">{{ progress }}%</text> <text class="percent">{{ progress }}%</text>
<text class="hint">正在为您弹出设备</text> <text class="hint">{{ $t('waiting.preparing') }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="desc">若长时间未弹出请联系现场工作人员或稍后重试</view> <view class="desc">{{ $t('waiting.longTimeNotice') }}</view>
</view> </view>
</template> </template>
@@ -24,6 +24,9 @@
import { ref, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app' import { onLoad, onUnload } from '@dcloudio/uni-app'
import { getOrderByOrderNoScorePayStatus, cancelOrder } from '@/config/api/order.js' import { getOrderByOrderNoScorePayStatus, cancelOrder } from '@/config/api/order.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const progress = ref(0) const progress = ref(0)
const leftRotateDeg = ref(0) const leftRotateDeg = ref(0)
@@ -83,7 +86,7 @@
await cancelOrder({ orderId: orderNo.value }) await cancelOrder({ orderId: orderNo.value })
} }
} catch (e) {} } catch (e) {}
uni.showToast({ title: '设备租借失败,订单已取消', icon: 'none' }) uni.showToast({ title: $t('waiting.rentFailed'), icon: 'none' })
setTimeout(() => { setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' }) uni.switchTab({ url: '/pages/index/index' })
}, 800) }, 800)
@@ -111,7 +114,7 @@
// 60 // 60
timeoutTimer = setTimeout(() => { timeoutTimer = setTimeout(() => {
stopAllTimers() stopAllTimers()
uni.showToast({ title: '等待超时,请稍后重试', icon: 'none' }) uni.showToast({ title: $t('waiting.timeout'), icon: 'none' })
setTimeout(() => { setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' }) uni.switchTab({ url: '/pages/index/index' })
}, 800) }, 800)
@@ -119,6 +122,9 @@
} }
onLoad((query) => { onLoad((query) => {
uni.setNavigationBarTitle({
title: $t('waiting.title')
})
if (query && query.orderNo) { if (query && query.orderNo) {
orderNo.value = query.orderNo orderNo.value = query.orderNo
} }
+219
View File
@@ -23,6 +23,9 @@ importers:
uview-ui: uview-ui:
specifier: 1.8.8 specifier: 1.8.8
version: 1.8.8 version: 1.8.8
vue-i18n:
specifier: '9'
version: 9.14.5(vue@3.5.22)
devDependencies: devDependencies:
sass: sass:
specifier: ^1.57.1 specifier: ^1.57.1
@@ -33,9 +36,38 @@ importers:
packages: packages:
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.28.5':
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
'@climblee/uv-ui@1.1.20': '@climblee/uv-ui@1.1.20':
resolution: {integrity: sha512-jkyesHJsPJkF4Nap9ZmG1/ibKlxXA5M8+ntqKXwwloIsYSYL5SOKb0gyPj17aBOU1PkJpmeiZ8PwnTolhK2/HA==} resolution: {integrity: sha512-jkyesHJsPJkF4Nap9ZmG1/ibKlxXA5M8+ntqKXwwloIsYSYL5SOKb0gyPj17aBOU1PkJpmeiZ8PwnTolhK2/HA==}
'@intlify/core-base@9.14.5':
resolution: {integrity: sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==}
engines: {node: '>= 16'}
'@intlify/message-compiler@9.14.5':
resolution: {integrity: sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==}
engines: {node: '>= 16'}
'@intlify/shared@9.14.5':
resolution: {integrity: sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==}
engines: {node: '>= 16'}
'@jridgewell/gen-mapping@0.3.8': '@jridgewell/gen-mapping@0.3.8':
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@@ -54,6 +86,9 @@ packages:
'@jridgewell/sourcemap-codec@1.5.0': '@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.25': '@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -160,6 +195,38 @@ packages:
'@types/node@24.0.4': '@types/node@24.0.4':
resolution: {integrity: sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==} resolution: {integrity: sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==}
'@vue/compiler-core@3.5.22':
resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==}
'@vue/compiler-dom@3.5.22':
resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==}
'@vue/compiler-sfc@3.5.22':
resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==}
'@vue/compiler-ssr@3.5.22':
resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==}
'@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
'@vue/reactivity@3.5.22':
resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==}
'@vue/runtime-core@3.5.22':
resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==}
'@vue/runtime-dom@3.5.22':
resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==}
'@vue/server-renderer@3.5.22':
resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==}
peerDependencies:
vue: 3.5.22
'@vue/shared@3.5.22':
resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==}
'@webassemblyjs/ast@1.14.1': '@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@@ -279,6 +346,9 @@ packages:
commander@2.20.3: commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
debug@3.1.0: debug@3.1.0:
resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==}
peerDependencies: peerDependencies:
@@ -307,6 +377,10 @@ packages:
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
es-define-property@1.0.1: es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -346,6 +420,9 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
events@3.3.0: events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'} engines: {node: '>=0.8.x'}
@@ -443,6 +520,9 @@ packages:
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
engines: {node: '>=6.11.5'} engines: {node: '>=6.11.5'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
math-intrinsics@1.1.0: math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -465,6 +545,11 @@ packages:
ms@2.0.0: ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
neo-async@2.6.2: neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -481,6 +566,10 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
proxy-from-env@1.1.0: proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
@@ -590,6 +679,20 @@ packages:
uview-ui@1.8.8: uview-ui@1.8.8:
resolution: {integrity: sha512-Osal3yzXiHor0In9OPTZuXTaqTbDglMZ9RGK/MPYDoQQs+y0hrBCUD0Xp5T70C8i2lLu2X6Z11zJhmsQWMR7Jg==} resolution: {integrity: sha512-Osal3yzXiHor0In9OPTZuXTaqTbDglMZ9RGK/MPYDoQQs+y0hrBCUD0Xp5T70C8i2lLu2X6Z11zJhmsQWMR7Jg==}
vue-i18n@9.14.5:
resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==}
engines: {node: '>= 16'}
peerDependencies:
vue: ^3.0.0
vue@3.5.22:
resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
watchpack@2.4.4: watchpack@2.4.4:
resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@@ -610,8 +713,33 @@ packages:
snapshots: snapshots:
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
'@babel/parser@7.28.5':
dependencies:
'@babel/types': 7.28.5
'@babel/types@7.28.5':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
'@climblee/uv-ui@1.1.20': {} '@climblee/uv-ui@1.1.20': {}
'@intlify/core-base@9.14.5':
dependencies:
'@intlify/message-compiler': 9.14.5
'@intlify/shared': 9.14.5
'@intlify/message-compiler@9.14.5':
dependencies:
'@intlify/shared': 9.14.5
source-map-js: 1.2.1
'@intlify/shared@9.14.5': {}
'@jridgewell/gen-mapping@0.3.8': '@jridgewell/gen-mapping@0.3.8':
dependencies: dependencies:
'@jridgewell/set-array': 1.2.1 '@jridgewell/set-array': 1.2.1
@@ -629,6 +757,8 @@ snapshots:
'@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.25': '@jridgewell/trace-mapping@0.3.25':
dependencies: dependencies:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
@@ -713,6 +843,62 @@ snapshots:
dependencies: dependencies:
undici-types: 7.8.0 undici-types: 7.8.0
'@vue/compiler-core@3.5.22':
dependencies:
'@babel/parser': 7.28.5
'@vue/shared': 3.5.22
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.1
'@vue/compiler-dom@3.5.22':
dependencies:
'@vue/compiler-core': 3.5.22
'@vue/shared': 3.5.22
'@vue/compiler-sfc@3.5.22':
dependencies:
'@babel/parser': 7.28.5
'@vue/compiler-core': 3.5.22
'@vue/compiler-dom': 3.5.22
'@vue/compiler-ssr': 3.5.22
'@vue/shared': 3.5.22
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.6
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.22':
dependencies:
'@vue/compiler-dom': 3.5.22
'@vue/shared': 3.5.22
'@vue/devtools-api@6.6.4': {}
'@vue/reactivity@3.5.22':
dependencies:
'@vue/shared': 3.5.22
'@vue/runtime-core@3.5.22':
dependencies:
'@vue/reactivity': 3.5.22
'@vue/shared': 3.5.22
'@vue/runtime-dom@3.5.22':
dependencies:
'@vue/reactivity': 3.5.22
'@vue/runtime-core': 3.5.22
'@vue/shared': 3.5.22
csstype: 3.1.3
'@vue/server-renderer@3.5.22(vue@3.5.22)':
dependencies:
'@vue/compiler-ssr': 3.5.22
'@vue/shared': 3.5.22
vue: 3.5.22
'@vue/shared@3.5.22': {}
'@webassemblyjs/ast@1.14.1': '@webassemblyjs/ast@1.14.1':
dependencies: dependencies:
'@webassemblyjs/helper-numbers': 1.13.2 '@webassemblyjs/helper-numbers': 1.13.2
@@ -866,6 +1052,8 @@ snapshots:
commander@2.20.3: {} commander@2.20.3: {}
csstype@3.1.3: {}
debug@3.1.0: debug@3.1.0:
dependencies: dependencies:
ms: 2.0.0 ms: 2.0.0
@@ -888,6 +1076,8 @@ snapshots:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
tapable: 2.2.2 tapable: 2.2.2
entities@4.5.0: {}
es-define-property@1.0.1: {} es-define-property@1.0.1: {}
es-errors@1.3.0: {} es-errors@1.3.0: {}
@@ -920,6 +1110,8 @@ snapshots:
estraverse@5.3.0: {} estraverse@5.3.0: {}
estree-walker@2.0.2: {}
events@3.3.0: {} events@3.3.0: {}
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
@@ -1010,6 +1202,10 @@ snapshots:
loader-runner@4.3.0: {} loader-runner@4.3.0: {}
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
merge-stream@2.0.0: {} merge-stream@2.0.0: {}
@@ -1028,6 +1224,8 @@ snapshots:
ms@2.0.0: {} ms@2.0.0: {}
nanoid@3.3.11: {}
neo-async@2.6.2: {} neo-async@2.6.2: {}
node-addon-api@7.1.1: node-addon-api@7.1.1:
@@ -1040,6 +1238,12 @@ snapshots:
picomatch@2.3.1: picomatch@2.3.1:
optional: true optional: true
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
proxy-from-env@1.1.0: {} proxy-from-env@1.1.0: {}
randombytes@2.1.0: randombytes@2.1.0:
@@ -1128,6 +1332,21 @@ snapshots:
uview-ui@1.8.8: {} uview-ui@1.8.8: {}
vue-i18n@9.14.5(vue@3.5.22):
dependencies:
'@intlify/core-base': 9.14.5
'@intlify/shared': 9.14.5
'@vue/devtools-api': 6.6.4
vue: 3.5.22
vue@3.5.22:
dependencies:
'@vue/compiler-dom': 3.5.22
'@vue/compiler-sfc': 3.5.22
'@vue/runtime-dom': 3.5.22
'@vue/server-renderer': 3.5.22(vue@3.5.22)
'@vue/shared': 3.5.22
watchpack@2.4.4: watchpack@2.4.4:
dependencies: dependencies:
glob-to-regexp: 0.4.1 glob-to-regexp: 0.4.1
+33
View File
@@ -0,0 +1,33 @@
// i18n工具函数 - 用于在 Vue 3 setup 中安全获取 $t
import { getCurrentInstance } from 'vue'
/**
* setup 中使用 i18n
* @returns {{ t: Function, locale: string, i18n: object }}
*/
export function useI18n() {
const instance = getCurrentInstance()
if (!instance || !instance.proxy) {
return {
t: (key) => key,
locale: 'zh-CN',
i18n: null
}
}
const proxy = instance.proxy
// 返回一个函数,每次调用时动态获取 $t(确保 $t 已经注入)
return {
t: (key, ...args) => {
if (proxy.$t && typeof proxy.$t === 'function') {
return proxy.$t(key, ...args)
}
return key
},
locale: proxy.$i18n?.locale || 'zh-CN',
i18n: proxy.$i18n
}
}