diff --git a/.vscode/settings.json b/.vscode/settings.json index 6ff0621a..1f7c5744 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "editor.bracketPairColorization.enabled": true, "editor.guides.bracketPairs": true, "editor.formatOnSave": true, + "editor.defaultFormatter": "dbaeumer.vscode-eslint", "editor.codeActionsOnSave": [ "source.fixAll.eslint" ], diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 603dfa28..5b47917d 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/package.json b/package.json index 17b24c34..b4e76ddd 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "productName": "leafage-ms", "author": "wq.li ", "private": true, + "type": "module", "scripts": { "lint": "eslint --ext .js,.ts,.vue ./", "test": "echo \"No test specified\" && exit 0", @@ -14,15 +15,15 @@ "dependencies": { "@quasar/extras": "^1.16.11", "axios": "^1.6.8", + "lottie-web": "^5.12.2", "pinia": "^2.1.7", - "quasar": "^2.15.1", + "quasar": "^2.16.4", "vue": "^3.4.21", - "vue-i18n": "^9.11.0", - "vue-router": "^4.3.0" + "vue-i18n": "^9.13.1", + "vue-router": "^4.3.2" }, "devDependencies": { - "@intlify/vite-plugin-vue-i18n": "^7.0.0", - "@quasar/app-vite": "^1.8.0", + "@quasar/app-vite": "^1.9.3", "@types/node": "^20.12.5", "@typescript-eslint/eslint-plugin": "^7.5.0", "@typescript-eslint/parser": "^7.5.0", @@ -33,8 +34,7 @@ "eslint-plugin-n": "^16.6.2", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "^9.24.0", - "lottie-web": "^5.12.2", - "msw": "^2.2.13", + "msw": "^2.3.0", "postcss": "^8.4.38", "typescript": "^5.4.4" }, diff --git a/postcss.config.cjs b/postcss.config.cts similarity index 100% rename from postcss.config.cjs rename to postcss.config.cts diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index 2d139dac..bdfdb11d 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.2.13' +const PACKAGE_VERSION = '2.3.0' const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/quasar.config.js b/quasar.config.cjs similarity index 95% rename from quasar.config.js rename to quasar.config.cjs index 743e175f..b4c42bb0 100644 --- a/quasar.config.js +++ b/quasar.config.cjs @@ -31,7 +31,8 @@ module.exports = configure(function (ctx) { boot: [ 'i18n', 'axios', - 'msw-server' + 'msw-server', + 'router' ], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css @@ -105,14 +106,14 @@ module.exports = configure(function (ctx) { // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer devServer: { // https: true - open: true // opens browser window automatically - // proxy: { - // '^/api': { - // target: 'http://127.0.0.1:8763', - // changeOrigin: true, - // rewrite: (path) => path.replace(/^\/api/, '') - // } - // } + open: true, // opens browser window automatically + proxy: { + '^/api': { + target: 'http://127.0.0.1:8763', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } }, // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework @@ -120,7 +121,7 @@ module.exports = configure(function (ctx) { config: { dark: 'auto', notify: { - position: 'top' + progress: true } }, diff --git a/src/boot/.gitkeep b/src/boot/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/boot/axios.ts b/src/boot/axios.ts index fdda0896..af636dab 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -1,56 +1,41 @@ import { boot } from 'quasar/wrappers' -import axios, { AxiosInstance, AxiosError } from 'axios' +import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' +import { Notify } from 'quasar' -declare module '@vue/runtime-core' { - interface ComponentCustomProperties { - $api: AxiosInstance; - } -} +const abortControllerMap: Map = new Map() -// Be careful when using SSR for cross-request state pollution -// due to creating a Singleton instance here; -// If any client changes this (global) instance, it might be a -// good idea to move this instance creation inside of the -// "export default () => {}" function below (which runs individually -// for each client) -const api = axios.create({ baseURL: process.env.API }) - -export default boot(({ app }) => { - // for use inside Vue files (Options API) through this.$api - - // 创建 AbortController 实例 - const abortController = new AbortController() - - // 获取 AbortController 的信号 - const signal = abortController.signal +const api: AxiosInstance = axios.create({ baseURL: process.env.API, timeout: 10000 }) +export default boot(() => { // 请求拦截器 api.interceptors.request.use( - config => { + (config: InternalAxiosRequestConfig) => { + // 创建 AbortController 实例 + const abortController = new AbortController() // 将 signal 添加到请求配置中 - config.signal = signal + const url = config.url || '' + config.signal = abortController.signal + abortControllerMap.set(url, abortController) return config }, - error => { + (error: AxiosError) => { + Notify.create({ type: 'negative', message: error.message }) return Promise.reject(error) } ) // 响应拦截器 api.interceptors.response.use( - response => { + (response: AxiosResponse) => { + const url = response.config.url || '' + abortControllerMap.delete(url) return response }, (error: AxiosError) => { - // 如果请求失败,取消后续请求 - // abortController.abort() + Notify.create({ type: 'negative', message: error.message }) return Promise.reject(error) } ) - - app.config.globalProperties.$api = api - // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form) - // so you can easily perform requests against your app's API }) export { api } diff --git a/src/boot/i18n.ts b/src/boot/i18n.ts index 6d59bc6e..28082814 100644 --- a/src/boot/i18n.ts +++ b/src/boot/i18n.ts @@ -1,26 +1,7 @@ import { boot } from 'quasar/wrappers' import { createI18n } from 'vue-i18n' - import messages from 'src/i18n' -export type MessageLanguages = keyof typeof messages -// Type-define 'en-US' as the master schema for the resource -export type MessageSchema = typeof messages['en-US'] - -// See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition -/* eslint-disable @typescript-eslint/no-empty-interface */ -declare module 'vue-i18n' { - // define the locale messages schema - export interface DefineLocaleMessage extends MessageSchema { } - - // define the datetime format schema - export interface DefineDateTimeFormat { } - - // define the number format schema - export interface DefineNumberFormat { } -} -/* eslint-enable @typescript-eslint/no-empty-interface */ - export default boot(({ app }) => { const i18n = createI18n({ locale: 'en-US', diff --git a/src/boot/router.ts b/src/boot/router.ts new file mode 100644 index 00000000..1eaf5f57 --- /dev/null +++ b/src/boot/router.ts @@ -0,0 +1,22 @@ +import { boot } from 'quasar/wrappers' +import { useUserStore } from 'stores/user-store' + +export default boot(({ router, store }) => { + router.beforeEach((to, from, next) => { + // Now you need to add your authentication logic here, like calling an API endpoint + const userStore = useUserStore(store) + if (userStore.getUsername) { + if (to.path === '/login') { + next('/') + } else { + next() + } + } else { + if (to.path === '/login') { + next() + } else { + next(`/login?redirect=${to.path}`) + } + } + }) +}) diff --git a/src/components/EssentialLink.vue b/src/components/EssentialLink.vue index 843ef530..02395894 100644 --- a/src/components/EssentialLink.vue +++ b/src/components/EssentialLink.vue @@ -17,6 +17,7 @@ export interface EssentialLinkProps { icon?: string; } withDefaults(defineProps(), { + title: '', link: '#', icon: '' }) diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts index a0db6613..728ecff6 100644 --- a/src/i18n/en-US/index.ts +++ b/src/i18n/en-US/index.ts @@ -26,11 +26,10 @@ export default { no: 'NO.', name: 'Name', - alias: 'Alias', id: 'ID', superior: 'Superior', description: 'Description', - modifyTime: 'Modify Time', + lastModifiedDate: 'Last Modified Date', username: 'Username', password: 'Password', diff --git a/src/i18n/zh-CN/index.ts b/src/i18n/zh-CN/index.ts index b26493de..8c4b2bd6 100644 --- a/src/i18n/zh-CN/index.ts +++ b/src/i18n/zh-CN/index.ts @@ -26,11 +26,10 @@ export default { no: '序号', name: '名称', - alias: '别名', id: '主键', superior: '上级', description: '描述', - modifyTime: '更新时间', + lastModifiedDate: '更新时间', username: '账号', password: '密码', diff --git a/src/i18n/zh-TW/index.ts b/src/i18n/zh-TW/index.ts index 01acffa2..98109fce 100644 --- a/src/i18n/zh-TW/index.ts +++ b/src/i18n/zh-TW/index.ts @@ -26,11 +26,10 @@ export default { no: '序號', name: '名稱', - alias: '別名', id: '主鍵', superior: '上級', description: '描述', - modifyTime: '更新時間', + lastModifiedDate: '更新時間', username: '賬號', password: '密碼', diff --git a/src/layouts/BlankLayout.vue b/src/layouts/BlankLayout.vue deleted file mode 100644 index 5e1bb628..00000000 --- a/src/layouts/BlankLayout.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index fce964ac..4b981881 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -3,11 +3,12 @@ + Management System - + - - - + @@ -35,17 +33,20 @@ + + - - avatar + + avatar {{ userStore.getUsername }} - Sign out + Change Password - - Home + + Sign Out @@ -59,6 +60,7 @@ + @@ -72,15 +74,25 @@ import { ref } from 'vue' import { useI18n } from 'vue-i18n' import { useUserStore } from 'stores/user-store' +import { useRouter } from 'vue-router' +import { useQuasar } from 'quasar' +import { api } from 'boot/axios' + import SideBarLeft from './SideBarLeft.vue' const userStore = useUserStore() +const { replace } = useRouter() +const $q = useQuasar() const { locale } = useI18n({ useScope: 'global' }) -const leftDrawerOpen = ref(false) +const leftDrawerOpen = ref(false) + +function onLogout() { + api.post('/logout').then(() => { + userStore.clearUser() -function toggleLeftDrawer() { - leftDrawerOpen.value = !leftDrawerOpen.value + replace('/login') + }).catch(error => $q.notify({ type: 'negative', message: error.message })) } diff --git a/src/layouts/SideBarLeft.vue b/src/layouts/SideBarLeft.vue index ee448198..c7588ec1 100644 --- a/src/layouts/SideBarLeft.vue +++ b/src/layouts/SideBarLeft.vue @@ -1,12 +1,6 @@ - - diff --git a/src/pages/ErrorNotFound.vue b/src/pages/ErrorNotFound.vue index f4e0bb84..97530b77 100644 --- a/src/pages/ErrorNotFound.vue +++ b/src/pages/ErrorNotFound.vue @@ -9,15 +9,8 @@ Oops. Nothing here... - + diff --git a/src/pages/IndexPage.vue b/src/pages/IndexPage.vue index 0bb3c851..3edb498a 100644 --- a/src/pages/IndexPage.vue +++ b/src/pages/IndexPage.vue @@ -5,6 +5,3 @@ - - diff --git a/src/pages/LoginPage.vue b/src/pages/LoginPage.vue index 5281b127..46d3f4de 100644 --- a/src/pages/LoginPage.vue +++ b/src/pages/LoginPage.vue @@ -3,10 +3,10 @@ - + - + @@ -27,81 +27,70 @@ - +
+ style="height: 35em; width: 35em; top: -19em; right: -14em; background-color: var('$primary');" />
+ style="height: 21em; width: 21em; bottom: 4em; right: -7em; background-color: var('$positive');" />
+ style="height: 42em; width: 42em; bottom: -19em; left: -14em; background-color: var('$warning');" />
+ style="height: 21em; width: 21em; bottom: -16em; left: 14em; background-color: var('$negative');" /> - - -
-
-
- - {{ $t('welcome') }} - - - {{ $t('subtitle') }} - -
+ +
+
+
+ + {{ $t('welcome') }} + + + {{ $t('subtitle') }} +
- - +
+ - - -
- - - -
- -
-
- {{ $t('signinTo') }} -
- - - - - - - - -
-
-
+ :style="{ backgroundColor: $q.dark.isActive ? '' : '#f3fbff' }"> + +
+
+
- - +
+ {{ $t('signinTo') }} +
+ + + + + + + + +
+
- - + +

Copyright © 2018 - {{ new Date().getFullYear() }} leafage.top All rights reserved.

@@ -110,7 +99,7 @@