mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-04 21:52:27 +08:00
Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee8aed2f62 | ||
|
|
260397f3ae | ||
|
|
fa983a9b64 | ||
|
|
4c49e28596 | ||
|
|
9a5fe5249c | ||
|
|
02235bf6fc | ||
|
|
0656035857 | ||
|
|
81e8222461 | ||
|
|
09411a927e | ||
|
|
347cd91735 | ||
|
|
9b2effbc55 | ||
|
|
d72f42dcd8 | ||
|
|
165c358ef5 | ||
|
|
fc181ce543 | ||
|
|
f04b9ba7f9 | ||
|
|
ee0e507e47 | ||
|
|
9d18715e90 | ||
|
|
74f0e4764e | ||
|
|
4cbdd9bc66 | ||
|
|
e03b1bdfca | ||
|
|
b6a350e1be | ||
|
|
503437a433 | ||
|
|
d4518849bf | ||
|
|
39178761b1 | ||
|
|
934de6b1e6 | ||
|
|
58d13a242d | ||
|
|
5d891c1f44 | ||
|
|
e1528823f7 | ||
|
|
1fa35db71b | ||
|
|
80729d925e | ||
|
|
e228184b72 | ||
|
|
2f541106f2 | ||
|
|
5f81f4cc77 | ||
|
|
b4f32f50c5 | ||
|
|
5d180b6fd5 | ||
|
|
3d6465346c | ||
|
|
dc98b96ce2 | ||
|
|
3f84af912f | ||
|
|
8dcd66b654 | ||
|
|
4bdc54a77f | ||
|
|
0ceb7437a4 | ||
|
|
b984828f33 | ||
|
|
1d10826760 | ||
|
|
74334de7e0 | ||
|
|
e153837497 | ||
|
|
d0ec44b48c | ||
|
|
e9efbb5e67 | ||
|
|
aa90e0a468 | ||
|
|
9049f743a8 | ||
|
|
1f858929d6 | ||
|
|
5a50bc0dc4 | ||
|
|
a35318f808 | ||
|
|
7a18f58f4a | ||
|
|
3fb8c85112 | ||
|
|
921dd91eda | ||
|
|
bcd2480f69 | ||
|
|
590d1615cb | ||
|
|
23dcfe000b | ||
|
|
47e47c8a59 | ||
|
|
719ad34a94 | ||
|
|
a0deba504a | ||
|
|
25d6134923 | ||
|
|
a646de72e2 | ||
|
|
e63963cf08 | ||
|
|
c43ec3ef1c | ||
|
|
4f0a7967f7 | ||
|
|
1c594b49c0 | ||
|
|
b68103455b | ||
|
|
724af94c08 | ||
|
|
af68d4bc67 | ||
|
|
2fb36695db | ||
|
|
32116be1f2 | ||
|
|
4a47fa2f7d | ||
|
|
89f68789ff | ||
|
|
4c0de9d5c2 | ||
|
|
1e72351dea | ||
|
|
da24659944 | ||
|
|
eb21a99df1 | ||
|
|
c598d3f7bc | ||
|
|
fa0163a385 | ||
|
|
57fef38bfe | ||
|
|
d62ca84328 | ||
|
|
c504e61d70 | ||
|
|
c0d3e11346 | ||
|
|
4c27623a0f | ||
|
|
0107c5f035 | ||
|
|
6134a4c125 | ||
|
|
00dd7409b1 | ||
|
|
a7ad4a3634 | ||
|
|
327f8de00b | ||
|
|
026e4c1acd | ||
|
|
9ba51e4a34 | ||
|
|
bc2d2b4e94 | ||
|
|
e2f90cbf88 | ||
|
|
2da9b8a465 | ||
|
|
f99ecea202 | ||
|
|
e3326a0f11 | ||
|
|
775f5b63af | ||
|
|
874fd3437d | ||
|
|
7644c59799 | ||
|
|
f8ed3d1607 | ||
|
|
0c8bb030de | ||
|
|
41a6f59617 | ||
|
|
1a7eaf7efb | ||
|
|
5714233d57 | ||
|
|
5e2452f2df | ||
|
|
e9d3e1affe | ||
|
|
f6d419b94f | ||
|
|
cc0d1824f2 | ||
|
|
299f8c89a5 | ||
|
|
9e40480b9b | ||
|
|
c20ee24cd8 | ||
|
|
eb27f19087 | ||
|
|
3cbb6061c5 | ||
|
|
e07e36ce5d | ||
|
|
0f8b0d3557 | ||
|
|
28d2696061 | ||
|
|
b2c22996ce | ||
|
|
f616852053 | ||
|
|
92316285bb | ||
|
|
1077a20cbd | ||
|
|
7b9b391adc | ||
|
|
88ada7ebee | ||
|
|
a7257568cf | ||
|
|
403a844668 | ||
|
|
197e63cb51 | ||
|
|
6c3273e214 | ||
|
|
71be58fc1f | ||
|
|
e3e14b8259 | ||
|
|
5f625aa305 | ||
|
|
8167d4165b | ||
|
|
873e0d3e43 | ||
|
|
f20f4209a3 | ||
|
|
363ed7ae9e | ||
|
|
a21cc8aec8 | ||
|
|
0f229aea13 | ||
|
|
89151afce3 | ||
|
|
ffac6c0d78 | ||
|
|
c257ca0948 | ||
|
|
9bcd6d9700 | ||
|
|
a66f56b9ce | ||
|
|
9e58578706 | ||
|
|
9e3ebd62b2 | ||
|
|
5b852506a6 | ||
|
|
f42857884f | ||
|
|
e6505c88b7 | ||
|
|
55ee389184 |
16
.eslintrc.js
16
.eslintrc.js
@@ -25,6 +25,7 @@ module.exports = defineConfig({
|
||||
],
|
||||
rules: {
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
@@ -37,19 +38,12 @@ module.exports = defineConfig({
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '.*', args: 'none' }],
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
// we are only using this rule to check for unused arguments since TS
|
||||
// catches unused variables but not args.
|
||||
{ varsIgnorePattern: '.*', args: 'none' },
|
||||
],
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
|
||||
38
CHANGELOG.md
38
CHANGELOG.md
@@ -1,5 +1,43 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 1.9.1
|
||||
|
||||
- 优化 `typeSctipt` 类型定义
|
||||
- 优化 `setup` 语法
|
||||
- 依赖升级
|
||||
|
||||
## 1.9.0
|
||||
|
||||
- 新增 `BasicForm` 组件,支持 `setLoading`, `setSchema` 方法
|
||||
- 新增 `countField` 总数字段名配置
|
||||
- 优化 `yarn` 切换至 `pnpm`
|
||||
- 优化 `BasicForm` 组件,验证返回值
|
||||
- 优化 `BasicTable` 组件
|
||||
- 修复 `TableAction组件,左右间隔不生效` 关闭[253](https://github.com/jekip/naive-ui-admin/issues/253)
|
||||
- 修复 `BasicTable组件没有数据会一直请求接口` 关闭[#251](https://github.com/jekip/naive-ui-admin/issues/251)
|
||||
- 修复 `useModal+useForm组件的bug` 关闭[#250](https://github.com/jekip/naive-ui-admin/issues/250)
|
||||
- 修复 `手机端侧边导航风格不一致bug` 关闭[#247](https://github.com/jekip/naive-ui-admin/issues/247
|
||||
- 移除 `yarn.lock` 文件
|
||||
- 依赖升级
|
||||
## 1.8.2
|
||||
|
||||
- ### ✨ Features
|
||||
- 新增 `directive` 示例
|
||||
- 依赖升级
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- 修复 `样式异常` 图片
|
||||
|
||||
## 1.8.1
|
||||
|
||||
- ### ✨ Features
|
||||
- 新增 `clean:cache` 删除缓存命令
|
||||
- 新增 `clean:lib` 删除 `node_modules` 命令
|
||||
- 依赖升级
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- 修复 `开发环境` 运行控制台错误提示
|
||||
|
||||
## 1.8.0 (2022-04-01)
|
||||
|
||||
- ### ✨ Features
|
||||
|
||||
46
README.md
46
README.md
@@ -11,21 +11,38 @@
|
||||
|
||||
|
||||
## 预览
|
||||
- [naive-ui-admin](https://naive-ui-admin.vercel.app)
|
||||
- [naive-ui-admin](https://jekip.github.io)
|
||||
|
||||
账号:admin,密码:123456(随意)
|
||||
|
||||
## 提示
|
||||
|
||||
如果这个版本的功能和组件,并不能满足您的需求,不妨看看,我们全新 `NaiveAdmin v2` 他或许能让您眼前一亮O(∩_∩)O哈哈~
|
||||
如果开源版本的功能和组件,并不能满足您的需求,不妨看看,我们全新 `NaiveAdmin` 他或许能让您眼前一亮O(∩_∩)O哈哈~
|
||||
|
||||
[NaiveAdmin 官网](https://www.naiveadmin.com)
|
||||
|
||||
[NaiveAdmin v2 预览](https://pro.naiveadmin.com)
|
||||
[NaiveAdmin 变更日志](https://www.yuque.com/u5825/zaqu0e)
|
||||
|
||||
[NaiveAdmin v2 变更日志](https://www.naiveadmin.com/guide/changelog)
|
||||
[为什么选我们?](https://www.naiveadmin.com/choose/we)
|
||||
### Plus
|
||||
|
||||
## 新品
|
||||
基于 `NaiveUi` 全新设计版本,新增众多特性,强烈推荐
|
||||
|
||||
[NaiveAdmin Plus 预览](https://plus.naiveadmin.com)
|
||||
|
||||
### Arco vue
|
||||
|
||||
智能设计体系,连接轻盈体验
|
||||
|
||||
[NaiveAdmin Arco 预览](https://arco.naiveadmin.com)
|
||||
|
||||
### Element Plus
|
||||
|
||||
面向设计师和开发者的组件库
|
||||
|
||||
[Element Plus Admin 预览](https://element.naiveadmin.com)
|
||||
|
||||
以上版本同时具备 `NaiveAdmin` 功能/组件/页面,一如既往、开箱即用,欢迎前往查看。
|
||||
|
||||
### Antd vue
|
||||
|
||||
@@ -33,24 +50,9 @@
|
||||
|
||||
[NaiveAdmin Antd 预览](https://antd.naiveadmin.com)
|
||||
|
||||
### Arco vue
|
||||
|
||||
新产品,智能设计体系,连接轻盈体验
|
||||
|
||||
[NaiveAdmin Arco 预览](https://arco.naiveadmin.com)
|
||||
|
||||
### Element Plus
|
||||
|
||||
新产品,面向设计师和开发者的组件库
|
||||
|
||||
[Element Plus Admin 预览](https://element.naiveadmin.com)
|
||||
|
||||
以上版本同时具备 `NaiveAdmin v2` 功能/组件/页面,一如既往、开箱即用,欢迎前往查看。
|
||||
|
||||
|
||||
## 文档
|
||||
|
||||
[v1文档地址](https://naive-ui-admin-docs.vercel.app)
|
||||
[文档地址](https://jekip.github.io/docs)
|
||||
|
||||
## 准备
|
||||
|
||||
@@ -144,7 +146,7 @@ yarn build
|
||||
|
||||
`Naive Ui Admin` 使用或者其他问题,都可以在群内讨论或提问。
|
||||
|
||||

|
||||

|
||||
|
||||
## 赞助
|
||||
#### 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 🍹。
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Plugin to minimize and use ejs template syntax in index.html.
|
||||
* https://github.com/anncwb/vite-plugin-html
|
||||
*/
|
||||
import type { Plugin } from 'vite';
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
import html from 'vite-plugin-html';
|
||||
import { createHtmlPlugin } from 'vite-plugin-html';
|
||||
|
||||
import pkg from '../../../package.json';
|
||||
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
|
||||
@@ -18,11 +18,11 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
|
||||
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
|
||||
};
|
||||
|
||||
const htmlPlugin: Plugin[] = html({
|
||||
const htmlPlugin: PluginOption[] = createHtmlPlugin({
|
||||
minify: isBuild,
|
||||
inject: {
|
||||
// Inject data into ejs template
|
||||
injectData: {
|
||||
data: {
|
||||
title: VITE_GLOB_APP_TITLE,
|
||||
},
|
||||
// Embed the generated app.config.js file
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Plugin } from 'vite';
|
||||
import type { Plugin,PluginOption } from 'vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||
|
||||
@@ -12,7 +12,7 @@ import { configCompressPlugin } from './compress';
|
||||
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) {
|
||||
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
|
||||
|
||||
const vitePlugins: (Plugin | Plugin[])[] = [
|
||||
const vitePlugins: (Plugin | Plugin[] | PluginOption[])[] = [
|
||||
// have to
|
||||
vue(),
|
||||
// have to
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Introduces component library styles on demand.
|
||||
* https://github.com/anncwb/vite-plugin-style-import
|
||||
*/
|
||||
|
||||
import styleImport from 'vite-plugin-style-import';
|
||||
|
||||
export function configStyleImportPlugin(isBuild: boolean) {
|
||||
if (!isBuild) return [];
|
||||
const styleImportPlugin = styleImport({
|
||||
libs: [
|
||||
{
|
||||
libraryName: 'ant-design-vue',
|
||||
esModule: true,
|
||||
resolveStyle: (name) => {
|
||||
return `ant-design-vue/es/${name}/style/index`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return styleImportPlugin;
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
||||
|
||||
const modules = import.meta.globEager('./**/*.ts');
|
||||
interface IModuleType {
|
||||
default: any[];
|
||||
}
|
||||
|
||||
const modules = import.meta.glob<IModuleType>('./**/*.ts', { eager: true });
|
||||
|
||||
const mockModules: any[] = [];
|
||||
Object.keys(modules).forEach((key) => {
|
||||
|
||||
@@ -27,12 +27,15 @@ export default [
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
const { page = 1, pageSize = 10 } = query;
|
||||
const { page = 1, pageSize = 10, name } = query;
|
||||
const list = tableList(Number(pageSize));
|
||||
//并非真实,只是为了模拟搜索结果
|
||||
const count = name ? 30 : 60;
|
||||
return resultSuccess({
|
||||
page: Number(page),
|
||||
pageSize: Number(pageSize),
|
||||
pageCount: 60,
|
||||
pageCount: count,
|
||||
itemCount: count * Number(pageSize),
|
||||
list,
|
||||
});
|
||||
},
|
||||
|
||||
139
package.json
139
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "naive-ui-admin",
|
||||
"version": "1.8.0",
|
||||
"version": "1.9.1",
|
||||
"author": {
|
||||
"name": "Ahjung",
|
||||
"email": "735878602@qq.com",
|
||||
@@ -8,13 +8,16 @@
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"bootstrap": "yarn install",
|
||||
"serve": "npm run dev",
|
||||
"bootstrap": "pnpm install",
|
||||
"serve": "pnpm run dev",
|
||||
"dev": "vite",
|
||||
"build": "vite build && esno ./build/script/postBuild.ts",
|
||||
"build:no-cache": "yarn clean:cache && npm run build",
|
||||
"build:no-cache": "pnpm clean:cache && npm run build",
|
||||
"report": "cross-env REPORT=true npm run build",
|
||||
"preview": "vite preview",
|
||||
"preview": "npm run build && vite preview",
|
||||
"preview:dist": "vite preview",
|
||||
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
|
||||
"clean:lib": "rimraf node_modules",
|
||||
"build typecheck": "vuedx-typecheck . && vite build",
|
||||
"deploy": "gh-pages -d dist",
|
||||
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
|
||||
@@ -25,74 +28,76 @@
|
||||
"test prod gzip": "http-server dist --cors --gzip -c-1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vicons/antd": "^0.10.0",
|
||||
"@vicons/ionicons5": "^0.10.0",
|
||||
"@vueup/vue-quill": "^1.0.0-beta.8",
|
||||
"@vueuse/core": "^5.3.0",
|
||||
"axios": "^0.21.4",
|
||||
"@vicons/antd": "^0.12.0",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"axios": "^1.6.8",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"date-fns": "^2.28.0",
|
||||
"echarts": "^5.3.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"echarts": "^5.5.0",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^2.1.0",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"naive-ui": "^2.27.0",
|
||||
"pinia": "^2.0.13",
|
||||
"qs": "^6.10.3",
|
||||
"vfonts": "^0.1.0",
|
||||
"vue": "^3.2.31",
|
||||
"vue-router": "^4.0.14",
|
||||
"vue-types": "^4.1.1",
|
||||
"vuedraggable": "^4.1.0"
|
||||
"naive-ui": "^2.38.1",
|
||||
"pinia": "^2.1.7",
|
||||
"qs": "^6.12.0",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0",
|
||||
"vue-types": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^12.1.4",
|
||||
"@commitlint/config-conventional": "^12.1.4",
|
||||
"@types/lodash": "^4.14.181",
|
||||
"@types/node": "^15.14.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vitejs/plugin-vue": "^1.10.2",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.9",
|
||||
"@vue/compiler-sfc": "^3.2.31",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"commitizen": "^4.2.4",
|
||||
"core-js": "^3.21.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-define-config": "1.0.9",
|
||||
"eslint-plugin-jest": "^24.7.0",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"esno": "^0.7.3",
|
||||
"gh-pages": "^3.2.3",
|
||||
"husky": "^6.0.0",
|
||||
"jest": "^27.5.1",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^9.1.0",
|
||||
"lint-staged": "^11.2.6",
|
||||
"postcss": "^8.4.12",
|
||||
"prettier": "^2.6.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"@commitlint/cli": "^17.8.1",
|
||||
"@commitlint/config-conventional": "^17.8.1",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/node": "^18.19.31",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
||||
"@vue/compiler-sfc": "^3.4.21",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"commitizen": "^4.3.0",
|
||||
"core-js": "^3.36.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-define-config": "1.12.0",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.24.1",
|
||||
"esno": "^0.16.3",
|
||||
"gh-pages": "^4.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.4",
|
||||
"lint-staged": "^13.3.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.3.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-prettier": "^8.0.2",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"stylelint-order": "^4.1.0",
|
||||
"stylelint-scss": "^3.21.0",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"typescript": "^4.6.3",
|
||||
"unplugin-vue-components": "^0.17.21",
|
||||
"vite": "^2.9.1",
|
||||
"vite-plugin-compression": "^0.3.6",
|
||||
"vite-plugin-html": "^2.1.2",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-style-import": "^1.4.1",
|
||||
"vue-eslint-parser": "^7.11.0"
|
||||
"stylelint": "^14.16.1",
|
||||
"stylelint-config-prettier": "^9.0.5",
|
||||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"stylelint-scss": "^4.7.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^4.9.5",
|
||||
"unplugin-vue-components": "^0.22.12",
|
||||
"vite": "^3.2.10",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-mock": "^2.9.8",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"vue-demi": "^0.13.11",
|
||||
"vue-draggable-next": "^2.2.1",
|
||||
"vue-eslint-parser": "^9.4.2",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{vue,js,ts,tsx}": "eslint --fix"
|
||||
@@ -122,6 +127,6 @@
|
||||
},
|
||||
"homepage": "https://github.com/jekip/naive-ui-admin",
|
||||
"engines": {
|
||||
"node": "^12 || >=14"
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
|
||||
8459
pnpm-lock.yaml
generated
8459
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,37 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="renderer" content="webkit"/>
|
||||
<meta name="force-rendering" content="webkit"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<script src="<%= BASE_URL %>iconfont.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<!-- IE 浏览器跳转提示升级页面 start -->
|
||||
<script>
|
||||
if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) {
|
||||
window.open("https://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href));
|
||||
};
|
||||
</script>
|
||||
<!-- IE 浏览器跳转提示升级页面 end -->
|
||||
<script>
|
||||
function isIE() {
|
||||
if (!!window.ActiveXObject || "ActiveXObject" in window){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
23
src/App.vue
23
src/App.vue
@@ -21,16 +21,16 @@
|
||||
import { zhCN, dateZhCN, darkTheme } from 'naive-ui';
|
||||
import { LockScreen } from '@/components/Lockscreen';
|
||||
import { AppProvider } from '@/components/Application';
|
||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||
import { useScreenLockStore } from '@/store/modules/screenLock.js';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||
import { lighten } from '@/utils/index';
|
||||
|
||||
const route = useRoute();
|
||||
const useLockscreen = useLockscreenStore();
|
||||
const useScreenLock = useScreenLockStore();
|
||||
const designStore = useDesignSettingStore();
|
||||
const isLock = computed(() => useLockscreen.isLock);
|
||||
const lockTime = computed(() => useLockscreen.lockTime);
|
||||
const isLock = computed(() => useScreenLock.isLocked);
|
||||
const lockTime = computed(() => useScreenLock.lockTime);
|
||||
|
||||
/**
|
||||
* @type import('naive-ui').GlobalThemeOverrides
|
||||
@@ -43,6 +43,7 @@
|
||||
primaryColor: appTheme,
|
||||
primaryColorHover: lightenStr,
|
||||
primaryColorPressed: lightenStr,
|
||||
primaryColorSuppl: appTheme,
|
||||
},
|
||||
LoadingBar: {
|
||||
colorLoading: appTheme,
|
||||
@@ -52,21 +53,21 @@
|
||||
|
||||
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
|
||||
|
||||
let timer;
|
||||
let timer: NodeJS.Timer;
|
||||
|
||||
const timekeeping = () => {
|
||||
clearInterval(timer);
|
||||
if (route.name == 'login' || isLock.value) return;
|
||||
// 设置不锁屏
|
||||
useLockscreen.setLock(false);
|
||||
useScreenLock.setLock(false);
|
||||
// 重置锁屏时间
|
||||
useLockscreen.setLockTime();
|
||||
useScreenLock.setLockTime();
|
||||
timer = setInterval(() => {
|
||||
// 锁屏倒计时递减
|
||||
useLockscreen.setLockTime(lockTime.value - 1);
|
||||
useScreenLock.setLockTime(lockTime.value - 1);
|
||||
if (lockTime.value <= 0) {
|
||||
// 设置锁屏
|
||||
useLockscreen.setLock(true);
|
||||
useScreenLock.setLock(true);
|
||||
return clearInterval(timer);
|
||||
}
|
||||
}, 1000);
|
||||
@@ -80,7 +81,3 @@
|
||||
document.removeEventListener('mousedown', timekeeping);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import 'styles/index.less';
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
<template>
|
||||
<n-loading-bar-provider>
|
||||
<n-dialog-provider>
|
||||
<DialogContent />
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<MessageContent />
|
||||
<slot slot="default"></slot>
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-loading-bar-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<slot name="default"></slot>
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
NDialogProvider,
|
||||
NNotificationProvider,
|
||||
NMessageProvider,
|
||||
NLoadingBarProvider,
|
||||
} from 'naive-ui';
|
||||
import { MessageContent } from '@/components/MessageContent';
|
||||
import { DialogContent } from '@/components/DialogContent';
|
||||
import { NDialogProvider, NNotificationProvider, NMessageProvider } from 'naive-ui';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Application',
|
||||
@@ -29,9 +18,6 @@
|
||||
NDialogProvider,
|
||||
NNotificationProvider,
|
||||
NMessageProvider,
|
||||
NLoadingBarProvider,
|
||||
MessageContent,
|
||||
DialogContent,
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import DialogContent from './index.vue';
|
||||
|
||||
export { DialogContent };
|
||||
@@ -1,12 +0,0 @@
|
||||
<template></template>
|
||||
<script lang="ts">
|
||||
import { useDialog } from 'naive-ui';
|
||||
|
||||
export default {
|
||||
name: 'DialogContent',
|
||||
setup() {
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$dialog'] = useDialog();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -8,7 +8,7 @@
|
||||
{{ schema.label }}
|
||||
<n-tooltip trigger="hover" :style="schema.labelMessageStyle">
|
||||
<template #trigger>
|
||||
<n-icon size="18" class="cursor-pointer text-gray-400">
|
||||
<n-icon size="18" class="text-gray-400 cursor-pointer">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
@@ -90,6 +90,7 @@
|
||||
v-bind="getSubmitBtnOptions"
|
||||
@click="handleSubmit"
|
||||
:loading="loadingSub"
|
||||
attr-type="submit"
|
||||
>{{ getProps.submitButtonText }}</n-button
|
||||
>
|
||||
<n-button
|
||||
@@ -138,7 +139,7 @@
|
||||
import { deepMerge } from '@/utils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicUpload',
|
||||
name: 'BasicForm',
|
||||
components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
|
||||
props: {
|
||||
...basicProps,
|
||||
@@ -230,7 +231,6 @@
|
||||
});
|
||||
|
||||
const { handleFormValues, initDefault } = useFormValues({
|
||||
getProps,
|
||||
defaultFormModel,
|
||||
getSchema,
|
||||
formModel,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComponentType } from '/types/index';
|
||||
import { ComponentType } from './types/index';
|
||||
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
|
||||
@@ -80,6 +80,15 @@ export function useForm(props?: Props): UseFormReturnType {
|
||||
const form = await getForm();
|
||||
return form.validate(nameList);
|
||||
},
|
||||
|
||||
setLoading: (value: boolean) => {
|
||||
loadedRef.value = value;
|
||||
},
|
||||
|
||||
setSchema: async (values) => {
|
||||
const form = await getForm();
|
||||
form.setSchema(values);
|
||||
},
|
||||
};
|
||||
|
||||
return [register, methods];
|
||||
|
||||
@@ -32,23 +32,27 @@ export function useFormEvents({
|
||||
}
|
||||
|
||||
// 提交
|
||||
async function handleSubmit(e?: Event): Promise<void> {
|
||||
async function handleSubmit(e?: Event): Promise<object | boolean> {
|
||||
e && e.preventDefault();
|
||||
loadingSub.value = true;
|
||||
const { submitFunc } = unref(getProps);
|
||||
if (submitFunc && isFunction(submitFunc)) {
|
||||
await submitFunc();
|
||||
return;
|
||||
loadingSub.value = false;
|
||||
return false;
|
||||
}
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
if (!formEl) return false;
|
||||
try {
|
||||
await validate();
|
||||
const values = getFieldsValue();
|
||||
loadingSub.value = false;
|
||||
emit('submit', formModel);
|
||||
return true;
|
||||
} catch (error) {
|
||||
emit('submit', values);
|
||||
return values;
|
||||
} catch (error: any) {
|
||||
emit('submit', false);
|
||||
loadingSub.value = false;
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -96,6 +100,10 @@ export function useFormEvents({
|
||||
});
|
||||
}
|
||||
|
||||
function setLoading(value: boolean): void {
|
||||
loadingSub.value = value;
|
||||
}
|
||||
|
||||
return {
|
||||
handleSubmit,
|
||||
validate,
|
||||
@@ -103,5 +111,6 @@ export function useFormEvents({
|
||||
getFieldsValue,
|
||||
clearValidate,
|
||||
setFieldsValue,
|
||||
setLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,16 +41,19 @@ export interface FormProps {
|
||||
submitFunc?: () => Promise<void>;
|
||||
submitOnReset?: boolean;
|
||||
baseGridStyle?: CSSProperties;
|
||||
collapsedRows?: number;
|
||||
}
|
||||
|
||||
export interface FormActionType {
|
||||
submit: () => Promise<any>;
|
||||
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||
setFieldsValue: <T>(values: T) => Promise<void>;
|
||||
setSchema: (schemaProps: Partial<FormSchema[]>) => Promise<void>;
|
||||
setFieldsValue: (values: Recordable) => void;
|
||||
clearValidate: (name?: string | string[]) => Promise<void>;
|
||||
getFieldsValue: () => Recordable;
|
||||
resetFields: () => Promise<void>;
|
||||
validate: (nameList?: any[]) => Promise<any>;
|
||||
setLoading: (status: boolean) => void;
|
||||
}
|
||||
|
||||
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import LoadingContent from './index.vue';
|
||||
|
||||
export { LoadingContent };
|
||||
@@ -1,12 +0,0 @@
|
||||
<template></template>
|
||||
<script lang="ts">
|
||||
import { useLoadingBar } from 'naive-ui';
|
||||
|
||||
export default {
|
||||
name: 'LoadingContent',
|
||||
setup() {
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$loading'] = useLoadingBar();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -60,11 +60,11 @@
|
||||
</template>
|
||||
</n-input>
|
||||
|
||||
<div class="w-full flex" v-if="isLoginError">
|
||||
<div class="flex w-full" v-if="isLoginError">
|
||||
<span class="text-red-500">{{ errorMsg }}</span>
|
||||
</div>
|
||||
|
||||
<div class="w-full mt-1 flex justify-around">
|
||||
<div class="flex justify-around w-full mt-1">
|
||||
<div><a @click="showLogin = false">返回</a></div>
|
||||
<div><a @click="goLogin">重新登录</a></div>
|
||||
<div><a @click="onLogin">进入系统</a></div>
|
||||
@@ -91,11 +91,11 @@
|
||||
import { useOnline } from '@/hooks/useOnline';
|
||||
import { useTime } from '@/hooks/useTime';
|
||||
import { useBattery } from '@/hooks/useBattery';
|
||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useScreenLockStore } from '@/store/modules/screenLock';
|
||||
import { UserInfoType, useUserStore } from '@/store/modules/user';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Lockscreen',
|
||||
name: 'ScreenLock',
|
||||
components: {
|
||||
LockOutlined,
|
||||
LoadingOutlined,
|
||||
@@ -106,7 +106,7 @@
|
||||
recharge,
|
||||
},
|
||||
setup() {
|
||||
const useLockscreen = useLockscreenStore();
|
||||
const useScreenLock = useScreenLockStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 获取时间
|
||||
@@ -117,7 +117,7 @@
|
||||
const route = useRoute();
|
||||
|
||||
const { battery, batteryStatus, calcDischargingTime, calcChargingTime } = useBattery();
|
||||
const userInfo: object = userStore.getUserInfo || {};
|
||||
const userInfo: UserInfoType = userStore.getUserInfo || {};
|
||||
const username = userInfo['username'] || '';
|
||||
const state = reactive({
|
||||
showLogin: false,
|
||||
@@ -146,7 +146,7 @@
|
||||
const { code, message } = await userStore.login(params);
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
onLockLogin(false);
|
||||
useLockscreen.setLock(false);
|
||||
useScreenLock.setLock(false);
|
||||
} else {
|
||||
state.errorMsg = message;
|
||||
state.isLoginError = true;
|
||||
@@ -157,7 +157,7 @@
|
||||
//重新登录
|
||||
const goLogin = () => {
|
||||
onLockLogin(false);
|
||||
useLockscreen.setLock(false);
|
||||
useScreenLock.setLock(false);
|
||||
router.replace({
|
||||
path: '/login',
|
||||
query: {
|
||||
|
||||
@@ -135,22 +135,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
@width: ~`Math.round(Math.random() * 100) ` px;
|
||||
@left: calc(15px + `Math.round(Math.random(70)) `);
|
||||
each(range(15), {
|
||||
.xiaoma-@{value} {
|
||||
height: (@value * 50px);
|
||||
}
|
||||
li:nth-child(@{index}) {
|
||||
top: 50%;
|
||||
left: @left;
|
||||
width: @width;
|
||||
height: @width;
|
||||
transform: translate(-50%, -50%);
|
||||
/*animation: moveToTop (Math.random(6) + 3s) ease-in-out -(Math.random(5000) / 1000s) infinite;*/
|
||||
}
|
||||
});
|
||||
|
||||
@keyframes rotate {
|
||||
50% {
|
||||
border-radius: 45% / 42% 38% 58% 49%;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import MessageContent from './index.vue';
|
||||
|
||||
export { MessageContent };
|
||||
@@ -1,12 +0,0 @@
|
||||
<template></template>
|
||||
<script lang="ts">
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
export default {
|
||||
name: 'MessageContent',
|
||||
setup() {
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$message'] = useMessage();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -21,16 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
getCurrentInstance,
|
||||
ref,
|
||||
nextTick,
|
||||
unref,
|
||||
computed,
|
||||
useAttrs,
|
||||
defineEmits,
|
||||
defineProps,
|
||||
} from 'vue';
|
||||
import { getCurrentInstance, ref, nextTick, unref, computed, useAttrs } from 'vue';
|
||||
import { basicProps } from './props';
|
||||
import startDrag from '@/utils/Drag';
|
||||
import { deepMerge } from '@/utils';
|
||||
@@ -41,7 +32,7 @@
|
||||
const props = defineProps({ ...basicProps });
|
||||
const emit = defineEmits(['on-close', 'on-ok', 'register']);
|
||||
|
||||
const propsRef = ref(<Partial<ModalProps> | null>null);
|
||||
const propsRef = ref<Partial<ModalProps> | null>(null);
|
||||
|
||||
const isModal = ref(false);
|
||||
const subLoading = ref(false);
|
||||
@@ -93,7 +84,6 @@
|
||||
|
||||
function handleSubmit() {
|
||||
subLoading.value = true;
|
||||
console.log(subLoading.value);
|
||||
emit('on-ok');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, onUnmounted, unref, getCurrentInstance, watch, nextTick } from 'vue';
|
||||
import { ref, unref, getCurrentInstance, watch } from 'vue';
|
||||
import { isProdMode } from '@/utils/env';
|
||||
import { ModalMethods, UseModalReturnType } from '../type';
|
||||
import { getDynamicProps } from '@/utils';
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
<!--顶部右侧区域-->
|
||||
<slot name="toolbar"></slot>
|
||||
|
||||
<!--斑马纹-->
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<div class="mr-2 table-toolbar-right-icon">
|
||||
<n-switch v-model:value="isStriped" @update:value="setStriped" />
|
||||
</div>
|
||||
</template>
|
||||
<span>表格斑马纹</span>
|
||||
</n-tooltip>
|
||||
<n-divider vertical />
|
||||
|
||||
<!--刷新-->
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
@@ -61,6 +72,7 @@
|
||||
<n-data-table
|
||||
ref="tableElRef"
|
||||
v-bind="getBindValues"
|
||||
:striped="isStriped"
|
||||
:pagination="pagination"
|
||||
@update:page="updatePage"
|
||||
@update:page-size="updatePageSize"
|
||||
@@ -144,7 +156,7 @@
|
||||
const tableElRef = ref<ComponentRef>(null);
|
||||
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
let paginationEl: HTMLElement | null;
|
||||
|
||||
const isStriped = ref(false);
|
||||
const tableData = ref<Recordable[]>([]);
|
||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||
|
||||
@@ -223,6 +235,8 @@
|
||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||
}
|
||||
|
||||
const setStriped = (value: boolean) => (isStriped.value = value);
|
||||
|
||||
const tableAction = {
|
||||
reload,
|
||||
setColumns,
|
||||
@@ -250,7 +264,7 @@
|
||||
const headerH = 64;
|
||||
let paginationH = 2;
|
||||
let marginH = 24;
|
||||
if (!isBoolean(pagination)) {
|
||||
if (!isBoolean(unref(pagination))) {
|
||||
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
||||
if (paginationEl) {
|
||||
const offsetHeight = paginationEl.offsetHeight;
|
||||
@@ -288,6 +302,8 @@
|
||||
updatePageSize,
|
||||
pagination,
|
||||
tableAction,
|
||||
setStriped,
|
||||
isStriped,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<div class="tableAction">
|
||||
<div class="flex items-center justify-center">
|
||||
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
|
||||
<n-button v-bind="action" class="mx-2">{{ action.label }}</n-button>
|
||||
<n-button v-bind="action" class="mx-1">
|
||||
{{ action.label }}
|
||||
<template #icon v-if="action.hasOwnProperty('icon')">
|
||||
<n-icon :component="action.icon" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-dropdown
|
||||
v-if="dropDownActions && getDropdownList.length"
|
||||
@@ -11,7 +16,7 @@
|
||||
@select="select"
|
||||
>
|
||||
<slot name="more"></slot>
|
||||
<n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right">
|
||||
<n-button v-bind="getMoreProps" class="mx-1" v-if="!$slots.more" icon-placement="right">
|
||||
<div class="flex items-center">
|
||||
<span>更多</span>
|
||||
<n-icon size="14" class="ml-1">
|
||||
@@ -75,7 +80,7 @@
|
||||
const getDropdownList = computed(() => {
|
||||
return (toRaw(props.dropDownActions) || [])
|
||||
.filter((action) => {
|
||||
return hasPermission(action.auth) && isIfShow(action);
|
||||
return hasPermission(action.auth as string[]) && isIfShow(action);
|
||||
})
|
||||
.map((action) => {
|
||||
const { popConfirm } = action;
|
||||
@@ -108,7 +113,7 @@
|
||||
const getActions = computed(() => {
|
||||
return (toRaw(props.actions) || [])
|
||||
.filter((action) => {
|
||||
return hasPermission(action.auth) && isIfShow(action);
|
||||
return hasPermission(action.auth as string[]) && isIfShow(action);
|
||||
})
|
||||
.map((action) => {
|
||||
const { popConfirm } = action;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
:class="getWrapperClass"
|
||||
ref="elRef"
|
||||
@options-change="handleOptionsChange"
|
||||
@pressEnter="handleEnter"
|
||||
@press-enter="handleEnter"
|
||||
/>
|
||||
</div>
|
||||
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||
@@ -51,7 +51,6 @@
|
||||
import { EventEnum } from '@/components/Table/src/componentMap';
|
||||
|
||||
import { parseISO, format } from 'date-fns';
|
||||
import { Fn, LabelValueOptions } from '/#/index';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditableCell',
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
VerticalRightOutlined,
|
||||
VerticalLeftOutlined,
|
||||
} from '@vicons/antd';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||
|
||||
interface Options {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ref, ComputedRef, unref, computed, onMounted, watchEffect, watch } from 'vue';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
import { isBoolean } from '@/utils/is';
|
||||
import { isBoolean, isFunction, isArray } from '@/utils/is';
|
||||
import { APISETTING } from '../const';
|
||||
|
||||
export function useDataSource(
|
||||
@@ -46,14 +46,14 @@ export function useDataSource(
|
||||
async function fetch(opt?) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { request, pagination }: any = unref(propsRef);
|
||||
const { request, pagination, beforeRequest, afterRequest }: any = unref(propsRef);
|
||||
if (!request) return;
|
||||
//组装分页信息
|
||||
const pageField = APISETTING.pageField;
|
||||
const sizeField = APISETTING.sizeField;
|
||||
const totalField = APISETTING.totalField;
|
||||
const listField = APISETTING.listField;
|
||||
|
||||
const itemCount = APISETTING.countField;
|
||||
let pageParams = {};
|
||||
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
|
||||
|
||||
@@ -64,32 +64,45 @@ export function useDataSource(
|
||||
pageParams[sizeField] = pageSize;
|
||||
}
|
||||
|
||||
const params = {
|
||||
let params = {
|
||||
...pageParams,
|
||||
...opt,
|
||||
};
|
||||
if (beforeRequest && isFunction(beforeRequest)) {
|
||||
// The params parameter can be modified by outsiders
|
||||
params = (await beforeRequest(params)) || params;
|
||||
}
|
||||
const res = await request(params);
|
||||
|
||||
const resultTotal = res[totalField] || 0;
|
||||
const resultTotal = res[totalField];
|
||||
const currentPage = res[pageField];
|
||||
const total = res[itemCount];
|
||||
const results = res[listField] ? res[listField] : [];
|
||||
|
||||
// 如果数据异常,需获取正确的页码再次执行
|
||||
if (resultTotal) {
|
||||
if (page > resultTotal) {
|
||||
const currentTotalPage = Math.ceil(total / pageSize);
|
||||
if (page > currentTotalPage) {
|
||||
setPagination({
|
||||
[pageField]: resultTotal,
|
||||
page: currentTotalPage,
|
||||
itemCount: total,
|
||||
});
|
||||
fetch(opt);
|
||||
return await fetch(opt);
|
||||
}
|
||||
}
|
||||
const resultInfo = res[listField] ? res[listField] : [];
|
||||
let resultInfo = res[listField] ? res[listField] : [];
|
||||
if (afterRequest && isFunction(afterRequest)) {
|
||||
// can modify the data returned by the interface for processing
|
||||
resultInfo = (await afterRequest(resultInfo)) || resultInfo;
|
||||
}
|
||||
dataSourceRef.value = resultInfo;
|
||||
setPagination({
|
||||
[pageField]: currentPage,
|
||||
[totalField]: resultTotal,
|
||||
page: currentPage,
|
||||
pageCount: resultTotal,
|
||||
itemCount: total,
|
||||
});
|
||||
if (opt && opt[pageField]) {
|
||||
setPagination({
|
||||
[pageField]: opt[pageField] || 1,
|
||||
page: opt[pageField] || 1,
|
||||
});
|
||||
}
|
||||
emit('fetch-success', {
|
||||
@@ -100,9 +113,9 @@ export function useDataSource(
|
||||
console.error(error);
|
||||
emit('fetch-error', error);
|
||||
dataSourceRef.value = [];
|
||||
// setPagination({
|
||||
// pageCount: 0,
|
||||
// });
|
||||
setPagination({
|
||||
pageCount: 0,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,40 @@
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { computed, unref, ref, ComputedRef } from 'vue';
|
||||
import { computed, unref, ref, ComputedRef, watch } from 'vue';
|
||||
|
||||
import { isBoolean } from '@/utils/is';
|
||||
import { APISETTING, DEFAULTPAGESIZE, PAGESIZES } from '../const';
|
||||
import { DEFAULTPAGESIZE, PAGESIZES } from '../const';
|
||||
|
||||
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
||||
const configRef = ref<PaginationProps>({});
|
||||
const show = ref(true);
|
||||
|
||||
watch(
|
||||
() => unref(refProps).pagination,
|
||||
(pagination) => {
|
||||
if (!isBoolean(pagination) && pagination) {
|
||||
configRef.value = {
|
||||
...unref(configRef),
|
||||
...(pagination ?? {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const getPaginationInfo = computed((): PaginationProps | boolean => {
|
||||
const { pagination } = unref(refProps);
|
||||
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
|
||||
return false;
|
||||
}
|
||||
const { totalField } = APISETTING;
|
||||
return {
|
||||
pageSize: DEFAULTPAGESIZE,
|
||||
pageSizes: PAGESIZES,
|
||||
page: 1, //当前页
|
||||
pageSize: DEFAULTPAGESIZE, //分页大小
|
||||
pageSizes: PAGESIZES, // 每页条数
|
||||
showSizePicker: true,
|
||||
showQuickJumper: true,
|
||||
prefix: (pagingInfo) => `共 ${pagingInfo.itemCount} 条`, // 不需要可以通过 pagination 重置或者删除
|
||||
...(isBoolean(pagination) ? {} : pagination),
|
||||
...unref(configRef),
|
||||
pageCount: unref(configRef)[totalField],
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -25,10 +25,18 @@ export const basicProps = {
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
beforeRequest: {
|
||||
type: Function as PropType<(...arg: any[]) => void | Promise<any>>,
|
||||
default: null,
|
||||
},
|
||||
request: {
|
||||
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
||||
default: null,
|
||||
},
|
||||
afterRequest: {
|
||||
type: Function as PropType<(...arg: any[]) => void | Promise<any>>,
|
||||
default: null,
|
||||
},
|
||||
rowKey: {
|
||||
type: [String, Function] as PropType<string | ((record) => string)>,
|
||||
default: undefined,
|
||||
|
||||
@@ -5,4 +5,5 @@ export type ComponentType =
|
||||
| 'NCheckbox'
|
||||
| 'NSwitch'
|
||||
| 'NDatePicker'
|
||||
| 'NTimePicker';
|
||||
| 'NTimePicker'
|
||||
| 'NCascader';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
export interface PaginationProps {
|
||||
page?: number;
|
||||
pageCount?: number;
|
||||
pageSize?: number;
|
||||
pageSizes?: number[];
|
||||
showSizePicker?: boolean;
|
||||
showQuickJumper?: boolean;
|
||||
page?: number; //受控模式下的当前页
|
||||
itemCount?: number; //总条数
|
||||
pageCount?: number; //总页数
|
||||
pageSize?: number; //受控模式下的分页大小
|
||||
pageSizes?: number[]; //每页条数, 可自定义
|
||||
showSizePicker?: boolean; //是否显示每页条数的选择器
|
||||
showQuickJumper?: boolean; //是否显示快速跳转
|
||||
prefix?: any; //分页前缀
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
|
||||
import type { InternalRowData, TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
|
||||
import { ComponentType } from './componentType';
|
||||
export interface BasicColumn extends TableBaseColumn {
|
||||
export interface BasicColumn<T = InternalRowData> extends TableBaseColumn<T> {
|
||||
//编辑表格
|
||||
edit?: boolean;
|
||||
editRow?: boolean;
|
||||
@@ -34,4 +34,5 @@ export interface BasicTableProps {
|
||||
actionColumn: any[];
|
||||
canResize: boolean;
|
||||
resizeHeightOffset: number;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { NButton } from 'naive-ui';
|
||||
import type { Component } from 'vue';
|
||||
import { PermissionsEnum } from '@/enums/permissionsEnum';
|
||||
export interface ActionItem extends NButton.props {
|
||||
export interface ActionItem extends Partial<InstanceType<typeof NButton>> {
|
||||
onClick?: Fn;
|
||||
label?: string;
|
||||
color?: 'success' | 'error' | 'warning';
|
||||
icon?: string;
|
||||
type?: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'default';
|
||||
// 设定 color 后会覆盖 type 的样式
|
||||
color?: string;
|
||||
icon?: Component;
|
||||
popConfirm?: PopConfirm;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
@@ -20,5 +23,5 @@ export interface PopConfirm {
|
||||
cancelText?: string;
|
||||
confirm: Fn;
|
||||
cancel?: Fn;
|
||||
icon?: string;
|
||||
icon?: Component;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
v-if="imgList.length < maxNumber"
|
||||
>
|
||||
<n-upload
|
||||
class="w-auto"
|
||||
v-bind="$props"
|
||||
:file-list-style="{ display: 'none' }"
|
||||
@before-upload="beforeUpload"
|
||||
@@ -109,10 +110,11 @@
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
imgList.value = props.value.map((item) => {
|
||||
state.imgList = props.value.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
//预览
|
||||
@@ -236,6 +238,7 @@
|
||||
&-info {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
9
src/config/website.config.ts
Normal file
9
src/config/website.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import logoImage from '@/assets/images/logo.png';
|
||||
import loginImage from '@/assets/images/account-logo.png';
|
||||
|
||||
export const websiteConfig = Object.freeze({
|
||||
title: 'NaiveUiAdmin',
|
||||
logo: logoImage,
|
||||
loginImage: loginImage,
|
||||
loginDesc: 'Naive Ui Admin中后台前端/设计解决方案',
|
||||
});
|
||||
34
src/directives/copy.ts
Normal file
34
src/directives/copy.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* v-copy
|
||||
* 复制某个值至剪贴板
|
||||
* 接收参数:string类型/Ref<string>类型/Reactive<string>类型
|
||||
*/
|
||||
import type { Directive, DirectiveBinding } from 'vue';
|
||||
|
||||
interface ElType extends HTMLElement {
|
||||
copyData: string | number;
|
||||
__handleClick__: any;
|
||||
}
|
||||
const copy: Directive = {
|
||||
mounted(el: ElType, binding: DirectiveBinding) {
|
||||
el.copyData = binding.value;
|
||||
el.addEventListener('click', handleClick);
|
||||
},
|
||||
updated(el: ElType, binding: DirectiveBinding) {
|
||||
el.copyData = binding.value;
|
||||
},
|
||||
beforeUnmount(el: ElType) {
|
||||
el.removeEventListener('click', el.__handleClick__);
|
||||
},
|
||||
};
|
||||
function handleClick(this: any) {
|
||||
const input = document.createElement('input');
|
||||
input.value = this.copyData.toLocaleString();
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
document.execCommand('Copy');
|
||||
document.body.removeChild(input);
|
||||
console.log('复制成功', this.copyData);
|
||||
}
|
||||
|
||||
export default copy;
|
||||
31
src/directives/debounce.ts
Normal file
31
src/directives/debounce.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* v-debounce
|
||||
* 按钮防抖指令,可自行扩展至input
|
||||
* 接收参数:function类型
|
||||
*/
|
||||
import type { Directive, DirectiveBinding } from 'vue';
|
||||
interface ElType extends HTMLElement {
|
||||
__handleClick__: () => any;
|
||||
}
|
||||
const debounce: Directive = {
|
||||
mounted(el: ElType, binding: DirectiveBinding) {
|
||||
if (typeof binding.value !== 'function') {
|
||||
throw 'callback must be a function';
|
||||
}
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
el.__handleClick__ = function () {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
binding.value();
|
||||
}, 500);
|
||||
};
|
||||
el.addEventListener('click', el.__handleClick__);
|
||||
},
|
||||
beforeUnmount(el: ElType) {
|
||||
el.removeEventListener('click', el.__handleClick__);
|
||||
},
|
||||
};
|
||||
|
||||
export default debounce;
|
||||
49
src/directives/draggable.ts
Normal file
49
src/directives/draggable.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
|
||||
|
||||
思路:
|
||||
1、设置需要拖拽的元素为absolute,其父元素为relative。
|
||||
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
|
||||
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
|
||||
4、鼠标松开(onmouseup)时完成一次拖拽
|
||||
|
||||
使用:在 Dom 上加上 v-draggable 即可
|
||||
<div class="dialog-model" v-draggable></div>
|
||||
*/
|
||||
import type { Directive } from 'vue';
|
||||
interface ElType extends HTMLElement {
|
||||
parentNode: any;
|
||||
}
|
||||
const draggable: Directive = {
|
||||
mounted: function (el: ElType) {
|
||||
el.style.cursor = 'move';
|
||||
el.style.position = 'absolute';
|
||||
el.onmousedown = function (e) {
|
||||
const disX = e.pageX - el.offsetLeft;
|
||||
const disY = e.pageY - el.offsetTop;
|
||||
document.onmousemove = function (e) {
|
||||
let x = e.pageX - disX;
|
||||
let y = e.pageY - disY;
|
||||
const maxX = el.parentNode.offsetWidth - el.offsetWidth;
|
||||
const maxY = el.parentNode.offsetHeight - el.offsetHeight;
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
} else if (x > maxX) {
|
||||
x = maxX;
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
} else if (y > maxY) {
|
||||
y = maxY;
|
||||
}
|
||||
el.style.left = x + 'px';
|
||||
el.style.top = y + 'px';
|
||||
};
|
||||
document.onmouseup = function () {
|
||||
document.onmousemove = document.onmouseup = null;
|
||||
};
|
||||
};
|
||||
},
|
||||
};
|
||||
export default draggable;
|
||||
49
src/directives/longpress.ts
Normal file
49
src/directives/longpress.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* v-longpress
|
||||
* 长按指令,长按时触发事件
|
||||
*/
|
||||
import type { Directive, DirectiveBinding } from 'vue';
|
||||
|
||||
const directive: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
if (typeof binding.value !== 'function') {
|
||||
throw 'callback must be a function';
|
||||
}
|
||||
// 定义变量
|
||||
let pressTimer: any = null;
|
||||
// 创建计时器( 2秒后执行函数 )
|
||||
const start = (e: any) => {
|
||||
if (e.button) {
|
||||
if (e.type === 'click' && e.button !== 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pressTimer === null) {
|
||||
pressTimer = setTimeout(() => {
|
||||
handler(e);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
// 取消计时器
|
||||
const cancel = () => {
|
||||
if (pressTimer !== null) {
|
||||
clearTimeout(pressTimer);
|
||||
pressTimer = null;
|
||||
}
|
||||
};
|
||||
// 运行函数
|
||||
const handler = (e: MouseEvent | TouchEvent) => {
|
||||
binding.value(e);
|
||||
};
|
||||
// 添加事件监听器
|
||||
el.addEventListener('mousedown', start);
|
||||
el.addEventListener('touchstart', start);
|
||||
// 取消计时器
|
||||
el.addEventListener('click', cancel);
|
||||
el.addEventListener('mouseout', cancel);
|
||||
el.addEventListener('touchend', cancel);
|
||||
el.addEventListener('touchcancel', cancel);
|
||||
},
|
||||
};
|
||||
|
||||
export default directive;
|
||||
41
src/directives/throttle.ts
Normal file
41
src/directives/throttle.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
|
||||
|
||||
思路:
|
||||
1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮
|
||||
2、将需要触发的方法绑定在指令上
|
||||
|
||||
使用:给 Dom 加上 v-throttle 及回调函数即可
|
||||
<button v-throttle="debounceClick">节流提交</button>
|
||||
*/
|
||||
import type { Directive, DirectiveBinding } from 'vue';
|
||||
interface ElType extends HTMLElement {
|
||||
__handleClick__: () => any;
|
||||
disabled: boolean;
|
||||
}
|
||||
const throttle: Directive = {
|
||||
mounted(el: ElType, binding: DirectiveBinding) {
|
||||
if (typeof binding.value !== 'function') {
|
||||
throw 'callback must be a function';
|
||||
}
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
el.__handleClick__ = function () {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
if (!el.disabled) {
|
||||
el.disabled = true;
|
||||
binding.value();
|
||||
timer = setTimeout(() => {
|
||||
el.disabled = false;
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
el.addEventListener('click', el.__handleClick__);
|
||||
},
|
||||
beforeUnmount(el: ElType) {
|
||||
el.removeEventListener('click', el.__handleClick__);
|
||||
},
|
||||
};
|
||||
|
||||
export default throttle;
|
||||
4
src/enums/permissionsEnum.ts
Normal file
4
src/enums/permissionsEnum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface PermissionsEnum {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
import { useAsync } from './use-async';
|
||||
import { useAsync } from './useAsync';
|
||||
|
||||
export { useAsync };
|
||||
|
||||
@@ -4,39 +4,39 @@ import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
export function useProjectSetting() {
|
||||
const projectStore = useProjectSettingStore();
|
||||
|
||||
const getNavMode = computed(() => projectStore.navMode);
|
||||
const navMode = computed(() => projectStore.navMode);
|
||||
|
||||
const getNavTheme = computed(() => projectStore.navTheme);
|
||||
const navTheme = computed(() => projectStore.navTheme);
|
||||
|
||||
const getIsMobile = computed(() => projectStore.isMobile);
|
||||
const isMobile = computed(() => projectStore.isMobile);
|
||||
|
||||
const getHeaderSetting = computed(() => projectStore.headerSetting);
|
||||
const headerSetting = computed(() => projectStore.headerSetting);
|
||||
|
||||
const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting);
|
||||
const multiTabsSetting = computed(() => projectStore.multiTabsSetting);
|
||||
|
||||
const getMenuSetting = computed(() => projectStore.menuSetting);
|
||||
const menuSetting = computed(() => projectStore.menuSetting);
|
||||
|
||||
const getCrumbsSetting = computed(() => projectStore.crumbsSetting);
|
||||
const crumbsSetting = computed(() => projectStore.crumbsSetting);
|
||||
|
||||
const getPermissionMode = computed(() => projectStore.permissionMode);
|
||||
const permissionMode = computed(() => projectStore.permissionMode);
|
||||
|
||||
const getShowFooter = computed(() => projectStore.showFooter);
|
||||
const showFooter = computed(() => projectStore.showFooter);
|
||||
|
||||
const getIsPageAnimate = computed(() => projectStore.isPageAnimate);
|
||||
const isPageAnimate = computed(() => projectStore.isPageAnimate);
|
||||
|
||||
const getPageAnimateType = computed(() => projectStore.pageAnimateType);
|
||||
const pageAnimateType = computed(() => projectStore.pageAnimateType);
|
||||
|
||||
return {
|
||||
getNavMode,
|
||||
getNavTheme,
|
||||
getIsMobile,
|
||||
getHeaderSetting,
|
||||
getMultiTabsSetting,
|
||||
getMenuSetting,
|
||||
getCrumbsSetting,
|
||||
getPermissionMode,
|
||||
getShowFooter,
|
||||
getIsPageAnimate,
|
||||
getPageAnimateType,
|
||||
navMode,
|
||||
navTheme,
|
||||
isMobile,
|
||||
headerSetting,
|
||||
multiTabsSetting,
|
||||
menuSetting,
|
||||
crumbsSetting,
|
||||
permissionMode,
|
||||
showFooter,
|
||||
isPageAnimate,
|
||||
pageAnimateType,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { debounce } from 'lodash';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* description: 获取页面宽度
|
||||
|
||||
@@ -10,23 +10,27 @@ import { useBreakpoint } from '@/hooks/event/useBreakpoint';
|
||||
|
||||
import echarts from '@/utils/lib/echarts';
|
||||
|
||||
// import { useRootSetting } from '@/hooks/setting/useRootSetting';
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||
|
||||
export function useECharts(
|
||||
elRef: Ref<HTMLDivElement>,
|
||||
theme: 'light' | 'dark' | 'default' = 'light'
|
||||
theme: 'light' | 'dark' | 'default' = 'default'
|
||||
) {
|
||||
// const { getDarkMode } = useRootSetting();
|
||||
const getDarkMode = 'light';
|
||||
const { getDarkTheme: getSysDarkTheme } = useDesignSetting();
|
||||
|
||||
const getDarkTheme = computed(() => {
|
||||
const sysTheme: string = getSysDarkTheme.value ? 'dark' : 'light';
|
||||
return theme === 'default' ? sysTheme : theme;
|
||||
});
|
||||
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let resizeFn: Fn = resize;
|
||||
const cacheOptions = ref<EChartsOption>({});
|
||||
const cacheOptions = ref({});
|
||||
let removeResizeFn: Fn = () => {};
|
||||
|
||||
resizeFn = useDebounceFn(resize, 200);
|
||||
|
||||
const getOptions = computed((): EChartsOption => {
|
||||
if (getDarkMode !== 'dark') {
|
||||
if (getDarkTheme.value !== 'dark') {
|
||||
return cacheOptions.value;
|
||||
}
|
||||
return {
|
||||
@@ -67,7 +71,7 @@ export function useECharts(
|
||||
nextTick(() => {
|
||||
useTimeoutFn(() => {
|
||||
if (!chartInstance) {
|
||||
initCharts(getDarkMode.value as 'default');
|
||||
initCharts(getDarkTheme.value as 'default');
|
||||
|
||||
if (!chartInstance) return;
|
||||
}
|
||||
@@ -83,7 +87,7 @@ export function useECharts(
|
||||
}
|
||||
|
||||
watch(
|
||||
() => getDarkMode.value,
|
||||
() => getDarkTheme.value,
|
||||
(theme) => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
@@ -93,18 +97,20 @@ export function useECharts(
|
||||
}
|
||||
);
|
||||
|
||||
tryOnUnmounted(() => {
|
||||
tryOnUnmounted(disposeInstance);
|
||||
|
||||
function getInstance(): echarts.ECharts | null {
|
||||
if (!chartInstance) {
|
||||
initCharts(getDarkTheme.value as 'default');
|
||||
}
|
||||
return chartInstance;
|
||||
}
|
||||
|
||||
function disposeInstance() {
|
||||
if (!chartInstance) return;
|
||||
removeResizeFn();
|
||||
chartInstance.dispose();
|
||||
chartInstance = null;
|
||||
});
|
||||
|
||||
function getInstance(): echarts.ECharts | null {
|
||||
if (!chartInstance) {
|
||||
initCharts(getDarkMode.value as 'default');
|
||||
}
|
||||
return chartInstance;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -112,5 +118,6 @@ export function useECharts(
|
||||
resize,
|
||||
echarts,
|
||||
getInstance,
|
||||
disposeInstance,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,8 +259,7 @@
|
||||
title: props.title,
|
||||
isDrawer: false,
|
||||
placement: 'right',
|
||||
alertText:
|
||||
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
|
||||
alertText: '该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置',
|
||||
appThemeList: designStore.appThemeList,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
|
||||
>
|
||||
<div class="logo" v-if="navMode === 'horizontal'">
|
||||
<img src="~@/assets/images/logo.png" alt="" />
|
||||
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
|
||||
<img :src="websiteConfig.logo" alt="" />
|
||||
<h2 v-show="!collapsed" class="title">{{ websiteConfig.title }}</h2>
|
||||
</div>
|
||||
<AsideMenu
|
||||
v-model:collapsed="collapsed"
|
||||
:collapsed="collapsed"
|
||||
v-model:location="getMenuLocation"
|
||||
:inverted="getInverted"
|
||||
mode="horizontal"
|
||||
@@ -21,7 +21,7 @@
|
||||
<!-- 菜单收起 -->
|
||||
<div
|
||||
class="ml-1 layout-header-trigger layout-header-trigger-min"
|
||||
@click="() => $emit('update:collapsed', !collapsed)"
|
||||
@click="handleMenuCollapsed"
|
||||
>
|
||||
<n-icon size="18" v-if="collapsed">
|
||||
<MenuUnfoldOutlined />
|
||||
@@ -42,8 +42,11 @@
|
||||
</div>
|
||||
<!-- 面包屑 -->
|
||||
<n-breadcrumb v-if="crumbsSetting.show">
|
||||
<template v-for="routeItem in breadcrumbList" :key="routeItem.name">
|
||||
<n-breadcrumb-item>
|
||||
<template
|
||||
v-for="routeItem in breadcrumbList"
|
||||
:key="routeItem.name === 'Redirect' ? void 0 : routeItem.name"
|
||||
>
|
||||
<n-breadcrumb-item v-if="routeItem.meta.title">
|
||||
<n-dropdown
|
||||
v-if="routeItem.children.length"
|
||||
:options="routeItem.children"
|
||||
@@ -72,7 +75,7 @@
|
||||
<div
|
||||
class="layout-header-trigger layout-header-trigger-min"
|
||||
v-for="item in iconList"
|
||||
:key="item.icon.name"
|
||||
:key="item.icon"
|
||||
>
|
||||
<n-tooltip placement="bottom">
|
||||
<template #trigger>
|
||||
@@ -98,12 +101,14 @@
|
||||
<div class="layout-header-trigger layout-header-trigger-min">
|
||||
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
||||
<div class="avatar">
|
||||
<n-avatar round>
|
||||
{{ username }}
|
||||
<n-avatar round :src="websiteConfig.logo">
|
||||
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</n-avatar>
|
||||
<n-divider vertical />
|
||||
<span>{{ username }}</span>
|
||||
</div>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
@@ -131,10 +136,11 @@
|
||||
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
|
||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||
import { useScreenLockStore } from '@/store/modules/screenLock';
|
||||
import ProjectSetting from './ProjectSetting.vue';
|
||||
import { AsideMenu } from '@/layout/components/Menu';
|
||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||
import { websiteConfig } from '@/config/website.config';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageHeader',
|
||||
@@ -147,39 +153,38 @@
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
emits: ['update:collapsed'],
|
||||
setup(props, { emit }) {
|
||||
const userStore = useUserStore();
|
||||
const useLockscreen = useLockscreenStore();
|
||||
const useLockscreen = useScreenLockStore();
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const { getNavMode, getNavTheme, getHeaderSetting, getMenuSetting, getCrumbsSetting } =
|
||||
useProjectSetting();
|
||||
|
||||
const { username } = userStore?.info || {};
|
||||
const { navMode, navTheme, headerSetting, menuSetting, crumbsSetting } = useProjectSetting();
|
||||
|
||||
const drawerSetting = ref();
|
||||
|
||||
const state = reactive({
|
||||
username: username || '',
|
||||
username: userStore?.info?.username ?? '',
|
||||
fullscreenIcon: 'FullscreenOutlined',
|
||||
navMode: getNavMode,
|
||||
navTheme: getNavTheme,
|
||||
headerSetting: getHeaderSetting,
|
||||
crumbsSetting: getCrumbsSetting,
|
||||
navMode,
|
||||
navTheme,
|
||||
headerSetting,
|
||||
crumbsSetting,
|
||||
});
|
||||
|
||||
const getInverted = computed(() => {
|
||||
const navTheme = unref(getNavTheme);
|
||||
return ['light', 'header-dark'].includes(navTheme) ? props.inverted : !props.inverted;
|
||||
return ['light', 'header-dark'].includes(unref(navTheme))
|
||||
? props.inverted
|
||||
: !props.inverted;
|
||||
});
|
||||
|
||||
const mixMenu = computed(() => {
|
||||
return unref(getMenuSetting).mixMenu;
|
||||
return unref(menuSetting).mixMenu;
|
||||
});
|
||||
|
||||
const getChangeStyle = computed(() => {
|
||||
const { collapsed } = props;
|
||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
||||
const { minMenuWidth, menuWidth } = unref(menuSetting);
|
||||
return {
|
||||
left: collapsed ? `${minMenuWidth}px` : `${menuWidth}px`,
|
||||
width: `calc(100% - ${collapsed ? `${minMenuWidth}px` : `${menuWidth}px`})`,
|
||||
@@ -319,6 +324,10 @@
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
function handleMenuCollapsed() {
|
||||
emit('update:collapsed', !props.collapsed);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
iconList,
|
||||
@@ -336,6 +345,8 @@
|
||||
getInverted,
|
||||
getMenuLocation,
|
||||
mixMenu,
|
||||
websiteConfig,
|
||||
handleMenuCollapsed,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -347,7 +358,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
height: @header-height;
|
||||
height: 64px;
|
||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||
transition: all 0.2s ease-in-out;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="logo">
|
||||
<img src="~@/assets/images/logo.png" alt="" :class="{ 'mr-2': !collapsed }" />
|
||||
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
|
||||
<img :src="websiteConfig.logo" alt="" :class="{ 'mr-2': !collapsed }" />
|
||||
<h2 v-show="!collapsed" class="title">{{ websiteConfig.title }}</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { websiteConfig } from '@/config/website.config';
|
||||
export default {
|
||||
name: 'Index',
|
||||
props: {
|
||||
@@ -13,6 +14,11 @@
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
websiteConfig,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -32,7 +38,7 @@
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
<template>
|
||||
<RouterView>
|
||||
<template #default="{ Component, route }">
|
||||
<transition :name="getTransitionName" mode="out-in" appear>
|
||||
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
|
||||
<template v-if="mode === 'production'">
|
||||
<transition :name="getTransitionName" mode="out-in" appear>
|
||||
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component v-else :is="Component" :key="route.fullPath" />
|
||||
</transition>
|
||||
</template>
|
||||
<template v-else>
|
||||
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component v-else :is="Component" :key="route.fullPath" />
|
||||
</transition>
|
||||
</template>
|
||||
</template>
|
||||
</RouterView>
|
||||
</template>
|
||||
@@ -30,18 +38,20 @@
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getIsPageAnimate, getPageAnimateType } = useProjectSetting();
|
||||
const { isPageAnimate, pageAnimateType } = useProjectSetting();
|
||||
const asyncRouteStore = useAsyncRouteStore();
|
||||
// 需要缓存的路由组件
|
||||
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
|
||||
|
||||
const getTransitionName = computed(() => {
|
||||
return unref(getIsPageAnimate) ? unref(getPageAnimateType) : '';
|
||||
return unref(isPageAnimate) ? unref(pageAnimateType) : '';
|
||||
});
|
||||
|
||||
const mode = import.meta.env.MODE;
|
||||
return {
|
||||
keepAliveComponents,
|
||||
getTransitionName,
|
||||
mode,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Menu',
|
||||
name: 'AppMenu',
|
||||
components: {},
|
||||
props: {
|
||||
mode: {
|
||||
@@ -41,7 +41,7 @@
|
||||
default: 'left',
|
||||
},
|
||||
},
|
||||
emits: ['update:collapsed'],
|
||||
emits: ['update:collapsed', 'clickMenuItem'],
|
||||
setup(props, { emit }) {
|
||||
// 当前路由
|
||||
const currentRoute = useRoute();
|
||||
@@ -52,9 +52,7 @@
|
||||
const selectedKeys = ref<string>(currentRoute.name as string);
|
||||
const headerMenuSelectKey = ref<string>('');
|
||||
|
||||
const { getNavMode } = useProjectSetting();
|
||||
|
||||
const navMode = getNavMode;
|
||||
const { navMode } = useProjectSetting();
|
||||
|
||||
// 获取当前打开的子菜单
|
||||
const matched = currentRoute.matched;
|
||||
@@ -99,13 +97,16 @@
|
||||
() => currentRoute.fullPath,
|
||||
() => {
|
||||
updateMenu();
|
||||
const matched = currentRoute.matched;
|
||||
state.openKeys = matched.map((item) => item.name);
|
||||
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
|
||||
selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string);
|
||||
}
|
||||
);
|
||||
|
||||
function updateSelectedKeys() {
|
||||
const matched = currentRoute.matched;
|
||||
state.openKeys = matched.map((item) => item.name);
|
||||
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
|
||||
selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string);
|
||||
}
|
||||
|
||||
function updateMenu() {
|
||||
if (!settingStore.menuSetting.mixMenu) {
|
||||
menus.value = generatorMenu(asyncRouteStore.getMenus);
|
||||
@@ -116,6 +117,7 @@
|
||||
const activeMenu: string = currentRoute?.matched[0].meta?.activeMenu as string;
|
||||
headerMenuSelectKey.value = (activeMenu ? activeMenu : firstRouteName) || '';
|
||||
}
|
||||
updateSelectedKeys();
|
||||
}
|
||||
|
||||
// 点击菜单
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import {
|
||||
DownOutlined,
|
||||
ReloadOutlined,
|
||||
CloseOutlined,
|
||||
VerticalRightOutlined,
|
||||
VerticalLeftOutlined,
|
||||
ColumnWidthOutlined,
|
||||
MinusOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { Dropdown, Tabs, Card } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
[Tabs.name]: Tabs,
|
||||
[Tabs.TabPane.name]: Tabs.TabPane,
|
||||
[Dropdown.name]: Dropdown,
|
||||
[Card.name]: Card,
|
||||
MinusOutlined,
|
||||
DownOutlined,
|
||||
ReloadOutlined,
|
||||
CloseOutlined,
|
||||
VerticalRightOutlined,
|
||||
VerticalLeftOutlined,
|
||||
ColumnWidthOutlined,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="tabs-view"
|
||||
class="box-border tabs-view"
|
||||
:class="{
|
||||
'tabs-view-fix': multiTabsSetting.fixed,
|
||||
'tabs-view-fixed-header': isMultiHeaderFixed,
|
||||
@@ -35,7 +35,7 @@
|
||||
<div
|
||||
:id="`tag${element.fullPath.split('/').join('\/')}`"
|
||||
class="tabs-card-scroll-item"
|
||||
:class="{ 'active-item': activeKey === element.path }"
|
||||
:class="{ 'active-item': activeKey === element.fullPath }"
|
||||
@click.stop="goPage(element)"
|
||||
@contextmenu="handleContextMenu($event, element)"
|
||||
>
|
||||
@@ -82,7 +82,6 @@
|
||||
computed,
|
||||
ref,
|
||||
toRefs,
|
||||
unref,
|
||||
provide,
|
||||
watch,
|
||||
onMounted,
|
||||
@@ -130,7 +129,7 @@
|
||||
},
|
||||
setup(props) {
|
||||
const { getDarkTheme, getAppTheme } = useDesignSetting();
|
||||
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting, getIsMobile } =
|
||||
const { navMode, headerSetting, menuSetting, multiTabsSetting, isMobile } =
|
||||
useProjectSetting();
|
||||
const settingStore = useProjectSettingStore();
|
||||
|
||||
@@ -161,7 +160,7 @@
|
||||
dropdownY: 0,
|
||||
showDropdown: false,
|
||||
isMultiHeaderFixed: false,
|
||||
multiTabsSetting: getMultiTabsSetting,
|
||||
multiTabsSetting: multiTabsSetting,
|
||||
});
|
||||
|
||||
// 获取简易的路由对象
|
||||
@@ -173,25 +172,23 @@
|
||||
const isMixMenuNoneSub = computed(() => {
|
||||
const mixMenu = settingStore.menuSetting.mixMenu;
|
||||
const currentRoute = useRoute();
|
||||
const navMode = unref(getNavMode);
|
||||
if (unref(navMode) != 'horizontal-mix') return true;
|
||||
return !(unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
|
||||
if (navMode.value != 'horizontal-mix') return true;
|
||||
return !(navMode.value === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
|
||||
});
|
||||
|
||||
//动态组装样式 菜单缩进
|
||||
const getChangeStyle = computed(() => {
|
||||
const { collapsed } = props;
|
||||
const navMode = unref(getNavMode);
|
||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
||||
const { fixed }: any = unref(getMultiTabsSetting);
|
||||
const { minMenuWidth, menuWidth }: any = menuSetting.value;
|
||||
const { fixed }: any = multiTabsSetting.value;
|
||||
let lenNum =
|
||||
navMode === 'horizontal' || !isMixMenuNoneSub.value
|
||||
navMode.value === 'horizontal' || !isMixMenuNoneSub.value
|
||||
? '0px'
|
||||
: collapsed
|
||||
? `${minMenuWidth}px`
|
||||
: `${menuWidth}px`;
|
||||
|
||||
if (getIsMobile.value) {
|
||||
if (isMobile.value) {
|
||||
return {
|
||||
left: '0px',
|
||||
width: '100%',
|
||||
@@ -205,7 +202,7 @@
|
||||
|
||||
//tags 右侧下拉菜单
|
||||
const TabsMenuOptions = computed(() => {
|
||||
const isDisabled = unref(tabsList).length <= 1;
|
||||
const isDisabled = tabsList.value.length <= 1;
|
||||
return [
|
||||
{
|
||||
label: '刷新当前',
|
||||
@@ -215,7 +212,7 @@
|
||||
{
|
||||
label: `关闭当前`,
|
||||
key: '2',
|
||||
disabled: unref(isCurrent) || isDisabled,
|
||||
disabled: isCurrent.value || isDisabled,
|
||||
icon: renderIcon(CloseOutlined),
|
||||
},
|
||||
{
|
||||
@@ -263,8 +260,8 @@
|
||||
window.pageYOffset ||
|
||||
document.body.scrollTop; // 滚动条偏移量
|
||||
state.isMultiHeaderFixed = !!(
|
||||
!getHeaderSetting.value.fixed &&
|
||||
getMultiTabsSetting.value.fixed &&
|
||||
!headerSetting.value.fixed &&
|
||||
multiTabsSetting.value.fixed &&
|
||||
scrollTop >= 64
|
||||
);
|
||||
}
|
||||
@@ -297,7 +294,7 @@
|
||||
(to) => {
|
||||
if (whiteList.includes(route.name as string)) return;
|
||||
state.activeKey = to;
|
||||
tabsViewStore.addTabs(getSimpleRoute(route));
|
||||
tabsViewStore.addTab(getSimpleRoute(route));
|
||||
updateNavScroll(true);
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -328,7 +325,7 @@
|
||||
const reloadPage = () => {
|
||||
delKeepAliveCompName();
|
||||
router.push({
|
||||
path: '/redirect' + unref(route).fullPath,
|
||||
path: '/redirect' + route.fullPath,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -642,7 +639,6 @@
|
||||
background: var(--color);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
//margin-right: 10px;
|
||||
|
||||
&-btn {
|
||||
color: var(--color);
|
||||
@@ -665,7 +661,7 @@
|
||||
.tabs-view-fix {
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
padding: 6px 19px 6px 10px;
|
||||
padding: 6px 10px 6px 10px;
|
||||
left: 200px;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,22 @@
|
||||
</n-layout-sider>
|
||||
|
||||
<n-drawer
|
||||
v-model:show="showSideDrawder"
|
||||
v-model:show="showSideDrawer"
|
||||
:width="menuWidth"
|
||||
:placement="'left'"
|
||||
class="layout-side-drawer"
|
||||
>
|
||||
<Logo :collapsed="collapsed" />
|
||||
<AsideMenu @clickMenuItem="collapsed = false" />
|
||||
<n-layout-sider
|
||||
:position="fixedMenu"
|
||||
:collapsed="false"
|
||||
:width="menuWidth"
|
||||
:native-scrollbar="false"
|
||||
:inverted="inverted"
|
||||
class="layout-sider"
|
||||
>
|
||||
<Logo :collapsed="collapsed" />
|
||||
<AsideMenu v-model:location="getMenuLocation" />
|
||||
</n-layout-sider>
|
||||
</n-drawer>
|
||||
|
||||
<n-layout :inverted="inverted">
|
||||
@@ -77,27 +86,24 @@
|
||||
import { PageHeader } from './components/Header';
|
||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||
import { useLoadingBar } from 'naive-ui';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
|
||||
const { getDarkTheme } = useDesignSetting();
|
||||
const {
|
||||
// getShowFooter,
|
||||
getNavMode,
|
||||
getNavTheme,
|
||||
getHeaderSetting,
|
||||
getMenuSetting,
|
||||
getMultiTabsSetting,
|
||||
// showFooter,
|
||||
navMode,
|
||||
navTheme,
|
||||
headerSetting,
|
||||
menuSetting,
|
||||
multiTabsSetting,
|
||||
} = useProjectSetting();
|
||||
|
||||
const settingStore = useProjectSettingStore();
|
||||
|
||||
const navMode = getNavMode;
|
||||
|
||||
const collapsed = ref<boolean>(false);
|
||||
|
||||
const { mobileWidth, menuWidth } = unref(getMenuSetting);
|
||||
const { mobileWidth, menuWidth } = unref(menuSetting);
|
||||
|
||||
const isMobile = computed<boolean>({
|
||||
get: () => settingStore.getIsMobile,
|
||||
@@ -105,12 +111,12 @@
|
||||
});
|
||||
|
||||
const fixedHeader = computed(() => {
|
||||
const { fixed } = unref(getHeaderSetting);
|
||||
const { fixed } = unref(headerSetting);
|
||||
return fixed ? 'absolute' : 'static';
|
||||
});
|
||||
|
||||
const isMixMenuNoneSub = computed(() => {
|
||||
const mixMenu = settingStore.menuSetting.mixMenu;
|
||||
const mixMenu = unref(menuSetting).mixMenu;
|
||||
const currentRoute = useRoute();
|
||||
if (unref(navMode) != 'horizontal-mix') return true;
|
||||
if (unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot) {
|
||||
@@ -120,45 +126,37 @@
|
||||
});
|
||||
|
||||
const fixedMenu = computed(() => {
|
||||
const { fixed } = unref(getHeaderSetting);
|
||||
const { fixed } = unref(headerSetting);
|
||||
return fixed ? 'absolute' : 'static';
|
||||
});
|
||||
|
||||
const isMultiTabs = computed(() => {
|
||||
return unref(getMultiTabsSetting).show;
|
||||
return unref(multiTabsSetting).show;
|
||||
});
|
||||
|
||||
const fixedMulti = computed(() => {
|
||||
return unref(getMultiTabsSetting).fixed;
|
||||
return unref(multiTabsSetting).fixed;
|
||||
});
|
||||
|
||||
const inverted = computed(() => {
|
||||
return ['dark', 'header-dark'].includes(unref(getNavTheme));
|
||||
return ['dark', 'header-dark'].includes(unref(navTheme));
|
||||
});
|
||||
|
||||
const getHeaderInverted = computed(() => {
|
||||
const navTheme = unref(getNavTheme);
|
||||
return ['light', 'header-dark'].includes(navTheme) ? unref(inverted) : !unref(inverted);
|
||||
return ['light', 'header-dark'].includes(unref(navTheme)) ? unref(inverted) : !unref(inverted);
|
||||
});
|
||||
|
||||
const leftMenuWidth = computed(() => {
|
||||
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
||||
const { minMenuWidth, menuWidth } = unref(menuSetting);
|
||||
return collapsed.value ? minMenuWidth : menuWidth;
|
||||
});
|
||||
|
||||
// const getChangeStyle = computed(() => {
|
||||
// const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
||||
// return {
|
||||
// 'padding-left': collapsed.value ? `${minMenuWidth}px` : `${menuWidth}px`,
|
||||
// };
|
||||
// });
|
||||
|
||||
const getMenuLocation = computed(() => {
|
||||
return 'left';
|
||||
});
|
||||
|
||||
// 控制显示或隐藏移动端侧边栏
|
||||
const showSideDrawder = computed({
|
||||
const showSideDrawer = computed({
|
||||
get: () => isMobile.value && collapsed.value,
|
||||
set: (val) => (collapsed.value = val),
|
||||
});
|
||||
@@ -185,9 +183,6 @@
|
||||
onMounted(() => {
|
||||
checkMobileMode();
|
||||
window.addEventListener('resize', watchWidth);
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$loading'] = useLoadingBar();
|
||||
window['$loading'].finish();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
28
src/main.ts
28
src/main.ts
@@ -1,19 +1,23 @@
|
||||
import './styles/tailwind.css';
|
||||
import './styles/index.less';
|
||||
import { createApp } from 'vue';
|
||||
import { setupNaiveDiscreteApi, setupNaive, setupDirectives } from '@/plugins';
|
||||
import App from './App.vue';
|
||||
import router, { setupRouter } from './router';
|
||||
import { setupStore } from '@/store';
|
||||
import { setupNaive, setupDirectives } from '@/plugins';
|
||||
import { AppProvider } from '@/components/Application';
|
||||
|
||||
async function bootstrap() {
|
||||
const appProvider = createApp(AppProvider);
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// 挂载状态管理
|
||||
setupStore(app);
|
||||
|
||||
// 注册全局常用的 naive-ui 组件
|
||||
setupNaive(app);
|
||||
|
||||
// 挂载 naive-ui 脱离上下文的 Api
|
||||
setupNaiveDiscreteApi();
|
||||
|
||||
// 注册全局自定义组件
|
||||
//setupCustomComponents();
|
||||
|
||||
@@ -23,18 +27,18 @@ async function bootstrap() {
|
||||
// 注册全局方法,如:app.config.globalProperties.$message = message
|
||||
//setupGlobalMethods(app);
|
||||
|
||||
// 挂载状态管理
|
||||
setupStore(app);
|
||||
|
||||
//优先挂载一下 Provider 解决路由守卫,Axios中可使用,Dialog,Message 等之类组件
|
||||
appProvider.mount('#appProvider', true);
|
||||
|
||||
// 挂载路由
|
||||
await setupRouter(app);
|
||||
setupRouter(app);
|
||||
|
||||
// 路由准备就绪后挂载APP实例
|
||||
// 路由准备就绪后挂载 APP 实例
|
||||
// https://router.vuejs.org/api/interfaces/router.html#isready
|
||||
await router.isReady();
|
||||
|
||||
// https://www.naiveui.com/en-US/os-theme/docs/style-conflict#About-Tailwind's-Preflight-Style-Override
|
||||
const meta = document.createElement('meta');
|
||||
meta.name = 'naive-ui-style';
|
||||
document.head.appendChild(meta);
|
||||
|
||||
app.mount('#app', true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { App } from 'vue';
|
||||
|
||||
import { permission } from '@/directives/permission';
|
||||
import copy from '@/directives/copy';
|
||||
import debounce from '@/directives/debounce';
|
||||
import throttle from '@/directives/throttle';
|
||||
import draggable from '@/directives/draggable';
|
||||
|
||||
/**
|
||||
* 注册全局自定义指令
|
||||
@@ -9,4 +13,12 @@ import { permission } from '@/directives/permission';
|
||||
export function setupDirectives(app: App) {
|
||||
// 权限控制指令(演示)
|
||||
app.directive('permission', permission);
|
||||
// 复制指令
|
||||
app.directive('copy', copy);
|
||||
// 防抖指令
|
||||
app.directive('debounce', debounce);
|
||||
// 节流指令
|
||||
app.directive('throttle', throttle);
|
||||
// 拖拽指令
|
||||
app.directive('draggable', draggable);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { setupNaive } from '@/plugins/naive';
|
||||
export { setupNaiveDiscreteApi } from '@/plugins/naiveDiscreteApi';
|
||||
export { setupDirectives } from '@/plugins/directives';
|
||||
export { setupCustomComponents } from '@/plugins/customComponents';
|
||||
export { setupGlobalMethods } from '@/plugins/globalMethods';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { App } from 'vue';
|
||||
import {
|
||||
create,
|
||||
NConfigProvider,
|
||||
NMessageProvider,
|
||||
NDialogProvider,
|
||||
NConfigProvider,
|
||||
NInput,
|
||||
NButton,
|
||||
NForm,
|
||||
@@ -66,8 +66,10 @@ import {
|
||||
NTimePicker,
|
||||
NBackTop,
|
||||
NSkeleton,
|
||||
NCascader,
|
||||
} from 'naive-ui';
|
||||
|
||||
// https://www.naiveui.com/en-US/os-theme/docs/import-on-demand
|
||||
const naive = create({
|
||||
components: [
|
||||
NMessageProvider,
|
||||
@@ -135,6 +137,7 @@ const naive = create({
|
||||
NTimePicker,
|
||||
NBackTop,
|
||||
NSkeleton,
|
||||
NCascader,
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
39
src/plugins/naiveDiscreteApi.ts
Normal file
39
src/plugins/naiveDiscreteApi.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as NaiveUI from 'naive-ui';
|
||||
import { computed } from 'vue';
|
||||
import { useDesignSetting } from '@/store/modules/designSetting';
|
||||
import { lighten } from '@/utils/index';
|
||||
|
||||
/**
|
||||
* 挂载 Naive-ui 脱离上下文的 API
|
||||
* 如果你想在 setup 外使用 useDialog、useMessage、useNotification、useLoadingBar,可以通过 createDiscreteApi 来构建对应的 API。
|
||||
* https://www.naiveui.com/zh-CN/dark/components/discrete
|
||||
*/
|
||||
|
||||
export function setupNaiveDiscreteApi() {
|
||||
const designStore = useDesignSetting();
|
||||
|
||||
const configProviderPropsRef = computed(() => ({
|
||||
theme: designStore.darkTheme ? NaiveUI.darkTheme : undefined,
|
||||
themeOverrides: {
|
||||
common: {
|
||||
primaryColor: designStore.appTheme,
|
||||
primaryColorHover: lighten(designStore.appTheme, 6),
|
||||
primaryColorPressed: lighten(designStore.appTheme, 6),
|
||||
},
|
||||
LoadingBar: {
|
||||
colorLoading: designStore.appTheme,
|
||||
},
|
||||
},
|
||||
}));
|
||||
const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
|
||||
['message', 'dialog', 'notification', 'loadingBar'],
|
||||
{
|
||||
configProviderProps: configProviderPropsRef,
|
||||
}
|
||||
);
|
||||
|
||||
window['$message'] = message;
|
||||
window['$dialog'] = dialog;
|
||||
window['$notification'] = notification;
|
||||
window['$loading'] = loadingBar;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AppRouteRecordRaw } from '@/router/types';
|
||||
import { ErrorPage, RedirectName, Layout } from '@/router/constant';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
// 404 on a page
|
||||
export const ErrorPageRoute: AppRouteRecordRaw = {
|
||||
export const ErrorPageRoute: RouteRecordRaw = {
|
||||
path: '/:path(.*)*',
|
||||
name: 'ErrorPage',
|
||||
component: Layout,
|
||||
@@ -23,7 +23,7 @@ export const ErrorPageRoute: AppRouteRecordRaw = {
|
||||
],
|
||||
};
|
||||
|
||||
export const RedirectRoute: AppRouteRecordRaw = {
|
||||
export const RedirectRoute: RouteRecordRaw = {
|
||||
path: '/redirect',
|
||||
name: RedirectName,
|
||||
component: Layout,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { adminMenus } from '@/api/system/menu';
|
||||
import { constantRouterIcon } from './router-icons';
|
||||
import { constantRouterIcon } from './icons';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout, ParentLayout } from '@/router/constant';
|
||||
import type { AppRouteRecordRaw } from '@/router/types';
|
||||
@@ -16,13 +16,13 @@ LayoutMap.set('IFRAME', Iframe);
|
||||
* @param parent
|
||||
* @returns {*}
|
||||
*/
|
||||
export const routerGenerator = (routerMap, parent?): any[] => {
|
||||
export const generateRoutes = (routerMap, parent?): any[] => {
|
||||
return routerMap.map((item) => {
|
||||
const currentRouter: any = {
|
||||
const currentRoute: any = {
|
||||
// 路由地址 动态拼接生成如 /dashboard/workplace
|
||||
path: `${(parent && parent.path) || ''}/${item.path}`,
|
||||
path: `${(parent && parent.path) ?? ''}/${item.path}`,
|
||||
// 路由名称,建议唯一
|
||||
name: item.name || '',
|
||||
name: item.name ?? '',
|
||||
// 该路由对应页面的 组件
|
||||
component: item.component,
|
||||
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
|
||||
@@ -35,17 +35,17 @@ export const routerGenerator = (routerMap, parent?): any[] => {
|
||||
};
|
||||
|
||||
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
|
||||
currentRouter.path = currentRouter.path.replace('//', '/');
|
||||
currentRoute.path = currentRoute.path.replace('//', '/');
|
||||
// 重定向
|
||||
item.redirect && (currentRouter.redirect = item.redirect);
|
||||
item.redirect && (currentRoute.redirect = item.redirect);
|
||||
// 是否有子菜单,并递归处理
|
||||
if (item.children && item.children.length > 0) {
|
||||
//如果未定义 redirect 默认第一个子路由为 redirect
|
||||
!item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`);
|
||||
!item.redirect && (currentRoute.redirect = `${item.path}/${item.children[0].path}`);
|
||||
// Recursion
|
||||
currentRouter.children = routerGenerator(item.children, currentRouter);
|
||||
currentRoute.children = generateRoutes(item.children, currentRoute);
|
||||
}
|
||||
return currentRouter;
|
||||
return currentRoute;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -53,19 +53,11 @@ export const routerGenerator = (routerMap, parent?): any[] => {
|
||||
* 动态生成菜单
|
||||
* @returns {Promise<Router>}
|
||||
*/
|
||||
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
adminMenus()
|
||||
.then((result) => {
|
||||
const routeList = routerGenerator(result);
|
||||
asyncImportRoute(routeList);
|
||||
|
||||
resolve(routeList);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
export const generateDynamicRoutes = async (): Promise<RouteRecordRaw[]> => {
|
||||
const result = await adminMenus();
|
||||
const router = generateRoutes(result);
|
||||
asyncImportRoute(router);
|
||||
return router;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { isNavigationFailure, Router } from 'vue-router';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
|
||||
import { useUser } from '@/store/modules/user';
|
||||
import { useAsyncRoute } from '@/store/modules/asyncRoute';
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
||||
import { storage } from '@/utils/Storage';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
@@ -12,8 +12,8 @@ const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
||||
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
|
||||
|
||||
export function createRouterGuards(router: Router) {
|
||||
const userStore = useUserStoreWidthOut();
|
||||
const asyncRouteStore = useAsyncRouteStoreWidthOut();
|
||||
const userStore = useUser();
|
||||
const asyncRouteStore = useAsyncRoute();
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const Loading = window['$loading'] || null;
|
||||
Loading && Loading.start();
|
||||
@@ -51,12 +51,12 @@ export function createRouterGuards(router: Router) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (asyncRouteStore.getIsDynamicAddedRoute) {
|
||||
if (asyncRouteStore.getIsDynamicRouteAdded) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const userInfo = await userStore.GetInfo();
|
||||
const userInfo = await userStore.getInfo();
|
||||
|
||||
const routes = await asyncRouteStore.generateRoutes(userInfo);
|
||||
|
||||
@@ -74,7 +74,7 @@ export function createRouterGuards(router: Router) {
|
||||
const redirectPath = (from.query.redirect || to.path) as string;
|
||||
const redirect = decodeURIComponent(redirectPath);
|
||||
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
|
||||
asyncRouteStore.setDynamicAddedRoute(true);
|
||||
asyncRouteStore.setDynamicRouteAdded(true);
|
||||
next(nextData);
|
||||
Loading && Loading.finish();
|
||||
});
|
||||
@@ -84,7 +84,7 @@ export function createRouterGuards(router: Router) {
|
||||
if (isNavigationFailure(failure)) {
|
||||
//console.log('failed navigation', failure)
|
||||
}
|
||||
const asyncRouteStore = useAsyncRouteStoreWidthOut();
|
||||
const asyncRouteStore = useAsyncRoute();
|
||||
// 在这里设置需要缓存的组件名称
|
||||
const keepAliveComponents = asyncRouteStore.keepAliveComponents;
|
||||
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name;
|
||||
@@ -1,21 +1,20 @@
|
||||
import { App } from 'vue';
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||
import { RedirectRoute } from '@/router/base';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
import { createRouterGuards } from './router-guards';
|
||||
import { createRouterGuards } from './guards';
|
||||
import type { IModuleType } from './types';
|
||||
|
||||
const modules = import.meta.globEager('./modules/**/*.ts');
|
||||
const modules = import.meta.glob<IModuleType>('./modules/**/*.ts', { eager: true });
|
||||
|
||||
const routeModuleList: RouteRecordRaw[] = [];
|
||||
|
||||
Object.keys(modules).forEach((key) => {
|
||||
const mod = modules[key].default || {};
|
||||
const routeModuleList: RouteRecordRaw[] = Object.keys(modules).reduce((list, key) => {
|
||||
const mod = modules[key].default ?? {};
|
||||
const modList = Array.isArray(mod) ? [...mod] : [mod];
|
||||
routeModuleList.push(...modList);
|
||||
});
|
||||
return [...list, ...modList];
|
||||
}, []);
|
||||
|
||||
function sortRoute(a, b) {
|
||||
return (a.meta?.sort || 0) - (b.meta?.sort || 0);
|
||||
return (a.meta?.sort ?? 0) - (b.meta?.sort ?? 0);
|
||||
}
|
||||
|
||||
routeModuleList.sort(sortRoute);
|
||||
@@ -42,10 +41,10 @@ export const LoginRoute: RouteRecordRaw = {
|
||||
export const asyncRoutes = [...routeModuleList];
|
||||
|
||||
//普通路由 无需验证权限
|
||||
export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];
|
||||
export const constantRouter: RouteRecordRaw[] = [LoginRoute, RootRoute, RedirectRoute];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(''),
|
||||
history: createWebHistory(),
|
||||
routes: constantRouter,
|
||||
strict: true,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { ProjectOutlined } from '@vicons/antd';
|
||||
import { renderIcon, renderNew } from '@/utils/index';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
@@ -19,8 +19,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: 'index',
|
||||
name: `about_index`,
|
||||
meta: {
|
||||
title: '关于',
|
||||
extra: renderNew(),
|
||||
title: '关于项目',
|
||||
activeMenu: 'about_index',
|
||||
},
|
||||
component: () => import('@/views/about/index.vue'),
|
||||
|
||||
31
src/router/modules/directive.ts
Normal file
31
src/router/modules/directive.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { BorderOuterOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/directive',
|
||||
name: 'directive',
|
||||
component: Layout,
|
||||
meta: {
|
||||
sort: 9,
|
||||
isRoot: true,
|
||||
activeMenu: 'directive_index',
|
||||
icon: renderIcon(BorderOuterOutlined),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: `directive_index`,
|
||||
meta: {
|
||||
title: '指令示例',
|
||||
activeMenu: 'directive_index',
|
||||
},
|
||||
component: () => import('@/views/directive/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@@ -6,12 +6,12 @@ import { renderIcon } from '@/utils/index';
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/external',
|
||||
name: 'https://naive-ui-admin-docs.vercel.app',
|
||||
name: 'https://docs.naiveadmin.com',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '项目文档',
|
||||
icon: renderIcon(DocumentTextOutline),
|
||||
sort: 9,
|
||||
sort: 11,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -31,7 +31,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'frame-docs',
|
||||
meta: {
|
||||
title: '项目文档(内嵌)',
|
||||
frameSrc: 'https://naive-ui-admin-docs.vercel.app',
|
||||
frameSrc: 'https://jekip.github.io/docs',
|
||||
},
|
||||
component: IFrame,
|
||||
},
|
||||
|
||||
20
src/router/modules/newVersion.ts
Normal file
20
src/router/modules/newVersion.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { SketchOutlined } from '@vicons/antd';
|
||||
import { renderIcon, renderNew } from '@/utils/index';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/newversion',
|
||||
name: 'https://www.naiveadmin.com',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Pro 版本',
|
||||
extra: renderNew(),
|
||||
icon: renderIcon(SketchOutlined),
|
||||
sort: 12,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { RouteRecordRaw, RouteMeta } from 'vue-router';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export type Component<T extends any = any> =
|
||||
export type Component<T = any> =
|
||||
| ReturnType<typeof defineComponent>
|
||||
| (() => Promise<typeof import('*.vue')>)
|
||||
| (() => Promise<T>);
|
||||
|
||||
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta' | 'children'> {
|
||||
name: string;
|
||||
meta: RouteMeta;
|
||||
component?: Component | string;
|
||||
@@ -53,3 +53,7 @@ export interface Menu {
|
||||
redirect?: string;
|
||||
sort?: number;
|
||||
}
|
||||
|
||||
export interface IModuleType {
|
||||
default: Array<RouteRecordRaw> | RouteRecordRaw;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ export default {
|
||||
listField: 'list',
|
||||
// 接口返回总页数字段名
|
||||
totalField: 'pageCount',
|
||||
//总数字段名
|
||||
countField: 'itemCount',
|
||||
},
|
||||
//默认分页数量
|
||||
defaultPageSize: 10,
|
||||
|
||||
@@ -37,6 +37,8 @@ const setting = {
|
||||
mixMenu: false,
|
||||
//触发移动端侧边栏的宽度
|
||||
mobileWidth: 800,
|
||||
// 折叠菜单
|
||||
collapsed: false,
|
||||
},
|
||||
//面包屑
|
||||
crumbsSetting: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { store } from '@/store';
|
||||
import { asyncRoutes, constantRouter } from '@/router/index';
|
||||
import { generatorDynamicRouter } from '@/router/generator-routers';
|
||||
import { generateDynamicRoutes } from '@/router/generator';
|
||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||
|
||||
interface TreeHelperConfig {
|
||||
@@ -23,9 +23,9 @@ const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAU
|
||||
export interface IAsyncRouteState {
|
||||
menus: RouteRecordRaw[];
|
||||
routers: any[];
|
||||
addRouters: any[];
|
||||
routersAdded: any[];
|
||||
keepAliveComponents: string[];
|
||||
isDynamicAddedRoute: boolean;
|
||||
isDynamicRouteAdded: boolean;
|
||||
}
|
||||
|
||||
function filter<T = any>(
|
||||
@@ -53,54 +53,53 @@ export const useAsyncRouteStore = defineStore({
|
||||
state: (): IAsyncRouteState => ({
|
||||
menus: [],
|
||||
routers: constantRouter,
|
||||
addRouters: [],
|
||||
routersAdded: [],
|
||||
keepAliveComponents: [],
|
||||
// Whether the route has been dynamically added
|
||||
isDynamicAddedRoute: false,
|
||||
isDynamicRouteAdded: false,
|
||||
}),
|
||||
getters: {
|
||||
getMenus(): RouteRecordRaw[] {
|
||||
return this.menus;
|
||||
},
|
||||
getIsDynamicAddedRoute(): boolean {
|
||||
return this.isDynamicAddedRoute;
|
||||
getIsDynamicRouteAdded(): boolean {
|
||||
return this.isDynamicRouteAdded;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getRouters() {
|
||||
return toRaw(this.addRouters);
|
||||
return toRaw(this.routersAdded);
|
||||
},
|
||||
setDynamicAddedRoute(added: boolean) {
|
||||
this.isDynamicAddedRoute = added;
|
||||
setDynamicRouteAdded(added: boolean) {
|
||||
this.isDynamicRouteAdded = added;
|
||||
},
|
||||
// 设置动态路由
|
||||
setRouters(routers) {
|
||||
this.addRouters = routers;
|
||||
setRouters(routers: RouteRecordRaw[]) {
|
||||
this.routersAdded = routers;
|
||||
this.routers = constantRouter.concat(routers);
|
||||
},
|
||||
setMenus(menus) {
|
||||
setMenus(menus: RouteRecordRaw[]) {
|
||||
// 设置动态路由
|
||||
this.menus = menus;
|
||||
},
|
||||
setKeepAliveComponents(compNames) {
|
||||
setKeepAliveComponents(compNames: string[]) {
|
||||
// 设置需要缓存的组件
|
||||
this.keepAliveComponents = compNames;
|
||||
},
|
||||
async generateRoutes(data) {
|
||||
let accessedRouters;
|
||||
const permissionsList = data.permissions || [];
|
||||
const permissionsList = data.permissions ?? [];
|
||||
const routeFilter = (route) => {
|
||||
const { meta } = route;
|
||||
const { permissions } = meta || {};
|
||||
if (!permissions) return true;
|
||||
return permissionsList.some((item) => permissions.includes(item.value));
|
||||
};
|
||||
const { getPermissionMode } = useProjectSetting();
|
||||
const permissionMode = unref(getPermissionMode);
|
||||
if (permissionMode === 'BACK') {
|
||||
const { permissionMode } = useProjectSetting();
|
||||
if (unref(permissionMode) === 'BACK') {
|
||||
// 动态获取菜单
|
||||
try {
|
||||
accessedRouters = await generatorDynamicRouter();
|
||||
accessedRouters = await generateDynamicRoutes();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
@@ -121,6 +120,6 @@ export const useAsyncRouteStore = defineStore({
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useAsyncRouteStoreWidthOut() {
|
||||
export function useAsyncRoute() {
|
||||
return useAsyncRouteStore(store);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,6 @@ export const useDesignSettingStore = defineStore({
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useDesignSettingWithOut() {
|
||||
export function useDesignSetting() {
|
||||
return useDesignSettingStore(store);
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
const allModules = import.meta.globEager('./*/index.ts');
|
||||
const modules = {} as any;
|
||||
Object.keys(allModules).forEach((path) => {
|
||||
const fileName = path.split('/')[1];
|
||||
modules[fileName] = allModules[path][fileName] || allModules[path].default || allModules[path];
|
||||
});
|
||||
|
||||
// export default modules
|
||||
import asyncRoute from './async-route';
|
||||
import user from './user';
|
||||
import tabsView from './tabs-view';
|
||||
import lockscreen from './lockscreen';
|
||||
|
||||
export default {
|
||||
asyncRoute,
|
||||
user,
|
||||
tabsView,
|
||||
lockscreen,
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { IS_LOCKSCREEN } from '@/store/mutation-types';
|
||||
import { storage } from '@/utils/Storage';
|
||||
|
||||
// 长时间不操作默认锁屏时间
|
||||
const initTime = 60 * 60;
|
||||
|
||||
const isLock = storage.get(IS_LOCKSCREEN, false);
|
||||
|
||||
export type ILockscreenState = {
|
||||
isLock: boolean; // 是否锁屏
|
||||
lockTime: number;
|
||||
};
|
||||
|
||||
export const useLockscreenStore = defineStore({
|
||||
id: 'app-lockscreen',
|
||||
state: (): ILockscreenState => ({
|
||||
isLock: isLock === true, // 是否锁屏
|
||||
lockTime: isLock == 'true' ? initTime : 0,
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
setLock(payload) {
|
||||
this.isLock = payload;
|
||||
storage.set(IS_LOCKSCREEN, this.isLock);
|
||||
},
|
||||
setLockTime(payload = initTime) {
|
||||
this.lockTime = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { store } from '@/store';
|
||||
import projectSetting from '@/settings/projectSetting';
|
||||
import type { IheaderSetting, ImenuSetting, ImultiTabsSetting, IcrumbsSetting } from '/#/config';
|
||||
import type { IHeaderSetting, IMenuSetting, IMultiTabsSetting, ICrumbsSetting } from '/#/config';
|
||||
|
||||
const {
|
||||
navMode,
|
||||
@@ -20,11 +19,11 @@ const {
|
||||
interface ProjectSettingState {
|
||||
navMode: string; //导航模式
|
||||
navTheme: string; //导航风格
|
||||
headerSetting: IheaderSetting; //顶部设置
|
||||
headerSetting: IHeaderSetting; //顶部设置
|
||||
showFooter: boolean; //页脚
|
||||
menuSetting: ImenuSetting; //多标签
|
||||
multiTabsSetting: ImultiTabsSetting; //多标签
|
||||
crumbsSetting: IcrumbsSetting; //面包屑
|
||||
menuSetting: IMenuSetting; //多标签
|
||||
multiTabsSetting: IMultiTabsSetting; //多标签
|
||||
crumbsSetting: ICrumbsSetting; //面包屑
|
||||
permissionMode: string; //权限模式
|
||||
isPageAnimate: boolean; //是否开启路由动画
|
||||
pageAnimateType: string; //路由动画类型
|
||||
@@ -69,7 +68,7 @@ export const useProjectSettingStore = defineStore({
|
||||
return this.multiTabsSetting;
|
||||
},
|
||||
getCrumbsSetting(): object {
|
||||
return this.multiTabsSetting;
|
||||
return this.crumbsSetting;
|
||||
},
|
||||
getPermissionMode(): string {
|
||||
return this.permissionMode;
|
||||
@@ -90,8 +89,3 @@ export const useProjectSettingStore = defineStore({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useProjectSettingStoreWithOut() {
|
||||
return useProjectSettingStore(store);
|
||||
}
|
||||
|
||||
31
src/store/modules/screenLock.ts
Normal file
31
src/store/modules/screenLock.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { IS_SCREENLOCKED } from '@/store/mutation-types';
|
||||
import { storage } from '@/utils/Storage';
|
||||
|
||||
// 长时间不操作默认锁屏时间
|
||||
const initTime = 60 * 60;
|
||||
|
||||
const isLocked = storage.get(IS_SCREENLOCKED, false);
|
||||
|
||||
export type IScreenLockState = {
|
||||
isLocked: boolean; // 是否锁屏
|
||||
lockTime: number;
|
||||
};
|
||||
|
||||
export const useScreenLockStore = defineStore({
|
||||
id: 'app-screen-lock',
|
||||
state: (): IScreenLockState => ({
|
||||
isLocked: isLocked === true, // 是否锁屏
|
||||
lockTime: isLocked == 'true' ? initTime : 0,
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
setLock(payload: boolean) {
|
||||
this.isLocked = payload;
|
||||
storage.set(IS_SCREENLOCKED, this.isLocked);
|
||||
},
|
||||
setLockTime(payload = initTime) {
|
||||
this.lockTime = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
import { TABS_ROUTES } from '../mutation-types';
|
||||
|
||||
// 不需要出现在标签页中的路由
|
||||
const whiteList = ['Redirect', 'login'];
|
||||
@@ -31,11 +30,11 @@ export const useTabsViewStore = defineStore({
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
initTabs(routes) {
|
||||
initTabs(routes: RouteItem[]) {
|
||||
// 初始化标签页
|
||||
this.tabsList = routes;
|
||||
},
|
||||
addTabs(route): boolean {
|
||||
addTab(route: RouteItem): boolean {
|
||||
// 添加标签页
|
||||
if (whiteList.includes(route.name)) return false;
|
||||
const isExists = this.tabsList.some((item) => item.fullPath == route.fullPath);
|
||||
@@ -44,28 +43,29 @@ export const useTabsViewStore = defineStore({
|
||||
}
|
||||
return true;
|
||||
},
|
||||
closeLeftTabs(route) {
|
||||
closeLeftTabs(route: RouteItem) {
|
||||
// 关闭左侧
|
||||
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
|
||||
this.tabsList.splice(0, index);
|
||||
this.tabsList = this.tabsList.filter((item, i) => i >= index || (item?.meta?.affix ?? false));
|
||||
},
|
||||
closeRightTabs(route) {
|
||||
closeRightTabs(route: RouteItem) {
|
||||
// 关闭右侧
|
||||
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
|
||||
this.tabsList.splice(index + 1);
|
||||
this.tabsList = this.tabsList.filter((item, i) => i <= index || (item?.meta?.affix ?? false));
|
||||
},
|
||||
closeOtherTabs(route) {
|
||||
closeOtherTabs(route: RouteItem) {
|
||||
// 关闭其他
|
||||
this.tabsList = this.tabsList.filter((item) => item.fullPath == route.fullPath);
|
||||
this.tabsList = this.tabsList.filter(
|
||||
(item) => item.fullPath == route.fullPath || (item?.meta?.affix ?? false)
|
||||
);
|
||||
},
|
||||
closeCurrentTab(route) {
|
||||
closeCurrentTab(route: RouteItem) {
|
||||
// 关闭当前页
|
||||
const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath);
|
||||
this.tabsList.splice(index, 1);
|
||||
},
|
||||
closeAllTabs() {
|
||||
// 关闭全部
|
||||
console.log(retainAffixRoute(this.tabsList));
|
||||
this.tabsList = retainAffixRoute(this.tabsList);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { createStorage } from '@/utils/Storage';
|
||||
import { store } from '@/store';
|
||||
import { ACCESS_TOKEN, CURRENT_USER, IS_LOCKSCREEN } from '@/store/mutation-types';
|
||||
import { ACCESS_TOKEN, CURRENT_USER, IS_SCREENLOCKED } from '@/store/mutation-types';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
|
||||
const Storage = createStorage({ storage: localStorage });
|
||||
import { getUserInfo, login } from '@/api/system/user';
|
||||
import { getUserInfo as getUserInfoApi, login } from '@/api/system/user';
|
||||
import { storage } from '@/utils/Storage';
|
||||
|
||||
export type UserInfoType = {
|
||||
// TODO: add your own data
|
||||
username: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export interface IUserState {
|
||||
token: string;
|
||||
username: string;
|
||||
welcome: string;
|
||||
avatar: string;
|
||||
permissions: any[];
|
||||
info: any;
|
||||
info: UserInfoType;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'app-user',
|
||||
state: (): IUserState => ({
|
||||
token: Storage.get(ACCESS_TOKEN, ''),
|
||||
token: storage.get(ACCESS_TOKEN, ''),
|
||||
username: '',
|
||||
welcome: '',
|
||||
avatar: '',
|
||||
permissions: [],
|
||||
info: Storage.get(CURRENT_USER, {}),
|
||||
info: storage.get(CURRENT_USER, {}),
|
||||
}),
|
||||
getters: {
|
||||
getToken(): string {
|
||||
@@ -40,7 +44,7 @@ export const useUserStore = defineStore({
|
||||
getPermissions(): [any][] {
|
||||
return this.permissions;
|
||||
},
|
||||
getUserInfo(): object {
|
||||
getUserInfo(): UserInfoType {
|
||||
return this.info;
|
||||
},
|
||||
},
|
||||
@@ -54,63 +58,49 @@ export const useUserStore = defineStore({
|
||||
setPermissions(permissions) {
|
||||
this.permissions = permissions;
|
||||
},
|
||||
setUserInfo(info) {
|
||||
setUserInfo(info: UserInfoType) {
|
||||
this.info = info;
|
||||
},
|
||||
// 登录
|
||||
async login(userInfo) {
|
||||
try {
|
||||
const response = await login(userInfo);
|
||||
const { result, code } = response;
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
const ex = 7 * 24 * 60 * 60 * 1000;
|
||||
storage.set(ACCESS_TOKEN, result.token, ex);
|
||||
storage.set(CURRENT_USER, result, ex);
|
||||
storage.set(IS_LOCKSCREEN, false);
|
||||
this.setToken(result.token);
|
||||
this.setUserInfo(result);
|
||||
}
|
||||
return Promise.resolve(response);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
async login(params: any) {
|
||||
const response = await login(params);
|
||||
const { result, code } = response;
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
const ex = 7 * 24 * 60 * 60;
|
||||
storage.set(ACCESS_TOKEN, result.token, ex);
|
||||
storage.set(CURRENT_USER, result, ex);
|
||||
storage.set(IS_SCREENLOCKED, false);
|
||||
this.setToken(result.token);
|
||||
this.setUserInfo(result);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo() {
|
||||
const that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo()
|
||||
.then((res) => {
|
||||
const result = res;
|
||||
if (result.permissions && result.permissions.length) {
|
||||
const permissionsList = result.permissions;
|
||||
that.setPermissions(permissionsList);
|
||||
that.setUserInfo(result);
|
||||
} else {
|
||||
reject(new Error('getInfo: permissionsList must be a non-null array !'));
|
||||
}
|
||||
that.setAvatar(result.avatar);
|
||||
resolve(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
async getInfo() {
|
||||
const result = await getUserInfoApi();
|
||||
if (result.permissions && result.permissions.length) {
|
||||
const permissionsList = result.permissions;
|
||||
this.setPermissions(permissionsList);
|
||||
this.setUserInfo(result);
|
||||
} else {
|
||||
throw new Error('getInfo: permissionsList must be a non-null array !');
|
||||
}
|
||||
this.setAvatar(result.avatar);
|
||||
return result;
|
||||
},
|
||||
|
||||
// 登出
|
||||
async logout() {
|
||||
this.setPermissions([]);
|
||||
this.setUserInfo('');
|
||||
this.setUserInfo({ name: '', email: '' });
|
||||
storage.remove(ACCESS_TOKEN);
|
||||
storage.remove(CURRENT_USER);
|
||||
return Promise.resolve('');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Need to be used outside the setup
|
||||
export function useUserStoreWidthOut() {
|
||||
export function useUser() {
|
||||
return useUserStore(store);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
|
||||
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
|
||||
export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
|
||||
export const IS_SCREENLOCKED = 'IS-SCREENLOCKED'; // 是否锁屏
|
||||
export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { IAsyncRouteState } from '@/store/modules/asyncRoute';
|
||||
import { IUserState } from '@/store/modules/user';
|
||||
import { ILockscreenState } from '@/store/modules/lockscreen';
|
||||
import { IScreenLockState } from '@/store/modules/screenLock';
|
||||
import { ITabsViewState } from '@/store/modules/tabsView';
|
||||
|
||||
export interface IStore {
|
||||
asyncRoute: IAsyncRouteState;
|
||||
user: IUserState;
|
||||
lockscreen: ILockscreenState;
|
||||
screenLock: IScreenLockState;
|
||||
tabsView: ITabsViewState;
|
||||
count: number;
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
#app,
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei,
|
||||
'\5FAE\8F6F\96C5\9ED1', Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
color: #515a6e;
|
||||
font-size: 14px;
|
||||
background-color: #f7f7f7;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
//重置样式
|
||||
.anticon {
|
||||
svg {
|
||||
vertical-align: initial;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2d8cf0;
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #57a3f3;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #2b85e4;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 滚动条凹槽的颜色,还可以设置边框属性 */
|
||||
*::-webkit-scrollbar-track-piece {
|
||||
background-color: #f8f8f8;
|
||||
-webkit-border-radius: 2em;
|
||||
-moz-border-radius: 2em;
|
||||
border-radius: 2em;
|
||||
}
|
||||
|
||||
/* 滚动条的宽度 */
|
||||
*::-webkit-scrollbar {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
/* 滚动条的设置 */
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #ddd;
|
||||
background-clip: padding-box;
|
||||
-webkit-border-radius: 2em;
|
||||
-moz-border-radius: 2em;
|
||||
border-radius: 2em;
|
||||
}
|
||||
|
||||
/* 滚动条鼠标移上去 */
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
/* router view transition */
|
||||
.zoom-fade-enter-active,
|
||||
.zoom-fade-leave-active {
|
||||
transition: transform 0.35s, opacity 0.28s ease-in-out;
|
||||
}
|
||||
|
||||
.zoom-fade-enter-from {
|
||||
opacity: 0;
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.zoom-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
//antd 卡片样式定制
|
||||
body .n-card {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
body .n-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
body .proCard {
|
||||
border-radius: 4px;
|
||||
|
||||
.n-card__content {
|
||||
padding: 16px;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body .n-modal {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
//body .proCardTabs{
|
||||
// .n-card__content{ padding-top: 3px}
|
||||
// .n-card__content:first-child{ padding-top: 3px}
|
||||
//}
|
||||
|
||||
.n-layout-page-header {
|
||||
margin: 0 -10px;
|
||||
}
|
||||
@@ -1,3 +1 @@
|
||||
@import 'transition/index.less';
|
||||
@import './var.less';
|
||||
@import './common.less';
|
||||
@import url('./transition/index.less');
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
@primaryColor: #2d8cf0;
|
||||
@primaryColorHover: #57a3f3;
|
||||
@header-height: 64px;
|
||||
@footer-height: 70px;
|
||||
@@ -6,122 +6,119 @@ const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
|
||||
* @param {string=} prefixKey -
|
||||
* @param {Object} [storage=localStorage] - sessionStorage | localStorage
|
||||
*/
|
||||
export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) => {
|
||||
|
||||
export default class Storage {
|
||||
private storage: globalThis.Storage;
|
||||
private prefixKey?: string;
|
||||
|
||||
constructor(prefixKey = '', storage = localStorage) {
|
||||
this.storage = storage;
|
||||
this.prefixKey = prefixKey;
|
||||
}
|
||||
|
||||
private getKey(key: string) {
|
||||
return `${this.prefixKey}${key}`.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地缓存类
|
||||
* @class Storage
|
||||
* @description 设置缓存
|
||||
* @param {string} key 缓存键
|
||||
* @param {*} value 缓存值
|
||||
* @param expire
|
||||
*/
|
||||
const Storage = class {
|
||||
private storage = storage;
|
||||
private prefixKey?: string = prefixKey;
|
||||
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
|
||||
const stringData = JSON.stringify({
|
||||
value,
|
||||
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
|
||||
});
|
||||
this.storage.setItem(this.getKey(key), stringData);
|
||||
}
|
||||
|
||||
private getKey(key: string) {
|
||||
return `${this.prefixKey}${key}`.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置缓存
|
||||
* @param {string} key 缓存键
|
||||
* @param {*} value 缓存值
|
||||
* @param expire
|
||||
*/
|
||||
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
|
||||
const stringData = JSON.stringify({
|
||||
value,
|
||||
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
|
||||
});
|
||||
this.storage.setItem(this.getKey(key), stringData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @param {string} key 缓存键
|
||||
* @param {*=} def 默认值
|
||||
*/
|
||||
get(key: string, def: any = null) {
|
||||
const item = this.storage.getItem(this.getKey(key));
|
||||
if (item) {
|
||||
try {
|
||||
const data = JSON.parse(item);
|
||||
const { value, expire } = data;
|
||||
// 在有效期内直接返回
|
||||
if (expire === null || expire >= Date.now()) {
|
||||
return value;
|
||||
}
|
||||
this.remove(key);
|
||||
} catch (e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存删除某项
|
||||
* @param {string} key
|
||||
*/
|
||||
remove(key: string) {
|
||||
this.storage.removeItem(this.getKey(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有缓存
|
||||
* @memberOf Cache
|
||||
*/
|
||||
clear(): void {
|
||||
this.storage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置cookie
|
||||
* @param {string} name cookie 名称
|
||||
* @param {*} value cookie 值
|
||||
* @param {number=} expire 过期时间
|
||||
* 如果过期时间为设置,默认关闭浏览器自动删除
|
||||
* @example
|
||||
*/
|
||||
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
|
||||
document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名字获取cookie值
|
||||
* @param name
|
||||
*/
|
||||
getCookie(name: string): string {
|
||||
const cookieArr = document.cookie.split('; ');
|
||||
for (let i = 0, length = cookieArr.length; i < length; i++) {
|
||||
const kv = cookieArr[i].split('=');
|
||||
if (kv[0] === this.getKey(name)) {
|
||||
return kv[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名字删除指定的cookie
|
||||
* @param {string} key
|
||||
*/
|
||||
removeCookie(key: string) {
|
||||
this.setCookie(key, 1, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空cookie,使所有cookie失效
|
||||
*/
|
||||
clearCookie(): void {
|
||||
const keys = document.cookie.match(/[^ =;]+(?==)/g);
|
||||
if (keys) {
|
||||
for (let i = keys.length; i--; ) {
|
||||
document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString();
|
||||
/**
|
||||
* 读取缓存
|
||||
* @param {string} key 缓存键
|
||||
* @param {*=} def 默认值
|
||||
*/
|
||||
get(key: string, def: any = null) {
|
||||
const item = this.storage.getItem(this.getKey(key));
|
||||
if (item) {
|
||||
try {
|
||||
const data = JSON.parse(item);
|
||||
const { value, expire } = data;
|
||||
// 在有效期内直接返回
|
||||
if (expire === null || expire >= Date.now()) {
|
||||
return value;
|
||||
}
|
||||
this.remove(key);
|
||||
} catch (e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
};
|
||||
return new Storage();
|
||||
};
|
||||
return def;
|
||||
}
|
||||
|
||||
export const storage = createStorage();
|
||||
/**
|
||||
* 从缓存删除某项
|
||||
* @param {string} key
|
||||
*/
|
||||
remove(key: string) {
|
||||
this.storage.removeItem(this.getKey(key));
|
||||
}
|
||||
|
||||
export default Storage;
|
||||
/**
|
||||
* 清空所有缓存
|
||||
* @memberOf Cache
|
||||
*/
|
||||
clear(): void {
|
||||
this.storage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置cookie
|
||||
* @param {string} name cookie 名称
|
||||
* @param {*} value cookie 值
|
||||
* @param {number=} expire 过期时间
|
||||
* 如果过期时间未设置,默认关闭浏览器自动删除
|
||||
* @example
|
||||
*/
|
||||
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
|
||||
document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名字获取cookie值
|
||||
* @param name
|
||||
*/
|
||||
getCookie(name: string): string {
|
||||
const cookieArr = document.cookie.split('; ');
|
||||
for (let i = 0, length = cookieArr.length; i < length; i++) {
|
||||
const kv = cookieArr[i].split('=');
|
||||
if (kv[0] === this.getKey(name)) {
|
||||
return kv[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名字删除指定的cookie
|
||||
* @param {string} key
|
||||
*/
|
||||
removeCookie(key: string) {
|
||||
this.setCookie(key, 1, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空cookie,使所有cookie失效
|
||||
*/
|
||||
clearCookie(): void {
|
||||
const keys = document.cookie.match(/[^ =;]+(?==)/g);
|
||||
if (keys) {
|
||||
for (let i = keys.length; i--; ) {
|
||||
document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const storage = new Storage('');
|
||||
|
||||
@@ -3,10 +3,10 @@ import { format } from 'date-fns';
|
||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
|
||||
const DATE_FORMAT = 'YYYY-MM-DD ';
|
||||
|
||||
export function formatToDateTime(date: null, formatStr = DATE_TIME_FORMAT): string {
|
||||
export function formatToDateTime(date: Date | number, formatStr = DATE_TIME_FORMAT): string {
|
||||
return format(date, formatStr);
|
||||
}
|
||||
|
||||
export function formatToDate(date: null, formatStr = DATE_FORMAT): string {
|
||||
export function formatToDate(date: Date | number, formatStr = DATE_FORMAT): string {
|
||||
return format(date, formatStr);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { setObjToUrlParams } from '@/utils/urlUtils';
|
||||
|
||||
import { RequestOptions, Result, CreateAxiosOptions } from './types';
|
||||
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import { useUser } from '@/store/modules/user';
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
@@ -176,7 +176,7 @@ const transform: AxiosTransform = {
|
||||
*/
|
||||
requestInterceptors: (config, options) => {
|
||||
// 请求之前处理config
|
||||
const userStore = useUserStoreWidthOut();
|
||||
const userStore = useUser();
|
||||
const token = userStore.getToken;
|
||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
||||
// jwt token
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { h, unref } from 'vue';
|
||||
import type { App, Plugin } from 'vue';
|
||||
import type { App, Plugin, Component } from 'vue';
|
||||
import { NIcon, NTag } from 'naive-ui';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
import { isObject } from './is/index';
|
||||
@@ -10,6 +10,28 @@ import { cloneDeep } from 'lodash-es';
|
||||
export function renderIcon(icon) {
|
||||
return () => h(NIcon, null, { default: () => h(icon) });
|
||||
}
|
||||
/**
|
||||
* font 图标(Font class)
|
||||
* */
|
||||
export function renderFontClassIcon(icon: string, iconName = 'iconfont') {
|
||||
return () => h('span', { class: [iconName, icon] });
|
||||
}
|
||||
/**
|
||||
* font 图标(Unicode)
|
||||
* */
|
||||
export function renderUnicodeIcon(icon: string, iconName = 'iconfont') {
|
||||
return () => h('span', { class: [iconName], innerHTML: icon });
|
||||
}
|
||||
/**
|
||||
* font svg 图标
|
||||
* */
|
||||
export function renderfontsvg(icon) {
|
||||
return () =>
|
||||
h(NIcon, null, {
|
||||
default: () =>
|
||||
h('svg', { class: `icon`, 'aria-hidden': 'true' }, h('use', { 'xlink:href': `#${icon}` })),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* render new Tag
|
||||
@@ -104,7 +126,10 @@ export function getChildrenRouter(routerMap: Array<any>) {
|
||||
* 判断根路由 Router
|
||||
* */
|
||||
export function isRootRouter(item) {
|
||||
return item.meta?.alwaysShow != true && item.children?.length === 1;
|
||||
return (
|
||||
item.meta?.alwaysShow != true &&
|
||||
item?.children?.filter((item) => !Boolean(item?.meta?.hidden))?.length === 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +144,7 @@ export function filterRouter(routerMap: Array<any>) {
|
||||
});
|
||||
}
|
||||
|
||||
export const withInstall = <T>(component: T, alias?: string) => {
|
||||
export const withInstall = <T extends Component>(component: T, alias?: string) => {
|
||||
const comp = component as any;
|
||||
comp.install = (app: App) => {
|
||||
app.component(comp.name || comp.displayName, component);
|
||||
@@ -162,7 +187,7 @@ export function getTreeAll(data: any[]): any[] {
|
||||
}
|
||||
|
||||
// dynamic use hook props
|
||||
export function getDynamicProps<T, U>(props: T): Partial<U> {
|
||||
export function getDynamicProps<T extends {}, U>(props: T): Partial<U> {
|
||||
const ret: Recordable = {};
|
||||
|
||||
Object.keys(props).map((key) => {
|
||||
@@ -211,5 +236,5 @@ export function lighten(color: string, amount: number) {
|
||||
* 判断是否 url
|
||||
* */
|
||||
export function isUrl(url: string) {
|
||||
return /(^http|https:\/\/)/g.test(url);
|
||||
return /^(http|https):\/\//g.test(url);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export function is(val: unknown, type: string) {
|
||||
* @description: 是否为函数
|
||||
*/
|
||||
export function isFunction<T = Function>(val: unknown): val is T {
|
||||
return is(val, 'Function');
|
||||
return is(val, 'Function') || is(val, 'AsyncFunction');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ export function isNumber(val: unknown): val is number {
|
||||
/**
|
||||
* @description: 是否为AsyncFunction
|
||||
*/
|
||||
export function isAsyncFunction<T = any>(val: unknown): val is Promise<T> {
|
||||
export function isAsyncFunction<T = any>(val: unknown): val is () => Promise<T> {
|
||||
return is(val, 'AsyncFunction');
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
VisualMapComponent,
|
||||
TimelineComponent,
|
||||
CalendarComponent,
|
||||
GraphicComponent,
|
||||
} from 'echarts/components';
|
||||
|
||||
import { SVGRenderer } from 'echarts/renderers';
|
||||
@@ -48,6 +49,7 @@ echarts.use([
|
||||
VisualMapComponent,
|
||||
TimelineComponent,
|
||||
CalendarComponent,
|
||||
GraphicComponent,
|
||||
]);
|
||||
|
||||
export default echarts;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
title="项目信息"
|
||||
class="mt-4 proCard"
|
||||
size="small"
|
||||
:segmented="{ content: 'hard' }"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="版本">
|
||||
@@ -22,16 +22,12 @@
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="文档地址">
|
||||
<div class="flex items-center">
|
||||
<a href="https://naive-ui-admin-docs.vercel.app" class="py-2" target="_blank"
|
||||
>查看文档地址</a
|
||||
>
|
||||
<a href="https://docs.naiveadmin.com" class="py-2" target="_blank">查看文档地址</a>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="预览地址">
|
||||
<div class="flex items-center">
|
||||
<a href="https://naive-ui-admin.vercel.app" class="py-2" target="_blank"
|
||||
>查看预览地址</a
|
||||
>
|
||||
<a href="https://v1.naiveadmin.com" class="py-2" target="_blank">查看预览地址</a>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="Github">
|
||||
@@ -56,7 +52,7 @@
|
||||
title="开发环境依赖"
|
||||
class="mt-4 proCard"
|
||||
size="small"
|
||||
:segmented="{ content: 'hard' }"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" class="py-2">
|
||||
<n-descriptions-item v-for="item in devSchema" :key="item.field" :label="item.field">
|
||||
@@ -70,7 +66,7 @@
|
||||
title="生产环境依赖"
|
||||
class="mt-4 proCard"
|
||||
size="small"
|
||||
:segmented="{ content: 'hard' }"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" class="py-2">
|
||||
<n-descriptions-item v-for="item in schema" :key="item.field" :label="item.field">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="需求池"
|
||||
:segmented="{ content: 'hard', footer: 'hard' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
@@ -45,7 +45,7 @@
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="开发中"
|
||||
:segmented="{ content: 'hard', footer: 'hard' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
@@ -72,7 +72,7 @@
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="已完成"
|
||||
:segmented="{ content: 'hard', footer: 'hard' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
@@ -98,7 +98,7 @@
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="已验收"
|
||||
:segmented="{ content: 'hard', footer: 'hard' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicForm } from '@/components/Form/index';
|
||||
import { BasicForm, FormSchema } from '@/components/Form/index';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
const schemas = [
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
@@ -153,6 +153,9 @@
|
||||
const message = useMessage();
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
if (!values) {
|
||||
return message.error('请填写完整信息');
|
||||
}
|
||||
console.log(values);
|
||||
message.success(JSON.stringify(values));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user