fix:修复bug

This commit is contained in:
2025-10-14 19:20:26 +08:00
parent 30e298d9d2
commit 4408673438
21 changed files with 1153 additions and 549 deletions
+279 -236
View File
@@ -3,10 +3,27 @@
<!-- 地图容器 --> <!-- 地图容器 -->
<view class="map-wrapper"> <view class="map-wrapper">
<!-- 使用小程序原生地图组件 --> <!-- 使用小程序原生地图组件 -->
<map id="map" class="native-map" :longitude="mapCenter.longitude" :latitude="mapCenter.latitude" <map id="map" class="native-map" :longitude="mapCenter.longitude" :latitude="mapCenter.latitude"
:markers="mapMarkers" :scale="mapZoom" :show-location="true" @regionchange="onMapRegionChange" :markers="mapMarkers" :scale="mapZoom" :show-location="true" @regionchange="onMapRegionChange"
@markertap="onMapMarkerTap" @callouttap="onCalloutTap" @updated="onMapUpdated" @markertap="onMapMarkerTap" @callouttap="onCalloutTap" @updated="onMapUpdated"
@error="onMapError"></map> @error="onMapError">
<!-- 覆盖在地图上的可点击控件使用 cover-view 以兼容小程序原生组件层级 -->
<cover-view class="map-side-controls">
<cover-view class="side-btn service" @tap="handleService">
<cover-image class="side-icon" src="/static/customer-service.png"></cover-image>
<!-- <cover-view class="side-text">客服</cover-view> -->
</cover-view>
<cover-view class="side-btn search" @tap="handleSearch">
<cover-image class="side-icon" src="/static/search-icon.png"></cover-image>
<!-- <cover-view class="side-text">搜索</cover-view> -->
</cover-view>
<cover-view class="side-btn locate" @tap="handleRelocate">
<cover-image class="side-icon" src="/static/location.png"></cover-image>
<!-- <cover-view class="side-text">定位</cover-view> -->
</cover-view>
</cover-view>
<!-- 使用原生 marker 方案渲染中心指示 regionchange 同步到地图中心 -->
</map>
@@ -18,35 +35,19 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 地图控制按钮 -->
<view class="map-controls">
<view class="control-btn location-control" @tap="handleRelocate">
<!-- <image class="control-icon" src="/static/scan-icon.png" mode="aspectFit" /> -->
<uv-icon name="map-fill" size="18"></uv-icon>
<text style="margin-left: 8rpx;">我的位置</text>
</view>
<view class="control-btn scan-control main-btn" @tap="handleScan">
<image class="control-icon" src="/static/scan-icon.png" mode="aspectFit" />
<text>扫码使用</text>
</view>
<view class="control-btn list-control" @tap="handleShowList">
<image class="control-icon" src="/static/map.png" mode="aspectFit" />
<text>附近设备</text>
</view>
</view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { import {
ref, ref,
computed, computed,
watch, watch,
onMounted, onMounted,
onUnmounted, onUnmounted,
nextTick nextTick,
} from 'vue' getCurrentInstance
} from 'vue'
// 导入地图工具函数 // 导入地图工具函数
import { import {
calculateDistanceSync calculateDistanceSync
@@ -75,7 +76,7 @@
]) ])
// Props // Props
const props = defineProps({ const props = defineProps({
userLocation: { userLocation: {
type: Object, type: Object,
default: null default: null
@@ -91,7 +92,15 @@
searchKeyword: { searchKeyword: {
type: String, type: String,
default: '' default: ''
} },
noticeText: {
type: String,
default: ''
},
enableMarkers: {
type: Boolean,
default: false
}
}) })
// Emits // Emits
@@ -114,91 +123,73 @@
const mapContext = ref(null) // 地图上下文 const mapContext = ref(null) // 地图上下文
// 方法 // 方法
const updateMapMarkers = () => { const updateMapMarkers = () => {
mapMarkers.value = [] const markers = []
// 中心 marker(始终存在,使用传入中心坐标或 userLocation
const centerLng = Number(mapCenter.value.longitude || (props.userLocation && props.userLocation.longitude))
const centerLat = Number(mapCenter.value.latitude || (props.userLocation && props.userLocation.latitude))
if (!isNaN(centerLng) && !isNaN(centerLat)) {
markers.push({
id: 999999, // 固定 id 作为中心点
latitude: centerLat,
longitude: centerLng,
iconPath: '/static/location-icon.png',
width: 30,
height: 40,
anchor: { x: 0.5, y: 1 } // 图钉尖端对准坐标
})
}
// 添加用户位置标记 // 可选:周边位置点
if (props.userLocation) { if (props.enableMarkers && props.filteredPositions && props.filteredPositions.length > 0) {
mapMarkers.value.push({ props.filteredPositions.forEach((pos, index) => {
id: 0, // ID必须是数字 if (pos.longitude && pos.latitude) {
// iconPath: '/static/scan-icon.png', const lat = parseFloat(pos.latitude)
width: 32, const lng = parseFloat(pos.longitude)
height: 32, if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
latitude: props.userLocation.latitude, markers.push({
longitude: props.userLocation.longitude, id: index + 1,
title: '我的位置', latitude: lat,
callout: { longitude: lng,
content: '我的位置', width: 24,
color: '#ffffff', height: 24,
fontSize: 12, title: pos.name
borderRadius: 4, })
bgColor: '#2196F3', }
padding: 6, }
display: 'BYCLICK' // 点击时显示 })
}, }
customCallout: {
anchorX: 0,
anchorY: 0
}
})
}
// 添加位置点标记 mapMarkers.value = markers
if (props.filteredPositions && props.filteredPositions.length > 0) { isLoading.value = false
props.filteredPositions.forEach((pos, index) => { }
if (pos.longitude && pos.latitude) {
// 验证纬度值是否在有效范围内
const lat = parseFloat(pos.latitude);
const lng = parseFloat(pos.longitude);
// 检查纬度是否在-90到90之间,经度是否在-180到180之间 // 移动地图到指定位置(使用 includePoints 以避免 mapid 错误,并兼容不同基础库)
if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) { const moveToLocation = (location) => {
mapMarkers.value.push({ if (!location || !location.longitude || !location.latitude) return
id: index + 1, // ID必须是数字,避免和用户位置的ID冲突 if (!mapContext.value) return
// iconPath: '/static/scan-icon.png',
width: 30,
height: 30,
latitude: lat,
longitude: lng,
title: pos.name,
position: pos, // 存储原始位置数据,用于事件处理
callout: {
content: pos.name,
color: '#333333',
fontSize: 12,
borderRadius: 4,
bgColor: '#ffffff',
padding: 6,
display: 'BYCLICK' // 点击时显示
}
})
} else {
console.warn(`忽略无效坐标: ${pos.name}, 纬度=${lat}, 经度=${lng}`);
}
}
})
}
isLoading.value = false try {
} mapContext.value.includePoints({
points: [{ longitude: Number(location.longitude), latitude: Number(location.latitude) }],
// 移动地图到指定位置 padding: [60, 60, 60, 60],
const moveToLocation = (location) => { success: () => {
if (!location || !location.longitude || !location.latitude) return console.log('地图已移动到指定位置(includePoints)')
},
if (mapContext.value) { fail: (err) => {
mapContext.value.moveToLocation({ console.warn('includePoints 失败,尝试 moveToLocation():', err)
longitude: location.longitude, // 回退尝试(不传参,部分基础库仅支持移动到用户当前位置)
latitude: location.latitude, try {
success: () => { mapContext.value.moveToLocation()
console.log('地图已移动到指定位置') } catch (e) {
}, console.error('moveToLocation 回退失败:', e)
fail: (error) => { }
console.error('移动地图失败:', error) }
} })
}) } catch (e) {
} console.error('移动地图异常:', e)
} }
}
// 监听用户位置变化 // 监听用户位置变化
watch(() => props.userLocation, (newLocation) => { watch(() => props.userLocation, (newLocation) => {
@@ -228,25 +219,26 @@
} }
// 地图区域变化事件 // 地图区域变化事件
const onMapRegionChange = (e) => { const onMapRegionChange = (e) => {
// 当地图区域变化结束时更新mapCenter // 在手势或缩放结束时更新中心坐标
if (e.type === 'end' && e.causedBy === 'drag') { if (e && e.type === 'end') {
// 获取地图中心点 if (mapContext.value) {
if (mapContext.value) { mapContext.value.getCenterLocation({
mapContext.value.getCenterLocation({ success: (res) => {
success: (res) => { if (res && res.longitude && res.latitude) {
if (res.longitude && res.latitude) { mapCenter.value = {
mapCenter.value = { longitude: res.longitude,
longitude: res.longitude, latitude: res.latitude
latitude: res.latitude }
} // 更新中心 marker 位置
emit('mapCenterChange', mapCenter.value) updateMapMarkers()
} emit('mapCenterChange', mapCenter.value)
} }
}) }
} })
} }
} }
}
// 标记点点击事件 // 标记点点击事件
const onMapMarkerTap = (e) => { const onMapMarkerTap = (e) => {
@@ -284,8 +276,55 @@
isLoading.value = false isLoading.value = false
} }
const handleRelocate = () => { const handleRelocate = () => {
emit('relocate') // 直接委托父级处理定位并移动地图,避免内部重复弹 loading
try {
emit('relocate')
} catch (e) {}
}
const handleSearch = () => {
// 选择位置后移动地图并通知父组件刷新附近列表(增强容错与平台兼容)
const onSuccess = (res) => {
try {
const lng = Number(res.longitude)
const lat = Number(res.latitude)
if (!isNaN(lng) && !isNaN(lat)) {
const center = { longitude: lng, latitude: lat }
moveToLocation(center)
emit('mapCenterChange', center)
} else {
uni.showToast({ title: '无效的坐标', icon: 'none' })
}
} catch (e) {
console.error('处理选择位置结果异常:', e)
}
}
const onFail = (err) => {
console.error('chooseLocation 失败:', err)
// 授权被拒时引导开启
if (err && (String(err.errMsg || '').includes('auth') || String(err.errMsg || '').includes('deny'))) {
// #ifdef MP-WEIXIN
wx.openSetting && wx.openSetting({})
// #endif
}
// uni.showToast({ title: '无法打开选择位置', icon: 'none' })
}
// #ifdef MP-WEIXIN
if (typeof wx !== 'undefined' && wx.chooseLocation) {
wx.chooseLocation({ success: onSuccess, fail: onFail })
return
}
// #endif
if (uni && typeof uni.chooseLocation === 'function') {
uni.chooseLocation({ success: onSuccess, fail: onFail })
} else {
uni.showToast({ title: '当前环境不支持选择位置', icon: 'none' })
}
}
const handleService = () => {
uni.navigateTo({ url: '/pages/help/index' })
} }
const handleScan = () => { const handleScan = () => {
@@ -297,19 +336,26 @@
} }
// 生命周期钩子 // 生命周期钩子
onMounted(() => { onMounted(() => {
// 初始化地图上下文 // 初始化地图上下文
nextTick(() => { nextTick(() => {
// 需要使用nextTick确保地图组件已经渲染 // 需要使用nextTick确保地图组件已经渲染
mapContext.value = uni.createMapContext('map') const inst = getCurrentInstance()
updateMapMarkers() const vm = (inst && (inst.proxy || inst)) || undefined
try {
mapContext.value = uni.createMapContext('map', vm)
} catch (e) {
// 兼容:如果第二参不被支持,退回单参
mapContext.value = uni.createMapContext('map')
}
updateMapMarkers()
// 初始化折叠面板 // 初始化折叠面板
if (collapseRef.value) { if (collapseRef.value) {
collapseRef.value.init() collapseRef.value.init()
} }
}) })
}) })
onUnmounted(() => { onUnmounted(() => {
// 清理工作 // 清理工作
@@ -340,34 +386,29 @@
/* 地图容器 */ /* 地图容器 */
.map-container { .map-container {
flex: 1; flex: 1;
position: relative; // position: fixed;
height: 60vh; // top: 0;
/* 增加高度 */ left: 0;
width: 92%; right: 0;
/* 略微增加宽度 */ bottom: 0;
margin: 10rpx auto 30rpx; width: 94vw;
/* 调整上下间距,左右自动居中 */ height: 78vh;
border-radius: 24rpx; margin: 20rpx;
/* 添加圆角 */ border-radius: 20rpx;
overflow: hidden; overflow: hidden;
/* 确保圆角生效 */
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
/* 添加阴影效果 */
.map-wrapper { .map-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border-radius: 24rpx; border-radius: 0;
/* 内层也添加圆角 */
.native-map { .native-map {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block; display: block;
border-radius: 24rpx; border-radius: 0;
/* 地图也添加圆角 */
} }
} }
@@ -407,41 +448,65 @@
} }
} }
.map-controls { /* 地图中心图钉(cover-view,避免使用 transform 以兼容小程序) */
.center-pin {
position: absolute; position: absolute;
right: 20rpx; left: 50%;
bottom: 20rpx; top: 50%;
left: 20rpx; z-index: 16;
display: flex; width: 0;
justify-content: center; height: 0;
align-items: center; }
gap: 20rpx;
.control-btn { .center-pin .pin-img {
min-width: 120rpx; position: absolute;
/* 减小按钮宽度 */ width: 48rpx;
height: 70rpx; height: 64rpx;
/* 减小按钮高度 */ /* 使图钉尖端对准中心点:X 向左偏半宽,Y 向上偏高度-阴影间距 */
background: #ffffff; margin-left: -24rpx;
border-radius: 35rpx; margin-top: -68rpx;
display: flex; }
.center-pin .pin-shadow {
position: absolute;
width: 28rpx;
height: 8rpx;
border-radius: 10rpx;
background: rgba(0, 0, 0, 0.18);
margin-left: -14rpx;
margin-top: -8rpx;
}
.map-side-controls {
position: absolute;
left: 20rpx;
bottom: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 300rpx;
margin: auto;
gap: 12rpx;
.side-btn {
// min-width: 160rpx;
margin: auto;
height: 72rpx;
background: rgba(255, 255, 255, 0.96);
border-radius: 36rpx;
display: inline-flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
transition: all 0.2s ease; padding: 0 18rpx;
padding: 0 16rpx; border: 2rpx solid #e0e0e0;
&:active { &:active {
transform: scale(0.95); transform: scale(0.95);
} }
.control-icon {
width: 32rpx;
height: 32rpx;
margin-right: 12rpx;
}
text { text {
font-size: 26rpx; font-size: 26rpx;
color: #333; color: #333;
@@ -449,65 +514,39 @@
font-weight: 500; font-weight: 500;
} }
&.main-btn { &.search {
min-width: 140rpx; border-color: #07c160;
/* 减小主按钮宽度 */
height: 80rpx;
/* 减小主按钮高度 */
box-shadow: 0 6rpx 20rpx rgba(33, 150, 243, 0.4);
transform: translateY(-5rpx);
.control-icon {
width: 36rpx;
height: 36rpx;
margin-right: 16rpx;
}
text {
font-size: 28rpx;
font-weight: 600;
}
&:active {
transform: translateY(-5rpx) scale(0.95);
}
} }
} }
}
.scan-control { /* 展示用图标列(与 cover-view 对齐) */
background: #2196F3; .map-side-icons {
position: absolute;
left: 20rpx;
bottom: 20rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
.control-icon { .side-btn {
filter: brightness(0) invert(1); min-width: 160rpx;
} height: 72rpx;
background: rgba(255, 255, 255, 0.96);
text { border-radius: 36rpx;
color: #ffffff; display: inline-flex;
} flex-direction: row;
} align-items: center;
justify-content: center;
.list-control { box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
background: #4CAF50; padding: 0 18rpx;
.control-icon {
filter: brightness(0) invert(1);
}
text {
color: #ffffff;
}
}
.location-control {
background: #ffffff;
border: 2rpx solid #e0e0e0; border: 2rpx solid #e0e0e0;
.control-icon { .label {
filter: none; margin-left: 8rpx;
} font-size: 26rpx;
text {
color: #333; color: #333;
font-weight: 500;
} }
} }
} }
@@ -523,5 +562,9 @@
} }
} }
.side-icon{
width: 32rpx;
height: 32rpx;
}
</style> </style>
+1 -1
View File
@@ -1,6 +1,6 @@
// export const URL = "https://my.gxfs123.com/api" //正式服务器 // export const URL = "https://my.gxfs123.com/api" //正式服务器
export const URL = "https://fansdev.gxfs123.com/api" //测试服务器 export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
// export const URL = "http://192.168.169.66:8080" //本地调试 // export const URL = "http://192.168.5.37:8080" //本地调试
// export const URL = "http://127.0.0.1:8080" //本地调试 // export const URL = "http://127.0.0.1:8080" //本地调试
export const appid = "wx2165f0be356ae7a9" //小程序appid export const appid = "wx2165f0be356ae7a9" //小程序appid
+19
View File
@@ -2,6 +2,7 @@ import request from './http'
import { URL, appid } from './url' import { URL, appid } from './url'
//用户登录
export const login = (data) => { export const login = (data) => {
return request({ return request({
url: '/app/user/login', url: '/app/user/login',
@@ -10,7 +11,16 @@ export const login = (data) => {
}) })
} }
//用户退出登录
export const userLogout = (data)=>{
return request({
url:'/auth/logout',
method:'post',
data
})
}
//获取用户信息
export const getMyIndexInfo = (data) => { export const getMyIndexInfo = (data) => {
return request({ return request({
url: '/app/user/userInfo', url: '/app/user/userInfo',
@@ -306,3 +316,12 @@ export const cancelExpressReturn = (id) => {
method: 'post' method: 'post'
}) })
} }
//获取通知接口
export const getNoticeTextData = (data)=>{
return request({
url: `/system/notice/title/${data.title}`,
method: 'get'
})
}
+1 -1
View File
@@ -66,7 +66,7 @@
"desc" : "您的位置信息将用于获取附近的设备" "desc" : "您的位置信息将用于获取附近的设备"
} }
}, },
"requiredPrivateInfos" : [ "getLocation" ] "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
}, },
"mp-alipay" : { "mp-alipay" : {
"usingComponents" : true "usingComponents" : true
+21 -19
View File
@@ -9,7 +9,11 @@
"pages": [{ "pages": [{
"path": "pages/index/index", "path": "pages/index/index",
"style": { "style": {
"navigationBarTitleText": "附近场地" "navigationBarTitleText": "",
"navigationBarBackgroundColor": "#ffffff",
"navigationStyle": "default",
"enableShareAppMessage": true,
"enableShareTimeline": true
} }
}, },
{ {
@@ -20,6 +24,22 @@
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
} }
}, },
{
"path": "pages/legal/agreement",
"style": {
"navigationBarTitleText": "用户协议",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/legal/privacy",
"style": {
"navigationBarTitleText": "隐私政策",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{ {
"path": "pages/my/index", "path": "pages/my/index",
"style": { "style": {
@@ -134,23 +154,5 @@
"navigationBarTitleText": "共享风扇", "navigationBarTitleText": "共享风扇",
"navigationBarBackgroundColor": "#F8F8F8", "navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#1976D2",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/home.png",
"selectedIconPath": "static/home-active.png"
},
{
"pagePath": "pages/my/index",
"text": "我的",
"iconPath": "static/user.png",
"selectedIconPath": "static/user-active.png"
}
]
} }
} }
+92 -41
View File
@@ -21,18 +21,22 @@
<view class="form-title">填写快递归还信息</view> <view class="form-title">填写快递归还信息</view>
<view class="form-item"> <view class="form-item">
<view class="item-label">联系电话</view> <view class="item-label">联系电话</view>
<input class="item-input" type="number" v-model="phone" placeholder="请输入联系电话" maxlength="20" /> <input class="item-input" type="number" v-model="phone" placeholder="请输入联系电话" maxlength="20"
:focus-class="'item-input-focus'" />
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="item-label">快递单号</view> <view class="item-label">快递单号</view>
<input class="item-input" type="text" v-model="trackingNumber" placeholder="请输入快递单号" maxlength="40" /> <input class="item-input" type="text" v-model="trackingNumber"
:placeholder="isFillMode ? '请输入需要补填的快递单号' : '请输入快递单号(可先留空)'" maxlength="40"
:focus-class="'item-input-focus'" />
</view> </view>
<view class="tips" v-if="tipsText">{{ tipsText }}</view> <view class="tips" v-if="tipsText">{{ tipsText }}</view>
</view> </view>
<!-- 提交操作条 --> <!-- 提交操作条 -->
<view class="bottom-bar"> <view class="bottom-bar">
<view class="action-item primary" @click="handleSubmit">提交信息</view> <view class="action-item primary" :class="{ disabled: submitting }" hover-class="primary-hover"
@click="!submitting && handleSubmit()">{{ isFillMode ? '确认补填' : '提交信息' }}</view>
</view> </view>
</view> </view>
</template> </template>
@@ -45,13 +49,13 @@
import { import {
onLoad onLoad
} from '@dcloudio/uni-app' } from '@dcloudio/uni-app'
import { import {
queryById, queryById,
applyExpressReturn, applyExpressReturn,
getExpressReturnByOrder, getExpressReturnByOrder,
getExpressReturnDetail, getExpressReturnDetail,
fillExpressTrackingNumber fillExpressTrackingNumber
} from '@/config/user.js' } from '@/config/user.js'
const orderId = ref('') const orderId = ref('')
const recordId = ref('') const recordId = ref('')
@@ -65,6 +69,7 @@ import {
const phone = ref('') const phone = ref('')
const trackingNumber = ref('') const trackingNumber = ref('')
const tipsText = ref('') const tipsText = ref('')
const submitting = ref(false)
onLoad(async (options) => { onLoad(async (options) => {
orderId.value = options?.orderId || '' orderId.value = options?.orderId || ''
@@ -117,7 +122,9 @@ import {
const loadRecordAndOrderByRecord = async () => { const loadRecordAndOrderByRecord = async () => {
try { try {
uni.showLoading({ title: '加载中' }) uni.showLoading({
title: '加载中'
})
const res = await getExpressReturnDetail(recordId.value) const res = await getExpressReturnDetail(recordId.value)
if (res?.code === 200 && res.data) { if (res?.code === 200 && res.data) {
if (res.data.orderId) { if (res.data.orderId) {
@@ -129,7 +136,10 @@ import {
throw new Error(res?.msg || '获取记录失败') throw new Error(res?.msg || '获取记录失败')
} }
} catch (e) { } catch (e) {
uni.showToast({ title: e.message || '加载失败', icon: 'none' }) uni.showToast({
title: e.message || '加载失败',
icon: 'none'
})
} finally { } finally {
uni.hideLoading() uni.hideLoading()
} }
@@ -149,15 +159,22 @@ import {
cancelText: '取消', cancelText: '取消',
success: (r) => { success: (r) => {
if (r.confirm) { if (r.confirm) {
uni.redirectTo({ url: `/pages/expressReturn/addExpressReturn?id=${rec.id}` }) uni.redirectTo({
url: `/pages/expressReturn/addExpressReturn?id=${rec.id}`
})
} }
} }
}) })
return return
} else { } else {
uni.showToast({ title: '已有归还记录', icon: 'none' }) uni.showToast({
title: '已有归还记录',
icon: 'none'
})
setTimeout(() => { setTimeout(() => {
uni.redirectTo({ url: `/pages/expressReturn/detail?id=${rec.id}` }) uni.redirectTo({
url: `/pages/expressReturn/detail?id=${rec.id}`
})
}, 800) }, 800)
} }
} }
@@ -186,32 +203,47 @@ import {
return true return true
} }
const handleSubmit = async () => { const handleSubmit = async () => {
if (!validate()) return if (!validate() || submitting.value) return
try { submitting.value = true
uni.showLoading({ title: isFillMode.value ? '补填中' : '提交中' }) try {
let res uni.showLoading({
if (isFillMode.value) { title: isFillMode.value ? '补填中' : '提交中'
res = await fillExpressTrackingNumber({ id: Number(recordId.value), logisticsTrackingNumber: trackingNumber.value }) })
} else { let res
res = await applyExpressReturn({ if (isFillMode.value) {
orderId: orderId.value, res = await fillExpressTrackingNumber({
logisticsTrackingNumber: trackingNumber.value, id: Number(recordId.value),
remark: '' logisticsTrackingNumber: trackingNumber.value
}) })
} } else {
if (res && res.code === 200) { res = await applyExpressReturn({
uni.showToast({ title: isFillMode.value ? '补填成功' : '提交成功', icon: 'success' }) orderId: orderId.value,
setTimeout(() => { uni.navigateBack() }, 800) logisticsTrackingNumber: trackingNumber.value,
} else { remark: ''
throw new Error(res?.msg || (isFillMode.value ? '补填失败' : '提交失败')) })
} }
} catch (e) { if (res && res.code === 200) {
uni.showToast({ title: e.message || (isFillMode.value ? '补填失败' : '提交失败'), icon: 'none' }) uni.showToast({
} finally { title: isFillMode.value ? '补填成功' : '提交成功',
uni.hideLoading() icon: 'success'
} })
} setTimeout(() => {
uni.navigateBack()
}, 800)
} else {
throw new Error(res?.msg || (isFillMode.value ? '补填失败' : '提交失败'))
}
} catch (e) {
uni.showToast({
title: e.message || (isFillMode.value ? '补填失败' : '提交失败'),
icon: 'none'
})
} finally {
submitting.value = false
uni.hideLoading()
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -287,6 +319,16 @@ const handleSubmit = async () => {
font-size: 28rpx; font-size: 28rpx;
color: #111827; color: #111827;
line-height: 1.2; line-height: 1.2;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.item-input::placeholder {
color: #9ca3af;
}
.item-input-focus {
border-color: #2563eb;
box-shadow: 0 0 0 4rpx rgba(37, 99, 235, 0.15);
} }
} }
@@ -322,6 +364,15 @@ const handleSubmit = async () => {
background: #1f2937; background: #1f2937;
color: #fff; color: #fff;
} }
&.disabled {
opacity: 0.6;
pointer-events: none;
}
}
.primary-hover {
background: #111827;
} }
} }
</style> </style>
+270 -180
View File
@@ -1,40 +1,38 @@
<template> <template>
<view class="container"> <view class="container fullscreen">
<!-- 顶部Logo区域 --> <view class="" style="font-size: 32rpx;font-weight: 600;margin: 15rpx 20rpx;">风电者共享风扇&充电宝</view>
<view class="header-section"> <view class="map-notice" v-if="noticeText">
<view class="logo-container"> <uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
<image class="logo-image" src="/static/logo.png" mode="aspectFit" /> icon="volume"></uv-notice-bar>
<text class="app-name">共享风扇</text>
</view>
<uv-notice-bar :text="noticeText" mode="link" :speed="50" :show-icon="true" color="#2196F3"
bg-color="#E3F2FD" icon="volume"></uv-notice-bar>
</view> </view>
<!-- 地图标题 -->
<!-- <view class="map-title">
<text>附近场地</text>
</view> -->
<!-- 全屏地图组件 -->
<!-- 地图组件 -->
<MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation" <MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword" :positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
@relocate="handleRelocate" @scan="handleScan" @showList="showLocationList" @markerTap="selectPosition" @relocate="handleRelocate" @scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
@mapCenterChange="onMapCenterChange" /> @mapCenterChange="onMapCenterChange" />
<!-- 操作步骤指引常驻显示 --> <!-- 底部操作栏附近设备 / 扫码使用 / 我的 -->
<view class="steps-guide"> <view class="bottom-actions">
<view class="guide-header"> <view class="action-btn secondary small btn-nearby" @click="showLocationList">
<text class="guide-title">使用指南</text> <view class="icon-wrap">
</view> <image class="action-icon" src="/static/map.png" mode="aspectFit" />
<view class="steps-container">
<view class="step-item" v-for="(step, index) in guideSteps" :key="index">
<view class="step-number">{{ index + 1 }}</view>
<view class="step-content">
<text class="step-title">{{ step.title }}</text>
<text class="step-desc">{{ step.desc }}</text>
</view>
</view> </view>
<text class="action-label">附近设备</text>
</view>
<view class="action-btn primary btn-scan" @click="handleScan">
<view class="icon-wrap">
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFit" />
</view>
<text class="action-label">扫码使用</text>
</view>
<view class="action-btn secondary small btn-my" @click="goMy">
<view class="icon-wrap">
<image class="action-icon" src="/static/user-active.png" mode="aspectFit" />
</view>
<text class="action-label">个人中心</text>
</view> </view>
</view> </view>
@@ -46,6 +44,8 @@
</view> </view>
</view> </view>
<!-- 场地列表弹窗 --> <!-- 场地列表弹窗 -->
<view class="location-popup" v-if="showLocationPopup"> <view class="location-popup" v-if="showLocationPopup">
<view class="popup-mask" @click="hideLocationList"></view> <view class="popup-mask" @click="hideLocationList"></view>
@@ -128,18 +128,23 @@
</template> </template>
<script setup> <script setup>
const redirectToLogin = () => { const redirectToLogin = () => {
try { try {
const pages = getCurrentPages() const pages = getCurrentPages()
const current = pages && pages.length ? pages[pages.length - 1] : null const current = pages && pages.length ? pages[pages.length - 1] : null
const route = current && current.route ? ('/' + current.route) : '/pages/index/index' const route = current && current.route ? ('/' + current.route) : '/pages/index/index'
const query = current && current.options ? Object.keys(current.options).map(k => `${k}=${encodeURIComponent(current.options[k])}`).join('&') : '' const query = current && current.options ? Object.keys(current.options).map(k =>
const redirect = encodeURIComponent(query ? `${route}?${query}` : route) `${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
uni.reLaunch({ url: `/pages/login/index?redirect=${redirect}` }) const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
} catch (e) { uni.reLaunch({
uni.reLaunch({ url: '/pages/login/index' }) url: `/pages/login/index?redirect=${redirect}`
} })
} } catch (e) {
uni.reLaunch({
url: '/pages/login/index'
})
}
}
import { import {
ref, ref,
computed, computed,
@@ -155,7 +160,8 @@
} from "../../config/url.js" } from "../../config/url.js"
import { import {
getDeviceInfo, getDeviceInfo,
getPotionsDetail getPotionsDetail,
getNoticeTextData
} from '../../config/user.js' } from '../../config/user.js'
// 导入地图工具函数 // 导入地图工具函数
import { import {
@@ -167,6 +173,13 @@
// 同样需要使用相对路径引入组件 // 同样需要使用相对路径引入组件
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径 // 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
import MapComponent from '../../components/MapComponent.vue' import MapComponent from '../../components/MapComponent.vue'
// 开启右上角分享菜单(仅 mp-weixin 有效)
// #ifdef MP-WEIXIN
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
// #endif
// 响应式数据 // 响应式数据
const searchKeyword = ref('') const searchKeyword = ref('')
@@ -180,26 +193,19 @@
const showLocationPopup = ref(false) const showLocationPopup = ref(false)
// 使用指南步骤 // 使用指南步骤
const guideSteps = ref([{ // 使用指南已取消
title: '扫码使用',
desc: '找到附近设备,扫描设备上的二维码即可开始租借'
},
{
title: '免押金支付',
desc: '无需支付押金,使用支付分免押即可完成租借'
},
{
title: '开始使用',
desc: '设备自动解锁,风扇弹出后取出即可开始使用'
},
{
title: '归还设备',
desc: '使用完毕后,按照设备规格要求将风扇还入即可结束订单'
}
])
// 滚动通知内容 // 滚动通知内容
const noticeText = ref('消费规则:每小时5元,不足1小时按1小时计费,最高24小时封顶,请爱护设备,使用后请及时归还') // const noticeText = ref('消费规则:每小时5元,不足1小时按1小时计费,最高24小时封顶,请爱护设备,使用后请及时归还')
const noticeText = ref('')
const getNoticeText = async()=>{
const parasm = {
'title':'用户端公告'
}
const res = await getNoticeTextData(parasm);
noticeText.value = res.data.noticeContent;
}
// 距离格式化函数 // 距离格式化函数
const formatDistance = (distanceInMeters) => { const formatDistance = (distanceInMeters) => {
@@ -239,6 +245,7 @@
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
testDistanceCalculation() testDistanceCalculation()
} }
await getNoticeText();
// 1. 先获取用户位置 // 1. 先获取用户位置
await getUserLocationAndAddress() await getUserLocationAndAddress()
@@ -329,11 +336,6 @@
const loadPositions = async () => { const loadPositions = async () => {
try { try {
if (!uni.getStorageSync('token')) {
redirectToLogin()
return
}
const res = await uni.request({ const res = await uni.request({
url: `${URL}/device/position/app/list`, url: `${URL}/device/position/app/list`,
method: 'GET', method: 'GET',
@@ -348,7 +350,10 @@
}) })
console.log(res); console.log(res);
if (res.statusCode === 200 && res.data.code === 200) { if (res.statusCode === 401 || res.data?.code === 401 || res.data?.code === 40101) {
redirectToLogin()
return
} else if (res.statusCode === 200 && res.data.code === 200) {
positionList.value = res.data.rows || [] positionList.value = res.data.rows || []
calculateDistances() calculateDistances()
filteredPositions.value = [...positionList.value] filteredPositions.value = [...positionList.value]
@@ -399,14 +404,9 @@
const loadPositionsByCenter = async (center) => { const loadPositionsByCenter = async (center) => {
try { try {
if (!uni.getStorageSync('token')) {
redirectToLogin()
return
}
// 使用原有接口获取所有场地 // 使用原有接口获取所有场地
const res = await uni.request({ const res = await uni.request({
url: `${URL}/device/position/list`, url: `${URL}/device/position/app/list`,
method: 'GET', method: 'GET',
header: { header: {
'Authorization': "Bearer " + uni.getStorageSync('token'), 'Authorization': "Bearer " + uni.getStorageSync('token'),
@@ -414,7 +414,10 @@
} }
}) })
if (res.statusCode === 200 && res.data.code === 200) { if (res.statusCode === 401 || res.data?.code === 401 || res.data?.code === 40101) {
redirectToLogin()
return
} else if (res.statusCode === 200 && res.data.code === 200) {
positionList.value = res.data.rows || [] positionList.value = res.data.rows || []
// 基于地图中心计算距离 // 基于地图中心计算距离
calculateDistances(center) calculateDistances(center)
@@ -437,17 +440,43 @@
} }
const handleRelocate = async () => { const handleRelocate = async () => {
uni.showLoading({ try {
title: '定位中...' uni.showLoading({
}) title: '定位中...'
})
// 直接重新加载当前页面 const loc = await getUserLocation()
uni.reLaunch({ const center = {
url: '/pages/index/index' longitude: Number(loc.longitude),
}) latitude: Number(loc.latitude)
}
userLocation.value = center
try {
uni.setStorageSync('userLocation', center)
} catch (_) {}
if (mapRef.value && typeof mapRef.value.moveToLocation === 'function') {
mapRef.value.moveToLocation(center)
}
await loadPositionsByCenter(center)
} catch (e) {
uni.showToast({
title: '定位失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
} }
const onMapCenterChange = (center) => { const onMapCenterChange = (center) => {
if (center && typeof center.longitude !== 'undefined' && typeof center.latitude !== 'undefined') {
userLocation.value = {
longitude: Number(center.longitude),
latitude: Number(center.latitude)
}
try {
uni.setStorageSync('userLocation', userLocation.value)
} catch (_) {}
}
loadPositionsByCenter(center) loadPositionsByCenter(center)
} }
@@ -467,6 +496,12 @@
}) })
} }
const goMy = () => {
uni.navigateTo({
url: '/pages/my/index'
})
}
const selectPositionFromPopup = (position) => { const selectPositionFromPopup = (position) => {
// 先关闭弹窗 // 先关闭弹窗
hideLocationList() hideLocationList()
@@ -524,11 +559,6 @@
return return
} }
if (!uni.getStorageSync('token')) {
redirectToLogin()
return
}
// 检查是否有使用中的订单 // 检查是否有使用中的订单
const inUseRes = await uni.request({ const inUseRes = await uni.request({
url: `${URL}/app/order/inUse`, url: `${URL}/app/order/inUse`,
@@ -539,7 +569,10 @@
} }
}) })
if (inUseRes.statusCode == 200 && inUseRes.data.code == 200 && inUseRes.data.data) { 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 const inUseOrder = inUseRes.data.data
uni.reLaunch({ uni.reLaunch({
url: `/pages/return/index?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}` url: `/pages/return/index?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
@@ -557,7 +590,10 @@
} }
}) })
if (orderRes.statusCode == 200 && orderRes.data.code == 200 && orderRes.data.data) { 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 const unpaidOrder = orderRes.data.data
uni.navigateTo({ uni.navigateTo({
url: `/pages/order/payment?orderId=${unpaidOrder.orderId}` url: `/pages/order/payment?orderId=${unpaidOrder.orderId}`
@@ -603,10 +639,10 @@
} }
} catch (error) { } catch (error) {
console.error('扫码处理失败:', error) console.error('扫码处理失败:', error)
uni.showToast({ // uni.showToast({
title: '扫码失败', // title: '扫码失败',
icon: 'none' // icon: 'none'
}) // })
} }
} }
@@ -625,15 +661,39 @@
} }
</script> </script>
<script>
// 选用 Page 级分享钩子(uni-app 需普通 script 导出)
export default {
// 分享给朋友
onShareAppMessage() {
return {
title: '风电者 - 共享风扇暖手充电宝',
path: '/pages/index/index',
// imageUrl: '/static/logo.png'
}
},
// 朋友圈
onShareTimeline() {
return {
title: '风电者 - 共享风扇暖手充电宝',
query: '',
// imageUrl: '/static/logo.png'
}
}
}
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
height: 100%; height: 100vh;
width: 100%; width: 100vw;
background-color: #f6f7fb; background-color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
// align-items: center; }
padding-top: 20rpx;
.fullscreen {
padding: 0;
} }
/* 顶部Logo和通知栏 */ /* 顶部Logo和通知栏 */
@@ -954,6 +1014,97 @@
} }
} }
/* 底部操作栏 */
.bottom-actions {
position: fixed;
left: 20rpx;
right: 20rpx;
bottom: 40rpx;
z-index: 1200;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
// gap: 16rpx;
padding: 0;
.action-btn {
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: center;
font-size: 26rpx;
font-weight: 600;
&.primary {
background: #07c160;
color: #fff;
border-radius: 64rpx;
height: 100rpx;
min-width: 320rpx;
// box-shadow: 0 16rpx 40rpx rgba(7, 193, 96, 0.35);
padding: 12rpx 24rpx;
.icon-wrap {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
// background: rgba(255, 255, 255, 0.25);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
}
}
&.secondary {
// background: rgba(255, 255, 255, 0.95);
color: #333;
border-radius: 24rpx;
height: 100rpx;
min-width: 180rpx;
// box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
padding: 12rpx 16rpx;
.icon-wrap {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #f7f8fa;
border: 2rpx solid #eaeaea;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
}
}
&.small {
height: 120rpx;
min-width: 180rpx;
border-radius: 28rpx;
}
}
.btn-nearby,
.btn-my {
gap: 10rpx;
}
.btn-scan {
flex-direction: row;
gap: 14rpx;
.icon-wrap {
margin-bottom: 0;
margin-right: 12rpx;
// background: rgba(255, 255, 255, 0.25);
box-shadow: none;
}
}
}
/* 加载状态 */ /* 加载状态 */
.loading-overlay { .loading-overlay {
position: fixed; position: fixed;
@@ -1119,90 +1270,29 @@
} }
} }
/* 操作步骤指引 */ .action-icon {
.steps-guide { width: 42rpx;
align-items: center; height: 42rpx;
align-content: center; filter: none;
background-color: rgba(255, 255, 255, 0.95); box-shadow: none;
}
.btn-scan .action-icon {
filter: brightness(0) invert(1);
}
.action-label {
line-height: 1;
}
.map-notice {
margin: 0 20rpx;
// position: absolute;
// left: 20rpx;
// right: 20rpx;
// top: 20rpx;
border-radius: 20rpx; border-radius: 20rpx;
padding: 0; z-index: 15;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
z-index: 10;
// max-width: calc(100% - 40rpx);
backdrop-filter: blur(15rpx);
border: 1rpx solid rgba(255, 255, 255, 0.9);
overflow: hidden;
width: 92%;
margin: 0 auto 20rpx;
}
.guide-header {
padding: 20rpx 24rpx;
background: linear-gradient(135deg, #2196F3, #1976D2);
border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
.guide-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
text-align: center;
display: block;
}
}
.steps-container {
display: flex;
flex-direction: column;
gap: 16rpx;
padding: 24rpx;
background-color: rgba(255, 255, 255, 0.9);
}
.step-item {
display: flex;
align-items: flex-start;
width: 100%;
padding: 16rpx 0;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
&:last-child {
border-bottom: none;
}
.step-number {
width: 40rpx;
height: 40rpx;
background: linear-gradient(135deg, #2196F3, #1976D2);
color: #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: bold;
margin-right: 20rpx;
flex-shrink: 0;
box-shadow: 0 4rpx 12rpx rgba(33, 150, 243, 0.4);
}
.step-content {
flex: 1;
padding-top: 4rpx;
.step-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
display: block;
}
.step-desc {
font-size: 26rpx;
color: #666;
display: block;
line-height: 1.5;
}
}
} }
</style> </style>
+134
View File
@@ -0,0 +1,134 @@
<template>
<view class="legal-page">
<view class="header">
<view class="title">用户协议</view>
<view class="subtitle">适用于风电者共享风扇租借服务最后更新{{ effectiveDate }}</view>
</view>
<scroll-view class="content" scroll-y>
<view class="h1">导言</view>
<view class="p">欢迎您使用{{ brandName }}共享风扇产品与相关服务用户协议下称本协议由您与{{ companyName }}我们就您使用{{ brandName }}小程序及租借共享风扇服务所订立</view>
<view class="p">在使用{{ brandName }}请您务必仔细阅读并充分理解本协议全部内容尤其是以加粗方式提示的条款包括但不限于责任限制争议解决适用法律未成年人保护等您点击登录/使用或实际使用服务即视为您已阅读并同意受本协议约束</view>
<view class="h1">账号与登录</view>
<view class="p">2.1 您可通过微信授权登录使用本服务为完成免押租借与订单结算您同意我们基于微信支付分进行信用评估及订单后结等必要处理</view>
<view class="p">2.2 您应保证提供信息真实准确完整并及时更新因您提供的信息不真实或未及时更新导致的服务受限订单异常或损失由您自行承担</view>
<view class="p">2.3 您应对账户下的全部行为负责妥善保管设备与账户凭证不得转借出租或以其他方式提供给他人使用</view>
<view class="h1">租借与使用规范</view>
<view class="p">3.1 租借流程{{ brandName }}小程序中发起租借 在设备端取用风扇 使用完毕后按指引在归还点归还或通过快递归还</view>
<view class="p">3.2 使用规范请合理使用设备避免进水摔落私自拆卸或改装请勿靠近明火与高温环境室外雨天请避免使用儿童应在监护下使用</view>
<view class="p">3.3 禁止行为将设备用于违法或不当用途以任何方式影响设备或系统的正常运行通过非正常手段规避计费或归还流程</view>
<view class="h1">计费与结算含微信支付分</view>
<view class="p"><text class="bold">4.1 计费规则</text>以小程序展示的实时计费规则为准可能包含时长计费封顶价服务费等订单生成后将据此计费</view>
<view class="p"><text class="bold">4.2 微信支付分免押</text>若您开通并通过信用评估可享受免押租借如评估未通过可能需预授权或押金具体以页面提示为准</view>
<view class="p">4.3 结算与扣款订单结束后我们将基于实际使用情况与平台规则完成结算并通过微信支付分/微信支付进行扣款</view>
<view class="p">4.4 异常与争议如对计费或结算有异议请在订单完成后48小时内通过我的-客服提交逾期可能影响处理结果</view>
<view class="h1">设备归还与逾期处理</view>
<view class="p">5.1 归还方式按照小程序指引在指定网点归还或通过快递归还功能寄回非指定方式可能导致订单异常与额外费用</view>
<view class="p">5.2 逾期处理未在规定时间内归还的系统将持续计费或按规则收取逾期费用长时间未归还的可能依约进行赔偿处理</view>
<view class="p">5.3 验收与结单归还后平台将进行完好性验收验收完成且费用结清后订单方可完结</view>
<view class="h1">违规损坏与赔偿</view>
<view class="p">6.1 设备损坏丢失若因不当使用故意破坏或未按规范保管导致设备损坏丢失您需按平台公示标准或实际维修/折损成本承担赔偿责任</view>
<view class="p">6.2 清洁与部件因人为污损缺失附件等造成的额外成本将据实向您收取</view>
<view class="p">6.3 风险控制如出现涉嫌恶意拖欠欺诈等平台可采取冻结服务追偿依法维权等措施</view>
<view class="h1">用户行为规范</view>
<view class="p">7.1 您承诺遵守法律法规与公序良俗不发表不传播违法违规或不当内容不干扰或破坏平台与设备的正常运行</view>
<view class="p">7.2 您不得对本小程序进行反向工程抓取或未经授权的自动化访问</view>
<view class="h1">知识产权</view>
<view class="p">8.1 {{ companyName }}及关联方对本小程序与服务中的商标标识界面文字图片代码等享有相应知识产权或合法授权</view>
<view class="p">8.2 未经书面许可任何人不得以任何方式使用复制传播或改作上述内容</view>
<view class="h1">免责声明与责任限制</view>
<view class="p"><text class="bold">9.1 由于不可抗力网络故障第三方服务稳定性等原因导致的服务中断或受限{{ companyName }}在法律允许范围内不承担责任但将尽力恢复服务</text></view>
<view class="p">9.2 您应对自身使用行为负责因您违反本协议或不当保管使用设备造成的损失由您自行承担或向相关方赔偿</view>
<view class="h1">隐私与个人信息保护</view>
<view class="p">10.1 我们严格按照隐私政策处理您的个人信息包括微信登录信息手机号经您授权后获取设备与订单信息位置与网点信息等</view>
<view class="p">10.2 详情请查阅本小程序内的隐私政策</view>
<view class="h1">十一服务变更与终止</view>
<view class="p">11.1 我们可能基于业务调整法律合规或用户体验优化对服务内容功能或规则进行变更或终止重要变更将通过小程序公告或站内消息提示</view>
<view class="p">11.2 如您不同意变更可停止使用并申请注销相关账户/信息受法律法规与账务结算限制</view>
<view class="h1">十二未成年人保护</view>
<view class="p">12.1 未成年人的监护人应指导其正确理解并遵守本协议未成年人使用服务应在监护下进行避免在危险环境中使用设备</view>
<view class="h1">十三通知与联系</view>
<view class="p">13.1 联系方式请通过小程序我的-客服与我们联系我们将尽快处理您的问题或争议</view>
<view class="h1">十四法律适用与争议解决</view>
<view class="p">14.1 本协议的订立生效履行解释与争议解决适用中华人民共和国法律不含冲突规范</view>
<view class="p">14.2 因本协议产生的争议优先友好协商协商不成的提交{{ disputeVenue }}有管辖权的人民法院诉讼解决</view>
<view class="h1">十五附则</view>
<view class="p">15.1 本协议自{{ effectiveDate }}起生效并长期有效除非我们另行发布版本更新</view>
<view class="p">15.2 协议条款如被认定无效或不可执行不影响其他条款的效力与执行</view>
</scroll-view>
<view class="footer">如对本协议有疑问请前往我的-客服咨询</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
const brandName = '风电者'
const companyName = '深圳乐慕智云科技有限公司'
const effectiveDate = '2025-10-13'
const disputeVenue = '平台所在地'
</script>
<style lang="scss" scoped>
.legal-page {
min-height: 100vh;
background: #f8f8f8;
padding: 24rpx 24rpx 40rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
.header {
margin-bottom: 16rpx;
.title {
font-size: 40rpx;
font-weight: 600;
color: #222;
margin-bottom: 8rpx;
}
.subtitle {
font-size: 24rpx;
color: #888;
}
}
.content {
// width: 100%;
background: #fff;
border-radius: 16rpx;
padding: 16rpx 20rpx;
flex: 1;
min-height: 0;
box-sizing: border-box;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04);
}
.h1 { font-size: 30rpx; font-weight: 600; color: #222; margin: 18rpx 0 12rpx; }
.p { font-size: 26rpx; color: #444; line-height: 1.8; margin-bottom: 10rpx; }
.bold { font-weight: 600; color: #222; }
.footer {
text-align: center;
margin-top: 16rpx;
font-size: 24rpx;
color: #888;
}
}
</style>
+145
View File
@@ -0,0 +1,145 @@
<template>
<view class="legal-page">
<view class="header">
<view class="title">隐私政策</view>
<view class="subtitle">适用于风电者共享风扇租借服务最后更新{{ effectiveDate }}</view>
</view>
<view class="card notice">
<view class="p">我们深知个人信息对您的重要性并会尽全力保护您的个人信息安全请您在使用{{ brandName }}服务前仔细阅读并理解本隐私政策</view>
</view>
<scroll-view class="content" scroll-y>
<view class="h1">适用范围</view>
<view class="p">
本政策由{{ companyName }}制定并发布适用于{{ brandName }}小程序及其提供的共享风扇租借服务我们在本文特指{{ companyName }}并将按照合法正当必要的原则处理您的个人信息
</view>
<view class="h1">我们收集的信息</view>
<view class="p">2.1 账号信息微信登录标识如openId/unionId昵称头像经您授权手机号经您授权后通过微信获取</view>
<view class="p">2.2 订单与设备信息租借记录使用时长费用归还点位设备状态异常记录等</view>
<view class="p">2.3 位置与网点信息在您授权后用于查找附近网点与导航不会在未授权情况下获取</view>
<view class="p">2.4 日志信息为保障服务安全与稳定我们可能记录操作日志网络请求与错误信息</view>
<view class="h1">信息使用目的</view>
<view class="p">3.1 提供核心功能身份验证免押租借微信支付分评估订单计费结算客服与售后</view>
<view class="p">3.2 安全风控防范欺诈违规与风险控制保障系统与设备安全</view>
<view class="p">3.3 产品优化统计与分析以改进体验在去标识化/匿名化后进行</view>
<view class="h1">微信支付分与支付</view>
<view class="p">4.1 为实现免押租借我们将与微信支付分进行必要的数据交互如信用评估结果订单结算相关数据处理遵循微信支付与微信支付分的规则</view>
<view class="p">4.2 如您未通过评估可能需进行预授权或押金处理以页面提示为准</view>
<view class="h1">共享转移与公开披露</view>
<view class="p">5.1 我们不会向第三方出售您的个人信息</view>
<view class="p">5.2 在实现必要功能时我们可能与合作方共享必要信息如支付与物流服务商并要求其按不低于本政策的标准保护您的信息</view>
<view class="p">5.3 因合并分立重组或破产清算导致的转移我们将要求新持有方继续受本政策约束否则将重新征得您的同意</view>
<view class="p">5.4 仅在法律法规或监管要求诉讼争议处理保护人身财产安全等情形下可能依法进行披露</view>
<view class="h1">信息存储与安全</view>
<view class="p">6.1 存储地点您的个人信息原则上存储于中华人民共和国境内如需跨境传输将遵循法律法规并征得您的同意</view>
<view class="p">6.2 存储期限为实现目的所必需的最短期限超期将删除或匿名化处理法律法规另有规定的除外</view>
<view class="p">6.3 安全措施我们采取加密传输访问控制最小化授权监控审计等措施保护您的信息安全</view>
<view class="h1">您的权利</view>
<view class="p">7.1 访问与更正您可通过我的-个人信息/客服访问或更正部分信息</view>
<view class="p">7.2 删除与撤回同意在符合法律与账务结算纠纷处理等必要条件时您可申请删除或撤回授权撤回后部分功能可能无法提供</view>
<view class="p">7.3 账号注销在符合条件并完成费用结清设备归还争议处理后您可申请注销账号</view>
<view class="h1">未成年人保护</view>
<view class="p">8.1 未成年人使用服务应在监护下进行我们不会在明知的情况下收集未成年人不必要的个人信息</view>
<view class="h1">政策更新与通知</view>
<view class="p">9.1 我们可能因功能迭代法律监管变化而更新本政策重要变更将通过小程序公告或站内消息提示更新后继续使用即视为您同意</view>
<view class="h1">联系我们</view>
<view class="p">10.1 您可通过我的-客服与我们联系以行使前述权利或就本政策提出疑问</view>
</scroll-view>
<view class="footer">如对本政策有疑问请前往我的-客服咨询</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
const brandName = '风电者'
const companyName = '深圳乐慕智云科技有限公司'
const effectiveDate = '2025-10-13'
</script>
<style lang="scss" scoped>
.legal-page {
min-height: 100vh;
background: #f8f8f8;
padding: 24rpx 24rpx 40rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
.header {
margin-bottom: 16rpx;
}
.title {
font-size: 40rpx;
font-weight: 600;
color: #222;
margin-bottom: 8rpx;
}
.subtitle {
font-size: 24rpx;
color: #888;
}
.card {
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
}
.notice {
margin-bottom: 16rpx;
}
.content {
background: #fff;
border-radius: 16rpx;
padding: 16rpx 20rpx;
flex: 1;
min-height: 0;
box-sizing: border-box;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
}
.h1 {
font-size: 30rpx;
font-weight: 600;
color: #222;
margin: 18rpx 0 12rpx;
}
.p {
font-size: 26rpx;
color: #444;
line-height: 1.8;
margin-bottom: 10rpx;
}
.bold {
font-weight: 600;
color: #222;
}
.footer {
text-align: center;
margin-top: 16rpx;
font-size: 24rpx;
color: #888;
}
}
</style>
+25 -14
View File
@@ -2,7 +2,7 @@
<view class="login-container"> <view class="login-container">
<view class="logo"> <view class="logo">
<image src="/static/logo.png" mode="aspectFit" /> <image src="/static/logo.png" mode="aspectFit" />
<text class="app-name">共享风扇</text> <text class="app-name">风电者共享风扇&充电宝</text>
</view> </view>
<view class="title">登录您的账号</view> <view class="title">登录您的账号</view>
@@ -14,9 +14,14 @@
</button> </button>
<!-- 仅微信登录不授权手机号时使用 --> <!-- 仅微信登录不授权手机号时使用 -->
<button class="btn outline" @click="onWeChatLogin">仅微信登录</button> <!-- <button class="btn outline" @click="onWeChatLogin">仅微信登录</button> -->
<view class="tips">登录即表示同意用户协议隐私政策</view> <view class="tips">
登录即表示同意
<text class="link" @tap="go('/pages/legal/agreement')">用户协议</text>
<text class="link" @tap="go('/pages/legal/privacy')">隐私政策</text>
</view>
</view> </view>
</template> </template>
@@ -37,7 +42,7 @@
const target = '/pages/index/index' const target = '/pages/index/index'
const tabPages = ['/pages/index/index', '/pages/my/index'] const tabPages = ['/pages/index/index', '/pages/my/index']
if (tabPages.includes(target)) { if (tabPages.includes(target)) {
uni.switchTab({ url: target }) uni.reLaunch({ url: target })
return return
} }
uni.reLaunch({ url: target }) uni.reLaunch({ url: target })
@@ -77,6 +82,10 @@
} catch (_) {} } catch (_) {}
} }
}) })
const go = (url) => {
uni.navigateTo({ url })
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -129,21 +138,23 @@
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
.primary { .primary {
background: #1976D2; background: #07c160;
color: #fff; color: #fff;
} box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
}
.outline { .outline {
background: #fff; background: #fff;
color: #1976D2; color: #07c160;
border: 2rpx solid #1976D2; border: 2rpx solid #07c160;
} }
.tips { .tips {
margin-top: 24rpx; margin-top: 24rpx;
font-size: 22rpx; font-size: 22rpx;
color: #999; color: #999;
.link { color: #07c160; }
} }
} }
</style> </style>
+58 -17
View File
@@ -88,6 +88,17 @@
<uni-icons type="right" size="16" color="#999"></uni-icons> <uni-icons type="right" size="16" color="#999"></uni-icons>
</view> </view>
</view> </view>
<view class="function-item" @click="doLogout()">
<view class="item-left">
<view class="item-icon">
<image src="/static/logout.png" mode="aspectFit"></image>
</view>
<text class="item-title">退出登录</text>
</view>
<view class="item-right">
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
</view> </view>
<!-- <!--
@@ -107,7 +118,7 @@
</view> </view>
</view> --> </view> -->
<!-- <u-popup ref="authPopup" mode="center" border-radius="15" width="600rpx" @open="onPopupOpen" @close="onPopupClose"> <!-- <u-popup ref="authPopup" mode="center" border-radius="15" width="600rpx" @open="onPopupOpen" @close="onPopupClose">
<view class="auth-popup"> <view class="auth-popup">
<view class="auth-title">授权登录</view> <view class="auth-title">授权登录</view>
<view class="auth-desc">获取您的微信头像昵称等公开信息</view> <view class="auth-desc">获取您的微信头像昵称等公开信息</view>
@@ -131,6 +142,9 @@
wxLogin, wxLogin,
getUserInfo getUserInfo
} from '../../util/index.js'; } from '../../util/index.js';
import {
userLogout
} from '@/config/user.js'
// 响应式状态 // 响应式状态
const userInfo = ref({}); const userInfo = ref({});
@@ -147,16 +161,13 @@
// 获取用户信息 // 获取用户信息
const getInfo = async () => { const getInfo = async () => {
try { try {
const token = uni.getStorageSync('token');
if (!token) {
redirectToLogin()
return
}
const res = await getUserInfo(); const res = await getUserInfo();
console.log('User info response:', res); console.log('User info response:', res);
if (res.code == 200) { if (res.code == 401 || res.code == 40101) {
redirectToLogin()
return
} else if (res.code == 200) {
// 保存openId // 保存openId
if (res.data.openId) { if (res.data.openId) {
openId.value = res.data.openId; openId.value = res.data.openId;
@@ -188,11 +199,16 @@
const pages = getCurrentPages() const pages = getCurrentPages()
const current = pages && pages.length ? pages[pages.length - 1] : null const current = pages && pages.length ? pages[pages.length - 1] : null
const route = current && current.route ? ('/' + current.route) : '/pages/index/index' const route = current && current.route ? ('/' + current.route) : '/pages/index/index'
const query = current && current.options ? Object.keys(current.options).map(k => `${k}=${encodeURIComponent(current.options[k])}`).join('&') : '' const query = current && current.options ? Object.keys(current.options).map(k =>
`${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
const redirect = encodeURIComponent(query ? `${route}?${query}` : route) const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
uni.reLaunch({ url: `/pages/login/index?redirect=${redirect}` }) uni.reLaunch({
url: `/pages/login/index?redirect=${redirect}`
})
} catch (e) { } catch (e) {
uni.reLaunch({ url: '/pages/login/index' }) uni.reLaunch({
url: '/pages/login/index'
})
} }
} }
@@ -345,6 +361,31 @@
// 只处理11位手机号 // 只处理11位手机号
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
} }
const doLogout = async () => {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: async (res) => {
if (res.confirm) {
const response = await userLogout();
if (response.code == 200) {
uni.showToast({
title:'退出成功',
icon:'none'
})
setTimeout(() => {
uni.removeStorageSync('token');
uni.removeStorageSync('userInfo');
uni.redirectTo({
url: '/pages/login/index'
})
}, 1500)
}
}
}
})
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -357,11 +398,11 @@
/* Header Section */ /* Header Section */
.header-section { .header-section {
padding: 40rpx; padding: 40rpx;
background: linear-gradient(135deg, #4facfe, #00f2fe); background: linear-gradient(135deg, #07c160, #05a14e);
position: relative; position: relative;
border-radius: 0 0 30rpx 30rpx; border-radius: 0 0 30rpx 30rpx;
margin-bottom: 20rpx; margin-bottom: 20rpx;
box-shadow: 0 10rpx 30rpx rgba(79, 172, 254, 0.2); box-shadow: 0 10rpx 30rpx rgba(7, 193, 96, 0.25);
} }
.user-profile { .user-profile {
@@ -437,11 +478,11 @@
.balance-amount { .balance-amount {
font-size: 48rpx; font-size: 48rpx;
font-weight: 600; font-weight: 600;
color: #4facfe; color: #07c160;
} }
.action-button { .action-button {
background: linear-gradient(135deg, #4facfe, #00f2fe); background: linear-gradient(135deg, #07c160, #05a14e);
border-radius: 40rpx; border-radius: 40rpx;
height: 80rpx; height: 80rpx;
display: flex; display: flex;
@@ -451,7 +492,7 @@
color: white; color: white;
font-weight: 500; font-weight: 500;
font-size: 30rpx; font-size: 30rpx;
box-shadow: 0 8rpx 16rpx rgba(79, 172, 254, 0.2); box-shadow: 0 8rpx 16rpx rgba(7, 193, 96, 0.25);
&:active { &:active {
opacity: 0.9; opacity: 0.9;
@@ -566,7 +607,7 @@
} }
.confirm-btn { .confirm-btn {
background: linear-gradient(135deg, #4facfe, #00f2fe); background: linear-gradient(135deg, #07c160, #05a14e);
color: white; color: white;
} }
</style> </style>
+9 -9
View File
@@ -432,7 +432,7 @@
z-index: 10; z-index: 10;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.tab-item { .tab-item {
flex: 1; flex: 1;
height: 90rpx; height: 90rpx;
display: flex; display: flex;
@@ -443,7 +443,7 @@
position: relative; position: relative;
&.active { &.active {
color: #1976D2; color: #07c160;
font-weight: 500; font-weight: 500;
&::after { &::after {
@@ -454,7 +454,7 @@
transform: translateX(-50%); transform: translateX(-50%);
width: 40rpx; width: 40rpx;
height: 4rpx; height: 4rpx;
background: #1976D2; background: #07c160;
border-radius: 2rpx; border-radius: 2rpx;
} }
} }
@@ -494,8 +494,8 @@
color: #FF9800; color: #FF9800;
} }
&.status-using { &.status-using {
color: #2196F3; color: #07c160;
} }
&.status-finished { &.status-finished {
@@ -647,10 +647,10 @@
justify-content: center; justify-content: center;
margin-bottom: 10rpx; margin-bottom: 10rpx;
&.primary { &.primary {
background: #1976D2; background: #07c160;
color: #fff; color: #fff;
} }
&.secondary { &.secondary {
background: #f5f5f5; background: #f5f5f5;
+79 -9
View File
@@ -177,8 +177,9 @@
currentStatusChecks: 0, currentStatusChecks: 0,
statusCheckInterval: 5000, // 5秒检查一次 statusCheckInterval: 5000, // 5秒检查一次
isPageActive: false, // 跟踪页面是否活跃 isPageActive: false, // 跟踪页面是否活跃
// 倒计时与快递归还触发(默认4小时=14400秒,可被配置覆盖) // 快递归还阈值(默认4小时=14400秒,可被系统配置覆盖),倒计时基于开始时间实时计算
// countdownRemaining: 14400, // expressThresholdSeconds: 14400,
expressThresholdSeconds: 180,
countdownRemaining: 0, countdownRemaining: 0,
showExpressAction: false, showExpressAction: false,
countdownTimer: null countdownTimer: null
@@ -227,6 +228,13 @@
} }
}) })
}, },
// 页面重新可见时,基于开始时间恢复倒计时(不重置阈值)
onShow() {
this.isPageActive = true
if (this.orderInfo.orderStatus === 'in_used') {
this.startExpressCountdown()
}
},
// 添加onHide生命周期,处理页面隐藏时的清理工作 // 添加onHide生命周期,处理页面隐藏时的清理工作
onHide() { onHide() {
console.log('归还页面隐藏,清理计时器资源和监控服务') console.log('归还页面隐藏,清理计时器资源和监控服务')
@@ -265,9 +273,13 @@
if (res && res.code === 200 && res.data && typeof res.data.expressReturnCountdownSeconds === 'number') { if (res && res.code === 200 && res.data && typeof res.data.expressReturnCountdownSeconds === 'number') {
const seconds = res.data.expressReturnCountdownSeconds const seconds = res.data.expressReturnCountdownSeconds
if (seconds > 0) { if (seconds > 0) {
this.countdownRemaining = seconds this.expressThresholdSeconds = seconds
} }
} }
// 配置加载后根据开始时间重新计算倒计时
if (this.orderInfo.orderStatus === 'in_used' && this.orderInfo.startTime) {
this.recomputeExpressCountdownFromStartTime()
}
} catch (e) { } catch (e) {
// 后端未实现或网络错误时,沿用默认 // 后端未实现或网络错误时,沿用默认
} }
@@ -275,8 +287,10 @@
// 启动快递归还倒计时 // 启动快递归还倒计时
startExpressCountdown() { startExpressCountdown() {
this.clearExpressCountdown() this.clearExpressCountdown()
this.showExpressAction = false // 基于开始时间重新计算剩余倒计时
// 使用当前设定的倒计时(可能已被系统配置覆盖) this.recomputeExpressCountdownFromStartTime()
// 若已到达阈值,直接展示快递归还入口
if (this.showExpressAction) return
this.countdownTimer = setInterval(() => { this.countdownTimer = setInterval(() => {
if (!this.isPageActive) { if (!this.isPageActive) {
this.clearExpressCountdown() this.clearExpressCountdown()
@@ -286,10 +300,9 @@
this.clearExpressCountdown() this.clearExpressCountdown()
return return
} }
if (this.countdownRemaining > 0) { // 每秒基于开始时间重新计算,避免计时误差累积
this.countdownRemaining -= 1 this.recomputeExpressCountdownFromStartTime()
} else { if (this.showExpressAction) {
this.showExpressAction = true
this.clearExpressCountdown() this.clearExpressCountdown()
} }
}, 1000) }, 1000)
@@ -302,6 +315,62 @@
this.countdownTimer = null this.countdownTimer = null
} }
}, },
// 解析开始时间字符串为时间戳(毫秒),兼容常见格式及 iOS/微信环境
parseStartTimeToMs(timeStr) {
if (!timeStr) return NaN
if (typeof timeStr === 'number') {
return timeStr < 1e12 ? timeStr * 1000 : timeStr
}
let normalized = String(timeStr).trim()
normalized = normalized.replace('T', ' ').replace(/\.\d+Z?$/, '')
const candidates = [
normalized,
normalized.replace(/-/g, '/')
]
for (let i = 0; i < candidates.length; i++) {
const ts = Date.parse(candidates[i])
if (!isNaN(ts)) return ts
}
const m = normalized.match(/(\d{4})[\/-](\d{1,2})[\/-](\d{1,2})\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?/)
if (m) {
const y = parseInt(m[1])
const mon = parseInt(m[2]) - 1
const d = parseInt(m[3])
const h = parseInt(m[4])
const min = parseInt(m[5])
const s = m[6] ? parseInt(m[6]) : 0
return new Date(y, mon, d, h, min, s).getTime()
}
const num = Number(normalized)
if (!isNaN(num)) {
return num < 1e12 ? num * 1000 : num
}
return NaN
},
// 基于开始时间与阈值计算剩余倒计时,仅在使用中生效
recomputeExpressCountdownFromStartTime() {
if (this.orderInfo.orderStatus !== 'in_used') {
this.showExpressAction = false
this.countdownRemaining = 0
return
}
const startMs = this.parseStartTimeToMs(this.orderInfo.startTime)
if (isNaN(startMs)) {
this.showExpressAction = false
this.countdownRemaining = 0
return
}
const nowMs = Date.now()
const elapsedSeconds = Math.max(0, Math.floor((nowMs - startMs) / 1000))
const remaining = this.expressThresholdSeconds - elapsedSeconds
if (remaining <= 0) {
this.countdownRemaining = 0
this.showExpressAction = true
} else {
this.countdownRemaining = remaining
this.showExpressAction = false
}
},
// 从订单监控服务中移除当前订单 // 从订单监控服务中移除当前订单
removeFromOrderMonitor() { removeFromOrderMonitor() {
if (this.orderInfo.orderId && this.$orderMonitor) { if (this.orderInfo.orderId && this.$orderMonitor) {
@@ -331,6 +400,7 @@
// 清理定时器 // 清理定时器
this.clearTimer() this.clearTimer()
this.clearStatusCheckTimer() this.clearStatusCheckTimer()
this.clearExpressCountdown()
// 显示归还成功弹窗 // 显示归还成功弹窗
uni.showModal({ uni.showModal({
+13 -15
View File
@@ -56,37 +56,35 @@ export default {
} }
}, },
onShow() { onShow() {
this.checkLoginStatus()
this.loadUserInfo() this.loadUserInfo()
}, },
methods: { methods: {
async loadUserInfo() { async loadUserInfo() {
try { try {
const res = await getUserInfo() const res = await getUserInfo()
if (res.code === 200) { if (res.code === 401 || res.code === 40101) {
// 无提示跳转至登录
try {
const pages = getCurrentPages()
const current = pages && pages.length ? pages[pages.length - 1] : null
const route = current && current.route ? ('/' + current.route) : '/pages/index/index'
const query = current && current.options ? Object.keys(current.options).map(k => `${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
uni.reLaunch({ url: `/pages/login/index?redirect=${redirect}` })
} catch (e) {
uni.reLaunch({ url: '/pages/login/index' })
}
} else if (res.code === 200) {
this.userInfo = res.data this.userInfo = res.data
this.isLogin = true this.isLogin = true
} else { } else {
this.isLogin = false this.isLogin = false
uni.showToast({
title: '获取用户信息失败',
icon: 'none'
})
} }
} catch (error) { } catch (error) {
console.error('加载用户信息失败:', error) console.error('加载用户信息失败:', error)
this.isLogin = false this.isLogin = false
} }
}, },
checkLoginStatus() {
const token = uni.getStorageSync('token')
this.isLogin = !!token
if (!this.isLogin) {
uni.redirectTo({
url: '/pages/login/index'
})
}
},
navigateTo(url) { navigateTo(url) {
uni.navigateTo({ url }) uni.navigateTo({ url })
}, },
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 676 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

+3 -3
View File
@@ -16,8 +16,8 @@
/* 颜色变量 */ /* 颜色变量 */
/* 行为相关颜色 */ /* 行为相关颜色 */
$uni-color-primary: #007aff; $uni-color-primary: #07c160; // brand green like Monster Charge
$uni-color-success: #4cd964; $uni-color-success: #07c160;
$uni-color-warning: #f0ad4e; $uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d; $uni-color-error: #dd524d;
@@ -69,7 +69,7 @@ $uni-spacing-col-lg: 12px;
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 $uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */ /* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色 $uni-color-title: #1f2937; // darker neutral
$uni-font-size-title:20px; $uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色 $uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px; $uni-font-size-subtitle:26px;