Compare commits

...

2 Commits

Author SHA1 Message Date
wk 346a17f8b2 feat:我的订单+支付返回 2026-03-04 16:58:40 +08:00
wk c32de7df7d 点赞收藏 2026-03-02 15:32:41 +08:00
25 changed files with 2184 additions and 740 deletions

74
App.vue
View File

@ -1,4 +1,6 @@
<script>
import { updateLuOrderPayStatus } from "@/api/service";
export default {
globalData: {
//
@ -22,6 +24,7 @@
//
const extraData = options.referrerInfo.extraData;
console.log("extraData",extraData)
if (!extraData) {
uni.showToast({
@ -31,10 +34,23 @@
});
} else {
if (extraData.code === 'success') {
//
// 1. 使
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: 'success'
title: '支付返回缺少订单号,请稍后在服务记录中查看',
icon: 'none',
duration: 3000
});
}
} else if (extraData.code === 'cancel') {
uni.showToast({
title: '支付已取消',
@ -52,6 +68,11 @@
},
onHide: function() {
console.log('App Hide')
//
if (this._orderStatusTimer) {
clearInterval(this._orderStatusTimer);
this._orderStatusTimer = null;
}
},
methods: {
//
@ -80,6 +101,55 @@
}
}, 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);
}
}
}

View File

@ -47,6 +47,24 @@ export function getMessagePage(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 = {}){
return request({
@ -63,6 +81,36 @@ export function appBuy (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(司机公会简称) 订单信息
export function getLuOrder (id){
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){
// return request({
@ -94,3 +151,31 @@ export function getLuMyOrderPage (params = {}){
// 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,
})
}

View File

@ -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>

View File

@ -52,13 +52,6 @@
"navigationStyle": "custom"
}
},
{
"path": "activitiesDetail",
"style": {
"navigationBarTitleText": "工会详情",
"navigationStyle": "custom"
}
},
{
"path": "mapDetail",
"style": {
@ -118,12 +111,33 @@
"navigationStyle": "custom"
}
},
{
"path": "pointsMemberRules",
"style": {
"navigationBarTitleText": "积分会员制度",
"navigationStyle": "custom"
}
},
{
"path": "serviceRecords",
"style": {
"navigationBarTitleText": "服务记录",
"navigationStyle": "custom"
}
},
{
"path": "userAgreement",
"style": {
"navigationBarTitleText": "用户服务协议",
"navigationStyle": "custom"
}
},
{
"path": "privacyPolicy",
"style": {
"navigationBarTitleText": "隐私政策",
"navigationStyle": "custom"
}
}
]
}

View File

@ -161,7 +161,7 @@ export default {
handleActivityClick(item) {
//
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) {
//
uni.navigateTo({
url: `/pages/detail/activitiesDetail?id=${item.id}`
url: `/pages/detail/richTextDetail?id=${item.id}`
});
},
},

View File

@ -39,7 +39,9 @@
></image>
</view>
<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-time">{{ formatTime(item.createTime) }}</view>
</view>
@ -48,7 +50,9 @@
<!-- 加载更多提示 -->
<view class="load-more" v-if="collectList.length > 0">
<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>
</view>
</view>
@ -62,7 +66,7 @@ import NavHeader from "@/components/NavHeader/NavHeader.vue";
export default {
components: {
NavHeader
NavHeader,
},
data() {
return {
@ -158,25 +162,9 @@ export default {
//
handleCollectClick(item) {
//
// collectType:
// 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",
});
}
}
// uni.navigateTo({
// url: `/pages/detail/richTextDetail?id=${item.collectId}`,
// });
},
},
};

View File

@ -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>

View File

