mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-04 21:52:27 +08:00
Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9ad5ba18a9 | ||
|
|
536e16f166 | ||
|
|
42f2256ea1 | ||
|
|
bf0d294322 | ||
|
|
51f5c64755 | ||
|
|
b49d9e8bd2 | ||
|
|
12e62d1179 | ||
|
|
6558e1597c | ||
|
|
7bf1e1265a | ||
|
|
1213de598e | ||
|
|
20c9dbbfe1 | ||
|
|
6dab2ab35b | ||
|
|
a424788c45 | ||
|
|
b31d5c2bd6 | ||
|
|
b979d9db32 | ||
|
|
a53c86e41b | ||
|
|
b16b5c8992 | ||
|
|
58dadbb95a | ||
|
|
4ebdbc7203 | ||
|
|
0729e56ed4 | ||
|
|
a50cbfa44d | ||
|
|
65d6d4d21e | ||
|
|
c5bb818f13 | ||
|
|
9e255da5d7 | ||
|
|
e2b5086be3 | ||
|
|
caaca83f78 | ||
|
|
91de971636 | ||
|
|
5fb005d5ae | ||
|
|
b42e0a2fef | ||
|
|
097dda5aa1 | ||
|
|
16714d4bdb | ||
|
|
a5438b4f50 | ||
|
|
4f5bbb0673 | ||
|
|
24cbde8b95 | ||
|
|
7222398cf0 | ||
|
|
261e27c139 | ||
|
|
e6505c88b7 | ||
|
|
55ee389184 |
@@ -15,7 +15,7 @@ VITE_DROP_CONSOLE = true
|
||||
|
||||
# 跨域代理,可以配置多个,请注意不要换行
|
||||
#VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
|
||||
# VITE_PROXY=[["/api","https://naive-ui-admin"]]
|
||||
#VITE_PROXY=[["/api","https://naive-ui-admin"]]
|
||||
|
||||
# API 接口地址
|
||||
VITE_GLOB_API_URL =
|
||||
|
||||
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',
|
||||
|
||||
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,5 +1,60 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 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
|
||||
- 新增 `多页签` 支持配置 `affix` 固定属性
|
||||
- 新增 `usePage` Hooks
|
||||
- 表格列支持 `draggable` 配置拖拽 合并 [#114](https://github.com/jekip/naive-ui-admin/pull/114)
|
||||
- 依赖升级
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- 修复 `多页签` 关闭全部缺陷
|
||||
- 修复 `多页签` 跳转缺陷(记得清空多页签缓存)
|
||||
## 1.7.0 (2022-02-14)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- 移除 `登录页面` 滑动验证组件
|
||||
- 修复 `BasicUpload` 组件,回显问题
|
||||
- 修复 `ts类型` 配置缺陷
|
||||
- 修复 `登录页面` message 交互缺陷
|
||||
- 修复 `表格编辑` 时间格式化异常 [#92](https://github.com/jekip/naive-ui-admin/issues/92)
|
||||
|
||||
- ### ✨ Features
|
||||
- 依赖升级
|
||||
|
||||
## 1.6.1 (2022-01-06)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
43
README.md
43
README.md
@@ -1,6 +1,3 @@
|
||||
## 留步
|
||||
少侠留步,早知如此绊人心,何如当初莫相识,右上角免费的 `Star` 点一点,帮我们突破一下 `2k`,谢谢!O(∩_∩)O
|
||||
|
||||
## 简介
|
||||
|
||||
[Naive Ui Admin](https://github.com/jekip/naive-ui-admin) 完全免费,且可商用,基于 [Vue3.0](https://github.com/vuejs/vue-next)、[Vite](https://github.com/vitejs/vite)、 [Naive UI](https://www.naiveui.com/)、[TypeScript](https://www.typescriptlang.org/) 的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目, 相信不管是从新技术使用还是其他方面,都能帮助到你。
|
||||
@@ -14,38 +11,48 @@
|
||||
|
||||
|
||||
## 预览
|
||||
- [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` 全新设计版本,新增众多特性,强烈推荐
|
||||
|
||||
### Antd vue
|
||||
|
||||
千呼万唤 `Naive Admin Antd` 也迎来了第一个版本,同时具备 `Naive Ui Admin` 优点,如果您选的技术栈是 `Antd` 的话,不妨看看。
|
||||
|
||||
[NaiveAdmin Antd 预览](https://antd.naiveadmin.com)
|
||||
[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
|
||||
|
||||
新产品,如果您选的技术栈是 `Antd` 的话,不妨看看
|
||||
|
||||
[NaiveAdmin Antd 预览](https://antd.naiveadmin.com)
|
||||
|
||||
## 文档
|
||||
|
||||
[v1文档地址](https://naive-ui-admin-docs.vercel.app)
|
||||
[文档地址](https://jekip.github.io/docs)
|
||||
|
||||
## 准备
|
||||
|
||||
@@ -137,11 +144,13 @@ yarn build
|
||||
|
||||
## 交流
|
||||
|
||||
`Naive Ui Admin` 在帮助开发者更方便地进行中大型管理系统开发,同时也提供 QQ 交流群使用问题欢迎在群内提问。
|
||||
`Naive Ui Admin` 使用或者其他问题,都可以在群内讨论或提问。
|
||||
|
||||
- QQ 群 `328347666`
|
||||

|
||||
|
||||
## 赞助
|
||||
#### 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 🍹。
|
||||
|
||||

|
||||
|
||||
[Paypal Me](https://www.paypal.com/paypalme/majunping)
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
144
package.json
144
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "naive-ui-admin",
|
||||
"version": "1.6.1",
|
||||
"version": "1.9.0",
|
||||
"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,75 +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.7",
|
||||
"@vueuse/core": "^5.0.3",
|
||||
"axios": "^0.21.1",
|
||||
"blueimp-md5": "^2.18.0",
|
||||
"date-fns": "^2.23.0",
|
||||
"echarts": "^5.1.2",
|
||||
"element-resize-detector": "^1.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"@vicons/antd": "^0.12.0",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"axios": "^1.4.0",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"echarts": "^5.4.3",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"makeit-captcha": "^1.2.5",
|
||||
"mitt": "^2.1.0",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"naive-ui": "^2.23.2",
|
||||
"pinia": "^2.0.0-rc.4",
|
||||
"qs": "^6.10.1",
|
||||
"vfonts": "^0.1.0",
|
||||
"vue": "^3.2.16",
|
||||
"vue-router": "^4.0.11",
|
||||
"vue-types": "^4.1.0",
|
||||
"vuedraggable": "^4.0.3"
|
||||
"naive-ui": "^2.34.4",
|
||||
"pinia": "^2.1.6",
|
||||
"qs": "^6.11.2",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-types": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^12.1.4",
|
||||
"@commitlint/config-conventional": "^12.1.4",
|
||||
"@types/lodash": "^4.14.170",
|
||||
"@types/node": "^15.12.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.2",
|
||||
"@typescript-eslint/parser": "^4.31.2",
|
||||
"@vitejs/plugin-vue": "^1.9.1",
|
||||
"@vitejs/plugin-vue-jsx": "^1.1.8",
|
||||
"@vue/compiler-sfc": "^3.2.16",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"autoprefixer": "^10.3.1",
|
||||
"commitizen": "^4.2.4",
|
||||
"core-js": "^3.14.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-define-config": "^1.0.9",
|
||||
"eslint-plugin-jest": "^24.4.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-vue": "^7.18.0",
|
||||
"esno": "^0.7.3",
|
||||
"gh-pages": "^3.2.0",
|
||||
"husky": "^6.0.0",
|
||||
"jest": "^27.0.6",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^9.0.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"postcss": "^8.3.5",
|
||||
"prettier": "^2.3.1",
|
||||
"pretty-quick": "^3.1.0",
|
||||
"@commitlint/cli": "^17.7.0",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"@types/lodash": "^4.14.197",
|
||||
"@types/node": "^18.17.4",
|
||||
"@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.3.4",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"commitizen": "^4.3.0",
|
||||
"core-js": "^3.32.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-define-config": "1.12.0",
|
||||
"eslint-plugin-jest": "^27.2.3",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"esno": "^0.16.3",
|
||||
"gh-pages": "^4.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.6.2",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^11.1.3",
|
||||
"lint-staged": "^13.2.3",
|
||||
"postcss": "^8.4.27",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"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.19.0",
|
||||
"tailwindcss": "^2.2.7",
|
||||
"typescript": "^4.4.3",
|
||||
"unplugin-vue-components": "^0.17.2",
|
||||
"vite": "^2.5.10",
|
||||
"vite-plugin-compression": "^0.3.1",
|
||||
"vite-plugin-html": "^2.0.7",
|
||||
"vite-plugin-mock": "^2.9.3",
|
||||
"vite-plugin-style-import": "^1.0.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.3.3",
|
||||
"typescript": "^4.9.5",
|
||||
"unplugin-vue-components": "^0.22.12",
|
||||
"vite": "^3.2.7",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"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.3.1",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{vue,js,ts,tsx}": "eslint --fix"
|
||||
@@ -123,6 +127,6 @@
|
||||
},
|
||||
"homepage": "https://github.com/jekip/naive-ui-admin",
|
||||
"engines": {
|
||||
"node": "^12 || >=14"
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
|
||||
7824
pnpm-lock.yaml
generated
Normal file
7824
pnpm-lock.yaml
generated
Normal file
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>
|
||||
21
src/App.vue
21
src/App.vue
@@ -18,19 +18,19 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted } from 'vue';
|
||||
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, darkTheme } from 'naive-ui';
|
||||
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);
|
||||
|
||||
@@ -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>
|
||||
@@ -138,7 +138,7 @@
|
||||
import { deepMerge } from '@/utils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicUpload',
|
||||
name: 'BasicForm',
|
||||
components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
|
||||
props: {
|
||||
...basicProps,
|
||||
@@ -230,7 +230,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,10 +1,9 @@
|
||||
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';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
export function useModal(props): UseModalReturnType {
|
||||
|
||||
const modalRef = ref<Nullable<ModalMethods>>(null);
|
||||
const currentInstance = getCurrentInstance();
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ export interface ModalMethods {
|
||||
/**
|
||||
* 支持修改,DialogOptions 參數
|
||||
*/
|
||||
export interface ModalProps extends DialogOptions { }
|
||||
export type ModalProps = DialogOptions;
|
||||
|
||||
export type RegisterFn = (ModalInstance: ModalMethods) => void;
|
||||
|
||||
export type UseModalReturnType = [RegisterFn, ModalMethods];
|
||||
export type UseModalReturnType = [RegisterFn, ModalMethods];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,14 +11,13 @@
|
||||
<CellComponent
|
||||
v-bind="getComponentProps"
|
||||
:component="getComponent"
|
||||
:style="getWrapperStyle"
|
||||
:popoverVisible="getRuleVisible"
|
||||
:ruleMessage="ruleMessage"
|
||||
:rule="getRule"
|
||||
:class="getWrapperClass"
|
||||
ref="elRef"
|
||||
@options-change="handleOptionsChange"
|
||||
@pressEnter="handleEnter"
|
||||
@press-enter="handleEnter"
|
||||
/>
|
||||
</div>
|
||||
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||
@@ -33,7 +32,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import type { BasicColumn } from '../../types/table';
|
||||
import type { EditRecordRow } from './index';
|
||||
|
||||
@@ -51,7 +50,7 @@
|
||||
import { set, omit } from 'lodash-es';
|
||||
import { EventEnum } from '@/components/Table/src/componentMap';
|
||||
|
||||
import { milliseconds, format } from 'date-fns';
|
||||
import { parseISO, format } from 'date-fns';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditableCell',
|
||||
@@ -105,16 +104,28 @@
|
||||
|
||||
const isCheckValue = unref(getIsCheckComp);
|
||||
|
||||
const valueField = isCheckValue ? 'checked' : 'value';
|
||||
let valueField = isCheckValue ? 'checked' : 'value';
|
||||
const val = unref(currentValueRef);
|
||||
|
||||
let value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
||||
|
||||
if (isString(value) && component === 'NDatePicker') {
|
||||
value = milliseconds(value as Duration);
|
||||
} else if (isArray(value) && component === 'NDatePicker') {
|
||||
value = value.map((item) => milliseconds(item));
|
||||
//TODO 特殊处理 NDatePicker 可能要根据项目 规范自行调整代码
|
||||
if (component === 'NDatePicker') {
|
||||
if (isString(value)) {
|
||||
if (compProps.valueFormat) {
|
||||
valueField = 'formatted-value';
|
||||
} else {
|
||||
value = parseISO(value as any).getTime();
|
||||
}
|
||||
} else if (isArray(value)) {
|
||||
if (compProps.valueFormat) {
|
||||
valueField = 'formatted-value';
|
||||
} else {
|
||||
value = value.map((item) => parseISO(item).getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onEvent: any = editComponent ? EventEnum[editComponent] : undefined;
|
||||
|
||||
return {
|
||||
@@ -146,15 +157,6 @@
|
||||
return option?.label ?? value;
|
||||
});
|
||||
|
||||
const getWrapperStyle = computed((): CSSProperties => {
|
||||
// if (unref(getIsCheckComp) || unref(getRowEditable)) {
|
||||
// return {};
|
||||
// }
|
||||
return {
|
||||
width: 'calc(100% - 48px)',
|
||||
};
|
||||
});
|
||||
|
||||
const getWrapperClass = computed(() => {
|
||||
const { align = 'center' } = props.column;
|
||||
return `edit-cell-align-${align}`;
|
||||
@@ -188,6 +190,7 @@
|
||||
|
||||
async function handleChange(e: any) {
|
||||
const component = unref(getComponent);
|
||||
const compProps = props.column?.editComponentProps ?? {};
|
||||
if (!e) {
|
||||
currentValueRef.value = e;
|
||||
} else if (e?.target && Reflect.has(e.target, 'value')) {
|
||||
@@ -198,10 +201,20 @@
|
||||
currentValueRef.value = e;
|
||||
}
|
||||
|
||||
//TODO 根据组件格式化值
|
||||
// if (component === 'NDatePicker') {
|
||||
// currentValueRef.value = format(currentValueRef.value,'yyyy-MM-dd HH:mm:ss');
|
||||
// }
|
||||
//TODO 特殊处理 NDatePicker 可能要根据项目 规范自行调整代码
|
||||
if (component === 'NDatePicker') {
|
||||
if (isNumber(currentValueRef.value)) {
|
||||
if (compProps.valueFormat) {
|
||||
currentValueRef.value = format(currentValueRef.value, compProps.valueFormat);
|
||||
}
|
||||
} else if (isArray(currentValueRef.value)) {
|
||||
if (compProps.valueFormat) {
|
||||
currentValueRef.value = currentValueRef.value.map((item) => {
|
||||
format(item, compProps.valueFormat);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onChange = props.column?.editComponentProps?.onChange;
|
||||
if (onChange && isFunction(onChange)) onChange(...arguments);
|
||||
@@ -303,104 +316,103 @@
|
||||
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
|
||||
if (props.record) {
|
||||
/* eslint-disable */
|
||||
isArray(props.record[cbs])
|
||||
? props.record[cbs]?.push(handle)
|
||||
: (props.record[cbs] = [handle]);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.record) {
|
||||
initCbs('submitCbs', handleSubmit);
|
||||
initCbs('validCbs', handleSubmiRule);
|
||||
initCbs('cancelCbs', handleCancel);
|
||||
|
||||
if (props.column.key) {
|
||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||
props.record.editValueRefs[props.column.key] = currentValueRef;
|
||||
}
|
||||
/* eslint-disable */
|
||||
props.record.onCancelEdit = () => {
|
||||
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
|
||||
};
|
||||
/* eslint-disable */
|
||||
props.record.onSubmitEdit = async() => {
|
||||
if (isArray(props.record?.submitCbs)) {
|
||||
const validFns = (props.record?.validCbs || []).map((fn) => fn());
|
||||
|
||||
const res = await Promise.all(validFns);
|
||||
|
||||
const pass = res.every((item) => !!item);
|
||||
|
||||
if (!pass) return;
|
||||
const submitFns = props.record?.submitCbs || [];
|
||||
submitFns.forEach((fn) => fn(false, false));
|
||||
table.emit?.('edit-row-end');
|
||||
return true;
|
||||
isArray(props.record[cbs])
|
||||
? props.record[cbs]?.push(handle)
|
||||
: (props.record[cbs] = [handle]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isEdit,
|
||||
handleEdit,
|
||||
currentValueRef,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
handleCancel,
|
||||
elRef,
|
||||
getComponent,
|
||||
getRule,
|
||||
onClickOutside,
|
||||
ruleMessage,
|
||||
getRuleVisible,
|
||||
getComponentProps,
|
||||
handleOptionsChange,
|
||||
getWrapperStyle,
|
||||
getWrapperClass,
|
||||
getRowEditable,
|
||||
getValues,
|
||||
handleEnter,
|
||||
// getSize,
|
||||
};
|
||||
},
|
||||
if (props.record) {
|
||||
initCbs('submitCbs', handleSubmit);
|
||||
initCbs('validCbs', handleSubmiRule);
|
||||
initCbs('cancelCbs', handleCancel);
|
||||
|
||||
if (props.column.key) {
|
||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||
props.record.editValueRefs[props.column.key] = currentValueRef;
|
||||
}
|
||||
/* eslint-disable */
|
||||
props.record.onCancelEdit = () => {
|
||||
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
|
||||
};
|
||||
/* eslint-disable */
|
||||
props.record.onSubmitEdit = async () => {
|
||||
if (isArray(props.record?.submitCbs)) {
|
||||
const validFns = (props.record?.validCbs || []).map((fn) => fn());
|
||||
|
||||
const res = await Promise.all(validFns);
|
||||
|
||||
const pass = res.every((item) => !!item);
|
||||
|
||||
if (!pass) return;
|
||||
const submitFns = props.record?.submitCbs || [];
|
||||
submitFns.forEach((fn) => fn(false, false));
|
||||
table.emit?.('edit-row-end');
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isEdit,
|
||||
handleEdit,
|
||||
currentValueRef,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
handleCancel,
|
||||
elRef,
|
||||
getComponent,
|
||||
getRule,
|
||||
onClickOutside,
|
||||
ruleMessage,
|
||||
getRuleVisible,
|
||||
getComponentProps,
|
||||
handleOptionsChange,
|
||||
getWrapperClass,
|
||||
getRowEditable,
|
||||
getValues,
|
||||
handleEnter,
|
||||
// getSize,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.editable-cell {
|
||||
&-content {
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
.editable-cell {
|
||||
&-content {
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&-comp{
|
||||
flex: 1;
|
||||
&-comp {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
font-size: 14px;
|
||||
//position: absolute;
|
||||
//top: 4px;
|
||||
//right: 0;
|
||||
display: none;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.edit-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
font-size: 14px;
|
||||
//position: absolute;
|
||||
//top: 4px;
|
||||
//right: 0;
|
||||
display: none;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.edit-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,13 +25,26 @@
|
||||
</template>
|
||||
<div class="table-toolbar-inner">
|
||||
<n-checkbox-group v-model:value="checkList" @update:value="onChange">
|
||||
<Draggable v-model="columnsList" animation="300" item-key="key" @end="draggableEnd">
|
||||
<Draggable
|
||||
v-model="columnsList"
|
||||
animation="300"
|
||||
item-key="key"
|
||||
filter=".no-draggable"
|
||||
:move="onMove"
|
||||
@end="draggableEnd"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div
|
||||
class="table-toolbar-inner-checkbox"
|
||||
:class="{ 'table-toolbar-inner-checkbox-dark': getDarkTheme === true }"
|
||||
:class="{
|
||||
'table-toolbar-inner-checkbox-dark': getDarkTheme === true,
|
||||
'no-draggable': element.draggable === false,
|
||||
}"
|
||||
>
|
||||
<span class="drag-icon">
|
||||
<span
|
||||
class="drag-icon"
|
||||
:class="{ 'drag-icon-hidden': element.draggable === false }"
|
||||
>
|
||||
<n-icon size="18">
|
||||
<DragOutlined />
|
||||
</n-icon>
|
||||
@@ -88,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 {
|
||||
@@ -211,6 +224,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
function onMove(e) {
|
||||
if (e.draggedContext.element.draggable === false) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//固定
|
||||
function fixedColumn(item, fixed) {
|
||||
if (!state.checkList.includes(item.key)) return;
|
||||
@@ -232,6 +250,7 @@
|
||||
onChange,
|
||||
onCheckAll,
|
||||
onSelection,
|
||||
onMove,
|
||||
resetColumns,
|
||||
fixedColumn,
|
||||
draggableEnd,
|
||||
@@ -275,6 +294,10 @@
|
||||
display: inline-flex;
|
||||
margin-right: 8px;
|
||||
cursor: move;
|
||||
&-hidden {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-item {
|
||||
|
||||
@@ -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;
|
||||
@@ -14,6 +14,8 @@ export interface BasicColumn extends TableBaseColumn {
|
||||
auth?: string[];
|
||||
// 业务控制是否显示
|
||||
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||
// 控制是否支持拖拽,默认支持
|
||||
draggable?: boolean;
|
||||
}
|
||||
|
||||
export interface TableActionType {
|
||||
@@ -32,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;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
<img :src="item" />
|
||||
</div>
|
||||
<div class="img-box-actions">
|
||||
<n-icon size="18" class="action-icon mx-2" @click="preview(item)">
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
|
||||
<EyeOutlined />
|
||||
</n-icon>
|
||||
<n-icon size="18" class="action-icon mx-2" @click="remove(index)">
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="remove(index)">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
@@ -36,7 +36,7 @@
|
||||
@before-upload="beforeUpload"
|
||||
@finish="finish"
|
||||
>
|
||||
<div class="flex justify-center flex-col">
|
||||
<div class="flex flex-col justify-center">
|
||||
<n-icon size="18" class="m-auto">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
@@ -68,7 +68,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, computed } from 'vue';
|
||||
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
|
||||
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { basicProps } from './props';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
@@ -106,11 +106,15 @@
|
||||
});
|
||||
|
||||
//赋值默认图片显示
|
||||
if (props.value.length) {
|
||||
state.imgList = props.value.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
}
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
state.imgList = props.value.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
//预览
|
||||
function preview(url: string) {
|
||||
@@ -233,6 +237,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,36 +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 getHeaderSetting = computed(() => projectStore.headerSetting);
|
||||
const isMobile = computed(() => projectStore.isMobile);
|
||||
|
||||
const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting);
|
||||
const headerSetting = computed(() => projectStore.headerSetting);
|
||||
|
||||
const getMenuSetting = computed(() => projectStore.menuSetting);
|
||||
const multiTabsSetting = computed(() => projectStore.multiTabsSetting);
|
||||
|
||||
const getCrumbsSetting = computed(() => projectStore.crumbsSetting);
|
||||
const menuSetting = computed(() => projectStore.menuSetting);
|
||||
|
||||
const getPermissionMode = computed(() => projectStore.permissionMode);
|
||||
const crumbsSetting = computed(() => projectStore.crumbsSetting);
|
||||
|
||||
const getShowFooter = computed(() => projectStore.showFooter);
|
||||
const permissionMode = computed(() => projectStore.permissionMode);
|
||||
|
||||
const getIsPageAnimate = computed(() => projectStore.isPageAnimate);
|
||||
const showFooter = computed(() => projectStore.showFooter);
|
||||
|
||||
const getPageAnimateType = computed(() => projectStore.pageAnimateType);
|
||||
const isPageAnimate = computed(() => projectStore.isPageAnimate);
|
||||
|
||||
const pageAnimateType = computed(() => projectStore.pageAnimateType);
|
||||
|
||||
return {
|
||||
getNavMode,
|
||||
getNavTheme,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
62
src/hooks/web/usePage.ts
Normal file
62
src/hooks/web/usePage.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { RouteLocationRaw, Router } from 'vue-router';
|
||||
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
import { RedirectName } from '@/router/constant';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
import { isString } from '@/utils/is';
|
||||
import { unref } from 'vue';
|
||||
|
||||
export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEnum };
|
||||
|
||||
function handleError(e: Error) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面切换
|
||||
*/
|
||||
export function useGo(_router?: Router) {
|
||||
let router;
|
||||
if (!_router) {
|
||||
router = useRouter();
|
||||
}
|
||||
const { push, replace } = _router || router;
|
||||
function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) {
|
||||
if (!opt) {
|
||||
return;
|
||||
}
|
||||
if (isString(opt)) {
|
||||
isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError);
|
||||
} else {
|
||||
const o = opt as RouteLocationRaw;
|
||||
isReplace ? replace(o).catch(handleError) : push(o).catch(handleError);
|
||||
}
|
||||
}
|
||||
return go;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重做当前页面
|
||||
*/
|
||||
export const useRedo = (_router?: Router) => {
|
||||
const { push, currentRoute } = _router || useRouter();
|
||||
const { query, params = {}, name, fullPath } = unref(currentRoute.value);
|
||||
function redo(): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
if (name === RedirectName) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
if (name && Object.keys(params).length > 0) {
|
||||
params['_redirect_type'] = 'name';
|
||||
params['path'] = String(name);
|
||||
} else {
|
||||
params['_redirect_type'] = 'path';
|
||||
params['path'] = fullPath;
|
||||
}
|
||||
push({ name: RedirectName, params, query }).then(() => resolve(true));
|
||||
});
|
||||
}
|
||||
return redo;
|
||||
};
|
||||
@@ -259,8 +259,7 @@
|
||||
title: props.title,
|
||||
isDrawer: false,
|
||||
placement: 'right',
|
||||
alertText:
|
||||
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
|
||||
alertText: '该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置',
|
||||
appThemeList: designStore.appThemeList,
|
||||
});
|
||||
|
||||
@@ -359,6 +358,7 @@
|
||||
margin: 0 5px 5px 0;
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
|
||||
.n-icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
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"
|
||||
@@ -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>
|
||||
@@ -131,10 +134,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',
|
||||
@@ -149,37 +153,37 @@
|
||||
},
|
||||
setup(props) {
|
||||
const userStore = useUserStore();
|
||||
const useLockscreen = useLockscreenStore();
|
||||
const useLockscreen = useScreenLockStore();
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const { getNavMode, getNavTheme, getHeaderSetting, getMenuSetting, getCrumbsSetting } =
|
||||
useProjectSetting();
|
||||
const { navMode, navTheme, headerSetting, menuSetting, crumbsSetting } = useProjectSetting();
|
||||
|
||||
const { username } = userStore?.info || {};
|
||||
const { name } = userStore?.info || {};
|
||||
|
||||
const drawerSetting = ref();
|
||||
|
||||
const state = reactive({
|
||||
username: username || '',
|
||||
username: name ?? '',
|
||||
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`})`,
|
||||
@@ -336,6 +340,7 @@
|
||||
getInverted,
|
||||
getMenuLocation,
|
||||
mixMenu,
|
||||
websiteConfig,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -347,7 +352,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>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<RouterView>
|
||||
<template #default="{ Component, route }">
|
||||
<transition :name="getTransitionName" mode="out-in" appear>
|
||||
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
|
||||
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component v-else :is="Component" :key="route.fullPath" />
|
||||
@@ -30,13 +30,13 @@
|
||||
},
|
||||
},
|
||||
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) : '';
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
// 点击菜单
|
||||
@@ -125,6 +127,7 @@
|
||||
} else {
|
||||
router.push({ name: key });
|
||||
}
|
||||
emit('clickMenuItem' as any, key);
|
||||
}
|
||||
|
||||
//展开菜单
|
||||
|
||||
@@ -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,16 +35,12 @@
|
||||
<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)"
|
||||
>
|
||||
<span>{{ element.meta.title }}</span>
|
||||
<n-icon
|
||||
size="14"
|
||||
@click.stop="closeTabItem(element)"
|
||||
v-if="element.path !== baseHome"
|
||||
>
|
||||
<n-icon size="14" @click.stop="closeTabItem(element)" v-if="!element.meta.affix">
|
||||
<CloseOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
@@ -86,7 +82,6 @@
|
||||
computed,
|
||||
ref,
|
||||
toRefs,
|
||||
unref,
|
||||
provide,
|
||||
watch,
|
||||
onMounted,
|
||||
@@ -116,6 +111,7 @@
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import { useGo } from '@/hooks/web/usePage';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TabsView',
|
||||
@@ -133,7 +129,7 @@
|
||||
},
|
||||
setup(props) {
|
||||
const { getDarkTheme, getAppTheme } = useDesignSetting();
|
||||
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } =
|
||||
const { navMode, headerSetting, menuSetting, multiTabsSetting, isMobile } =
|
||||
useProjectSetting();
|
||||
const settingStore = useProjectSettingStore();
|
||||
|
||||
@@ -145,6 +141,7 @@
|
||||
const navScroll: any = ref(null);
|
||||
const navWrap: any = ref(null);
|
||||
const isCurrent = ref(false);
|
||||
const go = useGo();
|
||||
|
||||
const themeVars = useThemeVars();
|
||||
|
||||
@@ -163,7 +160,7 @@
|
||||
dropdownY: 0,
|
||||
showDropdown: false,
|
||||
isMultiHeaderFixed: false,
|
||||
multiTabsSetting: getMultiTabsSetting,
|
||||
multiTabsSetting: multiTabsSetting,
|
||||
});
|
||||
|
||||
// 获取简易的路由对象
|
||||
@@ -175,23 +172,28 @@
|
||||
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 (isMobile.value) {
|
||||
return {
|
||||
left: '0px',
|
||||
width: '100%',
|
||||
};
|
||||
}
|
||||
return {
|
||||
left: lenNum,
|
||||
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
|
||||
@@ -200,7 +202,7 @@
|
||||
|
||||
//tags 右侧下拉菜单
|
||||
const TabsMenuOptions = computed(() => {
|
||||
const isDisabled = unref(tabsList).length <= 1;
|
||||
const isDisabled = tabsList.value.length <= 1;
|
||||
return [
|
||||
{
|
||||
label: '刷新当前',
|
||||
@@ -210,7 +212,7 @@
|
||||
{
|
||||
label: `关闭当前`,
|
||||
key: '2',
|
||||
disabled: unref(isCurrent) || isDisabled,
|
||||
disabled: isCurrent.value || isDisabled,
|
||||
icon: renderIcon(CloseOutlined),
|
||||
},
|
||||
{
|
||||
@@ -258,8 +260,8 @@
|
||||
window.pageYOffset ||
|
||||
document.body.scrollTop; // 滚动条偏移量
|
||||
state.isMultiHeaderFixed = !!(
|
||||
!getHeaderSetting.fixed &&
|
||||
getMultiTabsSetting.fixed &&
|
||||
!headerSetting.value.fixed &&
|
||||
multiTabsSetting.value.fixed &&
|
||||
scrollTop >= 64
|
||||
);
|
||||
}
|
||||
@@ -292,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 }
|
||||
@@ -323,7 +325,7 @@
|
||||
const reloadPage = () => {
|
||||
delKeepAliveCompName();
|
||||
router.push({
|
||||
path: '/redirect' + unref(route).fullPath,
|
||||
path: '/redirect' + route.fullPath,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -356,7 +358,6 @@
|
||||
|
||||
// 关闭全部
|
||||
const closeAll = () => {
|
||||
localStorage.removeItem('routes');
|
||||
tabsViewStore.closeAllTabs();
|
||||
router.replace(PageEnum.BASE_HOME);
|
||||
updateNavScroll();
|
||||
@@ -473,7 +474,7 @@
|
||||
const { fullPath } = e;
|
||||
if (fullPath === route.fullPath) return;
|
||||
state.activeKey = fullPath;
|
||||
router.push({ path: fullPath });
|
||||
go(e, true);
|
||||
}
|
||||
|
||||
//删除tab
|
||||
@@ -499,7 +500,6 @@
|
||||
navScroll,
|
||||
route,
|
||||
tabsList,
|
||||
baseHome: PageEnum.BASE_HOME_REDIRECT,
|
||||
goPage,
|
||||
closeTabItem,
|
||||
closeLeft,
|
||||
@@ -639,7 +639,6 @@
|
||||
background: var(--color);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
//margin-right: 10px;
|
||||
|
||||
&-btn {
|
||||
color: var(--color);
|
||||
@@ -662,7 +661,7 @@
|
||||
.tabs-view-fix {
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
padding: 6px 19px 6px 10px;
|
||||
padding: 6px 10px 6px 10px;
|
||||
left: 200px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<n-layout class="layout" :position="fixedMenu" has-sider>
|
||||
<n-layout-sider
|
||||
v-if="isMixMenuNoneSub && (navMode === 'vertical' || navMode === 'horizontal-mix')"
|
||||
v-if="
|
||||
!isMobile && isMixMenuNoneSub && (navMode === 'vertical' || navMode === 'horizontal-mix')
|
||||
"
|
||||
show-trigger="bar"
|
||||
@collapse="collapsed = true"
|
||||
:position="fixedMenu"
|
||||
@@ -18,6 +20,25 @@
|
||||
<AsideMenu v-model:collapsed="collapsed" v-model:location="getMenuLocation" />
|
||||
</n-layout-sider>
|
||||
|
||||
<n-drawer
|
||||
v-model:show="showSideDrawer"
|
||||
:width="menuWidth"
|
||||
:placement="'left'"
|
||||
class="layout-side-drawer"
|
||||
>
|
||||
<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">
|
||||
<n-layout-header :inverted="getHeaderInverted" :position="fixedHeader">
|
||||
<PageHeader v-model:collapsed="collapsed" :inverted="inverted" />
|
||||
@@ -65,33 +86,37 @@
|
||||
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(menuSetting);
|
||||
|
||||
const isMobile = computed<boolean>({
|
||||
get: () => settingStore.getIsMobile,
|
||||
set: (val) => settingStore.setIsMobile(val),
|
||||
});
|
||||
|
||||
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) {
|
||||
@@ -101,58 +126,79 @@
|
||||
});
|
||||
|
||||
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 showSideDrawer = computed({
|
||||
get: () => isMobile.value && collapsed.value,
|
||||
set: (val) => (collapsed.value = val),
|
||||
});
|
||||
|
||||
//判断是否触发移动端模式
|
||||
const checkMobileMode = () => {
|
||||
if (document.body.clientWidth <= mobileWidth) {
|
||||
isMobile.value = true;
|
||||
} else {
|
||||
isMobile.value = false;
|
||||
}
|
||||
collapsed.value = false;
|
||||
};
|
||||
|
||||
const watchWidth = () => {
|
||||
const Width = document.body.clientWidth;
|
||||
if (Width <= 950) {
|
||||
collapsed.value = true;
|
||||
} else collapsed.value = false;
|
||||
|
||||
checkMobileMode();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkMobileMode();
|
||||
window.addEventListener('resize', watchWidth);
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$loading'] = useLoadingBar();
|
||||
window['$loading'].finish();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.layout-side-drawer {
|
||||
background-color: rgb(0, 20, 40);
|
||||
|
||||
.layout-sider {
|
||||
min-height: 100vh;
|
||||
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
|
||||
position: relative;
|
||||
z-index: 13;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
@@ -213,7 +259,7 @@
|
||||
}
|
||||
|
||||
.fluid-header {
|
||||
padding-top: 0px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.main-view-fix {
|
||||
|
||||
29
src/main.ts
29
src/main.ts
@@ -1,23 +1,22 @@
|
||||
import './styles/tailwind.css';
|
||||
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 MakeitCaptcha from 'makeit-captcha';
|
||||
import 'makeit-captcha/dist/captcha.min.css';
|
||||
import { setupNaive, setupDirectives } from '@/plugins';
|
||||
import { AppProvider } from '@/components/Application';
|
||||
|
||||
async function bootstrap() {
|
||||
const appProvider = createApp(AppProvider);
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(MakeitCaptcha);
|
||||
// 挂载状态管理
|
||||
setupStore(app);
|
||||
|
||||
// 注册全局常用的 naive-ui 组件
|
||||
setupNaive(app);
|
||||
|
||||
// 挂载 naive-ui 脱离上下文的 Api
|
||||
setupNaiveDiscreteApi();
|
||||
|
||||
// 注册全局自定义组件
|
||||
//setupCustomComponents();
|
||||
|
||||
@@ -27,18 +26,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'),
|
||||
|
||||
@@ -34,6 +34,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
meta: {
|
||||
title: '主控台',
|
||||
permissions: ['dashboard_console'],
|
||||
affix: true,
|
||||
},
|
||||
component: () => import('@/views/dashboard/console/console.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,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -17,12 +17,21 @@ const routes: Array<RouteRecordRaw> = [
|
||||
icon: renderIcon(DesktopOutline),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'naive-admin',
|
||||
name: 'naive-admin',
|
||||
meta: {
|
||||
title: 'NaiveAdmin',
|
||||
frameSrc: 'https://www.naiveadmin.com',
|
||||
},
|
||||
component: IFrame,
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
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,
|
||||
|
||||
@@ -3,6 +3,8 @@ const setting = {
|
||||
navMode: 'vertical',
|
||||
//导航风格 dark 暗色侧边栏 light 白色侧边栏 header-dark 暗色顶栏
|
||||
navTheme: 'dark',
|
||||
// 是否处于移动端模式
|
||||
isMobile: false,
|
||||
//顶部
|
||||
headerSetting: {
|
||||
//背景色
|
||||
@@ -33,6 +35,10 @@ const setting = {
|
||||
fixed: true,
|
||||
//分割菜单
|
||||
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,11 +1,11 @@
|
||||
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,
|
||||
navTheme,
|
||||
isMobile,
|
||||
headerSetting,
|
||||
showFooter,
|
||||
menuSetting,
|
||||
@@ -19,14 +19,15 @@ 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; //路由动画类型
|
||||
isMobile: boolean; // 是否处于移动端模式
|
||||
}
|
||||
|
||||
export const useProjectSettingStore = defineStore({
|
||||
@@ -34,6 +35,7 @@ export const useProjectSettingStore = defineStore({
|
||||
state: (): ProjectSettingState => ({
|
||||
navMode: navMode,
|
||||
navTheme,
|
||||
isMobile,
|
||||
headerSetting,
|
||||
showFooter,
|
||||
menuSetting,
|
||||
@@ -50,6 +52,9 @@ export const useProjectSettingStore = defineStore({
|
||||
getNavTheme(): string {
|
||||
return this.navTheme;
|
||||
},
|
||||
getIsMobile(): boolean {
|
||||
return this.isMobile;
|
||||
},
|
||||
getHeaderSetting(): object {
|
||||
return this.headerSetting;
|
||||
},
|
||||
@@ -63,7 +68,7 @@ export const useProjectSettingStore = defineStore({
|
||||
return this.multiTabsSetting;
|
||||
},
|
||||
getCrumbsSetting(): object {
|
||||
return this.multiTabsSetting;
|
||||
return this.crumbsSetting;
|
||||
},
|
||||
getPermissionMode(): string {
|
||||
return this.permissionMode;
|
||||
@@ -79,10 +84,8 @@ export const useProjectSettingStore = defineStore({
|
||||
setNavTheme(value: string): void {
|
||||
this.navTheme = value;
|
||||
},
|
||||
setIsMobile(value: boolean): void {
|
||||
this.isMobile = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 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,19 +1,28 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
import { TABS_ROUTES } from '../mutation-types';
|
||||
|
||||
// 不需要出现在标签页中的路由
|
||||
const whiteList = ['Redirect', 'login'];
|
||||
|
||||
export type RouteItem = Partial<RouteLocationNormalized> & {
|
||||
fullPath: string;
|
||||
path: string;
|
||||
name: string;
|
||||
hash: string;
|
||||
meta: object;
|
||||
params: object;
|
||||
query: object;
|
||||
};
|
||||
|
||||
export type ITabsViewState = {
|
||||
tabsList: RouteItem[]; // 标签页
|
||||
};
|
||||
|
||||
//保留固定路由
|
||||
function retainAffixRoute(list: any[]) {
|
||||
return list.filter((item) => item?.meta?.affix ?? false);
|
||||
}
|
||||
|
||||
export const useTabsViewStore = defineStore({
|
||||
id: 'app-tabs-view',
|
||||
state: (): ITabsViewState => ({
|
||||
@@ -21,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);
|
||||
@@ -34,29 +43,30 @@ 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() {
|
||||
// 关闭全部
|
||||
this.tabsList = [];
|
||||
localStorage.removeItem(TABS_ROUTES);
|
||||
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
|
||||
name: 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,124 +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 .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 .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';
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user