feat: 提交
|
@ -73,6 +73,8 @@ export default defineConfigWithVueTs(
|
|||
|
||||
// allow debugger during development only
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
// 关闭驼峰命名规则
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -55,9 +55,11 @@
|
|||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"globals": "^15.12.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "~5.5.3",
|
||||
"vite-plugin-checker": "^0.8.0",
|
||||
"vite-plugin-mock": "^3.0.2",
|
||||
"vue-tsc": "^2.0.29"
|
||||
},
|
||||
"engines": {
|
||||
|
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 859 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
@ -12,7 +12,7 @@ export default defineConfig((ctx) => {
|
|||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: ['i18n', 'axios'],
|
||||
boot: ['i18n', 'axios', 'mock'],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css
|
||||
css: ['app.scss'],
|
||||
|
@ -93,13 +93,29 @@ export default defineConfig((ctx) => {
|
|||
},
|
||||
{ server: false },
|
||||
],
|
||||
|
||||
// 配置 vite-plugin-mock 插件
|
||||
[
|
||||
'vite-plugin-mock',
|
||||
{
|
||||
mockPath: './src/mock', // 指定 mock 文件目录
|
||||
localEnabled: true, // 开发环境启用 mock
|
||||
prodEnabled: false, // 生产环境禁用 mock
|
||||
injectCode: `
|
||||
import { setupProdMockServer } from './mockProdServer';
|
||||
setupProdMockServer();
|
||||
`,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver
|
||||
devServer: {
|
||||
// https: true,
|
||||
open: true, // opens browser window automatically
|
||||
open: false, // opens browser window automatically
|
||||
allowedHosts: ['fire.lexcubia.com'],
|
||||
port: 22222,
|
||||
},
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework
|
||||
|
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
@ -21,7 +21,7 @@ declare module 'vue-i18n' {
|
|||
}
|
||||
/* eslint-enable @typescript-eslint/no-empty-object-type */
|
||||
|
||||
import { useSettingStore } from 'src/stores/setting-store' // 导入 setting-store
|
||||
import { useSettingStore } from 'src/stores/setting' // 导入 setting-store
|
||||
|
||||
export default defineBoot(({ app }) => {
|
||||
const settingStore = useSettingStore() // 初始化 setting-store
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// mock--start
|
||||
import { setupProdMockServer } from 'mock/mockProdServer'
|
||||
// if (process.env.NODE_ENV === 'production') {
|
||||
setupProdMockServer()
|
||||
// }
|
||||
// mock--end
|
|
@ -0,0 +1,33 @@
|
|||
export const ThemeStyleQuasar = {
|
||||
primary: '#1976D2',
|
||||
secondary: '#26A69A',
|
||||
accent: '#9C27B0',
|
||||
positive: '#21BA45',
|
||||
negative: '#C10015',
|
||||
info: '#31CCEC',
|
||||
warning: '#F2C037',
|
||||
light: '#FFFFFF',
|
||||
dark: '#1D1D1D',
|
||||
}
|
||||
export const ThemeStyleElement = {
|
||||
primary: '#409EFF',
|
||||
secondary: '#26A69A',
|
||||
accent: '#9C27B0',
|
||||
positive: '#67C23A',
|
||||
negative: '#F56C6C',
|
||||
info: '#8896b3',
|
||||
warning: '#e6a23c',
|
||||
light: '#FFFFFF',
|
||||
dark: '#1D1D1D',
|
||||
}
|
||||
export const ThemeStyleAnt = {
|
||||
primary: '#1677ff',
|
||||
secondary: '#26A69A',
|
||||
accent: '#9C27B0',
|
||||
positive: '#52c41a',
|
||||
negative: '#f5222d',
|
||||
info: '#fafafa',
|
||||
warning: '#faad14',
|
||||
light: '#FFFFFF',
|
||||
dark: '#141414',
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<router-view />
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<q-layout>
|
||||
<q-page-container> 这里是登录页面(TODO) </q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
0
src/layouts/ContainerLayout.vue → src/layouts/MainLayout/ContainerLayout.vue
Normal file → Executable file
0
src/layouts/FooterLayout.vue → src/layouts/MainLayout/FooterLayout.vue
Normal file → Executable file
0
src/layouts/HeaderLayout.vue → src/layouts/MainLayout/HeaderLayout.vue
Normal file → Executable file
7
src/layouts/LeftDrawerLayout.vue → src/layouts/MainLayout/LeftDrawerLayout.vue
Normal file → Executable file
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<q-drawer
|
||||
show-if-above
|
||||
v-model="settingStore.leftDrawerOpen"
|
||||
side="left"
|
||||
elevated
|
||||
>
|
||||
<q-drawer v-model="settingStore.leftDrawerOpen" side="left" elevated>
|
||||
<!-- 左侧抽屉菜单 -->
|
||||
左栏
|
||||
</q-drawer>
|
7
src/layouts/MainLayout.vue → src/layouts/MainLayout/MainLayoutIndex.vue
Normal file → Executable file
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<q-layout view="hHh lpR lff">
|
||||
<q-layout :view="settingStore.layout">
|
||||
<HeaderLayout />
|
||||
<TabsLayout />
|
||||
<LeftDrawerLayout />
|
||||
|
@ -16,4 +16,7 @@ import LeftDrawerLayout from './LeftDrawerLayout.vue'
|
|||
import RightDrawerLayout from './RightDrawerLayout.vue'
|
||||
import FooterLayout from './FooterLayout.vue'
|
||||
import ContainerLayout from './ContainerLayout.vue'
|
||||
</script>
|
||||
import { useSettingStore } from 'stores/setting'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
</script>
|
7
src/layouts/RightDrawerLayout.vue → src/layouts/MainLayout/RightDrawerLayout.vue
Normal file → Executable file
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<q-drawer
|
||||
show-if-above
|
||||
v-model="settingStore.rightDrawerOpen"
|
||||
side="right"
|
||||
elevated
|
||||
>
|
||||
<q-drawer v-model="settingStore.rightDrawerOpen" side="right" elevated>
|
||||
<!-- 右侧抽屉内容 -->
|
||||
右栏
|
||||
</q-drawer>
|
|
@ -0,0 +1,16 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||
|
||||
// 定义 mock 接口列表
|
||||
const mockModules: any[] = []
|
||||
|
||||
// 自动加载 mock 文件夹中的所有 mock 数据
|
||||
const modules = import.meta.glob('./modules/*.ts', { eager: true })
|
||||
Object.keys(modules).forEach((key) => {
|
||||
mockModules.push(...(modules[key] as any).default)
|
||||
})
|
||||
|
||||
// 生产环境 mock 服务初始化方法
|
||||
export function setupProdMockServer() {
|
||||
createProdMockServer(mockModules)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export interface Theme {
|
||||
accent: string
|
||||
dark: string
|
||||
info: string
|
||||
light: string
|
||||
negative: string
|
||||
positive: string
|
||||
primary: string
|
||||
secondary: string
|
||||
warning: string
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<q-page class="row items-center justify-evenly"> 初始化页面 </q-page>
|
||||
<q-page class="row items-center justify-evenly"> 初始化 </q-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div>Page 1 Content</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// 无额外逻辑
|
||||
</script>
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div>Page 3 Content</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// 无额外逻辑
|
||||
</script>
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div>Page 2 Content</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// 无额外逻辑
|
||||
</script>
|
|
@ -1,18 +1,39 @@
|
|||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import LoginLayout from 'layouts/LoginLayout/LoginLayoutIndex.vue';
|
||||
import MainLayout from 'layouts/MainLayout/MainLayoutIndex.vue';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => LoginLayout,
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('layouts/MainLayout.vue'),
|
||||
component: () => MainLayout,
|
||||
children: [{ path: '', component: () => import('pages/IndexPage.vue') }],
|
||||
},
|
||||
|
||||
{
|
||||
path: '/pageOne',
|
||||
component: () => MainLayout,
|
||||
children: [{ path: '', component: () => import('pages/PageOne.vue') }],
|
||||
},
|
||||
{
|
||||
path: '/pageTwo',
|
||||
component: () => MainLayout,
|
||||
children: [{ path: '', component: () => import('pages/PageTwo.vue') }],
|
||||
},
|
||||
{
|
||||
path: '/pageThree',
|
||||
component: () => MainLayout,
|
||||
children: [{ path: '', component: () => import('pages/PageThree.vue') }],
|
||||
},
|
||||
// Always leave this as last one,
|
||||
// but you can also remove it
|
||||
{
|
||||
path: '/:catchAll(.*)*',
|
||||
component: () => import('pages/ErrorNotFound.vue'),
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
export default routes
|
||||
export default routes;
|
|
@ -2,9 +2,10 @@ import { defineStore, acceptHMRUpdate } from 'pinia'
|
|||
|
||||
export const useSettingStore = defineStore('setting', {
|
||||
state: () => ({
|
||||
layout: 'hHh lpR lff',
|
||||
theme: 'light',
|
||||
language: 'en-US',
|
||||
leftDrawerOpen: false,
|
||||
leftDrawerOpen: true,
|
||||
rightDrawerOpen: false,
|
||||
}),
|
||||
getters: {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { usePermissionStore } from './permission';
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
export const useTabMenuStore = defineStore('tabMenu', {
|
||||
state: () => ({
|
||||
tabMenus: [],
|
||||
currentTab: {},
|
||||
}),
|
||||
getters: {
|
||||
base() {
|
||||
return permissionStore.userMenu.filter(item => item.name === permissionStore.defaultPage[0])[0]
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
AddTabMenu(tab) {
|
||||
// When exiting, the userMenu is cleared
|
||||
// pass it
|
||||
if (this.base) {
|
||||
// If there is no default page, add it
|
||||
if (this.tabMenus.filter(item => item.path === this.base.path).length === 0) {
|
||||
this.tabMenus = this.tabMenus.concat([this.base])
|
||||
this.currentTab = this.base
|
||||
}
|
||||
// To determine whether a tab exists, the tab will not be passed when all menus are closed
|
||||
if (tab && !this.tabMenus.some(item => item.path === tab.path)) {
|
||||
this.tabMenus = this.tabMenus.concat([tab])
|
||||
this.currentTab = tab
|
||||
}
|
||||
}
|
||||
},
|
||||
ChangeCurrentTab(tab) {
|
||||
this.currentTab = tab
|
||||
},
|
||||
RemoveTab(tab) {
|
||||
const removeIndex = this.tabMenus.indexOf(tab)
|
||||
this.tabMenus = this.tabMenus.filter(item => item.path !== tab.path)
|
||||
this.currentTab = this.tabMenus[removeIndex - 1]
|
||||
},
|
||||
RemoveRightTab(tab) {
|
||||
const removeIndex = this.tabMenus.indexOf(tab)
|
||||
this.tabMenus = this.tabMenus.slice(0, removeIndex + 1)
|
||||
},
|
||||
RemoveLeftTab(tab) {
|
||||
const removeIndex = this.tabMenus.indexOf(tab)
|
||||
const rightMenu = this.tabMenus.slice(removeIndex)
|
||||
this.tabMenus = [this.base].concat(rightMenu)
|
||||
},
|
||||
DestroyTabMenu() {
|
||||
this.currentTab = ''
|
||||
this.tabMenus = []
|
||||
}
|
||||
},
|
||||
});
|
|
@ -0,0 +1,113 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { Cookies, SessionStorage } from 'quasar';
|
||||
import { usePermissionStore } from './permission';
|
||||
import { postAction } from 'src/api/manage';
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: undefined,
|
||||
username: undefined,
|
||||
nickname: undefined,
|
||||
realName: undefined,
|
||||
avatar: undefined,
|
||||
rememberMe: true,
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
async HandleLogin(loginForm) {
|
||||
const res = await postAction('public/login', loginForm)
|
||||
if (res.code === 1) {
|
||||
const token = res.data.token
|
||||
const username = res.data.username
|
||||
const nickname = res.data.nickname
|
||||
const realName = res.data.real_name
|
||||
const avatar = res.data.avatar
|
||||
this.SetToken(token)
|
||||
this.username = username
|
||||
Cookies.set('gqa-username', username)
|
||||
this.nickname = nickname
|
||||
Cookies.set('gqa-nickname', nickname)
|
||||
this.realName = realName
|
||||
Cookies.set('gqa-realName', realName)
|
||||
this.avatar = avatar
|
||||
Cookies.set('gqa-avatar', avatar)
|
||||
return true
|
||||
} else {
|
||||
return
|
||||
}
|
||||
},
|
||||
SetToken(token) {
|
||||
this.token = token
|
||||
if (this.rememberMe) {
|
||||
Cookies.set('gqa-token', token)
|
||||
} else {
|
||||
SessionStorage.set('gqa-token', token)
|
||||
}
|
||||
},
|
||||
ChangeRememberMe(type) {
|
||||
this.rememberMe = type
|
||||
},
|
||||
HandleLogout() {
|
||||
const permissionStore = usePermissionStore()
|
||||
permissionStore.ClearMenu()
|
||||
SessionStorage.remove('gqa-token')
|
||||
Cookies.remove('gqa-token')
|
||||
Cookies.remove('gqa-username')
|
||||
Cookies.remove('gqa-nickname')
|
||||
Cookies.remove('gqa-realName')
|
||||
Cookies.remove('gqa-avatar')
|
||||
// dont delete dict
|
||||
// LocalStorage.remove('gqa-dict')
|
||||
this.token = undefined
|
||||
this.username = undefined
|
||||
this.nickname = undefined
|
||||
this.realName = undefined
|
||||
this.avatar = undefined
|
||||
},
|
||||
GetToken() {
|
||||
if (SessionStorage.getItem('gqa-token')) {
|
||||
return SessionStorage.getItem('gqa-token')
|
||||
} else if (Cookies.get('gqa-token')) {
|
||||
return Cookies.get('gqa-token')
|
||||
} else {
|
||||
return this.token
|
||||
}
|
||||
},
|
||||
GetUsername() {
|
||||
if (this.username) {
|
||||
return this.username
|
||||
} else if (Cookies.get('gqa-username')) {
|
||||
return Cookies.get('gqa-username')
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
GetNickname() {
|
||||
if (this.nickname) {
|
||||
return this.nickname
|
||||
} else if (Cookies.get('gqa-nickname')) {
|
||||
return Cookies.get('gqa-nickname')
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
GetRealName() {
|
||||
if (this.realName) {
|
||||
return this.realName
|
||||
} else if (Cookies.get('gqa-realName')) {
|
||||
return Cookies.get('gqa-realName')
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
GetAvatar() {
|
||||
if (this.avatar) {
|
||||
return this.avatar
|
||||
} else if (Cookies.get('gqa-avatar')) {
|
||||
return Cookies.get('gqa-avatar')
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
import XEUtils from 'xe-utils'
|
||||
import { uniqueId } from 'lodash'
|
||||
|
||||
export const HandleAsideMenu = function (menuData, key, parentKey) {
|
||||
// change list to tree
|
||||
// menu to tree: key => name,parentKey => parentCode
|
||||
const menu = ArrayToTree(menuData, key, parentKey)
|
||||
return checkPathAndChildren(menu)
|
||||
}
|
||||
|
||||
function checkPathAndChildren(menu) {
|
||||
return menu.map(m => ({
|
||||
...m, path: m.path || uniqueId('gqa-null-path-'), ...m.children
|
||||
? { children: checkPathAndChildren(m.children) }
|
||||
: {}
|
||||
}))
|
||||
}
|
||||
|
||||
export const ArrayToTree = (arrayData, key, parentKey) => {
|
||||
const treeData = XEUtils.toArrayTree(arrayData, {
|
||||
key: key,
|
||||
parentKey: parentKey,
|
||||
// strict: true
|
||||
})
|
||||
return treeData
|
||||
}
|
||||
|
||||
export const TreeToArray = (treeData) => {
|
||||
const data = XEUtils.toTreeArray(treeData)
|
||||
return data
|
||||
}
|
||||
|
||||
export const ChangeNullChildren2Array = (data) => {
|
||||
const index = data?.length
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
if (data[i].children === null) {
|
||||
data[i].children = []
|
||||
} else {
|
||||
ChangeNullChildren2Array(data[i].children)
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { date } from 'quasar'
|
||||
|
||||
export const FormatDateTime = (datetime) => {
|
||||
return date.formatDate(datetime, "YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
|
||||
export const FormatDate = (datetime) => {
|
||||
return date.formatDate(datetime, "YYYY-MM-DD")
|
||||
}
|
||||
|
||||
export const FormatDateTimeShort = (datetime) => {
|
||||
return date.formatDate(datetime, "YYYYMMDDHHmmss")
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import { PrivateRoutes } from 'src/router/routes'
|
||||
const pagesFile = import.meta.glob('../pages/**/*.vue')
|
||||
const pluginsFile = import.meta.glob('../plugins/**/*.vue')
|
||||
|
||||
export const HandleRouter = (menuData) => {
|
||||
const result = []
|
||||
for (let item of menuData) {
|
||||
if (item.path !== '') {
|
||||
const obj = {
|
||||
path: item.path,
|
||||
name: item.name,
|
||||
component: pageImporter(item.component),
|
||||
meta: {
|
||||
hidden: item.hidden,
|
||||
keep_alive: item.keep_alive,
|
||||
title: item.title,
|
||||
icon: item.icon,
|
||||
parent_code: item.parent_code,
|
||||
},
|
||||
redirect: item.redirect,
|
||||
}
|
||||
result.push(obj)
|
||||
} else {
|
||||
if (item.is_link === "yesNo_yes") {
|
||||
delete item.path
|
||||
}
|
||||
}
|
||||
}
|
||||
// Replace the children that need authentication routing (empty by default) with the collated routes from the backend.
|
||||
PrivateRoutes[0].children = [...result]
|
||||
// return authentication routes
|
||||
return PrivateRoutes
|
||||
}
|
||||
|
||||
const pageImporter = (component) => {
|
||||
// Vite 版本:
|
||||
let fileKey = []
|
||||
let resultKey = ''
|
||||
if (component.split('/')[0] === 'pages') {
|
||||
fileKey = Object.keys(pagesFile)
|
||||
resultKey = getResultKey(fileKey, component)
|
||||
return pagesFile[resultKey[0]]
|
||||
} else if (component.split('/')[0] === 'plugins') {
|
||||
fileKey = Object.keys(pluginsFile)
|
||||
resultKey = getResultKey(fileKey, component)
|
||||
return pluginsFile[resultKey[0]]
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
// Quasar2 Webpack版本:
|
||||
// return () => Promise.resolve(require(`src/${component}.vue`).default)
|
||||
// Quasar1 Webpack版本:
|
||||
// return (resolve) => require([`src/pages/${component}`], resolve)
|
||||
}
|
||||
|
||||
const getResultKey = (fileKey, component) => {
|
||||
const resultKey = fileKey.filter(key => {
|
||||
return key.replace('../', '').replace('.vue', '') === component
|
||||
})
|
||||
return resultKey
|
||||
}
|