diff --git a/eslint.config.js b/eslint.config.js index 2fe17c7..530a235 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -29,6 +29,8 @@ export default antfu({ 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], 'vue/script-indent': ['error', 2, { baseIndent: 0 }], 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'no-useless-catch': 'off', }, env: { node: true, diff --git a/src/App.vue b/src/App.vue index 6fe725a..735c9d7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,10 +1,13 @@ <script setup lang="ts"> import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'; +import { useUserStore } from '@/stores/modules/user'; onLaunch(() => { console.log('App Launch'); }); onShow(() => { + const userStore = useUserStore(); + userStore.initUserInfo(); console.log('App Show'); }); onHide(() => { diff --git a/src/pages.json b/src/pages.json index 8b0c288..ccb88e3 100644 --- a/src/pages.json +++ b/src/pages.json @@ -7,7 +7,8 @@ "navigationBarTitleText": "Home" }, "meta": { - "tabBar": true + "tabBar": true, + "ignoreAuth": true } }, { @@ -17,7 +18,8 @@ "navigationBarTitleText": "Demo" }, "meta": { - "tabBar": true + "tabBar": true, + "ignoreAuth": true } }, { diff --git a/src/pages/about/index.vue b/src/pages/about/index.vue index f54bf09..dedd434 100644 --- a/src/pages/about/index.vue +++ b/src/pages/about/index.vue @@ -1,9 +1,41 @@ <script lang="ts" setup> +import BasicButton from '@/components/BasicButton/index.vue'; +import { useUserStore } from '@/stores/modules/user'; +const router = useRouter(); +const userStore = useUserStore(); +const { loggedIn, userInfo } = storeToRefs(userStore); + +function handleJump(url: string) { + router.push(url); +} + +// 登出 +function handleLoginOut() { + userStore.logout(); +} </script> <template> - <view>关于</view> + <view class="text-md pt-36"> + <view v-if="loggedIn" class="text-center"> + <image class="h-56 w-56" :src="userInfo?.avatar" /> + <view class="mt-2"> + {{ userInfo?.nickname }} + </view> + </view> + <view class="mt-6 flex flex-col gap-y-xl justify-center items-center"> + <BasicButton @click="handleJump('/pages/log/index?id=4345&title=log&word=关键词')"> + log + </BasicButton> + <BasicButton v-if="loggedIn" @click="handleLoginOut"> + 登出 + </BasicButton> + <BasicButton v-else @click="handleJump('/pages/login/index')"> + 登入 + </BasicButton> + </view> + </view> </template> <style lang="scss" scoped> diff --git a/src/pages/demo/index.vue b/src/pages/demo/index.vue index 0c0ba3d..c64a43d 100644 --- a/src/pages/demo/index.vue +++ b/src/pages/demo/index.vue @@ -1,16 +1,12 @@ <script lang="ts" setup> -import { ref } from 'vue'; - const demo = ref('Demo'); </script> <template> - <view>{{ demo }}</view> + <view class="pt-36 text-lg font-medium flex justify-center items-center"> + {{ demo }} + </view> </template> <style lang="scss" scoped> -.container { - padding: 128rpx 0; - text-align: center; -} </style> diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 122890d..9d46c09 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -1,24 +1,19 @@ <script setup lang="ts"> -const router = useRouter(); +import { getRandomIcon } from '@/utils/character'; +import { platform } from '@/utils/platform'; -function onClick() { - router.push({ name: 'Login' }); -} -function goLog() { - router.push({ name: 'Log' }); -} +const logo = getRandomIcon(); +const appTitle = 'uniapp-vue3'; </script> <template> - <view class="border border-blue border-solid text-mini text-primary center h-44"> - home + <view class="pt-36 flex flex-col gap-y-2 items-center"> + <image :src="logo" class="h-56 w-56" alt="" mode="widthFix" /> + <view class="text-xl font-semibold"> + {{ appTitle }} + </view> + <view>当前平台:{{ platform }}</view> </view> - <button @click="onClick"> - 登录 - </button> - <button @click="goLog"> - Log - </button> </template> <style lang="scss"> diff --git a/src/pages/log/index.vue b/src/pages/log/index.vue index bbf50f7..34eff32 100644 --- a/src/pages/log/index.vue +++ b/src/pages/log/index.vue @@ -9,10 +9,12 @@ const userStore = useUserStore(); <template> <view class="text-center"> - 登录后访问log + <view class="mt-36 text-center"> + 登录后访问log + </view> + <image class="my-4 h-48 w-48" :src="userStore.userInfo?.avatar" mode="aspectFit" lazy-load="false" binderror="" bindload="" /> + <view>{{ userStore.userInfo?.nickname }}</view> </view> - <image class="" :src="userStore.userInfo?.avatar" mode="aspectFit" lazy-load="false" binderror="" bindload="" /> - <view>用户昵称:{{ userStore.userInfo?.nickname }}</view> </template> <style lang="scss" scoped></style> diff --git a/src/router/guard.ts b/src/router/guard.ts index dcc62f2..fc82141 100644 --- a/src/router/guard.ts +++ b/src/router/guard.ts @@ -1,4 +1,5 @@ import type { Router } from 'uni-mini-router/lib/interfaces'; +import { isLogin } from '@/utils/auth'; export function createRouterGuard(router: Router) { createBeforeEachGuard(router); @@ -6,37 +7,38 @@ export function createRouterGuard(router: Router) { } function createBeforeEachGuard(router: Router) { - router.beforeEach((_1, _2, next) => { - // const authStore = useAuthStore(); - // if (to && to?.meta?.ignoreAuth) { - // // 如果目标路由忽略验证直接跳转 - // next(); - // } else if (!authStore.isLogin && to && to.name !== 'Login') { - // // 如果没有登录且目标路由不是登录页面则跳转到登录页面 - // // 将目标路由和参数传入登录页面,登录成功后直接跳转到目标路由,优化体验 - // next({ name: 'Login', params: { redirect: to.name!, ...to.query }, navType: 'push' }); - // } else if (authStore.isLogin && to && to.name === 'Login') { - // // 如果已经登录且目标页面是登录页面则跳转至首页 - // next({ name: 'Home', navType: 'replaceAll' }); - // } else { - // next(); - // } + router.beforeEach((to, _, next) => { + console.log('beforeEach', to); + const _isLogin = isLogin(); + if (to && to?.meta?.ignoreAuth) { + // 如果目标路由忽略验证直接跳转 + next(); + } else if (!_isLogin && to && to.name !== 'Login') { + // 如果没有登录且目标路由不是登录页面则跳转到登录页面 + // 将目标路由和参数传入登录页面,登录成功后直接跳转到目标路由,优化体验 + next({ name: 'Login', params: { redirect: to.name!, ...to.query }, navType: 'push' }); + } else if (_isLogin && to && to.name === 'Login') { + // 如果已经登录且目标页面是登录页面则跳转至首页 + next({ name: 'Home', navType: 'replaceAll' }); + } else { + next(); + } next(); }); } function createAfterEachGuard(router: Router) { - router.afterEach((_) => { - // if (to && to?.meta?.ignoreAuth) - // return; - // const authStore = useAuthStore(); - // if (!authStore.isLogin && to && to.name !== 'Login') { - // // 如果没有登录且目标路由不是登录页面则跳转到登录页面 - // router.push({ name: 'Login', params: { ...to.query } }); - // } else if (authStore.isLogin && to && to.name === 'Login') { - // // 如果已经登录且目标页面是登录页面则跳转至首页 - // router.replaceAll({ name: 'Home' }); - // } - console.log('afterEach', _); + router.afterEach((to) => { + if (to && to?.meta?.ignoreAuth) + return; + const _isLogin = isLogin(); + if (!_isLogin && to && to.name !== 'Login') { + // 如果没有登录且目标路由不是登录页面则跳转到登录页面 + router.push({ name: 'Login', params: { ...to.query } }); + } else if (_isLogin && to && to.name === 'Login') { + // 如果已经登录且目标页面是登录页面则跳转至首页 + router.replaceAll({ name: 'Home' }); + } + console.log('afterEach', to); }); } diff --git a/src/services/api/auth.ts b/src/services/api/auth.ts index 3d2d7e6..3de0828 100644 --- a/src/services/api/auth.ts +++ b/src/services/api/auth.ts @@ -9,7 +9,11 @@ const REFRESH_TOKEN = '/refresh/token'; * @param params */ export function login(params: LoginParams) { - return request.Post<LoginModel>(LOGIN, params); + return request.Post<LoginModel>(LOGIN, params, { + meta: { + ignoreAuth: true, + }, + }); } /** diff --git a/src/stores/modules/auth.ts b/src/stores/modules/auth.ts deleted file mode 100644 index 904bc28..0000000 --- a/src/stores/modules/auth.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { defineStore } from 'pinia'; -import { useRequest } from 'alova'; -import { getCache, removeCache, setCache } from '@/utils/cache'; -import { TOKEN_KEY } from '@/enums/cacheEnum'; -import { login as loginApi, logout as logoutApi } from '@/services/api/auth'; - -const authenticationScheme = 'Bearer'; - -export const useAuthStore = defineStore('AuthStore', () => { - const token = ref<string | null>(null); - - const initToken = () => { - token.value = getCache<string>(TOKEN_KEY) || null; - }; - - function setToken(value: string | null) { - setCache(TOKEN_KEY, value); - token.value = value; - } - - const getAuthorization = computed(() => { - return token.value ? `${authenticationScheme} ${token.value}` : ''; - }); - - // 登录 - const { send: sendLogin } = useRequest(loginApi, { immediate: false }); - const login = async (params: LoginParams) => { - try { - const res = await sendLogin(params); - setToken(res.token); - } catch (error) { - console.log(error); - } - }; - - // 登出 - const { send: sendLogout } = useRequest(logoutApi, { immediate: false }); - async function logout() { - try { - await sendLogout(); - removeCache(TOKEN_KEY); - token.value = null; - } catch (err: any) { - console.error(err); - } - } - - // 刷新token - async function refreshToken() { - try { - // const res = await refreshToken(); - // setToken(res.data.access_token); - // return res.data; - } catch (err: any) { - console.error(err); - } - } - - return { - token, - initToken, - setToken, - getAuthorization, - login, - logout, - refreshToken, - }; -}); diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts index 77ed453..cf78510 100644 --- a/src/stores/modules/user.ts +++ b/src/stores/modules/user.ts @@ -1,36 +1,69 @@ import { defineStore } from 'pinia'; import { useRequest } from 'alova'; -import { useAuthStore } from './auth'; import { getUserInfoApi } from '@/services/api/user'; import type { UserInfoModel } from '@/services/model/userModel'; +import { login as loginApi } from '@/services/api/auth'; +import { getToken, isLogin, setToken } from '@/utils/auth'; +import { removeCache } from '@/utils/cache'; +import { TOKEN_KEY } from '@/enums/cacheEnum'; export const useUserStore = defineStore('UserStore', () => { + const token = ref<string | null>(null); const userInfo = ref<UserInfoModel | null>(null); - const authStore = useAuthStore(); + // 初始化 + function initUserInfo() { + if (isLogin()) { + token.value = getToken(); + getUserInfo(); + } + } - const { send: _getUserInfo } = useRequest(getUserInfoApi, { initialData: null, immediate: false }); + // 是否登录 + const loggedIn = computed(() => !!token.value); + + // 登录 + const { send: sendLogin } = useRequest(loginApi, { immediate: false }); async function login(params: LoginParams) { try { - await authStore.login(params); + const res = await sendLogin(params); + token.value = res.token; + setToken(res.token); await getUserInfo(); } catch (error) { - console.log(error); + throw error; } } // 获取用户信息 + const { send: _getUserInfo } = useRequest(getUserInfoApi, { initialData: null, immediate: false }); async function getUserInfo() { try { userInfo.value = await _getUserInfo(); } catch (error) { - console.log(error); + throw error; + } + } + + // 登出 + // const { send: sendLogout } = useRequest(logoutApi, { immediate: false }); + async function logout() { + try { + // await sendLogout(); + removeCache(TOKEN_KEY); + userInfo.value = null; + token.value = null; + } catch (err: any) { + throw err; } } return { userInfo, + loggedIn, login, + logout, getUserInfo, + initUserInfo, }; }); diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 2b5dbb3..552f2a0 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,4 +1,26 @@ -import { getCache } from '@/utils/cache'; +import { getCache, setCache } from '@/utils/cache'; import { TOKEN_KEY } from '@/enums/cacheEnum'; -export const TOKEN = () => getCache<string>(TOKEN_KEY) || undefined; +const authenticationScheme = 'Bearer'; + +export function getToken() { + return getCache<string>(TOKEN_KEY) || null; +} + +export function getAuthorization() { + const token = getToken(); + return token ? `${authenticationScheme} ${token}` : null; +} + +export function setToken(token: string) { + return setCache(TOKEN_KEY, token); +} + +export function removeToken() { + return setCache(TOKEN_KEY, null); +} + +// 是否登录 +export function isLogin() { + return !!getToken(); +} diff --git a/src/utils/character.ts b/src/utils/character.ts index d424a8a..70a4d82 100644 --- a/src/utils/character.ts +++ b/src/utils/character.ts @@ -1,3 +1,6 @@ +import { random } from 'lodash-es'; +import multiavatar from '@multiavatar/multiavatar'; + const CHS_RANGE_START = 0x4E00; // 简体中文编码范围开始 const CHS_RANGE_END = 0x9FA5; // 简体中文编码范围结束 @@ -19,3 +22,13 @@ export function getRandomChsString(length: number) { } return result; } + +/** + * 随机 svg 图标 + */ +export function getRandomIcon() { + const svgCode = multiavatar(getRandomChsString(random(16, 32))); + return `data:image/svg+xml;charset=utf-8,${encodeURIComponent( + svgCode, + )}`; +} diff --git a/src/utils/env.ts b/src/utils/env.ts index 40f84dd..792d798 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -70,7 +70,7 @@ export function isUseMock(): boolean { * @example: */ export function getBaseUrl(): string { - return (isH5 && isDevMode()) ? getEnvValue<string>('VITE_PROXY_PREFIX') : getEnvValue<string>('VITE_BASE_URL'); + return (isH5() && isDevMode()) ? getEnvValue<string>('VITE_PROXY_PREFIX') : getEnvValue<string>('VITE_BASE_URL'); } /** @@ -79,5 +79,5 @@ export function getBaseUrl(): string { * @example: */ export function getUploadUrl(): string { - return (isH5 && isDevMode()) ? getEnvValue<string>('VITE_UPLOAD_PROXY_PREFIX') : getEnvValue<string>('VITE_UPLOAD_URL'); + return (isH5() && isDevMode()) ? getEnvValue<string>('VITE_UPLOAD_PROXY_PREFIX') : getEnvValue<string>('VITE_UPLOAD_URL'); } diff --git a/src/utils/http/index.ts b/src/utils/http/index.ts index f802f34..5ec2d96 100644 --- a/src/utils/http/index.ts +++ b/src/utils/http/index.ts @@ -4,13 +4,13 @@ import { assign } from 'lodash-es'; import { checkStatus } from './checkStatus'; import { getBaseUrl, isUseMock } from '@/utils/env'; import { mockAdapter } from '@/mock'; -import { useAuthStore } from '@/stores/modules/auth'; import { ContentTypeEnum, ResultEnum } from '@/enums/httpEnum'; import type { API } from '@/services/model/baseModel'; +import { getAuthorization } from '@/utils/auth'; const BASE_URL = getBaseUrl(); -const HEADER = { +const ContentType = { 'Content-Type': ContentTypeEnum.JSON, 'Accept': 'application/json, text/plain, */*', }; @@ -29,8 +29,14 @@ const alovaInstance = createAlova({ }), timeout: 5000, beforeRequest: (method) => { - const authStore = useAuthStore(); - method.config.headers = assign(method.config.headers, HEADER, authStore.getAuthorization); + method.config.headers = assign(method.config.headers, ContentType); + const { config } = method; + const ignoreAuth = !config.meta?.ignoreAuth; + const authorization = ignoreAuth ? getAuthorization() : null; + if (ignoreAuth && !authorization) { + throw new Error('[请求错误]:未登录'); + } + method.config.headers.authorization = getAuthorization(); }, responded: { /** @@ -52,16 +58,16 @@ const alovaInstance = createAlova({ return data as any; } checkStatus(statusCode, message || ''); - throw new Error(`[请求错误]:${message}`); + throw new Error(`请求错误[${code}]:${message}`); } - throw new Error(`[请求错误]:${errMsg}`); + throw new Error(`请求错误[${statusCode}]:${errMsg}`); }, /** * 请求失败的拦截器,请求错误时将会进入该拦截器。 */ onError: (err) => { - throw new Error(`[请求错误]:${err}`); + throw new Error(`请求错误:${err}`); }, }, }); diff --git a/src/utils/platform.ts b/src/utils/platform.ts index 974ddd2..d29584e 100644 --- a/src/utils/platform.ts +++ b/src/utils/platform.ts @@ -2,14 +2,16 @@ * @description 获取当前平台 */ -const platform = PLATFORM; -const isH5 = platform === 'h5'; -const isApp = platform === 'app'; -const isMp = platform.startsWith('mp-'); +export const platform = PLATFORM; -export { - platform, - isH5, - isApp, - isMp, -}; +export function isH5() { + return platform === 'h5'; +} + +export function isApp() { + return platform === 'app'; +} + +export function isMp() { + return platform.startsWith('mp-'); +} diff --git a/vite.config.ts b/vite.config.ts index ce65922..9ca06fe 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -62,6 +62,9 @@ export default defineConfig(async ({ mode }) => { { 'uni-mini-router': ['useRouter', 'useRoute'], }, + { + alova: ['useRequest'], + }, ], dts: 'typings/auto-imports.d.ts', eslintrc: {