fix:微信一键登录+积分转换元

cz_dev
wk 2026-01-13 12:12:48 +08:00
parent 134154715a
commit 15b9d11157
20 changed files with 1229 additions and 478 deletions

View File

@ -24,14 +24,15 @@ export function login(data) {
/**
* 小程序一键授权手机号登录
* @param {Object} data 登录数据
* @param {String} data.code 微信授权code
* @param {String} data.encryptedData 加密数据
* @param {String} data.iv 初始向量
* @param {String} data.phoneCode 手机 code, 小程序通过 wx.getPhoneNumber 方法获得
* @param {String} data.loginCode 登录 code, 小程序通过 wx.login 方法获得
* @param {String} data.state state 参数必填用于回调的随机值
* @param {String} data.inviteCode 邀请码可选第一位是类型第二位是用户id例如1-1, 0-1
* @returns {Promise} 返回登录结果包含token等
*/
export function loginByPhone(data) {
return request({
url: '/app-api/member/auth/login',
url: '/app-api/member/auth/weixin-mini-app-login',
method: 'POST',
data: data,
showLoading: true,

View File

@ -38,3 +38,12 @@ export function getGuildCoupon(params = {}) {
data: params,
})
}
// 获得图文消息分页
export function getMessagePage(params = {}) {
return request({
url: '/app-api/member/labor-union-message/page',
method: 'GET',
data: params,
})
}

View File

@ -1,6 +1,6 @@
{
"name" : "demo",
"appid" : "__UNI__B358CDA",
"appid" : "wxa3c0e1381f643f59",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
@ -50,7 +50,7 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"appid" : "wxa3c0e1381f643f59",
"setting" : {
"urlCheck" : false
},

View File

@ -1,12 +1,5 @@
{
"pages": [
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/index/index",
"style": {
@ -14,6 +7,13 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/service/service",
"style": {
@ -29,16 +29,9 @@
}
},
{
"path": "pages/profile/realNameAuth",
"path": "pages/webview/webview",
"style": {
"navigationBarTitleText": "实名认证",
"navigationStyle": "custom"
}
},
{
"path": "pages/profile/serviceRecords",
"style": {
"navigationBarTitleText": "服务记录",
"navigationBarTitleText": "网页",
"navigationStyle": "custom"
}
}
@ -67,6 +60,13 @@
"navigationBarTitleText": "地图",
"navigationStyle": "custom"
}
},
{
"path": "richTextDetail",
"style": {
"navigationBarTitleText": "详情",
"navigationStyle": "custom"
}
}
]
},
@ -102,6 +102,25 @@
}
}
]
},
{
"root": "pages/profileSub",
"pages": [
{
"path": "realNameAuth",
"style": {
"navigationBarTitleText": "实名认证",
"navigationStyle": "custom"
}
},
{
"path": "serviceRecords",
"style": {
"navigationBarTitleText": "服务记录",
"navigationStyle": "custom"
}
}
]
}
],
"globalStyle": {

View File

@ -107,7 +107,7 @@ export default {
const res = await getHomeData({
page: this.page,
pageSize: this.pageSize,
noticeType: 2, //
noticeType: 21, //
});
if (res) {
@ -159,10 +159,10 @@ export default {
//
handleActivityClick(item) {
//
// uni.navigateTo({
// url: `/pages/activities/detail?id=${item.id}`
// })
//
uni.navigateTo({
url: `/pages/detail/activitiesDetail?id=${item.id}`
});
},
//

View File

@ -10,9 +10,21 @@
<text class="loading-text">加载中...</text>
</view>
<!-- 富文本内容 -->
<view class="content-wrapper" v-else-if="activityDetail && activityDetail.content">
<rich-text :nodes="activityDetail.content"></rich-text>
<!-- 富文本内容 - 混合渲染 -->
<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>
<!-- 空数据提示 -->
@ -40,6 +52,7 @@ export default {
return {
activityId: null,
activityDetail: null,
parsedContent: [], //
loading: false,
};
},
@ -69,6 +82,10 @@ export default {
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);
@ -80,6 +97,64 @@ export default {
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>
@ -106,60 +181,40 @@ export default {
border-radius: 20rpx;
min-height: 200rpx;
box-sizing: border-box;
overflow: hidden;
overflow-x: hidden;
overflow-y: visible;
word-wrap: break-word;
word-break: break-all;
//
:deep(rich-text) {
//
.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;
display: block;
width: 100%;
box-sizing: border-box;
}
//
:deep(img) {
max-width: 100% !important;
width: auto !important;
height: auto !important;
display: block;
margin: 20rpx auto;
box-sizing: border-box;
}
:deep(p) {
margin: 20rpx 0;
line-height: 1.8;
}
:deep(h1),
:deep(h2),
:deep(h3),
:deep(h4),
:deep(h5),
:deep(h6) {
margin: 30rpx 0 20rpx 0;
font-weight: bold;
}
:deep(ul),
:deep(ol) {
margin: 20rpx 0;
padding-left: 40rpx;
}
:deep(li) {
margin: 10rpx 0;
}
:deep(a) {
color: #004294;
text-decoration: underline;
}
}
/* 加载状态 */

View File

@ -0,0 +1,239 @@
<template>
<view class="rich-text-detail-page">
<!-- 顶部导航栏 -->
<NavHeader :title="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 NavHeader from "@/components/NavHeader/NavHeader.vue";
export default {
components: {
NavHeader
},
data() {
return {
title: '',
content: '',
parsedContent: [], //
loading: false,
};
},
onLoad(options) {
//
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);
}
},
methods: {
// 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>
.rich-text-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

@ -65,10 +65,10 @@
<view class="menu-price-row">
<view class="current-price">
<view class="price-label">现价¥</view>
<view class="price-text">{{ item.salePrice || 0 }}</view>
<view class="price-text">{{ formatPrice(item.salePrice) }}</view>
</view>
<text class="original-price" v-if="item.originalPrice"
>原价¥{{ item.originalPrice }}</text
>原价¥{{ formatPrice(item.originalPrice) }}</text
>
<view class="quantity-control">
<view
@ -139,7 +139,8 @@ export default {
totalAmount() {
return this.menuList.reduce((total, item) => {
if (item.selected && item.quantity > 0) {
const price = item.salePrice || item.currentPrice || item.price || 0;
//
const price = (item.salePrice || item.currentPrice || item.price || 0) / 100;
return total + price * item.quantity;
}
return total;
@ -253,6 +254,17 @@ export default {
}
},
// 100
formatPrice(price) {
if (!price && price !== 0) {
return '0.00';
}
//
const yuan = price / 100;
//
return yuan.toFixed(2);
},
//
handleCheckout() {
if (this.totalAmount <= 0) {
@ -272,14 +284,9 @@ export default {
return;
}
// TODO:
console.log("结算商品:", selectedItems);
console.log("总金额:", this.totalAmount);
uni.showToast({
title: `结算金额:¥${this.totalAmount.toFixed(2)}`,
title: "该功能正在开发中",
icon: "none",
duration: 2000,
});
},
},
@ -487,7 +494,7 @@ export default {
.price-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 50rpx;
font-size: 48rpx;
color: #d51c3c;
}
}

View File

@ -157,12 +157,55 @@ export default {
this.getSystemInfo();
this.loadHomeData();
this.loadActivities();
//
// #ifdef MP-WEIXIN
uni.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
});
// #endif
},
onShow() {
//
this.loadHomeData();
this.loadActivities();
},
onPullDownRefresh() {
this.loadHomeData().finally(() => {
uni.stopPullDownRefresh();
});
},
//
onShareAppMessage() {
// 使banner使logo
const shareImage = this.bannerList && this.bannerList.length > 0
? this.bannerList[0].coverUrl
: '/static/home/logo.png';
return {
title: '广厦千万间司机公会 - 加入工会,权益保障,福利升级',
path: '/pages/index/index',
imageUrl: shareImage
};
},
//
// #ifdef MP-WEIXIN
onShareTimeline() {
// 使banner使logo
const shareImage = this.bannerList && this.bannerList.length > 0
? this.bannerList[0].coverUrl
: '/static/home/logo.png';
return {
title: '广厦千万间司机公会 - 加入工会,权益保障,福利升级',
query: '',
imageUrl: shareImage
};
},
// #endif
methods: {
//
getSystemInfo() {
@ -179,7 +222,7 @@ export default {
async loadHomeData() {
try {
// banner
const res = await getHomeData({ noticeType: 1 });
const res = await getHomeData({ noticeType: 22 });
if (res) {
this.bannerList = res.list || [];
}
@ -209,7 +252,7 @@ export default {
async loadActivities() {
try {
//
const res = await getHomeData({ page: 1, pageSize: 3, noticeType: 2 });
const res = await getHomeData({ page: 1, pageSize: 3, noticeType: 21 });
if (res) {
this.activitiesList = res.list || [];
}
@ -221,24 +264,10 @@ export default {
//
formatTime,
//
handleMenuClick() {
uni.showToast({
title: "菜单功能",
icon: "none",
});
},
// /
handleFocusClick() {
uni.showToast({
title: "关注功能",
icon: "none",
});
},
// banner
handleBannerClick(item) {
console.log(item, 222);
if (item.jumpUrl) {
//
if (
@ -265,31 +294,21 @@ export default {
url: item.jumpUrl,
});
}
} else if (item.content) {
// jumpUrl content
const title = item.title || '详情';
uni.navigateTo({
url: `/pages/detail/richTextDetail?title=${encodeURIComponent(title)}&content=${encodeURIComponent(item.content)}`
});
}
},
//
async handleJoinGuild() {
try {
//
// const res = await joinGuild()
// if (res.statusCode === 200) {
// uni.showToast({
// title: '',
// icon: 'success'
// })
// }
uni.showToast({
title: "加入工会",
title: "该功能正在开发中",
icon: "none",
});
} catch (error) {
console.error("加入工会失败:", error);
uni.showToast({
title: "操作失败,请重试",
icon: "none",
});
}
},
//
@ -340,10 +359,10 @@ export default {
//
handleActivityClick(item) {
//
// uni.navigateTo({
// url: `/pages/activities/detail?id=${item.id}`
// })
//
uni.navigateTo({
url: `/pages/detail/activitiesDetail?id=${item.id}`
});
},
//

View File

@ -1,27 +1,20 @@
<template>
<view class="login-page">
<!-- 状态栏占位 -->
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<!-- <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view> -->
<!-- 头部区域 -->
<view class="header">
<view class="back-btn" @click="handleBack" v-if="showBack">
<text class="back-icon"></text>
</view>
<view class="header-title">登录</view>
</view>
<NavHeader title="登录" />
<!-- 登录内容区 -->
<view class="login-content">
<!-- Logo区域 -->
<!-- <view class="logo-section">
<image class="logo" src="/static/logo.png" mode="aspectFit"></image>
</view> -->
<view class="logo-section">
<image class="logo" src="/static/home/logo.png" mode="aspectFit"></image>
</view>
<!-- 登录表单 -->
<view class="login-form">
<!-- 手机号输入 -->
<view class="form-item">
<!-- <view class="form-item">
<view class="input-wrapper">
<text class="input-label">手机号</text>
<input
@ -32,10 +25,9 @@
maxlength="11"
/>
</view>
</view>
</view> -->
<!-- 密码输入 -->
<view class="form-item">
<!-- <view class="form-item">
<view class="input-wrapper">
<text class="input-label">密码</text>
<input
@ -48,24 +40,24 @@
<text class="toggle-icon">{{ showPassword ? '👁️' : '👁️‍🗨️' }}</text>
</view>
</view>
</view>
</view> -->
<!-- 登录按钮 -->
<button
<!-- <button
class="login-btn"
:class="{ disabled: !canLogin }"
:disabled="!canLogin || loading"
@click="handleLogin"
>
{{ loading ? '登录中...' : '登录' }}
</button>
</button> -->
<!-- 分割线 -->
<view class="divider">
<!-- <view class="divider">
<view class="divider-line"></view>
<text class="divider-text"></text>
<view class="divider-line"></view>
</view>
</view> -->
<!-- 小程序一键授权登录 -->
<!-- #ifdef MP-WEIXIN -->
@ -103,7 +95,10 @@ export default {
formData: {
mobile: '',
password: ''
}
},
//
state: '', // state
inviteCode: '' //
}
},
computed: {
@ -131,6 +126,14 @@ export default {
return
}
// state inviteCode
if (options.state) {
this.state = options.state
}
if (options.inviteCode) {
this.inviteCode = options.inviteCode
}
//
const pages = getCurrentPages()
if (pages.length > 1) {
@ -261,7 +264,7 @@ export default {
if (e.detail.errMsg === 'getPhoneNumber:ok') {
this.loading = true
try {
// code
// 1. code
const loginRes = await new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
@ -270,25 +273,51 @@ export default {
})
})
//
//
// codeencryptedDataiv
const res = await loginByPhone({
code: loginRes.code,
encryptedData: e.detail.encryptedData,
iv: e.detail.iv
if (loginRes.errMsg !== 'login:ok') {
uni.showToast({
title: '获取登录凭证失败',
icon: 'none'
})
this.loading = false
return
}
// token
const token = res?.token || res?.data?.token || res?.accessToken || res?.access_token
// 2.
// /app-api/member/auth/weixin-mini-app-login
// phoneCodecodeloginCodecodestateinviteCode
// state
const state = this.generateState()
const loginParams = {
phoneCode: e.detail.code, // code, wx.getPhoneNumber
loginCode: loginRes.code, // code, wx.login
state: state, // state
}
//
const inviteCode = this.getInviteCode()
if (inviteCode) {
loginParams.inviteCode = inviteCode
}
const res = await loginByPhone(loginParams)
// token
// { code: 0, data: { accessToken, refreshToken, expiresTime, userId, ... } }
// request code === 0 res.data.data || res.data res data
const token = res?.accessToken || res?.data?.accessToken || res?.token || res?.data?.token
if (token) {
uni.setStorageSync('token', token)
console.log('Token 已保存:', token)
} else {
console.error('未找到 token返回数据:', res)
}
// refreshTokenaccessToken
const refreshToken = res?.refreshToken || res?.data?.refreshToken
if (refreshToken) {
uni.setStorageSync('refreshToken', refreshToken)
console.log('RefreshToken 已保存:', refreshToken)
}
// token
@ -303,18 +332,25 @@ export default {
uni.setStorageSync('userId', userId)
}
//
const userInfo = res?.userInfo || res?.data?.userInfo || res?.user || res?.data?.user
if (userInfo) {
uni.setStorageSync('userInfo', userInfo)
// openid
const openid = res?.openid || res?.data?.openid
if (openid) {
uni.setStorageSync('openid', openid)
}
//
try {
const userInfoRes = await getUserInfo()
if (userInfoRes && userInfoRes.data) {
// res.data
uni.setStorageSync('userInfo', userInfoRes.data)
} else if (userInfoRes) {
//
uni.setStorageSync('userInfo', userInfoRes)
}
} catch (error) {
console.error('获取用户信息失败:', error)
//
}
uni.showToast({
@ -345,6 +381,45 @@ export default {
icon: 'none'
})
}
},
// state
generateState() {
// UUID 9b2ffbc1-7425-4155-9894-9d5c08541d62
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
const generateUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
const state = generateUUID()
// state 便使
uni.setStorageSync('loginState', state)
return state
},
//
getInviteCode() {
//
if (this.inviteCode) {
return this.inviteCode
}
//
const storedInviteCode = uni.getStorageSync('inviteCode')
if (storedInviteCode) {
return storedInviteCode
}
//
const app = getApp()
if (app && app.globalData && app.globalData.inviteCode) {
return app.globalData.inviteCode
}
return ''
}
}
}
@ -399,11 +474,13 @@ export default {
.logo-section {
display: flex;
justify-content: center;
margin-bottom: 80rpx;
align-items: center;
margin-bottom: 120rpx;
margin-top: 80rpx;
.logo {
width: 200rpx;
height: 200rpx;
width: 306rpx;
height: 72rpx;
}
}

View File

@ -14,11 +14,24 @@
}"
>
<view class="user-info">
<!-- #ifdef MP-WEIXIN -->
<button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="handleChooseAvatar">
<image
class="avatar"
:src="userInfo.avatar || '/static/logo.png'"
mode="aspectFill"
></image>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="avatar-wrapper" @click="handleChooseAvatar">
<image
class="avatar"
:src="userInfo.avatar || '/static/logo.png'"
mode="aspectFill"
></image>
</view>
<!-- #endif -->
<image
src="/static/profile/avatar-hg.png"
mode="aspectFill"
@ -42,13 +55,13 @@
<!-- 功能卡片 -->
<view class="action-cards">
<view class="action-card real-name-auth" @click="goToRealNameAuth">
<view class="action-card real-name-auth" @tap="goToRealNameAuth">
<view class="card-content">
<text class="card-title">实名认证</text>
<text class="card-desc">请完善身份信息</text>
</view>
</view>
<view class="action-card service-records" @click="goToServiceRecords">
<view class="action-card service-records" @tap="goToServiceRecords">
<view class="card-content">
<text class="card-title">服务记录</text>
<text class="card-desc">查看服务记录</text>
@ -58,7 +71,7 @@
<!-- 菜单列表 -->
<view class="menu-list">
<view class="menu-item" @click="goToFavorites">
<view class="menu-item" @tap="goToFavorites">
<view class="menu-icon"
><image
src="/static/profile/my-favorite.png"
@ -68,14 +81,14 @@
<text class="menu-text">我的收藏</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goToComplaints">
<view class="menu-item" @tap="goToComplaints">
<view class="menu-icon"
><image src="/static/profile/complaints.png" mode="aspectFill"></image
></view>
<text class="menu-text">投诉建议</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goToPostMessage">
<view class="menu-item" @tap="goToPostMessage">
<view class="menu-icon"
><image
src="/static/profile/publish-message.png"
@ -103,19 +116,22 @@ export default {
statusBarHeight: 0,
navBarHeight: 0,
currentTime: "10:55",
userInfo: {},
userInfo: {
level: {} // level
},
noticeNum: 3,
uploading: false, //
};
},
onLoad() {
this.getSystemInfo();
this.updateTime();
//
this.loadUserInfo();
// this.loadUserInfo();
},
onShow() {
//
// this.loadUserInfo();
//
this.loadUserInfo();
},
methods: {
//
@ -191,18 +207,18 @@ export default {
},
goToMemberBenefits() {
uni.showToast({
title: "会员权益",
title: "该功能正在开发中",
icon: "none",
});
},
goToRealNameAuth() {
uni.navigateTo({
url: "/pages/profile/realNameAuth",
url: "/pages/profileSub/realNameAuth",
});
},
goToServiceRecords() {
uni.navigateTo({
url: "/pages/profile/serviceRecords",
url: "/pages/profileSub/serviceRecords",
});
},
goToFavorites() {
@ -239,6 +255,136 @@ export default {
},
});
},
// 使 chooseAvatar使 chooseImage
handleChooseAvatar(e) {
// #ifdef MP-WEIXIN
// 使 chooseAvatar
if (e.detail && e.detail.avatarUrl) {
const avatarUrl = e.detail.avatarUrl;
//
this.userInfo.avatar = avatarUrl;
//
this.uploadAvatar(avatarUrl);
}
// #endif
// #ifndef MP-WEIXIN
// 使 chooseImage
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePath = res.tempFilePaths[0];
//
this.userInfo.avatar = tempFilePath;
//
this.uploadAvatar(tempFilePath);
},
fail: (err) => {
console.error('选择图片失败:', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
// #endif
},
//
uploadAvatar(filePath) {
if (this.uploading) {
return;
}
this.uploading = true;
uni.showLoading({
title: '上传中...',
mask: true
});
// token
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',
formData: {
directory: 'avatar' //
},
header: {
'Authorization': `Bearer ${token}`,
'tenant-id': '1'
},
success: (res) => {
uni.hideLoading();
try {
const data = JSON.parse(res.data);
if (data.code === 200 || data.code === 0) {
// URL
const imageUrl = data.data?.url || data.data || data.url;
if (imageUrl) {
//
this.userInfo.avatar = imageUrl;
//
uni.setStorageSync('userInfo', this.userInfo);
//
this.updateUserAvatar(imageUrl);
uni.showToast({
title: '头像上传成功',
icon: 'success',
duration: 1500
});
} else {
throw new Error('上传成功但未返回图片地址');
}
} else {
throw new Error(data.message || data.msg || '上传失败');
}
} catch (error) {
console.error('解析上传结果失败:', error);
uni.showToast({
title: error.message || '上传失败,请重试',
icon: 'none'
});
//
this.loadUserInfo();
}
},
fail: (err) => {
uni.hideLoading();
console.error('上传头像失败:', err);
uni.showToast({
title: '上传失败,请检查网络',
icon: 'none'
});
//
this.loadUserInfo();
},
complete: () => {
this.uploading = false;
}
});
},
//
async updateUserAvatar(avatarUrl) {
try {
//
// await updateUserInfo({ avatar: avatarUrl });
//
console.log('头像已更新:', avatarUrl);
} catch (error) {
console.error('更新用户头像失败:', error);
// 使使
}
},
},
};
</script>
@ -280,10 +426,44 @@ export default {
align-items: center;
padding-top: 89rpx;
padding-left: 23rpx;
.avatar-btn {
width: 109rpx;
height: 108rpx;
margin-right: 24rpx;
padding: 0;
background: transparent;
border: none;
line-height: 1;
&::after {
border: none;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.avatar-wrapper {
width: 109rpx;
height: 108rpx;
margin-right: 24rpx;
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.avatar {
width: 109rpx;
height: 108rpx;
margin-right: 24rpx;
border-radius: 50%;
}
.avatar-hg {
width: 51rpx;

View File

@ -166,114 +166,114 @@ export default {
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",
},
],
// 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",
// },
// ],
},
};
},
@ -391,35 +391,23 @@ export default {
//
handlePay(item) {
uni.showToast({
title: "跳转支付页面",
title: "该功能正在开发中",
icon: "none",
});
//
// uni.navigateTo({
// url: `/pages/payment/payment?orderNo=${item.orderNo}&amount=${item.amount}`
// });
},
//
handleViewDetail(item) {
uni.showToast({
title: "查看详情",
title: "该功能正在开发中",
icon: "none",
});
//
// uni.navigateTo({
// url: `/pages/order/detail?orderNo=${item.orderNo}`
// });
},
//
handleReview(item) {
uni.showToast({
title: "跳转评价页面",
title: "该功能正在开发中",
icon: "none",
});
//
// uni.navigateTo({
// url: `/pages/order/review?orderNo=${item.orderNo}`
// });
},
},
};

View File

@ -25,7 +25,8 @@
<!-- 分类标签栏 -->
<view class="category-bar">
<view class="location-selector" @click="handleLocationSelect">
<!-- 只有店铺类型美食维修才显示距离选择器 -->
<view class="location-selector" v-if="isStoreCategory()" @click="handleLocationSelect">
<text class="location-text">{{ selectedDistance ? `附近${selectedDistance}km` : '附近' }}</text>
<image
class="location-arrow"
@ -95,8 +96,9 @@
<!-- 右侧信息 -->
<view class="service-info">
<view class="service-title-row">
<text class="service-title">{{ item.name }}</text>
<view class="distance-info">
<text class="service-title">{{ item.name || item.title }}</text>
<!-- 只有店铺类型才显示距离 -->
<view class="distance-info" v-if="isStoreCategory()">
<image
class="location-icon"
src="/static/service/location-icon.png"
@ -106,6 +108,8 @@
</view>
</view>
<!-- 店铺类型显示门店信息 -->
<template v-if="isStoreCategory()">
<view class="service-detail">
<text class="detail-label">门店地址:</text>
<text class="detail-value">{{ item.address }}</text>
@ -115,10 +119,18 @@
<text class="detail-label">联系电话:</text>
<text class="detail-value">{{ item.phone }}</text>
</view>
</template>
<!-- 消息类型显示摘要 -->
<template v-else>
<view class="service-detail" v-if="item.summary">
<text class="detail-value">{{ item.summary }}</text>
</view>
</template>
<view class="service-tags-row">
<view class="service-tags">
<view class="tag-item tag-pink"> 会员特惠 </view>
<view class="tag-item tag-pink"> {{ item.publishUserName || '会员特惠' }} </view>
<view class="tag-item tag-orange">{{
currentCategoryLabel
}}</view>
@ -126,7 +138,8 @@
</view>
</view>
<view class="enter-btn" @click.stop="handleEnterStore(item)">
<!-- 只有店铺类型才显示"进店"按钮 -->
<view class="enter-btn" v-if="isStoreCategory()" @click.stop="handleEnterStore(item)">
进店
</view>
</view>
@ -182,7 +195,7 @@
</template>
<script>
import { getDictDataByType, getGuildStorePage } from "@/api/service";
import { getDictDataByType, getGuildStorePage,getMessagePage } from "@/api/service";
export default {
data() {
return {
@ -209,12 +222,23 @@ export default {
onLoad() {
this.getSystemInfo();
this.getServiceCategoryFun(); //
this.checkAndRequestLocation(); //
// this.checkAndRequestLocation(); //
},
onShow() {
//
//
if (this.categoryList && this.categoryList.length > 0) {
this.checkAndSetCategory();
//
if (this.currentCategory !== null) {
this.pageNo = 1;
this.serviceList = [];
this.hasMore = true;
this.loadServiceList();
}
} else {
// checkAndSetCategory
this.getServiceCategoryFun();
}
},
methods: {
@ -227,14 +251,28 @@ export default {
getServiceCategoryFun() {
getDictDataByType({ type: "member_labor_service_type" }).then((res) => {
if (res) {
this.categoryList = res || [];
const dictList = res || [];
// """"
const fixedCategories = [
{ id: 'food', label: '美食', value: 'food', isStore: true }, // isStore: true 使
{ id: 'repair', label: '维修', value: 'repair', isStore: true }
];
//
this.categoryList = [...fixedCategories, ...dictList];
//
this.checkAndSetCategory();
//
if (this.currentCategory !== null) {
this.pageNo = 1;
this.serviceList = [];
this.hasMore = true;
this.loadServiceList();
}
}
});
},
//
// onShow
checkAndSetCategory() {
const app = getApp();
if (app && app.globalData && app.globalData.serviceCategory) {
@ -242,20 +280,23 @@ export default {
// globalData
app.globalData.serviceCategory = null;
// '0' -> 'food', '1' -> 'repair'
let mappedValue = categoryValue;
if (categoryValue === '0') {
mappedValue = 'food';
} else if (categoryValue === '1') {
mappedValue = 'repair';
}
// value
const matchedCategory = this.categoryList.find(
item => item.value === categoryValue
item => item.value === mappedValue || item.value === categoryValue
);
if (matchedCategory) {
//
this.currentCategory = matchedCategory.value;
this.currentCategoryLabel = matchedCategory.label;
//
this.pageNo = 1;
this.serviceList = [];
this.hasMore = true;
this.loadServiceList();
return;
}
}
@ -265,11 +306,6 @@ export default {
const firstCategory = this.categoryList[0];
this.currentCategory = firstCategory.value;
this.currentCategoryLabel = firstCategory.label;
//
this.pageNo = 1;
this.serviceList = [];
this.hasMore = true;
this.loadServiceList();
}
},
@ -371,6 +407,10 @@ export default {
handleCategoryClick(item) {
this.currentCategory = item.value;
this.currentCategoryLabel = item.label;
//
if (!this.isStoreCategory()) {
this.selectedDistance = null;
}
//
this.pageNo = 1;
this.serviceList = [];
@ -378,12 +418,34 @@ export default {
this.loadServiceList();
},
//
isStoreCategory() {
return this.currentCategory === 'food' || this.currentCategory === 'repair';
},
//
handleServiceItemClick(item) {
//
//
if (this.isStoreCategory()) {
//
uni.navigateTo({
url: `/pages/detail/serviceDetail?id=${item.id}&categoryLabel=${this.currentCategoryLabel}`,
});
} else {
//
const title = '详情';
const content = item.content || '';
if (content) {
uni.navigateTo({
url: `/pages/detail/richTextDetail?title=${encodeURIComponent(title)}&content=${encodeURIComponent(content)}`
});
} else {
uni.showToast({
title: '暂无内容',
icon: 'none'
});
}
}
},
// -
@ -407,11 +469,15 @@ export default {
this.loading = true;
}
//
let res;
//
if (this.isStoreCategory()) {
// 使
const params = {
pageNo: this.pageNo,
pageSize: this.pageSize,
type: this.currentCategory,
type: this.currentCategory === 'food' ? '0' : '1', // : '0', : '1'
name: this.searchKeyword,
};
@ -420,7 +486,18 @@ export default {
params.distance = this.selectedDistance;
}
const res = await getGuildStorePage(params);
res = await getGuildStorePage(params);
} else {
// 使
const params = {
pageNo: this.pageNo,
pageSize: this.pageSize,
messageType: this.currentCategory, // 使 value messageType
title: this.searchKeyword, // 使 title
};
res = await getMessagePage(params);
}
if (res) {
const newList = res.list || [];

View File

@ -0,0 +1,80 @@
<template>
<view class="webview-page">
<!-- 头部导航栏 -->
<NavHeader title="网页" />
<!-- WebView 容器 -->
<view class="webview-container">
<!-- #ifdef MP-WEIXIN -->
<web-view :src="webviewUrl"></web-view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="unsupported-tip">
<text>当前平台不支持 WebView</text>
</view>
<!-- #endif -->
</view>
</view>
</template>
<script>
import NavHeader from "@/components/NavHeader/NavHeader.vue";
export default {
components: {
NavHeader
},
data() {
return {
webviewUrl: ''
}
},
onLoad(options) {
// URL
if (options.url) {
this.webviewUrl = decodeURIComponent(options.url);
} else {
uni.showToast({
title: '缺少网页地址',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}
}
</script>
<style lang="scss" scoped>
.webview-page {
height: 100vh;
background-color: #ffffff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.webview-container {
flex: 1;
height: 0; // flex: 1 使
.unsupported-tip {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 28rpx;
color: #999999;
}
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB