feat(权限): 实现权限管理系统并集成到各页面
添加权限管理系统,包括权限检查工具函数和用户状态管理 - 创建权限检查工具函数 checkPermission 和权限类型枚举 - 实现用户状态管理 store 用于集中管理用户信息和权限状态 - 在各页面集成权限检查,包括个人中心、卖车、购买等功能 - 添加权限使用示例文档 - 重构导航逻辑使用权限检查替代原有验证方式
Showing
9 changed files
with
626 additions
and
94 deletions
| ... | @@ -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 | } | ... | ... |
src/stores/user.js
0 → 100644
| 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 | +}) |
src/utils/permission-usage-examples.md
0 → 100644
| 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 |
src/utils/permission.js
0 → 100644
| 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 | +} |
-
Please register or login to post a comment