feat(useTitle): Add useTitle

feat(useNProgress): Add useNProgress
This commit is contained in:
kailong321200875 2022-01-09 10:57:50 +08:00
parent 3fc7d4d39a
commit c5ab3599c8
24 changed files with 1225 additions and 844 deletions

View File

@ -14,6 +14,5 @@
"i18n-ally.enabledParsers": ["ts"], "i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en", "i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN", "i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"], "i18n-ally.enabledFrameworks": ["vue", "react"]
"god.tsconfig": "./tsconfig.json"
} }

View File

@ -1,8 +1,10 @@
import { config } from '@/config/axios' import { config } from '@/config/axios/config'
import { MockMethod } from 'vite-plugin-mock' import { MockMethod } from 'vite-plugin-mock'
const { result_code } = config const { result_code } = config
const timeout = 2000
const List: { const List: {
username: string username: string
password: string password: string
@ -28,6 +30,7 @@ export default [
{ {
url: '/user/login', url: '/user/login',
method: 'post', method: 'post',
timeout,
response: ({ body }) => { response: ({ body }) => {
const data = body const data = body
let hasUser = false let hasUser = false

View File

@ -26,13 +26,14 @@
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.1.0", "@iconify/iconify": "^2.1.0",
"@vueuse/core": "^7.5.1", "@vueuse/core": "^7.5.3",
"@zxcvbn-ts/core": "^1.2.0", "@zxcvbn-ts/core": "^1.2.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^0.24.0", "axios": "^0.24.0",
"element-plus": "1.3.0-beta.1", "element-plus": "1.3.0-beta.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.9", "pinia": "^2.0.9",
"qs": "^6.10.2", "qs": "^6.10.2",
"vue": "3.2.26", "vue": "3.2.26",
@ -44,28 +45,30 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.0.1", "@commitlint/cli": "^16.0.1",
"@commitlint/config-conventional": "^16.0.0", "@commitlint/config-conventional": "^16.0.0",
"@iconify/json": "^1.1.450", "@iconify/json": "^1.1.453",
"@intlify/vite-plugin-vue-i18n": "^3.2.1", "@intlify/vite-plugin-vue-i18n": "^3.2.1",
"@purge-icons/generated": "^0.7.0", "@purge-icons/generated": "^0.7.0",
"@types/lodash-es": "^4.17.5", "@types/lodash-es": "^4.17.5",
"@types/node": "^17.0.5", "@types/node": "^17.0.8",
"@typescript-eslint/eslint-plugin": "^5.8.1", "@types/nprogress": "^0.2.0",
"@typescript-eslint/parser": "^5.8.1", "@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"@vitejs/plugin-vue": "^2.0.1", "@vitejs/plugin-vue": "^2.0.1",
"@vitejs/plugin-vue-jsx": "^1.3.3", "@vitejs/plugin-vue-jsx": "^1.3.3",
"autoprefixer": "^10.4.1", "autoprefixer": "^10.4.2",
"commitizen": "^4.2.4", "commitizen": "^4.2.4",
"eslint": "^8.6.0", "eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.2.1", "eslint-define-config": "^1.2.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0", "eslint-plugin-vue": "^8.2.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"less": "^4.1.2", "less": "^4.1.2",
"lint-staged": "^12.1.4", "lint-staged": "^12.1.7",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-less": "^5.0.0", "postcss-less": "^6.0.0",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
@ -82,7 +85,7 @@
"vite-plugin-style-import": "^1.4.1", "vite-plugin-style-import": "^1.4.1",
"vite-plugin-svg-icons": "^1.1.0", "vite-plugin-svg-icons": "^1.1.0",
"vite-plugin-windicss": "^1.6.1", "vite-plugin-windicss": "^1.6.1",
"vue-tsc": "^0.30.1", "vue-tsc": "^0.30.2",
"windicss": "^3.4.2", "windicss": "^3.4.2",
"windicss-analysis": "^0.3.5" "windicss-analysis": "^0.3.5"
}, },

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
import { useAxios } from '@/hooks/web/useAxios' import { useAxios } from '@/hooks/web/useAxios'
import type { UserLoginType } from './types'
const { request } = useAxios() const { request } = useAxios()
export const loginApi = ({ data }: AxiosConfig) => { export const loginApi = (data: UserLoginType) => {
return request({ url: '/user/login', method: 'post', data }) return request({ url: '/user/login', method: 'post', data })
} }

4
src/api/login/types.ts Normal file
View File

@ -0,0 +1,4 @@
export type UserLoginType = {
username: string
password: string
}

View File

@ -16,7 +16,7 @@ provide('configGlobal', props)
</script> </script>
<template> <template>
<ElConfigProvider :locale="locale.elLocale" :size="size"> <ElConfigProvider :locale="locale.elLocale" :message="{ max: 1 }" :size="size">
<slot></slot> <slot></slot>
</ElConfigProvider> </ElConfigProvider>
</template> </template>

View File

@ -21,7 +21,6 @@ export interface AppState {
greyMode: boolean greyMode: boolean
showBackTop: boolean showBackTop: boolean
showMenuTab: boolean showMenuTab: boolean
requestTime: boolean
isDark: boolean isDark: boolean
size: ElememtPlusSzie size: ElememtPlusSzie
sizeMap: ElememtPlusSzie[] sizeMap: ElememtPlusSzie[]
@ -44,7 +43,6 @@ export const appModules: AppState = {
greyMode: false, // 是否开始灰色模式,用于特殊悼念日 greyMode: false, // 是否开始灰色模式,用于特殊悼念日
showBackTop: true, // 是否显示回到顶部 showBackTop: true, // 是否显示回到顶部
showMenuTab: false, // 是否固定一级菜单 showMenuTab: false, // 是否固定一级菜单
requestTime: false, // 是否在接口调用时添加时间戳避免IE缓存
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式 isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
size: wsCache.get('default') || 'default', // 组件尺寸 size: wsCache.get('default') || 'default', // 组件尺寸
sizeMap: ['default', 'large', 'small'] sizeMap: ['default', 'large', 'small']

View File

@ -6,7 +6,7 @@ const config: {
test: string test: string
} }
result_code: number | string result_code: number | string
default_headers: AxiosHeadersType default_headers: AxiosHeaders
request_timeout: number request_timeout: number
} = { } = {
/** /**

View File

@ -10,7 +10,7 @@ import { ElMessage } from 'element-plus'
import qs from 'qs' import qs from 'qs'
import { config } from '@/config/axios' import { config } from '@/config/axios/config'
const { result_code, base_url } = config const { result_code, base_url } = config
@ -25,7 +25,6 @@ const service: AxiosInstance = axios.create({
// request拦截器 // request拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config: AxiosRequestConfig) => { (config: AxiosRequestConfig) => {
console.log('我进来了吗')
if ( if (
config.method === 'post' && config.method === 'post' &&
(config.headers as AxiosRequestHeaders)['Content-Type'] === (config.headers as AxiosRequestHeaders)['Content-Type'] ===
@ -59,7 +58,6 @@ service.interceptors.request.use(
// response 拦截器 // response 拦截器
service.interceptors.response.use( service.interceptors.response.use(
(response: AxiosResponse<Recordable>) => { (response: AxiosResponse<Recordable>) => {
console.log(response)
if (response.data.code === result_code) { if (response.data.code === result_code) {
return response.data return response.data
} else { } else {
@ -67,7 +65,6 @@ service.interceptors.response.use(
} }
}, },
(error: AxiosError) => { (error: AxiosError) => {
console.log(error)
console.log('err' + error) // for debug console.log('err' + error) // for debug
ElMessage.error(error.message) ElMessage.error(error.message)
return Promise.reject(error) return Promise.reject(error)

View File

@ -1,12 +1,8 @@
import { service } from '@/plugins/axios' import { service } from '@/config/axios'
import { AxiosPromise } from 'axios' import { AxiosPromise } from 'axios'
import { useAppStoreWithOut } from '@/store/modules/app' import { config } from '@/config/axios/config'
import { config } from '@/config/axios'
const appStore = useAppStoreWithOut()
const { default_headers } = config const { default_headers } = config
@ -22,7 +18,7 @@ export function useAxios() {
return service({ return service({
url: url, url: url,
method, method,
params: appStore.getRequestTime ? { time: new Date().getTime(), ...(params || {}) } : params, params,
data, data,
responseType: responseType, responseType: responseType,
headers: { headers: {

View File

@ -0,0 +1,32 @@
import { watch, ref, nextTick, unref } from 'vue'
import type { NProgressOptions } from 'nprogress'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { useCssVar } from '@vueuse/core'
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
export function useNProgress() {
const isLoading = ref(false)
NProgress.configure({ showSpinner: false } as NProgressOptions)
watch(
() => isLoading.value,
async (loading: boolean) => {
loading ? NProgress.start() : NProgress.done()
await nextTick()
const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef
if (bar) {
bar.style.background = unref(primaryColor.value)
}
}
)
function toggle() {
isLoading.value = !isLoading.value
}
return {
toggle
}
}

25
src/hooks/web/useTitle.ts Normal file
View File

@ -0,0 +1,25 @@
import { watch, ref } from 'vue'
import { isString } from '@/utils/is'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useI18n } from '@/hooks/web/useI18n'
const appStore = useAppStoreWithOut()
export function useTitle(newTitle?: string) {
const { t } = useI18n()
const title = ref(
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
)
watch(
title,
(n, o) => {
if (isString(n) && n !== o && document) {
document.title = n
}
},
{ immediate: true }
)
return title
}

9
src/layout/Layout.vue Normal file
View File

@ -0,0 +1,9 @@
<script setup lang="ts"></script>
<template>
<section>
<router-view v-slot="{ Component, route }">
<component :is="Component" :key="route.fullPath" />
</router-view>
</section>
</template>

View File

@ -22,6 +22,12 @@ export default {
remember: 'Remember me', remember: 'Remember me',
forgetPassword: 'Forget password' forgetPassword: 'Forget password'
}, },
router: {
login: 'Login'
},
mock: {
loginErr: 'Wrong account or password'
},
formDemo: { formDemo: {
input: 'Input', input: 'Input',
inputNumber: 'InputNumber', inputNumber: 'InputNumber',

View File

@ -22,6 +22,12 @@ export default {
remember: '记住我', remember: '记住我',
forgetPassword: '忘记密码' forgetPassword: '忘记密码'
}, },
router: {
login: '登录'
},
mock: {
loginErr: '账号或密码错误'
},
formDemo: { formDemo: {
input: '输入框', input: '输入框',
inputNumber: '数字输入框', inputNumber: '数字输入框',

View File

@ -29,6 +29,8 @@ import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import './permission'
async function setupAll() { async function setupAll() {
const app = createApp(App) const app = createApp(App)

51
src/permission.ts Normal file
View File

@ -0,0 +1,51 @@
import router from './router'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useCache } from '@/hooks/web/useCache'
// import type { RouteRecordRaw } from 'vue-router'
import { useTitle } from '@/hooks/web/useTitle'
import { useNProgress } from '@/hooks/web/useNProgress'
const appStore = useAppStoreWithOut()
const { wsCache } = useCache()
const { toggle } = useNProgress()
const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
console.log(from)
toggle()
if (wsCache.get(appStore.getUserInfo)) {
if (to.path === '/login') {
next({ path: '/' })
} else {
// if (permissionStore.getIsAddRouters) {
// next()
// return
// }
// permissionStore.generateRoutes().then(() => {
// permissionStore.addRouters.forEach(async (route) => {
// await router.addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
// })
// const redirectPath = from.query.redirect || to.path
// const redirect = decodeURIComponent(redirectPath as string)
// const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
// permissionStore.setIsAddRouters(true)
// next(nextData)
// })
next()
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
}
}
})
router.afterEach(async (to) => {
useTitle(to?.meta?.title as string)
toggle() // 结束Progress
})

View File

@ -2,16 +2,36 @@ import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import type { App } from 'vue' import type { App } from 'vue'
// import { getParentLayout } from './helper' // import { getParentLayout } from './helper'
import { t } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
/* Layout */
const Layout = () => import('@/layout/Layout.vue')
export const constantRouterMap: AppRouteRecordRaw[] = [ export const constantRouterMap: AppRouteRecordRaw[] = [
{
path: '/redirect',
component: Layout,
name: 'Redirect',
children: [
{
path: '/redirect/:path*',
name: 'Redirect',
component: () => import('@/views/Redirect/Redirect.vue'),
meta: {}
}
],
meta: {
hidden: true
}
},
{ {
path: '/login', path: '/login',
component: () => import('@/views/Login/Login.vue'), component: () => import('@/views/Login/Login.vue'),
name: 'Login', name: 'Login',
meta: { meta: {
hidden: true, hidden: true,
title: t('common.login'), title: t('router.login'),
noTagsView: true noTagsView: true
} }
} }

View File

@ -58,9 +58,6 @@ export const useAppStore = defineStore({
getShowMenuTab(): boolean { getShowMenuTab(): boolean {
return this.showMenuTab return this.showMenuTab
}, },
getRequestTime(): boolean {
return this.requestTime
},
getIsDark(): boolean { getIsDark(): boolean {
return this.isDark return this.isDark
}, },
@ -117,9 +114,6 @@ export const useAppStore = defineStore({
setShowMenuTab(showMenuTab: boolean) { setShowMenuTab(showMenuTab: boolean) {
this.showMenuTab = showMenuTab this.showMenuTab = showMenuTab
}, },
setRequestTime(requestTime: boolean) {
this.requestTime = requestTime
},
setIsDark(isDark: boolean) { setIsDark(isDark: boolean) {
this.isDark = isDark this.isDark = isDark
if (this.isDark) { if (this.isDark) {

View File

@ -5,7 +5,8 @@ import { useI18n } from '@/hooks/web/useI18n'
import { ElButton, ElCheckbox, ElLink } from 'element-plus' import { ElButton, ElCheckbox, ElLink } from 'element-plus'
import { required } from '@/utils/formRules' import { required } from '@/utils/formRules'
import { useForm } from '@/hooks/web/useForm' import { useForm } from '@/hooks/web/useForm'
import { loginApi } from '../api' import { loginApi } from '@/api/login'
import type { UserLoginType } from '@/api/login/types'
const { t } = useI18n() const { t } = useI18n()
@ -87,12 +88,11 @@ async function signIn() {
if (validate) { if (validate) {
loading.value = true loading.value = true
const { getFormData } = methods const { getFormData } = methods
const formData = await getFormData() const formData = (await getFormData()) as UserLoginType
const res = await loginApi({ const res = await loginApi(formData)
data: formData .catch(() => {})
}) .finally(() => (loading.value = false))
console.log(res) console.log(res)
loading.value = false
} }
} }
</script> </script>
@ -118,7 +118,9 @@ async function signIn() {
</template> </template>
<template #login> <template #login>
<ElButton type="primary" class="w-[100%]" @click="signIn">{{ t('login.login') }}</ElButton> <ElButton :loading="loading" type="primary" class="w-[100%]" @click="signIn">
{{ t('login.login') }}
</ElButton>
</template> </template>
<template #otherIcon> <template #otherIcon>

View File

@ -0,0 +1,30 @@
<template>
<div></div>
</template>
<script setup lang="ts">
import { unref } from 'vue'
import { useRouter } from 'vue-router'
const { currentRoute, replace } = useRouter()
const { params, query } = unref(currentRoute)
const { path, _redirect_type = 'path' } = params
Reflect.deleteProperty(params, '_redirect_type')
Reflect.deleteProperty(params, 'path')
const _path = Array.isArray(path) ? path.join('/') : path
if (_redirect_type === 'name') {
replace({
name: _path,
query,
params
})
} else {
replace({
path: _path.startsWith('/') ? _path : '/' + _path,
query
})
}
</script>

18
types/global.d.ts vendored
View File

@ -16,16 +16,20 @@ declare type ComponentRef<T> = InstanceType<T>
declare type LocaleType = 'zh-CN' | 'en' declare type LocaleType = 'zh-CN' | 'en'
declare type AxiosHeaders =
| 'application/json'
| 'application/x-www-form-urlencoded'
| 'multipart/form-data'
declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put'
declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
declare type AxiosConfig = { declare type AxiosConfig = {
params?: Recordable params?: Recordable
data?: Recordable data?: Recordable
url?: string url?: string
method?: 'get' | 'post' | 'delete' | 'put' method?: AxiosMethod
headersType?: string headersType?: string
responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream' responseType?: AxiosResponseType
} }
declare type AxiosHeadersType =
| 'application/json'
| 'application/x-www-form-urlencoded'
| 'multipart/form-data'

View File

@ -22,7 +22,6 @@ function pathResolve(dir: string) {
export default ({ command, mode }: ConfigEnv): UserConfig => { export default ({ command, mode }: ConfigEnv): UserConfig => {
let env = null let env = null
const isBuild = command === 'build' const isBuild = command === 'build'
console.log(isBuild)
if (!isBuild) { if (!isBuild) {
env = loadEnv((process.argv[3] === '--mode' ? process.argv[4] : process.argv[3]), root) env = loadEnv((process.argv[3] === '--mode' ? process.argv[4] : process.argv[3]), root)
} else { } else {