diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..89450d6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2024 little3201 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/assets/bg.json b/public/bg.json similarity index 100% rename from src/assets/bg.json rename to public/bg.json diff --git a/quasar.config.js b/quasar.config.js index e55cf08..b66aba7 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -127,7 +127,7 @@ module.exports = configure(function (ctx) { // directives: [], // Quasar plugins - plugins: [] + plugins: ['Notify', 'SessionStorage'] }, // animations: 'all', // --- includes all animations diff --git a/src/boot/axios.ts b/src/boot/axios.ts index 468ea0f..27b52e1 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -1,5 +1,5 @@ import { boot } from 'quasar/wrappers' -import axios, { AxiosInstance } from 'axios' +import axios, { AxiosInstance, AxiosError } from 'axios' declare module '@vue/runtime-core' { interface ComponentCustomProperties { @@ -13,11 +13,41 @@ declare module '@vue/runtime-core' { // 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: '/api' }) +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 + + // 请求拦截器 + api.interceptors.request.use( + config => { + // 将 signal 添加到请求配置中 + config.signal = signal + return config + }, + error => { + return Promise.reject(error) + } + ) + + // 响应拦截器 + api.interceptors.response.use( + response => { + return response + }, + (error: AxiosError) => { + // 如果请求失败,取消后续请求 + abortController.abort() + 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 diff --git a/src/components/EssentialLink.vue b/src/components/EssentialLink.vue index 46ec9de..843ef53 100644 --- a/src/components/EssentialLink.vue +++ b/src/components/EssentialLink.vue @@ -5,7 +5,7 @@ </q-item-section> <q-item-section> - <q-item-label>{{ title }}</q-item-label> + <q-item-label>{{ $t(title) }}</q-item-label> </q-item-section> </q-item> </template> diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts index 19105c5..2df7f14 100644 --- a/src/i18n/en-US/index.ts +++ b/src/i18n/en-US/index.ts @@ -81,6 +81,7 @@ export default { grant: 'Grants', members: 'memberss', + home: 'Home', dashboard: 'Dashboard', system: 'System', groups: 'Groups', diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 26e966b..63b26bd 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,9 +1,9 @@ +import enUS from './en-US' import zhCN from './zh-CN' import zhTW from './zh-TW' -import enUS from './en-US' export default { + 'en-US': enUS, 'zh-CN': zhCN, - 'zh-TW': zhTW, - 'en-US': enUS + 'zh-TW': zhTW } diff --git a/src/i18n/zh-CN/index.ts b/src/i18n/zh-CN/index.ts index a2efd13..7bdadae 100644 --- a/src/i18n/zh-CN/index.ts +++ b/src/i18n/zh-CN/index.ts @@ -81,6 +81,7 @@ export default { grant: '授权', members: '成员', + home: '首页', dashboard: '控制台', system: '系统管理', groups: '分组', diff --git a/src/i18n/zh-TW/index.ts b/src/i18n/zh-TW/index.ts index e281c9a..938bfc8 100644 --- a/src/i18n/zh-TW/index.ts +++ b/src/i18n/zh-TW/index.ts @@ -81,6 +81,7 @@ export default { grant: '授權', members: '成員', + home: '首頁', dashboard: '控制台', system: '系統管理', groups: '分組', diff --git a/src/layouts/BlankLayout.vue b/src/layouts/BlankLayout.vue new file mode 100644 index 0000000..5e1bb62 --- /dev/null +++ b/src/layouts/BlankLayout.vue @@ -0,0 +1,3 @@ +<template> + <router-view /> +</template> diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index ccd95fd..f09aeba 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -1,29 +1,52 @@ <template> <q-layout view="hHh LpR lFf"> - <q-header elevated> <q-toolbar> - <q-btn id="btn-drawer" title="btn-drawer" dense flat round icon="sym_r_menu" @click="toggleLeftDrawer" - aria-disabled="false" /> <q-toolbar-title :shrink="true"> - <q-img alt="logo" src="/logo-only.svg" style="width: 46px; height: 46px;" /> - {{ $t('Application') }} + <q-img alt="logo" src="/logo-only.svg" style="width: 38px; height: 38px;" /> </q-toolbar-title> + <q-toolbar-title :shrink="true"> + <span>Management System</span> + </q-toolbar-title> + <q-btn id="btn-drawer" title="btn-drawer" dense flat round icon="sym_r_menu" @click="toggleLeftDrawer" + aria-disabled="false" /> - <q-separator color="white" vertical inset /> - - <q-breadcrumbs class="text-grey q-ma-sm" active-color="white" style="font-size: 16px"> - <q-breadcrumbs-el label="Home" icon="sym_r_home" /> - <q-breadcrumbs-el label="Components" icon="sym_r_widgets" /> - <q-breadcrumbs-el label="Breadcrumbs" /> + <q-breadcrumbs class="q-ma-sm" active-color="white" style="font-size: 16px"> + <q-breadcrumbs-el v-for="(route, index) in $route.matched" :key="index" + :label="$t(route.name ? route.name.toString() : '')" + :icon="route.meta.icon ? route.meta.icon.toString() : undefined" /> </q-breadcrumbs> <q-space /> + <q-toolbar-title :shrink="true"> + <q-btn icon="sym_r_language" round flat> + <q-menu> + <q-list dense separator> + <q-item clickable v-close-popup :active="locale === 'en-US'" @click="locale = 'en-US'"> + <q-item-section>English(US)</q-item-section> + </q-item> + <q-item clickable v-close-popup :active="locale === 'zh-CN'" @click="locale = 'zh-CN'"> + <q-item-section>中文(简体)</q-item-section> + </q-item> + <q-item clickable v-close-popup :active="locale === 'zh-TW'" @click="locale = 'zh-TW'"> + <q-item-section>中文(繁體)</q-item-section> + </q-item> + </q-list> + </q-menu> + </q-btn> + + <q-toggle v-model="darkTheme" icon="sym_r_dark_mode" unchecked-icon="sym_r_light_mode" + :color="darkTheme ? 'black' : ''" /> + + <q-btn flat dense round icon="sym_r_notifications" class="q-mr-md"> + <q-badge floating color="red" rounded style="top: 0px; right: 0px;" /> + </q-btn> <q-avatar size="md"> <img alt="avatar" src="https://cdn.quasar.dev/img/avatar.png"> </q-avatar> + <span class="q-ml-sm" style="font-size: 16px;">{{ userStore.getUsername }}</span> </q-toolbar-title> </q-toolbar> @@ -50,11 +73,18 @@ <script setup lang="ts"> import { ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { useUserStore } from 'stores/user-store' import SideBarLeft from './SideBarLeft.vue' +const userStore = useUserStore() + +const { locale } = useI18n({ useScope: 'global' }) +const darkTheme = ref(false) const leftDrawerOpen = ref(false) function toggleLeftDrawer() { leftDrawerOpen.value = !leftDrawerOpen.value } </script> +src/stores/user-store diff --git a/src/layouts/SideBarLeft.vue b/src/layouts/SideBarLeft.vue index 4574eea..ddb6194 100644 --- a/src/layouts/SideBarLeft.vue +++ b/src/layouts/SideBarLeft.vue @@ -2,17 +2,17 @@ <q-list> <q-list> <EssentialLink v-bind="{ - title: 'Dashboard', + title: 'dashboard', icon: 'sym_r_dashboard', link: '/dashboard' }" /> <EssentialLink v-bind="{ - title: 'Home', - icon: 'sym_r_dashboard', + title: 'home', + icon: 'sym_r_home', link: '/' }" /> - <q-expansion-item expand-separator icon="sym_r_settings" label="System" default-opened> + <q-expansion-item expand-separator icon="sym_r_settings" :label="$t('system')" default-opened> <q-card> <q-card-section> <EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" /> @@ -29,29 +29,29 @@ import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue' const essentialLinks: EssentialLinkProps[] = [ { - title: 'User', + title: 'users', icon: 'sym_r_manage_accounts', - link: '/system/user' + link: '/system/users' }, { - title: 'Group', + title: 'groups', icon: 'sym_r_group', - link: '/system/group' + link: '/system/groups' }, { - title: 'Role', + title: 'roles', icon: 'sym_r_admin_panel_settings', - link: '/system/role' + link: '/system/roles' }, { - title: 'Dictionary', + title: 'dictionaries', icon: 'sym_r_library_books', - link: '/system/dictionary' + link: '/system/dictionaries' }, { - title: 'Region', + title: 'regions', icon: 'sym_r_public', - link: '/system/region' + link: '/system/regions' } ] </script> diff --git a/src/pages/DashboardIndex.vue b/src/pages/DashboardPage.vue similarity index 100% rename from src/pages/DashboardIndex.vue rename to src/pages/DashboardPage.vue diff --git a/src/pages/LoginPage.vue b/src/pages/LoginPage.vue index 39ea517..77d4916 100644 --- a/src/pages/LoginPage.vue +++ b/src/pages/LoginPage.vue @@ -10,17 +10,15 @@ <q-toolbar-title :shrink="true"> <q-btn icon="sym_r_language" round flat> <q-menu> - <q-list dense> - <q-item clickable v-close-popup> - <q-item-section>简体中文</q-item-section> + <q-list dense separator> + <q-item clickable v-close-popup :active="locale === 'en-US'" @click="locale = 'en-US'"> + <q-item-section>English(US)</q-item-section> </q-item> - <q-separator /> - <q-item clickable v-close-popup> - <q-item-section>繁體中文</q-item-section> + <q-item clickable v-close-popup :active="locale === 'zh-CN'" @click="locale = 'zh-CN'"> + <q-item-section>中文(简体)</q-item-section> </q-item> - <q-separator /> - <q-item clickable v-close-popup> - <q-item-section>English(US)</q-item-section> + <q-item clickable v-close-popup :active="locale === 'zh-TW'" @click="locale = 'zh-TW'"> + <q-item-section>中文(繁體)</q-item-section> </q-item> </q-list> </q-menu> @@ -105,10 +103,10 @@ {{ $t('signinTo') }} </div> <q-form @submit="onSubmit" class="q-mt-md"> - <q-input :disable="loading" dense no-error-icon v-model.trim="form.username" color="black" + <q-input :disable="loading" dense no-error-icon v-model.trim="form.username" :placeholder="$t('username')" :rules="[(val) => (val && val.length > 0) || $t('username')]" /> <q-input :disable="loading" dense no-error-icon :type="isPwd ? 'password' : 'text'" - v-model.trim="form.password" :placeholder="$t('password')" color="black" + v-model.trim="form.password" :placeholder="$t('password')" :rules="[(val) => (val && val.length > 0) || $t('password')]"> <template v-slot:append> <q-icon size="xs" :name="isPwd ? 'sym_r_visibility_off' : 'sym_r_visibility'" @@ -138,46 +136,71 @@ </q-layout> </template> -<script setup> +<script setup lang="ts"> import { onBeforeMount, ref, onMounted } from 'vue' -// import { useQuasar } from 'quasar' +import { useI18n } from 'vue-i18n' +import { useRouter } from 'vue-router' import lottie from 'lottie-web' +import { api } from 'boot/axios' +import { useUserStore } from 'stores/user-store' -// const $q = useQuasar() +const router = useRouter() +const userStore = useUserStore() onBeforeMount(() => { }) + +const { locale } = useI18n({ useScope: 'global' }) const isPwd = ref(true) const rememberMe = ref(true) +const darkTheme = ref(false) +const loading = ref(false) +const lottieRef = ref<HTMLDivElement | null>(null) + const form = ref({ username: '', - password: '', - captcha: '', - captcha_id: '' + password: '' }) -const loading = ref(false) -const changeRememberMe = (value) => { + +onMounted(() => { + show() +}) + +function changeRememberMe(value: boolean) { return value } -const openLink = (url) => { +function openLink(url: string) { window.open(url) } -const darkTheme = ref(false) -const onSubmit = async () => { } +function onSubmit() { + loading.value = true -const lottieRef = ref(null) -onMounted(() => { - show() -}) -const show = () => { - lottie.loadAnimation({ - container: lottieRef.value, - renderer: 'svg', - loop: true, - autoplay: true, - path: 'src/assets/bg.json' + api.post('/login', new URLSearchParams(form.value)).then(res => { + console.log(res.data) + userStore.updateUser(form.value.username) + // 获取之前路由 + const redirectRoute = router.currentRoute.value.query.redirect as string | undefined + router.replace(redirectRoute || '/') + }).catch(error => { + console.error('Request failed:', error.message) + }).finally(() => { + // 在请求结束后执行 + loading.value = false }) } + +function show() { + if (lottieRef.value) { + lottie.loadAnimation({ + container: lottieRef.value, + renderer: 'svg', + loop: true, + autoplay: true, + path: '/bg.json' + }) + } +} </script> +src/stores/user-store diff --git a/src/pages/system/dictionary/IndexPage.vue b/src/pages/system/dictionary/IndexPage.vue index 06ed257..c712698 100644 --- a/src/pages/system/dictionary/IndexPage.vue +++ b/src/pages/system/dictionary/IndexPage.vue @@ -53,11 +53,10 @@ import { ref, onMounted } from 'vue' import { exportFile, useQuasar, date } from 'quasar' import type { QTableProps } from 'quasar' - -import type { Dictionary } from 'src/api/models.type' - import { api } from 'boot/axios' + import { SERVER_URL } from 'src/api/paths' +import type { Dictionary } from 'src/api/models.type' const $q = useQuasar() @@ -102,7 +101,8 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> const { page, rowsPerPage, sortBy, descending } = props.pagination const params = { page: page - 1, size: rowsPerPage } - await api.get(SERVER_URL.DICTIONARY, { params }).then(res => { + try { + const res = await api.get(SERVER_URL.DICTIONARY, { params }) rows.value = res.data.content pagination.value.page = page pagination.value.sortBy = sortBy @@ -110,10 +110,11 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> pagination.value.rowsPerPage = rowsPerPage pagination.value.sortBy = res.data.sortBy pagination.value.descending = descending - }).catch(error => console.log(error)) - .finally(function () { - loading.value = false - }) + } catch (error) { + console.log(error) + } finally { + loading.value = false + } } function addRow() { diff --git a/src/pages/system/group/IndexPage.vue b/src/pages/system/group/IndexPage.vue index d58e902..f764850 100644 --- a/src/pages/system/group/IndexPage.vue +++ b/src/pages/system/group/IndexPage.vue @@ -62,11 +62,10 @@ import { ref, onMounted } from 'vue' import type { QTableProps } from 'quasar' import { exportFile, useQuasar, date } from 'quasar' - -import type { Group } from 'src/api/models.type' - import { api } from 'boot/axios' + import { SERVER_URL } from 'src/api/paths' +import type { Group } from 'src/api/models.type' const $q = useQuasar() @@ -112,7 +111,8 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> const { page, rowsPerPage, sortBy, descending } = props.pagination const params = { page: page - 1, size: rowsPerPage } - await api.get(SERVER_URL.GROUP, { params }).then(res => { + try { + const res = await api.get(SERVER_URL.GROUP, { params }) rows.value = res.data.content pagination.value.page = page pagination.value.sortBy = sortBy @@ -120,10 +120,11 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> pagination.value.rowsPerPage = rowsPerPage pagination.value.sortBy = res.data.sortBy pagination.value.descending = descending - }).catch(error => console.log(error)) - .finally(function () { - loading.value = false - }) + } catch (error) { + console.log(error) + } finally { + loading.value = false + } } function addRow() { diff --git a/src/pages/system/region/IndexPage.vue b/src/pages/system/region/IndexPage.vue index e4f4dbb..c7433d0 100644 --- a/src/pages/system/region/IndexPage.vue +++ b/src/pages/system/region/IndexPage.vue @@ -53,11 +53,10 @@ import { ref, onMounted } from 'vue' import type { QTableProps } from 'quasar' import { exportFile, useQuasar, date } from 'quasar' - -import type { Region } from 'src/api/models.type' - import { api } from 'boot/axios' + import { SERVER_URL } from 'src/api/paths' +import type { Region } from 'src/api/models.type' const $q = useQuasar() @@ -104,7 +103,8 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> const { page, rowsPerPage, sortBy, descending } = props.pagination const params = { page: page - 1, size: rowsPerPage } - await api.get(SERVER_URL.REGION, { params }).then(res => { + try { + const res = await api.get(SERVER_URL.REGION, { params }) rows.value = res.data.content pagination.value.page = page pagination.value.sortBy = sortBy @@ -112,10 +112,11 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> pagination.value.rowsPerPage = rowsPerPage pagination.value.sortBy = res.data.sortBy pagination.value.descending = descending - }).catch(error => console.log(error)) - .finally(function () { - loading.value = false - }) + } catch (error) { + console.log(error) + } finally { + loading.value = false + } } function addRow() { diff --git a/src/pages/system/role/IndexPage.vue b/src/pages/system/role/IndexPage.vue index 7b37b7a..d9e0e1d 100644 --- a/src/pages/system/role/IndexPage.vue +++ b/src/pages/system/role/IndexPage.vue @@ -62,11 +62,10 @@ import { ref, onMounted } from 'vue' import { exportFile, useQuasar, date } from 'quasar' import type { QTableProps } from 'quasar' - -import type { Role } from 'src/api/models.type' - import { api } from 'boot/axios' + import { SERVER_URL } from 'src/api/paths' +import type { Role } from 'src/api/models.type' const $q = useQuasar() @@ -112,7 +111,8 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> const { page, rowsPerPage, sortBy, descending } = props.pagination const params = { page: page - 1, size: rowsPerPage } - await api.get(SERVER_URL.ROLE, { params }).then(res => { + try { + const res = await api.get(SERVER_URL.ROLE, { params }) rows.value = res.data.content pagination.value.page = page pagination.value.sortBy = sortBy @@ -120,10 +120,11 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> pagination.value.rowsPerPage = rowsPerPage pagination.value.sortBy = res.data.sortBy pagination.value.descending = descending - }).catch(error => console.log(error)) - .finally(function () { - loading.value = false - }) + } catch (error) { + console.log(error) + } finally { + loading.value = false + } } function addRow() { diff --git a/src/pages/system/user/IndexPage.vue b/src/pages/system/user/IndexPage.vue index 66c96bd..07a123a 100644 --- a/src/pages/system/user/IndexPage.vue +++ b/src/pages/system/user/IndexPage.vue @@ -73,11 +73,10 @@ import { ref, onMounted } from 'vue' import type { QTableProps } from 'quasar' import { exportFile, useQuasar, date } from 'quasar' - -import type { User } from 'src/api/models.type' - import { api } from 'boot/axios' + import { SERVER_URL } from 'src/api/paths' +import type { User } from 'src/api/models.type' const $q = useQuasar() @@ -128,7 +127,9 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> const { page, rowsPerPage, sortBy, descending } = props.pagination const params = { page: page - 1, size: rowsPerPage } - await api.get(SERVER_URL.USER, { params }).then(res => { + + try { + const res = await api.get(SERVER_URL.USER, { params }) rows.value = res.data.content pagination.value.page = page pagination.value.sortBy = sortBy @@ -136,10 +137,11 @@ async function onRequest(props: Parameters<NonNullable<QTableProps['onRequest']> pagination.value.rowsPerPage = rowsPerPage pagination.value.sortBy = res.data.sortBy pagination.value.descending = descending - }).catch(error => console.log(error)) - .finally(() => { - loading.value = false - }) + } catch (error) { + console.log(error) + } finally { + loading.value = false + } } function addRow() { diff --git a/src/router/routes.ts b/src/router/routes.ts index 0f5a0c3..eb86d0c 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -3,19 +3,59 @@ import { RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', + name: 'application', component: () => import('layouts/MainLayout.vue'), + meta: { icon: 'sym_r_grid_view' }, children: [ - { path: '', component: () => import('pages/IndexPage.vue') }, - { path: 'dashboard', component: () => import('pages/DashboardIndex.vue') }, + { + path: '', + name: 'home', + component: () => import('pages/IndexPage.vue'), + meta: { icon: 'sym_r_home' } + }, + { + path: 'dashboard', + name: 'dashboard', + component: () => import('pages/DashboardPage.vue'), + meta: { icon: 'sym_r_dashboard_customize' } + }, { path: 'system', - redirect: { path: 'user' }, + name: 'system', + component: () => import('layouts/BlankLayout.vue'), + meta: { icon: 'sym_r_settings' }, + redirect: { path: 'users' }, children: [ - { path: 'user', component: () => import('pages/system/user/IndexPage.vue') }, - { path: 'group', component: () => import('pages/system/group/IndexPage.vue') }, - { path: 'role', component: () => import('pages/system/role/IndexPage.vue') }, - { path: 'dictionary', component: () => import('pages/system/dictionary/IndexPage.vue') }, - { path: 'region', component: () => import('pages/system/region/IndexPage.vue') } + { + path: 'users', + name: 'users', + component: () => import('pages/system/user/IndexPage.vue'), + meta: { icon: 'sym_r_manage_accounts' } + }, + { + path: 'groups', + name: 'groups', + component: () => import('pages/system/group/IndexPage.vue'), + meta: { icon: 'sym_r_group' } + }, + { + path: 'roles', + name: 'roles', + component: () => import('pages/system/role/IndexPage.vue'), + meta: { icon: 'sym_r_admin_panel_settings' } + }, + { + path: 'dictionaries', + name: 'dictionaries', + component: () => import('pages/system/dictionary/IndexPage.vue'), + meta: { icon: 'sym_r_menu_book' } + }, + { + path: 'regions', + name: 'regions', + component: () => import('pages/system/region/IndexPage.vue'), + meta: { icon: 'sym_r_public' } + } ] } ] diff --git a/src/stores/example-store.ts b/src/stores/example-store.ts deleted file mode 100644 index b1543ca..0000000 --- a/src/stores/example-store.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineStore } from 'pinia' - -export const useCounterStore = defineStore('counter', { - state: () => ({ - counter: 0 - }), - getters: { - doubleCount: (state) => state.counter * 2 - }, - actions: { - increment() { - this.counter++ - } - } -}) diff --git a/src/stores/user-store.ts b/src/stores/user-store.ts new file mode 100644 index 0000000..ca59622 --- /dev/null +++ b/src/stores/user-store.ts @@ -0,0 +1,34 @@ +import { defineStore } from 'pinia' +import { SessionStorage } from 'quasar' + +interface User { + username: string; +} + +export const useUserStore = defineStore('user', { + state: () => ({ + isLoggedIn: false, + user: null as User | null + }), + getters: { + getUsername(): string | null { + const user = JSON.parse(SessionStorage.getItem('user') || '{}') as User | null + return this.user ? this.user.username : (user ? user.username : null) + } + }, + actions: { + updateUser(username: string) { + // 更新用户状态 + this.isLoggedIn = true + this.user = { username } + SessionStorage.set('user', JSON.stringify(this.user)) + }, + + clearUser() { + // 清除用户状态 + this.isLoggedIn = false + this.user = null + SessionStorage.remove('user') + } + } +})