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
+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',
}
// URLfeeConfig
if (options.feeConfig) {
try {
console.log('从URL获取到feeConfig:', options.feeConfig)
const feeConfigStr = decodeURIComponent(options.feeConfig)
// deviceInfofeeConfig
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'
})
}