create-unibest 4.0.2 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ <script lang="ts" setup>
2
+ import { storeToRefs } from 'pinia'
3
+ import { LOGIN_PAGE } from '@/router/config'
4
+ import { useUserStore } from '@/store'
5
+ import { useTokenStore } from '@/store/token'
6
+
7
+ definePage({
8
+ style: {
9
+ navigationBarTitleText: '我的',
10
+ },
11
+ })
12
+
13
+ const userStore = useUserStore()
14
+ const tokenStore = useTokenStore()
15
+ // 使用storeToRefs解构userInfo
16
+ const { userInfo } = storeToRefs(userStore)
17
+
18
+ // 微信小程序下登录
19
+ async function handleLogin() {
20
+ // #ifdef MP-WEIXIN
21
+ // 微信登录
22
+ await tokenStore.wxLogin()
23
+
24
+ // #endif
25
+ // #ifndef MP-WEIXIN
26
+ uni.navigateTo({
27
+ url: `${LOGIN_PAGE}`,
28
+ })
29
+ // #endif
30
+ }
31
+
32
+ function handleLogout() {
33
+ uni.showModal({
34
+ title: '提示',
35
+ content: '确定要退出登录吗?',
36
+ success: (res) => {
37
+ if (res.confirm) {
38
+ // 清空用户信息
39
+ useTokenStore().logout()
40
+ // 执行退出登录逻辑
41
+ uni.showToast({
42
+ title: '退出登录成功',
43
+ icon: 'success',
44
+ })
45
+ // #ifdef MP-WEIXIN
46
+ // 微信小程序,去首页
47
+ // uni.reLaunch({ url: '/pages/index/index' })
48
+ // #endif
49
+ // #ifndef MP-WEIXIN
50
+ // 非微信小程序,去登录页
51
+ // uni.navigateTo({ url: LOGIN_PAGE })
52
+ // #endif
53
+ }
54
+ },
55
+ })
56
+ }
57
+ </script>
58
+
59
+ <template>
60
+ <view class="profile-container">
61
+ <view class="mt-3 break-all px-3 text-center text-green-500">
62
+ {{ userInfo.username ? '已登录' : '未登录' }}
63
+ </view>
64
+ <view class="mt-3 break-all px-3">
65
+ {{ JSON.stringify(userInfo, null, 2) }}
66
+ </view>
67
+
68
+ <view class="mt-[60vh] px-3">
69
+ <view class="m-auto w-160px text-center">
70
+ <button v-if="tokenStore.hasLogin" type="warn" class="w-full" @click="handleLogout">
71
+ 退出登录
72
+ </button>
73
+ <button v-else type="primary" class="w-full" @click="handleLogin">
74
+ 登录
75
+ </button>
76
+ </view>
77
+ </view>
78
+ </view>
79
+ </template>
@@ -0,0 +1,30 @@
1
+ import { getAllPages } from '@/utils'
2
+
3
+ export const LOGIN_STRATEGY_MAP = {
4
+ DEFAULT_NO_NEED_LOGIN: 0, // 黑名单策略,默认可以进入APP
5
+ DEFAULT_NEED_LOGIN: 1, // 白名单策略,默认不可以进入APP,需要强制登录
6
+ }
7
+ // TODO: 1/3 登录策略,默认使用`无需登录策略`,即默认不需要登录就可以访问
8
+ export const LOGIN_STRATEGY = LOGIN_STRATEGY_MAP.DEFAULT_NO_NEED_LOGIN
9
+ export const isNeedLoginMode = LOGIN_STRATEGY === LOGIN_STRATEGY_MAP.DEFAULT_NEED_LOGIN
10
+
11
+ export const LOGIN_PAGE = '/pages/auth/login'
12
+ export const REGISTER_PAGE = '/pages/auth/register'
13
+
14
+ export const LOGIN_PAGE_LIST = [LOGIN_PAGE, REGISTER_PAGE]
15
+
16
+ // 在 definePage 里面配置了 excludeLoginPath 的页面,功能与 EXCLUDE_LOGIN_PATH_LIST 相同
17
+ export const excludeLoginPathList = getAllPages('excludeLoginPath').map(page => page.path)
18
+
19
+ // 排除在外的列表,白名单策略指白名单列表,黑名单策略指黑名单列表
20
+ // TODO: 2/3 在 definePage 配置 excludeLoginPath,或者在下面配置 EXCLUDE_LOGIN_PATH_LIST
21
+ export const EXCLUDE_LOGIN_PATH_LIST = [
22
+ '/pages/xxx/index', // 示例值
23
+ '/pages-sub/xxx/index', // 示例值
24
+ ...excludeLoginPathList, // 都是以 / 开头的 path
25
+ ]
26
+
27
+ // 在小程序里面是否使用H5的登录页,默认为 false
28
+ // 如果为 true 则复用 h5 的登录逻辑
29
+ // TODO: 3/3 确定自己的登录页是否需要在小程序里面使用
30
+ export const LOGIN_PAGE_ENABLE_IN_MP = false
@@ -0,0 +1,145 @@
1
+ import { isMp } from '@uni-helper/uni-env'
2
+ /**
3
+ * by 菲鸽 on 2025-08-19
4
+ * 路由拦截,通常也是登录拦截
5
+ * 黑、白名单的配置,请看 config.ts 文件, EXCLUDE_LOGIN_PATH_LIST
6
+ */
7
+ import { useTokenStore } from '@/store/token'
8
+ import { isPageTabbar, tabbarStore } from '@/tabbar/store'
9
+ import { getAllPages, getLastPage, HOME_PAGE, parseUrlToObj } from '@/utils/index'
10
+ import { EXCLUDE_LOGIN_PATH_LIST, isNeedLoginMode, LOGIN_PAGE, LOGIN_PAGE_ENABLE_IN_MP } from './config'
11
+
12
+ export const FG_LOG_ENABLE = false
13
+
14
+ export function judgeIsExcludePath(path: string) {
15
+ const isDev = import.meta.env.DEV
16
+ if (!isDev) {
17
+ return EXCLUDE_LOGIN_PATH_LIST.includes(path)
18
+ }
19
+ const allExcludeLoginPages = getAllPages('excludeLoginPath') // dev 环境下,需要每次都重新获取,否则新配置就不会生效
20
+ return EXCLUDE_LOGIN_PATH_LIST.includes(path) || (isDev && allExcludeLoginPages.some(page => page.path === path))
21
+ }
22
+
23
+ export const navigateToInterceptor = {
24
+ // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
25
+ // 增加对相对路径的处理,BY 网友 @ideal
26
+ invoke({ url, query }: { url: string, query?: Record<string, string> }) {
27
+ if (url === undefined) {
28
+ return
29
+ }
30
+ let { path, query: _query } = parseUrlToObj(url)
31
+
32
+ FG_LOG_ENABLE && console.log('\n\n路由拦截器:-------------------------------------')
33
+ FG_LOG_ENABLE && console.log('路由拦截器 1: url->', url, ', query ->', query)
34
+ const myQuery = { ..._query, ...query }
35
+ // /pages/route-interceptor/index?name=feige&age=30
36
+ FG_LOG_ENABLE && console.log('路由拦截器 2: path->', path, ', _query ->', _query)
37
+ FG_LOG_ENABLE && console.log('路由拦截器 3: myQuery ->', myQuery)
38
+
39
+ // 处理相对路径
40
+ if (!path.startsWith('/')) {
41
+ const currentPath = getLastPage()?.route || ''
42
+ const normalizedCurrentPath = currentPath.startsWith('/') ? currentPath : `/${currentPath}`
43
+ const baseDir = normalizedCurrentPath.substring(0, normalizedCurrentPath.lastIndexOf('/'))
44
+ path = `${baseDir}/${path}`
45
+ }
46
+
47
+ // // 处理路由不存在的情况
48
+ // if (path !== '/' && !getAllPages().some(page => page.path === path)) {
49
+ // console.warn('路由不存在:', path)
50
+ // return false // 明确表示阻止原路由继续执行
51
+ // }
52
+
53
+ // // 插件页面
54
+ // if (url.startsWith('plugin://')) {
55
+ // FG_LOG_ENABLE && console.log('路由拦截器 4: plugin:// 路径 ==>', url)
56
+ // path = url
57
+ // }
58
+
59
+ // 处理直接进入路由非首页时,tabbarIndex 不正确的问题
60
+ tabbarStore.setAutoCurIdx(path)
61
+
62
+ // 小程序里面使用平台自带的登录,则不走下面的逻辑
63
+ if (isMp && !LOGIN_PAGE_ENABLE_IN_MP) {
64
+ return true // 明确表示允许路由继续执行
65
+ }
66
+
67
+ const tokenStore = useTokenStore()
68
+ FG_LOG_ENABLE && console.log('tokenStore.hasLogin:', tokenStore.hasLogin)
69
+
70
+ // 不管黑白名单,登录了就直接去吧(但是当前不能是登录页)
71
+ if (tokenStore.hasLogin) {
72
+ if (path !== LOGIN_PAGE) {
73
+ return true // 明确表示允许路由继续执行
74
+ }
75
+ else {
76
+ console.log('已经登录,但是还在登录页', myQuery.redirect)
77
+ const url = myQuery.redirect || HOME_PAGE
78
+ if (isPageTabbar(url)) {
79
+ uni.switchTab({ url })
80
+ }
81
+ else {
82
+ uni.navigateTo({ url })
83
+ }
84
+ return false // 明确表示阻止原路由继续执行
85
+ }
86
+ }
87
+ let fullPath = path
88
+
89
+ if (Object.keys(myQuery).length) {
90
+ fullPath += `?${Object.keys(myQuery).map(key => `${key}=${myQuery[key]}`).join('&')}`
91
+ }
92
+ const redirectUrl = `${LOGIN_PAGE}?redirect=${encodeURIComponent(fullPath)}`
93
+
94
+ // #region 1/2 默认需要登录的情况(白名单策略) ---------------------------
95
+ if (isNeedLoginMode) {
96
+ // 需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示白名单,可以直接通过
97
+ if (judgeIsExcludePath(path)) {
98
+ return true // 明确表示允许路由继续执行
99
+ }
100
+ // 否则需要重定向到登录页
101
+ else {
102
+ if (path === LOGIN_PAGE) {
103
+ return true // 明确表示允许路由继续执行
104
+ }
105
+ FG_LOG_ENABLE && console.log('1 isNeedLogin(白名单策略) redirectUrl:', redirectUrl)
106
+ uni.navigateTo({ url: redirectUrl })
107
+ return false // 明确表示阻止原路由继续执行
108
+ }
109
+ }
110
+ // #endregion 1/2 默认需要登录的情况(白名单策略) ---------------------------
111
+
112
+ // #region 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
113
+ else {
114
+ // 不需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示黑名单,需要重定向到登录页
115
+ if (judgeIsExcludePath(path)) {
116
+ FG_LOG_ENABLE && console.log('2 isNeedLogin(黑名单策略) redirectUrl:', redirectUrl)
117
+ uni.navigateTo({ url: redirectUrl })
118
+ return false // 修改为false,阻止原路由继续执行
119
+ }
120
+ return true // 明确表示允许路由继续执行
121
+ }
122
+ // #endregion 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
123
+ },
124
+ }
125
+
126
+ // 针对 chooseLocation 的特殊处理
127
+ export const chooseLocationInterceptor = {
128
+ invoke(options: any) {
129
+ // 直接放行 chooseLocation 调用
130
+ FG_LOG_ENABLE && console.log('chooseLocation 调用,直接放行:', options)
131
+ return true
132
+ },
133
+ }
134
+
135
+ export const routeInterceptor = {
136
+ install() {
137
+ uni.addInterceptor('navigateTo', navigateToInterceptor)
138
+ uni.addInterceptor('reLaunch', navigateToInterceptor)
139
+ uni.addInterceptor('redirectTo', navigateToInterceptor)
140
+ uni.addInterceptor('switchTab', navigateToInterceptor)
141
+
142
+ // 添加 chooseLocation 的拦截器,确保直接放行
143
+ uni.addInterceptor('chooseLocation', chooseLocationInterceptor)
144
+ },
145
+ }
@@ -0,0 +1,9 @@
1
+ import type { FeatureContext } from '../../packages/cli/src/features/interface'
2
+
3
+ export const preApply = async (ctx: FeatureContext) => {
4
+ console.log(`[login] Pre-apply for ${ctx.projectPath}`)
5
+ }
6
+
7
+ export const postApply = async (ctx: FeatureContext) => {
8
+ console.log(`[login] Post-apply for ${ctx.projectPath}`)
9
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@unibest/feature-login",
3
+ "version": "1.0.0",
4
+ "description": "unibest login feature",
5
+ "dependencies": {}
6
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "loginStrategy": {
6
+ "type": "string",
7
+ "enum": ["blacklist", "whitelist"],
8
+ "default": "blacklist",
9
+ "description": "登录策略:blacklist(黑名单) 或 whitelist(白名单)"
10
+ }
11
+ },
12
+ "required": []
13
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-unibest",
3
3
  "type": "module",
4
- "version": "4.0.2",
4
+ "version": "4.0.3",
5
5
  "packageManager": "pnpm@10.10.0",
6
6
  "description": "快速创建 unibest 项目的脚手架工具",
7
7
  "author": "feige996",