feat: 提交

This commit is contained in:
Lexcubia 2025-04-09 09:52:03 +00:00
parent 093e176cf5
commit c7f12cc4cf
71 changed files with 452 additions and 6007 deletions

0
.editorconfig Normal file → Executable file
View File

0
.gitignore vendored Normal file → Executable file
View File

0
.husky/commit-msg Normal file → Executable file
View File

0
.husky/pre-commit Normal file → Executable file
View File

0
.npmrc Normal file → Executable file
View File

0
.prettierrc.json Normal file → Executable file
View File

0
.vscode/extensions.json vendored Normal file → Executable file
View File

0
.vscode/settings.json vendored Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

0
commitlint.config.ts Normal file → Executable file
View File

2
eslint.config.js Normal file → Executable file
View File

@ -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',
},
},

0
index.html Normal file → Executable file
View File

View File

@ -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": {

0
postcss.config.js Normal file → Executable file
View File

0
public/favicon.ico Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

0
public/icons/favicon-128x128.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

0
public/icons/favicon-16x16.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 859 B

0
public/icons/favicon-32x32.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

0
public/icons/favicon-96x96.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

20
quasar.config.ts Normal file → Executable file
View File

@ -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

0
src-electron/electron-env.d.ts vendored Normal file → Executable file
View File

0
src-electron/electron-main.ts Normal file → Executable file
View File

0
src-electron/electron-preload.ts Normal file → Executable file
View File

0
src-electron/icons/icon.icns Normal file → Executable file
View File

0
src-electron/icons/icon.ico Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

0
src-electron/icons/icon.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
src/App.vue Normal file → Executable file
View File

0
src/assets/quasar-logo-vertical.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

0
src/boot/.gitkeep Normal file → Executable file
View File

0
src/boot/axios.ts Normal file → Executable file
View File

2
src/boot/i18n.ts Normal file → Executable file
View File

@ -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

6
src/boot/mock.ts Executable file
View File

@ -0,0 +1,6 @@
// mock--start
import { setupProdMockServer } from 'mock/mockProdServer'
// if (process.env.NODE_ENV === 'production') {
setupProdMockServer()
// }
// mock--end

0
src/components/EssentialLink.vue Normal file → Executable file
View File

0
src/components/ExampleComponent.vue Normal file → Executable file
View File

0
src/components/models.ts Normal file → Executable file
View File

View File

@ -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
src/config/setting.ts Executable file
View File

0
src/css/app.scss Normal file → Executable file
View File

0
src/css/quasar.variables.scss Normal file → Executable file
View File

0
src/env.d.ts vendored Normal file → Executable file
View File

0
src/i18n/en-US/index.ts Normal file → Executable file
View File

0
src/i18n/index.ts Normal file → Executable file
View File

0
src/i18n/zh-CN/index.ts Normal file → Executable file
View File

View File

@ -0,0 +1,9 @@
<template>
<q-layout>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script lang="ts" setup></script>

View File

@ -0,0 +1,7 @@
<template>
<q-layout>
<q-page-container> 这里是登录页面(TODO) </q-page-container>
</q-layout>
</template>
<script lang="ts" setup></script>

View 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>

View 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>

View 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>

View File

View File

@ -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)
}

11
src/model/theme.ts Executable file
View File

@ -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
}

0
src/pages/ErrorNotFound.vue Normal file → Executable file
View File

2
src/pages/IndexPage.vue Normal file → Executable file
View File

@ -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>

9
src/pages/PageOne.vue Executable file
View File

@ -0,0 +1,9 @@
<template>
<q-page>
<div>Page 1 Content</div>
</q-page>
</template>
<script lang="ts" setup>
//
</script>

9
src/pages/PageThree.vue Executable file
View File

@ -0,0 +1,9 @@
<template>
<q-page>
<div>Page 3 Content</div>
</q-page>
</template>
<script lang="ts" setup>
//
</script>

9
src/pages/PageTwo.vue Executable file
View File

@ -0,0 +1,9 @@
<template>
<q-page>
<div>Page 2 Content</div>
</q-page>
</template>
<script lang="ts" setup>
//
</script>

0
src/router/index.ts Normal file → Executable file
View File

31
src/router/routes.ts Normal file → Executable file
View File

@ -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;

0
src/stores/example-store.ts Normal file → Executable file
View File

0
src/stores/index.ts Normal file → Executable file
View File

3
src/stores/setting.ts Normal file → Executable file
View File

@ -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: {

54
src/stores/tabMenu.js Executable file
View File

@ -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 = []
}
},
});

113
src/stores/user.js Executable file
View File

@ -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 ""
}
},
},
});

43
src/utils/arrayAndTree.js Executable file
View File

@ -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 => nameparentKey => 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
}

13
src/utils/date.js Executable file
View File

@ -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")
}

61
src/utils/router.js Executable file
View File

@ -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
}

0
tsconfig.json Normal file → Executable file
View File

5983
yarn.lock

File diff suppressed because it is too large Load Diff