@ -12,9 +12,9 @@
:longitude="mapCenter.longitude"
:markers="markers"
:scale="16"
:show-location="false"
:enable-zoom="false"
:enable-scroll="false"
:show-location="true"
:enable-zoom="true"
:enable-scroll="true"
:enable-rotate="false"
:enable-poi="true"
></map>
@ -47,7 +47,7 @@
src="/static/service/location-icon.png"
mode="aspectFill"
></image>
<text class="distance-text">{{ storeInfo.distance || "0" }}km</text>
<text class="distance-text">{{ distanceText }}</text>
</view>
</view>
@ -74,7 +74,9 @@
</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>
@ -89,6 +91,7 @@
<script>
import { getGuildStoreDetail } from "@/api/service";
import NavHeader from "@/components/NavHeader/NavHeader.vue";
import { transformFromWGSToGCJ } from "@/utils/coordinate";
export default {
components: {
@ -110,6 +113,8 @@ export default {
userLocation: null,
//
mapInitialized: false,
// GCJ-02
storeLocationGCJ: null,
};
},
computed: {
@ -132,6 +137,29 @@ export default {
// ? `${(distance * 1000).toFixed(0)}m`
// : `${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) {
this.shopId = options.id;
@ -188,16 +216,46 @@ export default {
if (res.latitude && res.longitude) {
//
if (this.isValidCoordinate(res.latitude, res.longitude)) {
//
// WGS-84 GCJ-02使
try {
const wgs84Lat = parseFloat(res.latitude);
const wgs84Lng = parseFloat(res.longitude);
const gcj02Coord = transformFromWGSToGCJ(wgs84Lat, wgs84Lng);
//
this.storeLocationGCJ = {
latitude: gcj02Coord.latitude,
longitude: gcj02Coord.longitude,
};
// 使
this.mapCenter = {
latitude: parseFloat(res.latitude),
longitude: parseFloat(res.longitude),
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 {
console.warn("店铺经纬度无效:", res.latitude, res.longitude);
uni.showToast({
@ -227,10 +285,13 @@ export default {
//
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 lng = parseFloat(this.storeInfo.longitude);
const lat = this.storeLocationGCJ.latitude;
const lng = this.storeLocationGCJ.longitude;
//
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
// 使 WGS-84
calculateDistance(lat1, lng1, lat2, lng2) {
//
if (
@ -466,6 +586,30 @@ export default {
color: #ffffff;
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;
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<view class="rich-text-detail-page">
<!-- 顶部导航栏 -->
<NavHeader :title="title || '详情'" />
<NavHeader title="详情" />
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y="true">
@ -11,7 +11,10 @@
</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">
<!-- 如果是图片使用原生 image 组件 -->
<view v-if="item.type === 'image'" class="rich-text-image-wrapper">
@ -23,7 +26,11 @@
></image>
</view>
<!-- 如果是文本内容使用 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>
</view>
@ -37,45 +44,146 @@
<text class="empty-text">暂无内容</text>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<DetailActionBar
v-if="detailData"
:liked="isLiked"
:collected="isCollected"
:id="detailId"
:zanType="noticeType"
:type="noticeType"
/>
</view>
</template>
<script>
import { getGuildDetail } from "@/api/home.js";
import NavHeader from "@/components/NavHeader/NavHeader.vue";
import DetailActionBar from "@/components/DetailActionBar/DetailActionBar.vue";
export default {
components: {
NavHeader
NavHeader,
DetailActionBar,
},
data() {
return {
title: '',
content: '',
detailId: null, // ID
title: "",
content: "",
parsedContent: [], //
loading: false,
isLiked: false, //
isCollected: false, //
detailData: null, // 使
noticeType: null, //
};
},
onLoad(options) {
//
//
// 1. id activitiesDetail
// 2. title content richTextDetail
if (options.id) {
this.detailId = options.id;
this.loadDetailById();
} else if (options.content) {
//
if (options.title) {
this.title = decodeURIComponent(options.title);
}
if (options.content) {
let content = decodeURIComponent(options.content);
// HTML
this.parsedContent = this.parseHtmlContent(content);
} else {
uni.showToast({
title: "缺少内容",
icon: "none",
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
if (options.info) {
// info encodeURIComponent(JSON.stringify(obj)) decode + JSON.parse
try {
const parsed = JSON.parse(decodeURIComponent(options.info));
this.detailData = parsed;
this.detailId = parsed.id || this.detailId;
// noticeType/messageType/type
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: {
// 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
parseHtmlContent(html) {
if (!html) return [];
@ -94,8 +202,8 @@ export default {
const textContent = html.substring(lastIndex, match.index);
if (textContent.trim()) {
result.push({
type: 'text',
html: textContent
type: "text",
html: textContent,
});
}
}
@ -105,8 +213,8 @@ export default {
const srcMatch = imgAttrs.match(/src=["']([^"']+)["']/i);
if (srcMatch && srcMatch[1]) {
result.push({
type: 'image',
src: srcMatch[1]
type: "image",
src: srcMatch[1],
});
}
@ -118,8 +226,8 @@ export default {
const textContent = html.substring(lastIndex);
if (textContent.trim()) {
result.push({
type: 'text',
html: textContent
type: "text",
html: textContent,
});
}
}
@ -127,13 +235,13 @@ export default {
//
if (result.length === 0 && html.trim()) {
result.push({
type: 'text',
html: html
type: "text",
html: html,
});
}
return result;
}
},
},
};
</script>
@ -145,6 +253,7 @@ export default {
display: flex;
flex-direction: column;
overflow: hidden;
padding-bottom: 120rpx; //
}
/* 内容区域 */

View File

@ -3,8 +3,8 @@
<!-- 顶部导航栏 -->
<NavHeader title="店铺详情" />
<!-- 顶部店铺信息 -->
<view class="store-header">
<!-- 顶部店铺信息点击进入地图页 -->
<view class="store-header" @click="handleGoMap">
<view class="store-brand">
<image
class="brand-image"
@ -32,7 +32,7 @@
mode="aspectFill"
></image>
<text class="distance-text"
>距您 {{ storeInfo.distance || "0" }}km</text
>距您 {{ distance || "0" }}km</text
>
</view>
</view>
@ -120,7 +120,7 @@ import {
getGuildStoreDetail,
getGuildCoupon,
getPaySign,
appBuy,
appBuy
} from "@/api/service";
import NavHeader from "@/components/NavHeader/NavHeader.vue";
@ -135,8 +135,8 @@ export default {
categoryLabel: "",
shopId: null,
userInfo: {},
hasCheckedLogin: false, //
isLoadingAfterLogin: false, //
distance: null,
paying: false,
};
},
computed: {
@ -145,8 +145,7 @@ export default {
return this.menuList.reduce((total, item) => {
if (item.selected && item.quantity > 0) {
//
const price =
(item.salePrice || item.currentPrice || item.price || 0) / 100;
const price = (item.salePrice || 0) / 100;
return total + price * item.quantity;
}
return total;
@ -165,10 +164,15 @@ export default {
},
},
onLoad(options) {
console.log(options, 111110);
// ID
this.shopId = options.id;
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) {
this.loadStoreData();
} else {
@ -195,35 +199,23 @@ export default {
newUserInfo.id
) {
this.userInfo = newUserInfo;
this.isLoadingAfterLogin = true; //
// 401
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) {
//
this.userInfo = newUserInfo;
}
//
this.hasCheckedLogin = true;
},
methods: {
//
handleGoMap() {
if (!this.shopId) return;
uni.navigateTo({
url: `/pages/detail/mapDetail?id=${this.shopId}`,
});
},
//
async loadStoreData() {
try {
@ -247,6 +239,10 @@ export default {
const res = await getGuildStoreDetail(shopId);
if (res) {
this.storeInfo = res;
//
if (this.storeInfo.distance && typeof this.storeInfo.distance === 'number') {
this.storeInfo.distance = (this.storeInfo.distance / 1000).toFixed(2);
}
}
} catch (error) {
console.error("加载店铺详情失败:", error);
@ -275,7 +271,8 @@ export default {
item.selected = !item.selected;
if (!item.selected) {
item.quantity = 0;
} else if (item.quantity === 0) {
} else if (!item.quantity || item.quantity <= 0) {
// 0 1
item.quantity = 1;
}
},
@ -387,9 +384,12 @@ export default {
});
const randomstr = Math.floor(Math.random() * 10000000) + "";
//
//
// STring ,
// "1"
const trxamt =
order.payableAmount != null && order.payableAmount !== ""
? String(order.payableAmount)
: "1";
//
let params = {
appid: "00390105", // appid
body: bodyStr, //
@ -399,40 +399,37 @@ export default {
orgid: "56479107392N35H",
paytype: "W06",
randomstr: randomstr,
remark: "1:"+order.orderNumber+ ":" + bodyStr, // 1 , ,,body
orderNumber: order.orderNumber,
remark: "1:" + order.orderNumber + ":" + bodyStr, // 1 , , body
reqsn: order.orderNumber,
sign: "",
signtype: "RSA",
// trxamt: "" + order.payableAmount,
trxamt: "1",//
trxamt,
version: "12",
//"asinfo":"56479107392MP4J:01:0.01" //(, ) , ,01 , ,0.01
};
if (order.orderNumber) {
uni.setStorageSync("lastOrderNumber", order.orderNumber);
}
try {
const sign = await getPaySign(params);
console.log("测试返回的签名:" + sign);
params["sign"] = sign;
// 使 navigateToMiniProgram
uni.navigateToMiniProgram({
appId: "wxef277996acc166c3", // appid
extraData: params, //
extraData: params,
success(res) {
console.log("小程序跳转成功", res);
},
fail(err) {
console.error("小程序跳转失败", err);
//
uni.showToast({
title: "跳转失败,请稍后重试",
icon: "none",
});
},
complete() {
//
uni.showToast({ title: "跳转失败,请稍后重试", icon: "none" });
},
});
//----
} catch (e) {
console.error("获取支付签名失败:", e);
uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
}
},
},
};
@ -634,12 +631,12 @@ export default {
display: flex;
align-items: baseline;
font-weight: bold;
font-size: 24rpx;
font-size: 22rpx;
color: #d51c3c;
.price-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 48rpx;
font-size: 47rpx;
color: #d51c3c;
}
}
@ -647,7 +644,7 @@ export default {
.original-price {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 20rpx;
font-size: 18rpx;
color: #545454;
text-decoration: line-through;
background-color: rgba(115, 115, 115, 0.1);
@ -657,7 +654,7 @@ export default {
.quantity-control {
display: flex;
align-items: center;
gap: 20rpx;
gap: 15rpx;
margin-left: auto;
.quantity-btn {

View File

@ -302,15 +302,6 @@ export default {
});
}
},
//
async handleJoinGuild() {
uni.showToast({
title: "该功能正在开发中",
icon: "none",
});
},
//
handleViewAllBenefits() {
//
@ -361,7 +352,7 @@ export default {
handleActivityClick(item) {
//
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) {
//
uni.navigateTo({
url: `/pages/detail/activitiesDetail?id=${item.id}`
url: `/pages/detail/richTextDetail?id=${item.id}`
});
},
},

View File

@ -65,7 +65,7 @@
class="wechat-login-btn"
open-type="getPhoneNumber"
@getphonenumber="handleGetPhoneNumber"
:disabled="loading"
:disabled="loading || !agreedToTerms"
>
<text>一键授权手机号快速登录</text>
</button>
@ -76,6 +76,21 @@
<text>小程序一键授权登录仅支持微信小程序环境</text>
</view>
<!-- #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>
@ -92,6 +107,7 @@ export default {
showBack: false,
loading: false,
showPassword: false,
agreedToTerms: false, //
formData: {
mobile: '',
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) {
console.log('获取手机号授权结果:', e)
//
if (!this.agreedToTerms) {
uni.showToast({
title: '请先阅读并同意用户协议和隐私政策',
icon: 'none',
duration: 2000
})
return
}
if (e.detail.errMsg === 'getPhoneNumber:ok') {
this.loading = true
try {
@ -612,5 +657,51 @@ export default {
font-size: 24rpx;
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>

View File

@ -14,11 +14,16 @@
}"
>
<view class="user-info">
<view class="user-level">{{ userInfo.level.name }}</view>
<!-- #ifdef MP-WEIXIN -->
<button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="handleChooseAvatar">
<button
class="avatar-btn"
open-type="chooseAvatar"
@chooseavatar="handleChooseAvatar"
>
<image
class="avatar"
:src="userInfo.avatar || '/static/logo.png'"
:src="userInfo.avatar || '/static/tabbar/profile.png'"
mode="aspectFill"
></image>
</button>
@ -27,7 +32,7 @@
<view class="avatar-wrapper" @click="handleChooseAvatar">
<image
class="avatar"
:src="userInfo.avatar || '/static/logo.png'"
:src="userInfo.avatar || '/static/tabbar/profile.png'"
mode="aspectFill"
></image>
</view>
@ -117,7 +122,7 @@ export default {
navBarHeight: 0,
currentTime: "10:55",
userInfo: {
level: {} // level
level: {}, // level
},
noticeNum: 3,
uploading: false, //
@ -205,10 +210,10 @@ export default {
}
}
},
//
goToMemberBenefits() {
uni.showToast({
title: "该功能正在开发中",
icon: "none",
uni.navigateTo({
url: "/pages/profileSub/pointsMemberRules",
});
},
goToRealNameAuth() {
@ -273,8 +278,8 @@ export default {
// 使 chooseImage
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
sizeType: ["compressed"],
sourceType: ["album", "camera"],
success: (res) => {
const tempFilePath = res.tempFilePaths[0];
//
@ -283,12 +288,12 @@ export default {
this.uploadAvatar(tempFilePath);
},
fail: (err) => {
console.error('选择图片失败:', err);
console.error("选择图片失败:", err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
title: "选择图片失败",
icon: "none",
});
}
},
});
// #endif
},
@ -301,24 +306,24 @@ export default {
this.uploading = true;
uni.showLoading({
title: '上传中...',
mask: true
title: "上传中...",
mask: true,
});
// token
const token = uni.getStorageSync('token');
const BASE_URL = 'https://guangsh.manage.hschengtai.com';
const token = uni.getStorageSync("token");
const BASE_URL = "https://guangsh.manage.hschengtai.com";
uni.uploadFile({
url: `${BASE_URL}/app-api/infra/file/upload`,
filePath: filePath,
name: 'file',
name: "file",
formData: {
directory: 'avatar' //
directory: "avatar", //
},
header: {
'Authorization': `Bearer ${token}`,
'tenant-id': '1'
Authorization: `Bearer ${token}`,
"tenant-id": "1",
},
success: (res) => {
uni.hideLoading();
@ -331,27 +336,27 @@ export default {
//
this.userInfo.avatar = imageUrl;
//
uni.setStorageSync('userInfo', this.userInfo);
uni.setStorageSync("userInfo", this.userInfo);
//
this.updateUserAvatar(imageUrl);
uni.showToast({
title: '头像上传成功',
icon: 'success',
duration: 1500
title: "头像上传成功",
icon: "success",
duration: 1500,
});
} else {
throw new Error('上传成功但未返回图片地址');
throw new Error("上传成功但未返回图片地址");
}
} else {
throw new Error(data.message || data.msg || '上传失败');
throw new Error(data.message || data.msg || "上传失败");
}
} catch (error) {
console.error('解析上传结果失败:', error);
console.error("解析上传结果失败:", error);
uni.showToast({
title: error.message || '上传失败,请重试',
icon: 'none'
title: error.message || "上传失败,请重试",
icon: "none",
});
//
this.loadUserInfo();
@ -359,17 +364,17 @@ export default {
},
fail: (err) => {
uni.hideLoading();
console.error('上传头像失败:', err);
console.error("上传头像失败:", err);
uni.showToast({
title: '上传失败,请检查网络',
icon: 'none'
title: "上传失败,请检查网络",
icon: "none",
});
//
this.loadUserInfo();
},
complete: () => {
this.uploading = false;
}
},
});
},
@ -379,9 +384,9 @@ export default {
//
// await updateUserInfo({ avatar: avatarUrl });
//
console.log('头像已更新:', avatarUrl);
console.log("头像已更新:", avatarUrl);
} catch (error) {
console.error('更新用户头像失败:', error);
console.error("更新用户头像失败:", error);
// 使使
}
},
@ -427,6 +432,16 @@ export default {
padding-top: 89rpx;
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 {
width: 109rpx;
height: 108rpx;

View File

@ -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>

View File

@ -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>

View File

@ -204,12 +204,27 @@
</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">
<button
class="submit-btn"
:class="{ disabled: loading }"
:disabled="loading"
:class="{ disabled: loading || !agreedToTerms }"
:disabled="loading || !agreedToTerms"
@click="handleSubmit"
>
{{ loading ? '提交中...' : '提交认证' }}
@ -230,6 +245,7 @@ export default {
data() {
return {
loading: false,
agreedToTerms: false, //
idTypeIndex: 0,
idTypeOptions: [
{ 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 {
margin-top: 40rpx;
margin-top: 20rpx;
padding: 0 30rpx;
padding-bottom: 40rpx;
.submit-btn {

View File

@ -24,7 +24,7 @@
:class="{ active: currentTab === 'completed' }"
@click="switchTab('completed')"
>
<text class="tab-text">完成</text>
<text class="tab-text">退款</text>
</view>
<view
class="tab-item"
@ -60,39 +60,62 @@
class="record-item"
v-for="(item, index) in currentList"
:key="index"
@click="handleRecordClick(item)"
>
<!-- 头部门店 + 状态 -->
<view class="record-header">
<view class="record-title-row">
<text class="record-title">{{ item.serviceName }}</text>
<view class="record-shop-row">
<text class="shop-name">{{ item.shopName }}</text>
<view class="status-badge" :class="getStatusClass(item.status)">
<text class="status-text">{{ getStatusText(item.status) }}</text>
</view>
</view>
<text class="record-time">{{ item.createTime }}</text>
<text class="record-time">{{ formatTime(item.createTime) }}</text>
</view>
<view class="record-content">
<view class="record-info-row">
<text class="info-label">服务类型</text>
<text class="info-value">{{ item.serviceType }}</text>
<!-- 中部类似商品卡片 -->
<view class="record-body" @click="handleRecordClick(item)">
<view
class="record-goods"
v-for="(goods, gIndex) in getGoodsList(item)"
:key="goods.id || goods.couponId || gIndex"
>
<image
class="goods-image"
:src="
goods.coverUrl ||
goods.couponCoverUrl ||
goods.picUrl ||
'/static/home/entry_icon.png'
"
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 class="record-info-row">
<text class="info-label">服务门店</text>
<text class="info-value">{{ item.storeName }}</text>
</view>
<view class="record-info-row">
<text class="info-label">订单号</text>
<text class="info-value">{{ item.orderNo }}</text>
</view>
<view class="record-info-row">
<text class="info-label">订单金额</text>
<text class="info-value price">¥{{ item.amount }}</text>
</view>
<!-- 底部合计 + 操作按钮 -->
<view class="record-footer">
<view class="total-info">
<text class="total-label">实付款</text>
<text class="total-amount">¥{{ formatFen(item.payableAmount) }}</text>
</view>
<!-- 操作按钮区域 -->
<view class="record-actions" v-if="item.status === 'pending_payment'">
<view class="record-actions" v-if="item.status === 1">
<button
class="action-btn cancel-btn"
@click.stop="handleCancel(item)"
@ -103,19 +126,16 @@
立即支付
</button>
</view>
<view
class="record-actions"
v-else-if="item.status === 'pending_verification'"
>
<button
<view class="record-actions" v-else-if="item.status === 2">
<!-- <button
class="action-btn detail-btn"
@click.stop="handleViewDetail(item)"
>
查看详情
</button>
去核销
</button> -->
</view>
<view class="record-actions" v-else-if="item.status === 'completed'">
<button
<view class="record-actions" v-else-if="item.status === 3">
<!-- <button
class="action-btn detail-btn"
@click.stop="handleViewDetail(item)"
>
@ -126,17 +146,18 @@
@click.stop="handleReview(item)"
>
评价
</button>
</button> -->
</view>
<view class="record-actions" v-else-if="item.status === 'cancelled'">
<view class="record-actions" v-else-if="item.status === 4">
<button
class="action-btn detail-btn"
@click.stop="handleViewDetail(item)"
>
查看详情
删除
</button>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="currentList.length > 0">
@ -150,10 +171,11 @@
<script>
import NavHeader from "@/components/NavHeader/NavHeader.vue";
import { getLuMyOrderPage, cancelOrder, getPaySign } from "@/api/service";
export default {
components: {
NavHeader
NavHeader,
},
data() {
return {
@ -164,128 +186,64 @@ export default {
hasMore: true,
pageNo: 1,
pageSize: 10,
//
mockData: {
// pending_payment: [
// {
// id: 1,
// orderNo: "ORD20250101001",
// 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",
// },
// ],
//
recordsMap: {
pending_payment: [],
pending_verification: [],
completed: [],
cancelled: [],
},
};
},
computed: {
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();
},
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
switchTab(tab) {
if (this.currentTab === tab) return;
@ -306,34 +264,60 @@ export default {
},
//
getStatusText(status) {
const statusMap = {
pending_payment: "待支付",
pending_verification: "待核销",
completed: "已完成",
cancelled: "已取消",
};
return statusMap[status] || "";
return status === 1
? "待支付"
: status === 2
? "待核销"
: status === 3
? "已完成"
: status === 4
? "已取消"
: "";
},
//
getStatusClass(status) {
const classMap = {
pending_payment: "status-pending",
pending_verification: "status-verification",
completed: "status-completed",
cancelled: "status-cancelled",
};
return classMap[status] || "";
return status === 1
? "status-pending"
: status === 2
? "status-verification"
: status === 3
? "status-completed"
: status === 4
? "status-cancelled"
: "";
},
//
loadData() {
async loadData(append = false) {
if (this.loading) return;
this.loading = true;
//
setTimeout(() => {
const status = this.getStatusValue();
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.refreshing = false;
this.loadingMore = false;
// 使
}, 500);
}
},
//
handleRefresh() {
@ -347,12 +331,7 @@ export default {
if (this.hasMore && !this.loadingMore && !this.loading) {
this.loadingMore = true;
this.pageNo += 1;
//
setTimeout(() => {
this.loadingMore = false;
//
this.hasMore = false;
}, 500);
this.loadData(true);
}
},
//
@ -365,35 +344,95 @@ export default {
uni.showModal({
title: "提示",
content: "确定要取消该订单吗?",
success: (res) => {
success: async (res) => {
if (res.confirm) {
//
const res = await cancelOrder({
id: item.id,
});
if (res) {
uni.showToast({
title: "订单已取消",
icon: "success",
});
//
//
const index = this.mockData.pending_payment.findIndex(
(i) => i.id === item.id
);
if (index > -1) {
this.mockData.pending_payment.splice(index, 1);
//
this.mockData.cancelled.unshift({
...item,
status: "cancelled",
});
this.loadData();
}
}
},
});
},
//
handlePay(item) {
uni.showToast({
title: "该功能正在开发中",
icon: "none",
// handlePay
async handlePay(item) {
if (!item || !item.orderNumber) {
uni.showToast({ title: "订单信息异常", 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) {
@ -514,25 +553,23 @@ export default {
background-color: #ffffff;
border-radius: 20rpx;
margin-bottom: 20rpx;
padding: 30rpx;
padding: 24rpx 24rpx 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.record-header {
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
margin-bottom: 16rpx;
.record-title-row {
.record-shop-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
margin-bottom: 8rpx;
.record-title {
.shop-name {
flex: 1;
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 30rpx;
font-weight: 600;
font-size: 28rpx;
color: #1a1819;
}
@ -621,14 +658,98 @@ export default {
}
}
}
.record-body {
background-color: #f8f9fb;
border-radius: 16rpx;
padding: 18rpx;
.record-goods {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.goods-image {
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;
}
.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;
justify-content: flex-end;
gap: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
gap: 16rpx;
button {
margin: 0;
@ -636,32 +757,29 @@ export default {
}
.action-btn {
width: 131rpx;
height: 50rpx;
border-radius: 10rpx;
min-width: 150rpx;
height: 56rpx;
padding: 0 20rpx;
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 26rpx;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
&.cancel-btn {
background-color: #f5f5f5;
color: #666666;
box-sizing: border-box;
}
&.pay-btn {
.pay-btn {
background-color: #004294;
color: #ffffff;
}
&.detail-btn {
.detail-btn {
background-color: #f5f5f5;
color: #666666;
}
&.review-btn {
.review-btn {
background-color: #004294;
color: #ffffff;
}

View File

@ -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>

View File

@ -195,7 +195,7 @@
</template>
<script>
import { getDictDataByType, getGuildStorePage,getMessagePage } from "@/api/service";
import { getDictDataByType, getGuildStorePage, getMessagePage, isNeedMap } from "@/api/service";
export default {
data() {
return {
@ -217,6 +217,8 @@ export default {
//
selectedDistance: null, // kmnull
showDistancePicker: false, //
// 0=1=
needMapFlag: '0',
};
},
onLoad() {
@ -242,6 +244,22 @@ export default {
}
},
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) {
if (!distanceInMeters || distanceInMeters === 0) {
@ -480,20 +498,35 @@ export default {
},
//
handleServiceItemClick(item) {
async handleServiceItemClick(item) {
//
if (this.isStoreCategory()) {
//
// 0=1=
const flag = await this.fetchNeedMapFlag();
if (flag == '0') {
uni.navigateTo({
url: `/pages/detail/serviceDetail?id=${item.id}&categoryLabel=${this.currentCategoryLabel}`,
url: `/pages/detail/mapDetail?id=${item.id}`,
});
} else {
uni.navigateTo({
url: `/pages/detail/serviceDetail?id=${item.id}&categoryLabel=${this.currentCategoryLabel}&distance=${item.distance}`,
});
}
} else {
//
const title = '详情';
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) {
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 {
uni.showToast({
@ -505,10 +538,17 @@ export default {
},
// -
handleEnterStore(item) {
async handleEnterStore(item) {
const flag = await this.fetchNeedMapFlag();
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; // flexwidth: 0
height: 60rpx;
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 {
display: flex;

View File

@ -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

168
utils/coordinate.js 100644
View File

@ -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
};