hookehuyr

feat: 为所有制作计划书按钮添加登录权限检查

- 创建 usePlanPermission composable 统一权限检查逻辑
- 首页:在模板层添加权限检查
- 搜索页:在模板层添加权限检查
- 产品中心页:保持 openPlanPopup 函数内的权限检查
- 产品详情页:在模板层添加权限检查
- 修复权限重复调用问题(只在模板层检查一次)

影响文件:
- src/composables/usePlanPermission.js
- src/pages/index/index.vue
- src/pages/search/index.vue
- src/pages/product-center/index.vue
- src/pages/product-detail/index.vue
1 +/**
2 + * 计划书权限检查 Composable
3 + *
4 + * @description 统一处理制作计划书的登录权限检查
5 + * @module composables/usePlanPermission
6 + * @author Claude Code
7 + * @created 2026-02-12
8 + */
9 +
10 +import { useUserStore } from '@/stores/user'
11 +import Taro from '@tarojs/taro'
12 +
13 +/**
14 + * 计划书权限检查 Hook
15 + *
16 + * @description 提供统一的权限检查逻辑,用于制作计划书前的登录验证
17 + * @returns {Object} 权限检查方法
18 + *
19 + * @example
20 + * const { checkPlanPermission } = usePlanPermission()
21 + *
22 + * // 在点击计划书按钮时使用
23 + * checkPlanPermission(() => {
24 + * // 已登录时的回调逻辑
25 + * openPlanPopup(productId)
26 + * })
27 + */
28 +export function usePlanPermission() {
29 + const userStore = useUserStore()
30 +
31 + /**
32 + * 检查计划书权限
33 + *
34 + * @description 判断用户是否登录,未登录时提示并引导登录,已登录时执行回调
35 + * @param {Function} callback - 已登录时执行的回调函数
36 + * @returns {boolean} 是否有权限(true=已登录,false=未登录)
37 + *
38 + * @example
39 + * const hasPermission = checkPlanPermission(() => {
40 + * console.log('用户已登录,可以制作计划书')
41 + * })
42 + */
43 + const checkPlanPermission = (callback) => {
44 + console.log('[usePlanPermission] 检查权限,当前登录状态:', userStore.isLoggedIn)
45 + // 检查登录状态
46 + if (!userStore.isLoggedIn) {
47 + console.log('[usePlanPermission] 用户未登录,显示登录提示')
48 + // 未登录,显示提示框
49 + Taro.showModal({
50 + title: '提示',
51 + content: '请先登录后再制作计划书',
52 + confirmText: '去登录',
53 + cancelText: '取消',
54 + success: (res) => {
55 + if (res.confirm) {
56 + // 用户点击"去登录",跳转到登录页
57 + console.log('[usePlanPermission] 用户点击去登录')
58 + Taro.navigateTo({
59 + url: '/pages/login/index'
60 + })
61 + }
62 + }
63 + })
64 + return false
65 + }
66 +
67 + console.log('[usePlanPermission] 用户已登录,执行回调')
68 + // 已登录,执行回调
69 + callback?.()
70 + return true
71 + }
72 +
73 + return {
74 + checkPlanPermission
75 + }
76 +}
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
95 :tags="product.tags" 95 :tags="product.tags"
96 :class="{ 'mb-[24rpx]': index < hotProducts.length - 1 }" 96 :class="{ 'mb-[24rpx]': index < hotProducts.length - 1 }"
97 @detail="goToProductDetail" 97 @detail="goToProductDetail"
98 - @plan="openPlanPopup" 98 + @plan="(productId) => checkPlanPermission(() => openPlanPopup(productId))"
99 /> 99 />
100 </view> 100 </view>
101 </view> 101 </view>
...@@ -173,11 +173,15 @@ import { listAPI } from '@/api/get_product'; ...@@ -173,11 +173,15 @@ import { listAPI } from '@/api/get_product';
173 import { weekHotAPI } from '@/api/file'; 173 import { weekHotAPI } from '@/api/file';
174 import { homeIconAPI } from '@/api/home'; 174 import { homeIconAPI } from '@/api/home';
175 import { usePlanSubmit } from '@/composables/usePlanSubmit'; 175 import { usePlanSubmit } from '@/composables/usePlanSubmit';
176 +import { usePlanPermission } from '@/composables/usePlanPermission';
176 177
177 178
178 // User Store 179 // User Store
179 const userStore = useUserStore(); 180 const userStore = useUserStore();
180 181
182 +// 获取权限检查方法
183 +const { checkPlanPermission } = usePlanPermission();
184 +
181 // Header Image Error State 185 // Header Image Error State
182 /** 186 /**
183 * 头部图片加载失败状态 187 * 头部图片加载失败状态
......
...@@ -142,6 +142,7 @@ ...@@ -142,6 +142,7 @@
142 import { ref, computed } from 'vue' 142 import { ref, computed } from 'vue'
143 import Taro, { useLoad } from '@tarojs/taro' 143 import Taro, { useLoad } from '@tarojs/taro'
144 import { useGo } from '@/hooks/useGo' 144 import { useGo } from '@/hooks/useGo'
145 +import { useUserStore } from '@/stores/user'
145 import { useListItemClick, ListType } from '@/composables/useListItemClick' 146 import { useListItemClick, ListType } from '@/composables/useListItemClick'
146 import LoadMoreList from '@/components/list/LoadMoreList' 147 import LoadMoreList from '@/components/list/LoadMoreList'
147 import NavHeader from '@/components/navigation/NavHeader.vue' 148 import NavHeader from '@/components/navigation/NavHeader.vue'
...@@ -150,6 +151,7 @@ import PlanFormContainer from '@/components/plan/PlanFormContainer.vue' ...@@ -150,6 +151,7 @@ import PlanFormContainer from '@/components/plan/PlanFormContainer.vue'
150 import { listAPI } from '@/api/get_product' 151 import { listAPI } from '@/api/get_product'
151 import { mockProductListAPI } from '@/utils/mockData' 152 import { mockProductListAPI } from '@/utils/mockData'
152 import { usePlanSubmit } from '@/composables/usePlanSubmit' 153 import { usePlanSubmit } from '@/composables/usePlanSubmit'
154 +import { usePlanPermission } from '@/composables/usePlanPermission'
153 155
154 // ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API 156 // ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
155 // const USE_MOCK_DATA = process.env.NODE_ENV === 'development' 157 // const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
...@@ -157,6 +159,12 @@ const USE_MOCK_DATA = false ...@@ -157,6 +159,12 @@ const USE_MOCK_DATA = false
157 159
158 const go = useGo() 160 const go = useGo()
159 161
162 +// User Store
163 +const userStore = useUserStore()
164 +
165 +// 获取权限检查方法
166 +const { checkPlanPermission } = usePlanPermission()
167 +
160 /** 168 /**
161 * 当前列表数据 169 * 当前列表数据
162 * @type {Ref<Array<any>>} 170 * @type {Ref<Array<any>>}
...@@ -495,8 +503,12 @@ const { handleClick: handleProductClick } = useListItemClick({ ...@@ -495,8 +503,12 @@ const { handleClick: handleProductClick } = useListItemClick({
495 * @param {Object} product - 产品对象 503 * @param {Object} product - 产品对象
496 */ 504 */
497 const openPlanPopup = (product) => { 505 const openPlanPopup = (product) => {
506 + console.log('[Product Center] 点击制作计划书,当前登录状态:', userStore.isLoggedIn)
507 + checkPlanPermission(() => {
508 + console.log('[Product Center] 权限检查通过,打开计划书弹窗')
498 selectedProduct.value = product 509 selectedProduct.value = product
499 showPlanPopup.value = true 510 showPlanPopup.value = true
511 + })
500 } 512 }
501 513
502 // 使用 composable 统一处理计划书提交后逻辑 514 // 使用 composable 统一处理计划书提交后逻辑
......
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
104 <nut-button 104 <nut-button
105 color="#2563EB" 105 color="#2563EB"
106 class="!w-full !h-[88rpx] !rounded-[16rpx] !text-[28rpx] !font-bold" 106 class="!w-full !h-[88rpx] !rounded-[16rpx] !text-[28rpx] !font-bold"
107 - @tap="openPlanPopup" 107 + @tap="() => checkPlanPermission(() => openPlanPopup())"
108 > 108 >
109 制作计划书 109 制作计划书
110 </nut-button> 110 </nut-button>
...@@ -134,6 +134,7 @@ import Taro, { useLoad } from '@tarojs/taro' ...@@ -134,6 +134,7 @@ import Taro, { useLoad } from '@tarojs/taro'
134 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons' 134 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'
135 import { detailAPI } from '@/api/get_product' 135 import { detailAPI } from '@/api/get_product'
136 import { usePlanSubmit } from '@/composables/usePlanSubmit' 136 import { usePlanSubmit } from '@/composables/usePlanSubmit'
137 +import { usePlanPermission } from '@/composables/usePlanPermission'
137 138
138 const { viewFile } = useFileOperation() 139 const { viewFile } = useFileOperation()
139 140
...@@ -211,8 +212,10 @@ const viewDocument = (doc) => { ...@@ -211,8 +212,10 @@ const viewDocument = (doc) => {
211 /** 212 /**
212 * 打开计划书弹窗 213 * 打开计划书弹窗
213 * 214 *
214 - * @description 打开当前产品的计划书表单 215 + * @description 检查登录权限后,打开当前产品的计划书表单
215 */ 216 */
217 +const { checkPlanPermission } = usePlanPermission()
218 +
216 const openPlanPopup = () => { 219 const openPlanPopup = () => {
217 showPlanPopup.value = true 220 showPlanPopup.value = true
218 } 221 }
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
74 :tags="item.tags || []" 74 :tags="item.tags || []"
75 class="search-result-item" 75 class="search-result-item"
76 @detail="goToProductDetail" 76 @detail="goToProductDetail"
77 - @plan="openPlanPopup" 77 + @plan="(productId) => checkPlanPermission(() => openPlanPopup(productId))"
78 /> 78 />
79 79
80 <!-- File Results --> 80 <!-- File Results -->
...@@ -138,12 +138,16 @@ import PlanFormContainer from '@/components/plan/PlanFormContainer.vue' ...@@ -138,12 +138,16 @@ import PlanFormContainer from '@/components/plan/PlanFormContainer.vue'
138 import { searchAPI } from '@/api/search' 138 import { searchAPI } from '@/api/search'
139 import { mockSearchAPI } from '@/utils/mockData' 139 import { mockSearchAPI } from '@/utils/mockData'
140 import { usePlanSubmit } from '@/composables/usePlanSubmit' 140 import { usePlanSubmit } from '@/composables/usePlanSubmit'
141 +import { usePlanPermission } from '@/composables/usePlanPermission'
141 142
142 // ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API 143 // ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
143 const USE_MOCK_DATA = process.env.NODE_ENV === 'development' 144 const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
144 145
145 const go = useGo() 146 const go = useGo()
146 147
148 +// 获取权限检查方法
149 +const { checkPlanPermission } = usePlanPermission()
150 +
147 /** 151 /**
148 * 搜索页面状态管理 152 * 搜索页面状态管理
149 * @description 支持双类型(产品/资料)搜索,自动切换分类 153 * @description 支持双类型(产品/资料)搜索,自动切换分类
......