fix:修复bug

This commit is contained in:
2026-01-22 10:52:58 +08:00
parent b0daa7b59b
commit 6a1dff4b94
46 changed files with 3779 additions and 2522 deletions
+1 -1
View File
@@ -40,7 +40,7 @@
import { computed } from 'vue'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
const props = defineProps({
show: { type: Boolean, default: false },
+15 -5
View File
@@ -1,5 +1,5 @@
<template>
<view class="map-container" :class="{ 'full-width': props.fullWidth }" :style="{ '--map-height': props.customHeight || '78vh' }">
<view class="map-container" :class="{ 'full-width': props.fullWidth }" :style="{ '--map-height': props.customHeight || '72vh' }">
<!-- 地图容器 -->
<view class="map-wrapper">
<!-- 使用小程序原生地图组件 -->
@@ -68,7 +68,7 @@
import { useI18n } from '../utils/i18n.js'
// 获取 i18n 实例
const { t: $t } = useI18n()
const { t } = useI18n()
// 引用折叠面板组件的ref
const collapseRef = ref(null)
@@ -272,13 +272,19 @@
const onMapRegionChange = (e) => {
// 只处理结束事件
if (!e || e.type !== 'end') {
if (!e || (e.type !== 'end' && e.type !== 'regionchange')) {
return
}
const causedBy = e.causedBy || e.detail?.causedBy
if (causedBy === 'gesture' || causedBy === 'scale' || causedBy === 'drag'||causedBy==='update') {
// H5 环境下可能没有 causedBy,只要是 end 事件就处理
const isH5 = false;
// #ifdef H5
const h5Status = true;
// #endif
if (causedBy === 'gesture' || causedBy === 'scale' || causedBy === 'drag' || causedBy === 'update' || (typeof h5Status !== 'undefined' && e.type === 'end')) {
// 清除之前的定时器
if (regionChangeTimer) {
clearTimeout(regionChangeTimer)
@@ -481,22 +487,26 @@ const handleSearch = () => {
/* 地图容器 */
.map-container {
flex: 1;
position: relative;
// position: fixed;
// top: 0;
left: 0;
right: 0;
bottom: 0;
width: 94vw;
height: calc(100% - 20rpx); /* 减少高度,避免覆盖底部按钮 */
height: var(--map-height, calc(100% - 20rpx)); /* 使用变量或默认高度 */
margin: 20rpx;
margin-bottom: 0; /* 底部不需要边距 */
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
&.full-width {
width: 100%;
margin: 0;
border-radius: 0;
height: 100%;
}
.map-wrapper {
+4 -4
View File
@@ -98,7 +98,7 @@
import { computed } from 'vue';
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
const props = defineProps({
order: { type: Object, required: true },
@@ -144,7 +144,7 @@
const isFinished = computed(() => normalizedStatus.value === 'used_done');
const isCancelled = computed(() => normalizedStatus.value === 'order_cancelled');
const titleText = computed(() => $t('order.rentFan'));
const titleText = computed(() => t('order.rentFan'));
// 显示金额(优先后端给定字段)
const displayAmount = computed(() => props.order.amount || props.order.payAmount || props.order.actualDeviceAmount || props.order.currentFee || '0.00');
@@ -158,8 +158,8 @@
const minutes = Math.floor(diffMs / 60000);
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
if (hours > 0) return `${hours}${$t('time.hour')}${mins}${$t('time.minute')}`;
return `${mins}${$t('time.minute')}`;
if (hours > 0) return `${hours}${t('time.hour')}${mins}${t('time.minute')}`;
return `${mins}${t('time.minute')}`;
});
function parseDate(str) {
+12
View File
@@ -79,3 +79,15 @@ export const forcefOpenEmptyGrid = (deviceNo) => {
})
}
// 发送租借指令
export const sendRentCommand = (data) => {
return request({
url: '/app/device/sendRentCommand',
method: 'post',
data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
}
+32
View File
@@ -104,6 +104,38 @@ export const closeWithMaxFee = (orderNo) => {
})
}
// 创建微信支付订单
export const createWxPayment = (orderNo) => {
return request({
url: `/app/wx-payment/create/${orderNo}`,
method: 'get'
})
}
// 获取正在使用中的订单
export const getInUseOrder = () => {
return request({
url: '/app/order/inUse',
method: 'get'
})
}
// 获取待支付订单
export const getUnpaidOrder = () => {
return request({
url: '/app/order/unpaid',
method: 'get'
})
}
// 查询微信支付状态
export const getWxPaymentStatus = (orderNo) => {
return request({
url: `/app/wx-payment/status/${orderNo}`,
method: 'get'
})
}
// 通过订单号获取支付分订单信息
export const getOrderByOrderNoScore = (orderNo) => {
console.log('通过订单号获取支付分订单信息', orderNo);
+33 -7
View File
@@ -27,12 +27,38 @@ export const getCommonByBrand = (brandName) => {
})
}
// 查询激活的且临近关闭时间最近的活动
export const getActiveActivity = () => {
return request({
url: '/device/activity/agent/list',
method: 'get',
hideLoading: true
})
// 获取当前协议内容
export const getCurrentAgreement = (data) => {
return request({
url: '/device/agreementConfig/current',
method: 'get',
data
})
}
// 获取当前广告内容
export const getCurrentAdvertisement = (data) => {
return request({
url: '/device/advertisementConfig/current',
method: 'get',
data
})
}
// 获取当前公告内容
export const getCurrentAnnouncement = (data) => {
return request({
url: '/device/announcementConfig/current',
method: 'get',
data
})
}
export const getActiveActivity = (data) => {
return request({
url: '/device/activeActivity/current',
method: 'get',
data
})
}
+31 -2
View File
@@ -10,6 +10,24 @@ export const login = (data) => {
})
}
// 发送验证码
export const sendVerifyCode = (phonenumber) => {
return request({
url: '/app/user/sms/code',
method: 'get',
data: { phonenumber }
})
}
// 手机号+验证码登录
export const loginWithCode = (phonenumber, smsCode) => {
return request({
url: '/app/user/sms/login',
method: 'post',
data: { phonenumber, smsCode }
})
}
// 用户退出登录
export const userLogout = (data) => {
return request({
@@ -56,7 +74,8 @@ export const uploadUserAvatar = (filePath) => {
header: {
'appid': appid,
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
'Clientid': uni.getStorageSync('client_id'),
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
},
success: (res) => {
try {
@@ -83,7 +102,8 @@ export const uploadOssResource = (filePath) => {
header: {
'appid': appid,
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
'Clientid': uni.getStorageSync('client_id'),
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
},
success: (res) => {
try {
@@ -115,3 +135,12 @@ export const withdrawDeposit = (orderNo) => {
})
}
// 获取微信用户手机号
export const getWxUserPhoneNumber = (data) => {
return request({
url: '/app/user/getPhoneNumber',
method: 'post',
data
})
}
+2 -1
View File
@@ -33,7 +33,8 @@ const request = (option) => {
...option.headers,
'appid': appid,
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
'Clientid': uni.getStorageSync('client_id'),
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
},
success(res) {
+1 -1
View File
@@ -1,7 +1,7 @@
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
// export const URL = "https://manager.fdzpower.com/api" //正式服务器
export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
// export const URL = "http://192.168.5.52:8080" //本地调试
// export const URL = "http://192.168.5.123:8080" //本地调试
// export const URL = "http://127.0.0.1:8080" //本地调试
export const appid = "wx2165f0be356ae7a9" //微信小程序appid
+55 -7
View File
@@ -49,6 +49,8 @@ export default {
loginRequired: 'Please login first',
operationSuccess: 'Operation successful',
operationFailed: 'Operation failed',
sending: 'Sending...',
loggingIn: 'Logging in...',
refresh: 'Refresh',
pull: 'Pull to refresh',
release: 'Release to refresh',
@@ -114,7 +116,8 @@ export default {
businessHours: 'Business Hours: ',
navigateHere: 'Navigate Here',
coordinateError: 'Invalid location coordinates',
notExist: 'Location does not exist'
notExist: 'Location does not exist',
supportCouponOrMember: 'Coupons & Cards Available'
},
device: {
@@ -172,6 +175,8 @@ export default {
deposit: 'Deposit',
rentFee: 'Rent Fee',
payNow: 'Pay Now',
myCoupons:'Coupons',
myCards:'Member Cards',
cancelOrder: 'Cancel Order',
quickReturn: 'Quick Return',
returnDevice: 'Return Device',
@@ -246,6 +251,8 @@ export default {
perHalfHour: 'per half hour',
deviceNoEject: 'Not Ejected',
returnReminder: 'Return Reminder',
canUsePromotion: 'Coupons & Cards Available',
usedPromotion: 'Promotion Applied',
convertToOwn: 'Don\'t want to return? Convert to own',
convertToOwnTitle: 'Convert to Own',
convertToOwnConfirm: 'Only ¥99 to convert to own. The power bank will be yours. Confirm?',
@@ -261,7 +268,9 @@ export default {
deviceNoEjectTitle: 'Device Not Ejected',
deviceNoEjectConfirm: 'Your power bank didn\'t eject? We will handle it immediately, expected to resolve within 5 minutes.',
deviceNoEjectSuccess: 'Feedback received, will be handled within 5 minutes',
deviceNoEjectFailed: 'Feedback submission failed, please try again'
deviceNoEjectFailed: 'Feedback submission failed, please try again',
returnProblemTip: 'After returning, if the order is still active, please go to ',
contactStaff: ' to contact staff.'
},
user: {
@@ -327,7 +336,22 @@ export default {
phoneSuccess: 'Success',
phoneError: 'Error',
phoneGetFailed: 'Failed',
authCodeFailed: 'Auth failed'
authCodeFailed: 'Auth failed',
phoneLogin: 'Phone Login',
phonePlaceholder: 'Enter phone number',
codePlaceholder: 'Enter verification code',
getCode: 'Get Code',
resend: 'Resend',
loginBtn: 'Login',
phoneRequired: 'Phone required',
phoneInvalid: 'Invalid phone number',
codeRequired: 'Verification code required',
codeSent: 'Code sent',
sendCodeFailed: 'Send code failed',
regionNotSupported: 'Non-mainland China, Hong Kong, Macau users please login via platform phone authorization',
onlyMainlandSupported: 'Currently only Mainland China is supported',
getServicePhoneFailed: 'Failed to get service phone',
noAuthToken: 'Login successful but no credentials obtained'
},
permission: {
@@ -361,7 +385,9 @@ export default {
package: 'Package',
total: 'Total',
paymentFailedRetry: 'Payment failed, retry?',
createPayOrderFailed: 'Failed'
createPayOrderFailed: 'Failed',
subscriptionSuccess: 'Subscription successful',
subscriptionFailed: 'Subscription failed, please try again'
},
feedback: {
@@ -421,6 +447,7 @@ export default {
phone: 'Phone',
email: 'Email',
workingHours: 'Working Hours',
workingHoursValue: 'Mon-Sun 09:00-22:00',
functionDeveloping: 'Feature in development',
faq1Question: 'How to rent a fan?',
faq1Answer: 'Click "Scan to Rent" on the homepage, scan the QR code on the device with WeChat, and complete payment as prompted.',
@@ -440,6 +467,7 @@ export default {
languageSetting: 'Language Setting',
chinese: '简体中文',
english: 'English',
languageSwitched: 'Language switched, refreshing...',
notification: 'Notification',
privacy: 'Privacy',
about: 'About',
@@ -664,6 +692,8 @@ export default {
withdrawNotice2: 'Withdrawal expected to arrive within 0-7 business days',
withdrawNotice3: 'If delayed, please contact customer service',
depositRecord: 'Deposit Record',
payRecord: 'Payment Record',
refundRecord: 'Refund Record',
orderNotReturned: 'Current order not returned, please return before withdraw',
alreadyRefunded: 'Deposit already refunded',
refundProcessing: 'Refund processing, please wait'
@@ -719,7 +749,7 @@ export default {
type: 'Type',
timesCard: 'Times Card',
durationCard: 'Duration Card',
remainingTimes: 'Remaining Times',
remainingTimes: 'Remaining: ',
remainingDuration: 'Remaining Duration',
hours: 'Hours',
validPeriod: 'Valid Period',
@@ -730,7 +760,23 @@ export default {
price: 'Purchase Price',
noCards: 'No cards',
buyNow: 'Buy Now',
getListFailed: 'Failed to get card list'
getListFailed: 'Failed to get card list',
dailyLimit: 'Daily Limit',
singleTimeLimit: 'Single Use Limit',
unlimited: 'Unlimited',
times: 'Times',
minutes: 'Minutes',
validWithinDays: 'days valid',
validFromPurchase: 'Valid from purchase',
daysValid: 'days',
currentCycleUsed: 'Current Cycle Used',
totalCount: 'Total Count',
expire: 'Expire',
expiredOn: 'Expired on ',
renew: 'Renew',
toUse: 'Use Now',
onlyForRegionBefore: 'Only for ',
onlyForRegionAfter: ''
},
myCoupon: {
@@ -744,7 +790,9 @@ export default {
noUsedCoupons: 'No used coupons',
noExpiredCoupons: 'No expired coupons',
buyNow: 'Buy Now',
getListFailed: 'Failed to get coupon list'
getListFailed: 'Failed to get coupon list',
onlyForRegionBefore: 'Only for ',
onlyForRegionAfter: ''
}
}
+57 -9
View File
@@ -49,6 +49,8 @@ export default {
loginRequired: '请先登录',
operationSuccess: '操作成功',
operationFailed: '操作失败',
sending: '发送中...',
loggingIn: '登录中...',
refresh: '刷新',
pull: '下拉刷新',
release: '释放刷新',
@@ -114,7 +116,8 @@ export default {
businessHours: '营业时间:',
navigateHere: '导航去这',
coordinateError: '该场地坐标信息异常',
notExist: '场地不存在'
notExist: '场地不存在',
supportCouponOrMember: '可使用优惠券、会员卡'
},
device: {
@@ -171,6 +174,8 @@ export default {
payAmount: '支付金额',
deposit: '押金',
rentFee: '租金',
myCards:'会员卡优惠',
myCoupons:'优惠券优惠',
payNow: '立即支付',
cancelOrder: '取消订单',
quickReturn: '快速归还',
@@ -246,6 +251,8 @@ export default {
perHalfHour: '每半小时',
deviceNoEject: '宝未弹出',
returnReminder: '归还提醒',
canUsePromotion: '可使用优惠券、会员卡',
usedPromotion: '已享受优惠',
convertToOwn: '不想还了?点击转为自用',
convertToOwnTitle: '转为自用',
convertToOwnConfirm: '仅需花费99元,即可转为自用,充电宝将归您所有,确认操作吗?',
@@ -261,7 +268,9 @@ export default {
deviceNoEjectTitle: '充电宝未弹出',
deviceNoEjectConfirm: '您的充电宝未弹出吗?我们将立即为您处理,预计5分钟内解决问题。',
deviceNoEjectSuccess: '反馈已受理,将在5分钟内处理',
deviceNoEjectFailed: '反馈提交失败,请稍后重试'
deviceNoEjectFailed: '反馈提交失败,请稍后重试',
returnProblemTip: '产品归还入仓后,订单仍未结束,请前往',
contactStaff: '联系工作人员。'
},
user: {
@@ -327,7 +336,22 @@ export default {
phoneSuccess: '手机号获取成功',
phoneError: '获取手机号异常',
phoneGetFailed: '获取手机号失败',
authCodeFailed: '获取授权码失败'
authCodeFailed: '获取授权码失败',
phoneLogin: '手机号登录',
phonePlaceholder: '请输入手机号码',
codePlaceholder: '请输入验证码',
getCode: '获取验证码',
resend: '重新发送',
loginBtn: '立即登录',
phoneRequired: '请输入手机号',
phoneInvalid: '请输入正确的手机号',
codeRequired: '请输入验证码',
codeSent: '验证码已发送',
sendCodeFailed: '发送验证码失败',
regionNotSupported: '非大陆、香港、澳门用户请通过平台手机号授权登录',
onlyMainlandSupported: '当前仅支持中国大陆地区',
getServicePhoneFailed: '获取客服电话失败',
noAuthToken: '登录成功但未获取到授权凭证'
},
permission: {
@@ -361,7 +385,9 @@ export default {
package: '套餐',
total: '合计',
paymentFailedRetry: '支付失败,请重试',
createPayOrderFailed: '创建支付订单失败'
createPayOrderFailed: '创建支付订单失败',
subscriptionSuccess: '订阅成功',
subscriptionFailed: '订阅失败,请稍后重试'
},
feedback: {
@@ -421,6 +447,7 @@ export default {
phone: '电话',
email: '邮箱',
workingHours: '工作时间',
workingHoursValue: '周一至周日 09:00-22:00',
functionDeveloping: '功能开发中',
faq1Question: '如何租借风扇?',
faq1Answer: '点击首页"扫码租借"按钮,使用微信扫描设备上的二维码,按提示完成支付即可使用。',
@@ -440,6 +467,7 @@ export default {
languageSetting: '语言设置',
chinese: '简体中文',
english: 'English',
languageSwitched: '语言已切换,正在刷新...',
notification: '通知',
privacy: '隐私',
about: '关于',
@@ -664,6 +692,8 @@ export default {
withdrawNotice2: '提现申请提交后预计0-7个工作日到账',
withdrawNotice3: '如超时未收到,请联系客服处理',
depositRecord: '押金记录',
payRecord: '支付记录',
refundRecord: '退还记录',
orderNotReturned: '当前订单尚未归还,请归还后再提现',
alreadyRefunded: '押金已退还,无需重复提现',
refundProcessing: '押金退还处理中,请耐心等待'
@@ -719,32 +749,50 @@ export default {
type: '类型',
timesCard: '次卡',
durationCard: '时长卡',
remainingTimes: '剩余次数',
remainingTimes: '剩余次数',
remainingDuration: '剩余时长',
hours: '小时',
validPeriod: '有效期',
active: '使用中',
expired: '已过期',
expired: '已失效',
used: '已用完',
position: '使用地点',
price: '购买价格',
noCards: '暂无会员卡',
buyNow: '立即购买',
getListFailed: '获取会员卡列表失败'
getListFailed: '获取会员卡列表失败',
dailyLimit: '每日限用',
singleTimeLimit: '单次限时',
unlimited: '不限',
times: '次',
minutes: '分钟',
validWithinDays: '天内有效',
validFromPurchase: '从购买时间起',
daysValid: '天有效',
currentCycleUsed: '本周期已使用',
totalCount: '总次数',
expire: '到期',
expiredOn: '失效于',
renew: '续卡',
toUse: '去使用',
onlyForRegionBefore: '仅限',
onlyForRegionAfter: '使用'
},
myCoupon: {
available: '可使用',
used: '已使用',
expired: '已过期',
useNow: '立即使用',
useNow: '使用',
usedStatus: '已使用',
expiredStatus: '已过期',
noAvailableCoupons: '暂无可用优惠券',
noUsedCoupons: '暂无已使用优惠券',
noExpiredCoupons: '暂无已过期优惠券',
buyNow: '立即购买',
getListFailed: '获取优惠券列表失败'
getListFailed: '获取优惠券列表失败',
onlyForRegionBefore: '仅限',
onlyForRegionAfter: '使用'
}
}
+13
View File
@@ -81,6 +81,19 @@
"mp-toutiao" : {
"usingComponents" : true
},
"h5" : {
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : "DJQBZ-WB53Q-WPS5B-4S6J7-53RMS-X4FJ2"
}
}
},
"router" : {
"mode" : "hash",
"base" : "./"
}
},
"uniStatistics" : {
"enable" : false
},
+9 -1
View File
@@ -19,7 +19,15 @@
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarBackgroundColor": "#C8F4D9",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/login/phone",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#C8F4D9",
"navigationBarTextStyle": "black"
}
},
+132 -150
View File
@@ -26,11 +26,11 @@
<view class="record-list">
<view class="record-item" v-for="(item, index) in records" :key="index">
<view class="record-info">
<text class="record-type">{{ item.type }}</text>
<text class="record-type">{{ item.typeText }}</text>
<text class="record-time">{{ item.time }}</text>
</view>
<text class="record-amount" :class="item.type === '退还' ? 'refund' : ''">
{{ item.type === '退还' ? '+' : '-' }}¥{{ item.amount }}
<text class="record-amount" :class="item.type === 'refund' ? 'refund' : ''">
{{ item.type === 'refund' ? '+' : '-' }}¥{{ item.amount }}
</text>
</view>
</view>
@@ -38,163 +38,145 @@
</view>
</template>
<script>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getUserInfo } from '../../util/index.js'
import { withdrawDeposit } from '../../config/api/user.js'
import { queryById } from '../../config/api/order.js'
import { useI18n } from '@/utils/i18n.js'
export default {
data() {
return {
depositAmount: '0.00',
orderNo: '',
records: [],
orderId:''
}
},
onLoad() {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('deposit.title')
})
// this.loadUserInfo()
},
onShow() {
this.loadUserInfo()
},
methods: {
async loadUserInfo() {
try {
const res = await getUserInfo()
console.log('loadUserInfo',res);
if (res.code === 200) {
this.depositAmount = res.data.balanceAmount || '0.00'
this.orderNo = res.data.latestOrderNo || ''
this.orderId = res.data.latestOrderId||''
// 如果存在余额,获取押金记录
if (parseFloat(this.depositAmount) > 0 && this.orderNo) {
this.records = [
{
type: '支付',
time: this.formatDate(new Date()),
amount: this.depositAmount
}
]
} else {
this.records = []
}
}
} catch (error) {
console.error('获取用户信息失败:', error)
uni.showToast({
title: this.$t('user.getUserInfoFailed'),
icon: 'none'
})
}
},
async handleWithdraw() {
if (parseFloat(this.depositAmount) <= 0) {
uni.showToast({
title: this.$t('deposit.noBalance'),
icon: 'none'
})
return
}
if(this.orderId.length!=0||this.orderNo.length!=0){
const res = await queryById(Number(this.orderId))
console.log(res);
}
// if(this.orderNo.length!=0){
// uni.showToast({
// title:'当前存在进行中的订单',
// icon:'none'
// })
// return
// }
const { t } = useI18n()
const depositAmount = ref('0.00')
const orderNo = ref('')
const orderId = ref('')
const records = ref([])
onMounted(() => {
uni.setNavigationBarTitle({
title: t('deposit.title')
})
})
onShow(() => {
loadUserInfo()
})
const loadUserInfo = async () => {
try {
const res = await getUserInfo()
if (res.code === 200) {
depositAmount.value = res.data.balanceAmount || '0.00'
orderNo.value = res.data.latestOrderNo || ''
orderId.value = res.data.latestOrderId || ''
uni.showModal({
title: this.$t('deposit.confirmWithdraw'),
content: this.$t('deposit.withdrawDesc'),
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: this.$t('deposit.withdrawing')
// 如果存在余额,获取押金记录
if (parseFloat(depositAmount.value) > 0 && orderNo.value) {
records.value = [
{
type: 'pay',
typeText: t('deposit.payRecord'),
time: formatDate(new Date()),
amount: depositAmount.value
}
]
} else {
records.value = []
}
}
} catch (error) {
uni.showToast({
title: t('user.getUserInfoFailed'),
icon: 'none'
})
}
}
const handleWithdraw = async () => {
if (parseFloat(depositAmount.value) <= 0) {
uni.showToast({
title: t('deposit.noBalance'),
icon: 'none'
})
return
}
uni.showModal({
title: t('deposit.confirmWithdraw'),
content: t('deposit.withdrawDesc'),
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: t('deposit.withdrawing')
})
try {
const result = await withdrawDeposit(orderNo.value)
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: t('deposit.withdrawSubmitted'),
icon: 'success'
})
try {
console.log('发起提现请求,订单号:', this.orderNo)
const result = await withdrawDeposit(this.orderNo)
console.log('提现响应:', result)
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: this.$t('deposit.withdrawSubmitted'),
icon: 'success'
})
// 更新余额为0
this.depositAmount = '0.00'
this.records.push({
type: '退还',
time: this.formatDate(new Date()),
amount: this.depositAmount
})
// 重新加载用户信息
setTimeout(() => {
this.loadUserInfo()
}, 1500)
} else {
throw new Error(result.msg || this.$t('deposit.withdrawFailed'))
}
} catch (error) {
console.error('提现失败:', error)
uni.hideLoading()
// 更详细的错误处理
let errorMessage = this.$t('deposit.withdrawFailed');
// 如果有具体错误信息,使用它
if (error.message) {
// 常见错误消息处理
if (error.message.includes('尚未归还')) {
errorMessage = this.$t('deposit.orderNotReturned');
} else if (error.message.includes('已退还')) {
errorMessage = this.$t('deposit.alreadyRefunded');
} else if (error.message.includes('处理中')) {
errorMessage = this.$t('deposit.refundProcessing');
} else if (error.message.includes('余额为0')) {
errorMessage = this.$t('deposit.noBalance');
} else {
// 使用后端返回的具体错误消息
errorMessage = error.message;
}
}
// 显示错误提示
uni.showModal({
title: this.$t('deposit.withdrawFailed'),
content: errorMessage,
showCancel: false
})
// 更新记录
records.value.push({
type: 'refund',
typeText: t('deposit.refundRecord'),
time: formatDate(new Date()),
amount: depositAmount.value
})
// 更新余额为0
depositAmount.value = '0.00'
// 重新加载用户信息
setTimeout(() => {
loadUserInfo()
}, 1500)
} else {
throw new Error(result.msg || t('deposit.withdrawFailed'))
}
} catch (error) {
uni.hideLoading()
// 更详细的错误处理
let errorMessage = t('deposit.withdrawFailed');
if (error.message) {
if (error.message.includes('尚未归还')) {
errorMessage = t('deposit.orderNotReturned');
} else if (error.message.includes('已退还')) {
errorMessage = t('deposit.alreadyRefunded');
} else if (error.message.includes('处理中')) {
errorMessage = t('deposit.refundProcessing');
} else if (error.message.includes('余额为0')) {
errorMessage = t('deposit.noBalance');
} else {
errorMessage = error.message;
}
}
uni.showModal({
title: t('deposit.withdrawFailed'),
content: errorMessage,
showCancel: false
})
}
})
},
formatDate(date) {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
}
}
})
}
const formatDate = (date) => {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}`
}
</script>
@@ -338,4 +320,4 @@ export default {
}
}
}
</style>
</style>
+50 -46
View File
@@ -131,7 +131,9 @@
import {
getOrderByOrderNoScore,
getOrderByOrderNo,
cancelOrder
cancelOrder,
getInUseOrder,
getUnpaidOrder
} from '@/config/api/order.js'
import {
initiateWeChatScorePayment,
@@ -143,7 +145,7 @@
} from '@/utils/i18n.js'
const {
t: $t
t
} = useI18n()
// 响应式状态
@@ -154,7 +156,7 @@
const deviceLocation = ref('一号教学楼大厅')
const hasActiveOrder = ref(false)
const deviceStatus = reactive({
text: $t('device.available'),
text: t('device.available'),
class: 'available'
})
const isLoggedIn = ref(true)
@@ -175,7 +177,7 @@
onMounted(async () => {
uni.setNavigationBarTitle({
title: $t('device.deviceInfo')
title: t('device.deviceInfo')
})
await checkUserPhone()
await fetchDeviceInfo()
@@ -219,7 +221,7 @@
// 用户拒绝授权的情况
if (e.detail.errMsg && e.detail.errMsg.includes('deny')) {
uni.showToast({
title: $t('auth.phoneRequired'),
title: t('auth.phoneRequired'),
icon: 'none'
})
return
@@ -228,7 +230,7 @@
// 获取到授权code
if (e.detail.code) {
uni.showLoading({
title: $t('auth.getting')
title: t('auth.getting')
})
console.log('获取到的授权code:', e.detail.code)
@@ -260,15 +262,15 @@
showPhoneAuthPopup.value = false
uni.showToast({
title: $t('auth.phoneSuccess'),
title: t('auth.phoneSuccess'),
icon: 'success'
})
} else {
// 记录详细信息,不抛出错误
console.warn('获取手机号响应异常:', res.msg || '未知错误')
uni.showModal({
title: $t('auth.phoneError'),
content: `${$t('common.statusCode')}: ${res.code}, ${$t('common.message')}: ${res.msg || $t('common.none')}`,
title: t('auth.phoneError'),
content: `${t('common.statusCode')}: ${res.code}, ${t('common.message')}: ${res.msg || t('common.none')}`,
showCancel: false
})
}
@@ -280,8 +282,8 @@
// 显示更详细的错误信息
let errMsg = err.message || err.toString()
uni.showModal({
title: $t('auth.phoneGetFailed'),
content: $t('common.errorInfo') + ': ' + errMsg,
title: t('auth.phoneGetFailed'),
content: t('common.errorInfo') + ': ' + errMsg,
showCancel: false
})
})
@@ -289,14 +291,14 @@
uni.hideLoading()
console.error('获取手机号外部错误:', outerError)
uni.showModal({
title: $t('common.unexpectedError'),
content: $t('common.processException') + ': ' + (outerError.message || outerError),
title: t('common.unexpectedError'),
content: t('common.processException') + ': ' + (outerError.message || outerError),
showCancel: false
})
}
} else {
uni.showToast({
title: $t('auth.authCodeFailed'),
title: t('auth.authCodeFailed'),
icon: 'none'
})
}
@@ -325,10 +327,10 @@
// 更新设备状态
if (deviceInfo.value.status) {
if (deviceInfo.value.status === 'online') {
deviceStatus.text = $t('device.available')
deviceStatus.text = t('device.available')
deviceStatus.class = 'available'
} else if (deviceInfo.value.status === 'offline') {
deviceStatus.text = $t('device.offline')
deviceStatus.text = t('device.offline')
deviceStatus.class = 'offline'
}
}
@@ -349,9 +351,9 @@
// 显示登录提示
const showLoginTip = () => {
uni.showModal({
title: $t('common.tips'),
content: $t('common.loginRequired'),
confirmText: $t('auth.goToLogin'),
title: t('common.tips'),
content: t('common.loginRequired'),
confirmText: t('auth.goToLogin'),
success: (res) => {
if (res.confirm) {
uni.navigateTo({
@@ -374,27 +376,29 @@
const checkOrderStatus = async () => {
try {
// 调用接口检查是否有进行中的订单
const result = await uni.$api.checkActiveOrder()
const inUseRes = await getInUseOrder()
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
const order = inUseRes.data
// 如果有正在进行的订单,跳转到归还页面,带上设备ID
uni.redirectTo({
url: `/pages/device/return?deviceId=${deviceId.value}`
})
return
}
if (result.hasOrder) {
const order = result.order // 假设后端返回 order 对象
// 检查订单状态
if (order.status === 'waiting_for_payment') {
// 跳转支付页面,带上订单ID
uni.redirectTo({
url: `/pages/order/payment?orderId=${order.orderId}&deviceId=${deviceId.value}`
})
} else if (order.status === 'in_used') {
// 如果有正在进行的订单,跳转到归还页面,带上设备ID
uni.redirectTo({
url: `/pages/device/return?deviceId=${deviceId.value}`
})
}
// 检查是否有待支付的订单
const unpaidRes = await getUnpaidOrder()
if (unpaidRes && unpaidRes.code === 200 && unpaidRes.data) {
const order = unpaidRes.data
// 跳转支付页面,带上订单ID
uni.redirectTo({
url: `/pages/order/payment?orderId=${order.orderId}&deviceId=${deviceId.value}`
})
}
} catch (error) {
console.error('检查订单状态失败:', error)
uni.showToast({
title: $t('order.getOrderStatusFailed'),
title: t('order.getOrderStatusFailed'),
icon: 'none'
})
}
@@ -427,7 +431,7 @@
return '30分钟'
}
// 按小时计费(默认)
return $t('time.hour')
return t('time.hour')
}
// 计算计费单位时间(分钟)
@@ -499,7 +503,7 @@
const submitRentOrder = async (payWay) => {
try {
uni.showLoading({
title: $t('common.processing')
title: t('common.processing')
})
// --- 第一步:先请求订阅消息(必须在用户点击的同步上下文中)---
if (payWay === 'wx-score-pay') {
@@ -532,7 +536,7 @@
// 调用设备租借接口
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value)
if (rentResult.code !== 200) {
throw new Error(rentResult.msg || $t('device.rentFailed'))
throw new Error(rentResult.msg || t('device.rentFailed'))
}
// 获取后端返回的订单信息
@@ -587,7 +591,7 @@
// 用户取消授权,需要取消订单
try {
uni.showLoading({
title: $t('order.cancelling')
title: t('order.cancelling')
});
const cancelRes = await cancelOrder({
orderId: order.orderNo
@@ -596,7 +600,7 @@
uni.hideLoading();
uni.showToast({
title: $t('order.orderCancelled'),
title: t('order.orderCancelled'),
icon: 'none',
duration: 2000
});
@@ -611,7 +615,7 @@
console.error('取消订单失败:', cancelError);
uni.hideLoading();
uni.showToast({
title: $t('order.cancelFailedContactService'),
title: t('order.cancelFailedContactService'),
icon: 'none'
});
}
@@ -622,7 +626,7 @@
// 支付分调用异常,也需要取消订单
try {
uni.showLoading({
title: $t('order.cancelling')
title: t('order.cancelling')
});
const cancelRes = await cancelOrder({
orderId: order.orderNo
@@ -635,7 +639,7 @@
}
uni.showToast({
title: $t('device.payScoreFailedCancelled'),
title: t('device.payScoreFailedCancelled'),
icon: 'none'
});
@@ -647,7 +651,7 @@
}
} else {
uni.showToast({
title: res?.msg || $t('device.getPayParamsFailed'),
title: res?.msg || t('device.getPayParamsFailed'),
icon: 'none'
});
}
@@ -655,7 +659,7 @@
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || $t('device.rentFailedRetry'),
title: error.message || t('device.rentFailedRetry'),
icon: 'none'
})
}
+20 -20
View File
@@ -62,11 +62,11 @@
} from '@/config/api/expressReturn.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('express.fillExpress')
title: t('express.fillExpress')
})
})
@@ -96,7 +96,7 @@
if (!orderId.value) {
uni.showToast({
title: $t('express.orderNoMissing'),
title: t('express.orderNoMissing'),
icon: 'none'
})
setTimeout(() => {
@@ -111,7 +111,7 @@
const loadOrder = async () => {
try {
uni.showLoading({
title: $t('common.loading')
title: t('common.loading')
})
const res = await queryById(orderId.value)
if (res?.code === 200 && res.data) {
@@ -121,11 +121,11 @@
// 默认联系电话可回填订单上的手机号(若有)
if (res.data.phone && !phone.value) phone.value = res.data.phone
} else {
throw new Error(res?.msg || $t('order.getOrderFailed'))
throw new Error(res?.msg || t('order.getOrderFailed'))
}
} catch (e) {
uni.showToast({
title: e.message || $t('express.loadFailed'),
title: e.message || t('express.loadFailed'),
icon: 'none'
})
} finally {
@@ -136,7 +136,7 @@
const loadRecordAndOrderByRecord = async () => {
try {
uni.showLoading({
title: $t('common.loading')
title: t('common.loading')
})
const res = await getExpressReturnDetail(recordId.value)
if (res?.code === 200 && res.data) {
@@ -146,11 +146,11 @@
}
if (res.data.userPhone && !phone.value) phone.value = res.data.userPhone
} else {
throw new Error(res?.msg || $t('express.getRecordFailed'))
throw new Error(res?.msg || t('express.getRecordFailed'))
}
} catch (e) {
uni.showToast({
title: e.message || $t('express.loadFailed'),
title: e.message || t('express.loadFailed'),
icon: 'none'
})
} finally {
@@ -166,10 +166,10 @@
if (rec.status === 0) {
recordId.value = rec.id
uni.showModal({
title: $t('common.tips'),
content: $t('express.existingReturnNotice'),
confirmText: $t('express.goToFill'),
cancelText: $t('common.cancel'),
title: t('common.tips'),
content: t('express.existingReturnNotice'),
confirmText: t('express.goToFill'),
cancelText: t('common.cancel'),
success: (r) => {
if (r.confirm) {
uni.redirectTo({
@@ -181,7 +181,7 @@
return
} else {
uni.showToast({
title: $t('express.alreadyHasRecord'),
title: t('express.alreadyHasRecord'),
icon: 'none'
})
setTimeout(() => {
@@ -201,14 +201,14 @@
const digits = (phone.value || '').replace(/\D/g, '')
if (!digits || digits.length < 5) {
uni.showToast({
title: $t('express.pleaseEnterValidPhone'),
title: t('express.pleaseEnterValidPhone'),
icon: 'none'
})
return false
}
if (isFillMode.value && !trackingNumber.value) {
uni.showToast({
title: $t('express.pleaseEnterTrackingNo'),
title: t('express.pleaseEnterTrackingNo'),
icon: 'none'
})
return false
@@ -221,7 +221,7 @@
submitting.value = true
try {
uni.showLoading({
title: isFillMode.value ? $t('common.filling') : $t('common.submitting')
title: isFillMode.value ? t('common.filling') : t('common.submitting')
})
let res
if (isFillMode.value) {
@@ -238,18 +238,18 @@
}
if (res && res.code === 200) {
uni.showToast({
title: isFillMode.value ? '补填成功' : '提交成功',
title: isFillMode.value ? t('express.fillSuccess') : t('express.submitSuccess'),
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 800)
} else {
throw new Error(res?.msg || (isFillMode.value ? '补填失败' : '提交失败'))
throw new Error(res?.msg || (isFillMode.value ? t('express.fillFailed') : t('express.submitFailed')))
}
} catch (e) {
uni.showToast({
title: e.message || (isFillMode.value ? '补填失败' : '提交失败'),
title: e.message || (isFillMode.value ? t('express.fillFailed') : t('express.submitFailed')),
icon: 'none'
})
} finally {
+18 -18
View File
@@ -91,7 +91,7 @@ import { getExpressReturnDetail } from '@/config/api/expressReturn.js'
import { getCustomerPhone } from '@/util/index.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 详情数据
const detailData = ref({
@@ -131,21 +131,21 @@ const getStatusIcon = (status) => {
// 获取状态文本
const getStatusText = (status) => {
const textMap = {
'completed': $t('express.returnCompleted'),
'processing': $t('express.processing'),
'pending': $t('express.pending')
'completed': t('express.returnCompleted'),
'processing': t('express.processing'),
'pending': t('express.pending')
}
return textMap[status] || $t('express.pending')
return textMap[status] || t('express.pending')
}
// 获取状态描述
const getStatusDesc = (status) => {
const descMap = {
'completed': $t('express.returnCompletedDesc'),
'processing': $t('express.processingDesc'),
'pending': $t('express.pendingDesc')
'completed': t('express.returnCompletedDesc'),
'processing': t('express.processingDesc'),
'pending': t('express.pendingDesc')
}
return descMap[status] || $t('express.pendingDesc')
return descMap[status] || t('express.pendingDesc')
}
// 复制运单号
@@ -154,7 +154,7 @@ const handleCopyTracking = () => {
data: detailData.value.trackingNumber,
success: () => {
uni.showToast({
title: $t('express.trackingNoCopied'),
title: t('express.trackingNoCopied'),
icon: 'success'
})
}
@@ -165,10 +165,10 @@ const handleCopyTracking = () => {
const handleContactService = () => {
const customerPhone = getCustomerPhone()
uni.showModal({
title: $t('user.customerService'),
content: `${$t('help.phone')}${customerPhone}\n${$t('help.workingHours')}${$t('express.workingHours')}`,
confirmText: $t('express.call'),
cancelText: $t('common.cancel'),
title: t('user.customerService'),
content: `${t('help.phone')}${customerPhone}\n${t('help.workingHours')}${t('express.workingHours')}`,
confirmText: t('express.call'),
cancelText: t('common.cancel'),
success: (res) => {
if (res.confirm) {
uni.makePhoneCall({
@@ -182,7 +182,7 @@ const handleContactService = () => {
// 页面加载时获取详情数据
onMounted(async () => {
uni.setNavigationBarTitle({
title: $t('express.returnDetail')
title: t('express.returnDetail')
})
const pages = getCurrentPages()
@@ -190,7 +190,7 @@ onMounted(async () => {
const options = currentPage.options || {}
if (!options.id) return
try {
uni.showLoading({ title: $t('common.loading') })
uni.showLoading({ title: t('common.loading') })
const res = await getExpressReturnDetail(options.id)
if (res && res.code === 200 && res.data) {
const r = res.data
@@ -208,10 +208,10 @@ onMounted(async () => {
remark: r.remark || ''
}
} else {
throw new Error(res?.msg || $t('express.getDetailFailed'))
throw new Error(res?.msg || t('express.getDetailFailed'))
}
} catch (e) {
uni.showToast({ title: e.message || $t('express.loadFailed'), icon: 'none' })
uni.showToast({ title: e.message || t('express.loadFailed'), icon: 'none' })
} finally {
uni.hideLoading()
}
+21 -22
View File
@@ -54,7 +54,7 @@ import { ref, onMounted } from 'vue'
import { getExpressReturnList } from '@/config/api/expressReturn.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
const returnList = ref([])
const loading = ref(false)
@@ -62,7 +62,7 @@ const query = ref({ pageNum: 1, pageSize: 20 })
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('express.returnRecord')
title: t('express.returnRecord')
})
loadList()
})
@@ -80,12 +80,12 @@ const loadList = async () => {
const rows = (res.data && (res.data.rows || res.data)) || []
returnList.value = rows.map(r => ({
id: r.id,
expressCompany: r.expressCompany || r.company || '待填写',
trackingNumber: r.logisticsTrackingNumber || r.trackingNumber || '待填写',
returnAddress: r.returnAddress || r.address || '待填写',
returnTime: r.expressFillTime || r.createTime || r.returnTime || '待填写',
packageType: r.packageType || '待填写',
weight: r.weight || '待填写',
expressCompany: r.expressCompany || r.company || t('express.toFill'),
trackingNumber: r.logisticsTrackingNumber || r.trackingNumber || t('express.toFill'),
returnAddress: r.returnAddress || r.address || t('express.toFill'),
returnTime: r.expressFillTime || r.createTime || r.returnTime || t('express.toFill'),
packageType: r.packageType || t('express.toFill'),
weight: r.weight || t('express.toFill'),
status: mapStatus(r.status),
rawStatus: r.status,
userPhone: r.userPhone,
@@ -93,10 +93,10 @@ const loadList = async () => {
remark: r.remark
}))
} else {
throw new Error(res?.msg || $t('express.getListFailed'))
throw new Error(res?.msg || t('express.getListFailed'))
}
} catch (e) {
uni.showToast({ title: e.message || $t('express.loadFailed'), icon: 'none' })
uni.showToast({ title: e.message || t('express.loadFailed'), icon: 'none' })
} finally {
loading.value = false
}
@@ -118,34 +118,33 @@ const getStatusClass = (status) => ({
}[status] || 'status-pending')
const getStatusText = (status) => ({
'completed': $t('express.billingPaused'),
'processing': $t('express.billingPaused'),
'pending': $t('express.billingPaused')
}[status] || $t('express.billingPaused'))
'completed': t('express.billingPaused'),
'processing': t('express.billingPaused'),
'pending': t('express.billingPaused')
}[status] || t('express.billingPaused'))
const getStatusBadge = (status) => ({
'completed': $t('express.completed'),
'processing': $t('express.processing'),
'pending': $t('express.pending')
}[status] || $t('express.pending'))
'completed': t('express.completed'),
'processing': t('express.processing'),
'pending': t('express.pending')
}[status] || t('express.pending'))
// 一键复制全部信息
const copyAllInfo = () => {
const allInfo = `${$t('express.recipient')}${recipientName}\n${$t('express.recipientAddressLabel')}${recipientAddress}`
const allInfo = `${t('express.recipient')}${recipientName}\n${t('express.recipientAddressLabel')}${recipientAddress}`
uni.setClipboardData({
data: allInfo,
success: () => {
uni.showToast({ title: $t('express.copySuccess'), icon: 'success' })
uni.showToast({ title: t('express.copySuccess'), icon: 'success' })
},
fail: () => {
uni.showToast({ title: $t('express.copyFailed'), icon: 'none' })
uni.showToast({ title: t('express.copyFailed'), icon: 'none' })
}
})
}
// 点击列表项
const handleItemClick = (item) => {
console.log('点击了归还记录:', item)
// 未填写(status=0 -> mapped 'pending')时跳转到补填页,其它跳详情
if (item && item.rawStatus === 0) {
uni.navigateTo({ url: `/pages/expressReturn/addExpressReturn?id=${item.id}` })
+17 -17
View File
@@ -98,13 +98,13 @@
} from '@/utils/i18n.js'
const {
t: $t
t
} = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('feedback.detail')
title: t('feedback.detail')
})
})
@@ -126,7 +126,7 @@
await loadDetail();
} else {
uni.showToast({
title: $t('feedback.idRequired'),
title: t('feedback.idRequired'),
icon: 'none'
});
setTimeout(() => {
@@ -170,7 +170,7 @@
try {
if (shouldShowLoading) {
uni.showLoading({
title: $t('common.loading')
title: t('common.loading')
});
}
@@ -180,7 +180,7 @@
await loadMessages(res.data.messages);
} else {
uni.showToast({
title: res.msg || $t('feedback.getDetailFailed'),
title: res.msg || t('feedback.getDetailFailed'),
icon: 'none'
});
setTimeout(() => {
@@ -190,7 +190,7 @@
} catch (error) {
console.error('获取投诉详情失败:', error);
uni.showToast({
title: $t('feedback.getDetailFailed'),
title: t('feedback.getDetailFailed'),
icon: 'none'
});
setTimeout(() => {
@@ -207,7 +207,7 @@
const submitReply = async () => {
if (!replyContent.value.trim()) {
uni.showToast({
title: $t('feedback.pleaseEnterReply'),
title: t('feedback.pleaseEnterReply'),
icon: 'none'
});
return;
@@ -215,7 +215,7 @@
try {
uni.showLoading({
title: $t('common.submitting')
title: t('common.submitting')
});
const res = await sendFeedbackMessage(feedbackId.value, {
@@ -224,7 +224,7 @@
if (res.code === 200) {
uni.showToast({
title: $t('feedback.replySuccess'),
title: t('feedback.replySuccess'),
icon: 'success'
});
replyContent.value = '';
@@ -234,14 +234,14 @@
});
} else {
uni.showToast({
title: res.msg || $t('feedback.replyFailed'),
title: res.msg || t('feedback.replyFailed'),
icon: 'none'
});
}
} catch (error) {
console.error('提交回复失败:', error);
uni.showToast({
title: $t('feedback.replyFailed'),
title: t('feedback.replyFailed'),
icon: 'none'
});
} finally {
@@ -252,11 +252,11 @@
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'pending': $t('feedback.pending'),
'in_progress': $t('feedback.processing'),
'resolved': $t('feedback.completed')
'pending': t('feedback.pending'),
'in_progress': t('feedback.processing'),
'resolved': t('feedback.completed')
};
return statusMap[status] || $t('feedback.pending');
return statusMap[status] || t('feedback.pending');
};
// 获取状态样式类
@@ -272,8 +272,8 @@
// 获取类型文本
const getTypeText = (type) => {
const typeMap = {
'complain': $t('feedback.complain'),
'suggestion': $t('feedback.suggestion')
'complain': t('feedback.complain'),
'suggestion': t('feedback.suggestion')
};
return typeMap[type] || type || '-';
};
+10 -10
View File
@@ -80,7 +80,7 @@
useI18n
} from '@/utils/i18n.js'
const {
t: $t
t
} = useI18n()
// 跳转到投诉记录列表
@@ -92,7 +92,7 @@
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('feedback.title')
title: t('feedback.title')
})
})
@@ -137,7 +137,7 @@
const submitFeedback = async () => {
if (selectedType.value === -1) {
uni.showToast({
title: $t('feedback.pleaseSelectType'),
title: t('feedback.pleaseSelectType'),
icon: 'none'
})
return
@@ -145,7 +145,7 @@
if (!description.value.trim()) {
uni.showToast({
title: $t('feedback.pleaseDescribe'),
title: t('feedback.pleaseDescribe'),
icon: 'none'
})
return
@@ -153,7 +153,7 @@
if (!contact.value) {
uni.showToast({
title: $t('feedback.pleaseContact'),
title: t('feedback.pleaseContact'),
icon: 'none'
})
return
@@ -169,7 +169,7 @@
try {
// 显示上传进度
uni.showLoading({
title: $t('feedback.uploading') || '上传中...',
title: t('feedback.uploading') || '上传中...',
mask: true
})
@@ -185,7 +185,7 @@
console.error(`文件 ${i + 1} 上传失败:`, err)
uni.hideLoading()
uni.showToast({
title: $t('feedback.imageUploadFailed'),
title: t('feedback.imageUploadFailed'),
icon: 'none'
})
return
@@ -208,7 +208,7 @@
// 处理响应
if (res && (res.code === 200 || res === true || res?.success === true)) {
uni.showToast({
title: $t('feedback.submitSuccess'),
title: t('feedback.submitSuccess'),
icon: 'success'
})
setTimeout(() => {
@@ -216,7 +216,7 @@
}, 1500);
} else {
uni.showToast({
title: (res && (res.msg || res.message)) || $t('feedback.submitFailed'),
title: (res && (res.msg || res.message)) || t('feedback.submitFailed'),
icon: 'none'
})
}
@@ -224,7 +224,7 @@
console.error('feedback submit failed:', err)
uni.hideLoading()
uni.showToast({
title: $t('error.networkError') || '网络错误,请重试',
title: t('error.networkError') || '网络错误,请重试',
icon: 'none'
})
}
+14 -14
View File
@@ -78,13 +78,13 @@
} from '@/utils/i18n.js'
const {
t: $t
t
} = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('feedback.recordList')
title: t('feedback.recordList')
})
})
@@ -100,25 +100,25 @@
// 状态标签
const statusTabs = reactive([{
get text() {
return $t('common.all')
return t('common.all')
},
status: ''
},
{
get text() {
return $t('feedback.pending')
return t('feedback.pending')
},
status: 'pending'
},
{
get text() {
return $t('feedback.processing')
return t('feedback.processing')
},
status: 'in_progress'
},
{
get text() {
return $t('feedback.completed')
return t('feedback.completed')
},
status: 'resolved'
}
@@ -176,14 +176,14 @@
}
} else {
uni.showToast({
title: res.msg || $t('feedback.getListFailed'),
title: res.msg || t('feedback.getListFailed'),
icon: 'none'
});
}
} catch (error) {
console.error('获取投诉列表失败:', error);
uni.showToast({
title: $t('feedback.getListFailed'),
title: t('feedback.getListFailed'),
icon: 'none'
});
} finally {
@@ -211,11 +211,11 @@
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'pending': $t('feedback.pending'),
'in_progress': $t('feedback.processing'),
'resolved': $t('feedback.completed')
'pending': t('feedback.pending'),
'in_progress': t('feedback.processing'),
'resolved': t('feedback.completed')
};
return statusMap[status] || $t('feedback.pending');
return statusMap[status] || t('feedback.pending');
};
// 获取状态样式类
@@ -231,8 +231,8 @@
// 获取类型文本
const getTypeText = (type) => {
const typeMap = {
'complain': $t('feedback.complain'),
'suggestion': $t('feedback.suggestion')
'complain': t('feedback.complain'),
'suggestion': t('feedback.suggestion')
};
return typeMap[type] || type || '-';
};
+21 -25
View File
@@ -26,40 +26,36 @@
</view>
<view class="contact-item">
<text class="label">{{ $t('help.workingHours') }}</text>
<text class="value">{{ HELP_CONTENT.CONTACT.SERVICE_TIME.VALUE }}</text>
<text class="value">{{ $t('help.workingHoursValue') }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { HELP_CONTENT } from '@/constants/help'
import { getCustomerPhone } from '@/util/index.js'
import { useI18n } from '@/utils/i18n.js'
export default {
data() {
return {
HELP_CONTENT,
faqList: HELP_CONTENT.FAQ_LIST,
customerPhone: HELP_CONTENT.CONTACT.PHONE.VALUE // 默认客服电话
}
},
onLoad() {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('help.title')
})
// 从缓存读取客服电话
this.customerPhone = getCustomerPhone()
},
methods: {
makePhoneCall() {
uni.makePhoneCall({
phoneNumber: this.customerPhone
})
}
}
const { t } = useI18n()
const faqList = ref(HELP_CONTENT.FAQ_LIST)
const customerPhone = ref(HELP_CONTENT.CONTACT.PHONE.VALUE)
onLoad(() => {
uni.setNavigationBarTitle({
title: t('help.title')
})
customerPhone.value = getCustomerPhone()
})
const makePhoneCall = () => {
uni.makePhoneCall({
phoneNumber: customerPhone.value
})
}
</script>
+45 -87
View File
@@ -198,10 +198,13 @@
transformDeviceData
} from '../../config/api/device.js'
import {
getPotionsDetail
getInUseOrder,
getUnpaidOrder
} from '../../config/api/order.js'
import {
getActiveActivity
getActiveActivity,
getCurrentAnnouncement,
getCurrentAdvertisement
} from '../../config/api/system.js'
// 导入地图工具函数
import {
@@ -227,7 +230,7 @@
// #endif
const {
t: $t
t
} = useI18n()
// 响应式数据
@@ -294,39 +297,21 @@
const bannerImages = ref([]) // 首页广告图片列表
const bannerImageList = ref([]) // 完整的广告配置列表(包含链接信息)
// 将语言代码转换为后端接受的格式
const convertLanguageCode = (lang) => {
// zh-CN -> zh_CN (转换下划线)
// en-US -> en_US (转换下划线)
return lang.replace(/-/g, '_')
}
// 获取公告内容(支持多语言)
const getNoticeText = async () => {
try {
// 获取当前语言设置
const currentLang = uni.getStorageSync('language') || 'zh-CN'
const languageCode = convertLanguageCode(currentLang)
console.log('加载公告,语言:', currentLang, '转换后:', languageCode)
console.log('加载公告')
// 调用接口获取公告内容
const res = await uni.request({
url: `${URL}/device/announcementConfig/current`,
method: 'GET',
header: {
'Content-Language': languageCode
},
data: {
type: 'wx_user_type' // 微信小程序用户端
}
const res = await getCurrentAnnouncement({
type: 'wx_user_type' // 微信小程序用户端
})
console.log('公告响应:', res)
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
if (res && res.code === 200 && res.data) {
// 使用后端自动解析的 announcement 字段
const announcement = res.data.data.announcement || ''
const announcement = res.data.announcement || ''
noticeText.value = announcement
// 设置通知栏高度
@@ -342,7 +327,7 @@
console.warn('缓存通知内容失败:', e)
}
} else {
console.warn('获取公告失败:', res.data?.msg || '未知错误')
console.warn('获取公告失败:', res?.msg || '未知错误')
}
} catch (error) {
console.error('获取公告失败:', error)
@@ -352,31 +337,20 @@
// 获取首页广告图片(支持多语言)
const getBannerImages = async () => {
try {
// 获取当前语言设置
const currentLang = uni.getStorageSync('language') || 'zh-CN'
const languageCode = convertLanguageCode(currentLang)
console.log('加载首页广告,语言:', currentLang, '转换后:', languageCode)
console.log('加载首页广告')
// 调用接口获取广告内容
const res = await uni.request({
url: `${URL}/device/advertisementConfig/current`,
method: 'GET',
header: {
'Content-Language': languageCode
},
data: {
appPlatform: 'wechat', // 微信平台
appType: 'user' ,// 用户端
pictureLocation:'home_banner'
}
const res = await getCurrentAdvertisement({
appPlatform: 'wechat', // 微信平台
appType: 'user' ,// 用户端
pictureLocation:'home_banner'
})
console.log('首页广告响应:', res)
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
if (res && res.code === 200 && res.data) {
// 使用 imageList 字段(包含图片和链接信息)
const imageList = res.data.data.imageList || []
const imageList = res.data.imageList || []
if (imageList.length > 0) {
bannerImageList.value = imageList
// 提取图片URL用于展示
@@ -385,7 +359,7 @@
console.warn('未获取到广告图片')
}
} else {
console.warn('获取首页广告失败:', res.data?.msg || '未知错误')
console.warn('获取首页广告失败:', res?.msg || '未知错误')
}
} catch (error) {
console.error('获取首页广告失败:', error)
@@ -415,7 +389,7 @@
fail: (err) => {
console.error('跳转小程序失败:', err)
uni.showToast({
title: '跳转失败',
title: t('common.loadFailed'),
icon: 'none'
})
}
@@ -423,7 +397,7 @@
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: '请在微信小程序中使用',
title: t('auth.pleaseUseInWechat'),
icon: 'none'
})
// #endif
@@ -568,10 +542,10 @@
console.warn('清理旧缓存失败:', e);
}
// 开发环境测试距离计算
if (process.env.NODE_ENV === 'development') {
testDistanceCalculation()
}
// // 开发环境测试距离计算
// if (process.env.NODE_ENV === 'development') {
// testDistanceCalculation()
// }
// 并行加载公告和广告(不依赖定位)
await Promise.all([
@@ -586,12 +560,12 @@
await loadPositions()
// 3. 查询活动并显示弹窗
await checkActiveActivity()
// await checkActiveActivity()
} catch (error) {
console.error('初始化失败:', error)
uni.showToast({
title: $t('home.getLocationFailed'),
title: t('home.getLocationFailed'),
icon: 'none'
})
} finally {
@@ -787,12 +761,16 @@
isRelocating.value = true
uni.showLoading({
title: $t('home.relocating'),
title: t('home.relocating'),
mask: true
})
// 重新获取用户真实位置(不使用缓存)
// 重新获取用户真实位置
const loc = await getUserLocation()
if (!loc || !loc.longitude || !loc.latitude) {
throw new Error('location failed')
}
const newLocation = {
longitude: Number(loc.longitude),
latitude: Number(loc.latitude)
@@ -827,7 +805,7 @@
uni.hideLoading()
uni.showToast({
title: $t('home.locateSuccess'),
title: t('home.locateSuccess'),
icon: 'success',
duration: 1500
})
@@ -836,7 +814,7 @@
uni.hideLoading()
uni.showToast({
title: e.errMsg || $t('home.locateFailed'),
title: e.errMsg || t('home.locateFailed'),
icon: 'none',
duration: 2000
})
@@ -868,7 +846,7 @@
const selectPosition = (position) => {
uni.showActionSheet({
itemList: ['扫码使用', '导航前往'],
itemList: [t('home.scanToUse'), t('home.navigate')],
success: (res) => {
switch (res.tapIndex) {
case 0:
@@ -948,27 +926,17 @@
if (!deviceNo) {
uni.showToast({
title: $t('home.invalidQRCode'),
title: t('home.invalidQRCode'),
icon: 'none'
})
return
}
// 检查是否有使用中的订单
const inUseRes = await uni.request({
url: `${URL}/app/order/inUse`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
const inUseRes = await getInUseOrder()
if (inUseRes.statusCode === 401 || inUseRes.data?.code === 401 || inUseRes.data?.code === 40101) {
redirectToLogin()
return
} else if (inUseRes.statusCode == 200 && inUseRes.data.code == 200 && inUseRes.data.data) {
const inUseOrder = inUseRes.data.data
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
const inUseOrder = inUseRes.data
uni.reLaunch({
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
})
@@ -976,20 +944,10 @@
}
// 检查是否有待支付订单
const orderRes = await uni.request({
url: `${URL}/app/order/unpaid`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
const orderRes = await getUnpaidOrder()
if (orderRes.statusCode === 401 || orderRes.data?.code === 401 || orderRes.data?.code === 40101) {
redirectToLogin()
return
} else if (orderRes.statusCode == 200 && orderRes.data.code == 200 && orderRes.data.data) {
const unpaidOrder = orderRes.data.data
if (orderRes && orderRes.code === 200 && orderRes.data) {
const unpaidOrder = orderRes.data
uni.navigateTo({
url: `/pages/order/payment?orderId=${unpaidOrder.orderId}`
})
@@ -1018,7 +976,7 @@
}
} else {
uni.showToast({
title: '获取设备信息失败',
title: t('device.getDeviceInfoFailed'),
icon: 'none'
})
uni.navigateTo({
+3 -3
View File
@@ -12,7 +12,7 @@
} from 'vue'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 外部网页地址
const webUrl = ref('https://joininvestment.gxfs123.com/')
@@ -27,7 +27,7 @@
const handleError = (e) => {
console.error('web-view 加载错误:', e)
uni.showToast({
title: $t('join.pageLoadFailed'),
title: t('join.pageLoadFailed'),
icon: 'none',
duration: 2000
})
@@ -35,7 +35,7 @@
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('join.title')
title: t('join.title')
})
console.log('招商页面加载,外部网址:', webUrl.value)
})
+13 -33
View File
@@ -36,9 +36,9 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useI18n } from '@/utils/i18n.js'
import { URL } from '@/config/url.js'
import { getCurrentAgreement } from '@/config/api/system.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 响应式数据
const loading = ref(true)
@@ -50,13 +50,6 @@
remark: ''
})
// 将语言代码转换为后端接受的格式
const convertLanguageCode = (lang) => {
// zh-CN -> zh-CN (保持不变)
// en-US -> en_US (转换下划线)
return lang.replace(/-/g, '_')
}
// 加载协议内容
const loadAgreement = async () => {
loading.value = true
@@ -64,35 +57,22 @@
errorMessage.value = ''
try {
// 获取当前语言设置
const currentLang = uni.getStorageSync('language') || 'zh-CN'
const languageCode = convertLanguageCode(currentLang)
console.log('加载用户协议,语言:', currentLang, '转换后:', languageCode)
console.log('加载用户协议')
// 调用接口获取协议内容
const res = await uni.request({
url: `${URL}/device/agreementConfig/current`,
method: 'GET',
header: {
'Content-Language': languageCode,
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
},
data: {
agreementCode: 'USER_AGREEMENT',
appPlatform: 'wechat',
appType: 'user'
}
const res = await getCurrentAgreement({
agreementCode: 'USER_AGREEMENT',
appPlatform: 'wechat',
appType: 'user'
})
console.log('用户协议响应:', res)
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
if (res && res.code === 200 && res.data) {
agreementData.value = {
title: res.data.data.title || $t('legal.agreement'),
content: res.data.data.content || '',
remark: res.data.data.remark || ''
title: res.data.title || t('legal.agreement'),
content: res.data.content || '',
remark: res.data.remark || ''
}
// 设置页面标题
@@ -100,12 +80,12 @@
title: agreementData.value.title
})
} else {
throw new Error(res.data.msg || $t('common.loadFailed'))
throw new Error(res?.msg || t('common.loadFailed'))
}
} catch (err) {
console.error('加载用户协议失败:', err)
error.value = true
errorMessage.value = err.message || $t('common.loadFailed')
errorMessage.value = err.message || t('common.loadFailed')
} finally {
loading.value = false
}
+13 -33
View File
@@ -36,9 +36,9 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useI18n } from '@/utils/i18n.js'
import { URL } from '@/config/url.js'
import { getCurrentAgreement } from '@/config/api/system.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 响应式数据
const loading = ref(true)
@@ -50,13 +50,6 @@
remark: ''
})
// 将语言代码转换为后端接受的格式
const convertLanguageCode = (lang) => {
// zh-CN -> zh-CN (保持不变)
// en-US -> en_US (转换下划线)
return lang.replace(/-/g, '_')
}
// 加载协议内容
const loadAgreement = async () => {
loading.value = true
@@ -64,35 +57,22 @@
errorMessage.value = ''
try {
// 获取当前语言设置
const currentLang = uni.getStorageSync('language') || 'zh-CN'
const languageCode = convertLanguageCode(currentLang)
console.log('加载隐私政策,语言:', currentLang, '转换后:', languageCode)
console.log('加载隐私政策')
// 调用接口获取协议内容
const res = await uni.request({
url: `${URL}/device/agreementConfig/current`,
method: 'GET',
header: {
'Content-Language': languageCode,
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
},
data: {
agreementCode: 'PRIVACY_POLICY',
appPlatform: 'wechat',
appType: 'user'
}
const res = await getCurrentAgreement({
agreementCode: 'PRIVACY_POLICY',
appPlatform: 'wechat',
appType: 'user'
})
console.log('隐私政策响应:', res)
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
if (res && res.code === 200 && res.data) {
agreementData.value = {
title: res.data.data.title || $t('legal.privacy'),
content: res.data.data.content || '',
remark: res.data.data.remark || ''
title: res.data.title || t('legal.privacy'),
content: res.data.content || '',
remark: res.data.remark || ''
}
// 设置页面标题
@@ -100,12 +80,12 @@
title: agreementData.value.title
})
} else {
throw new Error(res.data.msg || $t('common.loadFailed'))
throw new Error(res?.msg || t('common.loadFailed'))
}
} catch (err) {
console.error('加载隐私政策失败:', err)
error.value = true
errorMessage.value = err.message || $t('common.loadFailed')
errorMessage.value = err.message || t('common.loadFailed')
} finally {
loading.value = false
}
+28 -17
View File
@@ -16,8 +16,10 @@
{{ $t('auth.getPhoneNumber') }}
</button>
<!-- 仅微信登录不授权手机号时使用 -->
<!-- <button class="btn outline" @click="onWeChatLogin">仅微信登录</button> -->
<!-- 手机号验证码登录 -->
<button class="btn outline" @click="goToPhoneLogin" v-if="isHTML5">
{{ $t('auth.phoneLogin') }}
</button>
<view class="agreement-box">
<checkbox-group @change="onAgreementChange">
@@ -41,22 +43,23 @@
import { wxLogin, getUserPhoneNumber, getUserInfo } from '../../util/index.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('auth.loginTitle')
title: t('auth.loginTitle')
})
})
const isHTML5 = ref(false) // 是否是HTML5模式
const redirect = ref('/pages/index/index')
const isAgreed = ref(false) // 是否同意协议
// 勾选协议变化
const onAgreementChange = (e) => {
isAgreed.value = e.detail.value.includes('agreed')
console.log('协议勾选状态:', isAgreed.value, e.detail.value)
}
// 未勾选协议时点击登录按钮
@@ -79,10 +82,10 @@
// 未勾选,弹窗提示
uni.showModal({
title: $t('common.tips'),
content: $t('auth.pleaseAgreeToTerms'),
confirmText: $t('common.confirm'),
cancelText: $t('common.cancel'),
title: t('common.tips'),
content: t('auth.pleaseAgreeToTerms'),
confirmText: t('common.confirm'),
cancelText: t('common.cancel'),
success: (res) => {
if (res.confirm) {
// 用户点击同意,自动勾选
@@ -90,7 +93,7 @@
resolve()
} else {
// 用户点击取消
reject(new Error('需要同意协议才能登录'))
reject(new Error(t('auth.pleaseAgreeToTerms')))
}
}
})
@@ -119,18 +122,18 @@
await checkAgreement()
await wxLogin()
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
if (error.message !== '需要同意协议才能登录') {
uni.showToast({ title: error.message || '登录失败', icon: 'none' })
if (error.message !== t('auth.pleaseAgreeToTerms')) {
uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
}
}
}
const onGetPhoneNumber = async (e) => {
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
uni.showToast({ title: $t('auth.phoneCancelled'), icon: 'none' })
uni.showToast({ title: t('auth.phoneCancelled'), icon: 'none' })
return
}
@@ -139,10 +142,10 @@
await wxLogin()
// 再用微信返回的临时 code 换取手机号
await getUserPhoneNumber(e.detail.code)
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
await navigateAfterLogin()
} catch (error) {
uni.showToast({ title: error.message || $t('auth.loginFailed'), icon: 'none' })
uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
}
}
@@ -152,17 +155,25 @@
redirect.value = decodeURIComponent(opts.redirect)
} catch (_) {}
}
// #ifdef H5
isHTML5.value = true
// #endif
})
const go = (url) => {
uni.navigateTo({ url })
}
// 跳转到手机号登录页面
const goToPhoneLogin = () => {
uni.navigateTo({ url: '/pages/login/phone' })
}
</script>
<style lang="scss" scoped>
.login-container {
min-height: 100vh;
background: #f8f8f8;
background: linear-gradient(180deg, #C8F4D9 0%, #FFFFFF 100%);
padding: 80rpx 40rpx 40rpx;
display: flex;
flex-direction: column;
+394
View File
@@ -0,0 +1,394 @@
<template>
<view class="login-container">
<view class="header">
<view class="title">Hello,</view>
<view class="subtitle">{{ $t('app.welcome') }}</view>
</view>
<!-- 国家区号选择器 -->
<view class="form-group">
<view class="phone-input-wrapper">
<view class="country-code" @click="showCountryPicker">
<text>{{ countryCode }}</text>
<text class="arrow"></text>
</view>
<view class="divider"></view>
<input
class="phone-input"
v-model="phone"
type="number"
maxlength="11"
:placeholder="$t('auth.phonePlaceholder')"
/>
</view>
</view>
<!-- 验证码输入 -->
<view class="form-group">
<view class="code-input-wrapper">
<input
class="code-input"
v-model="verifyCode"
type="number"
maxlength="6"
:placeholder="$t('auth.codePlaceholder')"
/>
<view class="code-btn" @click="handleSendCode" :class="{ disabled: countdown > 0 }">
<text class="code-btn-text">{{ countdown > 0 ? `${countdown}s` : $t('auth.getCode') }}</text>
</view>
</view>
</view>
<!-- 区域提示 -->
<view class="region-notice">
<text>{{ $t('auth.regionNotSupported') }}</text>
</view>
<!-- 登录按钮 -->
<view class="login-btn" @click="handleLogin">
<text class="login-btn-text">{{ $t('auth.loginBtn') }}</text>
</view>
<!-- 协议勾选 -->
<view class="agreement-box">
<checkbox-group @change="onAgreementChange">
<label class="agreement-label">
<checkbox value="agreed" :checked="isAgreed" color="#07c160" class="agreement-checkbox" />
<text class="agreement-text">
{{ $t('auth.agreeToTerms') }}
<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')">{{ $t('user.privacyPolicy') }}</text>
</text>
</label>
</checkbox-group>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { sendVerifyCode, loginWithCode } from '@/config/api/user.js'
import { fetchAndCacheCustomerPhone } from '@/util/index.js'
import { useI18n } from '@/utils/i18n.js'
const { t } = useI18n()
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: t('auth.phoneLogin')
})
})
const redirect = ref('/pages/index/index')
const isAgreed = ref(false) // 是否同意协议
const phone = ref('') // 手机号
const verifyCode = ref('') // 验证码
const countryCode = ref('+86') // 国家区号
const countdown = ref(0) // 验证码倒计时
let timer = null // 计时器
// 勾选协议变化
const onAgreementChange = (e) => {
isAgreed.value = e.detail.value.includes('agreed')
}
// 显示国家区号选择器(暂时仅支持+86)
const showCountryPicker = () => {
uni.showToast({
title: t('auth.onlyMainlandSupported'),
icon: 'none'
})
}
// 验证手机号格式
const validatePhone = () => {
if (!phone.value) {
uni.showToast({ title: t('auth.phoneRequired'), icon: 'none' })
return false
}
const phoneReg = /^1[3-9]\d{9}$/
if (!phoneReg.test(phone.value)) {
uni.showToast({ title: t('auth.phoneInvalid'), icon: 'none' })
return false
}
return true
}
// 发送验证码
const handleSendCode = async () => {
if (countdown.value > 0) return
if (!validatePhone()) return
try {
uni.showLoading({ title: t('common.sending') })
await sendVerifyCode(phone.value)
uni.hideLoading()
uni.showToast({ title: t('auth.codeSent'), icon: 'success' })
// 启动60秒倒计时
countdown.value = 60
timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer)
timer = null
}
}, 1000)
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || t('auth.sendCodeFailed'),
icon: 'none'
})
}
}
// 登录
const handleLogin = async () => {
if (!validatePhone()) return
if (!verifyCode.value) {
uni.showToast({ title: t('auth.codeRequired'), icon: 'none' })
return
}
if (!isAgreed.value) {
uni.showToast({ title: t('auth.pleaseAgreeToTerms'), icon: 'none' })
return
}
try {
uni.showLoading({ title: t('common.loggingIn') })
const res = await loginWithCode(phone.value, verifyCode.value)
// 保存token和client_id
// 兼容多种返回格式:res.data.token, res.token, res.data.access_token
const token = res.token || (res.data && (res.data.token || res.data.access_token))
const clientId = res.client_id || (res.data && (res.data.client_id || res.data.clientId))
if (token) {
uni.setStorageSync('token', token)
if (clientId) {
uni.setStorageSync('client_id', clientId)
}
// 登录成功后获取并缓存客服电话
fetchAndCacheCustomerPhone().catch(err => {
console.error(t('auth.getServicePhoneFailed'), err)
})
} else {
throw new Error(t('auth.noAuthToken'))
}
uni.hideLoading()
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
// 跳转到首页
setTimeout(() => {
uni.reLaunch({ url: redirect.value })
}, 1500)
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || t('auth.loginFailed'),
icon: 'none'
})
}
}
// 页面加载
onLoad((opts) => {
if (opts && opts.redirect) {
try {
redirect.value = decodeURIComponent(opts.redirect)
} catch (_) {}
}
})
const go = (url) => {
uni.navigateTo({ url })
}
// 清理定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer)
timer = null
}
})
</script>
<style lang="scss" scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(180deg, #C8F4D9 0%, #FFFFFF 100%);
padding: 0 48rpx;
box-sizing: border-box;
position: relative;
.header {
padding-top: 120rpx;
margin-bottom: 80rpx;
.title {
font-size: 64rpx;
font-weight: 700;
color: #000000;
line-height: 1.2;
margin-bottom: 8rpx;
}
.subtitle {
font-size: 64rpx;
font-weight: 700;
color: #000000;
line-height: 1.2;
}
}
.form-group {
margin-bottom: 32rpx;
.phone-input-wrapper {
background: #FFFFFF;
border-radius: 48rpx;
height: 96rpx;
display: flex;
align-items: center;
padding: 0 32rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
.country-code {
display: flex;
align-items: center;
font-size: 32rpx;
color: #333333;
padding-right: 16rpx;
.arrow {
margin-left: 8rpx;
font-size: 20rpx;
color: #999999;
}
}
.divider {
width: 2rpx;
height: 40rpx;
background: #E5E5E5;
margin: 0 16rpx;
}
.phone-input {
flex: 1;
font-size: 32rpx;
color: #333333;
}
}
.code-input-wrapper {
background: #FFFFFF;
border-radius: 48rpx;
height: 96rpx;
display: flex;
align-items: center;
padding: 0 32rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
.code-input {
flex: 1;
font-size: 32rpx;
color: #333333;
}
.code-btn {
padding-left: 24rpx;
border-left: 2rpx solid #E5E5E5;
&.disabled {
opacity: 0.5;
}
.code-btn-text {
font-size: 28rpx;
color: #07c160;
font-weight: 500;
white-space: nowrap;
}
}
}
}
.region-notice {
margin-bottom: 48rpx;
padding: 0 8rpx;
text {
font-size: 24rpx;
color: #666666;
line-height: 1.6;
}
}
.login-btn {
background: #07c160;
border-radius: 60rpx;
height: 112rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
margin-bottom: 48rpx;
&:active {
opacity: 0.9;
}
.login-btn-text {
font-size: 36rpx;
color: #FFFFFF;
font-weight: 600;
}
}
.agreement-box {
position: absolute;
left: 48rpx;
right: 48rpx;
bottom: 60rpx;
display: flex;
justify-content: center;
align-items: center;
.agreement-label {
display: flex;
align-items: center;
width: 100%;
.agreement-checkbox {
flex-shrink: 0;
transform: scale(0.75);
margin-right: 4rpx;
}
.agreement-text {
flex: 1;
font-size: 24rpx;
color: #666;
line-height: 1.8;
word-break: break-all;
.link {
color: #07c160;
font-weight: 500;
text-decoration: none;
}
}
}
}
}
</style>
+420 -186
View File
@@ -2,30 +2,63 @@
<view class="my-card-page">
<!-- 会员卡列表 -->
<view class="card-list" v-if="cardList.length > 0">
<view v-for="card in cardList" :key="card.id" class="card-item" @click="viewCardDetail(card)">
<view class="card-header">
<text class="card-name">{{ card.name }}</text>
<view class="card-status" :class="getStatusClass(card.status)">
<text class="status-text">{{ getStatusText(card.status) }}</text>
<view v-for="card in cardList" :key="card.id" style="position: relative;background-color: #f5f5f5;"
@click="viewCardDetail(card)" :style="card.cardType==='COUNT'?'height: 240rpx;':'height: 200rpx;'">
<view
style="height: 120rpx;background-color: #ffffff;z-index: 999;border-radius: 25rpx;padding: 32rpx;position: absolute;top: 0;left: 0;right: 0;">
<!-- 卡片头部标题和日期 -->
<view class="card-header">
<text class="card-name">{{ card.name }}</text>
<view class="card-date">
<text class="date-text" v-if="card.status !== 'expired'">{{ card.endDate }}{{
$t('myCard.expire') }}</text>
<text class="date-text expired" v-else>{{ $t('myCard.expiredOn') }}{{ card.endDate }}</text>
</view>
</view>
<!-- 地区信息 -->
<view class="card-region">
<text
class="region-text">{{ $t('myCard.onlyForRegionBefore') }}{{ card.positionName }}{{ $t('myCard.onlyForRegionAfter') }}</text>
<!-- 状态标签 / 去使用按钮 -->
<view v-if="card.status !== 'expired'" class="status-tag active"
style="display: flex; align-items: center; gap: 4rpx; background-color: #FFE2B8; border-radius: 26rpx; padding: 6rpx 20rpx;"
@click.stop="handleUseCard(card)">
<text class="status-text" style="color: #D4A574;">{{ $t('myCard.toUse') }}</text>
<!-- <uv-icon name="scan" size="12" color="#D4A574"></uv-icon> -->
</view>
<view v-else class="status-tag" :class="getStatusClass(card.status)">
<text class="status-text">{{ getStatusText(card.status) }}</text>
</view>
</view>
</view>
<view class="card-info">
<text class="info-label">{{ $t('myCard.type') }}</text>
<text class="info-value">{{ card.cardType === 'COUNT' ? $t('myCard.timesCard') :
$t('myCard.durationCard') }}</text>
</view>
<view class="card-info" v-if="card.type === 'times'">
<text class="info-label">{{ $t('myCard.remainingTimes') }}</text>
<text class="info-value highlight">{{ card.remainingTimes }}/{{ card.totalTimes }}</text>
</view>
<view class="card-info" v-if="card.type === 'duration'">
<text class="info-label">{{ $t('myCard.remainingDuration') }}</text>
<text class="info-value highlight">{{ card.remainingDuration }}{{ $t('myCard.hours') }}</text>
</view>
<view class="card-info">
<text class="info-label">{{ $t('myCard.validPeriod') }}</text>
<text class="info-value">{{ card.startDate }} - {{ card.endDate }}</text>
<!-- 使用情况和操作按钮 -->
<view style="position: absolute; bottom: -10rpx; left: 0; right: 0; padding: 20rpx;z-index:1;"
v-if="card.cardType==='COUNT'">
<view class="card-footer">
<!-- 次卡信息 -->
<view v-if="card.cardType === 'COUNT'" class="card-usage-info">
<text class="usage-text">{{ $t('myCard.remainingTimes') }}{{ card.remainingCount }}{{
$t('myCard.times') }}</text>
</view>
<!-- 时长卡信息 -->
<view v-if="card.cardType === 'TIME'" class="card-usage-info">
<text class="usage-text">{{ $t('myCard.durationCard') }}</text>
</view>
<!-- 操作按钮 -->
<view class="card-actions">
<!-- 续卡按钮仅次卡显示 -->
<view v-if="card.cardType === 'COUNT'" class="renew-btn" @click.stop="renewCard(card)">
<text class="renew-text">{{ $t('myCard.renew') }}</text>
<uv-icon name="arrow-right" size="14" color="#D4A574"></uv-icon>
<!-- <text class="arrow">{{ '>' }}</text> -->
</view>
</view>
</view>
</view>
</view>
</view>
@@ -41,202 +74,403 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useI18n } from '@/utils/i18n.js'
import {
getMemberCardsByStatus
} from '@/config/api/member.js'
const { t: $t } = useI18n()
import {
ref,
onMounted
} from 'vue'
import {
useI18n
} from '@/utils/i18n.js'
import {
getMemberCardsByStatus
} from '@/config/api/member.js'
import {
getQueryString
} from '@/util/index.js'
import {
getDeviceInfo
} from '@/config/api/device.js'
import {
getInUseOrder,
getUnpaidOrder
} from '@/config/api/order.js'
const {
t
} = useI18n()
// 会员卡列表
const cardList = ref([])
// 会员卡列表
const cardList = ref([])
// 获取会员卡列表
const getCardList = async () => {
try {
const response = await getMemberCardsByStatus()
// 处理API返回的数据,转换为模板需要的格式
if (response.code === 200 && response.data) {
cardList.value = response.data.map(item => ({
id: item.id,
name: item.cardName,
cardType: item.cardType, // TIME -> time
totalTimes: item.totalCount,
remainingTimes: item.remainingCount,
remainingDuration: item.singleLimitMinutes,
status: item.status,
startDate: item.startTime,
endDate: item.endTime,
positionName: item.positionName,
purchasePrice: item.purchasePrice,
// 添加可能的其他字段
...item
}))
} else {
cardList.value = []
// 获取会员卡列表
const getCardList = async () => {
try {
const response = await getMemberCardsByStatus()
// 处理API返回的数据,转换为模板需要的格式
if (response.code === 200 && response.data) {
cardList.value = response.data.map(item => ({
id: item.id,
name: item.cardName,
cardType: item.cardType, // TIME 或 COUNT
// 次卡相关
totalCount: item.totalCount,
remainingCount: item.remainingCount,
singleLimitMinutesForCount: item.singleLimitMinutesForCount,
// 时长卡相关
cycleDays: item.cycleDays,
dailyLimitCount: item.dailyLimitCount,
singleLimitMinutes: item.singleLimitMinutes,
currentCycleStartTime: item.currentCycleStartTime,
currentCycleUsedCount: item.currentCycleUsedCount,
// 通用信息
status: item.status,
startDate: item.startTime?.split(' ')[0] || item.startTime,
endDate: item.endTime?.split(' ')[0] || item.endTime,
positionId: item.positionId,
positionName: item.positionName,
purchasePrice: item.purchasePrice,
remark: item.remark
}))
} else {
cardList.value = []
}
} catch (error) {
console.error('获取会员卡列表失败:', error)
uni.showToast({
title: t('myCard.getListFailed'),
icon: 'none'
})
}
} catch (error) {
console.error('获取会员卡列表失败:', error)
uni.showToast({
title: $t('myCard.getListFailed'),
icon: 'none'
}
// 计算进度条宽度
const getProgressWidth = (used, total) => {
if (!total || total === 0) return '0%'
const percentage = (used / total) * 100
return `${Math.min(percentage, 100)}%`
}
// 获取状态类名
const getStatusClass = (status) => {
const statusMap = {
'unused': 'active',
'expired': 'expired',
'used': 'used',
'active': 'active' // 兼容原始状态
}
return statusMap[status] || 'active' // 默认为active
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'unused': t('myCard.active'), // unused表示未使用,即活跃状态
'expired': t('myCard.expired'),
'used': t('myCard.used'),
'active': t('myCard.active') // 兼容原始状态
}
return statusMap[status] || t('myCard.active') // 默认为active
}
// 查看卡详情
const viewCardDetail = (card) => {
// TODO: 跳转到卡详情页面
// uni.showToast({
// title: t('common.functionDeveloping'),
// icon: 'none'
// })
}
// 续卡
const renewCard = (card) => {
uni.navigateTo({
url: `/pages/purchase/index?positionId=${card.positionId}`
})
}
}
// 获取状态类名
const getStatusClass = (status) => {
const statusMap = {
'unused': 'active',
'expired': 'expired',
'used': 'used',
'active': 'active' // 兼容原始状态
// 去使用会员卡
const handleUseCard = async (card) => {
try {
const scanResult = await new Promise((resolve, reject) => {
uni.scanCode({
success: resolve,
fail: reject
})
})
console.log('扫码结果:', scanResult);
let deviceNo;
// 兼容不同平台的扫码结果
if (scanResult.scanType === 'QR_CODE' || scanResult.scanType === 'qrCode') {
deviceNo = getQueryString(scanResult.result, 'deviceNo')
} else if (scanResult.path) {
deviceNo = getQueryString(scanResult.path, 'deviceNo')
} else {
deviceNo = scanResult.result
}
if (!deviceNo) {
uni.showToast({
title: t('home.invalidQRCode'),
icon: 'none'
})
return
}
uni.showLoading({
title: t('common.getting')
})
// 检查是否有使用中的订单
const inUseRes = await getInUseOrder()
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
uni.hideLoading()
const inUseOrder = inUseRes.data
uni.reLaunch({
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
})
return
}
// 检查是否有待支付订单
const orderRes = await getUnpaidOrder()
if (orderRes && orderRes.code === 200 && orderRes.data) {
uni.hideLoading()
const unpaidOrder = orderRes.data
uni.navigateTo({
url: `/pages/order/payment?orderId=${unpaidOrder.orderId}`
})
return
}
// 获取设备信息并跳转详情
const deviceInfoRes = await getDeviceInfo(deviceNo)
uni.hideLoading()
if (deviceInfoRes.code === 200 && deviceInfoRes.data && deviceInfoRes.data.device) {
const deviceInfo = deviceInfoRes.data.device
let url = `/pages/device/detail?deviceNo=${deviceNo}`
if (deviceInfo.feeConfig) {
url += `&feeConfig=${encodeURIComponent(deviceInfo.feeConfig)}`
}
uni.navigateTo({
url
})
} else {
uni.navigateTo({
url: `/pages/device/detail?deviceNo=${deviceNo}`
})
}
} catch (error) {
console.error('扫码处理失败:', error)
if (error && error.errMsg !== 'scanCode:fail cancel') {
uni.showToast({
title: t('home.scanFailed'),
icon: 'none'
})
}
}
}
return statusMap[status] || 'active' // 默认为active
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'unused': $t('myCard.active'), // unused表示未使用,即活跃状态
'expired': $t('myCard.expired'),
'used': $t('myCard.used'),
'active': $t('myCard.active') // 兼容原始状态
// 去购买
const goToBuy = () => {
uni.navigateTo({
url: '/pages/purchase/index'
})
}
return statusMap[status] || $t('myCard.active') // 默认为active
}
// 查看卡详情
const viewCardDetail = (card) => {
// TODO: 跳转到卡详情页面
// uni.showToast({
// title: $t('common.functionDeveloping'),
// icon: 'none'
// })
}
// 去购买
const goToBuy = () => {
uni.navigateTo({
url: '/pages/purchase/index'
onMounted(() => {
uni.setNavigationBarTitle({
title: t('user.myCards')
})
getCardList()
})
}
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('user.myCards')
})
getCardList()
})
</script>
<style lang="scss" scoped>
.my-card-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20rpx;
}
.card-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.card-item {
background-color: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.card-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.card-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
&.active {
background-color: #e8f5e8;
color: #4caf50;
.my-card-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 24rpx;
}
&.expired {
background-color: #ffeaea;
color: #f44336;
.card-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
&.used {
background-color: #f0f0f0;
color: #999;
}
}
.card-item {
// background-color: #ffffff;
border-radius: 25rpx;
padding: 32rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
position: relative;
z-index: 5;
.card-info {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.info-label {
font-size: 26rpx;
color: #666;
}
.info-value {
font-size: 26rpx;
color: #333;
// 卡片头部
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
}
&.highlight {
color: #FF6B00;
font-weight: 600;
.card-name {
font-size: 36rpx;
font-weight: 600;
color: #333333;
line-height: 50rpx;
}
.card-date {
.date-text {
font-size: 24rpx;
color: #999999;
line-height: 34rpx;
&.expired {
color: #999999;
}
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
// 地区信息
.card-region {
margin-bottom: 24rpx;
align-items: center;
display: flex;
justify-content: space-between;
gap: 16rpx;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.5;
.region-text {
font-size: 26rpx;
color: #666666;
line-height: 36rpx;
}
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-bottom: 40rpx;
// 卡片底部
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
/* padding-top: 24rpx ; */
position: absolute;
background: rgba(255, 244, 227, 1);
z-index: 1;
right: 0;
bottom: 0;
left: 0;
border-radius: 0 0 25rpx 25rpx;
padding: 20rpx;
/* text-align: 30rpx ; */
}
.buy-btn {
padding: 20rpx 60rpx;
background-color: #B8741A;
border-radius: 48rpx;
.card-usage-info {
flex: 1;
.buy-text {
font-size: 28rpx;
color: #ffffff;
.usage-text {
font-size: 26rpx;
color: #D4A574;
font-weight: 500;
line-height: 36rpx;
}
}
}
</style>
.card-actions {
display: flex;
align-items: center;
gap: 16rpx;
}
// 续卡按钮
.renew-btn {
display: flex;
align-items: center;
gap: 4rpx;
padding: 8rpx 16rpx;
// background-color: #FFF9F0;
border-radius: 8rpx;
.renew-text {
font-size: 24rpx;
color: #D4A574;
line-height: 34rpx;
}
.arrow {
font-size: 24rpx;
color: #D4A574;
}
}
// 状态标签
.status-tag {
padding: 8rpx 20rpx;
border-radius: 8rpx;
.status-text {
font-size: 24rpx;
line-height: 34rpx;
}
&.active {
// background-color: #FFF9F0;
.status-text {
color: #D4A574;
}
}
&.expired {
// background-color: #F5F5F5;
.status-text {
color: #999999;
}
}
&.used {
// background-color: #F5F5F5;
.status-text {
color: #999999;
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-bottom: 40rpx;
}
.buy-btn {
padding: 20rpx 60rpx;
background-color: #B8741A;
border-radius: 48rpx;
.buy-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
}
</style>
+462 -343
View File
@@ -1,7 +1,7 @@
<template>
<view class="my-coupon-page">
<!-- Tab 切换 -->
<view class="tab-container">
<!-- <view class="tab-container">
<view class="tab-item" :class="{ active: currentTab === 'available' }" @click="switchTab('available')">
<text class="tab-text">{{ $t('myCoupon.available') }}</text>
</view>
@@ -11,25 +11,35 @@
<view class="tab-item" :class="{ active: currentTab === 'expired' }" @click="switchTab('expired')">
<text class="tab-text">{{ $t('myCoupon.expired') }}</text>
</view>
</view>
</view> -->
<!-- 优惠券列表 -->
<view class="coupon-list" v-if="filteredCoupons.length > 0">
<view v-for="coupon in filteredCoupons" :key="coupon.id" class="coupon-item-wrapper">
<view class="coupon-item" :class="getCouponClass(coupon.status)">
<!-- 左侧圆形缺口 -->
<view class="coupon-circle-left"></view>
<!-- 右侧圆形缺口 -->
<view class="coupon-circle-right"></view>
<!-- 虚线上下圆形缺口 -->
<view class="coupon-circle-top"></view>
<view class="coupon-circle-bottom"></view>
<view class="coupon-left">
<text class="coupon-value">{{ coupon.type === 'discount' ? coupon.discount + '折' : '¥' + coupon.value }}</text>
<text class="coupon-condition">{{ coupon.condition }}</text>
<text class="coupon-validity-left">{{ coupon.validity }}</text>
<view class="coupon-value">
<text v-if="coupon.type === 'cash'" class="coupon-unit">¥</text>
<text class="coupon-amount">{{ coupon.type === 'discount' ? coupon.discount : coupon.value }}</text>
<text v-if="coupon.type === 'discount'" class="coupon-unit"></text>
</view>
<view style="display: flex;flex-direction: column;">
<text class="coupon-condition">{{ coupon.condition }}</text>
<text class="coupon-validity-left">{{ coupon.validity }}</text>
</view>
</view>
<view class="coupon-divider"></view>
<view class="coupon-right">
<text class="coupon-name">{{ coupon.name }}</text>
<!-- <text class="coupon-name">{{ coupon.name }}</text> -->
<!-- <text class="coupon-region" v-if="coupon.positionName"
style="font-size: 22rpx; color: #999; margin-top: 4rpx;">
{{ $t('myCoupon.onlyForRegionBefore') }}{{ coupon.positionName }}{{ $t('myCoupon.onlyForRegionAfter') }}
</text> -->
<view class="use-btn" v-if="coupon.status === 'available'" @click="useCoupon(coupon)">
<text class="use-text">{{ $t('myCoupon.useNow') }}</text>
</view>
@@ -51,370 +61,479 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useI18n } from '@/utils/i18n.js'
import { getUserCoupons } from '@/config/api/coupon.js'
import { ref, computed, onMounted } from 'vue'
import { useI18n } from '@/utils/i18n.js'
import { getUserCoupons } from '@/config/api/coupon.js'
import { getQueryString } from '@/util/index.js'
import { getDeviceInfo } from '@/config/api/device.js'
import { getInUseOrder, getUnpaidOrder } from '@/config/api/order.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 当前选中的 Tab
const currentTab = ref('available')
// 当前选中的 Tab
const currentTab = ref('available')
// Tab 与 API 状态的映射
const tabToStatusMap = {
available: 'unused',
used: 'used',
expired: 'expired'
}
// // Tab 与 API 状态的映射
// const tabToStatusMap = {
// available: 'unused',
// used: 'used',
// expired: 'expired'
// }
// 优惠券列表
const couponList = ref([])
// 优惠券列表
const couponList = ref([])
// 过滤后的优惠券
const filteredCoupons = computed(() => {
return couponList.value.filter(coupon => coupon.status === currentTab.value)
})
// 过滤后的优惠券
const filteredCoupons = computed(() => {
return couponList.value.filter(coupon => coupon.status === currentTab.value)
})
// 获取优惠券列表
const getCouponList = async () => {
try {
const apiStatus = tabToStatusMap[currentTab.value]
const res = await getUserCoupons(apiStatus)
if (res.code === 200 && res.data) {
// 将后端数据转换为前端需要的格式
couponList.value = (res.data || []).map(item => {
// 判断优惠券类型:discount_coupon 折扣券,deduction_coupon 抵扣券
const isCashCoupon = item.couponType === 'deduction_coupon'
// 格式化使用条件
let condition = '无门槛'
if (item.usableCondition && item.usableCondition > 0) {
condition = `${item.usableCondition}可用`
}
// 格式化有效期
let validity = ''
if (currentTab.value === 'used') {
// 已使用显示使用时间(这里暂用开始时间,如后端有使用时间字段可替换)
validity = item.couponStartTime ? `使用时间 ${item.couponStartTime}` : ''
} else if (item.couponEndTime) {
validity = `有效期至 ${item.couponEndTime}`
}
return {
id: item.id,
name: item.couponName || '优惠券',
type: isCashCoupon ? 'cash' : 'discount',
value: item.deductAmount ? parseFloat(item.deductAmount) : 0,
discount: item.discountRate ? parseFloat(item.discountRate) * 10 : null,
condition: condition,
validity: validity,
status: currentTab.value
}
})
} else {
couponList.value = []
}
} catch (error) {
console.error('获取优惠券列表失败:', error)
// 获取优惠券列表
const getCouponList = async () => {
try {
// const apiStatus = tabToStatusMap[currentTab.value]
const res = await getUserCoupons('')
if (res.code === 200 && res.data) {
// 将后端数据转换为前端需要的格式
couponList.value = (res.data || []).map(item => {
// 判断优惠券类型:discount_coupon 折扣券,deduction_coupon 抵扣券
const isCashCoupon = item.couponType === 'deduction_coupon'
// 格式化使用条件
let condition = '无门槛'
if (item.usableCondition && item.usableCondition > 0) {
condition = `${item.usableCondition}可用`
}
// 格式化有效期
let validity = ''
if (currentTab.value === 'used') {
// 已使用显示使用时间
validity = item.couponStartTime ? `使用时间 ${item.couponStartTime.split(' ')[0]}` : ''
} else if (item.couponEndTime) {
validity = ` ${item.couponEndTime.split(' ')[0]} 过期`
}
return {
id: item.id,
name: item.couponName || '优惠券',
type: isCashCoupon ? 'cash' : 'discount',
value: item.deductAmount ? parseFloat(item.deductAmount) : 0,
discount: item.discountRate ? parseFloat(item.discountRate) * 10 : null,
condition: condition,
validity: validity,
status: currentTab.value,
positionName: item.positionName
}
})
} else {
couponList.value = []
}
} catch (error) {
console.error('获取优惠券列表失败:', error)
couponList.value = []
uni.showToast({
title: t('myCoupon.getListFailed'),
icon: 'none'
})
}
}
// 切换 Tab
const switchTab = (tab) => {
currentTab.value = tab
getCouponList()
}
// 获取优惠券样式类名
const getCouponClass = (status) => {
return status === 'available' ? '' : 'disabled'
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'used': t('myCoupon.usedStatus'),
'expired': t('myCoupon.expiredStatus')
}
return statusMap[status] || ''
}
// 获取空状态文本
const getEmptyText = () => {
const textMap = {
'available': t('myCoupon.noAvailableCoupons'),
'used': t('myCoupon.noUsedCoupons'),
'expired': t('myCoupon.noExpiredCoupons')
}
return textMap[currentTab.value] || ''
}
// 使用优惠券
const useCoupon = async (coupon) => {
try {
const scanResult = await new Promise((resolve, reject) => {
uni.scanCode({
success: resolve,
fail: reject
})
})
console.log('扫码结果:', scanResult);
let deviceNo;
// 兼容不同平台的扫码结果
if (scanResult.scanType === 'QR_CODE' || scanResult.scanType === 'qrCode') {
deviceNo = getQueryString(scanResult.result, 'deviceNo')
} else if (scanResult.path) {
deviceNo = getQueryString(scanResult.path, 'deviceNo')
} else {
deviceNo = scanResult.result
}
if (!deviceNo) {
uni.showToast({
title: $t('myCoupon.getListFailed'),
title: t('home.invalidQRCode'),
icon: 'none'
})
return
}
uni.showLoading({
title: t('common.getting')
})
// 检查是否有使用中的订单
const inUseRes = await getInUseOrder()
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
uni.hideLoading()
const inUseOrder = inUseRes.data
uni.reLaunch({
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
})
return
}
// 检查是否有待支付订单
const orderRes = await getUnpaidOrder()
if (orderRes && orderRes.code === 200 && orderRes.data) {
uni.hideLoading()
const unpaidOrder = orderRes.data
uni.navigateTo({
url: `/pages/order/payment?orderId=${unpaidOrder.orderId}`
})
return
}
// 获取设备信息并跳转详情
const deviceInfoRes = await getDeviceInfo(deviceNo)
uni.hideLoading()
if (deviceInfoRes.code === 200 && deviceInfoRes.data && deviceInfoRes.data.device) {
const deviceInfo = deviceInfoRes.data.device
let url = `/pages/device/detail?deviceNo=${deviceNo}`
if (deviceInfo.feeConfig) {
url += `&feeConfig=${encodeURIComponent(deviceInfo.feeConfig)}`
}
uni.navigateTo({
url
})
} else {
uni.navigateTo({
url: `/pages/device/detail?deviceNo=${deviceNo}`
})
}
} catch (error) {
console.error('扫码处理失败:', error)
if (error && error.errMsg !== 'scanCode:fail cancel') {
uni.showToast({
title: t('home.scanFailed'),
icon: 'none'
})
}
}
}
// 切换 Tab
const switchTab = (tab) => {
currentTab.value = tab
getCouponList()
}
// 获取优惠券样式类名
const getCouponClass = (status) => {
return status === 'available' ? '' : 'disabled'
}
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
'used': $t('myCoupon.usedStatus'),
'expired': $t('myCoupon.expiredStatus')
}
return statusMap[status] || ''
}
// 获取空状态文本
const getEmptyText = () => {
const textMap = {
'available': $t('myCoupon.noAvailableCoupons'),
'used': $t('myCoupon.noUsedCoupons'),
'expired': $t('myCoupon.noExpiredCoupons')
}
return textMap[currentTab.value] || ''
}
// 使用优惠券
const useCoupon = (coupon) => {
// TODO: 使用优惠券逻辑
uni.navigateTo({
url: '/pages/index/index'
})
}
// 去购买
const goToBuy = () => {
uni.navigateTo({
url: '/pages/purchase/index?tab=coupon'
})
}
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('user.myCoupons')
})
getCouponList()
// 去购买
const goToBuy = () => {
uni.navigateTo({
url: '/pages/purchase/index?tab=coupon'
})
}
onMounted(() => {
uni.setNavigationBarTitle({
title: t('user.myCoupons')
})
getCouponList()
})
</script>
<style lang="scss" scoped>
.my-coupon-page {
min-height: 100vh;
background-color: #f5f5f5;
}
// 优惠券样式变量封装
$coupon-theme-color: #A16300;
$coupon-divider-color: #B8741A;
$coupon-bg-faded: #f5f5f5;
$coupon-active-bg-start: #FFF4E6;
$coupon-active-bg-end: #FFE8CC;
$coupon-divider-left: 65%;
$coupon-circle-radius: 16rpx;
/* Tab 切换 */
.tab-container {
position: sticky;
top: 0;
display: flex;
background-color: #ffffff;
z-index: 999;
padding: 20rpx 0;
}
.my-coupon-page {
min-height: 100vh;
background-color: #f5f5f5;
}
.tab-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
position: relative;
/* Tab 切换 */
.tab-container {
position: sticky;
top: 0;
display: flex;
background-color: #ffffff;
z-index: 999;
padding: 20rpx 0;
}
.tab-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
.tab-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
position: relative;
&.active {
.tab-text {
color: #333;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 6rpx;
background-color: #FFA928;
border-radius: 3rpx;
}
}
}
.coupon-list {
padding: 20rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.coupon-item-wrapper {
position: relative;
}
.coupon-item {
background: linear-gradient(135deg, #FFF4E6 0%, #FFE8CC 100%);
border-radius: 20rpx;
padding: 40rpx 30rpx;
display: flex;
align-items: stretch;
position: relative;
overflow: visible;
min-height: 180rpx;
box-sizing: border-box;
border: 2rpx solid transparent;
transition: all 0.3s;
&.selected {
border-color: #B8741A;
box-shadow: 0 4rpx 20rpx rgba(184, 116, 26, 0.2);
.coupon-circle-left,
.coupon-circle-right {
background-color: #FFF4E6;
border: 2rpx solid #B8741A;
}
}
&.disabled {
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
opacity: 0.6;
.coupon-value,
.coupon-condition,
.coupon-name {
color: #999;
}
.coupon-circle-left,
.coupon-circle-right {
background-color: #f5f5f5;
}
}
}
/* 左侧圆形缺口 */
.coupon-circle-left {
position: absolute;
left: -16rpx;
top: 50%;
transform: translateY(-50%);
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background-color: #f5f5f5;
z-index: 10;
}
/* 右侧圆形缺口 */
.coupon-circle-right {
position: absolute;
right: -16rpx;
top: 50%;
transform: translateY(-50%);
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background-color: #f5f5f5;
z-index: 10;
}
.coupon-left {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.coupon-value {
font-size: 48rpx;
font-weight: 700;
color: #B8741A;
}
.coupon-condition {
font-size: 24rpx;
color: #666;
}
.coupon-validity-left {
font-size: 22rpx;
color: #999;
margin-top: 8rpx;
}
.coupon-divider {
width: 2rpx;
height: 100%;
position: absolute;
left: 65%;
transform: translateX(-50%);
top: 0;
bottom: 0;
// min-height: 160rpx;
align-self: stretch;
background: repeating-linear-gradient(to bottom,
#B8741A 0rpx,
#B8741A 8rpx,
transparent 8rpx,
transparent 16rpx);
margin: 0 30rpx;
}
.coupon-right {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
align-items: flex-end;
}
.coupon-name {
.tab-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
&.active {
.tab-text {
color: #333;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 6rpx;
background-color: #FFA928;
border-radius: 3rpx;
}
}
}
.coupon-list {
padding: 20rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.coupon-item-wrapper {
position: relative;
}
.coupon-item {
background: #FFF4E3;
border-radius: 20rpx;
padding: 40rpx 30rpx;
display: flex;
align-items: stretch;
position: relative;
overflow: visible;
min-height: 180rpx;
box-sizing: border-box;
border: 2rpx solid transparent;
transition: all 0.3s;
&.selected {
border-color: $coupon-divider-color;
box-shadow: 0 4rpx 20rpx rgba(184, 116, 26, 0.2);
.coupon-circle-top,
.coupon-circle-bottom {
background-color: $coupon-active-bg-start;
border: 2rpx solid $coupon-divider-color;
}
}
&.disabled {
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
opacity: 0.6;
.coupon-value,
.coupon-condition,
.coupon-name {
color: #999;
}
.coupon-circle-top,
.coupon-circle-bottom {
background-color: #f5f5f5;
}
}
}
/* 虚线顶部圆形缺口 */
.coupon-circle-top {
position: absolute;
left: $coupon-divider-left+4%;
top: -$coupon-circle-radius;
transform: translateX(-50%);
width: $coupon-circle-radius * 2;
height: $coupon-circle-radius * 2;
border-radius: 50%;
background-color: $coupon-bg-faded;
z-index: 10;
}
/* 虚线底部圆形缺口 */
.coupon-circle-bottom {
position: absolute;
left: $coupon-divider-left+4%;
bottom: -$coupon-circle-radius;
transform: translateX(-50%);
width: $coupon-circle-radius * 2;
height: $coupon-circle-radius * 2;
border-radius: 50%;
background-color: $coupon-bg-faded;
z-index: 10;
}
.coupon-left {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
gap: 8rpx;
}
.coupon-value {
display: flex;
align-items: flex-end; // 单位在脚
color: $coupon-theme-color;
line-height: 1;
width:120rpx;
}
.coupon-amount {
font-size: 56rpx; // 值要大
font-weight: 700;
}
.coupon-unit {
font-size: 24rpx; // 单位小
font-weight: 500;
margin-bottom: 6rpx; // 微调单位垂直位置,使其更贴合“脚”
margin-left: 4rpx;
margin-right: 4rpx;
}
.coupon-condition {
font-size: 24rpx;
color: #000;
font-weight: 600;
}
.coupon-validity-left {
font-size: 22rpx;
color: #999;
margin-top: 8rpx;
}
.coupon-divider {
width: 2rpx;
height: 100%;
position: absolute;
left: $coupon-divider-left;
transform: translateX(-50%);
top: 0;
bottom: 0;
align-self: stretch;
background: repeating-linear-gradient(to bottom,
$coupon-divider-color 0rpx,
$coupon-divider-color 8rpx,
transparent 8rpx,
transparent 16rpx);
margin: 0 30rpx;
}
.coupon-right {
// flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
align-items: center;
margin: auto 0;
width: 160rpx;
// align-content: center;
// transform: translateY(50%);
}
.coupon-name {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.coupon-validity {
font-size: 22rpx;
color: #999;
}
.use-btn {
// margin-top: 10rpx;
// padding: 12rpx 28rpx;
// background-color: #B8741A;
// border-radius: 40rpx;
.use-text {
font-size: 28rpx;
color: #A16300;
font-weight: 600;
color: #333;
}
}
.coupon-status {
margin-top: 10rpx;
font-size: 24rpx;
color: #999;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.5;
}
.coupon-validity {
font-size: 22rpx;
.empty-text {
font-size: 28rpx;
color: #999;
margin-bottom: 40rpx;
}
.use-btn {
margin-top: 10rpx;
padding: 12rpx 28rpx;
.buy-btn {
padding: 20rpx 60rpx;
background-color: #B8741A;
border-radius: 40rpx;
border-radius: 48rpx;
.use-text {
font-size: 24rpx;
.buy-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
.coupon-status {
margin-top: 10rpx;
font-size: 24rpx;
color: #999;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-bottom: 40rpx;
}
.buy-btn {
padding: 20rpx 60rpx;
background-color: #B8741A;
border-radius: 48rpx;
.buy-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
}
}
</style>
+35 -84
View File
@@ -155,13 +155,12 @@
import {
uploadUserAvatar
} from '../../config/api/user.js'
import {
URL
} from '../../config/url.js'
import { getCurrentAdvertisement } from '@/config/api/system.js'
import { getInUseOrder } from '@/config/api/order.js'
import { useI18n } from '@/utils/i18n.js'
// 设置页执行退出登录,此页不再直接调用
const { t: $t } = useI18n()
const { t } = useI18n()
// 响应式状态
const userInfo = ref({});
@@ -175,48 +174,25 @@ import {
const bannerImages = ref([]) // 广告图片列表
const bannerImageList = ref([]) // 完整的广告配置列表(包含链接信息)
// 将语言代码转换为后端接受的格式
const convertLanguageCode = (lang) => {
// zh-CN -> zh_CN (转换下划线)
// en-US -> en_US (转换下划线)
return lang.replace(/-/g, '_')
}
// 获取广告图片
const getBannerImages = async () => {
try {
// 获取当前语言设置
const currentLang = uni.getStorageSync('language') || 'zh-CN'
const languageCode = convertLanguageCode(currentLang)
console.log('加载个人中心广告,语言:', currentLang, '转换后:', languageCode)
// 调用接口获取广告内容
const res = await uni.request({
url: `${URL}/device/advertisementConfig/current`,
method: 'GET',
header: {
'Content-Language': languageCode
},
data: {
appPlatform: 'wechat', // 微信平台
appType: 'user' // 用户端
}
const res = await getCurrentAdvertisement({
appPlatform: 'wechat', // 微信平台
appType: 'user' // 用户端
})
console.log('个人中心广告响应:', res)
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
if (res && res.code === 200 && res.data) {
// 使用 imageList 字段(包含图片和链接信息)
const imageList = res.data.data.imageList || []
const imageList = res.data.imageList || []
if (imageList.length > 0) {
bannerImageList.value = imageList
// 提取图片URL用于展示
bannerImages.value = imageList.map(item => item.imageUrl)
console.log('个人中心广告加载成功,图片数量:', imageList.length)
}
} else {
console.warn('获取个人中心广告失败:', res.data?.msg || '未知错误')
console.warn('获取个人中心广告失败:', res?.msg || '未知错误')
}
} catch (error) {
console.error('获取个人中心广告失败:', error)
@@ -226,12 +202,10 @@ import {
// 处理广告点击
const handleBannerClick = (index) => {
if (!bannerImageList.value || !bannerImageList.value[index]) {
console.warn('未找到对应的广告配置')
return
}
const config = bannerImageList.value[index]
console.log('点击广告:', index, config)
// 根据链接类型进行跳转
if (config.linkType === 'miniapp' && config.appId) {
@@ -241,12 +215,11 @@ import {
appId: config.appId,
path: config.linkUrl || '',
success: () => {
console.log('跳转小程序成功')
},
fail: (err) => {
console.error('跳转小程序失败:', err)
uni.showToast({
title: '跳转失败',
title: t('common.loadFailed'),
icon: 'none'
})
}
@@ -254,7 +227,7 @@ import {
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: '请在微信小程序中使用',
title: t('auth.pleaseUseInWechat'),
icon: 'none'
})
// #endif
@@ -268,15 +241,13 @@ import {
uni.navigateTo({
url: config.linkUrl
})
} else {
console.log('无有效的跳转配置')
}
}
// 页面加载时初始化
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('user.personalCenter')
title: t('user.personalCenter')
})
getInfo();
initVersion();
@@ -293,7 +264,6 @@ import {
const getInfo = async () => {
try {
const res = await getUserInfo();
console.log('User info response:', res);
if (res.code == 401 || res.code == 40101) {
redirectToLogin()
@@ -317,9 +287,8 @@ import {
deposit.value = res.data.balanceAmount || '0.00';
}
} catch (error) {
console.error('获取用户信息失败:', error);
uni.showToast({
title: $t('user.getUserInfoFailed'),
title: t('user.getUserInfoFailed'),
icon: 'none'
});
}
@@ -374,43 +343,29 @@ import {
const handleQuickReturn = async () => {
try {
uni.showLoading({
title: $t('common.loading')
title: t('common.loading')
});
// 获取使用中的订单
const res = await uni.request({
url: `${URL}/app/order/inUse`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
});
const res = await getInUseOrder();
uni.hideLoading();
if (res.statusCode === 401 || res.data?.code === 401 || res.data?.code === 40101) {
redirectToLogin();
return;
}
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
const inUseOrder = res.data.data;
if (res && res.code === 200 && res.data) {
const inUseOrder = res.data;
// 跳转到统一订单详情页面
uni.navigateTo({
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${inUseOrder.deviceNo}`
});
} else {
uni.showToast({
title: $t('order.noOrder'),
title: t('order.noOrder'),
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('获取使用中订单失败:', error);
uni.showToast({
title: $t('order.getOrderFailed'),
title: t('order.getOrderFailed'),
icon: 'none'
});
}
@@ -433,7 +388,7 @@ import {
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: $t('auth.pleaseUseInWechat'),
title: t('auth.pleaseUseInWechat'),
icon: 'none'
})
// #endif
@@ -450,13 +405,13 @@ import {
const avatarLocalPath = e?.detail?.avatarUrl
if (!avatarLocalPath) {
uni.showToast({
title: '未选择头像',
title: t('user.noAvatar'),
icon: 'none'
})
return
}
uni.showLoading({
title: $t('common.uploading'),
title: t('common.uploading'),
mask: true
})
const uploadRes = await uploadUserAvatar(avatarLocalPath)
@@ -469,14 +424,13 @@ import {
uni.setStorageSync('userInfo', userInfo.value)
}
uni.showToast({
title: '头像已更新',
title: t('user.avatarUpdated'),
icon: 'success'
})
await getInfo()
} catch (err) {
console.error('选择/上传头像失败:', err)
uni.showToast({
title: '头像更新失败',
title: t('user.avatarUploadFailed'),
icon: 'none'
})
} finally {
@@ -494,14 +448,12 @@ import {
// 弹窗打开事件处理
const onPopupOpen = () => {
console.log('授权弹窗已打开');
isPopupVisible.value = true;
// 这里可以添加弹窗打开后的逻辑
};
// 弹窗关闭事件处理
const onPopupClose = () => {
console.log('授权弹窗已关闭');
isPopupVisible.value = false;
// 这里可以添加弹窗关闭后的逻辑
};
@@ -510,7 +462,7 @@ import {
const getUserProfile = () => {
// #ifdef MP-WEIXIN
uni.showLoading({
title: $t('common.getting'),
title: t('common.getting'),
mask: true
});
@@ -524,7 +476,7 @@ import {
fail: (err) => {
console.error('获取用户信息失败:', err);
uni.showToast({
title: '获取用户信息失败',
title: t('user.getUserInfoFailed'),
icon: 'none'
});
},
@@ -537,7 +489,7 @@ import {
// #ifndef MP-WEIXIN
uni.showToast({
title: $t('auth.pleaseUseInWechat'),
title: t('auth.pleaseUseInWechat'),
icon: 'none'
});
closeAuthPopup();
@@ -566,7 +518,7 @@ import {
// });
uni.showToast({
title: $t('user.updateSuccess'),
title: t('user.updateSuccess'),
icon: 'success'
});
@@ -575,7 +527,7 @@ import {
} catch (error) {
console.error('更新用户信息失败:', error);
uni.showToast({
title: $t('user.updateFailed'),
title: t('user.updateFailed'),
icon: 'none'
});
}
@@ -587,7 +539,7 @@ import {
const avatarUrl = wxUserInfo?.avatarUrl
if (!avatarUrl) {
uni.showToast({
title: '未获取到头像地址',
title: t('user.noAvatarUrl'),
icon: 'none'
})
return
@@ -601,7 +553,7 @@ import {
resolve(res.tempFilePath)
return
}
reject(new Error('头像下载失败'))
reject(new Error(t('user.avatarDownloadFailed')))
},
fail: reject
})
@@ -618,14 +570,13 @@ import {
uni.setStorageSync('userInfo', userInfo.value)
}
uni.showToast({
title: '头像已更新',
title: t('user.avatarUpdated'),
icon: 'success'
})
await getInfo()
} catch (error) {
console.error('头像上传失败:', error)
uni.showToast({
title: '头像上传失败',
title: t('user.avatarUploadFailed'),
icon: 'none'
})
} finally {
@@ -644,7 +595,7 @@ import {
// 关于我们
const handleAboutUs = () => {
uni.showToast({
title: $t('help.functionDeveloping'),
title: t('help.functionDeveloping'),
icon: 'none'
});
};
@@ -652,7 +603,7 @@ import {
// 隐私政策
const handlePrivacyPolicy = () => {
uni.showToast({
title: $t('help.functionDeveloping'),
title: t('help.functionDeveloping'),
icon: 'none'
});
};
+154 -87
View File
@@ -4,7 +4,13 @@
<view class="page-header">
<view class="header-top">
<view class="header-left">
<view class="header-title">{{ getOrderStatusText() }}</view>
<view class="header-title-row">
<text class="header-title">{{ getOrderStatusText() }}</text>
<!-- 优惠券/会员卡可用提示标签 -->
<view v-if="orderInfo.orderStatus === 'in_used' && canUsePromotionTag" class="promotion-tag">
{{ $t('order.canUsePromotion') }}
</view>
</view>
<view class="header-desc">{{ getStatusDesc() }}</view>
</view>
<view class="header-right" v-if="orderInfo.orderStatus === 'in_used'">
@@ -78,6 +84,14 @@
<view class="rent-label">{{ $t('order.paymentMethod') }}</view>
<view class="rent-value">{{ getPayWayText() }}</view>
</view>
<!-- 优惠信息显示 - 订单完成且使用了优惠 -->
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.discountTypeName">
<view class="rent-label">{{ $t('order.usedPromotion') }}</view>
<view class="rent-value promotion-value">
<image src="/static/promotion-icon.png" class="promotion-icon" mode="aspectFit"></image>
{{ orderInfo.discountTypeName }}
</view>
</view>
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.endTime">
<view class="rent-label">{{ $t('order.returnTime') }}</view>
<view class="rent-value">{{ orderInfo.endTime }}</view>
@@ -94,7 +108,7 @@
</view>
<view class="">
<view class="" style="font-size: 24rpx;text-align: center;">
产品归还入仓后订单仍未结束请前往<text @click="contactService" style="color:#07c160 ;">客服中心</text>联系工作人员
{{ $t('order.returnProblemTip') }}<text @click="contactService" style="color:#07c160 ;">{{ $t('user.customerService') }}</text>{{ $t('order.contactStaff') }}
</view>
</view>
@@ -209,8 +223,12 @@
cancelOrder,
reportDeviceNoEject,
convertToOwned,
closeWithMaxFee
closeWithMaxFee,
getInUseOrder
} from '@/config/api/order.js'
import {
withdrawDeposit
} from '@/config/api/user.js'
import {
addUserFeedback
} from '@/config/api/feedback.js'
@@ -222,7 +240,7 @@
} from "@/config/url.js"
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
const instance = getCurrentInstance()
const $orderMonitor = instance?.proxy?.$orderMonitor || null
@@ -251,7 +269,12 @@
isSupportExpressReturn: 'yes',
freeRentTime: '',
unitPrice: '',
orderType: ''
orderType: '',
canUseMember: false,
canUseCoupon: false,
userMemberCardId: '',
userPurchaseId: '',
discountTypeName: ''
})
const timer = ref(null)
const statusCheckTimer = ref(null)
@@ -264,8 +287,28 @@
const countdownRemaining = ref(0)
const showExpressAction = ref(false)
const countdownTimer = ref(null)
const feeRuleText = ref('5.0元/60分钟 前15分钟内归还免费 不足60分钟按60分钟计费 封顶99元 持续计费至99元视为买断')
const feeRuleText = ref('')
const convertToOwnPopup = ref(null)
// 计算属性:是否显示优惠券/会员卡可用提示
const canUsePromotionTag = computed(() => {
return orderInfo.value.canUseMember === true || orderInfo.value.canUseCoupon === true
})
// 计算属性:是否使用了优惠
const hasUsedPromotion = computed(() => {
return !!(orderInfo.value.userMemberCardId || orderInfo.value.userPurchaseId)
})
// 获取已使用优惠的文本
const getUsedPromotionText = () => {
if (orderInfo.value.userMemberCardId) {
return t('user.myCards')
} else if (orderInfo.value.userPurchaseId) {
return t('user.myCoupons')
}
return '-'
}
// 判断订单是否已完成
const isOrderCompleted = () => {
return orderInfo.value.orderStatus === 'used_done' ||
@@ -275,27 +318,27 @@
// 获取订单状态文字
const getOrderStatusText = () => {
const statusMap = {
'waiting_for_payment': $t('order.waitingForPayment'),
'payment_in_progress': $t('order.paymentInProgress'),
'payment_successful': $t('order.paymentSuccess'),
'in_used': $t('order.inUse'),
'payment_failed': $t('order.paymentFailed'),
'order_cancelled': $t('order.cancelled'),
'used_done': $t('order.finished'),
'used_down': $t('order.finished')
'waiting_for_payment': t('order.waitingForPayment'),
'payment_in_progress': t('order.paymentInProgress'),
'payment_successful': t('order.paymentSuccess'),
'in_used': t('order.inUse'),
'payment_failed': t('order.paymentFailed'),
'order_cancelled': t('order.cancelled'),
'used_done': t('order.finished'),
'used_down': t('order.finished')
}
return statusMap[orderInfo.value.orderStatus] || $t('order.orderDetail')
return statusMap[orderInfo.value.orderStatus] || t('order.orderDetail')
}
// 获取状态描述
const getStatusDesc = () => {
const descMap = {
'waiting_for_payment': $t('order.pleasePaySoon'),
'in_used': $t('order.pleaseReturnInTime'),
'used_done': $t('order.returnedThankYou'),
'used_down': $t('order.returnedThankYou'),
'order_cancelled': $t('order.orderCancelled'),
'payment_failed': $t('order.paymentFailedRetry')
'waiting_for_payment': t('order.pleasePaySoon'),
'in_used': t('order.pleaseReturnInTime'),
'used_done': t('order.returnedThankYou'),
'used_down': t('order.returnedThankYou'),
'order_cancelled': t('order.orderCancelled'),
'payment_failed': t('order.paymentFailedRetry')
}
return descMap[orderInfo.value.orderStatus] || ''
}
@@ -303,11 +346,11 @@
// 获取支付方式文本
const getPayWayText = () => {
const payWayMap = {
'wx_score_pay': $t('order.depositFree'),
'wx_member_pay': $t('order.memberOrder'),
'wx_pay': $t('order.depositPay')
'wx_score_pay': t('order.depositFree'),
'wx_member_pay': t('order.memberOrder'),
'wx_pay': t('order.depositPay')
}
return payWayMap[orderInfo.value.payWay] || $t('order.depositFree')
return payWayMap[orderInfo.value.payWay] || t('order.depositFree')
}
// 获取免费时长显示
@@ -321,13 +364,13 @@
if (!orderInfo.value.unitPrice || !orderInfo.value.orderType) return '-'
const orderTypeMap = {
'hours': $t('time.hours'),
'minutes': $t('time.minutes'),
'halfhours': $t('time.halfHours')
'hours': t('time.hours'),
'minutes': t('time.minutes'),
'halfhours': t('time.halfHours')
}
const orderTypeText = orderTypeMap[orderInfo.value.orderType] || orderInfo.value.orderType
return `${orderInfo.value.unitPrice}${$t('unit.yuan')}/${orderTypeText}`
return `${orderInfo.value.unitPrice}${t('unit.yuan')}/${orderTypeText}`
}
// 格式化倒计时(显示为 HH:MM:SS 格式)
@@ -378,21 +421,21 @@
if (totalMinutes >= 60) {
const hours = Math.floor(totalMinutes / 60)
const minutes = totalMinutes % 60
usedTime = minutes > 0 ? `${hours}小时${minutes}分钟` : `${hours}小时`
usedTime = minutes > 0 ? `${hours}${t('time.hour')}${minutes}${t('time.minute')}` : `${hours}${t('time.hour')}`
} else {
usedTime = `${totalMinutes}分钟`
usedTime = `${totalMinutes}${t('time.minute')}`
}
}
}
// 如果还是没有值,使用默认值
if (!usedTime) {
usedTime = '0分钟'
usedTime = `0${t('time.minute')}`
}
// 解析时长字符串,例如 "1小时5分钟" 或 "5分钟"
const hourMatch = usedTime.match(/(\d+)小时/)
const minuteMatch = usedTime.match(/(\d+)分钟/)
const hourMatch = usedTime.match(new RegExp(`(\\d+)${t('time.hour')}`))
const minuteMatch = usedTime.match(new RegExp(`(\\d+)${t('time.minute')}`))
let displayNumber = ''
let displayUnit = ''
@@ -400,19 +443,19 @@
if (hourMatch && minuteMatch) {
// 有小时也有分钟,如 "1小时5分钟"
displayNumber = `${hourMatch[1]}`
displayUnit = `小时${minuteMatch[1]}分钟`
displayUnit = `${t('time.hour')}${minuteMatch[1]}${t('time.minute')}`
} else if (hourMatch) {
// 只有小时,如 "1小时"
displayNumber = hourMatch[1]
displayUnit = '小时'
displayUnit = t('time.hour')
} else if (minuteMatch) {
// 只有分钟,如 "5分钟"
displayNumber = minuteMatch[1]
displayUnit = '分钟'
displayUnit = t('time.minute')
} else {
// 默认情况
displayNumber = '0'
displayUnit = '分钟'
displayUnit = t('time.minute')
}
return {
@@ -424,7 +467,7 @@
// 获取使用时长标签文本
const getUsedTimeLabel = () => {
// 使用中状态显示"已使用",已完成状态显示"使用时长"
return orderInfo.value.orderStatus === 'in_used' ? $t('order.used') : $t('order.duration')
return orderInfo.value.orderStatus === 'in_used' ? t('order.used') : t('order.duration')
}
// 获取订单费用(不含单位)
@@ -663,7 +706,7 @@
if (currentStatusChecks.value >= maxStatusChecks.value) {
clearStatusCheckTimer()
uni.showToast({
title: $t('order.pleaseRefreshManually'),
title: t('order.pleaseRefreshManually'),
icon: 'none',
duration: 3000
})
@@ -709,6 +752,13 @@
orderInfo.value.unitPrice = orderData.unitPrice || ''
orderInfo.value.orderType = orderData.orderType || ''
// 保存优惠券/会员卡相关信息
orderInfo.value.canUseMember = orderData.canUseMember === true
orderInfo.value.canUseCoupon = orderData.canUseCoupon === true
orderInfo.value.userMemberCardId = orderData.userMemberCardId || ''
orderInfo.value.userPurchaseId = orderData.userPurchaseId || ''
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
// 保存快递归还开始时间(小时为单位)
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
@@ -741,7 +791,7 @@
// 显示订单完成提示
if (orderInfo.value.orderStatus === 'used_done' || orderInfo.value.orderStatus === 'used_down') {
uni.showToast({
title: $t('order.orderCompleted'),
title: t('order.orderCompleted'),
icon: 'success'
})
}
@@ -763,7 +813,7 @@
try {
if (!orderInfo.value.orderId) {
throw new Error($t('order.orderIdRequired'))
throw new Error(t('order.orderIdRequired'))
}
isLoadingOrder.value = true
@@ -813,12 +863,12 @@
}
}
} else {
throw new Error(result.msg || $t('order.getOrderFailed'))
throw new Error(result.msg || t('order.getOrderFailed'))
}
} catch (error) {
console.error('获取订单详情错误:', error)
uni.showToast({
title: error.message || $t('order.getOrderFailed'),
title: error.message || t('order.getOrderFailed'),
icon: 'none'
})
setTimeout(() => {
@@ -834,22 +884,15 @@
const getOrderByDevice = async () => {
try {
if (!deviceId.value) {
throw new Error($t('device.deviceNoRequired'))
throw new Error(t('device.deviceNoRequired'))
}
const inUseRes = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/order/inUse`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
const inUseRes = await getInUseOrder()
console.log('通过设备号查询订单结果:', JSON.stringify(inUseRes))
if (inUseRes.statusCode === 200 && inUseRes.data.code === 200 && inUseRes.data.data) {
const inUseOrder = inUseRes.data.data
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
const inUseOrder = inUseRes.data
console.log('使用中的订单:', inUseOrder)
orderInfo.value.orderId = inUseOrder.orderId
@@ -859,12 +902,12 @@
getOrderDetails()
} else {
throw new Error($t('order.noOrderInUse'))
throw new Error(t('order.noOrderInUse'))
}
} catch (error) {
console.error('通过设备号查询订单失败:', error)
uni.showToast({
title: error.message || $t('order.getOrderFailed'),
title: error.message || t('order.getOrderFailed'),
icon: 'none'
})
setTimeout(() => {
@@ -885,13 +928,13 @@
// 取消订单
const handleCancelOrder = () => {
uni.showModal({
title: $t('order.confirmCancel'),
content: $t('order.confirmCancelContent'),
title: t('order.confirmCancel'),
content: t('order.confirmCancelContent'),
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: $t('common.processing')
title: t('common.processing')
})
const result = await cancelOrder({
orderId: orderInfo.value.orderId
@@ -899,17 +942,17 @@
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: $t('order.cancelSuccess'),
title: t('order.cancelSuccess'),
icon: 'success'
})
await getOrderDetails()
} else {
throw new Error(result.msg || $t('order.cancelFailed'))
throw new Error(result.msg || t('order.cancelFailed'))
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || $t('order.cancelFailed'),
title: error.message || t('order.cancelFailed'),
icon: 'none'
})
}
@@ -929,22 +972,14 @@
const handleWithdraw = async () => {
try {
uni.showLoading({
title: $t('common.processing')
title: t('common.processing')
})
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/withdraw/add/${orderInfo.value.orderNo}`,
method: 'GET',
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
const res = await withdrawDeposit(orderInfo.value.orderNo)
if (res.statusCode === 200 && res.data.code === 200) {
if (res && res.code === 200) {
uni.showToast({
title: $t('order.refundSuccess'),
title: t('order.refundSuccess'),
icon: 'success'
})
@@ -955,12 +990,12 @@
getOrderDetails()
}, 1500)
} else {
throw new Error(res.data.msg || $t('order.refundFailed'))
throw new Error(res?.msg || t('order.refundFailed'))
}
} catch (error) {
console.error('退款申请错误:', error)
console.error('退款申请失败:', error)
uni.showToast({
title: error.message || $t('order.refundFailed'),
title: error.message || t('order.refundFailed'),
icon: 'none'
})
} finally {
@@ -1002,14 +1037,14 @@
})
uni.showToast({
title: '订阅成功',
title: t('payment.subscriptionSuccess'),
icon: 'success',
duration: 2000
})
} catch (error) {
console.log('订阅消息异常', error)
uni.showToast({
title: '订阅失败,请稍后重试',
title: t('payment.subscriptionFailed'),
icon: 'none',
duration: 2000
})
@@ -1040,7 +1075,7 @@
try {
closeConvertToOwnPopup()
uni.showLoading({
title: $t('common.processing')
title: t('common.processing')
})
const result = await closeWithMaxFee(orderInfo.value.orderNo)
@@ -1048,7 +1083,7 @@
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: $t('order.convertToOwnWithMaxFeeSuccess'),
title: t('order.convertToOwnWithMaxFeeSuccess'),
icon: 'success',
duration: 2000
})
@@ -1058,12 +1093,12 @@
getOrderDetails()
}, 2000)
} else {
throw new Error(result.msg || $t('order.convertToOwnWithMaxFeeFailed'))
throw new Error(result.msg || t('order.convertToOwnWithMaxFeeFailed'))
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || $t('order.convertToOwnWithMaxFeeFailed'),
title: error.message || t('order.convertToOwnWithMaxFeeFailed'),
icon: 'none',
duration: 2000
})
@@ -1076,7 +1111,7 @@
// 设置页面标题
uni.setNavigationBarTitle({
title: $t('order.orderDetail')
title: t('order.orderDetail')
})
isPageActive.value = true
@@ -1101,7 +1136,7 @@
getOrderDetails()
} else {
uni.showToast({
title: $t('order.orderInfoMissing'),
title: t('order.orderInfoMissing'),
icon: 'none'
})
setTimeout(() => {
@@ -1161,12 +1196,18 @@
.header-left {
flex: 1;
}
.header-title-row {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 12rpx;
}
.header-title {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 12rpx;
}
.header-desc {
@@ -1210,6 +1251,18 @@
}
}
}
.promotion-tag {
padding: 6rpx 16rpx;
background: linear-gradient(135deg, #FFF4E6 0%, #FFE9CC 100%);
border-radius: 10rpx;
border: 1rpx solid #FFB84D;
font-size: 22rpx;
color: #FF8C00;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
// 订单信息卡片
.info-card {
@@ -1355,6 +1408,20 @@
color: #333;
text-align: right;
max-width: 400rpx;
&.promotion-value {
display: flex;
align-items: center;
justify-content: flex-end;
color: #FF8C00;
font-weight: 500;
.promotion-icon {
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
}
}
}
}
+35 -41
View File
@@ -46,7 +46,8 @@
getOrderList,
queryById,
getOrderByOrderNoScorePayStatus,
cancelOrder
cancelOrder,
createWxPayment
} from '../../config/api/order.js';
import {
confirmPaymentAndRent
@@ -59,7 +60,7 @@
} from '../../config/url.js';
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 初始化状态
const currentTab = ref(0);
@@ -68,62 +69,62 @@
// 订单状态映射
const orderStatusMap = reactive({
'0': {
get text() { return $t('order.waitingForPayment') },
get text() { return t('order.waitingForPayment') },
class: 'status-waiting'
},
'1': {
get text() { return $t('order.inUse') },
get text() { return t('order.inUse') },
class: 'status-using'
},
'2': {
get text() { return $t('order.finished') },
get text() { return t('order.finished') },
class: 'status-finished'
},
'3': {
get text() { return $t('order.cancelled') },
get text() { return t('order.cancelled') },
class: 'status-cancelled'
},
'waiting_for_payment': {
get text() { return $t('order.waitingForPayment') },
get text() { return t('order.waitingForPayment') },
class: 'status-waiting'
},
'in_used': {
get text() { return $t('order.inUse') },
get text() { return t('order.inUse') },
class: 'status-using'
},
'used_done': {
get text() { return $t('order.finished') },
get text() { return t('order.finished') },
class: 'status-finished'
},
'order_cancelled': {
get text() { return $t('order.cancelled') },
get text() { return t('order.cancelled') },
class: 'status-cancelled'
},
'express_return': {
get text() { return $t('express.title') },
get text() { return t('express.title') },
class: 'status-express-return'
}
});
// 订单状态标签
const orderStatusTabs = reactive([{
get text() { return $t('common.all') },
get text() { return t('common.all') },
status: []
},
{
get text() { return $t('order.waitingForPayment') },
get text() { return t('order.waitingForPayment') },
status: ['waiting_for_payment']
},
{
get text() { return $t('order.inUse') },
get text() { return t('order.inUse') },
status: ['in_used']
},
{
get text() { return $t('order.finished') },
get text() { return t('order.finished') },
status: ['used_done']
},
{
get text() { return $t('order.cancelled') },
get text() { return t('order.cancelled') },
status: ['order_cancelled']
}
]);
@@ -216,7 +217,7 @@
} catch (error) {
console.error('获取订单列表失败:', error);
uni.showToast({
title: $t('order.getOrderListFailed'),
title: t('order.getOrderListFailed'),
icon: 'none'
});
}
@@ -233,7 +234,7 @@
// 设置页面标题并监听订单完成事件
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('order.myOrders')
title: t('order.myOrders')
})
// 监听订单完成事件
@@ -251,14 +252,14 @@
const res = await getOrderByOrderNoScorePayStatus(order.orderNo);
if (res.code === 200) {
uni.showToast({
title: $t('order.syncSuccess'),
title: t('order.syncSuccess'),
icon: 'success'
});
await loadOrderList(orderStatusTabs[currentTab.value].status);
}
} catch (error) {
uni.showToast({
title: $t('order.syncFailed'),
title: t('order.syncFailed'),
icon: 'none'
});
}
@@ -285,28 +286,21 @@
const handlePayment = async (order) => {
try {
uni.showLoading({
title: $t('common.processing')
title: t('common.processing')
});
// 调用后端创建微信支付订单接口
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-payment/create/${order.orderNo}`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
});
const res = await createWxPayment(order.orderNo);
if (res.statusCode === 200 && res.data.code === 200) {
const payParams = res.data.data;
if (res && res.code === 200) {
const payParams = res.data;
// 调用微信支付
await uni.requestPayment({
...payParams,
success: async () => {
uni.showToast({
title: $t('payment.paymentSuccess'),
title: t('payment.paymentSuccess'),
icon: 'success'
});
@@ -322,18 +316,18 @@
},
fail: (err) => {
console.error('支付失败:', err);
throw new Error($t('payment.paymentFailedRetry'));
throw new Error(t('payment.paymentFailedRetry'));
}
});
} else {
throw new Error(res.data.msg || '创建支付订单失败');
throw new Error(res?.msg || '创建支付订单失败');
}
uni.hideLoading();
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || $t('payment.paymentFailed'),
title: error.message || t('payment.paymentFailed'),
icon: 'none'
});
}
@@ -343,12 +337,12 @@
const handleCancelOrder = async (order) => {
try {
uni.showModal({
title: $t('order.confirmCancel'),
content: $t('order.confirmCancelContent'),
title: t('order.confirmCancel'),
content: t('order.confirmCancelContent'),
success: async (res) => {
if (res.confirm) {
uni.showLoading({
title: $t('common.processing')
title: t('common.processing')
});
const result = await cancelOrder({
@@ -358,14 +352,14 @@
if (result) {
uni.hideLoading();
uni.showToast({
title: $t('order.cancelSuccess'),
title: t('order.cancelSuccess'),
icon: 'success'
});
// 刷新订单列表
await loadOrderList();
} else {
throw new Error(result.msg || $t('order.cancelFailed'));
throw new Error(result.msg || t('order.cancelFailed'));
}
}
}
@@ -373,7 +367,7 @@
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || $t('order.cancelFailed'),
title: error.message || t('order.cancelFailed'),
icon: 'none'
});
}
+196 -322
View File
@@ -59,345 +59,219 @@
</view>
</template>
<script>
import { queryById, updateOrderPackage } from '@/config/api/order.js'
<script setup>
import { ref, computed, reactive } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { queryById, createWxPayment } from '@/config/api/order.js'
import { getDeviceInfo } from '@/config/api/device.js'
import { updateUserBalance } from '@/config/api/user.js'
import {
URL
}from"@/config/url.js"
import { useI18n } from '@/utils/i18n.js'
export default {
data() {
return {
orderId: null,
deviceNo: null,
orderInfo: {},
packageInfo: {
time: '',
price: '0.00'
},
deviceInfo: null,
passedTotalAmount: null,
passedDepositAmount: null,
orderStatus: {
get text() { return this.$t('payment.waitingForPayment') },
get desc() { return this.$t('payment.pleasePayIn15Min') },
class: 'waiting'
}
}
},
computed: {
totalAmount() {
if (this.passedTotalAmount !== null) {
return parseFloat(this.passedTotalAmount).toFixed(2);
}
const deposit = parseFloat(this.orderInfo.deposit || this.passedDepositAmount || 99)
const packagePrice = parseFloat(this.packageInfo.price || 0)
return (deposit + packagePrice).toFixed(2)
},
// 计算每小时的价格
hourlyRate() {
const price = parseFloat(this.packageInfo.price || 0);
let time = parseFloat(this.packageInfo.time || 1);
// 如果时间单位不是小时(例如分钟),需要转换
if (this.packageInfo.time && this.packageInfo.time.includes('分钟')) {
time = time / 60; // 将分钟转换为小时
} else if (this.packageInfo.time && this.packageInfo.time.includes('次')) {
// 按次计费时,暂时显示单次价格
return price.toFixed(2);
}
// 避免除以零
if (time <= 0) time = 1;
// 计算每小时价格
return (price / time).toFixed(2);
}
},
onLoad(options) {
// 设置页面标题
uni.setNavigationBarTitle({
title: this.$t('payment.orderPayment')
const { t } = useI18n()
const orderId = ref(null)
const deviceNo = ref(null)
const orderInfo = ref({})
const packageInfo = ref({
time: '',
price: '0.00'
})
const deviceInfo = ref(null)
const passedTotalAmount = ref(null)
const passedDepositAmount = ref(null)
const orderStatus = reactive({
get text() { return t('payment.waitingForPayment') },
get desc() { return t('payment.pleasePayIn15Min') },
class: 'waiting'
})
const totalAmount = computed(() => {
if (passedTotalAmount.value !== null) {
return parseFloat(passedTotalAmount.value).toFixed(2);
}
const deposit = parseFloat(orderInfo.value.deposit || passedDepositAmount.value || 99)
const packagePrice = parseFloat(packageInfo.value.price || 0)
return (deposit + packagePrice).toFixed(2)
})
// 加载订单信息
const loadOrderInfo = async () => {
try {
uni.showLoading({
title: t('common.loading')
})
if (options && options.orderId) {
this.orderId = options.orderId
const res = await queryById(orderId.value)
if (res.code === 200 && res.data) {
const orderData = res.data
if (options.totalAmount) {
this.passedTotalAmount = options.depositAmount;
// 处理创建时间
let formattedTime;
try {
if (orderData.createTime) {
formattedTime = formatTime(new Date(orderData.createTime));
} else {
formattedTime = formatTime(new Date());
}
} catch (e) {
console.error('时间格式化错误:', e);
formattedTime = formatTime(new Date());
}
if (options.depositAmount) {
this.passedDepositAmount = options.depositAmount;
orderInfo.value = {
orderNo: orderData.orderNo || orderData.orderId,
deviceNo: orderData.deviceNo,
createTime: formattedTime,
phone: orderData.phone,
deposit: passedDepositAmount.value || orderData.depositAmount || '99.00',
}
// 如果URL中包含了feeConfig参数,保存它
if (options.feeConfig) {
try {
console.log('从URL获取到feeConfig:', options.feeConfig)
const feeConfigStr = decodeURIComponent(options.feeConfig)
// 创建一个临时的deviceInfo对象保存feeConfig
this.deviceInfo = { feeConfig: feeConfigStr }
} catch (e) {
console.error('解析URL中的feeConfig失败:', e)
if (orderData.packageTime && orderData.packagePrice) {
const timeInHours = (parseFloat(orderData.packageTime) / 60).toFixed(1);
packageInfo.value = {
time: timeInHours.toString(),
price: orderData.packagePrice.toString()
}
}
this.loadOrderInfo()
} else {
uni.showToast({
title: this.$t('order.orderNotExist'),
icon: 'none'
})
setTimeout(() => {
uni.redirectTo({
url: '/pages/index/index'
})
}, 1500)
deviceNo.value = orderData.deviceNo;
await loadDeviceInfo();
} else {
throw new Error(t('order.getOrderFailed'))
}
},
methods: {
// 加载订单信息
async loadOrderInfo() {
try {
uni.showLoading({
title: this.$t('common.loading')
})
const res = await queryById(this.orderId)
if (res.code === 200 && res.data) {
const orderData = res.data
// 处理创建时间,确保显示的是格式化后的时间
let formattedTime;
try {
// 如果orderData.createTime存在并且是有效的日期字符串/时间戳,则格式化它
if (orderData.createTime) {
formattedTime = this.formatTime(new Date(orderData.createTime));
} else {
// 如果createTime不存在,使用当前时间作为创建时间
formattedTime = this.formatTime(new Date());
}
} catch (e) {
console.error('时间格式化错误:', e);
formattedTime = this.formatTime(new Date());
}
this.orderInfo = {
orderNo: orderData.orderNo || orderData.orderId,
deviceNo: orderData.deviceNo,
createTime: formattedTime,
phone: orderData.phone,
deposit: this.passedDepositAmount || orderData.depositAmount || '99.00',
}
// 直接从订单数据中获取套餐信息
if (orderData.packageTime && orderData.packagePrice) {
// 将分钟转换为小时
const timeInHours = (parseFloat(orderData.packageTime) / 60).toFixed(1);
this.packageInfo = {
time: timeInHours.toString(),
price: orderData.packagePrice.toString()
}
}
// 获取设备信息(但不再用于设置套餐信息)
this.deviceNo = orderData.deviceNo;
await this.loadDeviceInfo();
} else {
throw new Error('获取订单信息失败')
}
uni.hideLoading()
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || '获取订单信息失败',
icon: 'none'
})
}
},
// 加载设备信息
async loadDeviceInfo() {
if (!this.deviceNo) return;
try {
const res = await getDeviceInfo(this.deviceNo);
if (res.code === 200 && res.data) {
this.deviceInfo = res.data.device;
// 设置存款金额
if (this.deviceInfo && this.deviceInfo.depositAmount) {
this.orderInfo.deposit = this.deviceInfo.depositAmount;
}
}
} catch (error) {
console.error('获取设备信息失败:', error);
}
},
// 处理支付
async handlePayment() {
try {
uni.showLoading({
title: this.$t('common.processing')
})
// 调用后端创建微信支付订单接口
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-payment/create/${this.orderInfo.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: async () => {
uni.showToast({
title: this.$t('payment.paymentSuccess'),
icon: 'success'
});
// 更新用户余额
try {
await updateUserBalance(this.orderId);
} catch (error) {
console.warn('更新用户余额失败:', error);
}
// 支付成功后直接跳转到订单页面,不再轮询
setTimeout(() => {
uni.redirectTo({
url: `/pages/order/index?orderId=${this.orderId}`
});
}, 1500);
},
fail: (err) => {
console.error('支付失败:', err)
throw new Error(this.$t('payment.paymentFailedRetry'))
}
})
} else {
throw new Error(res.data.msg || '创建支付订单失败')
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || '支付失败',
icon: 'none'
})
}
},
// 发送租借指令
async sendRentCommand() {
try {
uni.showLoading({
title: this.$t('common.processing')
})
// 调用发送租借指令的接口
const res = await this.sendRentRequest()
if (res.code === 200) {
uni.hideLoading()
uni.showToast({
title: this.$t('device.rentSuccess'),
icon: 'success'
})
// 跳转到订单列表页面
setTimeout(() => {
uni.redirectTo({
url: `/pages/order/index?orderId=${this.orderId}`
})
}, 1500)
} else {
throw new Error(res.msg || this.$t('device.rentFailed'))
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || this.$t('device.rentFailed'),
icon: 'none'
})
}
},
// 发送租借请求
sendRentRequest() {
return new Promise((resolve, reject) => {
uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/device/sendRentCommand`,
method: 'POST',
data: {
orderId: this.orderId
},
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
},
success(res) {
if (res.statusCode === 200) {
resolve(res.data)
} else {
reject(new Error('请求失败'))
}
},
fail(err) {
reject(err)
}
})
})
},
// 格式化时间
formatTime(date) {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hour = date.getHours().toString().padStart(2, '0')
const minute = date.getMinutes().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
},
// 检查订单状态(单次查询,不轮询)
async checkOrderStatus() {
try {
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-payment/status/${this.orderInfo.orderNo}`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
});
if (res.statusCode === 200 && res.data.code === 200) {
return res.data.data;
} else {
throw new Error('查询订单状态失败');
}
} catch (error) {
console.error('查询订单状态错误:', error);
return null;
}
},
} catch (error) {
console.error('获取订单信息失败:', error)
uni.showToast({
title: error.message || t('order.getOrderFailed'),
icon: 'none'
})
} finally {
uni.hideLoading()
}
}
// 加载设备信息
const loadDeviceInfo = async () => {
if (!deviceNo.value) return;
try {
const res = await getDeviceInfo(deviceNo.value);
if (res.code === 200 && res.data) {
deviceInfo.value = res.data.device;
if (deviceInfo.value && deviceInfo.value.depositAmount) {
orderInfo.value.deposit = deviceInfo.value.depositAmount;
}
}
} catch (error) {
console.error('获取设备信息失败:', error);
}
}
// 处理支付
const handlePayment = async () => {
try {
uni.showLoading({
title: t('common.processing')
})
const res = await createWxPayment(orderInfo.value.orderNo)
if (res && res.code === 200) {
const payParams = res.data
await uni.requestPayment({
...payParams,
success: async () => {
uni.showToast({
title: t('payment.paymentSuccess'),
icon: 'success'
});
try {
await updateUserBalance(orderId.value);
} catch (error) {
console.warn('更新用户余额失败:', error);
}
setTimeout(() => {
uni.redirectTo({
url: `/pages/order/index?orderId=${orderId.value}`
});
}, 1500);
},
fail: (err) => {
console.error('支付失败:', err)
uni.showToast({
title: t('payment.paymentFailedRetry'),
icon: 'none'
})
}
})
} else {
throw new Error(res?.msg || t('payment.createPayOrderFailed'))
}
} catch (error) {
console.error('支付失败:', error)
uni.showToast({
title: error.message || t('payment.paymentFailed'),
icon: 'none'
})
} finally {
uni.hideLoading()
}
}
// 格式化时间
const formatTime = (date) => {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hour = date.getHours().toString().padStart(2, '0')
const minute = date.getMinutes().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
}
onLoad((options) => {
uni.setNavigationBarTitle({
title: t('payment.orderPayment')
})
if (options && options.orderId) {
orderId.value = options.orderId
if (options.totalAmount) {
passedTotalAmount.value = options.totalAmount;
}
if (options.depositAmount) {
passedDepositAmount.value = options.depositAmount;
}
if (options.feeConfig) {
try {
const feeConfigStr = decodeURIComponent(options.feeConfig)
deviceInfo.value = { feeConfig: feeConfigStr }
} catch (e) {
console.error('解析URL中的feeConfig失败:', e)
}
}
loadOrderInfo()
} else {
uni.showToast({
title: t('order.orderNotExist'),
icon: 'none'
})
setTimeout(() => {
uni.redirectTo({
url: '/pages/index/index'
})
}, 1500)
}
})
</script>
<style lang="scss" scoped>
+4 -11
View File
@@ -85,6 +85,7 @@
<script>
import { queryById } from '@/config/api/order.js'
import { withdrawDeposit } from '@/config/api/user.js'
import {
URL
}from"@/config/url.js"
@@ -234,17 +235,9 @@ export default {
try {
uni.showLoading({ title: this.$t('common.processing') });
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/withdraw/add/${this.orderInfo.orderNo}`,
method: 'GET',
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
const res = await withdrawDeposit(this.orderInfo.orderNo)
if (res.statusCode === 200 && res.data.code === 200) {
if (res && res.code === 200) {
uni.showToast({
title: this.$t('order.refundSuccess'),
icon: 'success'
@@ -259,7 +252,7 @@ export default {
this.loadOrderInfo();
}, 1500);
} else {
throw new Error(res.data.msg || this.$t('order.refundFailed'));
throw new Error(res?.msg || this.$t('order.refundFailed'));
}
} catch (error) {
console.error('退款申请错误:', error);
+6 -6
View File
@@ -74,7 +74,7 @@ import {
} from '../../config/api/device.js'
import { useI18n } from '../../utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
const positionInfo = ref({})
const positionId = ref('')
@@ -112,7 +112,7 @@ const { t: $t } = useI18n()
const loadPositionDetail = async () => {
try {
uni.showLoading({
title: $t('common.loading')
title: t('common.loading')
})
// 获取用户位置用于查询附近设备
@@ -147,7 +147,7 @@ const { t: $t } = useI18n()
positionInfo.value = position
} else {
uni.showToast({
title: $t('location.notExist'),
title: t('location.notExist'),
icon: 'none'
})
}
@@ -155,7 +155,7 @@ const { t: $t } = useI18n()
} catch (e) {
console.error('加载设备详情失败:', e)
uni.showToast({
title: $t('common.loadFailed'),
title: t('common.loadFailed'),
icon: 'none'
})
} finally {
@@ -166,7 +166,7 @@ const { t: $t } = useI18n()
const navigateToPosition = () => {
if (!positionInfo.value.latitude || !positionInfo.value.longitude) {
uni.showToast({
title: $t('location.coordinateError'),
title: t('location.coordinateError'),
icon: 'none'
})
return
@@ -181,7 +181,7 @@ const { t: $t } = useI18n()
longitude < -180 || longitude > 180 ||
(latitude === 0 && longitude === 0)) {
uni.showToast({
title: $t('location.coordinateError'),
title: t('location.coordinateError'),
icon: 'none'
})
return
+913 -704
View File
File diff suppressed because it is too large Load Diff
+5 -11
View File
@@ -143,7 +143,8 @@
<script>
import {
queryById,
cancelOrder
cancelOrder,
getInUseOrder
} from '@/config/api/order.js'
import {
getSystemConfig
@@ -710,19 +711,12 @@
}
// 这里调用API查询该设备的使用中订单
const inUseRes = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/order/inUse`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
const inUseRes = await getInUseOrder()
console.log('通过设备号查询订单结果:', JSON.stringify(inUseRes))
if (inUseRes.statusCode === 200 && inUseRes.data.code === 200 && inUseRes.data.data) {
const inUseOrder = inUseRes.data.data
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
const inUseOrder = inUseRes.data
console.log('使用中的订单:', inUseOrder)
// 更新订单ID
+88 -77
View File
@@ -22,52 +22,52 @@
:class="{ available: isRentable(item), invalid: !isValidCoordinate(item.latitude, item.longitude) }"
v-for="(item, index) in filteredPositions" :key="item.positionId || index"
@click="goToPositionDetail(item)">
<view class="thumb">
<image v-if="item.deviceImg" :src="item.deviceImg" class="thumb-img" mode="aspectFill">
</image>
<image v-else src="/static/device-info.png" class="thumb-img" mode="aspectFit"></image>
</view>
<view class="info">
<view class="row top">
<view class="name">{{ item.name }}</view>
<!-- 第一行三列布局 -->
<view class="card-row-first">
<!-- 第一列缩略图 -->
<view class="thumb">
<image v-if="item.deviceImg" :src="item.deviceImg" class="thumb-img" mode="aspectFill">
</image>
<image v-else src="/static/device-info.png" class="thumb-img" mode="aspectFit"></image>
</view>
<!-- 第二列信息 -->
<view class="info">
<view class="row top">
<view class="name">{{ item.name }}</view>
</view>
<view class="row sub" v-if="item.location">
<text class="addr">{{ item.location }}</text>
</view>
<view class="row meta" v-if="item.workTime && item.workTime !== '0'">
<text class="time">{{ $t('location.businessHours') }}{{ item.workTime }}</text>
</view>
<view class="row meta" v-if="!isValidCoordinate(item.latitude, item.longitude)">
<text class="time" style="color: #ff6b6b;">{{ $t('location.coordinateError') }}</text>
</view>
</view>
<view class="row sub" v-if="item.location">
<text class="addr">{{ item.location }}</text>
</view>
<view class="row meta" v-if="item.workTime && item.workTime !== '0'">
<text class="time">{{ $t('location.businessHours') }}{{ item.workTime }}</text>
</view>
<view class="row meta" v-if="!isValidCoordinate(item.latitude, item.longitude)">
<text class="time" style="color: #ff6b6b;">{{ $t('location.coordinateError') }}</text>
</view>
<view class="row meta"
v-if="item.availablePowerBankCount !== undefined && item.availablePowerBankCount !== null">
<text class="time">可租借{{ item.availablePowerBankCount }} </text>
</view>
<view class="row meta"
v-if="item.availableEmptyGridCount !== undefined && item.availableEmptyGridCount !== null">
<text class="time">可归还{{ item.availableEmptyGridCount }} 个空位</text>
</view>
<view class="row meta remark-info" v-if="item.remark">
<text class="time">💰 {{ item.remark }}</text>
<!-- 第三列操作 -->
<view class="actions">
<view class="nav" :class="{ disabled: !isValidCoordinate(item.latitude, item.longitude) }"
@click.stop="navigateToPosition(item)">
<image src="/static/luxian.png" class="action-icon" mode="aspectFit"></image>
</view>
<view class="distance"
v-if="item.distance && isValidCoordinate(item.latitude, item.longitude)">
{{ item.distance }}
</view>
</view>
</view>
<!-- 第二行标签 -->
<view class="card-row-second">
<view class="tags">
<view class="tag rent" v-if="isRentable(item)">{{ $t('location.rent') }}</view>
<view class="tag return" v-if="isReturnable(item)">{{ $t('location.return') }}</view>
<view class="tag" v-if="isCoupon(item)">{{CouponOrmember(item)}}</view>
<view class="tag coupon" v-if="item.supportCouponOrMember">{{ $t('location.supportCouponOrMember') }}</view>
</view>
</view>
<view class="actions">
<view class="nav" :class="{ disabled: !isValidCoordinate(item.latitude, item.longitude) }"
@click.stop="navigateToPosition(item)">
<image src="/static/luxian.png" class="action-icon" mode="aspectFit"></image>
</view>
<view class="distance"
v-if="item.distance && isValidCoordinate(item.latitude, item.longitude)">
{{ item.distance }}</view>
</view>
</view>
<view class="empty-state" v-if="!isLoading && (!positionList || positionList.length === 0)">
@@ -101,13 +101,13 @@
} from '@/utils/i18n.js'
const {
t: $t
t
} = useI18n()
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('search.title')
title: t('search.title')
})
// uni.showLoading({
// title:'11111',
@@ -132,11 +132,6 @@
if (typeof item?.canReturn !== 'undefined') return !!item.canReturn
return String(item?.status || '').toLowerCase() === 'online'
}
const isCoupon = (item)=>{
if(typeof item.canCoupon!=='undefined')return !!item.canCoupon
return String(item?.canCoupon||'').toLowerCase()==='true';
}
const formatDistance = (meters) => {
if (meters < 1000) return `${Math.round(meters)}m`
@@ -192,11 +187,6 @@
positionList.value.sort((a, b) => (a.distanceInMeters || 999999) - (b.distanceInMeters || 999999))
applyFilter()
}
const CouponOrmember= async(item)=>{
return "可使用优惠券、会员卡"
}
const loadPositions = async (center) => {
try {
@@ -227,8 +217,8 @@
return {
...transformed,
canRent: activeTab.value === 'rent' ? true : (device.availablePowerBankCount > 0),
canReturn: activeTab.value === 'return' ? true : (device.availableEmptyGridCount >
0)
canReturn: activeTab.value === 'return' ? true : (device.availableEmptyGridCount > 0),
supportCouponOrMember: device.supportCouponOrMember || false
}
})
@@ -267,7 +257,7 @@ const init = async () => {
positionList.value = []
filteredPositions.value = []
uni.showToast({
title: $t('home.getLocationFailed'),
title: t('home.getLocationFailed'),
icon: 'none'
})
} finally {
@@ -289,7 +279,7 @@ const init = async () => {
const navigateToPosition = (position) => {
if (!isValidCoordinate(position.latitude, position.longitude)) {
uni.showToast({
title: $t('search.invalidCoordinate'),
title: t('search.invalidCoordinate'),
icon: 'none'
})
return
@@ -307,7 +297,7 @@ const init = async () => {
const goToPositionDetail = (position) => {
if (!position.positionId) {
uni.showToast({
title: $t('search.positionInfoError'),
title: t('search.positionInfoError'),
icon: 'none'
})
return
@@ -403,9 +393,8 @@ const init = async () => {
}
.card {
display: grid;
grid-template-columns: 120rpx 1fr 72rpx;
align-items: center;
display: flex;
flex-direction: column;
gap: 16rpx;
padding: 20rpx;
border-radius: 20rpx;
@@ -424,6 +413,21 @@ const init = async () => {
background: #FFF9F9;
}
// 第一行:三列布局
.card-row-first {
display: grid;
grid-template-columns: 120rpx 1fr 72rpx;
align-items: center;
gap: 16rpx;
}
// 第二行:标签
.card-row-second {
width: 100%;
padding-top: 8rpx;
// border-top: 1rpx solid #F0F0F0;
}
.thumb {
width: 120rpx;
height: 120rpx;
@@ -453,7 +457,7 @@ const init = async () => {
font-size: 30rpx;
font-weight: 700;
color: #2A2A2A;
max-width: 70%;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -485,28 +489,34 @@ const init = async () => {
}
}
}
}
.tags {
display: flex;
gap: 10rpx;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
}
.tag {
padding: 6rpx 14rpx;
border-radius: 16rpx;
font-size: 22rpx;
font-weight: 600;
}
.tag {
padding: 6rpx 14rpx;
border-radius: 16rpx;
font-size: 22rpx;
font-weight: 600;
}
.tag.rent {
background: #E8F5E8;
color: #3EAB64;
}
.tag.rent {
background: #E8F5E8;
color: #3EAB64;
}
.tag.return {
background: #E8F2FF;
color: #3578e5;
}
.tag.return {
background: #E8F2FF;
color: #3578e5;
}
.tag.coupon {
background: #FFF9F0;
color: #D4A574;
}
.actions {
@@ -514,6 +524,7 @@ const init = async () => {
align-items: center;
justify-content: center;
flex-direction: column;
gap: 8rpx;
}
.nav {
+9 -33
View File
@@ -33,7 +33,7 @@ import { ref, computed, onMounted, getCurrentInstance } from 'vue'
import { userLogout } from '@/config/api/user.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 获取全局 i18n 实例
const instance = getCurrentInstance()
@@ -42,7 +42,7 @@ const globalI18n = instance?.appContext?.config?.globalProperties?.$i18n
// 设置页面标题
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('settings.title')
title: t('settings.title')
})
})
@@ -51,7 +51,7 @@ const currentLanguage = ref(uni.getStorageSync('language') || 'zh-CN')
// 当前语言文本显示
const currentLanguageText = computed(() => {
return currentLanguage.value === 'zh-CN' ? '简体中文' : 'English'
return currentLanguage.value === 'zh-CN' ? t('settings.chinese') : t('settings.english')
})
const navigateTo = (url) => {
@@ -61,57 +61,33 @@ const navigateTo = (url) => {
// 显示语言选择器
const showLanguageSelector = () => {
uni.showActionSheet({
itemList: ['简体中文', 'English'],
itemList: [t('settings.chinese'), t('settings.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...',
title: t('settings.languageSwitched'),
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)
}
url: '/pages/index/index'
})
}, 800)
}
@@ -121,13 +97,13 @@ const showLanguageSelector = () => {
const handleLogout = async () => {
uni.showModal({
title: $t('common.tips'),
content: $t('user.confirmLogout'),
title: t('common.tips'),
content: t('user.confirmLogout'),
success: async (res) => {
if (res.confirm) {
const response = await userLogout();
if (response.code == 200) {
uni.showToast({ title: $t('user.logoutSuccess'), icon: 'none' })
uni.showToast({ title: t('user.logoutSuccess'), icon: 'none' })
setTimeout(() => {
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
+12 -12
View File
@@ -59,7 +59,7 @@ import { ref, reactive, onMounted } from 'vue';
import { getMyIndexInfo, uploadUserAvatar, updateUserInfo } from '../../config/api/user.js';
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 响应式状态
const userInfo = ref({
@@ -75,7 +75,7 @@ const isEditingNickname = ref(false);
// 页面加载时初始化
onMounted(() => {
uni.setNavigationBarTitle({
title: $t('userProfile.title')
title: t('userProfile.title')
})
loadUserInfo();
});
@@ -102,7 +102,7 @@ const loadUserInfo = async () => {
} catch (error) {
console.error('获取用户信息失败:', error);
uni.showToast({
title: $t('user.getUserInfoFailed'),
title: t('user.getUserInfoFailed'),
icon: 'none'
});
}
@@ -138,13 +138,13 @@ const onChooseAvatar = async (e) => {
const avatarLocalPath = e?.detail?.avatarUrl;
if (!avatarLocalPath) {
uni.showToast({
title: $t('user.noAvatar'),
title: t('user.noAvatar'),
icon: 'none'
});
return;
}
uni.showLoading({
title: $t('userProfile.uploading'),
title: t('userProfile.uploading'),
mask: true
});
const uploadRes = await uploadUserAvatar(avatarLocalPath);
@@ -157,14 +157,14 @@ const onChooseAvatar = async (e) => {
uni.setStorageSync('userInfo', userInfo.value);
}
uni.showToast({
title: $t('user.avatarUpdated'),
title: t('user.avatarUpdated'),
icon: 'success'
});
await loadUserInfo();
} catch (err) {
console.error('选择/上传头像失败:', err);
uni.showToast({
title: $t('user.avatarUploadFailed'),
title: t('user.avatarUploadFailed'),
icon: 'none'
});
} finally {
@@ -188,7 +188,7 @@ const cancelEditNickname = () => {
const saveNickname = async () => {
if (!newNickname.value || !newNickname.value.trim()) {
uni.showToast({
title: $t('userProfile.nicknameRequired'),
title: t('userProfile.nicknameRequired'),
icon: 'none'
});
return;
@@ -196,7 +196,7 @@ const saveNickname = async () => {
try {
uni.showLoading({
title: $t('userProfile.saving'),
title: t('userProfile.saving'),
mask: true
});
@@ -228,19 +228,19 @@ const saveNickname = async () => {
uni.hideLoading();
uni.showToast({
title: $t('userProfile.nicknameUpdated'),
title: t('userProfile.nicknameUpdated'),
icon: 'success'
});
isEditingNickname.value = false;
} else {
throw new Error(res.message || $t('userProfile.updateFailed'));
throw new Error(res.message || t('userProfile.updateFailed'));
}
} catch (error) {
console.error('修改昵称失败:', error);
uni.hideLoading();
uni.showToast({
title: error.message || $t('userProfile.updateFailed'),
title: error.message || t('userProfile.updateFailed'),
icon: 'none'
});
}
+4 -4
View File
@@ -26,7 +26,7 @@
import { getOrderByOrderNoScorePayStatus, cancelOrder } from '@/config/api/order.js'
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
const progress = ref(0)
const leftRotateDeg = ref(0)
@@ -91,7 +91,7 @@
await cancelOrder({ orderId: orderNo.value })
}
} catch (e) {}
uni.showToast({ title: $t('waiting.rentFailed'), icon: 'none' })
uni.showToast({ title: t('waiting.rentFailed'), icon: 'none' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/index/index' })
}, 800)
@@ -129,7 +129,7 @@
// 超时保护:例如 60 秒
timeoutTimer = setTimeout(() => {
stopAllTimers()
uni.showToast({ title: $t('waiting.timeout'), icon: 'none' })
uni.showToast({ title: t('waiting.timeout'), icon: 'none' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/index/index' })
}, 800)
@@ -138,7 +138,7 @@
onLoad((query) => {
uni.setNavigationBarTitle({
title: $t('waiting.title')
title: t('waiting.title')
})
if (query) {
if (query.orderNo) {
+3 -3
View File
@@ -11,7 +11,7 @@
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const { t } = useI18n()
// 网页链接
const webUrl = ref('')
@@ -24,7 +24,7 @@
console.log('加载外部链接:', webUrl.value)
} else {
uni.showToast({
title: $t('common.invalidUrl') || '无效的链接',
title: t('common.invalidUrl') || '无效的链接',
icon: 'none',
duration: 2000
})
@@ -44,7 +44,7 @@
const handleError = (e) => {
console.error('网页加载错误:', e)
uni.showToast({
title: $t('common.loadFailed') || '加载失败',
title: t('common.loadFailed') || '加载失败',
icon: 'none'
})
}
+13 -44
View File
@@ -1,6 +1,7 @@
import {
login,
getMyIndexInfo
getMyIndexInfo,
getWxUserPhoneNumber
} from "../config/api/user"
import {
URL,
@@ -84,52 +85,20 @@ export const getUserInfo = () => {
export const getUserPhoneNumber = (code) => {
return new Promise(async (resolve, reject) => {
try {
// 获取登录态信息
const token = uni.getStorageSync('token')
if (!token) {
reject(new Error('用户未登录'))
return
}
console.log('开始请求获取手机号,code=', code, '请求地址=', URL + '/app/user/getPhoneNumber')
console.log('开始请求获取手机号,code=', code)
// 发送请求到后端获取手机号
uni.request({
url: URL + '/app/user/getPhoneNumber',
method: 'POST',
header: {
'Authorization': 'Bearer ' + token,
'clientId': uni.getStorageSync('client_id'),
'Content-Type': 'application/json'
},
data: {
code: code, // 微信获取手机号授权后的临时code
appid: appid
},
success: (res) => {
console.log('请求获取手机号接口成功,原始响应:', JSON.stringify(res))
// 检查HTTP状态码
if (res.statusCode !== 200) {
reject(new Error(`HTTP错误: ${res.statusCode}`))
return
}
// 确保响应体存在
if (!res.data) {
reject(new Error('服务器返回空数据'))
return
}
// 正常返回响应数据,不做额外判断,由调用方处理业务状态码
resolve(res.data)
},
fail: (err) => {
console.error('请求获取手机号接口网络错误:', err)
reject(new Error('请求获取手机号接口失败: ' + (err.errMsg || JSON.stringify(err))))
}
const res = await getWxUserPhoneNumber({
code: code, // 微信获取手机号授权后的临时code
appid: appid
})
if (res) {
console.log('请求获取手机号接口成功:', JSON.stringify(res))
resolve(res)
} else {
reject(new Error('服务器返回空数据'))
}
} catch (error) {
console.error('获取手机号过程中出现异常:', error)
reject(new Error('获取手机号异常: ' + (error.message || JSON.stringify(error))))
+319 -13
View File
@@ -4,16 +4,16 @@ const DEFAULT_LOCALE = 'zh-CN'
const permissionTexts = {
'zh-CN': {
locationTitle: '位置信息授权',
locationNeed: '需要获取您的位置信息以展示附近设备,请在设置-权限管理中开启定位权限。',
locationDenied: '未授权定位,将无法展示附近设备。您可以稍后在设置-权限管理中重新开启定位权限。',
locationNeed: '需要获取您的位置信息以展示附近设备,请在"设置-权限管理"中开启定位权限。',
locationDenied: '未授权定位,将无法展示附近设备。您可以稍后在"设置-权限管理"中重新开启定位权限。',
goToSettings: '去设置',
later: '暂不',
gotIt: '知道了'
},
'en-US': {
locationTitle: 'Location Permission',
locationNeed: 'We need your location to show nearby devices. Please enable location in Settings > Permissions.',
locationDenied: 'Location access is disabled. You can re-enable it later in Settings > Permissions.',
locationNeed: 'We need your location to show nearby devices. Please enable location in "Settings > Permissions".',
locationDenied: 'Location access is disabled. You can re-enable it later in "Settings > Permissions".',
goToSettings: 'Set',
later: 'Skip',
gotIt: 'OK'
@@ -37,7 +37,14 @@ const getPermissionText = (key) => {
}
// 腾讯地图Key
const QQMAP_KEY = 'RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35';
const QQMAP_KEY =
// #ifdef H5
'DJQBZ-WB53Q-WPS5B-4S6J7-53RMS-X4FJ2'
// #endif
// #ifndef H5
'RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35'
// #endif
;
// 内联腾讯地图SDK核心代码
const QQMapWX = (function() {
@@ -118,7 +125,7 @@ const QQMapWX = (function() {
}
};
if (!param.location) {
wx.getLocation({
uni.getLocation({
type: 'gcj02',
success: locationsuccess,
fail: locationfail,
@@ -280,10 +287,16 @@ const QQMapWX = (function() {
const locationsuccess = function (result) {
requestParam.location = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
// #ifdef H5
// H5环境使用JSONP方式
that._requestJsonp(URL_GET_GEOCODER, requestParam, options, 'reverseGeocoder');
// #endif
// #ifndef H5
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_GET_GEOCODER,
data: requestParam
}, 'reverseGeocoder'));
// #endif
};
Utils.locationProcess(options, locationsuccess);
}
@@ -323,10 +336,16 @@ const QQMapWX = (function() {
const locationsuccess = function (result) {
requestParam.boundary = "nearby(" + result.latitude + "," + result.longitude + "," + distance + "," + auto_extend + ")";
wx.request(Utils.buildWxRequestConfig(options, {
// #ifdef H5
// H5环境使用JSONP方式
that._requestJsonp(URL_SEARCH, requestParam, options, 'search');
// #endif
// #ifndef H5
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SEARCH,
data: requestParam
}, 'search'));
// #endif
};
Utils.locationProcess(options, locationsuccess);
@@ -368,17 +387,27 @@ const QQMapWX = (function() {
if (options.location) {
const locationsuccess = function (result) {
requestParam.location = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
// #ifdef H5
that._requestJsonp(URL_SUGGESTION, requestParam, options, "suggest");
// #endif
// #ifndef H5
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION,
data: requestParam
}, "suggest"));
// #endif
};
Utils.locationProcess(options, locationsuccess);
} else {
wx.request(Utils.buildWxRequestConfig(options, {
// #ifdef H5
that._requestJsonp(URL_SUGGESTION, requestParam, options, "suggest");
// #endif
// #ifndef H5
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_SUGGESTION,
data: requestParam
}, "suggest"));
// #endif
}
}
@@ -408,14 +437,64 @@ const QQMapWX = (function() {
const locationsuccess = function (result) {
requestParam.from = result.latitude + ',' + result.longitude;
wx.request(Utils.buildWxRequestConfig(options, {
// #ifdef H5
that._requestJsonp(URL_DISTANCE, requestParam, options, 'calculateDistance');
// #endif
// #ifndef H5
uni.request(Utils.buildWxRequestConfig(options, {
url: URL_DISTANCE,
data: requestParam
}, 'calculateDistance'));
// #endif
};
Utils.locationProcess(options, locationsuccess);
}
// H5环境JSONP请求方法
_requestJsonp(url, params, options, feature) {
const callbackName = `qqmap_callback_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 构建URL参数
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
const fullUrl = `${url}?${queryString}&output=jsonp&callback=${callbackName}`;
// 创建全局回调函数
window[callbackName] = (data) => {
// 清理回调函数和script标签
delete window[callbackName];
const script = document.getElementById(callbackName);
if (script && script.parentNode) {
script.parentNode.removeChild(script);
}
if (data.status === 0) {
Utils.handleData(options, data, feature);
} else {
options.fail(data);
}
options.complete(data);
};
// 创建script标签进行JSONP请求
const script = document.createElement('script');
script.id = callbackName;
script.src = fullUrl;
script.onerror = () => {
delete window[callbackName];
const scriptEl = document.getElementById(callbackName);
if (scriptEl && scriptEl.parentNode) {
scriptEl.parentNode.removeChild(scriptEl);
}
const error = { status: 600, message: '请求失败' };
options.fail(error);
options.complete(error);
};
document.head.appendChild(script);
}
}
return QQMapWX;
@@ -550,8 +629,9 @@ function getUserLocation() {
});
// #endif
// 非微信小程序平台:使用 uni.getLocation 做一个尽量兼容的兜底
// 非微信小程序平台:根据平台使用不同的定位方式
// #ifndef MP-WEIXIN
// 统一使用 uni.getLocation,框架会根据环境自动选择最佳定位方式
uni.getLocation({
type: 'gcj02',
success: (res) => {
@@ -565,6 +645,16 @@ function getUserLocation() {
},
fail: (error) => {
console.error('获取位置失败:', error);
// H5 环境下的特殊错误提示
// #ifdef H5
if (error.errMsg && error.errMsg.indexOf('permission denied') !== -1) {
uni.showModal({
title: getPermissionText('locationTitle'),
content: getPermissionText('locationNeed'),
showCancel: false
});
}
// #endif
reject(error);
}
});
@@ -575,6 +665,57 @@ function getUserLocation() {
// 逆地理编码 - 根据经纬度获取地址信息
function getRegeo(longitude, latitude) {
return new Promise((resolve, reject) => {
// #ifdef H5
// H5环境:使用JSONP方式调用腾讯地图API,避免跨域问题
const callbackName = `qqmap_geocoder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 创建全局回调函数
window[callbackName] = (data) => {
// 清理回调函数和script标签
delete window[callbackName];
const script = document.getElementById(callbackName);
if (script && script.parentNode) {
script.parentNode.removeChild(script);
}
if (data.status === 0) {
const result = data.result;
resolve({
success: true,
data: {
formatted_address: result.address,
addressComponent: {
city: result.address_component.city,
district: result.address_component.district,
province: result.address_component.province,
street: result.address_component.street,
street_number: result.address_component.street_number
}
}
});
} else {
console.error('H5逆地理编码失败:', data);
reject({ success: false, message: data.message || '逆地理编码失败' });
}
};
// 创建script标签进行JSONP请求
const script = document.createElement('script');
script.id = callbackName;
script.src = `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`;
script.onerror = () => {
delete window[callbackName];
const scriptEl = document.getElementById(callbackName);
if (scriptEl && scriptEl.parentNode) {
scriptEl.parentNode.removeChild(scriptEl);
}
reject({ success: false, message: '逆地理编码请求失败' });
};
document.head.appendChild(script);
// #endif
// #ifdef MP-WEIXIN
// 小程序环境:使用 QQMapWX SDK
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
@@ -608,12 +749,68 @@ function getRegeo(longitude, latitude) {
reject({ success: false, message: error.message || '逆地理编码失败' });
}
});
// #endif
});
}
// 搜索周边POI
function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
return new Promise((resolve, reject) => {
// #ifdef H5
// H5环境:使用JSONP方式调用腾讯地图API
const callbackName = `qqmap_search_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 创建全局回调函数
window[callbackName] = (data) => {
// 清理回调函数和script标签
delete window[callbackName];
const script = document.getElementById(callbackName);
if (script && script.parentNode) {
script.parentNode.removeChild(script);
}
if (data.status === 0) {
const searchSimplify = data.data.map(item => ({
id: item.id || null,
title: item.title || null,
latitude: item.location && item.location.lat || null,
longitude: item.location && item.location.lng || null,
address: item.address || null,
category: item.category || null,
tel: item.tel || null,
adcode: item.ad_info && item.ad_info.adcode || null,
city: item.ad_info && item.ad_info.city || null,
district: item.ad_info && item.ad_info.district || null,
province: item.ad_info && item.ad_info.province || null
}));
resolve({
success: true,
data: searchSimplify
});
} else {
console.error('H5搜索POI失败:', data);
reject({ success: false, message: data.message || '搜索POI失败' });
}
};
// 创建script标签进行JSONP请求
const script = document.createElement('script');
script.id = callbackName;
script.src = `https://apis.map.qq.com/ws/place/v1/search?boundary=nearby(${latitude},${longitude},${radius})&keyword=${encodeURIComponent(keyword)}&orderby=_distance&page_size=10&page_index=1&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`;
script.onerror = () => {
delete window[callbackName];
const scriptEl = document.getElementById(callbackName);
if (scriptEl && scriptEl.parentNode) {
scriptEl.parentNode.removeChild(scriptEl);
}
reject({ success: false, message: '搜索POI请求失败' });
};
document.head.appendChild(script);
// #endif
// #ifdef MP-WEIXIN
// 小程序环境:使用 QQMapWX SDK
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
@@ -639,12 +836,64 @@ function getPoiAround(longitude, latitude, keyword = '', radius = 1000) {
reject({ success: false, message: error.message || '搜索POI失败' });
}
});
// #endif
});
}
// 计算距离(异步)
function calculateDistance(from, to) {
return new Promise((resolve, reject) => {
// #ifdef H5
// H5环境:使用JSONP方式调用腾讯地图API
const callbackName = `qqmap_distance_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 创建全局回调函数
window[callbackName] = (data) => {
// 清理回调函数和script标签
delete window[callbackName];
const script = document.getElementById(callbackName);
if (script && script.parentNode) {
script.parentNode.removeChild(script);
}
if (data.status === 0) {
const distances = data.result.elements.map(element => element.distance);
resolve({
success: true,
data: distances
});
} else {
console.error('H5计算距离失败:', data);
reject({ success: false, message: data.message || '计算距离失败' });
}
};
// 创建script标签进行JSONP请求
const script = document.createElement('script');
script.id = callbackName;
// 构建to参数
let toStr = '';
if (Array.isArray(to)) {
toStr = to.map(p => `${p.latitude},${p.longitude}`).join(';');
} else {
toStr = `${to.latitude},${to.longitude}`;
}
const fromStr = `${from.latitude},${from.longitude}`;
script.src = `https://apis.map.qq.com/ws/distance/v1/?mode=walking&from=${fromStr}&to=${toStr}&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`;
script.onerror = () => {
delete window[callbackName];
const scriptEl = document.getElementById(callbackName);
if (scriptEl && scriptEl.parentNode) {
scriptEl.parentNode.removeChild(scriptEl);
}
reject({ success: false, message: '计算距离请求失败' });
};
document.head.appendChild(script);
// #endif
// #ifdef MP-WEIXIN
// 小程序环境:使用 QQMapWX SDK
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
@@ -667,6 +916,7 @@ function calculateDistance(from, to) {
reject({ success: false, message: error.message || '计算距离失败' });
}
});
// #endif
});
}
@@ -685,6 +935,61 @@ function calculateDistanceSync(lat1, lng1, lat2, lng2) {
// 关键词提示
function getSuggestion(keyword, region = '全国') {
return new Promise((resolve, reject) => {
// #ifdef H5
// H5环境:使用JSONP方式调用腾讯地图API
const callbackName = `qqmap_suggestion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 创建全局回调函数
window[callbackName] = (data) => {
// 清理回调函数和script标签
delete window[callbackName];
const script = document.getElementById(callbackName);
if (script && script.parentNode) {
script.parentNode.removeChild(script);
}
if (data.status === 0) {
const suggestSimplify = data.data.map(item => ({
adcode: item.adcode || null,
address: item.address || null,
category: item.category || null,
city: item.city || null,
district: item.district || null,
id: item.id || null,
latitude: item.location && item.location.lat || null,
longitude: item.location && item.location.lng || null,
province: item.province || null,
title: item.title || null,
type: item.type || null
}));
resolve({
success: true,
data: suggestSimplify
});
} else {
console.error('H5关键词提示失败:', data);
reject({ success: false, message: data.message || '关键词提示失败' });
}
};
// 创建script标签进行JSONP请求
const script = document.createElement('script');
script.id = callbackName;
script.src = `https://apis.map.qq.com/ws/place/v1/suggestion?keyword=${encodeURIComponent(keyword)}&region=${encodeURIComponent(region)}&page_size=10&page_index=1&key=${QQMAP_KEY}&output=jsonp&callback=${callbackName}`;
script.onerror = () => {
delete window[callbackName];
const scriptEl = document.getElementById(callbackName);
if (scriptEl && scriptEl.parentNode) {
scriptEl.parentNode.removeChild(scriptEl);
}
reject({ success: false, message: '关键词提示请求失败' });
};
document.head.appendChild(script);
// #endif
// #ifdef MP-WEIXIN
// 小程序环境:使用 QQMapWX SDK
const qqmap = getQQMapInstance();
if (!qqmap) {
reject({ success: false, message: '腾讯地图SDK未初始化' });
@@ -706,6 +1011,7 @@ function getSuggestion(keyword, region = '全国') {
reject({ success: false, message: error.message || '关键词提示失败' });
}
});
// #endif
});
}
@@ -732,4 +1038,4 @@ export function testDistanceCalculation() {
console.log('转换为公里:', (distance / 1000).toFixed(2), '公里');
return distance;
}
}
+37 -8
View File
@@ -249,16 +249,45 @@ class OrderMonitor {
export const orderMonitor = new OrderMonitor()
// 监听页面切换事件
uni.onAppRoute((route) => {
const pagePath = route.path || ''
const pageSegments = pagePath.split('/')
const pageName = pageSegments[pageSegments.length - 1]
// #ifdef MP-WEIXIN || APP-PLUS
if (typeof uni.onAppRoute === 'function') {
uni.onAppRoute((route) => {
const pagePath = route.path || ''
const pageSegments = pagePath.split('/')
const pageName = pageSegments[pageSegments.length - 1]
// 设置当前活跃页面
orderMonitor.setActivePage(pageName || null)
console.log('页面切换:', pagePath, '当前活跃页面:', pageName)
})
}
// #endif
// #ifdef H5
// H5环境下通过路由监听实现页面切换检测
if (typeof window !== 'undefined') {
const updateActivePageForH5 = () => {
const pagePath = window.location.pathname || window.location.hash
const pageSegments = pagePath.split('/')
const pageName = pageSegments[pageSegments.length - 1].replace(/[?#].*$/, '')
if (pageName) {
orderMonitor.setActivePage(pageName)
console.log('页面切换(H5):', pagePath, '当前活跃页面:', pageName)
}
}
// 设置当前活跃页面
orderMonitor.setActivePage(pageName || null)
// 监听 popstate 事件(浏览器前进后退)
window.addEventListener('popstate', updateActivePageForH5)
console.log('页面切换:', pagePath, '当前活跃页面:', pageName)
})
// 监听 hashchange 事件(hash 模式路由)
window.addEventListener('hashchange', updateActivePageForH5)
// 初始化时执行一次
updateActivePageForH5()
}
// #endif
// 页面加载时自动恢复监控上次的活跃订单(如果有)
const initOrderMonitor = () => {