Compare commits
2 Commits
5772dccfbd
...
346a17f8b2
| Author | SHA1 | Date |
|---|---|---|
|
|
346a17f8b2 | |
|
|
c32de7df7d |
78
App.vue
78
App.vue
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { updateLuOrderPayStatus } from "@/api/service";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
globalData: {
|
globalData: {
|
||||||
// 用于从首页跳转到服务页面时传递需要高亮的分类
|
// 用于从首页跳转到服务页面时传递需要高亮的分类
|
||||||
|
|
@ -22,6 +24,7 @@
|
||||||
|
|
||||||
// 从收银台小程序返回的逻辑
|
// 从收银台小程序返回的逻辑
|
||||||
const extraData = options.referrerInfo.extraData;
|
const extraData = options.referrerInfo.extraData;
|
||||||
|
console.log("extraData",extraData)
|
||||||
|
|
||||||
if (!extraData) {
|
if (!extraData) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|
@ -31,10 +34,23 @@
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (extraData.code === 'success') {
|
if (extraData.code === 'success') {
|
||||||
uni.showToast({
|
// 有返回成功标记后,启动轮询查询订单状态
|
||||||
title: '支付成功',
|
// 1. 优先使用收银台回传的订单号
|
||||||
icon: 'success'
|
const orderNumberFromExtra =
|
||||||
});
|
extraData.orderNumber || extraData.reqsn || extraData.orderNo;
|
||||||
|
// 2. 如果对方没有回传,则使用我们在跳转前自己缓存到本地的订单号
|
||||||
|
const storedOrderNumber = uni.getStorageSync("lastOrderNumber");
|
||||||
|
const orderNumber = orderNumberFromExtra || storedOrderNumber;
|
||||||
|
|
||||||
|
if (orderNumber) {
|
||||||
|
this.startOrderStatusPolling(orderNumber);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付返回缺少订单号,请稍后在服务记录中查看',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (extraData.code === 'cancel') {
|
} else if (extraData.code === 'cancel') {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '支付已取消',
|
title: '支付已取消',
|
||||||
|
|
@ -52,6 +68,11 @@
|
||||||
},
|
},
|
||||||
onHide: function() {
|
onHide: function() {
|
||||||
console.log('App Hide')
|
console.log('App Hide')
|
||||||
|
// 页面整体隐藏时,清理轮询定时器
|
||||||
|
if (this._orderStatusTimer) {
|
||||||
|
clearInterval(this._orderStatusTimer);
|
||||||
|
this._orderStatusTimer = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
|
|
@ -80,6 +101,55 @@
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 启动轮询支付状态
|
||||||
|
startOrderStatusPolling(orderNumber) {
|
||||||
|
// 避免重复轮询
|
||||||
|
if (this._orderStatusTimer) {
|
||||||
|
clearInterval(this._orderStatusTimer);
|
||||||
|
this._orderStatusTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let times = 0;
|
||||||
|
const maxTimes = 5; // 最多轮询 20 次,大约 1 分钟
|
||||||
|
|
||||||
|
this._orderStatusTimer = setInterval(async () => {
|
||||||
|
times++;
|
||||||
|
try {
|
||||||
|
const res = await updateLuOrderPayStatus({ orderNumber });
|
||||||
|
// 约定:接口有返回数据即认为支付成功
|
||||||
|
if (res) {
|
||||||
|
clearInterval(this._orderStatusTimer);
|
||||||
|
this._orderStatusTimer = null;
|
||||||
|
// 成功后清理本地缓存的订单号
|
||||||
|
uni.removeStorageSync("lastOrderNumber");
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 跳转到服务记录页面,默认展示「待核销」tab
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/profileSub/serviceRecords?tab=pending_verification'
|
||||||
|
});
|
||||||
|
} else if (times >= maxTimes) {
|
||||||
|
clearInterval(this._orderStatusTimer);
|
||||||
|
this._orderStatusTimer = null;
|
||||||
|
uni.showToast({
|
||||||
|
title: '支付状态确认超时,请稍后在服务记录中查看',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询订单支付状态失败:', error);
|
||||||
|
if (times >= maxTimes) {
|
||||||
|
clearInterval(this._orderStatusTimer);
|
||||||
|
this._orderStatusTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,24 @@ export function getMessagePage(params = {}) {
|
||||||
data: params,
|
data: params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 会员点赞和取消根据赞状态设置
|
||||||
|
export function setMemberLike(params = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/labor-union-zan/zan',
|
||||||
|
method: 'POST',
|
||||||
|
data: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 会员收藏
|
||||||
|
export function setMemberCollect(params = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/labor-union-collect/collect',
|
||||||
|
method: 'POST',
|
||||||
|
data: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
// 获取支付的签名
|
// 获取支付的签名
|
||||||
export function getPaySign(params = {}){
|
export function getPaySign(params = {}){
|
||||||
return request({
|
return request({
|
||||||
|
|
@ -63,6 +81,36 @@ export function appBuy (params = {}){
|
||||||
data: params,
|
data: params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建会员司机公会分享记录
|
||||||
|
export function createMemberShare(params = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/labor-union-share/create',
|
||||||
|
method: 'POST',
|
||||||
|
data: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断是否需要地图
|
||||||
|
export function isNeedMap(params = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/app/getCnf',
|
||||||
|
method: 'GET',
|
||||||
|
data: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工会优惠券/商品下单并发起微信支付(需要后端返回小程序支付参数)
|
||||||
|
// 约定:后端返回字段需包含 timeStamp、nonceStr、package、signType、paySign(或等价字段)
|
||||||
|
// export function createGuildCouponWxPay(params = {}) {
|
||||||
|
// return request({
|
||||||
|
// // TODO: 如后端实际路径不同,请替换这里的 url
|
||||||
|
// url: '/app-api/member/labor-union-coupon/pay',
|
||||||
|
// method: 'POST',
|
||||||
|
// data: params,
|
||||||
|
// showLoading: true,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
// 获取lu(司机公会简称) 订单信息
|
// 获取lu(司机公会简称) 订单信息
|
||||||
export function getLuOrder (id){
|
export function getLuOrder (id){
|
||||||
return request({
|
return request({
|
||||||
|
|
@ -79,6 +127,15 @@ export function getLuMyOrderPage (params = {}){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 订单支付状态更新
|
||||||
|
export function updateLuOrderPayStatus(params = {}){
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/lu-order/getPayStatus',
|
||||||
|
method: 'GET',
|
||||||
|
data: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 删除优惠卷购买 (暂时不用)
|
// 删除优惠卷购买 (暂时不用)
|
||||||
// export function delCouponPurchaseBuy(id){
|
// export function delCouponPurchaseBuy(id){
|
||||||
// return request({
|
// return request({
|
||||||
|
|
@ -93,4 +150,32 @@ export function getLuMyOrderPage (params = {}){
|
||||||
// url: '/app-api/member/labor-union-coupon-purchase/cancel?id='+id,
|
// url: '/app-api/member/labor-union-coupon-purchase/cancel?id='+id,
|
||||||
// method: 'POST',
|
// method: 'POST',
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// 获得我的订单列表
|
||||||
|
export function getMyOrderPage(params = {}){
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/lu-order/myPage',
|
||||||
|
method: 'GET',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除订单
|
||||||
|
export function deleteOrder(id){
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/lu-order/delete?id='+id,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消订单(待支付状态)
|
||||||
|
export function cancelOrder(params = {}){
|
||||||
|
return request({
|
||||||
|
url: '/app-api/member/lu-order/cancel',
|
||||||
|
method: 'POST',
|
||||||
|
data: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
<template>
|
||||||
|
<view class="detail-action-bar">
|
||||||
|
<!-- <view class="action-item" @click="handleContact">
|
||||||
|
<image class="action-icon" src="/static/service/phone_icon.png" mode="aspectFill"></image>
|
||||||
|
<text class="action-text">联系</text>
|
||||||
|
</view> -->
|
||||||
|
|
||||||
|
<view class="action-item" @click="handleLike">
|
||||||
|
<image class="action-icon" src="/static/service/like_icon.png" mode="aspectFill"></image>
|
||||||
|
<text class="action-text">点赞</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<button class="action-item share-button" open-type="share" @click="handleShareClick">
|
||||||
|
<image class="action-icon" src="/static/service/share_icon.png" mode="aspectFill"></image>
|
||||||
|
<text class="action-text">分享</text>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<view class="action-item" @click="handleCollect">
|
||||||
|
<image class="action-icon" src="/static/service/collect_icon.png" mode="aspectFill"></image>
|
||||||
|
<text class="action-text">收藏</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { setMemberLike, setMemberCollect, createMemberShare } from '@/api/service.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DetailActionBar',
|
||||||
|
props: {
|
||||||
|
// 是否已点赞
|
||||||
|
liked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否已收藏
|
||||||
|
collected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 联系电话(用于联系功能)
|
||||||
|
contactPhone: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 内容ID
|
||||||
|
id: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
// 点赞类型
|
||||||
|
zanType: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
// 内容类型(用于收藏接口)
|
||||||
|
type: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLiked: this.liked,
|
||||||
|
isCollected: this.collected,
|
||||||
|
likeLoading: false,
|
||||||
|
collectLoading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
liked(newVal) {
|
||||||
|
this.isLiked = newVal
|
||||||
|
},
|
||||||
|
collected(newVal) {
|
||||||
|
this.isCollected = newVal
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 处理联系
|
||||||
|
handleContact() {
|
||||||
|
if (this.contactPhone) {
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber: this.contactPhone,
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('拨打电话失败:', err)
|
||||||
|
uni.showToast({
|
||||||
|
title: '拨打电话失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$emit('contact')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理点赞
|
||||||
|
async handleLike() {
|
||||||
|
if (this.likeLoading) return
|
||||||
|
|
||||||
|
|
||||||
|
const newLikedState = !this.isLiked
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.likeLoading = true
|
||||||
|
// 先更新UI状态,提供即时反馈
|
||||||
|
this.isLiked = newLikedState
|
||||||
|
|
||||||
|
const res = await setMemberLike({
|
||||||
|
zanId: this.id,
|
||||||
|
zanType: this.zanType,
|
||||||
|
status: newLikedState ? 1 : 2 ,//赞状态不能为空 1 赞,2 取消赞
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
uni.showToast({
|
||||||
|
title: newLikedState ? '点赞成功' : '取消点赞',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 1500
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('点赞操作失败:', error)
|
||||||
|
// 操作失败,恢复原状态
|
||||||
|
this.isLiked = !newLikedState
|
||||||
|
uni.showToast({
|
||||||
|
title: '操作失败,请重试',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.likeLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理分享点击(用于非微信小程序平台)
|
||||||
|
handleShareClick() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
// 微信小程序中,button 的 open-type="share" 会自动触发分享
|
||||||
|
// 不需要额外处理
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
// 其他平台,触发分享事件
|
||||||
|
this.$emit('share')
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
// 分享成功后调用接口(由页面调用)
|
||||||
|
async handleShareSuccess() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await createMemberShare({
|
||||||
|
shareId: this.id,
|
||||||
|
shareContentType: this.type
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
console.log('分享记录创建成功')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建分享记录失败:', error)
|
||||||
|
// 分享记录失败不影响用户体验,只记录错误
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理收藏
|
||||||
|
async handleCollect() {
|
||||||
|
if (this.collectLoading) return
|
||||||
|
|
||||||
|
|
||||||
|
const newCollectedState = !this.isCollected
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.collectLoading = true
|
||||||
|
// 先更新UI状态,提供即时反馈
|
||||||
|
this.isCollected = newCollectedState
|
||||||
|
|
||||||
|
const res = await setMemberCollect({
|
||||||
|
collectId: this.id,
|
||||||
|
collectType: this.type,
|
||||||
|
status: newCollectedState ? 1 : 2 ,//收藏状态不能为空 1 收藏,2 取消收藏
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
uni.showToast({
|
||||||
|
title: newCollectedState ? '收藏成功' : '取消收藏',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 1500
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('收藏操作失败:', error)
|
||||||
|
// 操作失败,恢复原状态
|
||||||
|
this.isCollected = !newCollectedState
|
||||||
|
uni.showToast({
|
||||||
|
title: '操作失败,请重试',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.collectLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.detail-action-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-top: 1rpx solid #e2e8f1;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.action-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
padding: 10rpx 0;
|
||||||
|
|
||||||
|
&.share-button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
line-height: normal;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10rpx 0;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
font-size: 48rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-text {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666666;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #004294;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
28
pages.json
28
pages.json
|
|
@ -52,13 +52,6 @@
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "activitiesDetail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "工会详情",
|
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "mapDetail",
|
"path": "mapDetail",
|
||||||
"style": {
|
"style": {
|
||||||
|
|
@ -118,12 +111,33 @@
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pointsMemberRules",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "积分会员制度",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "serviceRecords",
|
"path": "serviceRecords",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "服务记录",
|
"navigationBarTitleText": "服务记录",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "userAgreement",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户服务协议",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "privacyPolicy",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "隐私政策",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ export default {
|
||||||
handleActivityClick(item) {
|
handleActivityClick(item) {
|
||||||
// 点击活动项跳转到详情页
|
// 点击活动项跳转到详情页
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/detail/activitiesDetail?id=${item.id}`
|
url: `/pages/detail/richTextDetail?id=${item.id}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ export default {
|
||||||
async handleJoinActivity(item) {
|
async handleJoinActivity(item) {
|
||||||
// 跳转到活动详情页
|
// 跳转到活动详情页
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/detail/activitiesDetail?id=${item.id}`
|
url: `/pages/detail/richTextDetail?id=${item.id}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,9 @@
|
||||||
></image>
|
></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="collect-content">
|
<view class="collect-content">
|
||||||
<view class="collect-title">{{ item.title || item.collectName || '未命名' }}</view>
|
<view class="collect-title">{{
|
||||||
|
item.title || item.collectName || "未命名"
|
||||||
|
}}</view>
|
||||||
<view class="collect-desc" v-if="item.info">{{ item.info }}</view>
|
<view class="collect-desc" v-if="item.info">{{ item.info }}</view>
|
||||||
<view class="collect-time">{{ formatTime(item.createTime) }}</view>
|
<view class="collect-time">{{ formatTime(item.createTime) }}</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -48,7 +50,9 @@
|
||||||
<!-- 加载更多提示 -->
|
<!-- 加载更多提示 -->
|
||||||
<view class="load-more" v-if="collectList.length > 0">
|
<view class="load-more" v-if="collectList.length > 0">
|
||||||
<text v-if="loadingMore" class="load-more-text">加载中...</text>
|
<text v-if="loadingMore" class="load-more-text">加载中...</text>
|
||||||
<text v-else-if="!hasMore" class="load-more-text">没有更多数据了</text>
|
<text v-else-if="!hasMore" class="load-more-text"
|
||||||
|
>没有更多数据了</text
|
||||||
|
>
|
||||||
<text v-else class="load-more-text">上拉加载更多</text>
|
<text v-else class="load-more-text">上拉加载更多</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -62,7 +66,7 @@ import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NavHeader
|
NavHeader,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -158,25 +162,9 @@ export default {
|
||||||
|
|
||||||
// 收藏项点击
|
// 收藏项点击
|
||||||
handleCollectClick(item) {
|
handleCollectClick(item) {
|
||||||
// 根据收藏类型跳转到对应的详情页
|
// uni.navigateTo({
|
||||||
// collectType: 收藏的类型
|
// url: `/pages/detail/richTextDetail?id=${item.collectId}`,
|
||||||
// collectId: 收藏的id
|
// });
|
||||||
if (item.collectType && item.collectId) {
|
|
||||||
// 这里可以根据 collectType 判断跳转到不同的详情页
|
|
||||||
// 例如:1-活动详情,2-服务详情等
|
|
||||||
if (item.collectType === 2) {
|
|
||||||
// 假设类型2是活动
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/detail/activitiesDetail?id=${item.collectId}`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 其他类型可以在这里扩展
|
|
||||||
uni.showToast({
|
|
||||||
title: "暂不支持查看详情",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,259 +0,0 @@
|
||||||
<template>
|
|
||||||
<view class="activities-detail-page">
|
|
||||||
<!-- 顶部导航栏 -->
|
|
||||||
<NavHeader title="工会详情" />
|
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<scroll-view class="content-scroll" scroll-y="true">
|
|
||||||
<!-- 加载中 -->
|
|
||||||
<view class="loading-state" v-if="loading">
|
|
||||||
<text class="loading-text">加载中...</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 富文本内容 - 混合渲染 -->
|
|
||||||
<view class="content-wrapper" v-else-if="parsedContent && parsedContent.length > 0">
|
|
||||||
<block v-for="(item, index) in parsedContent" :key="index">
|
|
||||||
<!-- 如果是图片,使用原生 image 组件 -->
|
|
||||||
<view v-if="item.type === 'image'" class="rich-text-image-wrapper">
|
|
||||||
<image
|
|
||||||
class="rich-text-image"
|
|
||||||
:src="item.src"
|
|
||||||
mode="widthFix"
|
|
||||||
:lazy-load="true"
|
|
||||||
></image>
|
|
||||||
</view>
|
|
||||||
<!-- 如果是文本内容,使用 rich-text -->
|
|
||||||
<rich-text v-else-if="item.type === 'text'" :nodes="item.html" class="rich-text-content"></rich-text>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 空数据提示 -->
|
|
||||||
<view class="empty-state" v-else>
|
|
||||||
<image
|
|
||||||
class="empty-icon"
|
|
||||||
src="/static/home/entry_icon.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
></image>
|
|
||||||
<text class="empty-text">暂无内容</text>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { getGuildDetail } from "@/api/home.js";
|
|
||||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
NavHeader
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
activityId: null,
|
|
||||||
activityDetail: null,
|
|
||||||
parsedContent: [], // 解析后的内容数组
|
|
||||||
loading: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
onLoad(options) {
|
|
||||||
if (options.id) {
|
|
||||||
this.activityId = options.id;
|
|
||||||
this.loadActivityDetail();
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: "参数错误",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.navigateBack();
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 加载活动详情
|
|
||||||
async loadActivityDetail() {
|
|
||||||
if (!this.activityId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
|
||||||
const res = await getGuildDetail(this.activityId);
|
|
||||||
if (res) {
|
|
||||||
this.activityDetail = res;
|
|
||||||
// 解析 HTML,分离图片和文本
|
|
||||||
if (res.content) {
|
|
||||||
this.parsedContent = this.parseHtmlContent(res.content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("加载活动详情失败:", error);
|
|
||||||
uni.showToast({
|
|
||||||
title: "加载失败,请重试",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 解析 HTML 内容,将图片和文本分离
|
|
||||||
parseHtmlContent(html) {
|
|
||||||
if (!html) return [];
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
let currentIndex = 0;
|
|
||||||
|
|
||||||
// 匹配所有 img 标签
|
|
||||||
const imgRegex = /<img([^>]*)>/gi;
|
|
||||||
let match;
|
|
||||||
let lastIndex = 0;
|
|
||||||
|
|
||||||
while ((match = imgRegex.exec(html)) !== null) {
|
|
||||||
// 添加图片之前的文本内容
|
|
||||||
if (match.index > lastIndex) {
|
|
||||||
const textContent = html.substring(lastIndex, match.index);
|
|
||||||
if (textContent.trim()) {
|
|
||||||
result.push({
|
|
||||||
type: 'text',
|
|
||||||
html: textContent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取图片 src
|
|
||||||
const imgAttrs = match[1];
|
|
||||||
const srcMatch = imgAttrs.match(/src=["']([^"']+)["']/i);
|
|
||||||
if (srcMatch && srcMatch[1]) {
|
|
||||||
result.push({
|
|
||||||
type: 'image',
|
|
||||||
src: srcMatch[1]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = match.index + match[0].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加最后剩余的文本内容
|
|
||||||
if (lastIndex < html.length) {
|
|
||||||
const textContent = html.substring(lastIndex);
|
|
||||||
if (textContent.trim()) {
|
|
||||||
result.push({
|
|
||||||
type: 'text',
|
|
||||||
html: textContent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有匹配到图片,直接返回整个内容作为文本
|
|
||||||
if (result.length === 0 && html.trim()) {
|
|
||||||
result.push({
|
|
||||||
type: 'text',
|
|
||||||
html: html
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.activities-detail-page {
|
|
||||||
height: 100vh;
|
|
||||||
background-color: #e2e8f1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 内容区域 */
|
|
||||||
.content-scroll {
|
|
||||||
flex: 1;
|
|
||||||
height: 0; // 配合 flex: 1 使用,让 scroll-view 可以滚动
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-wrapper {
|
|
||||||
padding: 30rpx 20rpx;
|
|
||||||
background-color: #ffffff;
|
|
||||||
margin: 20rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
min-height: 200rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: visible;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
|
|
||||||
// 图片容器样式
|
|
||||||
.rich-text-image-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
margin: 20rpx 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.rich-text-image {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文本内容样式
|
|
||||||
.rich-text-content {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
font-family: PingFang-SC, PingFang-SC;
|
|
||||||
font-size: 28rpx;
|
|
||||||
line-height: 1.8;
|
|
||||||
color: #333333;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载状态 */
|
|
||||||
.loading-state {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 200rpx 0;
|
|
||||||
min-height: 500rpx;
|
|
||||||
|
|
||||||
.loading-text {
|
|
||||||
font-family: PingFang-SC, PingFang-SC;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 空数据提示 */
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 200rpx 0;
|
|
||||||
min-height: 500rpx;
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
width: 200rpx;
|
|
||||||
height: 200rpx;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
font-family: PingFang-SC, PingFang-SC;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -12,9 +12,9 @@
|
||||||
:longitude="mapCenter.longitude"
|
:longitude="mapCenter.longitude"
|
||||||
:markers="markers"
|
:markers="markers"
|
||||||
:scale="16"
|
:scale="16"
|
||||||
:show-location="false"
|
:show-location="true"
|
||||||
:enable-zoom="false"
|
:enable-zoom="true"
|
||||||
:enable-scroll="false"
|
:enable-scroll="true"
|
||||||
:enable-rotate="false"
|
:enable-rotate="false"
|
||||||
:enable-poi="true"
|
:enable-poi="true"
|
||||||
></map>
|
></map>
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
src="/static/service/location-icon.png"
|
src="/static/service/location-icon.png"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image>
|
||||||
<text class="distance-text">{{ storeInfo.distance || "0" }}km</text>
|
<text class="distance-text">{{ distanceText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -74,7 +74,9 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- <view class="enter-store-btn" @click="handleEnterStore"> 进店 </view> -->
|
<view class="action-row">
|
||||||
|
<view class="action-btn action-nav" @click="handleNavigate">导航</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -89,6 +91,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getGuildStoreDetail } from "@/api/service";
|
import { getGuildStoreDetail } from "@/api/service";
|
||||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
import { transformFromWGSToGCJ } from "@/utils/coordinate";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -110,6 +113,8 @@ export default {
|
||||||
userLocation: null,
|
userLocation: null,
|
||||||
// 地图是否已初始化(避免重复移动)
|
// 地图是否已初始化(避免重复移动)
|
||||||
mapInitialized: false,
|
mapInitialized: false,
|
||||||
|
// 店铺坐标(GCJ-02,用于地图显示)
|
||||||
|
storeLocationGCJ: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -132,6 +137,29 @@ export default {
|
||||||
// ? `${(distance * 1000).toFixed(0)}m`
|
// ? `${(distance * 1000).toFixed(0)}m`
|
||||||
// : `${distance.toFixed(1)}km`;
|
// : `${distance.toFixed(1)}km`;
|
||||||
// },
|
// },
|
||||||
|
distanceText() {
|
||||||
|
if (
|
||||||
|
!this.userLocation ||
|
||||||
|
!this.storeInfo ||
|
||||||
|
!this.storeInfo.latitude ||
|
||||||
|
!this.storeInfo.longitude
|
||||||
|
) {
|
||||||
|
return "0km";
|
||||||
|
}
|
||||||
|
// 计算距离时使用原始 WGS-84 坐标(店铺坐标)和用户位置坐标
|
||||||
|
// 注意:如果用户位置也是 WGS-84,直接计算;如果是 GCJ-02,需要转换
|
||||||
|
// 但距离计算对坐标系不敏感,误差很小,可以直接使用
|
||||||
|
const lat = parseFloat(this.storeInfo.latitude);
|
||||||
|
const lng = parseFloat(this.storeInfo.longitude);
|
||||||
|
if (!this.isValidCoordinate(lat, lng)) return "0km";
|
||||||
|
const distance = this.calculateDistance(
|
||||||
|
this.userLocation.latitude,
|
||||||
|
this.userLocation.longitude,
|
||||||
|
lat,
|
||||||
|
lng
|
||||||
|
);
|
||||||
|
return distance < 1 ? `${(distance * 1000).toFixed(0)}m` : `${distance.toFixed(1)}km`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
this.shopId = options.id;
|
this.shopId = options.id;
|
||||||
|
|
@ -188,16 +216,46 @@ export default {
|
||||||
if (res.latitude && res.longitude) {
|
if (res.latitude && res.longitude) {
|
||||||
// 验证经纬度是否有效
|
// 验证经纬度是否有效
|
||||||
if (this.isValidCoordinate(res.latitude, res.longitude)) {
|
if (this.isValidCoordinate(res.latitude, res.longitude)) {
|
||||||
// 直接设置地图中心点,避免从默认位置移动
|
// 后台返回的是 WGS-84 坐标系,需要转换为 GCJ-02(腾讯地图使用)
|
||||||
this.mapCenter = {
|
try {
|
||||||
latitude: parseFloat(res.latitude),
|
const wgs84Lat = parseFloat(res.latitude);
|
||||||
longitude: parseFloat(res.longitude),
|
const wgs84Lng = parseFloat(res.longitude);
|
||||||
};
|
const gcj02Coord = transformFromWGSToGCJ(wgs84Lat, wgs84Lng);
|
||||||
this.setMapMarkers();
|
|
||||||
// 标记地图已初始化,此时再显示地图,避免移动动画
|
// 保存转换后的坐标
|
||||||
this.$nextTick(() => {
|
this.storeLocationGCJ = {
|
||||||
this.mapInitialized = true;
|
latitude: gcj02Coord.latitude,
|
||||||
});
|
longitude: gcj02Coord.longitude,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用转换后的坐标设置地图中心点
|
||||||
|
this.mapCenter = {
|
||||||
|
latitude: gcj02Coord.latitude,
|
||||||
|
longitude: gcj02Coord.longitude,
|
||||||
|
};
|
||||||
|
this.setMapMarkers();
|
||||||
|
// 标记地图已初始化,此时再显示地图,避免移动动画
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.mapInitialized = true;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("坐标转换失败:", error);
|
||||||
|
// 转换失败时使用原始坐标(降级处理)
|
||||||
|
const fallbackLat = parseFloat(res.latitude);
|
||||||
|
const fallbackLng = parseFloat(res.longitude);
|
||||||
|
this.storeLocationGCJ = {
|
||||||
|
latitude: fallbackLat,
|
||||||
|
longitude: fallbackLng,
|
||||||
|
};
|
||||||
|
this.mapCenter = {
|
||||||
|
latitude: fallbackLat,
|
||||||
|
longitude: fallbackLng,
|
||||||
|
};
|
||||||
|
this.setMapMarkers();
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.mapInitialized = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("店铺经纬度无效:", res.latitude, res.longitude);
|
console.warn("店铺经纬度无效:", res.latitude, res.longitude);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|
@ -227,10 +285,13 @@ export default {
|
||||||
|
|
||||||
// 设置地图标记点
|
// 设置地图标记点
|
||||||
setMapMarkers() {
|
setMapMarkers() {
|
||||||
if (!this.storeInfo.latitude || !this.storeInfo.longitude) return;
|
// 使用转换后的 GCJ-02 坐标
|
||||||
|
if (!this.storeLocationGCJ || !this.storeLocationGCJ.latitude || !this.storeLocationGCJ.longitude) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lat = parseFloat(this.storeInfo.latitude);
|
const lat = this.storeLocationGCJ.latitude;
|
||||||
const lng = parseFloat(this.storeInfo.longitude);
|
const lng = this.storeLocationGCJ.longitude;
|
||||||
|
|
||||||
// 再次验证经纬度有效性
|
// 再次验证经纬度有效性
|
||||||
if (!this.isValidCoordinate(lat, lng)) {
|
if (!this.isValidCoordinate(lat, lng)) {
|
||||||
|
|
@ -259,7 +320,66 @@ export default {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 导航到店铺(打开系统地图)
|
||||||
|
handleNavigate() {
|
||||||
|
// 使用转换后的 GCJ-02 坐标进行导航(微信的 openLocation 需要 GCJ-02 坐标系)
|
||||||
|
if (!this.storeLocationGCJ || !this.storeLocationGCJ.latitude || !this.storeLocationGCJ.longitude) {
|
||||||
|
uni.showToast({ title: "暂无位置信息", icon: "none" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lat = this.storeLocationGCJ.latitude;
|
||||||
|
const lng = this.storeLocationGCJ.longitude;
|
||||||
|
if (!this.isValidCoordinate(lat, lng)) {
|
||||||
|
uni.showToast({ title: "位置信息无效", icon: "none" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在微信小程序中,使用 wx.openLocation 打开地图选择器
|
||||||
|
// 用户可以选择使用哪个地图 App(高德、百度、腾讯等)来导航
|
||||||
|
// wx.openLocation 需要 GCJ-02 坐标系
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.openLocation({
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lng,
|
||||||
|
name: this.storeInfo.name || "店铺位置",
|
||||||
|
address: this.storeInfo.address || "",
|
||||||
|
scale: 18,
|
||||||
|
success: () => {
|
||||||
|
console.log("打开地图成功");
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error("打开地图失败:", err);
|
||||||
|
uni.showToast({
|
||||||
|
title: "打开地图失败,请检查位置权限",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
// 非微信小程序环境,使用 uni.openLocation
|
||||||
|
uni.openLocation({
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lng,
|
||||||
|
name: this.storeInfo.name || "店铺位置",
|
||||||
|
address: this.storeInfo.address || "",
|
||||||
|
success: () => {
|
||||||
|
console.log("打开地图成功");
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error("打开地图失败:", err);
|
||||||
|
uni.showToast({
|
||||||
|
title: "打开地图失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
// 计算两点之间的距离(单位:km)
|
// 计算两点之间的距离(单位:km)
|
||||||
|
// 注意:计算距离时使用原始 WGS-84 坐标,因为距离计算不受坐标系影响
|
||||||
calculateDistance(lat1, lng1, lat2, lng2) {
|
calculateDistance(lat1, lng1, lat2, lng2) {
|
||||||
// 验证坐标有效性
|
// 验证坐标有效性
|
||||||
if (
|
if (
|
||||||
|
|
@ -466,6 +586,30 @@ export default {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-top: auto;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 12rpx 30rpx;
|
||||||
|
border-radius: 25rpx;
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-nav {
|
||||||
|
background: #004294;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-call {
|
||||||
|
background: #ff6b00;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="rich-text-detail-page">
|
<view class="rich-text-detail-page">
|
||||||
<!-- 顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<NavHeader :title="title || '详情'" />
|
<NavHeader title="详情" />
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<scroll-view class="content-scroll" scroll-y="true">
|
<scroll-view class="content-scroll" scroll-y="true">
|
||||||
|
|
@ -11,7 +11,10 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 富文本内容 - 混合渲染 -->
|
<!-- 富文本内容 - 混合渲染 -->
|
||||||
<view class="content-wrapper" v-else-if="parsedContent && parsedContent.length > 0">
|
<view
|
||||||
|
class="content-wrapper"
|
||||||
|
v-else-if="parsedContent && parsedContent.length > 0"
|
||||||
|
>
|
||||||
<block v-for="(item, index) in parsedContent" :key="index">
|
<block v-for="(item, index) in parsedContent" :key="index">
|
||||||
<!-- 如果是图片,使用原生 image 组件 -->
|
<!-- 如果是图片,使用原生 image 组件 -->
|
||||||
<view v-if="item.type === 'image'" class="rich-text-image-wrapper">
|
<view v-if="item.type === 'image'" class="rich-text-image-wrapper">
|
||||||
|
|
@ -23,7 +26,11 @@
|
||||||
></image>
|
></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- 如果是文本内容,使用 rich-text -->
|
<!-- 如果是文本内容,使用 rich-text -->
|
||||||
<rich-text v-else-if="item.type === 'text'" :nodes="item.html" class="rich-text-content"></rich-text>
|
<rich-text
|
||||||
|
v-else-if="item.type === 'text'"
|
||||||
|
:nodes="item.html"
|
||||||
|
class="rich-text-content"
|
||||||
|
></rich-text>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -37,103 +44,204 @@
|
||||||
<text class="empty-text">暂无内容</text>
|
<text class="empty-text">暂无内容</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<DetailActionBar
|
||||||
|
v-if="detailData"
|
||||||
|
:liked="isLiked"
|
||||||
|
:collected="isCollected"
|
||||||
|
:id="detailId"
|
||||||
|
:zanType="noticeType"
|
||||||
|
:type="noticeType"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { getGuildDetail } from "@/api/home.js";
|
||||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
import DetailActionBar from "@/components/DetailActionBar/DetailActionBar.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NavHeader
|
NavHeader,
|
||||||
|
DetailActionBar,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
title: '',
|
detailId: null, // 详情ID(用于通过接口获取数据)
|
||||||
content: '',
|
title: "",
|
||||||
|
content: "",
|
||||||
parsedContent: [], // 解析后的内容数组
|
parsedContent: [], // 解析后的内容数组
|
||||||
loading: false,
|
loading: false,
|
||||||
|
isLiked: false, // 是否已点赞
|
||||||
|
isCollected: false, // 是否已收藏
|
||||||
|
detailData: null, // 详情数据(通过接口获取时使用)
|
||||||
|
noticeType: null, // 内容类型
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
// 从路由参数获取标题和内容
|
// 支持两种模式:
|
||||||
if (options.title) {
|
// 1. 通过 id 调用接口获取详情(原 activitiesDetail 的用法)
|
||||||
this.title = decodeURIComponent(options.title);
|
// 2. 直接传入 title 和 content(原 richTextDetail 的用法)
|
||||||
}
|
if (options.id) {
|
||||||
|
this.detailId = options.id;
|
||||||
if (options.content) {
|
this.loadDetailById();
|
||||||
|
} else if (options.content) {
|
||||||
|
// 直接传入内容模式
|
||||||
|
if (options.title) {
|
||||||
|
this.title = decodeURIComponent(options.title);
|
||||||
|
}
|
||||||
let content = decodeURIComponent(options.content);
|
let content = decodeURIComponent(options.content);
|
||||||
// 解析 HTML,分离图片和文本
|
// 解析 HTML,分离图片和文本
|
||||||
this.parsedContent = this.parseHtmlContent(content);
|
this.parsedContent = this.parseHtmlContent(content);
|
||||||
} else {
|
if (options.info) {
|
||||||
uni.showToast({
|
// info 从上个页面传过来是 encodeURIComponent(JSON.stringify(obj)),这里要 decode + JSON.parse
|
||||||
title: "缺少内容",
|
try {
|
||||||
icon: "none",
|
const parsed = JSON.parse(decodeURIComponent(options.info));
|
||||||
});
|
this.detailData = parsed;
|
||||||
setTimeout(() => {
|
this.detailId = parsed.id || this.detailId;
|
||||||
uni.navigateBack();
|
// 兼容字段:noticeType/messageType/type
|
||||||
}, 1500);
|
this.noticeType = parsed.noticeType ?? parsed.messageType ?? parsed.type ?? this.noticeType;
|
||||||
|
// 如果没传 title,则用 info 里的 title
|
||||||
|
if (!this.title && parsed.title) {
|
||||||
|
this.title = parsed.title;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析 info 失败:', e, options.info);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 分享给朋友(微信小程序)
|
||||||
|
onShareAppMessage() {
|
||||||
|
if (!this.detailData) {
|
||||||
|
return {
|
||||||
|
title: this.title || "详情",
|
||||||
|
path: this.detailId
|
||||||
|
? `/pages/detail/richTextDetail?id=${this.detailId}`
|
||||||
|
: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: this.detailData.title || this.title || "详情",
|
||||||
|
path: `/pages/detail/richTextDetail?id=${this.detailId}`,
|
||||||
|
imageUrl: this.detailData.coverUrl || "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 分享到朋友圈(微信小程序)
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
onShareTimeline() {
|
||||||
|
if (!this.detailData) {
|
||||||
|
return {
|
||||||
|
title: this.title || "详情",
|
||||||
|
query: this.detailId ? `id=${this.detailId}` : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: this.detailData.title || this.title || "详情",
|
||||||
|
query: `id=${this.detailId}`,
|
||||||
|
imageUrl: this.detailData.coverUrl || "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
methods: {
|
methods: {
|
||||||
|
// 通过 ID 加载详情(原 activitiesDetail 的功能)
|
||||||
|
async loadDetailById() {
|
||||||
|
if (!this.detailId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
const res = await getGuildDetail(this.detailId);
|
||||||
|
if (res) {
|
||||||
|
this.detailData = res;
|
||||||
|
this.noticeType = res.noticeType;
|
||||||
|
this.title = res.title || "详情";
|
||||||
|
// 解析 HTML,分离图片和文本
|
||||||
|
if (res.content) {
|
||||||
|
this.parsedContent = this.parseHtmlContent(res.content);
|
||||||
|
}
|
||||||
|
// 设置点赞和收藏状态(如果接口返回了这些字段)
|
||||||
|
if (res.isLiked !== undefined) {
|
||||||
|
this.isLiked = res.isLiked;
|
||||||
|
}
|
||||||
|
if (res.isCollected !== undefined) {
|
||||||
|
this.isCollected = res.isCollected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载详情失败:", error);
|
||||||
|
uni.showToast({
|
||||||
|
title: "加载失败,请重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 解析 HTML 内容,将图片和文本分离
|
// 解析 HTML 内容,将图片和文本分离
|
||||||
parseHtmlContent(html) {
|
parseHtmlContent(html) {
|
||||||
if (!html) return [];
|
if (!html) return [];
|
||||||
|
|
||||||
const result = [];
|
const result = [];
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
|
|
||||||
// 匹配所有 img 标签
|
// 匹配所有 img 标签
|
||||||
const imgRegex = /<img([^>]*)>/gi;
|
const imgRegex = /<img([^>]*)>/gi;
|
||||||
let match;
|
let match;
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
|
|
||||||
while ((match = imgRegex.exec(html)) !== null) {
|
while ((match = imgRegex.exec(html)) !== null) {
|
||||||
// 添加图片之前的文本内容
|
// 添加图片之前的文本内容
|
||||||
if (match.index > lastIndex) {
|
if (match.index > lastIndex) {
|
||||||
const textContent = html.substring(lastIndex, match.index);
|
const textContent = html.substring(lastIndex, match.index);
|
||||||
if (textContent.trim()) {
|
if (textContent.trim()) {
|
||||||
result.push({
|
result.push({
|
||||||
type: 'text',
|
type: "text",
|
||||||
html: textContent
|
html: textContent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取图片 src
|
// 提取图片 src
|
||||||
const imgAttrs = match[1];
|
const imgAttrs = match[1];
|
||||||
const srcMatch = imgAttrs.match(/src=["']([^"']+)["']/i);
|
const srcMatch = imgAttrs.match(/src=["']([^"']+)["']/i);
|
||||||
if (srcMatch && srcMatch[1]) {
|
if (srcMatch && srcMatch[1]) {
|
||||||
result.push({
|
result.push({
|
||||||
type: 'image',
|
type: "image",
|
||||||
src: srcMatch[1]
|
src: srcMatch[1],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIndex = match.index + match[0].length;
|
lastIndex = match.index + match[0].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加最后剩余的文本内容
|
// 添加最后剩余的文本内容
|
||||||
if (lastIndex < html.length) {
|
if (lastIndex < html.length) {
|
||||||
const textContent = html.substring(lastIndex);
|
const textContent = html.substring(lastIndex);
|
||||||
if (textContent.trim()) {
|
if (textContent.trim()) {
|
||||||
result.push({
|
result.push({
|
||||||
type: 'text',
|
type: "text",
|
||||||
html: textContent
|
html: textContent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有匹配到图片,直接返回整个内容作为文本
|
// 如果没有匹配到图片,直接返回整个内容作为文本
|
||||||
if (result.length === 0 && html.trim()) {
|
if (result.length === 0 && html.trim()) {
|
||||||
result.push({
|
result.push({
|
||||||
type: 'text',
|
type: "text",
|
||||||
html: html
|
html: html,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -145,6 +253,7 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding-bottom: 120rpx; // 为底部操作栏留出空间
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 内容区域 */
|
/* 内容区域 */
|
||||||
|
|
@ -164,7 +273,7 @@ export default {
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
|
||||||
// 图片容器样式
|
// 图片容器样式
|
||||||
.rich-text-image-wrapper {
|
.rich-text-image-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -173,7 +282,7 @@ export default {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.rich-text-image {
|
.rich-text-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
@ -181,7 +290,7 @@ export default {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文本内容样式
|
// 文本内容样式
|
||||||
.rich-text-content {
|
.rich-text-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
<!-- 顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<NavHeader title="店铺详情" />
|
<NavHeader title="店铺详情" />
|
||||||
|
|
||||||
<!-- 顶部店铺信息 -->
|
<!-- 顶部店铺信息(点击进入地图页) -->
|
||||||
<view class="store-header">
|
<view class="store-header" @click="handleGoMap">
|
||||||
<view class="store-brand">
|
<view class="store-brand">
|
||||||
<image
|
<image
|
||||||
class="brand-image"
|
class="brand-image"
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image>
|
||||||
<text class="distance-text"
|
<text class="distance-text"
|
||||||
>距您 {{ storeInfo.distance || "0" }}km</text
|
>距您 {{ distance || "0" }}km</text
|
||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -120,7 +120,7 @@ import {
|
||||||
getGuildStoreDetail,
|
getGuildStoreDetail,
|
||||||
getGuildCoupon,
|
getGuildCoupon,
|
||||||
getPaySign,
|
getPaySign,
|
||||||
appBuy,
|
appBuy
|
||||||
} from "@/api/service";
|
} from "@/api/service";
|
||||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
|
||||||
|
|
@ -135,8 +135,8 @@ export default {
|
||||||
categoryLabel: "",
|
categoryLabel: "",
|
||||||
shopId: null,
|
shopId: null,
|
||||||
userInfo: {},
|
userInfo: {},
|
||||||
hasCheckedLogin: false, // 是否已经检查过登录状态
|
distance: null,
|
||||||
isLoadingAfterLogin: false, // 是否正在登录后重新加载
|
paying: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -145,8 +145,7 @@ export default {
|
||||||
return this.menuList.reduce((total, item) => {
|
return this.menuList.reduce((total, item) => {
|
||||||
if (item.selected && item.quantity > 0) {
|
if (item.selected && item.quantity > 0) {
|
||||||
// 价格是以分为单位,需要转换为元
|
// 价格是以分为单位,需要转换为元
|
||||||
const price =
|
const price = (item.salePrice || 0) / 100;
|
||||||
(item.salePrice || item.currentPrice || item.price || 0) / 100;
|
|
||||||
return total + price * item.quantity;
|
return total + price * item.quantity;
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
|
|
@ -165,10 +164,15 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
|
console.log(options, 111110);
|
||||||
// 从路由参数获取店铺ID和分类标签
|
// 从路由参数获取店铺ID和分类标签
|
||||||
this.shopId = options.id;
|
this.shopId = options.id;
|
||||||
this.categoryLabel = options.categoryLabel;
|
this.categoryLabel = options.categoryLabel;
|
||||||
this.userInfo = uni.getStorageSync("userInfo") || {};
|
// 将距离从米转换为千米(保留2位小数)
|
||||||
|
if (options.distance) {
|
||||||
|
this.distance = (parseFloat(options.distance) / 1000).toFixed(2);
|
||||||
|
}
|
||||||
|
this.userInfo = uni.getStorageSync('userInfo') || {};
|
||||||
if (this.shopId) {
|
if (this.shopId) {
|
||||||
this.loadStoreData();
|
this.loadStoreData();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -195,35 +199,23 @@ export default {
|
||||||
newUserInfo.id
|
newUserInfo.id
|
||||||
) {
|
) {
|
||||||
this.userInfo = newUserInfo;
|
this.userInfo = newUserInfo;
|
||||||
this.isLoadingAfterLogin = true; // 标记正在登录后重新加载
|
// 如果有店铺ID,重新加载店铺数据(特别是菜单数据,可能需要登录才能查看)
|
||||||
|
if (this.shopId) {
|
||||||
// 设置一个全局标记,表示刚刚登录返回,用于防止 401 弹窗
|
this.loadStoreData();
|
||||||
uni.setStorageSync("justBackFromLogin", true);
|
}
|
||||||
// 5秒后清除标记
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.removeStorageSync("justBackFromLogin");
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
// 延迟一下,确保 token 已经保存并生效,避免与之前的请求冲突
|
|
||||||
setTimeout(() => {
|
|
||||||
// 如果有店铺ID,重新加载店铺数据(特别是菜单数据,可能需要登录才能查看)
|
|
||||||
if (this.shopId) {
|
|
||||||
this.loadStoreData();
|
|
||||||
}
|
|
||||||
// 加载完成后清除标记
|
|
||||||
setTimeout(() => {
|
|
||||||
this.isLoadingAfterLogin = false;
|
|
||||||
}, 1000);
|
|
||||||
}, 500); // 增加延迟时间,确保之前的请求已经完成
|
|
||||||
} else if (token) {
|
} else if (token) {
|
||||||
// 如果已登录,更新用户信息(可能用户信息有更新)
|
// 如果已登录,更新用户信息(可能用户信息有更新)
|
||||||
this.userInfo = newUserInfo;
|
this.userInfo = newUserInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记已经检查过登录状态
|
|
||||||
this.hasCheckedLogin = true;
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 顶部店铺信息点击:进入地图页
|
||||||
|
handleGoMap() {
|
||||||
|
if (!this.shopId) return;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/detail/mapDetail?id=${this.shopId}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
// 加载店铺数据
|
// 加载店铺数据
|
||||||
async loadStoreData() {
|
async loadStoreData() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -247,6 +239,10 @@ export default {
|
||||||
const res = await getGuildStoreDetail(shopId);
|
const res = await getGuildStoreDetail(shopId);
|
||||||
if (res) {
|
if (res) {
|
||||||
this.storeInfo = res;
|
this.storeInfo = res;
|
||||||
|
// 如果接口返回的距离是米,转换为千米
|
||||||
|
if (this.storeInfo.distance && typeof this.storeInfo.distance === 'number') {
|
||||||
|
this.storeInfo.distance = (this.storeInfo.distance / 1000).toFixed(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载店铺详情失败:", error);
|
console.error("加载店铺详情失败:", error);
|
||||||
|
|
@ -275,7 +271,8 @@ export default {
|
||||||
item.selected = !item.selected;
|
item.selected = !item.selected;
|
||||||
if (!item.selected) {
|
if (!item.selected) {
|
||||||
item.quantity = 0;
|
item.quantity = 0;
|
||||||
} else if (item.quantity === 0) {
|
} else if (!item.quantity || item.quantity <= 0) {
|
||||||
|
// 选中时,如果当前数量为 0 或未设置,则默认数量为 1
|
||||||
item.quantity = 1;
|
item.quantity = 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -387,9 +384,12 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
const randomstr = Math.floor(Math.random() * 10000000) + "";
|
const randomstr = Math.floor(Math.random() * 10000000) + "";
|
||||||
// 定义请求参数
|
// 金额:优先用订单实付金额(分),无则用 "1" 测试
|
||||||
// 仅排序,不修改任何字段值,参考对象中没有的字段放在最后
|
const trxamt =
|
||||||
// 注意参数 所有参数的类型用STring 不然会造成意外的报错,
|
order.payableAmount != null && order.payableAmount !== ""
|
||||||
|
? String(order.payableAmount)
|
||||||
|
: "1";
|
||||||
|
// 定义请求参数(与服务记录页「立即支付」保持一致)
|
||||||
let params = {
|
let params = {
|
||||||
appid: "00390105", // 平台分配的appid
|
appid: "00390105", // 平台分配的appid
|
||||||
body: bodyStr, //
|
body: bodyStr, //
|
||||||
|
|
@ -399,40 +399,37 @@ export default {
|
||||||
orgid: "56479107392N35H",
|
orgid: "56479107392N35H",
|
||||||
paytype: "W06",
|
paytype: "W06",
|
||||||
randomstr: randomstr,
|
randomstr: randomstr,
|
||||||
remark: "1:"+order.orderNumber+ ":" + bodyStr, // 第一个1 为租户信息, 给代入进去,第二个是订单号,第三个是body 信息
|
orderNumber: order.orderNumber,
|
||||||
|
remark: "1:" + order.orderNumber + ":" + bodyStr, // 第一个1 为租户信息, 第二个是订单号, 第三个是body
|
||||||
reqsn: order.orderNumber,
|
reqsn: order.orderNumber,
|
||||||
sign: "",
|
sign: "",
|
||||||
signtype: "RSA",
|
signtype: "RSA",
|
||||||
// trxamt: "" + order.payableAmount,
|
trxamt,
|
||||||
trxamt: "1",// 测试金额 生产 是上面那条
|
|
||||||
version: "12",
|
version: "12",
|
||||||
//"asinfo":"56479107392MP4J:01:0.01" //(后台已经设置固定比例, 会自动分账) 分账, 第一个是分账商户号,第二个是01 类型, 根据金额分账,0.01 元
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sign = await getPaySign(params);
|
if (order.orderNumber) {
|
||||||
console.log("测试返回的签名:" + sign);
|
uni.setStorageSync("lastOrderNumber", order.orderNumber);
|
||||||
params["sign"] = sign;
|
}
|
||||||
// 统一使用 navigateToMiniProgram 跳转小程序
|
|
||||||
uni.navigateToMiniProgram({
|
|
||||||
appId: "wxef277996acc166c3", // 目标小程序appid
|
|
||||||
extraData: params, // 传递给目标小程序的参数
|
|
||||||
success(res) {
|
|
||||||
console.log("小程序跳转成功", res);
|
|
||||||
},
|
|
||||||
fail(err) {
|
|
||||||
console.error("小程序跳转失败", err);
|
|
||||||
// 可根据需要添加失败后的提示或兜底逻辑
|
|
||||||
uni.showToast({
|
|
||||||
title: "跳转失败,请稍后重试",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
complete() {
|
|
||||||
// 无论成功失败都会执行的逻辑(可选)
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
//---- 支付
|
try {
|
||||||
|
const sign = await getPaySign(params);
|
||||||
|
params["sign"] = sign;
|
||||||
|
uni.navigateToMiniProgram({
|
||||||
|
appId: "wxef277996acc166c3", // 目标小程序appid
|
||||||
|
extraData: params,
|
||||||
|
success(res) {
|
||||||
|
console.log("小程序跳转成功", res);
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
console.error("小程序跳转失败", err);
|
||||||
|
uni.showToast({ title: "跳转失败,请稍后重试", icon: "none" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("获取支付签名失败:", e);
|
||||||
|
uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -634,12 +631,12 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 24rpx;
|
font-size: 22rpx;
|
||||||
color: #d51c3c;
|
color: #d51c3c;
|
||||||
.price-text {
|
.price-text {
|
||||||
font-family: PingFang-SC, PingFang-SC;
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 48rpx;
|
font-size: 47rpx;
|
||||||
color: #d51c3c;
|
color: #d51c3c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -647,7 +644,7 @@ export default {
|
||||||
.original-price {
|
.original-price {
|
||||||
font-family: PingFang-SC, PingFang-SC;
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 20rpx;
|
font-size: 18rpx;
|
||||||
color: #545454;
|
color: #545454;
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
background-color: rgba(115, 115, 115, 0.1);
|
background-color: rgba(115, 115, 115, 0.1);
|
||||||
|
|
@ -657,7 +654,7 @@ export default {
|
||||||
.quantity-control {
|
.quantity-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20rpx;
|
gap: 15rpx;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|
||||||
.quantity-btn {
|
.quantity-btn {
|
||||||
|
|
|
||||||
|
|
@ -302,15 +302,6 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 加入工会
|
|
||||||
async handleJoinGuild() {
|
|
||||||
uni.showToast({
|
|
||||||
title: "该功能正在开发中",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 查看全部福利
|
// 查看全部福利
|
||||||
handleViewAllBenefits() {
|
handleViewAllBenefits() {
|
||||||
// 清除分类信息,跳转到服务页面显示全部
|
// 清除分类信息,跳转到服务页面显示全部
|
||||||
|
|
@ -361,7 +352,7 @@ export default {
|
||||||
handleActivityClick(item) {
|
handleActivityClick(item) {
|
||||||
// 点击活动项跳转到详情页
|
// 点击活动项跳转到详情页
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/detail/activitiesDetail?id=${item.id}`
|
url: `/pages/detail/richTextDetail?id=${item.id}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -369,7 +360,7 @@ export default {
|
||||||
async handleJoinActivity(item) {
|
async handleJoinActivity(item) {
|
||||||
// 跳转到活动详情页
|
// 跳转到活动详情页
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/detail/activitiesDetail?id=${item.id}`
|
url: `/pages/detail/richTextDetail?id=${item.id}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
class="wechat-login-btn"
|
class="wechat-login-btn"
|
||||||
open-type="getPhoneNumber"
|
open-type="getPhoneNumber"
|
||||||
@getphonenumber="handleGetPhoneNumber"
|
@getphonenumber="handleGetPhoneNumber"
|
||||||
:disabled="loading"
|
:disabled="loading || !agreedToTerms"
|
||||||
>
|
>
|
||||||
<text>一键授权手机号快速登录</text>
|
<text>一键授权手机号快速登录</text>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -76,6 +76,21 @@
|
||||||
<text>小程序一键授权登录仅支持微信小程序环境</text>
|
<text>小程序一键授权登录仅支持微信小程序环境</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- 协议同意区域 -->
|
||||||
|
<view class="agreement-section">
|
||||||
|
<view class="agreement-checkbox" @click="toggleAgreement">
|
||||||
|
<view class="checkbox-icon" :class="{ checked: agreedToTerms }">
|
||||||
|
<text v-if="agreedToTerms" class="checkmark">✓</text>
|
||||||
|
</view>
|
||||||
|
<text class="agreement-text">
|
||||||
|
我已阅读并同意
|
||||||
|
<text class="link-text" @click.stop="goToUserAgreement">《用户服务协议》</text>
|
||||||
|
和
|
||||||
|
<text class="link-text" @click.stop="goToPrivacyPolicy">《隐私政策》</text>
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -92,6 +107,7 @@ export default {
|
||||||
showBack: false,
|
showBack: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
showPassword: false,
|
showPassword: false,
|
||||||
|
agreedToTerms: false, // 是否同意协议
|
||||||
formData: {
|
formData: {
|
||||||
mobile: '',
|
mobile: '',
|
||||||
password: ''
|
password: ''
|
||||||
|
|
@ -268,10 +284,39 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 切换协议同意状态
|
||||||
|
toggleAgreement() {
|
||||||
|
this.agreedToTerms = !this.agreedToTerms
|
||||||
|
},
|
||||||
|
|
||||||
|
// 跳转到用户协议
|
||||||
|
goToUserAgreement() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/profileSub/userAgreement'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 跳转到隐私政策
|
||||||
|
goToPrivacyPolicy() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/profileSub/privacyPolicy'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 小程序一键授权手机号登录
|
// 小程序一键授权手机号登录
|
||||||
async handleGetPhoneNumber(e) {
|
async handleGetPhoneNumber(e) {
|
||||||
console.log('获取手机号授权结果:', e)
|
console.log('获取手机号授权结果:', e)
|
||||||
|
|
||||||
|
// 检查是否同意协议
|
||||||
|
if (!this.agreedToTerms) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请先阅读并同意用户协议和隐私政策',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (e.detail.errMsg === 'getPhoneNumber:ok') {
|
if (e.detail.errMsg === 'getPhoneNumber:ok') {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -612,5 +657,51 @@ export default {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agreement-section {
|
||||||
|
margin-top: 40rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
|
||||||
|
.agreement-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
|
.checkbox-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border: 1rpx solid #cccccc;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
background-color: #004294;
|
||||||
|
border-color: #004294;
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666666;
|
||||||
|
line-height: 1.6;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.link-text {
|
||||||
|
color: #004294;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,16 @@
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<view class="user-info">
|
<view class="user-info">
|
||||||
|
<view class="user-level">{{ userInfo.level.name }}</view>
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
<button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="handleChooseAvatar">
|
<button
|
||||||
|
class="avatar-btn"
|
||||||
|
open-type="chooseAvatar"
|
||||||
|
@chooseavatar="handleChooseAvatar"
|
||||||
|
>
|
||||||
<image
|
<image
|
||||||
class="avatar"
|
class="avatar"
|
||||||
:src="userInfo.avatar || '/static/logo.png'"
|
:src="userInfo.avatar || '/static/tabbar/profile.png'"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -27,7 +32,7 @@
|
||||||
<view class="avatar-wrapper" @click="handleChooseAvatar">
|
<view class="avatar-wrapper" @click="handleChooseAvatar">
|
||||||
<image
|
<image
|
||||||
class="avatar"
|
class="avatar"
|
||||||
:src="userInfo.avatar || '/static/logo.png'"
|
:src="userInfo.avatar || '/static/tabbar/profile.png'"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -117,7 +122,7 @@ export default {
|
||||||
navBarHeight: 0,
|
navBarHeight: 0,
|
||||||
currentTime: "10:55",
|
currentTime: "10:55",
|
||||||
userInfo: {
|
userInfo: {
|
||||||
level: {} // 防止 level 未定义时报错
|
level: {}, // 防止 level 未定义时报错
|
||||||
},
|
},
|
||||||
noticeNum: 3,
|
noticeNum: 3,
|
||||||
uploading: false, // 是否正在上传头像
|
uploading: false, // 是否正在上传头像
|
||||||
|
|
@ -205,10 +210,10 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 会员权益
|
||||||
goToMemberBenefits() {
|
goToMemberBenefits() {
|
||||||
uni.showToast({
|
uni.navigateTo({
|
||||||
title: "该功能正在开发中",
|
url: "/pages/profileSub/pointsMemberRules",
|
||||||
icon: "none",
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
goToRealNameAuth() {
|
goToRealNameAuth() {
|
||||||
|
|
@ -268,13 +273,13 @@ export default {
|
||||||
this.uploadAvatar(avatarUrl);
|
this.uploadAvatar(avatarUrl);
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-WEIXIN
|
||||||
// 其他平台使用 chooseImage
|
// 其他平台使用 chooseImage
|
||||||
uni.chooseImage({
|
uni.chooseImage({
|
||||||
count: 1,
|
count: 1,
|
||||||
sizeType: ['compressed'],
|
sizeType: ["compressed"],
|
||||||
sourceType: ['album', 'camera'],
|
sourceType: ["album", "camera"],
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const tempFilePath = res.tempFilePaths[0];
|
const tempFilePath = res.tempFilePaths[0];
|
||||||
// 先显示本地预览
|
// 先显示本地预览
|
||||||
|
|
@ -283,12 +288,12 @@ export default {
|
||||||
this.uploadAvatar(tempFilePath);
|
this.uploadAvatar(tempFilePath);
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('选择图片失败:', err);
|
console.error("选择图片失败:", err);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '选择图片失败',
|
title: "选择图片失败",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
// #endif
|
// #endif
|
||||||
},
|
},
|
||||||
|
|
@ -301,24 +306,24 @@ export default {
|
||||||
|
|
||||||
this.uploading = true;
|
this.uploading = true;
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '上传中...',
|
title: "上传中...",
|
||||||
mask: true
|
mask: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取token
|
// 获取token
|
||||||
const token = uni.getStorageSync('token');
|
const token = uni.getStorageSync("token");
|
||||||
const BASE_URL = 'https://guangsh.manage.hschengtai.com';
|
const BASE_URL = "https://guangsh.manage.hschengtai.com";
|
||||||
|
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
url: `${BASE_URL}/app-api/infra/file/upload`,
|
url: `${BASE_URL}/app-api/infra/file/upload`,
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
name: 'file',
|
name: "file",
|
||||||
formData: {
|
formData: {
|
||||||
directory: 'avatar' // 头像目录
|
directory: "avatar", // 头像目录
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
'Authorization': `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
'tenant-id': '1'
|
"tenant-id": "1",
|
||||||
},
|
},
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
|
|
@ -331,27 +336,27 @@ export default {
|
||||||
// 更新用户信息中的头像
|
// 更新用户信息中的头像
|
||||||
this.userInfo.avatar = imageUrl;
|
this.userInfo.avatar = imageUrl;
|
||||||
// 保存到本地存储
|
// 保存到本地存储
|
||||||
uni.setStorageSync('userInfo', this.userInfo);
|
uni.setStorageSync("userInfo", this.userInfo);
|
||||||
|
|
||||||
// 调用更新用户信息接口(如果有的话)
|
// 调用更新用户信息接口(如果有的话)
|
||||||
this.updateUserAvatar(imageUrl);
|
this.updateUserAvatar(imageUrl);
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '头像上传成功',
|
title: "头像上传成功",
|
||||||
icon: 'success',
|
icon: "success",
|
||||||
duration: 1500
|
duration: 1500,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('上传成功但未返回图片地址');
|
throw new Error("上传成功但未返回图片地址");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.message || data.msg || '上传失败');
|
throw new Error(data.message || data.msg || "上传失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解析上传结果失败:', error);
|
console.error("解析上传结果失败:", error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: error.message || '上传失败,请重试',
|
title: error.message || "上传失败,请重试",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
// 上传失败,恢复原头像
|
// 上传失败,恢复原头像
|
||||||
this.loadUserInfo();
|
this.loadUserInfo();
|
||||||
|
|
@ -359,17 +364,17 @@ export default {
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
console.error('上传头像失败:', err);
|
console.error("上传头像失败:", err);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '上传失败,请检查网络',
|
title: "上传失败,请检查网络",
|
||||||
icon: 'none'
|
icon: "none",
|
||||||
});
|
});
|
||||||
// 上传失败,恢复原头像
|
// 上传失败,恢复原头像
|
||||||
this.loadUserInfo();
|
this.loadUserInfo();
|
||||||
},
|
},
|
||||||
complete: () => {
|
complete: () => {
|
||||||
this.uploading = false;
|
this.uploading = false;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -379,9 +384,9 @@ export default {
|
||||||
// 这里可以调用更新用户信息的接口
|
// 这里可以调用更新用户信息的接口
|
||||||
// 例如:await updateUserInfo({ avatar: avatarUrl });
|
// 例如:await updateUserInfo({ avatar: avatarUrl });
|
||||||
// 暂时只更新本地存储,如果后端有更新接口可以在这里调用
|
// 暂时只更新本地存储,如果后端有更新接口可以在这里调用
|
||||||
console.log('头像已更新:', avatarUrl);
|
console.log("头像已更新:", avatarUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新用户头像失败:', error);
|
console.error("更新用户头像失败:", error);
|
||||||
// 即使更新失败,本地已经保存了,不影响使用
|
// 即使更新失败,本地已经保存了,不影响使用
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -426,7 +431,17 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-top: 89rpx;
|
padding-top: 89rpx;
|
||||||
padding-left: 23rpx;
|
padding-left: 23rpx;
|
||||||
|
|
||||||
|
.user-level {
|
||||||
|
position: absolute;
|
||||||
|
top: 9rpx;
|
||||||
|
left: 28rpx;
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #fdd7b2;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar-btn {
|
.avatar-btn {
|
||||||
width: 109rpx;
|
width: 109rpx;
|
||||||
height: 108rpx;
|
height: 108rpx;
|
||||||
|
|
@ -435,30 +450,30 @@ export default {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-wrapper {
|
.avatar-wrapper {
|
||||||
width: 109rpx;
|
width: 109rpx;
|
||||||
height: 108rpx;
|
height: 108rpx;
|
||||||
margin-right: 24rpx;
|
margin-right: 24rpx;
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 109rpx;
|
width: 109rpx;
|
||||||
height: 108rpx;
|
height: 108rpx;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
<template>
|
||||||
|
<view class="rules-page">
|
||||||
|
<NavHeader title="积分会员制度" />
|
||||||
|
|
||||||
|
<scroll-view class="content-scroll" scroll-y="true">
|
||||||
|
<!-- 一、会员等级与积分门槛 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">一、会员等级与积分门槛</view>
|
||||||
|
|
||||||
|
<view class="table">
|
||||||
|
<view class="tr th">
|
||||||
|
<view class="td col-1">等级</view>
|
||||||
|
<view class="td col-2">所需积分</view>
|
||||||
|
<view class="td col-3">等级名称</view>
|
||||||
|
</view>
|
||||||
|
<view class="tr" v-for="row in levelRows" :key="row.level">
|
||||||
|
<view class="td col-1">{{ row.level }}</view>
|
||||||
|
<view class="td col-2">{{ row.points }}</view>
|
||||||
|
<view class="td col-3">{{ row.name }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tips">
|
||||||
|
<text class="tips-text">
|
||||||
|
升级规则:会员积分达到相应门槛后,系统在24小时内升级,并享受新等级的全部权益。
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 二、行为奖励 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">二、行为奖励</view>
|
||||||
|
|
||||||
|
<view class="table">
|
||||||
|
<view class="tr th">
|
||||||
|
<view class="td col-a">行为</view>
|
||||||
|
<view class="td col-b">奖励积分</view>
|
||||||
|
<view class="td col-c">频率/限制</view>
|
||||||
|
</view>
|
||||||
|
<view class="tr" v-for="row in rewardRows" :key="row.action">
|
||||||
|
<view class="td col-a">{{ row.action }}</view>
|
||||||
|
<view class="td col-b">{{ row.points }}</view>
|
||||||
|
<view class="td col-c">{{ row.limit }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 三、会员权益 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">三、会员权益</view>
|
||||||
|
|
||||||
|
<view class="table">
|
||||||
|
<view class="tr th">
|
||||||
|
<view class="td col-lv">等级</view>
|
||||||
|
<view class="td col-ben">享受权益</view>
|
||||||
|
</view>
|
||||||
|
<view class="tr" v-for="row in benefitRows" :key="row.level">
|
||||||
|
<view class="td col-lv">{{ row.level }}</view>
|
||||||
|
<view class="td col-ben">{{ row.benefits }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="bullet">
|
||||||
|
<view class="bullet-item">• 首批次注册会员并完善个人信息前100者,送精美小礼品;</view>
|
||||||
|
<view class="bullet-item">• 率先升级为中级会员前30名者,邀请前来公司参与活动(含精美礼品);</view>
|
||||||
|
<view class="bullet-item">• 率先升级为高级会员前10名者,享受xx地区一日游(车费、餐费);</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { NavHeader },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
levelRows: [
|
||||||
|
{ level: "初级", points: "0", name: "筑巢会员" },
|
||||||
|
{ level: "中级", points: "1000", name: "安居会员" },
|
||||||
|
{ level: "高级", points: "5000", name: "共富会员" },
|
||||||
|
],
|
||||||
|
rewardRows: [
|
||||||
|
{ action: "签到", points: "2分", limit: "一天一次" },
|
||||||
|
{ action: "吃饭消费", points: "3分", limit: "不限次" },
|
||||||
|
{ action: "保险修车", points: "100分", limit: "不限次" },
|
||||||
|
{ action: "完善个人信息", points: "100分", limit: "一次" },
|
||||||
|
{ action: "评价", points: "2分", limit: "一天5次" },
|
||||||
|
{ action: "完成首单", points: "30分", limit: "一次" },
|
||||||
|
{ action: "分享链接", points: "10分", limit: "一天5次" },
|
||||||
|
{ action: "推荐新用户", points: "50分", limit: "不限次" },
|
||||||
|
{ action: "参与社区发帖", points: "2分", limit: "一天5次" },
|
||||||
|
],
|
||||||
|
benefitRows: [
|
||||||
|
{
|
||||||
|
level: "初级会员",
|
||||||
|
benefits:
|
||||||
|
"工会合作餐厅吃饭最低价,修车9.5折,保险(华生诚泰)9折,租房优惠权益,三家医院绿色通道。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: "中级会员",
|
||||||
|
benefits:
|
||||||
|
"工会合作餐厅吃饭最低价,修车9折,保险(华盛诚泰)8折,租房优惠权益,三家医院绿色通道。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: "高级会员",
|
||||||
|
benefits:
|
||||||
|
"工会合作餐厅吃饭最低价,修车8.5折,保险(华生诚泰)8折,租房优惠权益,三家医院绿色通道。",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.rules-page {
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-scroll {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
padding: 20rpx 24rpx 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #111111;
|
||||||
|
margin: 10rpx 0 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border: 1rpx solid #d9d9d9;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1rpx solid #d9d9d9;
|
||||||
|
}
|
||||||
|
.tr:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th {
|
||||||
|
background: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td {
|
||||||
|
padding: 16rpx 12rpx;
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 1.4;
|
||||||
|
border-right: 1rpx solid #d9d9d9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表1列宽 */
|
||||||
|
.col-1 {
|
||||||
|
width: 22%;
|
||||||
|
}
|
||||||
|
.col-2 {
|
||||||
|
width: 28%;
|
||||||
|
}
|
||||||
|
.col-3 {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表2列宽 */
|
||||||
|
.col-a {
|
||||||
|
width: 34%;
|
||||||
|
}
|
||||||
|
.col-b {
|
||||||
|
width: 22%;
|
||||||
|
}
|
||||||
|
.col-c {
|
||||||
|
width: 44%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表3列宽 */
|
||||||
|
.col-lv {
|
||||||
|
width: 28%;
|
||||||
|
}
|
||||||
|
.col-ben {
|
||||||
|
width: 72%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
.tips-text {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #2f6fd6;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bullet {
|
||||||
|
margin-top: 14rpx;
|
||||||
|
}
|
||||||
|
.bullet-item {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
<template>
|
||||||
|
<view class="privacy-page">
|
||||||
|
<NavHeader title="隐私政策" />
|
||||||
|
<scroll-view class="content" scroll-y="true">
|
||||||
|
<view class="privacy-content">
|
||||||
|
<text class="privacy-text">{{ privacyText }}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
NavHeader
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
privacyText: `隐私政策
|
||||||
|
|
||||||
|
本隐私政策(以下简称"本政策")由广厦千万间司机公会(以下简称"我们")制定,旨在说明我们通过运营的微信小程序(以下简称"小程序")向您(以下简称"用户")提供餐饮、维修、保险、健康等服务过程中,对用户个人信息的收集、使用、存储、传输、共享、披露及保护等相关事宜。您在使用我们的服务时,即视为您已充分阅读、理解并同意本政策的全部内容,包括我们对本政策的后续更新。如您不同意本政策,请勿使用我们的服务。
|
||||||
|
|
||||||
|
一、适用范围
|
||||||
|
本政策适用于我们通过小程序向您提供的所有服务,包括但不限于餐饮服务、维修服务、保险服务、健康服务等。若您通过第三方平台或渠道使用我们的服务,还需遵守该第三方的相关隐私规定。
|
||||||
|
|
||||||
|
二、个人信息的收集范围与方式
|
||||||
|
(一)收集范围
|
||||||
|
为向您提供高质量的服务,我们仅收集为实现服务目的所必需的个人信息,具体包括:
|
||||||
|
1.基本身份信息:包括您的姓名、身份证号、联系电话等,用于完成身份核验、建立服务关系;
|
||||||
|
2.车辆相关信息:包括您的车牌号、车辆行驶证信息等,用于为您精准匹配维修、保险等服务;
|
||||||
|
3.服务相关信息:包括您的餐饮偏好、维修需求、保险配置需求、健康状况(仅收集服务必需的部分)、服务订单记录、费用支付记录、评价反馈等,用于处理订单、优化服务体验;
|
||||||
|
4.其他必要信息:为保障服务安全、履行法律法规义务所必需的其他信息。
|
||||||
|
|
||||||
|
(二)收集方式
|
||||||
|
1.您主动提供:您在小程序注册账号、提交服务申请、填写资料、购买服务、发表评价时,主动向我们提供的个人信息;
|
||||||
|
2.服务过程获取:在为您提供服务的过程中,我们通过小程序系统自动记录的相关信息,如服务使用记录、支付记录等;
|
||||||
|
3.第三方协助获取:在取得您明确授权的前提下,我们从合法的第三方机构(如保险机构、支付机构、合作维修商家等)获取的必要信息,用于完成服务对接;
|
||||||
|
4.其他合法方式:依据法律法规规定或有权机关要求,合法收集的相关信息。
|
||||||
|
|
||||||
|
三、个人信息的使用目的
|
||||||
|
我们收集的个人信息,仅用于以下合法目的:
|
||||||
|
1.完成用户身份核验,建立并维护合法、有效的服务关系;
|
||||||
|
2.为您提供餐饮、维修、保险、健康等约定服务,处理服务订单、完成费用结算;
|
||||||
|
3.分析您的服务需求,精准匹配服务资源,为您提供个性化的服务推荐;
|
||||||
|
4.对服务质量进行评估与优化,提升服务体验,改进服务流程;
|
||||||
|
5.保障服务安全,防范、识别并处理欺诈、违规等风险行为;
|
||||||
|
6.向您发送服务相关的通知、公告、优惠活动信息(如您明确拒绝,我们将停止发送);
|
||||||
|
7.处理您的咨询、投诉与建议,维护您的合法权益;
|
||||||
|
8.履行法律法规规定的信息留存、报告等义务;
|
||||||
|
9.其他经您明确同意的合法用途。
|
||||||
|
未经您的明确同意,我们不会将您的个人信息用于本政策约定以外的其他目的。
|
||||||
|
|
||||||
|
四、个人信息的存储与安全
|
||||||
|
(一)存储期限与地点
|
||||||
|
1.我们将按照法律法规的规定,在实现信息收集目的所必需的最短期限内存储您的个人信息。超出存储期限的,我们将对您的个人信息进行安全删除或匿名化处理。
|
||||||
|
2.您的个人信息将存储在中华人民共和国境内,如需向境外传输,我们将严格遵守相关法律法规的规定,取得您的明确同意并采取必要的安全保障措施。
|
||||||
|
|
||||||
|
(二)安全保障措施
|
||||||
|
我们高度重视您的个人信息安全,采取以下技术与管理措施保障信息安全:
|
||||||
|
1.技术防护:采用数据加密、访问控制、安全审计、防火墙等技术手段,防止个人信息被泄露、篡改、窃取;
|
||||||
|
2.管理规范:建立健全个人信息保护管理制度,明确各岗位的信息安全职责,对工作人员进行信息安全培训与考核;
|
||||||
|
3.权限管控:严格限制访问个人信息的人员范围,仅授权必要工作人员在履行职责时访问,并对访问行为进行记录与监控;
|
||||||
|
4.应急处置:制定个人信息安全事件应急预案,如发生信息泄露、毁损、丢失等安全事件,将立即启动应急预案,采取补救措施,并按照法律法规的规定及时向您及相关监管部门报告。
|
||||||
|
|
||||||
|
五、个人信息的共享与披露
|
||||||
|
(一)信息共享
|
||||||
|
我们不会随意向第三方共享您的个人信息,除非符合以下情形:
|
||||||
|
1.经您明确同意或授权:我们将在您授权的范围内,向您指定的第三方共享必要的个人信息;
|
||||||
|
2.服务提供必需:为向您提供约定的服务,需向合作的第三方机构(如保险机构、维修商家、支付机构等)共享必要的个人信息,且该第三方已签署保密协议,承诺严格保护您的个人信息;
|
||||||
|
3.法律法规要求:依据法律法规的规定、司法机关或行政机关的合法要求,向相关部门披露或共享个人信息;
|
||||||
|
4.保护合法权益:为保护我们的合法权益、服务秩序或社会公共利益,在合理必要的范围内共享个人信息;
|
||||||
|
5.匿名化处理:经过匿名化处理的信息,因其已无法识别您的身份,共享此类信息无需经过您的同意。
|
||||||
|
|
||||||
|
(二)信息披露
|
||||||
|
除本政策约定的情形及法律法规规定外,我们不会向任何第三方披露您的个人信息。
|
||||||
|
|
||||||
|
六、用户的权利
|
||||||
|
依据相关法律法规,您对您的个人信息享有以下权利:
|
||||||
|
1.查询与核对权:您有权查询、核对我们存储的您的个人信息,可通过小程序客服通道提交查询申请;
|
||||||
|
2.更正权:如您发现您的个人信息存在错误或不完整,有权要求我们予以更正、补充,我们将在审核确认后及时处理;
|
||||||
|
3.撤回授权权:您有权随时撤回对我们收集、使用您个人信息的授权,撤回授权可通过小程序客服通道申请;
|
||||||
|
4.删除权:在符合法律法规规定的情形下,您有权要求我们删除您的个人信息,我们将在审核通过后及时删除;
|
||||||
|
5.异议权:您对我们的个人信息处理行为有异议的,有权向我们提出,我们将在收到异议后及时核查并答复;
|
||||||
|
6.投诉举报权:如您认为我们的个人信息处理行为侵害了您的合法权益,有权向相关监管部门投诉、举报。
|
||||||
|
您行使上述权利时,可通过小程序客服通道或以下联系方式提交申请,我们将在合理期限内完成审核并反馈处理结果。
|
||||||
|
|
||||||
|
七、隐私政策的更新
|
||||||
|
1.因法律法规更新、业务模式调整、服务内容变化等原因,我们可能会对本政策进行修订。
|
||||||
|
2.修订后的隐私政策将通过小程序公告的方式公示,公示期满后正式生效。如您继续使用我们的服务,视为您已充分阅读、理解并同意修订后的隐私政策;如您不同意,应立即停止使用我们的服务,并可依据相关约定申请终止服务协议。
|
||||||
|
|
||||||
|
八、联系方式
|
||||||
|
如您对本隐私政策有任何疑问、意见或建议,或需要行使本政策约定的相关权利,可通过以下方式与我们联系:
|
||||||
|
1.联系地址:陕西省西安市雁塔区丈八东路2号3层301号
|
||||||
|
2.联系电话:18049239525
|
||||||
|
3.联系通道:小程序内客服聊天窗口
|
||||||
|
我们将在收到您的反馈后7个工作日内予以响应并处理。
|
||||||
|
|
||||||
|
九、附则
|
||||||
|
本隐私政策自发布之日起生效。如本隐私政策与相关法律法规存在冲突,以法律法规的规定为准。
|
||||||
|
|
||||||
|
广厦千万间司机公会`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.privacy-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-content {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #333333;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -204,12 +204,27 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 协议同意区域 -->
|
||||||
|
<view class="agreement-section">
|
||||||
|
<view class="agreement-checkbox" @click="toggleAgreement">
|
||||||
|
<view class="checkbox-icon" :class="{ checked: agreedToTerms }">
|
||||||
|
<text v-if="agreedToTerms" class="checkmark">✓</text>
|
||||||
|
</view>
|
||||||
|
<text class="agreement-text">
|
||||||
|
我已阅读并同意
|
||||||
|
<text class="link-text" @click.stop="goToUserAgreement">《用户服务协议》</text>
|
||||||
|
和
|
||||||
|
<text class="link-text" @click.stop="goToPrivacyPolicy">《隐私政策》</text>
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 提交按钮 -->
|
<!-- 提交按钮 -->
|
||||||
<view class="submit-section">
|
<view class="submit-section">
|
||||||
<button
|
<button
|
||||||
class="submit-btn"
|
class="submit-btn"
|
||||||
:class="{ disabled: loading }"
|
:class="{ disabled: loading || !agreedToTerms }"
|
||||||
:disabled="loading"
|
:disabled="loading || !agreedToTerms"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
>
|
>
|
||||||
{{ loading ? '提交中...' : '提交认证' }}
|
{{ loading ? '提交中...' : '提交认证' }}
|
||||||
|
|
@ -230,6 +245,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
agreedToTerms: false, // 是否同意协议
|
||||||
idTypeIndex: 0,
|
idTypeIndex: 0,
|
||||||
idTypeOptions: [
|
idTypeOptions: [
|
||||||
{ label: '身份证', value: 1 },
|
{ label: '身份证', value: 1 },
|
||||||
|
|
@ -549,8 +565,55 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agreement-section {
|
||||||
|
padding: 30rpx;
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
.agreement-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
|
.checkbox-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border: 1rpx solid #cccccc;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
background-color: #004294;
|
||||||
|
border-color: #004294;
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666666;
|
||||||
|
line-height: 1.6;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.link-text {
|
||||||
|
color: #004294;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.submit-section {
|
.submit-section {
|
||||||
margin-top: 40rpx;
|
margin-top: 20rpx;
|
||||||
|
padding: 0 30rpx;
|
||||||
padding-bottom: 40rpx;
|
padding-bottom: 40rpx;
|
||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
:class="{ active: currentTab === 'completed' }"
|
:class="{ active: currentTab === 'completed' }"
|
||||||
@click="switchTab('completed')"
|
@click="switchTab('completed')"
|
||||||
>
|
>
|
||||||
<text class="tab-text">已完成</text>
|
<text class="tab-text">已退款</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="tab-item"
|
class="tab-item"
|
||||||
|
|
@ -60,62 +60,82 @@
|
||||||
class="record-item"
|
class="record-item"
|
||||||
v-for="(item, index) in currentList"
|
v-for="(item, index) in currentList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="handleRecordClick(item)"
|
|
||||||
>
|
>
|
||||||
|
<!-- 头部:门店 + 状态 -->
|
||||||
<view class="record-header">
|
<view class="record-header">
|
||||||
<view class="record-title-row">
|
<view class="record-shop-row">
|
||||||
<text class="record-title">{{ item.serviceName }}</text>
|
<text class="shop-name">{{ item.shopName }}</text>
|
||||||
<view class="status-badge" :class="getStatusClass(item.status)">
|
<view class="status-badge" :class="getStatusClass(item.status)">
|
||||||
<text class="status-text">{{ getStatusText(item.status) }}</text>
|
<text class="status-text">{{ getStatusText(item.status) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="record-time">{{ item.createTime }}</text>
|
<text class="record-time">{{ formatTime(item.createTime) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="record-content">
|
<!-- 中部:类似商品卡片 -->
|
||||||
<view class="record-info-row">
|
<view class="record-body" @click="handleRecordClick(item)">
|
||||||
<text class="info-label">服务类型:</text>
|
<view
|
||||||
<text class="info-value">{{ item.serviceType }}</text>
|
class="record-goods"
|
||||||
</view>
|
v-for="(goods, gIndex) in getGoodsList(item)"
|
||||||
<view class="record-info-row">
|
:key="goods.id || goods.couponId || gIndex"
|
||||||
<text class="info-label">服务门店:</text>
|
>
|
||||||
<text class="info-value">{{ item.storeName }}</text>
|
<image
|
||||||
</view>
|
class="goods-image"
|
||||||
<view class="record-info-row">
|
:src="
|
||||||
<text class="info-label">订单号:</text>
|
goods.coverUrl ||
|
||||||
<text class="info-value">{{ item.orderNo }}</text>
|
goods.couponCoverUrl ||
|
||||||
</view>
|
goods.picUrl ||
|
||||||
<view class="record-info-row">
|
'/static/home/entry_icon.png'
|
||||||
<text class="info-label">订单金额:</text>
|
"
|
||||||
<text class="info-value price">¥{{ item.amount }}</text>
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<view class="goods-info">
|
||||||
|
<text class="goods-title">{{ goods.couponName || goods.name }}</text>
|
||||||
|
<!-- <text class="goods-subtitle">
|
||||||
|
订单号:{{ item.orderNumber }}
|
||||||
|
</text> -->
|
||||||
|
<view class="goods-meta-row">
|
||||||
|
<text class="goods-price">¥{{ formatFen(goods.salePrice) }}</text>
|
||||||
|
<text
|
||||||
|
class="goods-count"
|
||||||
|
v-if="(goods.num || goods.count || goods.quantity || goods.qty) > 1"
|
||||||
|
>
|
||||||
|
×{{ goods.num || goods.count || goods.quantity || goods.qty }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 操作按钮区域 -->
|
<!-- 底部:合计 + 操作按钮 -->
|
||||||
<view class="record-actions" v-if="item.status === 'pending_payment'">
|
<view class="record-footer">
|
||||||
<button
|
<view class="total-info">
|
||||||
class="action-btn cancel-btn"
|
<text class="total-label">实付款:</text>
|
||||||
@click.stop="handleCancel(item)"
|
<text class="total-amount">¥{{ formatFen(item.payableAmount) }}</text>
|
||||||
>
|
</view>
|
||||||
取消订单
|
|
||||||
</button>
|
<!-- 操作按钮区域 -->
|
||||||
<button class="action-btn pay-btn" @click.stop="handlePay(item)">
|
<view class="record-actions" v-if="item.status === 1">
|
||||||
立即支付
|
<button
|
||||||
</button>
|
class="action-btn cancel-btn"
|
||||||
</view>
|
@click.stop="handleCancel(item)"
|
||||||
<view
|
>
|
||||||
class="record-actions"
|
取消订单
|
||||||
v-else-if="item.status === 'pending_verification'"
|
</button>
|
||||||
>
|
<button class="action-btn pay-btn" @click.stop="handlePay(item)">
|
||||||
<button
|
立即支付
|
||||||
class="action-btn detail-btn"
|
</button>
|
||||||
@click.stop="handleViewDetail(item)"
|
</view>
|
||||||
>
|
<view class="record-actions" v-else-if="item.status === 2">
|
||||||
查看详情
|
<!-- <button
|
||||||
</button>
|
class="action-btn detail-btn"
|
||||||
</view>
|
@click.stop="handleViewDetail(item)"
|
||||||
<view class="record-actions" v-else-if="item.status === 'completed'">
|
>
|
||||||
<button
|
去核销
|
||||||
|
</button> -->
|
||||||
|
</view>
|
||||||
|
<view class="record-actions" v-else-if="item.status === 3">
|
||||||
|
<!-- <button
|
||||||
class="action-btn detail-btn"
|
class="action-btn detail-btn"
|
||||||
@click.stop="handleViewDetail(item)"
|
@click.stop="handleViewDetail(item)"
|
||||||
>
|
>
|
||||||
|
|
@ -126,15 +146,16 @@
|
||||||
@click.stop="handleReview(item)"
|
@click.stop="handleReview(item)"
|
||||||
>
|
>
|
||||||
评价
|
评价
|
||||||
</button>
|
</button> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="record-actions" v-else-if="item.status === 'cancelled'">
|
<view class="record-actions" v-else-if="item.status === 4">
|
||||||
<button
|
<button
|
||||||
class="action-btn detail-btn"
|
class="action-btn detail-btn"
|
||||||
@click.stop="handleViewDetail(item)"
|
@click.stop="handleViewDetail(item)"
|
||||||
>
|
>
|
||||||
查看详情
|
删除
|
||||||
</button>
|
</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -150,10 +171,11 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
import { getLuMyOrderPage, cancelOrder, getPaySign } from "@/api/service";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NavHeader
|
NavHeader,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -164,128 +186,64 @@ export default {
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
// 假数据
|
// 各状态对应的订单列表
|
||||||
mockData: {
|
recordsMap: {
|
||||||
// pending_payment: [
|
pending_payment: [],
|
||||||
// {
|
pending_verification: [],
|
||||||
// id: 1,
|
completed: [],
|
||||||
// orderNo: "ORD20250101001",
|
cancelled: [],
|
||||||
// serviceName: "汽车维修保养",
|
|
||||||
// serviceType: "维修服务",
|
|
||||||
// storeName: "XX汽车维修中心",
|
|
||||||
// amount: "299.00",
|
|
||||||
// createTime: "2025-01-15 10:30:00",
|
|
||||||
// status: "pending_payment",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 2,
|
|
||||||
// orderNo: "ORD20250101002",
|
|
||||||
// serviceName: "家电清洗服务",
|
|
||||||
// serviceType: "清洗服务",
|
|
||||||
// storeName: "XX家电清洗店",
|
|
||||||
// amount: "158.00",
|
|
||||||
// createTime: "2025-01-14 15:20:00",
|
|
||||||
// status: "pending_payment",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 3,
|
|
||||||
// orderNo: "ORD20250101003",
|
|
||||||
// serviceName: "手机维修",
|
|
||||||
// serviceType: "维修服务",
|
|
||||||
// storeName: "XX手机维修店",
|
|
||||||
// amount: "199.00",
|
|
||||||
// createTime: "2025-01-13 09:15:00",
|
|
||||||
// status: "pending_payment",
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// pending_verification: [
|
|
||||||
// {
|
|
||||||
// id: 4,
|
|
||||||
// orderNo: "ORD20250101004",
|
|
||||||
// serviceName: "汽车维修保养",
|
|
||||||
// serviceType: "维修服务",
|
|
||||||
// storeName: "XX汽车维修中心",
|
|
||||||
// amount: "299.00",
|
|
||||||
// createTime: "2025-01-10 14:30:00",
|
|
||||||
// status: "pending_verification",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 5,
|
|
||||||
// orderNo: "ORD20250101005",
|
|
||||||
// serviceName: "家电清洗服务",
|
|
||||||
// serviceType: "清洗服务",
|
|
||||||
// storeName: "XX家电清洗店",
|
|
||||||
// amount: "158.00",
|
|
||||||
// createTime: "2025-01-09 11:20:00",
|
|
||||||
// status: "pending_verification",
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// completed: [
|
|
||||||
// {
|
|
||||||
// id: 6,
|
|
||||||
// orderNo: "ORD20250101006",
|
|
||||||
// serviceName: "汽车维修保养",
|
|
||||||
// serviceType: "维修服务",
|
|
||||||
// storeName: "XX汽车维修中心",
|
|
||||||
// amount: "299.00",
|
|
||||||
// createTime: "2025-01-05 16:30:00",
|
|
||||||
// status: "completed",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 7,
|
|
||||||
// orderNo: "ORD20250101007",
|
|
||||||
// serviceName: "家电清洗服务",
|
|
||||||
// serviceType: "清洗服务",
|
|
||||||
// storeName: "XX家电清洗店",
|
|
||||||
// amount: "158.00",
|
|
||||||
// createTime: "2025-01-04 10:20:00",
|
|
||||||
// status: "completed",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 8,
|
|
||||||
// orderNo: "ORD20250101008",
|
|
||||||
// serviceName: "手机维修",
|
|
||||||
// serviceType: "维修服务",
|
|
||||||
// storeName: "XX手机维修店",
|
|
||||||
// amount: "199.00",
|
|
||||||
// createTime: "2025-01-03 08:15:00",
|
|
||||||
// status: "completed",
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// cancelled: [
|
|
||||||
// {
|
|
||||||
// id: 9,
|
|
||||||
// orderNo: "ORD20250101009",
|
|
||||||
// serviceName: "汽车维修保养",
|
|
||||||
// serviceType: "维修服务",
|
|
||||||
// storeName: "XX汽车维修中心",
|
|
||||||
// amount: "299.00",
|
|
||||||
// createTime: "2025-01-02 14:30:00",
|
|
||||||
// status: "cancelled",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: 10,
|
|
||||||
// orderNo: "ORD20250101010",
|
|
||||||
// serviceName: "家电清洗服务",
|
|
||||||
// serviceType: "清洗服务",
|
|
||||||
// storeName: "XX家电清洗店",
|
|
||||||
// amount: "158.00",
|
|
||||||
// createTime: "2025-01-01 11:20:00",
|
|
||||||
// status: "cancelled",
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentList() {
|
currentList() {
|
||||||
return this.mockData[this.currentTab] || [];
|
return this.recordsMap[this.currentTab] || [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad(options) {
|
||||||
|
// 如果通过参数传入 tab,优先使用传入的 tab 值
|
||||||
|
if (options && options.tab) {
|
||||||
|
this.currentTab = options.tab;
|
||||||
|
}
|
||||||
this.loadData();
|
this.loadData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 一个订单可能包含多个商品(couponPurchaseRespVOS)
|
||||||
|
getGoodsList(order) {
|
||||||
|
const list = (order && order.couponPurchaseRespVOS) || [];
|
||||||
|
return Array.isArray(list) && list.length ? list : [order || {}];
|
||||||
|
},
|
||||||
|
// 金额分转元(去掉后两位)
|
||||||
|
formatFen(fen) {
|
||||||
|
const n = Number(fen);
|
||||||
|
if (!Number.isFinite(n)) return fen || "0.00";
|
||||||
|
return (n / 100).toFixed(2);
|
||||||
|
},
|
||||||
|
// 时间戳转可读时间(支持秒/毫秒)
|
||||||
|
formatTime(val) {
|
||||||
|
if (val == null || val === "") return "";
|
||||||
|
let ts = Number(val);
|
||||||
|
if (!Number.isFinite(ts)) return val;
|
||||||
|
if (String(ts).length <= 10) ts *= 1000;
|
||||||
|
const d = new Date(ts);
|
||||||
|
const y = d.getFullYear();
|
||||||
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(d.getDate()).padStart(2, "0");
|
||||||
|
const h = String(d.getHours()).padStart(2, "0");
|
||||||
|
const min = String(d.getMinutes()).padStart(2, "0");
|
||||||
|
const s = String(d.getSeconds()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${day} ${h}:${min}:${s}`;
|
||||||
|
},
|
||||||
|
// 根据当前 tab 映射到接口所需的 status 值
|
||||||
|
getStatusValue() {
|
||||||
|
const map = {
|
||||||
|
pending_payment: 1, // 待支付
|
||||||
|
pending_verification: 2, // 待核销
|
||||||
|
completed: 3, // 已完成
|
||||||
|
cancelled: 4, // 已取消
|
||||||
|
};
|
||||||
|
return map[this.currentTab];
|
||||||
|
},
|
||||||
// 切换 Tab
|
// 切换 Tab
|
||||||
switchTab(tab) {
|
switchTab(tab) {
|
||||||
if (this.currentTab === tab) return;
|
if (this.currentTab === tab) return;
|
||||||
|
|
@ -306,34 +264,60 @@ export default {
|
||||||
},
|
},
|
||||||
// 获取状态文本
|
// 获取状态文本
|
||||||
getStatusText(status) {
|
getStatusText(status) {
|
||||||
const statusMap = {
|
return status === 1
|
||||||
pending_payment: "待支付",
|
? "待支付"
|
||||||
pending_verification: "待核销",
|
: status === 2
|
||||||
completed: "已完成",
|
? "待核销"
|
||||||
cancelled: "已取消",
|
: status === 3
|
||||||
};
|
? "已完成"
|
||||||
return statusMap[status] || "";
|
: status === 4
|
||||||
|
? "已取消"
|
||||||
|
: "";
|
||||||
},
|
},
|
||||||
// 获取状态样式类
|
// 获取状态样式类
|
||||||
getStatusClass(status) {
|
getStatusClass(status) {
|
||||||
const classMap = {
|
return status === 1
|
||||||
pending_payment: "status-pending",
|
? "status-pending"
|
||||||
pending_verification: "status-verification",
|
: status === 2
|
||||||
completed: "status-completed",
|
? "status-verification"
|
||||||
cancelled: "status-cancelled",
|
: status === 3
|
||||||
};
|
? "status-completed"
|
||||||
return classMap[status] || "";
|
: status === 4
|
||||||
|
? "status-cancelled"
|
||||||
|
: "";
|
||||||
},
|
},
|
||||||
// 加载数据
|
// 加载数据
|
||||||
loadData() {
|
async loadData(append = false) {
|
||||||
|
if (this.loading) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
// 模拟数据加载
|
const status = this.getStatusValue();
|
||||||
setTimeout(() => {
|
try {
|
||||||
|
const res = await getLuMyOrderPage({
|
||||||
|
pageNo: this.pageNo,
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
const list = res.list || [];
|
||||||
|
|
||||||
|
const currentList = this.recordsMap[this.currentTab] || [];
|
||||||
|
this.recordsMap = {
|
||||||
|
...this.recordsMap,
|
||||||
|
[this.currentTab]: append ? currentList.concat(list) : list,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 是否还有更多
|
||||||
|
this.hasMore = list.length >= this.pageSize;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("加载服务记录失败:", e);
|
||||||
|
uni.showToast({
|
||||||
|
title: "加载服务记录失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
this.loadingMore = false;
|
this.loadingMore = false;
|
||||||
// 这里使用假数据,实际应该调用接口
|
}
|
||||||
}, 500);
|
|
||||||
},
|
},
|
||||||
// 下拉刷新
|
// 下拉刷新
|
||||||
handleRefresh() {
|
handleRefresh() {
|
||||||
|
|
@ -347,12 +331,7 @@ export default {
|
||||||
if (this.hasMore && !this.loadingMore && !this.loading) {
|
if (this.hasMore && !this.loadingMore && !this.loading) {
|
||||||
this.loadingMore = true;
|
this.loadingMore = true;
|
||||||
this.pageNo += 1;
|
this.pageNo += 1;
|
||||||
// 模拟加载更多
|
this.loadData(true);
|
||||||
setTimeout(() => {
|
|
||||||
this.loadingMore = false;
|
|
||||||
// 假数据没有更多了
|
|
||||||
this.hasMore = false;
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 点击记录项
|
// 点击记录项
|
||||||
|
|
@ -365,35 +344,95 @@ export default {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: "提示",
|
title: "提示",
|
||||||
content: "确定要取消该订单吗?",
|
content: "确定要取消该订单吗?",
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.showToast({
|
|
||||||
title: "订单已取消",
|
|
||||||
icon: "success",
|
|
||||||
});
|
|
||||||
// 这里应该调用接口取消订单,然后刷新列表
|
// 这里应该调用接口取消订单,然后刷新列表
|
||||||
// 暂时从待支付列表中移除
|
const res = await cancelOrder({
|
||||||
const index = this.mockData.pending_payment.findIndex(
|
id: item.id,
|
||||||
(i) => i.id === item.id
|
});
|
||||||
);
|
if (res) {
|
||||||
if (index > -1) {
|
uni.showToast({
|
||||||
this.mockData.pending_payment.splice(index, 1);
|
title: "订单已取消",
|
||||||
// 添加到已取消列表
|
icon: "success",
|
||||||
this.mockData.cancelled.unshift({
|
|
||||||
...item,
|
|
||||||
status: "cancelled",
|
|
||||||
});
|
});
|
||||||
|
this.loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 立即支付
|
// 立即支付(与店铺详情页 handlePay 逻辑一致,跳转收银台小程序)
|
||||||
handlePay(item) {
|
async handlePay(item) {
|
||||||
uni.showToast({
|
if (!item || !item.orderNumber) {
|
||||||
title: "该功能正在开发中",
|
uni.showToast({ title: "订单信息异常", icon: "none" });
|
||||||
icon: "none",
|
return;
|
||||||
|
}
|
||||||
|
const couponArray = item.couponPurchaseRespVOS || [];
|
||||||
|
const couponMap = new Map();
|
||||||
|
couponArray.forEach((goods) => {
|
||||||
|
const key = goods.couponId;
|
||||||
|
if (!key) return;
|
||||||
|
const currentList = couponMap.get(key) || [];
|
||||||
|
currentList.push(goods);
|
||||||
|
couponMap.set(key, currentList);
|
||||||
});
|
});
|
||||||
|
let bodyStr = "";
|
||||||
|
couponMap.forEach((couponList) => {
|
||||||
|
bodyStr =
|
||||||
|
bodyStr +
|
||||||
|
(couponList[0] && couponList[0].couponName
|
||||||
|
? couponList[0].couponName
|
||||||
|
: "商品") +
|
||||||
|
"x" +
|
||||||
|
couponList.length +
|
||||||
|
";";
|
||||||
|
});
|
||||||
|
|
||||||
|
const randomstr = Math.floor(Math.random() * 10000000) + "";
|
||||||
|
const trxamt =
|
||||||
|
item.payableAmount != null && item.payableAmount !== ""
|
||||||
|
? String(item.payableAmount)
|
||||||
|
: "1";
|
||||||
|
const params = {
|
||||||
|
appid: "00390105",
|
||||||
|
body: bodyStr,
|
||||||
|
cusid: "56479107531MPMN",
|
||||||
|
notify_url:
|
||||||
|
"http://e989c692.natappfree.cc/admin-api/member/lu-order/tlNotice",
|
||||||
|
orgid: "56479107392N35H",
|
||||||
|
paytype: "W06",
|
||||||
|
randomstr: randomstr,
|
||||||
|
orderNumber: item.orderNumber,
|
||||||
|
remark: "1:" + item.orderNumber + ":" + bodyStr,
|
||||||
|
reqsn: item.orderNumber,
|
||||||
|
sign: "",
|
||||||
|
signtype: "RSA",
|
||||||
|
trxamt,
|
||||||
|
version: "12",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (item.orderNumber) {
|
||||||
|
uni.setStorageSync("lastOrderNumber", item.orderNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sign = await getPaySign(params);
|
||||||
|
params["sign"] = sign;
|
||||||
|
uni.navigateToMiniProgram({
|
||||||
|
appId: "wxef277996acc166c3",
|
||||||
|
extraData: params,
|
||||||
|
success(res) {
|
||||||
|
console.log("小程序跳转成功", res);
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
console.error("小程序跳转失败", err);
|
||||||
|
uni.showToast({ title: "跳转失败,请稍后重试", icon: "none" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("获取支付签名失败:", e);
|
||||||
|
uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 查看详情
|
// 查看详情
|
||||||
handleViewDetail(item) {
|
handleViewDetail(item) {
|
||||||
|
|
@ -514,25 +553,23 @@ export default {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
padding: 30rpx;
|
padding: 24rpx 24rpx 20rpx;
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
.record-header {
|
.record-header {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 16rpx;
|
||||||
padding-bottom: 20rpx;
|
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
|
||||||
|
|
||||||
.record-title-row {
|
.record-shop-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 12rpx;
|
margin-bottom: 8rpx;
|
||||||
|
|
||||||
.record-title {
|
.shop-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-family: PingFang-SC, PingFang-SC;
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
font-size: 30rpx;
|
font-size: 28rpx;
|
||||||
color: #1a1819;
|
color: #1a1819;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -621,47 +658,128 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.record-body {
|
||||||
|
background-color: #f8f9fb;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 18rpx;
|
||||||
|
|
||||||
.record-actions {
|
.record-goods {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 20rpx;
|
|
||||||
padding-top: 20rpx;
|
|
||||||
border-top: 1rpx solid #f0f0f0;
|
|
||||||
|
|
||||||
button{
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
width: 131rpx;
|
|
||||||
height: 50rpx;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
font-family: PingFang-SC, PingFang-SC;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 26rpx;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
&.cancel-btn {
|
.goods-image {
|
||||||
background-color: #f5f5f5;
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
|
||||||
|
.goods-title {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1a1819;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-subtitle {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-meta-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
|
||||||
|
.goods-price {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #d51c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-count {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
|
||||||
|
.total-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
.total-label {
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22rpx;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.pay-btn {
|
.total-amount {
|
||||||
|
margin-left: 4rpx;
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #1a1819;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
min-width: 150rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
font-family: PingFang-SC, PingFang-SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-btn {
|
||||||
background-color: #004294;
|
background-color: #004294;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.detail-btn {
|
.detail-btn {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.review-btn {
|
.review-btn {
|
||||||
background-color: #004294;
|
background-color: #004294;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
<template>
|
||||||
|
<view class="agreement-page">
|
||||||
|
<NavHeader title="用户服务协议" />
|
||||||
|
<scroll-view class="content" scroll-y="true">
|
||||||
|
<view class="agreement-content">
|
||||||
|
<text class="agreement-text">{{ agreementText }}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
NavHeader
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
agreementText: `用户服务协议
|
||||||
|
|
||||||
|
鉴于甲方运营微信小程序(以下简称"小程序"),为乙方提供餐饮、维修、保险、健康等专属收费服务;乙方自愿使用甲方提供的服务,为明确双方权利义务,根据《中华人民共和国民法典》《中华人民共和国个人信息保护法》等相关法律法规,甲乙双方本着平等、自愿、公平、诚实信用的原则,达成如下协议,以资共同遵守。
|
||||||
|
|
||||||
|
一、服务内容与范围
|
||||||
|
1.1 甲方通过小程序为乙方提供以下服务(具体服务详情以小程序内公示为准):
|
||||||
|
1.餐饮服务:包括但不限于餐饮优惠套餐选购、定点合作商家就餐预约、专属餐食配送对接等收费服务;
|
||||||
|
2.维修服务:包括但不限于车辆维修保养套餐销售、优先维修预约、上门维修费用对接、维修机构推荐等收费服务;
|
||||||
|
3.保险服务:包括但不限于车辆保险、人身意外险等产品的投保咨询、保费缴纳协助、理赔协助等收费服务;
|
||||||
|
4.健康服务:包括但不限于司机专属健康体检套餐、健康咨询、职业病预防指导等收费服务。
|
||||||
|
1.2 甲方有权根据市场需求、业务发展等情况调整服务内容及收费项目,相关调整将通过小程序公告或短信提前通知乙方。乙方继续使用服务的,视为认可调整后的内容。
|
||||||
|
|
||||||
|
二、服务费用与支付
|
||||||
|
2.1 收费标准:本协议项下服务均为收费服务,具体收费标准、计费方式、服务明细均在小程序对应服务页面明确公示,乙方可自行查看并选择购买。
|
||||||
|
2.2 价格调整:甲方可根据市场行情、服务成本等因素调整收费标准,调整后将通过小程序公告提前7个自然日公示,公示期满后生效。
|
||||||
|
2.3 支付方式:乙方应通过小程序内指定支付渠道(如微信支付)支付费用,支付成功后视为订单确认,甲方依订单约定提供服务。
|
||||||
|
2.4 套餐续费:套餐类、周期类服务需一次性支付对应周期费用,乙方应在服务期满前按小程序提示续费,逾期未续费的,服务自动终止。
|
||||||
|
|
||||||
|
三、费用退还
|
||||||
|
3.1 因甲方原因(如无法提供约定服务、服务存在重大瑕疵无法补救)导致服务无法履行的,甲方应根据乙方未使用的服务内容或时长,退还相应费用。
|
||||||
|
3.2 因乙方自身原因(如主动放弃服务、不再需要服务)申请退款的,除小程序明确标注可退款情形外,甲方不予退还已收费用。
|
||||||
|
3.3 退款申请:乙方需通过小程序客服提交书面退款申请及相关证明材料,甲方在收到申请后7个工作日内完成审核,审核通过的,退款金额原路返还;审核不通过的,告知乙方具体原因。
|
||||||
|
|
||||||
|
四、乙方权利与义务
|
||||||
|
4.1 乙方权利:
|
||||||
|
1.要求甲方按协议及订单约定提供符合标准的服务;
|
||||||
|
2.监督服务质量,对服务问题提出改进建议;
|
||||||
|
3.查询、更正本人的个人信息及服务订单、支付记录;
|
||||||
|
4.符合退款条件时,申请退还相应服务费用;
|
||||||
|
5.按约定申请终止本协议。
|
||||||
|
4.2 乙方义务:
|
||||||
|
1.向甲方提供真实、准确、完整的个人信息及相关资料,不得提供虚假信息或隐瞒重要事实;
|
||||||
|
2.遵守国家法律法规及甲方发布的服务规则,不得利用服务从事违法违规、损害他人合法权益的活动;
|
||||||
|
3.妥善保管小程序账号、密码等身份信息,对账号下所有操作行为承担责任,发现账号泄露、被盗用应及时通知甲方;
|
||||||
|
4.按协议及小程序公示标准,及时、足额支付服务费用;
|
||||||
|
5.配合甲方完成服务必需的信息核验、服务对接等工作。
|
||||||
|
|
||||||
|
五、甲方权利与义务
|
||||||
|
5.1 甲方权利:
|
||||||
|
1.按协议及公示标准向乙方收取服务费用;
|
||||||
|
2.取得乙方明确同意后,收集、使用乙方个人信息(详见《隐私政策》);
|
||||||
|
3.对乙方提供的信息进行必要核验,保障服务安全与合规;
|
||||||
|
4.乙方违约时,有权暂停或终止服务,并追究乙方相应责任;
|
||||||
|
5.提前公示后,调整服务内容、收费标准及服务规则。
|
||||||
|
5.2 甲方义务:
|
||||||
|
1.按协议及订单承诺,提供稳定、优质的各项服务;
|
||||||
|
2.在小程序显著位置公示收费标准、服务内容、退款规则等关键信息,保障乙方知情权;
|
||||||
|
3.严格保密乙方个人信息,不得泄露、篡改、非法出售或转让;
|
||||||
|
4.建立信息安全保障措施,保障乙方账号及信息安全;
|
||||||
|
5.设立客服通道,及时受理乙方咨询、投诉与建议,7个工作日内响应处理。
|
||||||
|
|
||||||
|
六、信息授权与保护
|
||||||
|
6.1 乙方知悉并同意,甲方为提供服务需收集、使用乙方相关个人信息,具体范围、方式、存储及保护措施详见《隐私政策》。《隐私政策》为本协议不可分割的组成部分,与本协议具有同等法律效力。
|
||||||
|
6.2 乙方有权随时撤回信息收集、使用的授权,但撤回后甲方可能无法继续提供部分或全部服务,由此产生的不利后果由乙方自行承担。
|
||||||
|
|
||||||
|
七、协议期限与终止
|
||||||
|
7.1 本协议自乙方点击小程序"同意"按钮之日起生效,有效期至一方按约定终止为止。
|
||||||
|
7.2 乙方可随时通过小程序客服提交协议终止申请,经甲方审核确认后,协议终止。终止后,乙方未使用完毕的服务可按退款条款申请退费。
|
||||||
|
7.3 乙方有下列情形之一的,甲方有权单方终止协议,无需承担违约责任:
|
||||||
|
1.提供虚假信息、伪造证明材料,影响服务开展的;
|
||||||
|
2.违反协议或甲方服务规则,经通知后限期未改正的;
|
||||||
|
3.利用服务从事违法违规活动的;
|
||||||
|
4.逾期支付费用超过15日,经催告仍未支付的;
|
||||||
|
5.其他严重损害甲方合法权益的行为。
|
||||||
|
7.4 因不可抗力、政策调整等不可归责于双方的原因导致协议无法履行的,协议自动终止,双方互不担责,甲方退还乙方未使用部分的服务费用。
|
||||||
|
|
||||||
|
八、违约责任
|
||||||
|
8.1 任何一方违反协议约定,给对方造成损失的,应承担全部赔偿责任(包括直接损失、维权产生的律师费、诉讼费等合理费用)。
|
||||||
|
8.2 甲方未按约定提供服务的,除退还相应费用外,还应按乙方已支付对应服务费用的10%支付违约金;违约金不足以弥补损失的,补足差额。
|
||||||
|
8.3 乙方未按时支付费用的,每逾期一日按逾期金额的0.5%支付违约金;逾期超过15日的,甲方有权终止协议并要求乙方赔偿损失。
|
||||||
|
8.4 乙方利用服务从事违法违规活动或损害第三方权益的,法律责任由乙方自行承担,给甲方造成损失的,乙方全额赔偿。
|
||||||
|
|
||||||
|
九、争议解决
|
||||||
|
因本协议签订、履行、解释产生的纠纷,双方应友好协商解决;协商不成的,任何一方均有权向甲方所在地有管辖权的人民法院提起诉讼。
|
||||||
|
|
||||||
|
十、其他
|
||||||
|
10.1 本协议未尽事宜,双方可另行签订补充协议,补充协议与本协议具有同等法律效力。
|
||||||
|
10.2 甲方通过小程序公告、短信发送的通知、公示,自发布或送达之日起生效,视为已履行告知义务,乙方应及时关注。
|
||||||
|
10.3 本协议条款被认定为无效或不可执行的,不影响其他条款的效力。`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.agreement-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-content {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: #333333;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -195,7 +195,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getDictDataByType, getGuildStorePage,getMessagePage } from "@/api/service";
|
import { getDictDataByType, getGuildStorePage, getMessagePage, isNeedMap } from "@/api/service";
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -217,6 +217,8 @@ export default {
|
||||||
// 位置相关
|
// 位置相关
|
||||||
selectedDistance: null, // 选中的距离(km),null表示不限距离
|
selectedDistance: null, // 选中的距离(km),null表示不限距离
|
||||||
showDistancePicker: false, // 是否显示距离选择器
|
showDistancePicker: false, // 是否显示距离选择器
|
||||||
|
// 配置开关:0=直接进地图,1=进商品页
|
||||||
|
needMapFlag: '0',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
|
|
@ -242,6 +244,22 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 读取"是否需要地图"开关(接口返回 data: 0/1)
|
||||||
|
// 每次点击详情前都需要调用接口,不使用缓存
|
||||||
|
async fetchNeedMapFlag() {
|
||||||
|
try {
|
||||||
|
const res = await isNeedMap();
|
||||||
|
console.log('isNeedMap', res);
|
||||||
|
// 兼容不同返回结构:{data:0} / {data:'0'} / 0
|
||||||
|
this.needMapFlag = res.paySysStatus;
|
||||||
|
return this.needMapFlag;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取地图开关失败:', e);
|
||||||
|
// 失败时默认走地图页,避免阻塞流程
|
||||||
|
this.needMapFlag = '0';
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
},
|
||||||
// 格式化距离显示:将米转换成千米
|
// 格式化距离显示:将米转换成千米
|
||||||
formatDistance(distanceInMeters) {
|
formatDistance(distanceInMeters) {
|
||||||
if (!distanceInMeters || distanceInMeters === 0) {
|
if (!distanceInMeters || distanceInMeters === 0) {
|
||||||
|
|
@ -480,20 +498,35 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// 进店按钮点击
|
// 进店按钮点击
|
||||||
handleServiceItemClick(item) {
|
async handleServiceItemClick(item) {
|
||||||
// 判断是店铺类型还是消息类型
|
// 判断是店铺类型还是消息类型
|
||||||
if (this.isStoreCategory()) {
|
if (this.isStoreCategory()) {
|
||||||
// 美食和维修跳转到店铺详情页面
|
// 先调接口判断:0=直接进地图,1=进商品页(商品页可再点顶部店铺信息进入地图)
|
||||||
uni.navigateTo({
|
const flag = await this.fetchNeedMapFlag();
|
||||||
url: `/pages/detail/serviceDetail?id=${item.id}&categoryLabel=${this.currentCategoryLabel}`,
|
if (flag == '0') {
|
||||||
});
|
uni.navigateTo({
|
||||||
|
url: `/pages/detail/mapDetail?id=${item.id}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/detail/serviceDetail?id=${item.id}&categoryLabel=${this.currentCategoryLabel}&distance=${item.distance}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 其他分类跳转到富文本详情页面
|
// 其他分类跳转到富文本详情页面
|
||||||
const title = '详情';
|
|
||||||
const content = item.content || '';
|
const content = item.content || '';
|
||||||
|
// 注意:URL 里必须 encodeURIComponent,否则 JSON 会被截断导致详情页拿不到 id/type
|
||||||
|
const infoObj = {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title || item.name || '详情',
|
||||||
|
// messageType / noticeType 兼容:不同接口字段可能不同
|
||||||
|
noticeType: item.messageType ?? item.noticeType ?? this.currentCategory,
|
||||||
|
coverUrl: item.coverUrl || '',
|
||||||
|
};
|
||||||
|
const info = encodeURIComponent(JSON.stringify(infoObj));
|
||||||
if (content) {
|
if (content) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/detail/richTextDetail?title=${encodeURIComponent(title)}&content=${encodeURIComponent(content)}`
|
url: `/pages/detail/richTextDetail?title=${encodeURIComponent(infoObj.title)}&info=${info}&content=${encodeURIComponent(content)}`
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|
@ -505,10 +538,17 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// 进店按钮点击 - 跳转到地图详情页
|
// 进店按钮点击 - 跳转到地图详情页
|
||||||
handleEnterStore(item) {
|
async handleEnterStore(item) {
|
||||||
uni.navigateTo({
|
const flag = await this.fetchNeedMapFlag();
|
||||||
url: `/pages/detail/mapDetail?id=${item.id}`,
|
if (flag == '0') {
|
||||||
});
|
uni.navigateTo({
|
||||||
|
url: `/pages/detail/mapDetail?id=${item.id}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/detail/serviceDetail?id=${item.id}&categoryLabel=${this.currentCategoryLabel}&distance=${item.distance}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 加载服务列表
|
// 加载服务列表
|
||||||
|
|
@ -707,6 +747,15 @@ export default {
|
||||||
width: 0; // 重要:flex布局时需要设置width: 0才能正确计算宽度
|
width: 0; // 重要:flex布局时需要设置width: 0才能正确计算宽度
|
||||||
height: 60rpx;
|
height: 60rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
// 隐藏滚动条(兼容多平台)
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none !important;
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
scrollbar-width: none; // Firefox
|
||||||
|
-ms-overflow-style: none; // IE 和 Edge
|
||||||
|
|
||||||
.category-list {
|
.category-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,8 @@ export default {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
|
|
@ -0,0 +1,168 @@
|
||||||
|
/**
|
||||||
|
* 判断经纬度是否超出中国境内(优化版:更精准的边界范围)
|
||||||
|
*/
|
||||||
|
function isLocationOutOfChina(latitude, longitude) {
|
||||||
|
// 先校验参数有效性
|
||||||
|
if (typeof latitude !== 'number' || typeof longitude !== 'number' || isNaN(latitude) || isNaN(longitude)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 优化后的中国经纬度范围(覆盖大部分核心区域,减少南海误判)
|
||||||
|
if (longitude < 72.004 || longitude > 137.8347 || latitude < 3.86 || latitude > 53.55) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将WGS-84(国际标准)转为GCJ-02(火星坐标):
|
||||||
|
*/
|
||||||
|
function transformFromWGSToGCJ(latitude, longitude) {
|
||||||
|
// 新增参数校验
|
||||||
|
if (typeof latitude !== 'number' || typeof longitude !== 'number' || isNaN(latitude) || isNaN(longitude)) {
|
||||||
|
throw new Error('输入的经纬度必须为有效数字');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修正:初始化为数字类型
|
||||||
|
let lat = latitude;
|
||||||
|
let lon = longitude;
|
||||||
|
const ee = 0.00669342162296594323;
|
||||||
|
const a = 6378245.0;
|
||||||
|
const pi = Math.PI; // 复用内置PI,提升精度
|
||||||
|
|
||||||
|
if (!isLocationOutOfChina(latitude, longitude)) {
|
||||||
|
const adjustLat = transformLatWithXY(longitude - 105.0, latitude - 35.0);
|
||||||
|
const adjustLon = transformLonWithXY(longitude - 105.0, latitude - 35.0);
|
||||||
|
const radLat = latitude / 180.0 * pi;
|
||||||
|
let magic = Math.sin(radLat);
|
||||||
|
magic = 1 - ee * magic * magic;
|
||||||
|
const sqrtMagic = Math.sqrt(magic);
|
||||||
|
lat = latitude + (adjustLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
||||||
|
lon = longitude + (adjustLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
||||||
|
}
|
||||||
|
return { latitude: lat, longitude: lon };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将GCJ-02(火星坐标)转为百度坐标:
|
||||||
|
*/
|
||||||
|
function transformFromGCJToBaidu(latitude, longitude) {
|
||||||
|
// 参数校验
|
||||||
|
if (typeof latitude !== 'number' || typeof longitude !== 'number' || isNaN(latitude) || isNaN(longitude)) {
|
||||||
|
throw new Error('输入的经纬度必须为有效数字');
|
||||||
|
}
|
||||||
|
|
||||||
|
const xPi = Math.PI * 3000.0 / 180.0;
|
||||||
|
const z = Math.sqrt(longitude * longitude + latitude * latitude) + 0.00002 * Math.sin(latitude * xPi);
|
||||||
|
const theta = Math.atan2(latitude, longitude) + 0.000003 * Math.cos(longitude * xPi);
|
||||||
|
const a_latitude = z * Math.sin(theta) + 0.006;
|
||||||
|
const a_longitude = z * Math.cos(theta) + 0.0065;
|
||||||
|
|
||||||
|
return { latitude: a_latitude, longitude: a_longitude };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将百度坐标转为GCJ-02(火星坐标):
|
||||||
|
*/
|
||||||
|
function transformFromBaiduToGCJ(latitude, longitude) {
|
||||||
|
// 参数校验
|
||||||
|
if (typeof latitude !== 'number' || typeof longitude !== 'number' || isNaN(latitude) || isNaN(longitude)) {
|
||||||
|
throw new Error('输入的经纬度必须为有效数字');
|
||||||
|
}
|
||||||
|
|
||||||
|
const xPi = Math.PI * 3000.0 / 180.0;
|
||||||
|
const x = longitude - 0.0065;
|
||||||
|
const y = latitude - 0.006;
|
||||||
|
const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * xPi);
|
||||||
|
const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * xPi);
|
||||||
|
const a_latitude = z * Math.sin(theta);
|
||||||
|
const a_longitude = z * Math.cos(theta);
|
||||||
|
|
||||||
|
return { latitude: a_latitude, longitude: a_longitude };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将GCJ-02(火星坐标)转为WGS-84:
|
||||||
|
*/
|
||||||
|
function transformFromGCJToWGS(latitude, longitude) {
|
||||||
|
// 参数校验
|
||||||
|
if (typeof latitude !== 'number' || typeof longitude !== 'number' || isNaN(latitude) || isNaN(longitude)) {
|
||||||
|
throw new Error('输入的经纬度必须为有效数字');
|
||||||
|
}
|
||||||
|
|
||||||
|
const threshold = 0.00001; // 约1米精度
|
||||||
|
const maxIteration = 30;
|
||||||
|
let iteration = 0;
|
||||||
|
|
||||||
|
// 初始边界
|
||||||
|
let minLat = latitude - 0.5;
|
||||||
|
let maxLat = latitude + 0.5;
|
||||||
|
let minLng = longitude - 0.5;
|
||||||
|
let maxLng = longitude + 0.5;
|
||||||
|
let delta = 1;
|
||||||
|
|
||||||
|
while (iteration < maxIteration && delta > threshold) {
|
||||||
|
iteration++;
|
||||||
|
const midLat = (minLat + maxLat) / 2;
|
||||||
|
const midLng = (minLng + maxLng) / 2;
|
||||||
|
const midPoint = transformFromWGSToGCJ(midLat, midLng);
|
||||||
|
delta = Math.abs(midPoint.latitude - latitude) + Math.abs(midPoint.longitude - longitude);
|
||||||
|
|
||||||
|
// 二分法缩小范围
|
||||||
|
if (midPoint.latitude > latitude) {
|
||||||
|
maxLat = midLat;
|
||||||
|
} else {
|
||||||
|
minLat = midLat;
|
||||||
|
}
|
||||||
|
if (midPoint.longitude > longitude) {
|
||||||
|
maxLng = midLng;
|
||||||
|
} else {
|
||||||
|
minLng = midLng;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { latitude: (minLat + maxLat) / 2, longitude: (minLng + maxLng) / 2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断点是否在矩形范围内
|
||||||
|
*/
|
||||||
|
function isContains(point, p1, p2) {
|
||||||
|
if (!point || !p1 || !p2) return false;
|
||||||
|
return (point.latitude >= Math.min(p1.latitude, p2.latitude)
|
||||||
|
&& point.latitude <= Math.max(p1.latitude, p2.latitude)
|
||||||
|
&& point.longitude >= Math.min(p1.longitude, p2.longitude)
|
||||||
|
&& point.longitude <= Math.max(p1.longitude, p2.longitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纬度偏移计算
|
||||||
|
*/
|
||||||
|
function transformLatWithXY(x, y) {
|
||||||
|
const pi = Math.PI;
|
||||||
|
let lat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
|
||||||
|
lat += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
lat += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
lat += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经度偏移计算
|
||||||
|
*/
|
||||||
|
function transformLonWithXY(x, y) {
|
||||||
|
const pi = Math.PI;
|
||||||
|
let lon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
|
||||||
|
lon += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||||
|
lon += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
|
||||||
|
lon += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
|
||||||
|
return lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
isLocationOutOfChina,
|
||||||
|
transformFromWGSToGCJ,
|
||||||
|
transformFromGCJToBaidu,
|
||||||
|
transformFromBaiduToGCJ,
|
||||||
|
transformFromGCJToWGS
|
||||||
|
};
|
||||||
|
|
||||||
Loading…
Reference in New Issue