hookehuyr

feat(权限): 实现权限管理系统并集成到各页面

添加权限管理系统,包括权限检查工具函数和用户状态管理
- 创建权限检查工具函数 checkPermission 和权限类型枚举
- 实现用户状态管理 store 用于集中管理用户信息和权限状态
- 在各页面集成权限检查,包括个人中心、卖车、购买等功能
- 添加权限使用示例文档
- 重构导航逻辑使用权限检查替代原有验证方式
...@@ -56,10 +56,13 @@ ...@@ -56,10 +56,13 @@
56 import { ref, onMounted } from 'vue' 56 import { ref, onMounted } from 'vue'
57 import Taro from '@tarojs/taro' 57 import Taro from '@tarojs/taro'
58 import { Home, Category, Comment, My } from '@nutui/icons-vue-taro' 58 import { Home, Category, Comment, My } from '@nutui/icons-vue-taro'
59 -import { getProfileAPI } from '@/api/index'; 59 +import { useUserStore } from '@/stores/user'
60 +import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
60 61
61 // 当前激活的tab 62 // 当前激活的tab
62 const activeTab = ref('') 63 const activeTab = ref('')
64 +// 用户状态管理
65 +const userStore = useUserStore()
63 66
64 /** 67 /**
65 * 获取当前页面路径并设置激活状态 68 * 获取当前页面路径并设置激活状态
...@@ -88,22 +91,23 @@ const getCurrentPage = () => { ...@@ -88,22 +91,23 @@ const getCurrentPage = () => {
88 * 导航到指定页面 91 * 导航到指定页面
89 * @param {string} url - 页面路径 92 * @param {string} url - 页面路径
90 */ 93 */
91 -const navigateTo = (url) => { 94 +const navigateTo = async (url) => {
92 - if ((url === '/pages/profile/index' || url === '/pages/sell/index') && !is_auth.value) { 95 + // 定义需要权限验证的页面
93 - Taro.showToast({ 96 + const permissionPages = {
94 - title: '请先完善个人信息', 97 + '/pages/profile/index': PERMISSION_TYPES.PROFILE,
95 - icon: 'none', 98 + '/pages/sell/index': PERMISSION_TYPES.SELL_CAR,
96 - duration: 2000, 99 + '/pages/messages/index': PERMISSION_TYPES.MESSAGE
97 - success: () => {
98 - setTimeout(() => {
99 - Taro.navigateTo({
100 - url: '/pages/register/index'
101 - })
102 - }, 2000);
103 - }
104 - })
105 - return
106 } 100 }
101 +
102 + // 检查是否需要权限验证
103 + const permissionType = permissionPages[url]
104 + if (permissionType) {
105 + const hasPermission = await checkPermission(permissionType)
106 + if (!hasPermission) {
107 + return
108 + }
109 + }
110 +
107 Taro.reLaunch({ 111 Taro.reLaunch({
108 url 112 url
109 }) 113 })
...@@ -129,27 +133,10 @@ const getIconColor = (tab) => { ...@@ -129,27 +133,10 @@ const getIconColor = (tab) => {
129 return activeTab.value === tab ? '#f97316' : '#6b7280' 133 return activeTab.value === tab ? '#f97316' : '#6b7280'
130 } 134 }
131 135
132 -const userInfo = ref({
133 - avatar: '',
134 - nickname: '',
135 - phone: '',
136 - gender: '',
137 - school: '',
138 - birthday: '',
139 -});
140 -
141 -const is_auth = ref(false);
142 -
143 onMounted(async () => { 136 onMounted(async () => {
144 getCurrentPage() 137 getCurrentPage()
145 - // 获取当前用户的信息 138 + // 初始化用户信息
146 - const user = await getProfileAPI(); 139 + await userStore.fetchUserInfo()
147 - if (user.code) {
148 - userInfo.value = user.data
149 - if (userInfo.value && userInfo.value.phone) {
150 - is_auth.value = true
151 - }
152 - }
153 }) 140 })
154 </script> 141 </script>
155 142
......
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-08 17:46:08 4 + * @LastEditTime: 2025-07-09 10:44:50
5 * @FilePath: /jgdl/src/pages/auth/index.vue 5 * @FilePath: /jgdl/src/pages/auth/index.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -36,7 +36,7 @@ export default { ...@@ -36,7 +36,7 @@ export default {
36 }) 36 })
37 request.post('/srv/?a=openid', { 37 request.post('/srv/?a=openid', {
38 code: res.code, 38 code: res.code,
39 - openid: 'h-001' 39 + openid: 'h-002'
40 // openid: 'o5NFZ5cFQtLRy3aVHaZMLkjHFusI' 40 // openid: 'o5NFZ5cFQtLRy3aVHaZMLkjHFusI'
41 // openid: 'o5NFZ5TpgG4FwYursGCLjcUJH2ak' 41 // openid: 'o5NFZ5TpgG4FwYursGCLjcUJH2ak'
42 // openid: 'o5NFZ5cqroPYwawCp8FEOxewtgnw' 42 // openid: 'o5NFZ5cqroPYwawCp8FEOxewtgnw'
......
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-08 15:51:46 4 + * @LastEditTime: 2025-07-09 10:42:01
5 * @FilePath: /jgdl/src/pages/productDetail/index.vue 5 * @FilePath: /jgdl/src/pages/productDetail/index.vue
6 * @Description: 商品详情页 6 * @Description: 商品详情页
7 --> 7 -->
...@@ -266,6 +266,7 @@ import { Share, Heart1, HeartFill, Message } from '@nutui/icons-vue-taro' ...@@ -266,6 +266,7 @@ import { Share, Heart1, HeartFill, Message } from '@nutui/icons-vue-taro'
266 import payCard from '@/components/payCard.vue' 266 import payCard from '@/components/payCard.vue'
267 import avatarImg from '@/assets/images/avatar.png' 267 import avatarImg from '@/assets/images/avatar.png'
268 import { getCurrentPageParam } from "@/utils/weapp" 268 import { getCurrentPageParam } from "@/utils/weapp"
269 +import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
269 270
270 // 默认头像 271 // 默认头像
271 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg' 272 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
...@@ -400,8 +401,11 @@ const copyWechat = () => { ...@@ -400,8 +401,11 @@ const copyWechat = () => {
400 /** 401 /**
401 * 联系卖家 402 * 联系卖家
402 */ 403 */
403 -const handleContactSeller = () => { 404 +const handleContactSeller = async () => {
404 - showContactModal.value = true 405 + const hasPermission = await checkPermission(PERMISSION_TYPES.CONTACT_SELLER)
406 + if (hasPermission) {
407 + showContactModal.value = true
408 + }
405 } 409 }
406 410
407 /** 411 /**
...@@ -448,12 +452,15 @@ const sendMessageToSeller = () => { ...@@ -448,12 +452,15 @@ const sendMessageToSeller = () => {
448 /** 452 /**
449 * 购买商品 453 * 购买商品
450 */ 454 */
451 -const handlePurchase = () => { 455 +const handlePurchase = async () => {
452 - onPay({ 456 + const hasPermission = await checkPermission(PERMISSION_TYPES.BUY_CAR)
453 - id: product.value.id, 457 + if (hasPermission) {
454 - remain_time: 1800, // 30分钟 458 + onPay({
455 - price: product.value.price 459 + id: product.value.id,
456 - }) 460 + remain_time: 1800, // 30分钟
461 + price: product.value.price
462 + })
463 + }
457 } 464 }
458 465
459 /** 466 /**
......
...@@ -78,30 +78,24 @@ ...@@ -78,30 +78,24 @@
78 </template> 78 </template>
79 79
80 <script setup> 80 <script setup>
81 -import { ref, onMounted } from 'vue' 81 +import { computed } from 'vue'
82 -import { getProfileAPI } from '@/api/index'
83 import { Heart, Clock, Notice, Cart, Message, Tips, Right, StarN } from '@nutui/icons-vue-taro' 82 import { Heart, Clock, Notice, Cart, Message, Tips, Right, StarN } from '@nutui/icons-vue-taro'
84 import Taro, { useDidShow } from '@tarojs/taro' 83 import Taro, { useDidShow } from '@tarojs/taro'
85 import TabBar from '@/components/TabBar.vue' 84 import TabBar from '@/components/TabBar.vue'
85 +import { useUserStore } from '@/stores/user'
86 +import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
86 87
87 // 默认头像 88 // 默认头像
88 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg' 89 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
89 90
90 -// 用户信息 91 +// 用户状态管理
91 -const userInfo = ref({ 92 +const userStore = useUserStore()
92 - nickname: '张同学', 93 +
93 - phone: '138****8888', 94 +// 用户信息计算属性
94 - avatar_url: '', 95 +const userInfo = computed(() => userStore.userInfo)
95 - follower_count: 0,
96 - order_count: 0,
97 - favorite_count: 0
98 -})
99 96
100 useDidShow(async () => { 97 useDidShow(async () => {
101 - const user = await getProfileAPI() 98 + await userStore.fetchUserInfo()
102 - if (user.code) {
103 - userInfo.value = user.data
104 - }
105 }) 99 })
106 100
107 /** 101 /**
...@@ -116,37 +110,49 @@ const onEditProfile = () => { ...@@ -116,37 +110,49 @@ const onEditProfile = () => {
116 /** 110 /**
117 * 我的关注 111 * 我的关注
118 */ 112 */
119 -const onMyFavorites = () => { 113 +const onMyFavorites = async () => {
120 - Taro.navigateTo({ 114 + const hasPermission = await checkPermission(PERMISSION_TYPES.BASIC_INFO)
121 - url: '/pages/myFavorites/index' 115 + if (hasPermission) {
122 - }) 116 + Taro.navigateTo({
117 + url: '/pages/myFavorites/index'
118 + })
119 + }
123 } 120 }
124 121
125 /** 122 /**
126 * 订单管理 123 * 订单管理
127 */ 124 */
128 -const onOrderManagement = () => { 125 +const onOrderManagement = async () => {
129 - Taro.navigateTo({ 126 + const hasPermission = await checkPermission(PERMISSION_TYPES.ORDER_MANAGEMENT)
130 - url: '/pages/myOrders/index' 127 + if (hasPermission) {
131 - }) 128 + Taro.navigateTo({
129 + url: '/pages/myOrders/index'
130 + })
131 + }
132 } 132 }
133 133
134 /** 134 /**
135 * 我的消息 135 * 我的消息
136 */ 136 */
137 -const onMessages = () => { 137 +const onMessages = async () => {
138 - Taro.navigateTo({ 138 + const hasPermission = await checkPermission(PERMISSION_TYPES.MESSAGE)
139 - url: '/pages/messages/index' 139 + if (hasPermission) {
140 - }) 140 + Taro.navigateTo({
141 + url: '/pages/messages/index'
142 + })
143 + }
141 } 144 }
142 145
143 /** 146 /**
144 * 我卖的车 147 * 我卖的车
145 */ 148 */
146 -const onMyCar = () => { 149 +const onMyCar = async () => {
147 - Taro.navigateTo({ 150 + const hasPermission = await checkPermission(PERMISSION_TYPES.SELL_CAR)
148 - url: '/pages/myCar/index' 151 + if (hasPermission) {
149 - }) 152 + Taro.navigateTo({
153 + url: '/pages/myCar/index'
154 + })
155 + }
150 } 156 }
151 157
152 /** 158 /**
......
...@@ -452,11 +452,17 @@ const handleRegister = async () => { ...@@ -452,11 +452,17 @@ const handleRegister = async () => {
452 const result = await updateProfileAPI(formData) 452 const result = await updateProfileAPI(formData)
453 453
454 if (result.code !== 0) { 454 if (result.code !== 0) {
455 - showToast(result.msg || '注册失败', 'error') 455 + Taro.showToast({
456 + title: result.msg || '注册失败',
457 + icon: 'error'
458 + })
456 return 459 return
457 } 460 }
458 461
459 - showToast('注册成功', 'success') 462 + Taro.showToast({
463 + title: '注册成功',
464 + icon: 'success'
465 + })
460 466
461 setTimeout(() => { 467 setTimeout(() => {
462 // 注册成功后跳转到登录页面或首页 468 // 注册成功后跳转到登录页面或首页
...@@ -464,7 +470,10 @@ const handleRegister = async () => { ...@@ -464,7 +470,10 @@ const handleRegister = async () => {
464 }, 1500) 470 }, 1500)
465 471
466 } catch (error) { 472 } catch (error) {
467 - showToast('注册失败,请重试', 'error') 473 + Taro.showToast({
474 + title: '注册失败,请重试',
475 + icon: 'error'
476 + })
468 } finally { 477 } finally {
469 isRegistering.value = false 478 isRegistering.value = false
470 } 479 }
......
...@@ -316,6 +316,7 @@ import { Plus, Right, Location, Close, RectLeft } from '@nutui/icons-vue-taro' ...@@ -316,6 +316,7 @@ import { Plus, Right, Location, Close, RectLeft } from '@nutui/icons-vue-taro'
316 import Taro from '@tarojs/taro' 316 import Taro from '@tarojs/taro'
317 import BASE_URL from '@/utils/config'; 317 import BASE_URL from '@/utils/config';
318 import BrandModelPicker from '@/components/BrandModelPicker.vue' 318 import BrandModelPicker from '@/components/BrandModelPicker.vue'
319 +import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
319 import './index.less' 320 import './index.less'
320 321
321 const themeVars = ref({ 322 const themeVars = ref({
...@@ -588,23 +589,23 @@ const showSchoolPicker = () => { ...@@ -588,23 +589,23 @@ const showSchoolPicker = () => {
588 /** 589 /**
589 * 显示品牌选择器 590 * 显示品牌选择器
590 */ 591 */
591 -const showBrandPicker = () => { 592 +// const showBrandPicker = () => {
592 - brandPickerVisible.value = true 593 +// brandPickerVisible.value = true
593 -} 594 +// }
594 595
595 /** 596 /**
596 * 显示型号选择器 597 * 显示型号选择器
597 */ 598 */
598 -const showModelPicker = () => { 599 +// const showModelPicker = () => {
599 - if (!formData.brand) { 600 +// if (!formData.brand) {
600 - Taro.showToast({ 601 +// Taro.showToast({
601 - title: '请先选择品牌', 602 +// title: '请先选择品牌',
602 - icon: 'none' 603 +// icon: 'none'
603 - }) 604 +// })
604 - return 605 +// return
605 - } 606 +// }
606 - modelPickerVisible.value = true 607 +// modelPickerVisible.value = true
607 -} 608 +// }
608 609
609 /** 610 /**
610 * 显示年份选择器 611 * 显示年份选择器
...@@ -897,7 +898,13 @@ const loadCarData = async () => { ...@@ -897,7 +898,13 @@ const loadCarData = async () => {
897 } 898 }
898 899
899 // 页面加载时执行 900 // 页面加载时执行
900 -onMounted(() => { 901 +onMounted(async () => {
902 + // 检查卖车权限
903 + const hasPermission = await checkPermission(PERMISSION_TYPES.SELL_CAR)
904 + if (!hasPermission) {
905 + return
906 + }
907 +
901 if (isEditMode.value) { 908 if (isEditMode.value) {
902 loadCarData() 909 loadCarData()
903 } 910 }
......
1 +/*
2 + * @Date: 2025-01-08 18:00:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-09 10:34:50
5 + * @FilePath: /jgdl/src/stores/user.js
6 + * @Description: 用户状态管理
7 + */
8 +import { defineStore } from 'pinia'
9 +import { getProfileAPI } from '@/api/index'
10 +import Taro from '@tarojs/taro'
11 +
12 +export const useUserStore = defineStore('user', {
13 + state: () => {
14 + return {
15 + userInfo: {
16 + avatar: '',
17 + nickname: '',
18 + phone: '',
19 + gender: '',
20 + school: '',
21 + birthday: '',
22 + avatar_url: '',
23 + follower_count: 0,
24 + order_count: 0,
25 + favorite_count: 0
26 + },
27 + isAuthenticated: false,
28 + isLoading: false
29 + }
30 + },
31 +
32 + getters: {
33 + /**
34 + * 检查用户是否已完善基本信息(主要是手机号)
35 + */
36 + hasCompleteProfile: (state) => {
37 + return !!(state.userInfo.phone && state.userInfo.phone.trim())
38 + },
39 +
40 + /**
41 + * 检查用户是否已认证
42 + */
43 + isUserAuthenticated: (state) => {
44 + return state.isAuthenticated && state.hasCompleteProfile
45 + },
46 +
47 + /**
48 + * 获取用户显示名称
49 + */
50 + displayName: (state) => {
51 + return state.userInfo.nickname || '用户'
52 + }
53 + },
54 +
55 + actions: {
56 + /**
57 + * 获取用户信息
58 + */
59 + async fetchUserInfo() {
60 + this.isLoading = true
61 + try {
62 + const response = await getProfileAPI()
63 + if (response.code) {
64 + this.userInfo = { ...this.userInfo, ...response.data }
65 + this.isAuthenticated = true
66 + return true
67 + } else {
68 + this.isAuthenticated = false
69 + return false
70 + }
71 + } catch (error) {
72 + console.error('获取用户信息失败:', error)
73 + this.isAuthenticated = false
74 + return false
75 + } finally {
76 + this.isLoading = false
77 + }
78 + },
79 +
80 + /**
81 + * 更新用户信息
82 + * @param {Object} newUserInfo - 新的用户信息
83 + */
84 + updateUserInfo(newUserInfo) {
85 + this.userInfo = { ...this.userInfo, ...newUserInfo }
86 + },
87 +
88 + /**
89 + * 清除用户信息(登出)
90 + */
91 + clearUserInfo() {
92 + this.userInfo = {
93 + avatar: '',
94 + nickname: '',
95 + phone: '',
96 + gender: '',
97 + school: '',
98 + birthday: '',
99 + avatar_url: '',
100 + follower_count: 0,
101 + order_count: 0,
102 + favorite_count: 0
103 + }
104 + this.isAuthenticated = false
105 + },
106 +
107 + /**
108 + * 检查并处理权限验证
109 + * @param {Object} options - 配置选项
110 + * @param {string} options.redirectUrl - 权限验证失败时的跳转地址
111 + * @param {string} options.message - 提示消息
112 + * @param {Function} options.onSuccess - 验证成功的回调
113 + * @param {Function} options.onFail - 验证失败的回调
114 + * @returns {Promise<boolean>} 是否通过权限验证
115 + */
116 + async checkPermission(options = {}) {
117 + const {
118 + redirectUrl = '/pages/register/index',
119 + message = '请先完善个人信息',
120 + onSuccess,
121 + onFail
122 + } = options
123 +
124 + // 如果用户信息为空,先尝试获取
125 + if (!this.isAuthenticated) {
126 + await this.fetchUserInfo()
127 + }
128 +
129 + // 检查是否有完整的用户信息
130 + if (this.hasCompleteProfile) {
131 + onSuccess && onSuccess()
132 + return true
133 + } else {
134 + // 权限验证失败,显示提示并跳转
135 + Taro.showToast({
136 + title: message,
137 + icon: 'none',
138 + duration: 2000,
139 + success: () => {
140 + setTimeout(() => {
141 + Taro.navigateTo({
142 + url: redirectUrl
143 + })
144 + }, 2000)
145 + }
146 + })
147 + onFail && onFail()
148 + return false
149 + }
150 + }
151 + }
152 +})
1 +# 权限管理系统使用示例
2 +
3 +本文档展示了如何在项目中使用新的权限管理系统。
4 +
5 +## 基本用法
6 +
7 +### 1. 在页面中检查权限
8 +
9 +```javascript
10 +import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
11 +
12 +// 检查卖车权限
13 +const handleSellCar = async () => {
14 + const hasPermission = await checkPermission(PERMISSION_TYPES.SELL_CAR)
15 + if (hasPermission) {
16 + // 执行卖车操作
17 + Taro.navigateTo({ url: '/pages/sell/index' })
18 + }
19 +}
20 +
21 +// 检查购买权限
22 +const handleBuyCar = async () => {
23 + const hasPermission = await checkPermission(PERMISSION_TYPES.BUY_CAR)
24 + if (hasPermission) {
25 + // 执行购买操作
26 + onPay({ id: carId, price: price })
27 + }
28 +}
29 +```
30 +
31 +### 2. 使用用户状态管理
32 +
33 +```javascript
34 +import { useUserStore } from '@/stores/user'
35 +
36 +const userStore = useUserStore()
37 +
38 +// 获取用户信息
39 +await userStore.fetchUserInfo()
40 +
41 +// 检查用户是否已认证
42 +if (userStore.isAuthenticated) {
43 + // 用户已认证
44 +}
45 +
46 +// 检查用户资料是否完整
47 +if (userStore.hasCompleteProfile) {
48 + // 用户资料完整
49 +}
50 +```
51 +
52 +### 3. 批量权限检查
53 +
54 +```javascript
55 +import { checkMultiplePermissions, PERMISSION_TYPES } from '@/utils/permission'
56 +
57 +const permissions = await checkMultiplePermissions([
58 + PERMISSION_TYPES.SELL_CAR,
59 + PERMISSION_TYPES.ORDER_MANAGEMENT,
60 + PERMISSION_TYPES.MESSAGE
61 +])
62 +
63 +if (permissions.SELL_CAR) {
64 + // 有卖车权限
65 +}
66 +
67 +if (permissions.ORDER_MANAGEMENT) {
68 + // 有订单管理权限
69 +}
70 +```
71 +
72 +### 4. 自定义权限检查
73 +
74 +```javascript
75 +import { checkUserFields } from '@/utils/permission'
76 +
77 +// 检查用户是否填写了特定字段
78 +const hasRequiredFields = await checkUserFields(['phone', 'school', 'nickname'])
79 +if (hasRequiredFields) {
80 + // 用户已填写所有必需字段
81 +}
82 +```
83 +
84 +## 权限类型说明
85 +
86 +- `BASIC_INFO`: 基础信息权限(需要手机号)
87 +- `SELL_CAR`: 卖车权限(需要手机号)
88 +- `BUY_CAR`: 买车权限(需要手机号)
89 +- `CONTACT_SELLER`: 联系卖家权限(需要手机号)
90 +- `ORDER_MANAGEMENT`: 订单管理权限(需要手机号)
91 +- `MESSAGE`: 消息权限(需要手机号)
92 +- `PROFILE`: 个人资料权限(需要手机号)
93 +- `COMPLETE_PROFILE`: 完整资料权限(需要手机号、学校、昵称)
94 +
95 +## 扩展权限类型
96 +
97 +如需添加新的权限类型,请在 `/src/utils/permission.js` 中:
98 +
99 +1.`PERMISSION_TYPES` 中添加新类型
100 +2.`PERMISSION_CONFIG` 中配置权限要求
101 +3. 根据需要自定义提示信息和跳转逻辑
102 +
103 +```javascript
104 +// 添加新权限类型
105 +export const PERMISSION_TYPES = {
106 + // ... 现有类型
107 + NEW_FEATURE: 'NEW_FEATURE'
108 +}
109 +
110 +// 配置权限要求
111 +export const PERMISSION_CONFIG = {
112 + // ... 现有配置
113 + [PERMISSION_TYPES.NEW_FEATURE]: {
114 + requiredFields: ['phone', 'realName'], // 需要的字段
115 + message: '使用此功能需要完善手机号和真实姓名',
116 + redirectUrl: '/pages/register/index'
117 + }
118 +}
119 +```
120 +
121 +## 注意事项
122 +
123 +1. 权限检查是异步操作,请使用 `await``.then()`
124 +2. 权限检查失败时会自动显示提示并跳转到注册页面
125 +3. 用户状态会自动缓存,避免重复请求
126 +4. 建议在页面加载时预先获取用户信息以提升用户体验
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * @Date: 2025-01-08 18:00:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-09 10:49:50
5 + * @FilePath: /jgdl/src/utils/permission.js
6 + * @Description: 权限控制工具函数
7 + */
8 +import { useUserStore } from '@/stores/user'
9 +import Taro from '@tarojs/taro'
10 +
11 +/**
12 + * 权限类型枚举
13 + */
14 +export const PERMISSION_TYPES = {
15 + // 基础权限:需要完善手机号等基本信息
16 + BASIC_INFO: 'basic_info',
17 + // 卖车权限:需要完善基本信息
18 + SELL_CAR: 'sell_car',
19 + // 买车权限:需要完善基本信息
20 + BUY_CAR: 'buy_car',
21 + // 个人中心权限:需要完善基本信息
22 + PROFILE: 'profile',
23 + // 订单管理权限:需要完善基本信息
24 + ORDER_MANAGEMENT: 'order_management',
25 + // 消息权限:需要完善基本信息
26 + // MESSAGE: 'message'
27 +}
28 +
29 +/**
30 + * 权限配置
31 + */
32 +const PERMISSION_CONFIG = {
33 + [PERMISSION_TYPES.BASIC_INFO]: {
34 + message: '请先完善个人信息',
35 + redirectUrl: '/pages/register/index',
36 + checkFields: ['phone']
37 + },
38 + [PERMISSION_TYPES.SELL_CAR]: {
39 + message: '发布车源需要先完善个人信息',
40 + redirectUrl: '/pages/register/index',
41 + checkFields: ['phone']
42 + },
43 + [PERMISSION_TYPES.BUY_CAR]: {
44 + message: '购买车辆需要先完善个人信息',
45 + redirectUrl: '/pages/register/index',
46 + checkFields: ['phone']
47 + },
48 + [PERMISSION_TYPES.PROFILE]: {
49 + message: '访问个人中心需要先完善个人信息',
50 + redirectUrl: '/pages/register/index',
51 + checkFields: ['phone']
52 + },
53 + [PERMISSION_TYPES.ORDER_MANAGEMENT]: {
54 + message: '查看订单需要先完善个人信息',
55 + redirectUrl: '/pages/register/index',
56 + checkFields: ['phone']
57 + },
58 + // [PERMISSION_TYPES.MESSAGE]: {
59 + // message: '查看消息需要先完善个人信息',
60 + // redirectUrl: '/pages/register/index',
61 + // checkFields: ['phone']
62 + // }
63 +}
64 +
65 +/**
66 + * 检查用户是否满足指定字段的要求
67 + * @param {Object} userInfo - 用户信息
68 + * @param {Array} checkFields - 需要检查的字段
69 + * @returns {boolean} 是否满足要求
70 + */
71 +function checkUserFields(userInfo, checkFields) {
72 + return checkFields.every(field => {
73 + const value = userInfo[field]
74 + return value && value.toString().trim() !== ''
75 + })
76 +}
77 +
78 +/**
79 + * 权限检查函数
80 + * @param {string} permissionType - 权限类型
81 + * @param {Object} options - 配置选项
82 + * @param {string} options.message - 自定义提示消息
83 + * @param {string} options.redirectUrl - 自定义跳转地址
84 + * @param {Array} options.checkFields - 自定义检查字段
85 + * @param {Function} options.onSuccess - 验证成功回调
86 + * @param {Function} options.onFail - 验证失败回调
87 + * @param {boolean} options.showToast - 是否显示提示(默认true)
88 + * @param {boolean} options.autoRedirect - 是否自动跳转(默认true)
89 + * @returns {Promise<boolean>} 是否通过权限验证
90 + */
91 +export async function checkPermission(permissionType, options = {}) {
92 + const userStore = useUserStore()
93 +
94 + // 获取权限配置
95 + const config = PERMISSION_CONFIG[permissionType] || PERMISSION_CONFIG[PERMISSION_TYPES.BASIC_INFO]
96 +
97 + // 合并配置
98 + const finalConfig = {
99 + message: options.message || config.message,
100 + redirectUrl: options.redirectUrl || config.redirectUrl,
101 + checkFields: options.checkFields || config.checkFields,
102 + showToast: options.showToast !== false,
103 + autoRedirect: options.autoRedirect !== false,
104 + onSuccess: options.onSuccess,
105 + onFail: options.onFail
106 + }
107 +
108 + try {
109 + // 如果用户信息为空,先尝试获取
110 + if (!userStore.isAuthenticated) {
111 + await userStore.fetchUserInfo()
112 + }
113 +
114 + // 检查用户字段
115 + const hasPermission = checkUserFields(userStore.userInfo, finalConfig.checkFields)
116 +
117 + if (hasPermission) {
118 + // 权限验证成功
119 + finalConfig.onSuccess && finalConfig.onSuccess()
120 + return true
121 + } else {
122 + // 权限验证失败
123 + if (finalConfig.showToast) {
124 + Taro.showToast({
125 + title: finalConfig.message,
126 + icon: 'none',
127 + duration: 2000,
128 + success: () => {
129 + if (finalConfig.autoRedirect) {
130 + setTimeout(() => {
131 + Taro.navigateTo({
132 + url: finalConfig.redirectUrl
133 + })
134 + }, 2000)
135 + }
136 + }
137 + })
138 + } else if (finalConfig.autoRedirect) {
139 + Taro.navigateTo({
140 + url: finalConfig.redirectUrl
141 + })
142 + }
143 +
144 + finalConfig.onFail && finalConfig.onFail()
145 + return false
146 + }
147 + } catch (error) {
148 + console.error('权限检查失败:', error)
149 + finalConfig.onFail && finalConfig.onFail(error)
150 + return false
151 + }
152 +}
153 +
154 +/**
155 + * 权限装饰器函数,用于包装需要权限验证的函数
156 + * @param {string} permissionType - 权限类型
157 + * @param {Object} options - 配置选项
158 + * @returns {Function} 装饰器函数
159 + */
160 +export function requirePermission(permissionType, options = {}) {
161 + return function(target, propertyKey, descriptor) {
162 + const originalMethod = descriptor.value
163 +
164 + descriptor.value = async function(...args) {
165 + const hasPermission = await checkPermission(permissionType, options)
166 + if (hasPermission) {
167 + return originalMethod.apply(this, args)
168 + }
169 + }
170 +
171 + return descriptor
172 + }
173 +}
174 +
175 +/**
176 + * 创建需要权限验证的导航函数
177 + * @param {string} url - 目标页面路径
178 + * @param {string} permissionType - 权限类型
179 + * @param {Object} options - 配置选项
180 + * @returns {Function} 导航函数
181 + */
182 +export function createPermissionNavigate(url, permissionType, options = {}) {
183 + return async function() {
184 + const hasPermission = await checkPermission(permissionType, options)
185 + if (hasPermission) {
186 + Taro.navigateTo({ url })
187 + }
188 + }
189 +}
190 +
191 +/**
192 + * 批量权限检查
193 + * @param {Array} permissions - 权限类型数组
194 + * @param {Object} options - 配置选项
195 + * @returns {Promise<boolean>} 是否全部通过权限验证
196 + */
197 +export async function checkMultiplePermissions(permissions, options = {}) {
198 + const results = await Promise.all(
199 + permissions.map(permission =>
200 + checkPermission(permission, { ...options, showToast: false, autoRedirect: false })
201 + )
202 + )
203 +
204 + const allPassed = results.every(result => result)
205 +
206 + if (!allPassed && options.showToast !== false) {
207 + const config = PERMISSION_CONFIG[permissions[0]] || PERMISSION_CONFIG[PERMISSION_TYPES.BASIC_INFO]
208 + Taro.showToast({
209 + title: options.message || config.message,
210 + icon: 'none',
211 + duration: 2000
212 + })
213 + }
214 +
215 + return allPassed
216 +}
217 +
218 +/**
219 + * 权限中间件,用于页面级别的权限控制
220 + * @param {string} permissionType - 权限类型
221 + * @param {Object} options - 配置选项
222 + * @returns {Function} 中间件函数
223 + */
224 +export function permissionMiddleware(permissionType, options = {}) {
225 + return async function() {
226 + const hasPermission = await checkPermission(permissionType, {
227 + ...options,
228 + autoRedirect: true
229 + })
230 +
231 + if (!hasPermission) {
232 + // 如果没有权限,阻止页面继续加载
233 + throw new Error('权限验证失败')
234 + }
235 +
236 + return hasPermission
237 + }
238 +}