fix:修复bug
@@ -6,7 +6,24 @@
|
||||
<map id="map" class="native-map" :longitude="mapCenter.longitude" :latitude="mapCenter.latitude"
|
||||
:markers="mapMarkers" :scale="mapZoom" :show-location="true" @regionchange="onMapRegionChange"
|
||||
@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 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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
nextTick
|
||||
} from 'vue'
|
||||
nextTick,
|
||||
getCurrentInstance
|
||||
} from 'vue'
|
||||
// 导入地图工具函数
|
||||
import {
|
||||
calculateDistanceSync
|
||||
@@ -91,6 +92,14 @@
|
||||
searchKeyword: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
noticeText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
enableMarkers: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
@@ -115,90 +124,72 @@
|
||||
|
||||
// 方法
|
||||
const updateMapMarkers = () => {
|
||||
mapMarkers.value = []
|
||||
|
||||
// 添加用户位置标记
|
||||
if (props.userLocation) {
|
||||
mapMarkers.value.push({
|
||||
id: 0, // ID必须是数字
|
||||
// iconPath: '/static/scan-icon.png',
|
||||
width: 32,
|
||||
height: 32,
|
||||
latitude: props.userLocation.latitude,
|
||||
longitude: props.userLocation.longitude,
|
||||
title: '我的位置',
|
||||
callout: {
|
||||
content: '我的位置',
|
||||
color: '#ffffff',
|
||||
fontSize: 12,
|
||||
borderRadius: 4,
|
||||
bgColor: '#2196F3',
|
||||
padding: 6,
|
||||
display: 'BYCLICK' // 点击时显示
|
||||
},
|
||||
customCallout: {
|
||||
anchorX: 0,
|
||||
anchorY: 0
|
||||
}
|
||||
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.filteredPositions && props.filteredPositions.length > 0) {
|
||||
// 可选:周边位置点
|
||||
if (props.enableMarkers && props.filteredPositions && props.filteredPositions.length > 0) {
|
||||
props.filteredPositions.forEach((pos, index) => {
|
||||
if (pos.longitude && pos.latitude) {
|
||||
// 验证纬度值是否在有效范围内
|
||||
const lat = parseFloat(pos.latitude);
|
||||
const lng = parseFloat(pos.longitude);
|
||||
|
||||
// 检查纬度是否在-90到90之间,经度是否在-180到180之间
|
||||
const lat = parseFloat(pos.latitude)
|
||||
const lng = parseFloat(pos.longitude)
|
||||
if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
|
||||
mapMarkers.value.push({
|
||||
id: index + 1, // ID必须是数字,避免和用户位置的ID冲突
|
||||
// iconPath: '/static/scan-icon.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
markers.push({
|
||||
id: index + 1,
|
||||
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' // 点击时显示
|
||||
}
|
||||
width: 24,
|
||||
height: 24,
|
||||
title: pos.name
|
||||
})
|
||||
} else {
|
||||
console.warn(`忽略无效坐标: ${pos.name}, 纬度=${lat}, 经度=${lng}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mapMarkers.value = markers
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
// 移动地图到指定位置
|
||||
const moveToLocation = (location) => {
|
||||
// 移动地图到指定位置(使用 includePoints 以避免 mapid 错误,并兼容不同基础库)
|
||||
const moveToLocation = (location) => {
|
||||
if (!location || !location.longitude || !location.latitude) return
|
||||
if (!mapContext.value) return
|
||||
|
||||
if (mapContext.value) {
|
||||
mapContext.value.moveToLocation({
|
||||
longitude: location.longitude,
|
||||
latitude: location.latitude,
|
||||
try {
|
||||
mapContext.value.includePoints({
|
||||
points: [{ longitude: Number(location.longitude), latitude: Number(location.latitude) }],
|
||||
padding: [60, 60, 60, 60],
|
||||
success: () => {
|
||||
console.log('地图已移动到指定位置')
|
||||
console.log('地图已移动到指定位置(includePoints)')
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('移动地图失败:', error)
|
||||
fail: (err) => {
|
||||
console.warn('includePoints 失败,尝试 moveToLocation():', err)
|
||||
// 回退尝试(不传参,部分基础库仅支持移动到用户当前位置)
|
||||
try {
|
||||
mapContext.value.moveToLocation()
|
||||
} catch (e) {
|
||||
console.error('moveToLocation 回退失败:', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('移动地图异常:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听用户位置变化
|
||||
watch(() => props.userLocation, (newLocation) => {
|
||||
@@ -229,17 +220,18 @@
|
||||
|
||||
// 地图区域变化事件
|
||||
const onMapRegionChange = (e) => {
|
||||
// 当地图区域变化结束时,更新mapCenter
|
||||
if (e.type === 'end' && e.causedBy === 'drag') {
|
||||
// 获取地图中心点
|
||||
// 在手势或缩放结束时更新中心坐标
|
||||
if (e && e.type === 'end') {
|
||||
if (mapContext.value) {
|
||||
mapContext.value.getCenterLocation({
|
||||
success: (res) => {
|
||||
if (res.longitude && res.latitude) {
|
||||
if (res && res.longitude && res.latitude) {
|
||||
mapCenter.value = {
|
||||
longitude: res.longitude,
|
||||
latitude: res.latitude
|
||||
}
|
||||
// 更新中心 marker 位置
|
||||
updateMapMarkers()
|
||||
emit('mapCenterChange', mapCenter.value)
|
||||
}
|
||||
}
|
||||
@@ -284,8 +276,55 @@
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
const handleRelocate = () => {
|
||||
const handleRelocate = () => {
|
||||
// 直接委托父级处理定位并移动地图,避免内部重复弹 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 = () => {
|
||||
@@ -297,11 +336,18 @@
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
// 初始化地图上下文
|
||||
nextTick(() => {
|
||||
// 需要使用nextTick确保地图组件已经渲染
|
||||
const inst = getCurrentInstance()
|
||||
const vm = (inst && (inst.proxy || inst)) || undefined
|
||||
try {
|
||||
mapContext.value = uni.createMapContext('map', vm)
|
||||
} catch (e) {
|
||||
// 兼容:如果第二参不被支持,退回单参
|
||||
mapContext.value = uni.createMapContext('map')
|
||||
}
|
||||
updateMapMarkers()
|
||||
|
||||
// 初始化折叠面板
|
||||
@@ -309,7 +355,7 @@
|
||||
collapseRef.value.init()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理工作
|
||||
@@ -340,34 +386,29 @@
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
height: 60vh;
|
||||
/* 增加高度 */
|
||||
width: 92%;
|
||||
/* 略微增加宽度 */
|
||||
margin: 10rpx auto 30rpx;
|
||||
/* 调整上下间距,左右自动居中 */
|
||||
border-radius: 24rpx;
|
||||
/* 添加圆角 */
|
||||
// position: fixed;
|
||||
// top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 94vw;
|
||||
height: 78vh;
|
||||
margin: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
/* 确保圆角生效 */
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
/* 添加阴影效果 */
|
||||
|
||||
.map-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 24rpx;
|
||||
/* 内层也添加圆角 */
|
||||
border-radius: 0;
|
||||
|
||||
.native-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border-radius: 24rpx;
|
||||
/* 地图也添加圆角 */
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,41 +448,65 @@
|
||||
}
|
||||
}
|
||||
|
||||
.map-controls {
|
||||
/* 地图中心图钉(cover-view,避免使用 transform 以兼容小程序) */
|
||||
.center-pin {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
bottom: 20rpx;
|
||||
left: 20rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 16;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
min-width: 120rpx;
|
||||
/* 减小按钮宽度 */
|
||||
height: 70rpx;
|
||||
/* 减小按钮高度 */
|
||||
background: #ffffff;
|
||||
border-radius: 35rpx;
|
||||
.center-pin .pin-img {
|
||||
position: absolute;
|
||||
width: 48rpx;
|
||||
height: 64rpx;
|
||||
/* 使图钉尖端对准中心点:X 向左偏半宽,Y 向上偏高度-阴影间距 */
|
||||
margin-left: -24rpx;
|
||||
margin-top: -68rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
padding: 0 16rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
|
||||
padding: 0 18rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.control-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
@@ -449,65 +514,39 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.main-btn {
|
||||
min-width: 140rpx;
|
||||
/* 减小主按钮宽度 */
|
||||
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);
|
||||
&.search {
|
||||
border-color: #07c160;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scan-control {
|
||||
background: #2196F3;
|
||||
/* 展示用图标列(与 cover-view 对齐) */
|
||||
.map-side-icons {
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
bottom: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
|
||||
.control-icon {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
text {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.list-control {
|
||||
background: #4CAF50;
|
||||
|
||||
.control-icon {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
text {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.location-control {
|
||||
background: #ffffff;
|
||||
.side-btn {
|
||||
min-width: 160rpx;
|
||||
height: 72rpx;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border-radius: 36rpx;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
|
||||
padding: 0 18rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
|
||||
.control-icon {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
text {
|
||||
.label {
|
||||
margin-left: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,5 +562,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.side-icon{
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
// export const URL = "https://my.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 appid = "wx2165f0be356ae7a9" //小程序appid
|
||||
@@ -2,6 +2,7 @@ import request from './http'
|
||||
import { URL, appid } from './url'
|
||||
|
||||
|
||||
//用户登录
|
||||
export const login = (data) => {
|
||||
return request({
|
||||
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) => {
|
||||
return request({
|
||||
url: '/app/user/userInfo',
|
||||
@@ -306,3 +316,12 @@ export const cancelExpressReturn = (id) => {
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
//获取通知接口
|
||||
export const getNoticeTextData = (data)=>{
|
||||
return request({
|
||||
url: `/system/notice/title/${data.title}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -66,7 +66,7 @@
|
||||
"desc" : "您的位置信息将用于获取附近的设备"
|
||||
}
|
||||
},
|
||||
"requiredPrivateInfos" : [ "getLocation" ]
|
||||
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
"pages": [{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "附近场地"
|
||||
"navigationBarTitleText": "",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationStyle": "default",
|
||||
"enableShareAppMessage": true,
|
||||
"enableShareTimeline": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -20,6 +24,22 @@
|
||||
"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",
|
||||
"style": {
|
||||
@@ -134,23 +154,5 @@
|
||||
"navigationBarTitleText": "共享风扇",
|
||||
"navigationBarBackgroundColor": "#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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -21,18 +21,22 @@
|
||||
<view class="form-title">填写快递归还信息</view>
|
||||
<view class="form-item">
|
||||
<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 class="form-item">
|
||||
<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 class="tips" v-if="tipsText">{{ tipsText }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交操作条 -->
|
||||
<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>
|
||||
</template>
|
||||
@@ -45,13 +49,13 @@
|
||||
import {
|
||||
onLoad
|
||||
} from '@dcloudio/uni-app'
|
||||
import {
|
||||
import {
|
||||
queryById,
|
||||
applyExpressReturn,
|
||||
getExpressReturnByOrder,
|
||||
getExpressReturnDetail,
|
||||
fillExpressTrackingNumber
|
||||
} from '@/config/user.js'
|
||||
} from '@/config/user.js'
|
||||
|
||||
const orderId = ref('')
|
||||
const recordId = ref('')
|
||||
@@ -65,6 +69,7 @@ import {
|
||||
const phone = ref('')
|
||||
const trackingNumber = ref('')
|
||||
const tipsText = ref('')
|
||||
const submitting = ref(false)
|
||||
|
||||
onLoad(async (options) => {
|
||||
orderId.value = options?.orderId || ''
|
||||
@@ -117,7 +122,9 @@ import {
|
||||
|
||||
const loadRecordAndOrderByRecord = async () => {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中' })
|
||||
uni.showLoading({
|
||||
title: '加载中'
|
||||
})
|
||||
const res = await getExpressReturnDetail(recordId.value)
|
||||
if (res?.code === 200 && res.data) {
|
||||
if (res.data.orderId) {
|
||||
@@ -129,7 +136,10 @@ import {
|
||||
throw new Error(res?.msg || '获取记录失败')
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '加载失败', icon: 'none' })
|
||||
uni.showToast({
|
||||
title: e.message || '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
@@ -149,15 +159,22 @@ import {
|
||||
cancelText: '取消',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
uni.redirectTo({ url: `/pages/expressReturn/addExpressReturn?id=${rec.id}` })
|
||||
uni.redirectTo({
|
||||
url: `/pages/expressReturn/addExpressReturn?id=${rec.id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '已有归还记录', icon: 'none' })
|
||||
uni.showToast({
|
||||
title: '已有归还记录',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: `/pages/expressReturn/detail?id=${rec.id}` })
|
||||
uni.redirectTo({
|
||||
url: `/pages/expressReturn/detail?id=${rec.id}`
|
||||
})
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
@@ -186,13 +203,19 @@ import {
|
||||
return true
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!validate()) return
|
||||
const handleSubmit = async () => {
|
||||
if (!validate() || submitting.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
uni.showLoading({ title: isFillMode.value ? '补填中' : '提交中' })
|
||||
uni.showLoading({
|
||||
title: isFillMode.value ? '补填中' : '提交中'
|
||||
})
|
||||
let res
|
||||
if (isFillMode.value) {
|
||||
res = await fillExpressTrackingNumber({ id: Number(recordId.value), logisticsTrackingNumber: trackingNumber.value })
|
||||
res = await fillExpressTrackingNumber({
|
||||
id: Number(recordId.value),
|
||||
logisticsTrackingNumber: trackingNumber.value
|
||||
})
|
||||
} else {
|
||||
res = await applyExpressReturn({
|
||||
orderId: orderId.value,
|
||||
@@ -201,17 +224,26 @@ const handleSubmit = async () => {
|
||||
})
|
||||
}
|
||||
if (res && res.code === 200) {
|
||||
uni.showToast({ title: isFillMode.value ? '补填成功' : '提交成功', icon: 'success' })
|
||||
setTimeout(() => { uni.navigateBack() }, 800)
|
||||
uni.showToast({
|
||||
title: isFillMode.value ? '补填成功' : '提交成功',
|
||||
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' })
|
||||
uni.showToast({
|
||||
title: e.message || (isFillMode.value ? '补填失败' : '提交失败'),
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -287,6 +319,16 @@ const handleSubmit = async () => {
|
||||
font-size: 28rpx;
|
||||
color: #111827;
|
||||
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;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.primary-hover {
|
||||
background: #111827;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +1,38 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部Logo区域 -->
|
||||
<view class="header-section">
|
||||
<view class="logo-container">
|
||||
<image class="logo-image" src="/static/logo.png" mode="aspectFit" />
|
||||
<text class="app-name">共享风扇</text>
|
||||
<view class="container fullscreen">
|
||||
<view class="" style="font-size: 32rpx;font-weight: 600;margin: 15rpx 20rpx;">风电者共享风扇&充电宝</view>
|
||||
<view class="map-notice" v-if="noticeText">
|
||||
<uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
|
||||
icon="volume"></uv-notice-bar>
|
||||
</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 class="map-title">
|
||||
<text>附近场地</text>
|
||||
</view> -->
|
||||
|
||||
|
||||
<!-- 地图组件 -->
|
||||
<!-- 全屏地图组件 -->
|
||||
<MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
|
||||
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
|
||||
@relocate="handleRelocate" @scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
||||
@mapCenterChange="onMapCenterChange" />
|
||||
|
||||
<!-- 操作步骤指引(常驻显示) -->
|
||||
<view class="steps-guide">
|
||||
<view class="guide-header">
|
||||
<text class="guide-title">使用指南</text>
|
||||
<!-- 底部操作栏:附近设备 / 扫码使用 / 我的 -->
|
||||
<view class="bottom-actions">
|
||||
<view class="action-btn secondary small btn-nearby" @click="showLocationList">
|
||||
<view class="icon-wrap">
|
||||
<image class="action-icon" src="/static/map.png" mode="aspectFit" />
|
||||
</view>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -46,6 +44,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<!-- 场地列表弹窗 -->
|
||||
<view class="location-popup" v-if="showLocationPopup">
|
||||
<view class="popup-mask" @click="hideLocationList"></view>
|
||||
@@ -133,11 +133,16 @@
|
||||
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 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}` })
|
||||
uni.reLaunch({
|
||||
url: `/pages/login/index?redirect=${redirect}`
|
||||
})
|
||||
} catch (e) {
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
import {
|
||||
@@ -155,7 +160,8 @@
|
||||
} from "../../config/url.js"
|
||||
import {
|
||||
getDeviceInfo,
|
||||
getPotionsDetail
|
||||
getPotionsDetail,
|
||||
getNoticeTextData
|
||||
} from '../../config/user.js'
|
||||
// 导入地图工具函数
|
||||
import {
|
||||
@@ -167,6 +173,13 @@
|
||||
// 同样需要使用相对路径引入组件
|
||||
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
|
||||
import MapComponent from '../../components/MapComponent.vue'
|
||||
// 开启右上角分享菜单(仅 mp-weixin 有效)
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menus: ['shareAppMessage', 'shareTimeline']
|
||||
})
|
||||
// #endif
|
||||
|
||||
// 响应式数据
|
||||
const searchKeyword = ref('')
|
||||
@@ -180,26 +193,19 @@
|
||||
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) => {
|
||||
@@ -239,6 +245,7 @@
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
testDistanceCalculation()
|
||||
}
|
||||
await getNoticeText();
|
||||
|
||||
// 1. 先获取用户位置
|
||||
await getUserLocationAndAddress()
|
||||
@@ -329,11 +336,6 @@
|
||||
|
||||
const loadPositions = async () => {
|
||||
try {
|
||||
if (!uni.getStorageSync('token')) {
|
||||
redirectToLogin()
|
||||
return
|
||||
}
|
||||
|
||||
const res = await uni.request({
|
||||
url: `${URL}/device/position/app/list`,
|
||||
method: 'GET',
|
||||
@@ -348,7 +350,10 @@
|
||||
})
|
||||
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 || []
|
||||
calculateDistances()
|
||||
filteredPositions.value = [...positionList.value]
|
||||
@@ -399,14 +404,9 @@
|
||||
|
||||
const loadPositionsByCenter = async (center) => {
|
||||
try {
|
||||
if (!uni.getStorageSync('token')) {
|
||||
redirectToLogin()
|
||||
return
|
||||
}
|
||||
|
||||
// 使用原有接口获取所有场地
|
||||
const res = await uni.request({
|
||||
url: `${URL}/device/position/list`,
|
||||
url: `${URL}/device/position/app/list`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'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 || []
|
||||
// 基于地图中心计算距离
|
||||
calculateDistances(center)
|
||||
@@ -437,17 +440,43 @@
|
||||
}
|
||||
|
||||
const handleRelocate = async () => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '定位中...'
|
||||
})
|
||||
|
||||
// 直接重新加载当前页面
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
const loc = await getUserLocation()
|
||||
const center = {
|
||||
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) => {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -467,6 +496,12 @@
|
||||
})
|
||||
}
|
||||
|
||||
const goMy = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/my/index'
|
||||
})
|
||||
}
|
||||
|
||||
const selectPositionFromPopup = (position) => {
|
||||
// 先关闭弹窗
|
||||
hideLocationList()
|
||||
@@ -524,11 +559,6 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (!uni.getStorageSync('token')) {
|
||||
redirectToLogin()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有使用中的订单
|
||||
const inUseRes = await uni.request({
|
||||
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
|
||||
uni.reLaunch({
|
||||
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
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/payment?orderId=${unpaidOrder.orderId}`
|
||||
@@ -603,10 +639,10 @@
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('扫码处理失败:', error)
|
||||
uni.showToast({
|
||||
title: '扫码失败',
|
||||
icon: 'none'
|
||||
})
|
||||
// uni.showToast({
|
||||
// title: '扫码失败',
|
||||
// icon: 'none'
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,15 +661,39 @@
|
||||
}
|
||||
</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>
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #f6f7fb;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// align-items: center;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 顶部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 {
|
||||
position: fixed;
|
||||
@@ -1119,90 +1270,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 操作步骤指引 */
|
||||
.steps-guide {
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
.action-icon {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
filter: none;
|
||||
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;
|
||||
padding: 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
z-index: 15;
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -2,7 +2,7 @@
|
||||
<view class="login-container">
|
||||
<view class="logo">
|
||||
<image src="/static/logo.png" mode="aspectFit" />
|
||||
<text class="app-name">共享风扇</text>
|
||||
<text class="app-name">风电者共享风扇&充电宝</text>
|
||||
</view>
|
||||
|
||||
<view class="title">登录您的账号</view>
|
||||
@@ -14,9 +14,14 @@
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@@ -37,7 +42,7 @@
|
||||
const target = '/pages/index/index'
|
||||
const tabPages = ['/pages/index/index', '/pages/my/index']
|
||||
if (tabPages.includes(target)) {
|
||||
uni.switchTab({ url: target })
|
||||
uni.reLaunch({ url: target })
|
||||
return
|
||||
}
|
||||
uni.reLaunch({ url: target })
|
||||
@@ -77,6 +82,10 @@
|
||||
} catch (_) {}
|
||||
}
|
||||
})
|
||||
|
||||
const go = (url) => {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -130,20 +139,22 @@
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: #1976D2;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
.outline {
|
||||
background: #fff;
|
||||
color: #1976D2;
|
||||
border: 2rpx solid #1976D2;
|
||||
color: #07c160;
|
||||
border: 2rpx solid #07c160;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
.link { color: #07c160; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -88,6 +88,17 @@
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</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>
|
||||
|
||||
<!--
|
||||
@@ -107,7 +118,7 @@
|
||||
</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-title">授权登录</view>
|
||||
<view class="auth-desc">获取您的微信头像、昵称等公开信息</view>
|
||||
@@ -131,6 +142,9 @@
|
||||
wxLogin,
|
||||
getUserInfo
|
||||
} from '../../util/index.js';
|
||||
import {
|
||||
userLogout
|
||||
} from '@/config/user.js'
|
||||
|
||||
// 响应式状态
|
||||
const userInfo = ref({});
|
||||
@@ -147,16 +161,13 @@
|
||||
// 获取用户信息
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
const token = uni.getStorageSync('token');
|
||||
if (!token) {
|
||||
redirectToLogin()
|
||||
return
|
||||
}
|
||||
|
||||
const res = await getUserInfo();
|
||||
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
|
||||
if (res.data.openId) {
|
||||
openId.value = res.data.openId;
|
||||
@@ -188,11 +199,16 @@
|
||||
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 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}` })
|
||||
uni.reLaunch({
|
||||
url: `/pages/login/index?redirect=${redirect}`
|
||||
})
|
||||
} catch (e) {
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,6 +361,31 @@
|
||||
// 只处理11位手机号
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -357,11 +398,11 @@
|
||||
/* Header Section */
|
||||
.header-section {
|
||||
padding: 40rpx;
|
||||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||||
background: linear-gradient(135deg, #07c160, #05a14e);
|
||||
position: relative;
|
||||
border-radius: 0 0 30rpx 30rpx;
|
||||
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 {
|
||||
@@ -437,11 +478,11 @@
|
||||
.balance-amount {
|
||||
font-size: 48rpx;
|
||||
font-weight: 600;
|
||||
color: #4facfe;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||||
background: linear-gradient(135deg, #07c160, #05a14e);
|
||||
border-radius: 40rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
@@ -451,7 +492,7 @@
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
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 {
|
||||
opacity: 0.9;
|
||||
@@ -566,7 +607,7 @@
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||||
background: linear-gradient(135deg, #07c160, #05a14e);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
@@ -443,7 +443,7 @@
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #1976D2;
|
||||
color: #07c160;
|
||||
font-weight: 500;
|
||||
|
||||
&::after {
|
||||
@@ -454,7 +454,7 @@
|
||||
transform: translateX(-50%);
|
||||
width: 40rpx;
|
||||
height: 4rpx;
|
||||
background: #1976D2;
|
||||
background: #07c160;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
@@ -495,7 +495,7 @@
|
||||
}
|
||||
|
||||
&.status-using {
|
||||
color: #2196F3;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
&.status-finished {
|
||||
@@ -648,7 +648,7 @@
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&.primary {
|
||||
background: #1976D2;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
@@ -177,8 +177,9 @@
|
||||
currentStatusChecks: 0,
|
||||
statusCheckInterval: 5000, // 5秒检查一次
|
||||
isPageActive: false, // 跟踪页面是否活跃
|
||||
// 倒计时与快递归还触发(默认4小时=14400秒,可被配置覆盖)
|
||||
// countdownRemaining: 14400,
|
||||
// 快递归还阈值(默认4小时=14400秒,可被系统配置覆盖),倒计时基于开始时间实时计算
|
||||
// expressThresholdSeconds: 14400,
|
||||
expressThresholdSeconds: 180,
|
||||
countdownRemaining: 0,
|
||||
showExpressAction: false,
|
||||
countdownTimer: null
|
||||
@@ -227,6 +228,13 @@
|
||||
}
|
||||
})
|
||||
},
|
||||
// 页面重新可见时,基于开始时间恢复倒计时(不重置阈值)
|
||||
onShow() {
|
||||
this.isPageActive = true
|
||||
if (this.orderInfo.orderStatus === 'in_used') {
|
||||
this.startExpressCountdown()
|
||||
}
|
||||
},
|
||||
// 添加onHide生命周期,处理页面隐藏时的清理工作
|
||||
onHide() {
|
||||
console.log('归还页面隐藏,清理计时器资源和监控服务')
|
||||
@@ -265,9 +273,13 @@
|
||||
if (res && res.code === 200 && res.data && typeof res.data.expressReturnCountdownSeconds === 'number') {
|
||||
const seconds = res.data.expressReturnCountdownSeconds
|
||||
if (seconds > 0) {
|
||||
this.countdownRemaining = seconds
|
||||
this.expressThresholdSeconds = seconds
|
||||
}
|
||||
}
|
||||
// 配置加载后根据开始时间重新计算倒计时
|
||||
if (this.orderInfo.orderStatus === 'in_used' && this.orderInfo.startTime) {
|
||||
this.recomputeExpressCountdownFromStartTime()
|
||||
}
|
||||
} catch (e) {
|
||||
// 后端未实现或网络错误时,沿用默认
|
||||
}
|
||||
@@ -275,8 +287,10 @@
|
||||
// 启动快递归还倒计时
|
||||
startExpressCountdown() {
|
||||
this.clearExpressCountdown()
|
||||
this.showExpressAction = false
|
||||
// 使用当前设定的倒计时(可能已被系统配置覆盖)
|
||||
// 基于开始时间重新计算剩余倒计时
|
||||
this.recomputeExpressCountdownFromStartTime()
|
||||
// 若已到达阈值,直接展示快递归还入口
|
||||
if (this.showExpressAction) return
|
||||
this.countdownTimer = setInterval(() => {
|
||||
if (!this.isPageActive) {
|
||||
this.clearExpressCountdown()
|
||||
@@ -286,10 +300,9 @@
|
||||
this.clearExpressCountdown()
|
||||
return
|
||||
}
|
||||
if (this.countdownRemaining > 0) {
|
||||
this.countdownRemaining -= 1
|
||||
} else {
|
||||
this.showExpressAction = true
|
||||
// 每秒基于开始时间重新计算,避免计时误差累积
|
||||
this.recomputeExpressCountdownFromStartTime()
|
||||
if (this.showExpressAction) {
|
||||
this.clearExpressCountdown()
|
||||
}
|
||||
}, 1000)
|
||||
@@ -302,6 +315,62 @@
|
||||
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() {
|
||||
if (this.orderInfo.orderId && this.$orderMonitor) {
|
||||
@@ -331,6 +400,7 @@
|
||||
// 清理定时器
|
||||
this.clearTimer()
|
||||
this.clearStatusCheckTimer()
|
||||
this.clearExpressCountdown()
|
||||
|
||||
// 显示归还成功弹窗
|
||||
uni.showModal({
|
||||
|
||||
@@ -56,37 +56,35 @@ export default {
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.checkLoginStatus()
|
||||
this.loadUserInfo()
|
||||
},
|
||||
methods: {
|
||||
async loadUserInfo() {
|
||||
try {
|
||||
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.isLogin = true
|
||||
} else {
|
||||
this.isLogin = false
|
||||
uni.showToast({
|
||||
title: '获取用户信息失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户信息失败:', error)
|
||||
this.isLogin = false
|
||||
}
|
||||
},
|
||||
checkLoginStatus() {
|
||||
const token = uni.getStorageSync('token')
|
||||
this.isLogin = !!token
|
||||
if (!this.isLogin) {
|
||||
uni.redirectTo({
|
||||
url: '/pages/login/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
navigateTo(url) {
|
||||
uni.navigateTo({ url })
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@@ -16,8 +16,8 @@
|
||||
/* 颜色变量 */
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-primary: #07c160; // brand green like Monster Charge
|
||||
$uni-color-success: #07c160;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
@@ -69,7 +69,7 @@ $uni-spacing-col-lg: 12px;
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-color-title: #1f2937; // darker neutral
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
|
||||