mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-13 09:42:27 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97a94e74c | ||
|
|
da5231b384 | ||
|
|
97ae37efd0 | ||
|
|
b19430170f | ||
|
|
b642d28815 | ||
|
|
619669ec9e | ||
|
|
044976b790 | ||
|
|
b43ab1ceb4 | ||
|
|
f773a3ed06 | ||
|
|
7f81152793 | ||
|
|
f6be8f521e | ||
|
|
fa8b33acbe | ||
|
|
54e68db0c2 | ||
|
|
9542345b54 |
19
.editorconfig
Normal file
19
.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset=utf-8
|
||||||
|
end_of_line=LF
|
||||||
|
insert_final_newline=true
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
max_line_length = 100
|
||||||
|
|
||||||
|
[*.{yml,yaml,json}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
@@ -13,14 +13,18 @@ VITE_BASE_URL = /
|
|||||||
# 是否删除console
|
# 是否删除console
|
||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
|
|
||||||
|
# 跨域代理,可以配置多个,请注意不要换行
|
||||||
|
#VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
|
||||||
|
# VITE_PROXY=[["/api","https://naive-ui-admin"]]
|
||||||
|
|
||||||
# API 接口地址
|
# API 接口地址
|
||||||
VITE_APP_API_URL = /
|
VITE_GLOB_API_URL =
|
||||||
|
|
||||||
# 图片上传地址
|
# 图片上传地址
|
||||||
VITE_GLOB_UPLOAD_URL= /
|
VITE_GLOB_UPLOAD_URL=
|
||||||
|
|
||||||
# 图片前缀地址
|
# 图片前缀地址
|
||||||
VITE_GLOB_IMG_URL= /
|
VITE_GLOB_IMG_URL=
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀
|
||||||
VITE_GLOB_API_URL_PREFIX = /api
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
|
|||||||
@@ -11,13 +11,21 @@ VITE_BASE_URL = /
|
|||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
|
|
||||||
# API
|
# API
|
||||||
VITE_APP_API_URL = /
|
VITE_GLOB_API_URL =
|
||||||
|
|
||||||
# 图片上传地址
|
# 图片上传地址
|
||||||
VITE_GLOB_UPLOAD_URL= /
|
VITE_GLOB_UPLOAD_URL=
|
||||||
|
|
||||||
# 图片前缀地址
|
# 图片前缀地址
|
||||||
VITE_GLOB_IMG_URL= /
|
VITE_GLOB_IMG_URL=
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀
|
||||||
VITE_GLOB_API_URL_PREFIX = /api
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
|
|
||||||
|
# 是否启用gzip压缩或brotli压缩
|
||||||
|
# 可选: gzip | brotli | none
|
||||||
|
# 如果你需要多种形式,你可以用','来分隔
|
||||||
|
VITE_BUILD_COMPRESS = 'none'
|
||||||
|
|
||||||
|
# 使用压缩时是否删除原始文件,默认为false
|
||||||
|
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
|
||||||
|
|||||||
74
.eslintrc.js
74
.eslintrc.js
@@ -1,9 +1,11 @@
|
|||||||
module.exports = {
|
// @ts-check
|
||||||
|
const { defineConfig } = require('eslint-define-config');
|
||||||
|
module.exports = defineConfig({
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
node: true,
|
node: true,
|
||||||
es6: true
|
es6: true,
|
||||||
},
|
},
|
||||||
parser: 'vue-eslint-parser',
|
parser: 'vue-eslint-parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
@@ -12,21 +14,46 @@ module.exports = {
|
|||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
jsxPragma: 'React',
|
jsxPragma: 'React',
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true
|
jsx: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'plugin:vue/vue3-recommended',
|
'plugin:vue/vue3-recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'prettier',
|
'prettier',
|
||||||
'plugin:prettier/recommended'
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:jest/recommended',
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/no-unused-components': 'off',
|
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||||
'vue/no-unused-vars': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'vue/no-v-for-template-key-on-child': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
'vue/custom-event-name-casing': 'off',
|
'vue/custom-event-name-casing': 'off',
|
||||||
// 'vue/attributes-order': 'off',
|
'no-use-before-define': 'off',
|
||||||
|
'@typescript-eslint/no-use-before-define': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
'@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: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'space-before-function-paren': 'off',
|
||||||
|
|
||||||
|
'vue/attributes-order': 'off',
|
||||||
'vue/one-component-per-file': 'off',
|
'vue/one-component-per-file': 'off',
|
||||||
'vue/html-closing-bracket-newline': 'off',
|
'vue/html-closing-bracket-newline': 'off',
|
||||||
'vue/max-attributes-per-line': 'off',
|
'vue/max-attributes-per-line': 'off',
|
||||||
@@ -34,34 +61,17 @@ module.exports = {
|
|||||||
'vue/singleline-html-element-content-newline': 'off',
|
'vue/singleline-html-element-content-newline': 'off',
|
||||||
'vue/attribute-hyphenation': 'off',
|
'vue/attribute-hyphenation': 'off',
|
||||||
'vue/require-default-prop': 'off',
|
'vue/require-default-prop': 'off',
|
||||||
|
|
||||||
'space-before-function-paren': 'off',
|
|
||||||
|
|
||||||
'@typescript-eslint/camelcase': 'off',
|
|
||||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
|
||||||
'@typescript-eslint/no-empty-function': 'off',
|
|
||||||
'no-use-before-define': 'off',
|
|
||||||
'@typescript-eslint/no-use-before-define': 'off',
|
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
|
||||||
'@typescript-eslint/ban-types': 'off',
|
|
||||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
|
||||||
'vue/html-self-closing': [
|
'vue/html-self-closing': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
html: {
|
html: {
|
||||||
void: 'always',
|
void: 'always',
|
||||||
normal: 'never',
|
normal: 'never',
|
||||||
component: 'always'
|
component: 'always',
|
||||||
},
|
},
|
||||||
svg: 'always',
|
svg: 'always',
|
||||||
math: 'always'
|
math: 'always',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
|
|||||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,3 +1,32 @@
|
|||||||
|
# 1.5 (2021-07-30)
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复表格列配置,拖拽时最后的操作列重复增加
|
||||||
|
- 多标签页交互优化
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- `项目文档`已上线
|
||||||
|
- `Application`组件加载机制优化,解决路由守卫,Axios中可使用,Dialog,Message 等之类组件
|
||||||
|
- `BasicTable` 组件新增`高度自适应`,`单元格编辑`,`整行编辑` 特性
|
||||||
|
- `nprogress` 移除,用 `Loading Bar`代替
|
||||||
|
- 打包支持`gzip`,`brotli` 压缩
|
||||||
|
- 新增代理`VITE_PROXY`配置
|
||||||
|
- 路由菜单,支持多级菜单
|
||||||
|
- 依赖升级
|
||||||
|
- 本次更新,有破坏性更新,涉及文件重命名,增删调整
|
||||||
|
|
||||||
|
|
||||||
|
# 1.4 (2021-07-21)
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- vite降至2.3.6
|
||||||
|
- 多标签页交互优化
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 新增 `TableAction` 组件
|
||||||
|
- 新增 `菜单权限管理` 示例
|
||||||
|
- 新增 `角色权限管理` 示例
|
||||||
|
- 持续更新更多实用组件及示例,感谢Star
|
||||||
|
|
||||||
|
|
||||||
# 1.3 (2021-07-19)
|
# 1.3 (2021-07-19)
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
- 修复多标签页左右切换按钮自适应展示
|
- 修复多标签页左右切换按钮自适应展示
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -1,6 +1,6 @@
|
|||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
Naive Ui Admin 是一个免费开源的中后台模版,使用了最新的`vue3`,`vite2`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
|
[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/) 的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目,该项目使用最新的前端技术栈,相信不管是从新技术使用还是其他方面,都能帮助到你。
|
||||||
|
|
||||||
## 特性
|
## 特性
|
||||||
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发
|
- **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发
|
||||||
@@ -10,32 +10,15 @@ Naive Ui Admin 是一个免费开源的中后台模版,使用了最新的`vue3
|
|||||||
- **权限** 内置完善的动态路由权限生成方案
|
- **权限** 内置完善的动态路由权限生成方案
|
||||||
- **组件** 二次封装了多个常用的组件
|
- **组件** 二次封装了多个常用的组件
|
||||||
|
|
||||||
### 页面功能
|
|
||||||
#### 系统看板
|
|
||||||
- [x] 主控台
|
|
||||||
- [ ] 监控页
|
|
||||||
- [x] 工作台
|
|
||||||
- [x] 表单页面
|
|
||||||
- [x] 列表页面
|
|
||||||
- [x] 异常页面
|
|
||||||
- [x] 结果页面
|
|
||||||
- [x] 设置页面
|
|
||||||
|
|
||||||
### 页面组件
|
|
||||||
#### ProTable
|
|
||||||
- [x] 基础表格
|
|
||||||
- [x] 上传图片
|
|
||||||
- [x] 滑块验证码
|
|
||||||
- 持续开发中...
|
|
||||||
|
|
||||||
## 在线预览
|
## 在线预览
|
||||||
- [naive-ui-admin](https://jekip.github.io)
|
- [naive-ui-admin](https://jekip.github.io)
|
||||||
|
|
||||||
账号:admin,密码:123456
|
账号:admin,密码:123456(随意)
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
[文档地址](https://github.com/jekip/naive-ui-admin) - 待完善
|
[文档地址](https://jekip.github.io/docs/)
|
||||||
|
|
||||||
## 准备
|
## 准备
|
||||||
|
|
||||||
@@ -82,7 +65,7 @@ yarn build
|
|||||||
[CHANGELOG](./CHANGELOG.md)
|
[CHANGELOG](./CHANGELOG.md)
|
||||||
|
|
||||||
## 感谢
|
## 感谢
|
||||||
[@Vben](https://github.com/anncwb/vue-vben-admin) 借鉴 vue-vben-admin 实现的骨架,同时也使用作者开发的 vite 插件,非常感谢作者。
|
[@Vben](https://github.com/anncwb/vue-vben-admin) 借鉴 vue-vben-admin 实现的骨架,同时也使用作者开发的 vite 插件,再次感谢作者。
|
||||||
|
|
||||||
|
|
||||||
## 如何贡献
|
## 如何贡献
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
|
|||||||
if (envName === 'VITE_PROXY') {
|
if (envName === 'VITE_PROXY') {
|
||||||
try {
|
try {
|
||||||
realName = JSON.parse(realName);
|
realName = JSON.parse(realName);
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ret[envName] = realName;
|
ret[envName] = realName;
|
||||||
process.env[envName] = realName;
|
process.env[envName] = realName;
|
||||||
@@ -51,8 +50,7 @@ export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.pr
|
|||||||
try {
|
try {
|
||||||
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
|
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
|
||||||
envConfig = { ...envConfig, ...env };
|
envConfig = { ...envConfig, ...env };
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(envConfig).forEach((key) => {
|
Object.keys(envConfig).forEach((key) => {
|
||||||
|
|||||||
35
build/vite/plugin/compress.ts
Normal file
35
build/vite/plugin/compress.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
|
||||||
|
* https://github.com/anncwb/vite-plugin-compression
|
||||||
|
*/
|
||||||
|
import type { Plugin } from 'vite';
|
||||||
|
|
||||||
|
import compressPlugin from 'vite-plugin-compression';
|
||||||
|
|
||||||
|
export function configCompressPlugin(
|
||||||
|
compress: 'gzip' | 'brotli' | 'none',
|
||||||
|
deleteOriginFile = false
|
||||||
|
): Plugin | Plugin[] {
|
||||||
|
const compressList = compress.split(',');
|
||||||
|
|
||||||
|
const plugins: Plugin[] = [];
|
||||||
|
|
||||||
|
if (compressList.includes('gzip')) {
|
||||||
|
plugins.push(
|
||||||
|
compressPlugin({
|
||||||
|
ext: '.gz',
|
||||||
|
deleteOriginFile,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (compressList.includes('brotli')) {
|
||||||
|
plugins.push(
|
||||||
|
compressPlugin({
|
||||||
|
ext: '.br',
|
||||||
|
algorithm: 'brotliCompress',
|
||||||
|
deleteOriginFile,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
@@ -5,9 +5,10 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
|
|||||||
|
|
||||||
import { configHtmlPlugin } from './html';
|
import { configHtmlPlugin } from './html';
|
||||||
import { configMockPlugin } from './mock';
|
import { configMockPlugin } from './mock';
|
||||||
|
import { configCompressPlugin } from './compress';
|
||||||
|
|
||||||
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) {
|
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) {
|
||||||
const { VITE_USE_MOCK } = viteEnv;
|
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
|
||||||
|
|
||||||
const vitePlugins: (Plugin | Plugin[])[] = [
|
const vitePlugins: (Plugin | Plugin[])[] = [
|
||||||
// have to
|
// have to
|
||||||
@@ -22,5 +23,12 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock)
|
|||||||
// vite-plugin-mock
|
// vite-plugin-mock
|
||||||
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock));
|
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock));
|
||||||
|
|
||||||
|
if (isBuild) {
|
||||||
|
// rollup-plugin-gzip
|
||||||
|
vitePlugins.push(
|
||||||
|
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return vitePlugins;
|
return vitePlugins;
|
||||||
}
|
}
|
||||||
|
|||||||
34
build/vite/proxy.ts
Normal file
34
build/vite/proxy.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Used to parse the .env.development proxy configuration
|
||||||
|
*/
|
||||||
|
import type { ProxyOptions } from 'vite';
|
||||||
|
|
||||||
|
type ProxyItem = [string, string];
|
||||||
|
|
||||||
|
type ProxyList = ProxyItem[];
|
||||||
|
|
||||||
|
type ProxyTargetList = Record<string, ProxyOptions & { rewrite: (path: string) => string }>;
|
||||||
|
|
||||||
|
const httpsRE = /^https:\/\//;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate proxy
|
||||||
|
* @param list
|
||||||
|
*/
|
||||||
|
export function createProxy(list: ProxyList = []) {
|
||||||
|
const ret: ProxyTargetList = {};
|
||||||
|
for (const [prefix, target] of list) {
|
||||||
|
const isHttps = httpsRE.test(target);
|
||||||
|
|
||||||
|
// https://github.com/http-party/node-http-proxy#options
|
||||||
|
ret[prefix] = {
|
||||||
|
target: target,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
|
||||||
|
// https is require secure=false
|
||||||
|
...(isHttps ? { secure: false } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
<style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
|
<style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="appProvider" style="display: none"></div>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="first-loading-wrp">
|
<div class="first-loading-wrp">
|
||||||
<div class="loading-wrp">
|
<div class="loading-wrp">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Mock from 'mockjs'
|
import Mock from 'mockjs';
|
||||||
|
|
||||||
export function resultSuccess(result, { message = 'ok' } = {}) {
|
export function resultSuccess(result, { message = 'ok' } = {}) {
|
||||||
return Mock.mock({
|
return Mock.mock({
|
||||||
@@ -51,9 +51,9 @@ export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]
|
|||||||
* @param {Function} callback 回调函数
|
* @param {Function} callback 回调函数
|
||||||
*/
|
*/
|
||||||
export function doCustomTimes(times: number, callback: any) {
|
export function doCustomTimes(times: number, callback: any) {
|
||||||
let i = -1
|
let i = -1;
|
||||||
while (++i < times) {
|
while (++i < times) {
|
||||||
callback(i)
|
callback(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Random } from 'mockjs'
|
import { Random } from 'mockjs';
|
||||||
import { resultSuccess } from '../_util'
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
const consoleInfo = {
|
const consoleInfo = {
|
||||||
//访问量
|
//访问量
|
||||||
@@ -13,7 +13,7 @@ const consoleInfo = {
|
|||||||
saleroom: {
|
saleroom: {
|
||||||
weekSaleroom: Random.float(10000, 99999, 2, 2),
|
weekSaleroom: Random.float(10000, 99999, 2, 2),
|
||||||
amount: Random.float(99999, 999999, 2, 2),
|
amount: Random.float(99999, 999999, 2, 2),
|
||||||
degree:Random.float(10,99)
|
degree: Random.float(10, 99),
|
||||||
},
|
},
|
||||||
//订单量
|
//订单量
|
||||||
orderLarge: {
|
orderLarge: {
|
||||||
@@ -27,10 +27,9 @@ const consoleInfo = {
|
|||||||
weekLarge: Random.float(10000, 99999, 2, 2),
|
weekLarge: Random.float(10000, 99999, 2, 2),
|
||||||
rise: Random.float(10, 99),
|
rise: Random.float(10, 99),
|
||||||
decline: Random.float(10, 99),
|
decline: Random.float(10, 99),
|
||||||
amount:Random.float(99999,999999,2,2)
|
amount: Random.float(99999, 999999, 2, 2),
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
//主控台 卡片数据
|
//主控台 卡片数据
|
||||||
@@ -41,7 +40,5 @@ export default [
|
|||||||
response: () => {
|
response: () => {
|
||||||
return resultSuccess(consoleInfo);
|
return resultSuccess(consoleInfo);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
89
mock/system/menu.ts
Normal file
89
mock/system/menu.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
|
const menuList = () => {
|
||||||
|
const result: any[] = [
|
||||||
|
{
|
||||||
|
label: 'Dashboard',
|
||||||
|
key: 'dashboard',
|
||||||
|
type: 1,
|
||||||
|
subtitle: 'dashboard',
|
||||||
|
openType: 1,
|
||||||
|
auth: 'dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: '主控台',
|
||||||
|
key: 'console',
|
||||||
|
type: 1,
|
||||||
|
subtitle: 'console',
|
||||||
|
openType: 1,
|
||||||
|
auth: 'console',
|
||||||
|
path: '/dashboard/console',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '工作台',
|
||||||
|
key: 'workplace',
|
||||||
|
type: 1,
|
||||||
|
subtitle: 'workplace',
|
||||||
|
openType: 1,
|
||||||
|
auth: 'workplace',
|
||||||
|
path: '/dashboard/workplace',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '表单管理',
|
||||||
|
key: 'form',
|
||||||
|
type: 1,
|
||||||
|
subtitle: 'form',
|
||||||
|
openType: 1,
|
||||||
|
auth: 'form',
|
||||||
|
path: '/form',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: '基础表单',
|
||||||
|
key: 'basic-form',
|
||||||
|
type: 1,
|
||||||
|
subtitle: 'basic-form',
|
||||||
|
openType: 1,
|
||||||
|
auth: 'basic-form',
|
||||||
|
path: '/form/basic-form',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '分步表单',
|
||||||
|
key: 'step-form',
|
||||||
|
type: 1,
|
||||||
|
subtitle: 'step-form',
|
||||||
|
openType: 1,
|
||||||
|
auth: 'step-form',
|
||||||
|
path: '/form/step-form',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '表单详情',
|
||||||
|
key: 'detail',
|
||||||
|
type: 1,
|
||||||
|
subtitle: 'detail',
|
||||||
|
openType: 1,
|
||||||
|
auth: 'detail',
|
||||||
|
path: '/form/detail',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/api/menu/list',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: () => {
|
||||||
|
const list = menuList();
|
||||||
|
return resultSuccess({
|
||||||
|
list,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
45
mock/system/role.ts
Normal file
45
mock/system/role.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { resultSuccess, doCustomTimes } from '../_util';
|
||||||
|
|
||||||
|
function getMenuKeys() {
|
||||||
|
const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
|
||||||
|
const newKeys = [];
|
||||||
|
doCustomTimes(parseInt(Math.random() * 6), () => {
|
||||||
|
const key = keys[Math.floor(Math.random() * keys.length)];
|
||||||
|
newKeys.push(key);
|
||||||
|
});
|
||||||
|
return Array.from(new Set(newKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleList = (pageSize) => {
|
||||||
|
const result: any[] = [];
|
||||||
|
doCustomTimes(pageSize, () => {
|
||||||
|
result.push({
|
||||||
|
id: '@integer(10,100)',
|
||||||
|
name: '@cname()',
|
||||||
|
explain: '@cname()',
|
||||||
|
isDefault: '@boolean()',
|
||||||
|
menu_keys: getMenuKeys(),
|
||||||
|
create_date: `@date('yyyy-MM-dd hh:mm:ss')`,
|
||||||
|
'status|1': ['normal', 'enable', 'disable'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/api/role/list',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: ({ query }) => {
|
||||||
|
const { page = 1, pageSize = 10 } = query;
|
||||||
|
const list = roleList(Number(pageSize));
|
||||||
|
return resultSuccess({
|
||||||
|
page: Number(page),
|
||||||
|
pageSize: Number(pageSize),
|
||||||
|
pageCount: 60,
|
||||||
|
list,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Random } from 'mockjs'
|
import { Random } from 'mockjs';
|
||||||
import { resultSuccess, doCustomTimes, resultPageSuccess } from '../_util'
|
import { resultSuccess, doCustomTimes } from '../_util';
|
||||||
|
|
||||||
const tableList = ((pageSize) => {
|
const tableList = (pageSize) => {
|
||||||
const result:any[] = []
|
const result: any[] = [];
|
||||||
doCustomTimes(pageSize, () => {
|
doCustomTimes(pageSize, () => {
|
||||||
result.push({
|
result.push({
|
||||||
id: '@integer(10,100)',
|
id: '@integer(10,100)',
|
||||||
@@ -14,12 +14,11 @@ const tableList = ((pageSize) => {
|
|||||||
date: `@date('yyyy-MM-dd')`,
|
date: `@date('yyyy-MM-dd')`,
|
||||||
time: `@time('HH:mm')`,
|
time: `@time('HH:mm')`,
|
||||||
'no|100000-10000000': 100000,
|
'no|100000-10000000': 100000,
|
||||||
'status|1': ['normal', 'enable', 'disable'],
|
'status|1': [true, false],
|
||||||
});
|
});
|
||||||
})
|
|
||||||
return result
|
|
||||||
});
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
//表格数据列表
|
//表格数据列表
|
||||||
@@ -29,16 +28,13 @@ export default [
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
response: ({ query }) => {
|
response: ({ query }) => {
|
||||||
const { page = 1, pageSize = 10 } = query;
|
const { page = 1, pageSize = 10 } = query;
|
||||||
const list = tableList(Number(pageSize))
|
const list = tableList(Number(pageSize));
|
||||||
return resultSuccess({
|
return resultSuccess({
|
||||||
page: Number(page),
|
page: Number(page),
|
||||||
pageSize: Number(pageSize),
|
pageSize: Number(pageSize),
|
||||||
pageCount: 60,
|
pageCount: 60,
|
||||||
list
|
list,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { MockMethod } from 'vite-plugin-mock'
|
import { resultSuccess } from '../_util';
|
||||||
import { resultSuccess, getRequestToken } from '../_util'
|
|
||||||
|
|
||||||
const menusList = [
|
const menusList = [
|
||||||
{
|
{
|
||||||
@@ -18,7 +17,7 @@ const menusList = [
|
|||||||
component: 'DashboardConsole',
|
component: 'DashboardConsole',
|
||||||
meta: {
|
meta: {
|
||||||
title: '主控台',
|
title: '主控台',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'monitor',
|
path: 'monitor',
|
||||||
@@ -26,7 +25,7 @@ const menusList = [
|
|||||||
component: 'DashboardMonitor',
|
component: 'DashboardMonitor',
|
||||||
meta: {
|
meta: {
|
||||||
title: '监控页',
|
title: '监控页',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'workplace',
|
path: 'workplace',
|
||||||
@@ -35,11 +34,11 @@ const menusList = [
|
|||||||
meta: {
|
meta: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
title: '工作台',
|
title: '工作台',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@@ -49,5 +48,5 @@ export default [
|
|||||||
response: () => {
|
response: () => {
|
||||||
return resultSuccess(menusList);
|
return resultSuccess(menusList);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Mock from 'mockjs'
|
import Mock from 'mockjs';
|
||||||
import { resultSuccess, getRequestToken } from '../_util'
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
const Random = Mock.Random
|
const Random = Mock.Random;
|
||||||
|
|
||||||
const token = Random.string('upper', 32, 32)
|
const token = Random.string('upper', 32, 32);
|
||||||
|
|
||||||
const adminInfo = {
|
const adminInfo = {
|
||||||
userId: '1',
|
userId: '1',
|
||||||
@@ -25,9 +25,17 @@ const adminInfo = {
|
|||||||
{
|
{
|
||||||
roleName: '工作台',
|
roleName: '工作台',
|
||||||
value: 'dashboard_workplace',
|
value: 'dashboard_workplace',
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
roleName: '基础列表',
|
||||||
|
value: 'basic_list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roleName: '基础列表删除',
|
||||||
|
value: 'basic_list_delete',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@@ -48,4 +56,4 @@ export default [
|
|||||||
return resultSuccess(adminInfo);
|
return resultSuccess(adminInfo);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|||||||
22
package.json
22
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "naive-ui-admin",
|
"name": "naive-ui-admin",
|
||||||
"version": "1.2",
|
"version": "1.5.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Ahjung",
|
"name": "Ahjung",
|
||||||
"email": "735878602@qq.com",
|
"email": "735878602@qq.com",
|
||||||
@@ -8,8 +8,12 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"bootstrap": "yarn install",
|
||||||
|
"serve": "npm run dev",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build && esno ./build/script/postBuild.ts",
|
"build": "vite build && esno ./build/script/postBuild.ts",
|
||||||
|
"build:no-cache": "yarn clean:cache && npm run build",
|
||||||
|
"report": "cross-env REPORT=true npm run build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build typecheck": "vuedx-typecheck . && vite build",
|
"build typecheck": "vuedx-typecheck . && vite build",
|
||||||
"deploy": "gh-pages -d dist",
|
"deploy": "gh-pages -d dist",
|
||||||
@@ -34,8 +38,7 @@
|
|||||||
"makeit-captcha": "^1.2.5",
|
"makeit-captcha": "^1.2.5",
|
||||||
"mitt": "^2.1.0",
|
"mitt": "^2.1.0",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"naive-ui": "^2.15.5",
|
"naive-ui": "^2.15.11",
|
||||||
"nprogress": "^1.0.0-1",
|
|
||||||
"pinia": "^2.0.0-beta.3",
|
"pinia": "^2.0.0-beta.3",
|
||||||
"qs": "^6.10.1",
|
"qs": "^6.10.1",
|
||||||
"vfonts": "^0.1.0",
|
"vfonts": "^0.1.0",
|
||||||
@@ -62,27 +65,32 @@
|
|||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"eslint": "^7.28.0",
|
"eslint": "^7.28.0",
|
||||||
"eslint-config-prettier": "^8.3.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-prettier": "^3.4.0",
|
||||||
"eslint-plugin-vue": "^7.11.1",
|
"eslint-plugin-vue": "^7.11.1",
|
||||||
"esno": "^0.7.3",
|
"esno": "^0.7.3",
|
||||||
"gh-pages": "^3.2.0",
|
"gh-pages": "^3.2.0",
|
||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
|
"jest": "^27.0.6",
|
||||||
"less": "^4.1.1",
|
"less": "^4.1.1",
|
||||||
"less-loader": "^9.0.0",
|
"less-loader": "^9.0.0",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"postcss": "^8.3.5",
|
"postcss": "^8.3.5",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"pretty-quick": "^3.1.0",
|
"pretty-quick": "^3.1.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"stylelint": "^13.13.1",
|
"stylelint": "^13.13.1",
|
||||||
"stylelint-config-prettier": "^8.0.2",
|
"stylelint-config-prettier": "^8.0.2",
|
||||||
"stylelint-config-standard": "^22.0.0",
|
"stylelint-config-standard": "^22.0.0",
|
||||||
"stylelint-order": "^4.1.0",
|
"stylelint-order": "^4.1.0",
|
||||||
"stylelint-scss": "^3.19.0",
|
"stylelint-scss": "^3.19.0",
|
||||||
"tailwindcss": "^2.2.4",
|
"tailwindcss": "^2.2.7",
|
||||||
"typescript": "^4.3.2",
|
"typescript": "^4.3.5",
|
||||||
"vite": "^2.4.2",
|
"vite": "2.3.6",
|
||||||
|
"vite-plugin-compression": "^0.3.1",
|
||||||
"vite-plugin-html": "^2.0.7",
|
"vite-plugin-html": "^2.0.7",
|
||||||
"vite-plugin-mock": "^2.9.1",
|
"vite-plugin-mock": "^2.9.3",
|
||||||
"vite-plugin-style-import": "^1.0.1",
|
"vite-plugin-style-import": "^1.0.1",
|
||||||
"vue-eslint-parser": "^7.8.0"
|
"vue-eslint-parser": "^7.8.0"
|
||||||
},
|
},
|
||||||
|
|||||||
78
src/App.vue
78
src/App.vue
@@ -11,70 +11,68 @@
|
|||||||
</AppProvider>
|
</AppProvider>
|
||||||
</NConfigProvider>
|
</NConfigProvider>
|
||||||
|
|
||||||
<transition v-if="isLock && $route.name != 'login'" name="slide-up">
|
<transition v-if="isLock && $route.name !== 'login'" name="slide-up">
|
||||||
<LockScreen />
|
<LockScreen />
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, onMounted, onUnmounted } from 'vue'
|
import { defineComponent, computed, onMounted, onUnmounted } from 'vue';
|
||||||
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, darkTheme } from 'naive-ui'
|
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, darkTheme } from 'naive-ui';
|
||||||
import { LockScreen } from '@/components/Lockscreen'
|
import { LockScreen } from '@/components/Lockscreen';
|
||||||
import { AppProvider } from '@/components/Application'
|
import { AppProvider } from '@/components/Application';
|
||||||
import { useLockscreenStore } from '@/store/modules/lockscreen'
|
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router';
|
||||||
import { useDesignSettingStore } from '@/store/modules/designSetting'
|
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: { LockScreen, AppProvider },
|
components: { LockScreen, AppProvider },
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
const useLockscreen = useLockscreenStore()
|
const useLockscreen = useLockscreenStore();
|
||||||
const designStore = useDesignSettingStore()
|
const designStore = useDesignSettingStore();
|
||||||
const isLock = computed(() => useLockscreen.isLock)
|
const isLock = computed(() => useLockscreen.isLock);
|
||||||
const lockTime = computed(() => useLockscreen.lockTime)
|
const lockTime = computed(() => useLockscreen.lockTime);
|
||||||
/**
|
|
||||||
* @type import('naive-ui').GlobalThemeOverrides
|
|
||||||
*/
|
|
||||||
const getThemeOverrides = computed(() => {
|
const getThemeOverrides = computed(() => {
|
||||||
return {
|
return {
|
||||||
common: {
|
common: {
|
||||||
primaryColor: designStore.appTheme,
|
primaryColor: designStore.appTheme,
|
||||||
primaryColorHover: '#57a3f3'
|
primaryColorHover: '#57a3f3',
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined))
|
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
|
||||||
|
|
||||||
let timer
|
let timer;
|
||||||
|
|
||||||
const timekeeping = () => {
|
const timekeeping = () => {
|
||||||
clearInterval(timer)
|
clearInterval(timer);
|
||||||
if (route.name == 'login' || isLock.value) return
|
if (route.name == 'login' || isLock.value) return;
|
||||||
// 设置不锁屏
|
// 设置不锁屏
|
||||||
useLockscreen.setLock(false)
|
useLockscreen.setLock(false);
|
||||||
// 重置锁屏时间
|
// 重置锁屏时间
|
||||||
useLockscreen.setLockTime()
|
useLockscreen.setLockTime();
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
// 锁屏倒计时递减
|
// 锁屏倒计时递减
|
||||||
useLockscreen.setLockTime(lockTime.value - 1)
|
useLockscreen.setLockTime(lockTime.value - 1);
|
||||||
if (lockTime.value <= 0) {
|
if (lockTime.value <= 0) {
|
||||||
// 设置锁屏
|
// 设置锁屏
|
||||||
useLockscreen.setLock(true)
|
useLockscreen.setLock(true);
|
||||||
return clearInterval(timer)
|
return clearInterval(timer);
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('mousedown', timekeeping)
|
document.addEventListener('mousedown', timekeeping);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('mousedown', timekeeping)
|
document.removeEventListener('mousedown', timekeeping);
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
darkTheme: createTheme([inputDark, datePickerDark]),
|
darkTheme: createTheme([inputDark, datePickerDark]),
|
||||||
@@ -82,16 +80,14 @@ export default defineComponent({
|
|||||||
zhCN,
|
zhCN,
|
||||||
dateZhCN,
|
dateZhCN,
|
||||||
isLock,
|
isLock,
|
||||||
getThemeOverrides
|
getThemeOverrides,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import 'styles/global.less';
|
|
||||||
@import 'styles/common.less';
|
@import 'styles/common.less';
|
||||||
@import 'styles/override.less';
|
|
||||||
|
|
||||||
.slide-up-enter-active,
|
.slide-up-enter-active,
|
||||||
.slide-up-leave-active {
|
.slide-up-leave-active {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import http from '@/utils/http/axios'
|
import http from '@/utils/http/axios';
|
||||||
|
|
||||||
//获取主控台信息
|
//获取主控台信息
|
||||||
export function getConsoleInfo() {
|
export function getConsoleInfo() {
|
||||||
return http.request(
|
return http.request({
|
||||||
{
|
|
||||||
url: '/dashboard/console',
|
url: '/dashboard/console',
|
||||||
method: 'get'
|
method: 'get',
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,23 @@
|
|||||||
import http from '@/utils/http/axios'
|
import http from '@/utils/http/axios';
|
||||||
import {
|
|
||||||
GetByUserIdParams,
|
|
||||||
GetMenuListByUserIdResult,
|
|
||||||
GetAuthCodeByUserIdResult
|
|
||||||
} from './model/menuModel'
|
|
||||||
|
|
||||||
enum Api {
|
|
||||||
adminMenus = '/menus',
|
|
||||||
GetBtnCodeListByUserId = '/getBtnCodeListByUserId'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 根据用户id获取用户菜单
|
* @description: 根据用户id获取用户菜单
|
||||||
*/
|
*/
|
||||||
export function adminMenus() {
|
export function adminMenus() {
|
||||||
return http.request<GetMenuListByUserIdResult>({
|
return http.request({
|
||||||
url: Api.adminMenus,
|
url: '/menus',
|
||||||
method: 'GET'
|
method: 'GET',
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户Id获取权限编码
|
* 获取tree菜单列表
|
||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export function getBtnCodeListByUserId(params: GetByUserIdParams) {
|
export function getMenuList(params?) {
|
||||||
return http.request<GetAuthCodeByUserIdResult>({
|
return http.request({
|
||||||
url: Api.GetBtnCodeListByUserId,
|
url: '/menu/list',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/api/system/role.ts
Normal file
11
src/api/system/role.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import http from '@/utils/http/axios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 角色列表
|
||||||
|
*/
|
||||||
|
export function getRoleList() {
|
||||||
|
return http.request({
|
||||||
|
url: '/role/list',
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,27 +1,25 @@
|
|||||||
import http from '@/utils/http/axios'
|
import http from '@/utils/http/axios';
|
||||||
|
|
||||||
export interface BasicResponseModel<T = any> {
|
export interface BasicResponseModel<T = any> {
|
||||||
code: number
|
code: number;
|
||||||
message: string
|
message: string;
|
||||||
result: T
|
result: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BasicPageParams {
|
export interface BasicPageParams {
|
||||||
pageNumber: number
|
pageNumber: number;
|
||||||
pageSize: number
|
pageSize: number;
|
||||||
total: number
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 获取用户信息
|
* @description: 获取用户信息
|
||||||
*/
|
*/
|
||||||
export function getUserInfo() {
|
export function getUserInfo() {
|
||||||
return http.request(
|
return http.request({
|
||||||
{
|
|
||||||
url: '/admin_info',
|
url: '/admin_info',
|
||||||
method: 'get'
|
method: 'get',
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,12 +30,12 @@ export function login(params) {
|
|||||||
{
|
{
|
||||||
url: '/login',
|
url: '/login',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isTransformRequestResult: false
|
isTransformResponse: false,
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,12 +46,12 @@ export function changePassword(params, uid) {
|
|||||||
{
|
{
|
||||||
url: `/user/u${uid}/changepw`,
|
url: `/user/u${uid}/changepw`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isTransformRequestResult: false
|
isTransformResponse: false,
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,6 +61,6 @@ export function logout(params) {
|
|||||||
return http.request({
|
return http.request({
|
||||||
url: '/login/logout',
|
url: '/login/logout',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import http from '@/utils/http/axios'
|
import http from '@/utils/http/axios';
|
||||||
|
|
||||||
//获取table
|
//获取table
|
||||||
export function getTableList(params) {
|
export function getTableList(params) {
|
||||||
return http.request(
|
return http.request({
|
||||||
{
|
|
||||||
url: '/table/list',
|
url: '/table/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params
|
params,
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<n-loading-bar-provider>
|
||||||
|
<LoadingContent />
|
||||||
<n-dialog-provider>
|
<n-dialog-provider>
|
||||||
<DialogContent />
|
<DialogContent />
|
||||||
<n-notification-provider>
|
<n-notification-provider>
|
||||||
@@ -8,18 +10,34 @@
|
|||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
</n-notification-provider>
|
</n-notification-provider>
|
||||||
</n-dialog-provider>
|
</n-dialog-provider>
|
||||||
|
</n-loading-bar-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue';
|
||||||
import { MessageContent } from '@/components/MessageContent'
|
import {
|
||||||
import { DialogContent } from '@/components/DialogContent'
|
NDialogProvider,
|
||||||
|
NNotificationProvider,
|
||||||
|
NMessageProvider,
|
||||||
|
NLoadingBarProvider,
|
||||||
|
} from 'naive-ui';
|
||||||
|
import { LoadingContent } from '@/components/LoadingContent';
|
||||||
|
import { MessageContent } from '@/components/MessageContent';
|
||||||
|
import { DialogContent } from '@/components/DialogContent';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Application',
|
name: 'Application',
|
||||||
components: { MessageContent, DialogContent },
|
components: {
|
||||||
|
NDialogProvider,
|
||||||
|
NNotificationProvider,
|
||||||
|
NMessageProvider,
|
||||||
|
NLoadingBarProvider,
|
||||||
|
LoadingContent,
|
||||||
|
MessageContent,
|
||||||
|
DialogContent,
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {}
|
return {};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import AppProvider from './Application.vue'
|
import AppProvider from './Application.vue';
|
||||||
|
|
||||||
export { AppProvider }
|
export { AppProvider };
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import DialogContent from './index.vue'
|
import DialogContent from './index.vue';
|
||||||
|
|
||||||
export { DialogContent }
|
export { DialogContent };
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template></template>
|
<template></template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useDialog } from 'naive-ui'
|
import { useDialog } from 'naive-ui';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DialogContent',
|
name: 'DialogContent',
|
||||||
setup() {
|
setup() {
|
||||||
//挂载在 window 方便与在js中使用
|
//挂载在 window 方便与在js中使用
|
||||||
window.$dialog = useDialog()
|
window['$dialog'] = useDialog();
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
3
src/components/LoadingContent/index.ts
Normal file
3
src/components/LoadingContent/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import LoadingContent from './index.vue';
|
||||||
|
|
||||||
|
export { LoadingContent };
|
||||||
12
src/components/LoadingContent/index.vue
Normal file
12
src/components/LoadingContent/index.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<template></template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { useLoadingBar } from 'naive-ui';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LoadingContent',
|
||||||
|
setup() {
|
||||||
|
//挂载在 window 方便与在js中使用
|
||||||
|
window['$loading'] = useLoadingBar();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
@contextmenu.prevent
|
@contextmenu.prevent
|
||||||
>
|
>
|
||||||
<template v-if="!showLogin">
|
<template v-if="!showLogin">
|
||||||
|
|
||||||
<div class="lock-box">
|
<div class="lock-box">
|
||||||
<div class="lock">
|
<div class="lock">
|
||||||
<span class="lock-icon" title="解锁屏幕" @click="onLockLogin(true)">
|
<span class="lock-icon" title="解锁屏幕" @click="onLockLogin(true)">
|
||||||
@@ -16,14 +15,13 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!--充电-->
|
<!--充电-->
|
||||||
<recharge
|
<recharge
|
||||||
:battery="battery"
|
:battery="battery"
|
||||||
:battery-status="batteryStatus"
|
:battery-status="batteryStatus"
|
||||||
:calc-discharging-time="calcDischargingTime"
|
:calc-discharging-time="calcDischargingTime"
|
||||||
></recharge>
|
/>
|
||||||
|
|
||||||
<div class="local-time">
|
<div class="local-time">
|
||||||
<div class="time">{{ hour }}:{{ minute }}</div>
|
<div class="time">{{ hour }}:{{ minute }}</div>
|
||||||
@@ -50,9 +48,10 @@
|
|||||||
type="password"
|
type="password"
|
||||||
autofocus
|
autofocus
|
||||||
v-model:value="loginParams.password"
|
v-model:value="loginParams.password"
|
||||||
placeholder="请输入登录密码">
|
placeholder="请输入登录密码"
|
||||||
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<n-icon @click="onLogin" style="cursor: pointer;">
|
<n-icon @click="onLogin" style="cursor: pointer">
|
||||||
<LoadingOutlined v-if="loginLoading" />
|
<LoadingOutlined v-if="loginLoading" />
|
||||||
<arrow-right-outlined v-else />
|
<arrow-right-outlined v-else />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -68,58 +67,56 @@
|
|||||||
<div><a @click="goLogin">重新登录</a></div>
|
<div><a @click="goLogin">重新登录</a></div>
|
||||||
<div><a @click="onLogin">进入系统</a></div>
|
<div><a @click="onLogin">进入系统</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted, reactive, toRefs, computed } from 'vue'
|
import { defineComponent, reactive, toRefs } from 'vue';
|
||||||
import { ResultEnum } from '@/enums/httpEnum'
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
import recharge from './Recharge.vue'
|
import recharge from './Recharge.vue';
|
||||||
import {
|
import {
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
UnlockOutlined,
|
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
ApiOutlined,
|
ApiOutlined,
|
||||||
ArrowRightOutlined,
|
ArrowRightOutlined,
|
||||||
WifiOutlined,
|
WifiOutlined,
|
||||||
} from '@vicons/antd'
|
} from '@vicons/antd';
|
||||||
|
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useOnline } from '@/hooks/useOnline'
|
import { useOnline } from '@/hooks/useOnline';
|
||||||
import { useTime } from '@/hooks/useTime'
|
import { useTime } from '@/hooks/useTime';
|
||||||
import { useBattery } from '@/hooks/useBattery'
|
import { useBattery } from '@/hooks/useBattery';
|
||||||
import { useLockscreenStore } from '@/store/modules/lockscreen'
|
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Lockscreen',
|
name: 'Lockscreen',
|
||||||
components: {
|
components: {
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
UnlockOutlined,
|
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
ArrowRightOutlined,
|
ArrowRightOutlined,
|
||||||
ApiOutlined,
|
ApiOutlined,
|
||||||
WifiOutlined,
|
WifiOutlined,
|
||||||
recharge,
|
recharge,
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup() {
|
||||||
const useLockscreen = useLockscreenStore()
|
const useLockscreen = useLockscreenStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 获取时间
|
// 获取时间
|
||||||
const { month, day, hour, minute, second, week } = useTime()
|
const { month, day, hour, minute, second, week } = useTime();
|
||||||
const { online } = useOnline()
|
const { online } = useOnline();
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
|
|
||||||
const { battery, batteryStatus, calcDischargingTime } = useBattery()
|
const { battery, batteryStatus, calcDischargingTime } = useBattery();
|
||||||
const { username } = userStore.getUserInfo || {}
|
const userInfo: object = userStore.getUserInfo || {};
|
||||||
|
const username = userInfo['username'] || '';
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
showLogin: false,
|
showLogin: false,
|
||||||
loginLoading: false, // 正在登录
|
loginLoading: false, // 正在登录
|
||||||
@@ -127,45 +124,45 @@ export default defineComponent({
|
|||||||
errorMsg: '密码错误',
|
errorMsg: '密码错误',
|
||||||
loginParams: {
|
loginParams: {
|
||||||
username: username || '',
|
username: username || '',
|
||||||
password: ''
|
password: '',
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// 解锁登录
|
// 解锁登录
|
||||||
const onLockLogin = (value: boolean) => (state.showLogin = value)
|
const onLockLogin = (value: boolean) => (state.showLogin = value);
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
const onLogin = async () => {
|
const onLogin = async () => {
|
||||||
if (!state.loginParams.password.trim()) {
|
if (!state.loginParams.password.trim()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const params = {
|
const params = {
|
||||||
isLock: true,
|
isLock: true,
|
||||||
...state.loginParams
|
...state.loginParams,
|
||||||
}
|
};
|
||||||
state.loginLoading = true
|
state.loginLoading = true;
|
||||||
const { code, result, message } = await userStore.login(params)
|
const { code, message } = await userStore.login(params);
|
||||||
if (code === ResultEnum.SUCCESS) {
|
if (code === ResultEnum.SUCCESS) {
|
||||||
onLockLogin(false)
|
onLockLogin(false);
|
||||||
useLockscreen.setLock(false)
|
useLockscreen.setLock(false);
|
||||||
} else {
|
} else {
|
||||||
state.errorMsg = message
|
state.errorMsg = message;
|
||||||
state.isLoginError = true
|
state.isLoginError = true;
|
||||||
}
|
|
||||||
state.loginLoading = false
|
|
||||||
}
|
}
|
||||||
|
state.loginLoading = false;
|
||||||
|
};
|
||||||
|
|
||||||
//重新登录
|
//重新登录
|
||||||
const goLogin = () => {
|
const goLogin = () => {
|
||||||
onLockLogin(false)
|
onLockLogin(false);
|
||||||
useLockscreen.setLock(false)
|
useLockscreen.setLock(false);
|
||||||
router.replace({
|
router.replace({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
query: {
|
query: {
|
||||||
redirect: route.fullPath
|
redirect: route.fullPath,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
@@ -181,10 +178,10 @@ export default defineComponent({
|
|||||||
calcDischargingTime,
|
calcDischargingTime,
|
||||||
onLockLogin,
|
onLockLogin,
|
||||||
onLogin,
|
onLogin,
|
||||||
goLogin
|
goLogin,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'HuaweiCharge',
|
name: 'HuaweiCharge',
|
||||||
@@ -29,20 +29,20 @@ export default defineComponent({
|
|||||||
battery: {
|
battery: {
|
||||||
// 电池对象
|
// 电池对象
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
calcDischargingTime: {
|
calcDischargingTime: {
|
||||||
// 电池剩余时间可用时间
|
// 电池剩余时间可用时间
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: '',
|
||||||
},
|
},
|
||||||
batteryStatus: {
|
batteryStatus: {
|
||||||
// 电池状态
|
// 电池状态
|
||||||
type: String,
|
type: String,
|
||||||
validator: (val: string) => ['充电中', '已充满', '已断开电源'].includes(val)
|
validator: (val: string) => ['充电中', '已充满', '已断开电源'].includes(val),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -147,7 +147,6 @@ each(range(15), {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
50% {
|
50% {
|
||||||
border-radius: 45% / 42% 38% 58% 49%;
|
border-radius: 45% / 42% 38% 58% 49%;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import LockScreen from './Lockscreen.vue'
|
import LockScreen from './Lockscreen.vue';
|
||||||
|
|
||||||
export { LockScreen }
|
export { LockScreen };
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import MessageContent from './index.vue'
|
import MessageContent from './index.vue';
|
||||||
|
|
||||||
export { MessageContent }
|
export { MessageContent };
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template></template>
|
<template></template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MessageContent',
|
name: 'MessageContent',
|
||||||
setup() {
|
setup() {
|
||||||
//挂载在 window 方便与在js中使用
|
//挂载在 window 方便与在js中使用
|
||||||
window.$message = useMessage()
|
window['$message'] = useMessage();
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
BasicTable 重封装组件说明
|
|
||||||
====
|
|
||||||
|
|
||||||
封装说明
|
|
||||||
----
|
|
||||||
|
|
||||||
> 基础的使用方式与 API 与 [官方版(data-table)](https://www.naiveui.com/zh-CN/os-theme/components/data-table#tree) 本一致,在其基础上,封装了加载数据的方法。
|
|
||||||
>
|
|
||||||
> 你无需在你是用表格的页面进行分页逻辑处理,仅需向 BasicTable 组件传递绑定 `:api="Promise"` 对象即可
|
|
||||||
>
|
|
||||||
> 例子1
|
|
||||||
----
|
|
||||||
(基础使用)
|
|
||||||
|
|
||||||
```vue
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BasicTable
|
|
||||||
title="表格列表"
|
|
||||||
:columns="columns"
|
|
||||||
:api="loadDataTable"
|
|
||||||
:row-key="row => row.id"
|
|
||||||
@update:checked-row-keys="onCheckedRow"
|
|
||||||
>
|
|
||||||
<template #toolbar>
|
|
||||||
<n-button type="primary">添加会员</n-button>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { BasicTable } from '@/components/Table'
|
|
||||||
import { getTableList } from '@/api/table/list'
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: 'id',
|
|
||||||
key: 'id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '名称',
|
|
||||||
key: 'name'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '地址',
|
|
||||||
key: 'address'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '日期',
|
|
||||||
key: 'date'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
export default defineComponent({
|
|
||||||
components: { BasicTable },
|
|
||||||
setup() {
|
|
||||||
const loadDataTable = async (params) => {
|
|
||||||
const data = await getTableList(params);
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
columns,
|
|
||||||
loadDataTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
API
|
|
||||||
----
|
|
||||||
BasicTable 在 NaiveUi 的 data-table 上进行了一层封装,支持了一些预设,并且封装了一些行为。这里只列出与 data-table 不同的 api。
|
|
||||||
|
|
||||||
> request:Promise 参考上面例子写法
|
|
||||||
> ref:可绑定ref 调用组件内部方法(data-table本身的方法和参数)
|
|
||||||
|
|
||||||
Methods
|
|
||||||
----
|
|
||||||
> reload:actionRef.value.reload()
|
|
||||||
|
|
||||||
> 其余方法,请打印查看
|
|
||||||
|
|
||||||
Slots
|
|
||||||
----
|
|
||||||
> 名称:tableTitle | 表格顶部左侧区域
|
|
||||||
> 名称:toolbar | 表格顶部右侧区域
|
|
||||||
|
|
||||||
|
|
||||||
更新时间
|
|
||||||
----
|
|
||||||
|
|
||||||
该文档最后更新于: 2021-07-12 PM 10:13
|
|
||||||
@@ -1 +1,4 @@
|
|||||||
export { default as BasicTable } from './src/Table.vue';
|
export { default as BasicTable } from './src/Table.vue';
|
||||||
|
export { default as TableAction } from './src/components/TableAction.vue';
|
||||||
|
export * from './src/types/table';
|
||||||
|
export * from './src/types/tableAction';
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
|
|
||||||
<!--顶部左侧区域-->
|
<!--顶部左侧区域-->
|
||||||
<div class="flex items-center table-toolbar-left">
|
<div class="flex items-center table-toolbar-left">
|
||||||
<template v-if="title">
|
<template v-if="title">
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center table-toolbar-right">
|
<div class="flex items-center table-toolbar-right">
|
||||||
|
|
||||||
<!--顶部右侧区域-->
|
<!--顶部右侧区域-->
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"></slot>
|
||||||
|
|
||||||
@@ -40,7 +38,12 @@
|
|||||||
<n-tooltip trigger="hover">
|
<n-tooltip trigger="hover">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="table-toolbar-right-icon">
|
<div class="table-toolbar-right-icon">
|
||||||
<n-dropdown @select="densitySelect" trigger="click" :options="densityOptions" v-model:value="tableSize">
|
<n-dropdown
|
||||||
|
@select="densitySelect"
|
||||||
|
trigger="click"
|
||||||
|
:options="densityOptions"
|
||||||
|
v-model:value="tableSize"
|
||||||
|
>
|
||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<ColumnHeightOutlined />
|
<ColumnHeightOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -51,13 +54,12 @@
|
|||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
|
|
||||||
<!--表格设置单独抽离成组件-->
|
<!--表格设置单独抽离成组件-->
|
||||||
<ColumnSetting></ColumnSetting>
|
<ColumnSetting />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="s-table">
|
<div class="s-table">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
|
ref="tableElRef"
|
||||||
v-bind="getBindValues"
|
v-bind="getBindValues"
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
@update:page="updatePage"
|
@update:page="updatePage"
|
||||||
@@ -71,57 +73,79 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { NDataTable } from 'naive-ui'
|
import { NDataTable } from 'naive-ui';
|
||||||
import { ref, defineComponent, reactive, unref, onMounted, toRaw, onBeforeMount, computed, toRefs, watch } from "vue"
|
import {
|
||||||
import { ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, QuestionCircleOutlined } from '@vicons/antd'
|
ref,
|
||||||
|
defineComponent,
|
||||||
|
reactive,
|
||||||
|
unref,
|
||||||
|
toRaw,
|
||||||
|
computed,
|
||||||
|
toRefs,
|
||||||
|
onMounted,
|
||||||
|
nextTick,
|
||||||
|
} from 'vue';
|
||||||
|
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||||
import { createTableContext } from './hooks/useTableContext';
|
import { createTableContext } from './hooks/useTableContext';
|
||||||
|
|
||||||
import ColumnSetting from './components/settings/ColumnSetting.vue'
|
import ColumnSetting from './components/settings/ColumnSetting.vue';
|
||||||
|
|
||||||
import { useLoading } from './hooks/useLoading';
|
import { useLoading } from './hooks/useLoading';
|
||||||
import { useColumns } from './hooks/useColumns';
|
import { useColumns } from './hooks/useColumns';
|
||||||
import { useDataSource } from './hooks/useDataSource';
|
import { useDataSource } from './hooks/useDataSource';
|
||||||
import { usePagination } from './hooks/usePagination';
|
import { usePagination } from './hooks/usePagination';
|
||||||
|
|
||||||
import { basicProps } from './props'
|
import { basicProps } from './props';
|
||||||
|
|
||||||
import { BasicTableProps } from './types/table'
|
import { BasicTableProps } from './types/table';
|
||||||
|
|
||||||
|
import { getViewportOffset } from '@/utils/domUtils';
|
||||||
|
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
|
||||||
|
import { isBoolean } from '@/utils/is';
|
||||||
|
|
||||||
const densityOptions = [
|
const densityOptions = [
|
||||||
{
|
{
|
||||||
type: "menu",
|
type: 'menu',
|
||||||
label: '紧凑',
|
label: '紧凑',
|
||||||
key: 'small',
|
key: 'small',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "menu",
|
type: 'menu',
|
||||||
label: '默认',
|
label: '默认',
|
||||||
key: "medium"
|
key: 'medium',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "menu",
|
type: 'menu',
|
||||||
label: '宽松',
|
label: '宽松',
|
||||||
key: 'large'
|
key: 'large',
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, ColumnSetting, QuestionCircleOutlined
|
ReloadOutlined,
|
||||||
|
ColumnHeightOutlined,
|
||||||
|
ColumnSetting,
|
||||||
|
QuestionCircleOutlined,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
...NDataTable.props, // 这里继承原 UI 组件的 props
|
...NDataTable.props, // 这里继承原 UI 组件的 props
|
||||||
...basicProps
|
...basicProps,
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'fetch-success',
|
'fetch-success',
|
||||||
'fetch-error',
|
'fetch-error',
|
||||||
'update:checked-row-keys'
|
'update:checked-row-keys',
|
||||||
|
'edit-end',
|
||||||
|
'edit-cancel',
|
||||||
|
'edit-row-end',
|
||||||
|
'edit-change',
|
||||||
],
|
],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
|
const deviceHeight = ref(150);
|
||||||
|
const tableElRef = ref<ComponentRef>(null);
|
||||||
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
||||||
|
let paginationEl: HTMLElement | null;
|
||||||
|
|
||||||
const tableData = ref<Recordable[]>([]);
|
const tableData = ref<Recordable[]>([]);
|
||||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||||
@@ -132,92 +156,76 @@ export default defineComponent({
|
|||||||
|
|
||||||
const { getLoading, setLoading } = useLoading(getProps);
|
const { getLoading, setLoading } = useLoading(getProps);
|
||||||
|
|
||||||
const {
|
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
||||||
getPaginationInfo,
|
|
||||||
getPagination,
|
|
||||||
setPagination,
|
|
||||||
setShowPagination,
|
|
||||||
getShowPagination,
|
|
||||||
} = usePagination(getProps)
|
|
||||||
|
|
||||||
const { getDataSourceRef, getRowKey, getDataSource, setDataSource, reload } = useDataSource(
|
const { getDataSourceRef, getRowKey, reload } = useDataSource(
|
||||||
getProps, {
|
getProps,
|
||||||
|
{
|
||||||
getPaginationInfo,
|
getPaginationInfo,
|
||||||
setPagination,
|
setPagination,
|
||||||
tableData,
|
tableData,
|
||||||
setLoading
|
setLoading,
|
||||||
}, emit
|
},
|
||||||
)
|
emit
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
|
||||||
getPageColumns,
|
useColumns(getProps);
|
||||||
setColumns,
|
|
||||||
getColumns,
|
|
||||||
getCacheColumns,
|
|
||||||
setCacheColumnsField,
|
|
||||||
getColumnsRef
|
|
||||||
} = useColumns(getProps)
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
tableSize: 'medium',
|
tableSize: 'medium',
|
||||||
isColumnSetting: false
|
isColumnSetting: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
//页码切换
|
//页码切换
|
||||||
function updatePage(page) {
|
function updatePage(page) {
|
||||||
setPagination({ page: page, });
|
setPagination({ page: page });
|
||||||
reload()
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
//分页数量切换
|
//分页数量切换
|
||||||
function updatePageSize(size) {
|
function updatePageSize(size) {
|
||||||
setPagination({ page: 1, pageSize: size, });
|
setPagination({ page: 1, pageSize: size });
|
||||||
reload()
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
//密度切换
|
//密度切换
|
||||||
function densitySelect(e) {
|
function densitySelect(e) {
|
||||||
state.tableSize = e
|
state.tableSize = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
//选中行
|
//选中行
|
||||||
function updateCheckedRowKeys(rowKeys) {
|
function updateCheckedRowKeys(rowKeys) {
|
||||||
emit('update:checked-row-keys', rowKeys)
|
emit('update:checked-row-keys', rowKeys);
|
||||||
}
|
|
||||||
|
|
||||||
//重置 Columns
|
|
||||||
const resetColumns = () => {
|
|
||||||
columns.map(item => {
|
|
||||||
item.isShow = true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取表格大小
|
//获取表格大小
|
||||||
const getTableSize = computed(() => state.tableSize)
|
const getTableSize = computed(() => state.tableSize);
|
||||||
|
|
||||||
//组装表格信息
|
//组装表格信息
|
||||||
const getBindValues = computed(() => {
|
const getBindValues = computed(() => {
|
||||||
const tableData = unref(getDataSourceRef);
|
const tableData = unref(getDataSourceRef);
|
||||||
let propsData = {
|
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
||||||
|
return {
|
||||||
...unref(getProps),
|
...unref(getProps),
|
||||||
loading: unref(getLoading),
|
loading: unref(getLoading),
|
||||||
columns: toRaw(unref(getPageColumns)),
|
columns: toRaw(unref(getPageColumns)),
|
||||||
rowKey: unref(getRowKey),
|
rowKey: unref(getRowKey),
|
||||||
data: tableData,
|
data: tableData,
|
||||||
size: unref(getTableSize),
|
size: unref(getTableSize),
|
||||||
remote: true
|
remote: true,
|
||||||
}
|
'max-height': maxHeight,
|
||||||
return propsData
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
//获取分页信息
|
//获取分页信息
|
||||||
const pagination = computed(() => toRaw(unref(getPaginationInfo)))
|
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
|
||||||
|
|
||||||
function setProps(props: Partial<BasicTableProps>) {
|
function setProps(props: Partial<BasicTableProps>) {
|
||||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableAction: TableActionType = {
|
const tableAction = {
|
||||||
reload,
|
reload,
|
||||||
setColumns,
|
setColumns,
|
||||||
setLoading,
|
setLoading,
|
||||||
@@ -228,14 +236,54 @@ export default defineComponent({
|
|||||||
setCacheColumnsField,
|
setCacheColumnsField,
|
||||||
emit,
|
emit,
|
||||||
getSize: () => {
|
getSize: () => {
|
||||||
return unref(getBindValues).size as SizeType;
|
return unref(getBindValues).size;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCanResize = computed(() => {
|
||||||
|
const { canResize } = unref(getProps);
|
||||||
|
return canResize;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function computeTableHeight() {
|
||||||
|
const table = unref(tableElRef);
|
||||||
|
if (!table) return;
|
||||||
|
if (!unref(getCanResize)) return;
|
||||||
|
const tableEl: any = table?.$el;
|
||||||
|
const headEl = tableEl.querySelector('.n-data-table-thead ');
|
||||||
|
const { bottomIncludeBody } = getViewportOffset(headEl);
|
||||||
|
const headerH = 64;
|
||||||
|
let paginationH = 2;
|
||||||
|
let marginH = 24;
|
||||||
|
if (!isBoolean(pagination)) {
|
||||||
|
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
||||||
|
if (paginationEl) {
|
||||||
|
const offsetHeight = paginationEl.offsetHeight;
|
||||||
|
paginationH += offsetHeight || 0;
|
||||||
|
} else {
|
||||||
|
paginationH += 28;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let height =
|
||||||
|
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
||||||
|
const maxHeight = props.maxHeight;
|
||||||
|
height = maxHeight && maxHeight < height ? maxHeight : height;
|
||||||
|
deviceHeight.value = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
useWindowSizeFn(computeTableHeight, 280);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
computeTableHeight();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
|
tableElRef,
|
||||||
getBindValues,
|
getBindValues,
|
||||||
densityOptions,
|
densityOptions,
|
||||||
reload,
|
reload,
|
||||||
@@ -244,14 +292,12 @@ export default defineComponent({
|
|||||||
updatePageSize,
|
updatePageSize,
|
||||||
updateCheckedRowKeys,
|
updateCheckedRowKeys,
|
||||||
pagination,
|
pagination,
|
||||||
resetColumns,
|
tableAction,
|
||||||
tableAction
|
};
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style lang='less' scoped>
|
<style lang="less" scoped>
|
||||||
.table-toolbar {
|
.table-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
41
src/components/Table/src/componentMap.ts
Normal file
41
src/components/Table/src/componentMap.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { Component } from 'vue';
|
||||||
|
import {
|
||||||
|
NInput,
|
||||||
|
NSelect,
|
||||||
|
NCheckbox,
|
||||||
|
NInputNumber,
|
||||||
|
NSwitch,
|
||||||
|
NDatePicker,
|
||||||
|
NTimePicker,
|
||||||
|
} from 'naive-ui';
|
||||||
|
import type { ComponentType } from './types/componentType';
|
||||||
|
|
||||||
|
export enum EventEnum {
|
||||||
|
NInput = 'on-input',
|
||||||
|
NInputNumber = 'on-input',
|
||||||
|
NSelect = 'on-update:value',
|
||||||
|
NSwitch = 'on-update:value',
|
||||||
|
NCheckbox = 'on-update:value',
|
||||||
|
NDatePicker = 'on-update:value',
|
||||||
|
NTimePicker = 'on-update:value',
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentMap = new Map<ComponentType, Component>();
|
||||||
|
|
||||||
|
componentMap.set('NInput', NInput);
|
||||||
|
componentMap.set('NInputNumber', NInputNumber);
|
||||||
|
componentMap.set('NSelect', NSelect);
|
||||||
|
componentMap.set('NSwitch', NSwitch);
|
||||||
|
componentMap.set('NCheckbox', NCheckbox);
|
||||||
|
componentMap.set('NDatePicker', NDatePicker);
|
||||||
|
componentMap.set('NTimePicker', NTimePicker);
|
||||||
|
|
||||||
|
export function add(compName: ComponentType, component: Component) {
|
||||||
|
componentMap.set(compName, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function del(compName: ComponentType) {
|
||||||
|
componentMap.delete(compName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { componentMap };
|
||||||
136
src/components/Table/src/components/TableAction.vue
Normal file
136
src/components/Table/src/components/TableAction.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
<n-dropdown
|
||||||
|
v-if="dropDownActions && getDropdownList.length"
|
||||||
|
trigger="hover"
|
||||||
|
:options="getDropdownList"
|
||||||
|
@select="select"
|
||||||
|
>
|
||||||
|
<slot name="more"></slot>
|
||||||
|
<n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span>更多</span>
|
||||||
|
<n-icon size="14" class="ml-1">
|
||||||
|
<DownOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
<!-- <template #icon>-->
|
||||||
|
<!-- -->
|
||||||
|
<!-- </template>-->
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType, computed, toRaw } from 'vue';
|
||||||
|
import { ActionItem } from '@/components/Table';
|
||||||
|
import { usePermission } from '@/hooks/web/usePermission';
|
||||||
|
import { isBoolean, isFunction } from '@/utils/is';
|
||||||
|
import { DownOutlined } from '@vicons/antd';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TableAction',
|
||||||
|
components: { DownOutlined },
|
||||||
|
props: {
|
||||||
|
actions: {
|
||||||
|
type: Array as PropType<ActionItem[]>,
|
||||||
|
default: null,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dropDownActions: {
|
||||||
|
type: Array as PropType<ActionItem[]>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: String as PropType<String>,
|
||||||
|
default: 'button',
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
type: Function as PropType<Function>,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
|
const actionType =
|
||||||
|
props.style === 'button' ? 'default' : props.style === 'text' ? 'primary' : 'default';
|
||||||
|
const actionText =
|
||||||
|
props.style === 'button' ? undefined : props.style === 'text' ? true : undefined;
|
||||||
|
|
||||||
|
const getMoreProps = computed(() => {
|
||||||
|
return {
|
||||||
|
text: actionText,
|
||||||
|
type: actionType,
|
||||||
|
size: 'small',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDropdownList = computed(() => {
|
||||||
|
return (toRaw(props.dropDownActions) || [])
|
||||||
|
.filter((action) => {
|
||||||
|
return hasPermission(action.auth) && isIfShow(action);
|
||||||
|
})
|
||||||
|
.map((action) => {
|
||||||
|
const { popConfirm } = action;
|
||||||
|
return {
|
||||||
|
size: 'small',
|
||||||
|
text: actionText,
|
||||||
|
type: actionType,
|
||||||
|
...action,
|
||||||
|
...popConfirm,
|
||||||
|
onConfirm: popConfirm?.confirm,
|
||||||
|
onCancel: popConfirm?.cancel,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function isIfShow(action: ActionItem): boolean {
|
||||||
|
const ifShow = action.ifShow;
|
||||||
|
|
||||||
|
let isIfShow = true;
|
||||||
|
|
||||||
|
if (isBoolean(ifShow)) {
|
||||||
|
isIfShow = ifShow;
|
||||||
|
}
|
||||||
|
if (isFunction(ifShow)) {
|
||||||
|
isIfShow = ifShow(action);
|
||||||
|
}
|
||||||
|
return isIfShow;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getActions = computed(() => {
|
||||||
|
return (toRaw(props.actions) || [])
|
||||||
|
.filter((action) => {
|
||||||
|
return hasPermission(action.auth) && isIfShow(action);
|
||||||
|
})
|
||||||
|
.map((action) => {
|
||||||
|
const { popConfirm } = action;
|
||||||
|
//需要展示什么风格,自己修改一下参数
|
||||||
|
return {
|
||||||
|
size: 'small',
|
||||||
|
text: actionText,
|
||||||
|
type: actionType,
|
||||||
|
...action,
|
||||||
|
...(popConfirm || {}),
|
||||||
|
onConfirm: popConfirm?.confirm,
|
||||||
|
onCancel: popConfirm?.cancel,
|
||||||
|
enable: !!popConfirm,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getActions,
|
||||||
|
getDropdownList,
|
||||||
|
getMoreProps,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import type { FunctionalComponent, defineComponent } from 'vue';
|
||||||
|
import type { ComponentType } from '../../types/componentType';
|
||||||
|
import { componentMap } from '@/components/Table/src/componentMap';
|
||||||
|
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { NPopover } from 'naive-ui';
|
||||||
|
|
||||||
|
export interface ComponentProps {
|
||||||
|
component: ComponentType;
|
||||||
|
rule: boolean;
|
||||||
|
popoverVisible: boolean;
|
||||||
|
ruleMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CellComponent: FunctionalComponent = (
|
||||||
|
{ component = 'NInput', rule = true, ruleMessage, popoverVisible }: ComponentProps,
|
||||||
|
{ attrs }
|
||||||
|
) => {
|
||||||
|
const Comp = componentMap.get(component) as typeof defineComponent;
|
||||||
|
|
||||||
|
const DefaultComp = h(Comp, attrs);
|
||||||
|
if (!rule) {
|
||||||
|
return DefaultComp;
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
NPopover,
|
||||||
|
{ 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' },
|
||||||
|
{
|
||||||
|
trigger: () => DefaultComp,
|
||||||
|
default: () =>
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
color: 'red',
|
||||||
|
width: '90px',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => ruleMessage,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
402
src/components/Table/src/components/editable/EditableCell.vue
Normal file
402
src/components/Table/src/components/editable/EditableCell.vue
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
<template>
|
||||||
|
<div class="editable-cell">
|
||||||
|
<div v-show="!isEdit" class="editable-cell-content" @click="handleEdit">
|
||||||
|
{{ getValues }}
|
||||||
|
<n-icon class="edit-icon" v-if="!column.editRow">
|
||||||
|
<FormOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex editable-cell-content" v-show="isEdit" v-click-outside="onClickOutside">
|
||||||
|
<CellComponent
|
||||||
|
v-bind="getComponentProps"
|
||||||
|
:component="getComponent"
|
||||||
|
:style="getWrapperStyle"
|
||||||
|
:popoverVisible="getRuleVisible"
|
||||||
|
:ruleMessage="ruleMessage"
|
||||||
|
:rule="getRule"
|
||||||
|
:class="getWrapperClass"
|
||||||
|
ref="elRef"
|
||||||
|
@options-change="handleOptionsChange"
|
||||||
|
@pressEnter="handleEnter"
|
||||||
|
/>
|
||||||
|
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||||
|
<n-icon class="cursor-pointer mx-2">
|
||||||
|
<CheckOutlined @click="handleSubmit" />
|
||||||
|
</n-icon>
|
||||||
|
<n-icon class="cursor-pointer mx-2">
|
||||||
|
<CloseOutlined @click="handleCancel" />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import type { CSSProperties, PropType } from 'vue';
|
||||||
|
import type { BasicColumn } from '../../types/table';
|
||||||
|
import type { EditRecordRow } from './index';
|
||||||
|
|
||||||
|
import { defineComponent, ref, unref, nextTick, computed, watchEffect, toRaw } from 'vue';
|
||||||
|
import { FormOutlined, CloseOutlined, CheckOutlined } from '@vicons/antd';
|
||||||
|
import { CellComponent } from './CellComponent';
|
||||||
|
|
||||||
|
import { useTableContext } from '../../hooks/useTableContext';
|
||||||
|
|
||||||
|
import clickOutside from '@/directives/clickOutside';
|
||||||
|
|
||||||
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
import { isString, isBoolean, isFunction, isNumber, isArray } from '@/utils/is';
|
||||||
|
import { createPlaceholderMessage } from './helper';
|
||||||
|
import { set, omit } from 'lodash-es';
|
||||||
|
import { EventEnum } from '@/components/Table/src/componentMap';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'EditableCell',
|
||||||
|
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent },
|
||||||
|
directives: {
|
||||||
|
clickOutside,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
type: Object as PropType<EditRecordRow>,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: Object as PropType<BasicColumn>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
index: propTypes.number,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const table = useTableContext();
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const elRef = ref();
|
||||||
|
const ruleVisible = ref(false);
|
||||||
|
const ruleMessage = ref('');
|
||||||
|
const optionsRef = ref<LabelValueOptions>([]);
|
||||||
|
const currentValueRef = ref<any>(props.value);
|
||||||
|
const defaultValueRef = ref<any>(props.value);
|
||||||
|
|
||||||
|
// const { prefixCls } = useDesign('editable-cell');
|
||||||
|
|
||||||
|
const getComponent = computed(() => props.column?.editComponent || 'NInput');
|
||||||
|
const getRule = computed(() => props.column?.editRule);
|
||||||
|
|
||||||
|
const getRuleVisible = computed(() => {
|
||||||
|
return unref(ruleMessage) && unref(ruleVisible);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getIsCheckComp = computed(() => {
|
||||||
|
const component = unref(getComponent);
|
||||||
|
return ['NCheckbox', 'NSwitch'].includes(component);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getComponentProps = computed(() => {
|
||||||
|
const compProps = props.column?.editComponentProps ?? {};
|
||||||
|
const editComponent = props.column?.editComponent ?? null;
|
||||||
|
const component = unref(getComponent);
|
||||||
|
const apiSelectProps: Recordable = {};
|
||||||
|
|
||||||
|
const isCheckValue = unref(getIsCheckComp);
|
||||||
|
|
||||||
|
const valueField = isCheckValue ? 'checked' : 'value';
|
||||||
|
const val = unref(currentValueRef);
|
||||||
|
|
||||||
|
let value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
||||||
|
|
||||||
|
if (component === 'NDatePicker') {
|
||||||
|
value = dayjs(value).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEvent: any = editComponent ? EventEnum[editComponent] : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
placeholder: createPlaceholderMessage(unref(getComponent)),
|
||||||
|
...apiSelectProps,
|
||||||
|
...omit(compProps, 'onChange'),
|
||||||
|
[onEvent]: handleChange,
|
||||||
|
[valueField]: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getValues = computed(() => {
|
||||||
|
const { editComponentProps, editValueMap } = props.column;
|
||||||
|
|
||||||
|
const value = unref(currentValueRef);
|
||||||
|
|
||||||
|
if (editValueMap && isFunction(editValueMap)) {
|
||||||
|
return editValueMap(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = unref(getComponent);
|
||||||
|
if (!component.includes('NSelect')) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
|
||||||
|
const option = options.find((item) => `${item.value}` === `${value}`);
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRowEditable = computed(() => {
|
||||||
|
const { editable } = props.record || {};
|
||||||
|
return !!editable;
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
defaultValueRef.value = props.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
const { editable } = props.column;
|
||||||
|
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
|
||||||
|
isEdit.value = !!editable || unref(getRowEditable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleEdit() {
|
||||||
|
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
|
||||||
|
ruleMessage.value = '';
|
||||||
|
isEdit.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
const el = unref(elRef);
|
||||||
|
el?.focus?.();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleChange(e: any) {
|
||||||
|
const component = unref(getComponent);
|
||||||
|
if (!e) {
|
||||||
|
currentValueRef.value = e;
|
||||||
|
} else if (e?.target && Reflect.has(e.target, 'value')) {
|
||||||
|
currentValueRef.value = (e as ChangeEvent).target.value;
|
||||||
|
} else if (component === 'NCheckbox') {
|
||||||
|
currentValueRef.value = (e as ChangeEvent).target.checked;
|
||||||
|
} else if (isString(e) || isBoolean(e) || isNumber(e)) {
|
||||||
|
currentValueRef.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO 这里组件参数格式,和dayjs格式不一致
|
||||||
|
if (component === 'NDatePicker') {
|
||||||
|
let format = (props.column.editComponentProps?.format)
|
||||||
|
.replace(/yyyy/g, 'YYYY')
|
||||||
|
.replace(/dd/g, 'DD');
|
||||||
|
currentValueRef.value = dayjs(currentValueRef.value).format(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = props.column?.editComponentProps?.onChange;
|
||||||
|
if (onChange && isFunction(onChange)) onChange(...arguments);
|
||||||
|
|
||||||
|
table.emit?.('edit-change', {
|
||||||
|
column: props.column,
|
||||||
|
value: unref(currentValueRef),
|
||||||
|
record: toRaw(props.record),
|
||||||
|
});
|
||||||
|
await handleSubmiRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmiRule() {
|
||||||
|
const { column, record } = props;
|
||||||
|
const { editRule } = column;
|
||||||
|
const currentValue = unref(currentValueRef);
|
||||||
|
|
||||||
|
if (editRule) {
|
||||||
|
if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
|
||||||
|
ruleVisible.value = true;
|
||||||
|
const component = unref(getComponent);
|
||||||
|
ruleMessage.value = createPlaceholderMessage(component);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isFunction(editRule)) {
|
||||||
|
const res = await editRule(currentValue, record as Recordable);
|
||||||
|
if (!!res) {
|
||||||
|
ruleMessage.value = res;
|
||||||
|
ruleVisible.value = true;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
ruleMessage.value = '';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ruleMessage.value = '';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(needEmit = true, valid = true) {
|
||||||
|
if (valid) {
|
||||||
|
const isPass = await handleSubmiRule();
|
||||||
|
if (!isPass) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { column, index, record } = props;
|
||||||
|
if (!record) return false;
|
||||||
|
const { key } = column;
|
||||||
|
const value = unref(currentValueRef);
|
||||||
|
if (!key) return;
|
||||||
|
|
||||||
|
const dataKey = key as string;
|
||||||
|
|
||||||
|
set(record, dataKey, value);
|
||||||
|
//const record = await table.updateTableData(index, dataKey, value);
|
||||||
|
needEmit && table.emit?.('edit-end', { record, index, key, value });
|
||||||
|
isEdit.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEnter() {
|
||||||
|
if (props.column?.editRow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await handleSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
isEdit.value = false;
|
||||||
|
currentValueRef.value = defaultValueRef.value;
|
||||||
|
const { column, index, record } = props;
|
||||||
|
const { key } = column;
|
||||||
|
ruleVisible.value = true;
|
||||||
|
ruleMessage.value = '';
|
||||||
|
table.emit?.('edit-cancel', {
|
||||||
|
record,
|
||||||
|
index,
|
||||||
|
key: key,
|
||||||
|
value: unref(currentValueRef),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickOutside() {
|
||||||
|
if (props.column?.editable || unref(getRowEditable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const component = unref(getComponent);
|
||||||
|
|
||||||
|
if (component.includes('NInput')) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only ApiSelect
|
||||||
|
function handleOptionsChange(options: LabelValueOptions) {
|
||||||
|
optionsRef.value = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isEdit,
|
||||||
|
handleEdit,
|
||||||
|
currentValueRef,
|
||||||
|
handleSubmit,
|
||||||
|
handleChange,
|
||||||
|
handleCancel,
|
||||||
|
elRef,
|
||||||
|
getComponent,
|
||||||
|
getRule,
|
||||||
|
onClickOutside,
|
||||||
|
ruleMessage,
|
||||||
|
getRuleVisible,
|
||||||
|
getComponentProps,
|
||||||
|
handleOptionsChange,
|
||||||
|
getWrapperStyle,
|
||||||
|
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;
|
||||||
|
|
||||||
|
.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>
|
||||||
15
src/components/Table/src/components/editable/helper.ts
Normal file
15
src/components/Table/src/components/editable/helper.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { ComponentType } from '../../types/componentType';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 生成placeholder
|
||||||
|
*/
|
||||||
|
export function createPlaceholderMessage(component: ComponentType) {
|
||||||
|
if (component === 'NInput') return '请输入';
|
||||||
|
if (
|
||||||
|
['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes(
|
||||||
|
component
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return '请选择';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
49
src/components/Table/src/components/editable/index.ts
Normal file
49
src/components/Table/src/components/editable/index.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import type { BasicColumn } from '@/components/Table/src/types/table';
|
||||||
|
import { h, Ref } from 'vue';
|
||||||
|
|
||||||
|
import EditableCell from './EditableCell.vue';
|
||||||
|
|
||||||
|
export function renderEditCell(column: BasicColumn) {
|
||||||
|
return (record, index) => {
|
||||||
|
const _key = column.key;
|
||||||
|
const value = record[_key];
|
||||||
|
record.onEdit = async (edit: boolean, submit = false) => {
|
||||||
|
if (!submit) {
|
||||||
|
record.editable = edit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!edit && submit) {
|
||||||
|
const res = await record.onSubmitEdit?.();
|
||||||
|
if (res) {
|
||||||
|
record.editable = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// cancel
|
||||||
|
if (!edit && !submit) {
|
||||||
|
record.onCancelEdit?.();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
return h(EditableCell, {
|
||||||
|
value,
|
||||||
|
record,
|
||||||
|
column,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EditRecordRow<T = Recordable> = Partial<
|
||||||
|
{
|
||||||
|
onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
|
||||||
|
editable: boolean;
|
||||||
|
onCancel: Fn;
|
||||||
|
onSubmit: Fn;
|
||||||
|
submitCbs: Fn[];
|
||||||
|
cancelCbs: Fn[];
|
||||||
|
validCbs: Fn[];
|
||||||
|
editValueRefs: Recordable<Ref>;
|
||||||
|
} & T
|
||||||
|
>;
|
||||||
@@ -11,18 +11,26 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="table-toolbar-inner-popover-title">
|
<div class="table-toolbar-inner-popover-title">
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-checkbox v-model:checked="checkAll" @update:checked="onCheckAll">列展示</n-checkbox>
|
<n-checkbox v-model:checked="checkAll" @update:checked="onCheckAll"
|
||||||
<n-checkbox v-model:checked="selection" @update:checked="onSelection">勾选列</n-checkbox>
|
>列展示</n-checkbox
|
||||||
<n-button text type="info" size="small" class="mt-1" @click="resetColumns">重置</n-button>
|
>
|
||||||
|
<n-checkbox v-model:checked="selection" @update:checked="onSelection"
|
||||||
|
>勾选列</n-checkbox
|
||||||
|
>
|
||||||
|
<n-button text type="info" size="small" class="mt-1" @click="resetColumns"
|
||||||
|
>重置</n-button
|
||||||
|
>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="table-toolbar-inner">
|
<div class="table-toolbar-inner">
|
||||||
<n-checkbox-group v-model:value="checkList" @update:value="onChange">
|
<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" @end="draggableEnd">
|
||||||
<template #item="{element, index}">
|
<template #item="{ element }">
|
||||||
<div class="table-toolbar-inner-checkbox"
|
<div
|
||||||
:class="{'table-toolbar-inner-checkbox-dark':getDarkTheme === true}">
|
class="table-toolbar-inner-checkbox"
|
||||||
|
:class="{ 'table-toolbar-inner-checkbox-dark': getDarkTheme === true }"
|
||||||
|
>
|
||||||
<span class="drag-icon">
|
<span class="drag-icon">
|
||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<DragOutlined />
|
<DragOutlined />
|
||||||
@@ -32,8 +40,12 @@
|
|||||||
<div class="fixed-item">
|
<div class="fixed-item">
|
||||||
<n-tooltip trigger="hover" placement="bottom">
|
<n-tooltip trigger="hover" placement="bottom">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-icon size="18" :color="element.fixed === 'left' ? '#2080f0':undefined"
|
<n-icon
|
||||||
class="cursor-pointer" @click="fixedColumn(element,'left')">
|
size="18"
|
||||||
|
:color="element.fixed === 'left' ? '#2080f0' : undefined"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="fixedColumn(element, 'left')"
|
||||||
|
>
|
||||||
<VerticalRightOutlined />
|
<VerticalRightOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -42,8 +54,12 @@
|
|||||||
<n-divider vertical />
|
<n-divider vertical />
|
||||||
<n-tooltip trigger="hover" placement="bottom">
|
<n-tooltip trigger="hover" placement="bottom">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-icon size="18" :color="element.fixed === 'right' ? '#2080f0':undefined"
|
<n-icon
|
||||||
class="cursor-pointer" @click="fixedColumn(element,'right')">
|
size="18"
|
||||||
|
:color="element.fixed === 'right' ? '#2080f0' : undefined"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="fixedColumn(element, 'right')"
|
||||||
|
>
|
||||||
<VerticalLeftOutlined />
|
<VerticalLeftOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -63,11 +79,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from "vue"
|
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from 'vue';
|
||||||
import { useTableContext } from '../../hooks/useTableContext';
|
import { useTableContext } from '../../hooks/useTableContext';
|
||||||
import { ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, VerticalRightOutlined, VerticalLeftOutlined } from '@vicons/antd'
|
import {
|
||||||
import Draggable from 'vuedraggable/src/vuedraggable'
|
SettingOutlined,
|
||||||
import { useDesignSetting } from "@/hooks/setting/useDesignSetting";
|
DragOutlined,
|
||||||
|
VerticalRightOutlined,
|
||||||
|
VerticalLeftOutlined,
|
||||||
|
} from '@vicons/antd';
|
||||||
|
import Draggable from 'vuedraggable/src/vuedraggable';
|
||||||
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -78,11 +99,14 @@ interface Options {
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ColumnSetting',
|
name: 'ColumnSetting',
|
||||||
components: {
|
components: {
|
||||||
ReloadOutlined, ColumnHeightOutlined, SettingOutlined, DragOutlined, Draggable,
|
SettingOutlined,
|
||||||
VerticalRightOutlined, VerticalLeftOutlined
|
DragOutlined,
|
||||||
|
Draggable,
|
||||||
|
VerticalRightOutlined,
|
||||||
|
VerticalLeftOutlined,
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup() {
|
||||||
const { getDarkTheme } = useDesignSetting()
|
const { getDarkTheme } = useDesignSetting();
|
||||||
const table = useTableContext();
|
const table = useTableContext();
|
||||||
const columnsList = ref<Options[]>([]);
|
const columnsList = ref<Options[]>([]);
|
||||||
const cacheColumnsList = ref<Options[]>([]);
|
const cacheColumnsList = ref<Options[]>([]);
|
||||||
@@ -91,12 +115,12 @@ export default defineComponent({
|
|||||||
selection: false,
|
selection: false,
|
||||||
checkAll: true,
|
checkAll: true,
|
||||||
checkList: [],
|
checkList: [],
|
||||||
defaultCheckList: []
|
defaultCheckList: [],
|
||||||
})
|
});
|
||||||
|
|
||||||
const getSelection = computed(() => {
|
const getSelection = computed(() => {
|
||||||
return state.selection
|
return state.selection;
|
||||||
})
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const columns = table.getColumns();
|
const columns = table.getColumns();
|
||||||
@@ -107,96 +131,93 @@ export default defineComponent({
|
|||||||
|
|
||||||
//初始化
|
//初始化
|
||||||
function init() {
|
function init() {
|
||||||
const columns = getColumns();
|
const columns: any[] = getColumns();
|
||||||
const checkList = columns.map(item => item.key)
|
const checkList: any = columns.map((item) => item.key);
|
||||||
state.checkList = checkList
|
state.checkList = checkList;
|
||||||
state.defaultCheckList = checkList
|
state.defaultCheckList = checkList;
|
||||||
columnsList.value = columns
|
columnsList.value = columns;
|
||||||
cacheColumnsList.value = columns
|
cacheColumnsList.value = columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
//切换
|
//切换
|
||||||
function onChange(checkList) {
|
function onChange(checkList) {
|
||||||
if (state.selection) {
|
if (state.selection) {
|
||||||
checkList.unshift('selection')
|
checkList.unshift('selection');
|
||||||
}
|
}
|
||||||
setColumns(checkList)
|
setColumns(checkList);
|
||||||
}
|
}
|
||||||
|
|
||||||
//设置
|
//设置
|
||||||
function setColumns(columns) {
|
function setColumns(columns) {
|
||||||
table.setColumns(columns)
|
table.setColumns(columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取
|
//获取
|
||||||
function getColumns() {
|
function getColumns() {
|
||||||
let newRet = []
|
let newRet = [];
|
||||||
table.getColumns().forEach(item => {
|
table.getColumns().forEach((item) => {
|
||||||
newRet.push({ ...item })
|
newRet.push({ ...item });
|
||||||
})
|
});
|
||||||
return newRet
|
return newRet.filter((item) => item.key != 'action' && item.title != '操作');
|
||||||
}
|
}
|
||||||
|
|
||||||
//重置
|
//重置
|
||||||
function resetColumns() {
|
function resetColumns() {
|
||||||
state.checkList = [...state.defaultCheckList]
|
state.checkList = [...state.defaultCheckList];
|
||||||
state.checkAll = true;
|
state.checkAll = true;
|
||||||
let cacheColumnsKeys: any[] = table.getCacheColumns()
|
let cacheColumnsKeys: any[] = table.getCacheColumns();
|
||||||
let newColumns = cacheColumnsKeys.map(item => {
|
let newColumns = cacheColumnsKeys.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
fixed: undefined
|
fixed: undefined,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
setColumns(newColumns);
|
setColumns(newColumns);
|
||||||
columnsList.value = newColumns
|
columnsList.value = newColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
//全选
|
//全选
|
||||||
function onCheckAll(e) {
|
function onCheckAll(e) {
|
||||||
let checkList = table.getCacheColumns(true)
|
let checkList = table.getCacheColumns(true);
|
||||||
if (e) {
|
if (e) {
|
||||||
setColumns(checkList);
|
setColumns(checkList);
|
||||||
state.checkList = checkList
|
state.checkList = checkList;
|
||||||
} else {
|
} else {
|
||||||
setColumns([]);
|
setColumns([]);
|
||||||
state.checkList = []
|
state.checkList = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//拖拽排序
|
//拖拽排序
|
||||||
function draggableEnd() {
|
function draggableEnd() {
|
||||||
const newColumns = toRaw(unref(columnsList))
|
const newColumns = toRaw(unref(columnsList));
|
||||||
columnsList.value = newColumns
|
columnsList.value = newColumns;
|
||||||
setColumns(newColumns);
|
setColumns(newColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
//勾选列
|
//勾选列
|
||||||
function onSelection(e) {
|
function onSelection(e) {
|
||||||
let checkList = table.getCacheColumns()
|
let checkList = table.getCacheColumns();
|
||||||
if (e) {
|
if (e) {
|
||||||
checkList.unshift({ type: 'selection', key: 'selection' })
|
checkList.unshift({ type: 'selection', key: 'selection' });
|
||||||
setColumns(checkList);
|
setColumns(checkList);
|
||||||
} else {
|
} else {
|
||||||
checkList.splice(0, 1)
|
checkList.splice(0, 1);
|
||||||
setColumns(checkList);
|
setColumns(checkList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//固定
|
//固定
|
||||||
function fixedColumn(item, fixed) {
|
function fixedColumn(item, fixed) {
|
||||||
console.log('item:', item)
|
|
||||||
if (!state.checkList.includes(item.key)) return;
|
if (!state.checkList.includes(item.key)) return;
|
||||||
let columns = getColumns();
|
let columns = getColumns();
|
||||||
const isFixed = item.fixed === fixed ? undefined : fixed
|
const isFixed = item.fixed === fixed ? undefined : fixed;
|
||||||
let index = columns.findIndex(res => res.key === item.key)
|
let index = columns.findIndex((res) => res.key === item.key);
|
||||||
console.log('index:', index)
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
columns[index].fixed = isFixed;
|
columns[index].fixed = isFixed;
|
||||||
}
|
}
|
||||||
table.setCacheColumnsField(item.key, { fixed: isFixed })
|
table.setCacheColumnsField(item.key, { fixed: isFixed });
|
||||||
columnsList.value[index].fixed = isFixed
|
columnsList.value[index].fixed = isFixed;
|
||||||
console.log('columnsList:', columnsList.value)
|
|
||||||
setColumns(columns);
|
setColumns(columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +231,10 @@ export default defineComponent({
|
|||||||
resetColumns,
|
resetColumns,
|
||||||
fixedColumn,
|
fixedColumn,
|
||||||
draggableEnd,
|
draggableEnd,
|
||||||
getSelection
|
getSelection,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@@ -270,7 +291,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
&-checkbox-dark {
|
&-checkbox-dark {
|
||||||
&:hover {
|
&:hover {
|
||||||
background: hsla(0, 0%, 100%, .08);
|
background: hsla(0, 0%, 100%, 0.08);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import componentSetting from '@/settings/componentSetting'
|
import componentSetting from '@/settings/componentSetting';
|
||||||
|
|
||||||
const { table } = componentSetting
|
const { table } = componentSetting;
|
||||||
|
|
||||||
const { apiSetting, defaultPageSize, pageSizes } = table;
|
const { apiSetting, defaultPageSize, pageSizes } = table;
|
||||||
|
|
||||||
@@ -9,7 +9,3 @@ export const DEFAULTPAGESIZE = defaultPageSize;
|
|||||||
export const APISETTING = apiSetting;
|
export const APISETTING = apiSetting;
|
||||||
|
|
||||||
export const PAGESIZES = pageSizes;
|
export const PAGESIZES = pageSizes;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue';
|
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw, h } from 'vue';
|
||||||
import type { BasicColumn, BasicTableProps } from '../types/table';
|
import type { BasicColumn, BasicTableProps } from '../types/table';
|
||||||
import { isEqual, cloneDeep } from 'lodash-es';
|
import { isEqual, cloneDeep } from 'lodash-es';
|
||||||
import { isArray, isString } from '@/utils/is';
|
import { isArray, isString, isBoolean, isFunction } from '@/utils/is';
|
||||||
|
import { usePermission } from '@/hooks/web/usePermission';
|
||||||
|
import { ActionItem } from '@/components/Table';
|
||||||
|
import { renderEditCell } from '../components/editable';
|
||||||
|
import { NTooltip, NIcon } from 'naive-ui';
|
||||||
|
import { FormOutlined } from '@vicons/antd';
|
||||||
|
|
||||||
export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
||||||
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
|
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
|
||||||
@@ -9,14 +14,71 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
|
|
||||||
const getColumnsRef = computed(() => {
|
const getColumnsRef = computed(() => {
|
||||||
const columns = cloneDeep(unref(columnsRef));
|
const columns = cloneDeep(unref(columnsRef));
|
||||||
|
|
||||||
|
handleActionColumn(propsRef, columns);
|
||||||
|
if (!columns) return [];
|
||||||
return columns;
|
return columns;
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
|
function isIfShow(action: ActionItem): boolean {
|
||||||
|
const ifShow = action.ifShow;
|
||||||
|
|
||||||
|
let isIfShow = true;
|
||||||
|
|
||||||
|
if (isBoolean(ifShow)) {
|
||||||
|
isIfShow = ifShow;
|
||||||
|
}
|
||||||
|
if (isFunction(ifShow)) {
|
||||||
|
isIfShow = ifShow(action);
|
||||||
|
}
|
||||||
|
return isIfShow;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderTooltip = (trigger, content) => {
|
||||||
|
return h(NTooltip, null, {
|
||||||
|
trigger: () => trigger,
|
||||||
|
default: () => content,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getPageColumns = computed(() => {
|
const getPageColumns = computed(() => {
|
||||||
const pageColumns = unref(getColumnsRef);
|
const pageColumns = unref(getColumnsRef);
|
||||||
const columns = cloneDeep(pageColumns);
|
const columns = cloneDeep(pageColumns);
|
||||||
return columns
|
return columns
|
||||||
|
.filter((column) => {
|
||||||
|
// @ts-ignore
|
||||||
|
return hasPermission(column.auth) && isIfShow(column);
|
||||||
})
|
})
|
||||||
|
.map((column) => {
|
||||||
|
const { edit, editRow } = column;
|
||||||
|
if (edit) {
|
||||||
|
column.render = renderEditCell(column);
|
||||||
|
if (edit) {
|
||||||
|
const title: any = column.title;
|
||||||
|
column.title = () => {
|
||||||
|
return renderTooltip(
|
||||||
|
h('span', {}, [
|
||||||
|
h('span', { style: { 'margin-right': '5px' } }, title),
|
||||||
|
h(
|
||||||
|
NIcon,
|
||||||
|
{
|
||||||
|
size: 14,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => h(FormOutlined),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
'该列可编辑'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return column;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => unref(propsRef).columns,
|
() => unref(propsRef).columns,
|
||||||
@@ -26,6 +88,14 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
|
||||||
|
const { actionColumn } = unref(propsRef);
|
||||||
|
if (!actionColumn) return;
|
||||||
|
// @ts-ignore
|
||||||
|
columns.push({
|
||||||
|
...actionColumn,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//设置
|
//设置
|
||||||
function setColumns(columnList: string[]) {
|
function setColumns(columnList: string[]) {
|
||||||
@@ -41,34 +111,32 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
if (!isString(columns[0])) {
|
if (!isString(columns[0])) {
|
||||||
columnsRef.value = columns;
|
columnsRef.value = columns;
|
||||||
} else {
|
} else {
|
||||||
const newColumns: any[] = []
|
const newColumns: any[] = [];
|
||||||
cacheColumns.forEach(item => {
|
cacheColumns.forEach((item) => {
|
||||||
if (columnList.includes(item.key)) {
|
if (columnList.includes(item.key)) {
|
||||||
newColumns.push({ ...item })
|
newColumns.push({ ...item });
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
if (!isEqual(cacheKeys, columns)) {
|
if (!isEqual(cacheKeys, columns)) {
|
||||||
newColumns.sort((prev, next) => {
|
newColumns.sort((prev, next) => {
|
||||||
return (
|
return cacheKeys.indexOf(prev.key) - cacheKeys.indexOf(next.key);
|
||||||
cacheKeys.indexOf(prev.key) - cacheKeys.indexOf(next.key)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
columnsRef.value = newColumns
|
columnsRef.value = newColumns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取
|
//获取
|
||||||
function getColumns() {
|
function getColumns() {
|
||||||
let columns = toRaw(unref(getColumnsRef));
|
const columns = toRaw(unref(getColumnsRef));
|
||||||
return columns.map(item => {
|
return columns.map((item) => {
|
||||||
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined }
|
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined };
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取原始
|
//获取原始
|
||||||
function getCacheColumns(isKey?: boolean): any[] {
|
function getCacheColumns(isKey?: boolean): any[] {
|
||||||
return isKey ? cacheColumns.map(item => item.key) : cacheColumns;
|
return isKey ? cacheColumns.map((item) => item.key) : cacheColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
//更新原始数据单个字段
|
//更新原始数据单个字段
|
||||||
@@ -90,6 +158,6 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
setCacheColumnsField,
|
setCacheColumnsField,
|
||||||
setColumns,
|
setColumns,
|
||||||
getColumns,
|
getColumns,
|
||||||
getPageColumns
|
getPageColumns,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import { APISETTING } from '../const';
|
|||||||
|
|
||||||
export function useDataSource(
|
export function useDataSource(
|
||||||
propsRef: ComputedRef<BasicTableProps>,
|
propsRef: ComputedRef<BasicTableProps>,
|
||||||
{
|
{ getPaginationInfo, setPagination, setLoading, tableData },
|
||||||
getPaginationInfo,
|
|
||||||
setPagination,
|
|
||||||
setLoading,
|
|
||||||
tableData
|
|
||||||
},
|
|
||||||
emit
|
emit
|
||||||
) {
|
) {
|
||||||
const dataSourceRef = ref([]);
|
const dataSourceRef = ref([]);
|
||||||
@@ -33,8 +28,10 @@ export function useDataSource(
|
|||||||
|
|
||||||
const getRowKey = computed(() => {
|
const getRowKey = computed(() => {
|
||||||
const { rowKey }: any = unref(propsRef);
|
const { rowKey }: any = unref(propsRef);
|
||||||
return rowKey ? rowKey : () => {
|
return rowKey
|
||||||
return 'key'
|
? rowKey
|
||||||
|
: () => {
|
||||||
|
return 'key';
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,10 +49,10 @@ export function useDataSource(
|
|||||||
const { request, pagination }: any = unref(propsRef);
|
const { request, pagination }: any = unref(propsRef);
|
||||||
|
|
||||||
//组装分页信息
|
//组装分页信息
|
||||||
const pageField = APISETTING.pageField
|
const pageField = APISETTING.pageField;
|
||||||
const sizeField = APISETTING.sizeField
|
const sizeField = APISETTING.sizeField;
|
||||||
const totalField = APISETTING.totalField
|
const totalField = APISETTING.totalField;
|
||||||
const listField = APISETTING.listField
|
const listField = APISETTING.listField;
|
||||||
|
|
||||||
let pageParams = {};
|
let pageParams = {};
|
||||||
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
|
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
|
||||||
@@ -67,13 +64,13 @@ export function useDataSource(
|
|||||||
pageParams[sizeField] = pageSize;
|
pageParams[sizeField] = pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = {
|
const params = {
|
||||||
...pageParams,
|
...pageParams,
|
||||||
}
|
};
|
||||||
const res = await request(params);
|
const res = await request(params);
|
||||||
|
|
||||||
const resultTotal = res[totalField] || 0
|
const resultTotal = res[totalField] || 0;
|
||||||
const currentPage = res[pageField]
|
const currentPage = res[pageField];
|
||||||
|
|
||||||
// 如果数据异常,需获取正确的页码再次执行
|
// 如果数据异常,需获取正确的页码再次执行
|
||||||
if (resultTotal) {
|
if (resultTotal) {
|
||||||
@@ -85,7 +82,7 @@ export function useDataSource(
|
|||||||
fetch(opt);
|
fetch(opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let resultInfo = res[listField] ? res[listField] : []
|
const resultInfo = res[listField] ? res[listField] : [];
|
||||||
dataSourceRef.value = resultInfo;
|
dataSourceRef.value = resultInfo;
|
||||||
setPagination({
|
setPagination({
|
||||||
[pageField]: currentPage,
|
[pageField]: currentPage,
|
||||||
@@ -98,10 +95,10 @@ export function useDataSource(
|
|||||||
}
|
}
|
||||||
emit('fetch-success', {
|
emit('fetch-success', {
|
||||||
items: unref(resultInfo),
|
items: unref(resultInfo),
|
||||||
resultTotal
|
resultTotal,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
emit('fetch-error', error);
|
emit('fetch-error', error);
|
||||||
dataSourceRef.value = [];
|
dataSourceRef.value = [];
|
||||||
// setPagination({
|
// setPagination({
|
||||||
@@ -115,7 +112,7 @@ export function useDataSource(
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetch();
|
fetch();
|
||||||
}, 16)
|
}, 16);
|
||||||
});
|
});
|
||||||
|
|
||||||
function setTableData(values) {
|
function setTableData(values) {
|
||||||
@@ -136,6 +133,6 @@ export function useDataSource(
|
|||||||
getDataSourceRef,
|
getDataSourceRef,
|
||||||
getDataSource,
|
getDataSource,
|
||||||
setTableData,
|
setTableData,
|
||||||
reload
|
reload,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue';
|
||||||
import { BasicColumn } from './types/table'
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
import { BasicColumn } from './types/table';
|
||||||
|
|
||||||
export const basicProps = {
|
export const basicProps = {
|
||||||
title: {
|
title: {
|
||||||
@@ -16,8 +17,7 @@ export const basicProps = {
|
|||||||
},
|
},
|
||||||
tableData: {
|
tableData: {
|
||||||
type: [Object],
|
type: [Object],
|
||||||
default: () => {
|
default: () => [],
|
||||||
},
|
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
type: [Array] as PropType<BasicColumn[]>,
|
type: [Array] as PropType<BasicColumn[]>,
|
||||||
@@ -27,7 +27,7 @@ export const basicProps = {
|
|||||||
request: {
|
request: {
|
||||||
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
||||||
default: null,
|
default: null,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
rowKey: {
|
rowKey: {
|
||||||
type: [String, Function] as PropType<string | ((record) => string)>,
|
type: [String, Function] as PropType<string | ((record) => string)>,
|
||||||
@@ -35,11 +35,17 @@ export const basicProps = {
|
|||||||
},
|
},
|
||||||
pagination: {
|
pagination: {
|
||||||
type: [Object, Boolean],
|
type: [Object, Boolean],
|
||||||
default: () => {
|
default: () => {},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
//废弃
|
||||||
showPagination: {
|
showPagination: {
|
||||||
type: [String, Boolean],
|
type: [String, Boolean],
|
||||||
default: 'auto'
|
default: 'auto',
|
||||||
}
|
},
|
||||||
}
|
actionColumn: {
|
||||||
|
type: Object as PropType<BasicColumn>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
canResize: propTypes.bool.def(true),
|
||||||
|
resizeHeightOffset: propTypes.number.def(0),
|
||||||
|
};
|
||||||
|
|||||||
8
src/components/Table/src/types/componentType.ts
Normal file
8
src/components/Table/src/types/componentType.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export type ComponentType =
|
||||||
|
| 'NInput'
|
||||||
|
| 'NInputNumber'
|
||||||
|
| 'NSelect'
|
||||||
|
| 'NCheckbox'
|
||||||
|
| 'NSwitch'
|
||||||
|
| 'NDatePicker'
|
||||||
|
| 'NTimePicker';
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import Pagination from 'naive-ui/lib/pagination';
|
|
||||||
import { VNodeChild } from 'vue';
|
|
||||||
|
|
||||||
export interface PaginationProps {
|
export interface PaginationProps {
|
||||||
page?: number;
|
page?: number;
|
||||||
pageCount?: number,
|
pageCount?: number;
|
||||||
pageSize?: number,
|
pageSize?: number;
|
||||||
pageSizes?: number[],
|
pageSizes?: number[];
|
||||||
showSizePicker?: boolean,
|
showSizePicker?: boolean;
|
||||||
showQuickJumper?: boolean,
|
showQuickJumper?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
import type {
|
import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
|
||||||
TableBaseColumn,
|
import { ComponentType } from './componentType';
|
||||||
} from 'naive-ui/lib/data-table/src/interface';
|
|
||||||
|
|
||||||
export interface BasicColumn extends TableBaseColumn {
|
export interface BasicColumn extends TableBaseColumn {
|
||||||
|
//编辑表格
|
||||||
|
edit?: boolean;
|
||||||
|
editRow?: boolean;
|
||||||
|
editable?: boolean;
|
||||||
|
editComponent?: ComponentType;
|
||||||
|
editComponentProps?: Recordable;
|
||||||
|
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
|
||||||
|
editValueMap?: (value: any) => string;
|
||||||
|
onEditRow?: () => void;
|
||||||
|
// 权限编码控制是否显示
|
||||||
|
auth?: RoleEnum | RoleEnum[] | string | string[];
|
||||||
|
// 业务控制是否显示
|
||||||
|
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableActionType {
|
export interface TableActionType {
|
||||||
reload: (opt) => Promise<void>;
|
reload: (opt) => Promise<void>;
|
||||||
emit?: any;
|
emit?: any;
|
||||||
getColumns: (opt) => BasicColumn[];
|
getColumns: (opt?) => BasicColumn[];
|
||||||
setColumns: (columns: BasicColumn[] | string[]) => void;
|
setColumns: (columns: BasicColumn[] | string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BasicTableProps<T = any> {
|
export interface BasicTableProps {
|
||||||
title?: string,
|
title?: string;
|
||||||
dataSource: Function,
|
dataSource: Function;
|
||||||
columns: any[],
|
columns: any[];
|
||||||
pagination: object,
|
pagination: object;
|
||||||
showPagination: boolean
|
showPagination: boolean;
|
||||||
|
actionColumn: any[];
|
||||||
|
canResize: boolean;
|
||||||
|
resizeHeightOffset: number;
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/components/Table/src/types/tableAction.ts
Normal file
26
src/components/Table/src/types/tableAction.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import { NButton } from 'naive-ui';
|
||||||
|
import { RoleEnum } from '@/enums/roleEnum';
|
||||||
|
// @ts-ignore
|
||||||
|
export interface ActionItem extends NButton.props {
|
||||||
|
onClick?: Fn;
|
||||||
|
label?: string;
|
||||||
|
color?: 'success' | 'error' | 'warning';
|
||||||
|
icon?: string;
|
||||||
|
popConfirm?: PopConfirm;
|
||||||
|
disabled?: boolean;
|
||||||
|
divider?: boolean;
|
||||||
|
// 权限编码控制是否显示
|
||||||
|
auth?: RoleEnum | RoleEnum[] | string | string[];
|
||||||
|
// 业务控制是否显示
|
||||||
|
ifShow?: boolean | ((action: ActionItem) => boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PopConfirm {
|
||||||
|
title: string;
|
||||||
|
okText?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
confirm: Fn;
|
||||||
|
cancel?: Fn;
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
@@ -2,9 +2,13 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="upload">
|
<div class="upload">
|
||||||
<div class="upload-card">
|
<div class="upload-card">
|
||||||
|
|
||||||
<!--图片列表-->
|
<!--图片列表-->
|
||||||
<div class="upload-card-item" :style="getCSSProperties" v-for="(item,index) in imgList">
|
<div
|
||||||
|
class="upload-card-item"
|
||||||
|
:style="getCSSProperties"
|
||||||
|
v-for="(item, index) in imgList"
|
||||||
|
:key="`img_${index}`"
|
||||||
|
>
|
||||||
<div class="upload-card-item-info">
|
<div class="upload-card-item-info">
|
||||||
<div class="img-box">
|
<div class="img-box">
|
||||||
<img :src="item" />
|
<img :src="item" />
|
||||||
@@ -21,8 +25,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--上传图片-->
|
<!--上传图片-->
|
||||||
<div class="upload-card-item upload-card-item-select-picture" :style="getCSSProperties"
|
<div
|
||||||
v-if="imgList.length < maxNumber">
|
class="upload-card-item upload-card-item-select-picture"
|
||||||
|
:style="getCSSProperties"
|
||||||
|
v-if="imgList.length < maxNumber"
|
||||||
|
>
|
||||||
<n-upload
|
<n-upload
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
:file-list-style="{ display: 'none' }"
|
:file-list-style="{ display: 'none' }"
|
||||||
@@ -37,7 +44,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-upload>
|
</n-upload>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,7 +53,6 @@
|
|||||||
{{ helpText }}
|
{{ helpText }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
</n-space>
|
</n-space>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--预览图片-->
|
<!--预览图片-->
|
||||||
@@ -63,17 +68,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, toRefs, reactive, computed } from "vue";
|
import { defineComponent, toRefs, reactive, computed } from 'vue';
|
||||||
import { EyeOutlined, DeleteOutlined, PlusOutlined } from "@vicons/antd";
|
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||||
import { NUpload } from 'naive-ui'
|
import { NUpload } from 'naive-ui';
|
||||||
import { basicProps } from "./props";
|
import { basicProps } from './props';
|
||||||
import { useMessage, useDialog } from 'naive-ui'
|
import { useMessage, useDialog } from 'naive-ui';
|
||||||
import { ResultEnum } from '@/enums/httpEnum'
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
import componentSetting from '@/settings/componentSetting'
|
import componentSetting from '@/settings/componentSetting';
|
||||||
import { useGlobSetting } from '@/hooks/setting'
|
import { useGlobSetting } from '@/hooks/setting';
|
||||||
import { isString } from '@/utils/is'
|
import { isString } from '@/utils/is';
|
||||||
|
|
||||||
const globSetting = useGlobSetting()
|
const globSetting = useGlobSetting();
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'BasicUpload',
|
name: 'BasicUpload',
|
||||||
@@ -81,40 +86,38 @@ export default defineComponent({
|
|||||||
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
|
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
|
||||||
props: {
|
props: {
|
||||||
...NUpload.props, // 这里继承原 UI 组件的 props
|
...NUpload.props, // 这里继承原 UI 组件的 props
|
||||||
...basicProps
|
...basicProps,
|
||||||
},
|
},
|
||||||
emits: ['uploadChange', 'delete'],
|
emits: ['uploadChange', 'delete'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const { value, width, height } = props
|
|
||||||
|
|
||||||
const getCSSProperties = computed(() => {
|
const getCSSProperties = computed(() => {
|
||||||
return {
|
return {
|
||||||
width: `${ width }px`,
|
width: `${props.width}px`,
|
||||||
height: `${ height }px`,
|
height: `${props.height}px`,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage();
|
||||||
const dialog = useDialog()
|
const dialog = useDialog();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
showModal: false,
|
showModal: false,
|
||||||
previewUrl: '',
|
previewUrl: '',
|
||||||
originalImgList: [],
|
originalImgList: [],
|
||||||
imgList: []
|
imgList: [],
|
||||||
})
|
});
|
||||||
|
|
||||||
//赋值默认图片显示
|
//赋值默认图片显示
|
||||||
if (value.length) {
|
if (props.value.length) {
|
||||||
state.imgList = value.map(item => {
|
state.imgList = props.value.map((item) => {
|
||||||
return getImgUrl(item)
|
return getImgUrl(item);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//预览
|
//预览
|
||||||
function preview(url: string) {
|
function preview(url: string) {
|
||||||
state.showModal = true
|
state.showModal = true;
|
||||||
state.previewUrl = url
|
state.previewUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
//删除
|
//删除
|
||||||
@@ -125,32 +128,29 @@ export default defineComponent({
|
|||||||
positiveText: '确定',
|
positiveText: '确定',
|
||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
state.imgList.splice(index, 1)
|
state.imgList.splice(index, 1);
|
||||||
state.originalImgList.splice(index, 1)
|
state.originalImgList.splice(index, 1);
|
||||||
emit('uploadChange', state.originalImgList)
|
emit('uploadChange', state.originalImgList);
|
||||||
emit('delete', state.originalImgList)
|
emit('delete', state.originalImgList);
|
||||||
},
|
},
|
||||||
onNegativeClick: () => {
|
onNegativeClick: () => {},
|
||||||
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//组装完整图片地址
|
//组装完整图片地址
|
||||||
function getImgUrl(url: string) {
|
function getImgUrl(url: string): string {
|
||||||
const { imgUrl } = globSetting
|
const { imgUrl } = globSetting;
|
||||||
return (/(^http|https:\/\/)/g).test(url) ? url : `${ imgUrl }${ url }`
|
return /(^http|https:\/\/)/g.test(url) ? url : `${imgUrl}${url}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function checkFileType(fileType: string) {
|
function checkFileType(fileType: string) {
|
||||||
return componentSetting.upload.fileType.includes(fileType)
|
return componentSetting.upload.fileType.includes(fileType);
|
||||||
}
|
}
|
||||||
|
|
||||||
//上传之前
|
//上传之前
|
||||||
function beforeUpload({ file, fileList }) {
|
function beforeUpload({ file }) {
|
||||||
const fileInfo = file.file;
|
const fileInfo = file.file;
|
||||||
const { maxSize, accept, maxNumber } = props;
|
const { maxSize, accept } = props;
|
||||||
const acceptRef = (isString(accept) && accept.split(',')) || [];
|
const acceptRef = (isString(accept) && accept.split(',')) || [];
|
||||||
|
|
||||||
// 设置最大值,则判断
|
// 设置最大值,则判断
|
||||||
@@ -160,29 +160,29 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置类型,则判断
|
// 设置类型,则判断
|
||||||
const fileType = componentSetting.upload.fileType
|
const fileType = componentSetting.upload.fileType;
|
||||||
if (acceptRef.length > 0 && !checkFileType(fileInfo.type)) {
|
if (acceptRef.length > 0 && !checkFileType(fileInfo.type)) {
|
||||||
message.error(`只能上传文件类型为${fileType.join(',')}`);
|
message.error(`只能上传文件类型为${fileType.join(',')}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//上传结束
|
//上传结束
|
||||||
function finish({ event: Event }) {
|
function finish({ event: Event }) {
|
||||||
const res = eval('(' + Event.target.response + ')');
|
const res = eval('(' + Event.target.response + ')');
|
||||||
const infoField = componentSetting.upload.apiSetting.infoField
|
const infoField = componentSetting.upload.apiSetting.infoField;
|
||||||
const { code } = res
|
const { code } = res;
|
||||||
const message = (res.msg || res.message) || '上传失败'
|
const message = res.msg || res.message || '上传失败';
|
||||||
const result = res[infoField]
|
const result = res[infoField];
|
||||||
//成功
|
//成功
|
||||||
if (code === ResultEnum.SUCCESS) {
|
if (code === ResultEnum.SUCCESS) {
|
||||||
let imgUrl: string = getImgUrl(result.photo)
|
let imgUrl = getImgUrl(result.photo);
|
||||||
state.imgList.push(imgUrl)
|
state.imgList.push(imgUrl);
|
||||||
state.originalImgList.push(result.photo)
|
state.originalImgList.push(result.photo);
|
||||||
emit('uploadChange', state.originalImgList)
|
emit('uploadChange', state.originalImgList);
|
||||||
} else message.error(message)
|
} else message.error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -191,14 +191,13 @@ export default defineComponent({
|
|||||||
preview,
|
preview,
|
||||||
remove,
|
remove,
|
||||||
beforeUpload,
|
beforeUpload,
|
||||||
getCSSProperties
|
getCSSProperties,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|
||||||
.upload {
|
.upload {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -223,9 +222,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: 0 0;
|
background: 0 0;
|
||||||
|
.upload-card-item-info::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&-info::before {
|
&-info::before {
|
||||||
opacity: 1
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,9 +248,9 @@ export default defineComponent({
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, .5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all .3s;
|
transition: all 0.3s;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +269,7 @@ export default defineComponent({
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all .3s;
|
transition: all 0.3s;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -277,16 +279,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-icon {
|
.action-icon {
|
||||||
color: rgba(255, 255, 255, .85);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #fff
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item-select-picture {
|
&-item-select-picture {
|
||||||
@@ -300,14 +301,6 @@ export default defineComponent({
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item:hover {
|
|
||||||
background: 0 0;
|
|
||||||
|
|
||||||
.upload-card-item-info::before {
|
|
||||||
opacity: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue';
|
||||||
import { NUpload } from 'naive-ui'
|
import { NUpload } from 'naive-ui';
|
||||||
|
|
||||||
export const basicProps = {
|
export const basicProps = {
|
||||||
...NUpload.props,
|
...NUpload.props,
|
||||||
@@ -13,22 +13,22 @@ export const basicProps = {
|
|||||||
},
|
},
|
||||||
maxSize: {
|
maxSize: {
|
||||||
type: Number as PropType<number>,
|
type: Number as PropType<number>,
|
||||||
default: 2
|
default: 2,
|
||||||
},
|
},
|
||||||
maxNumber: {
|
maxNumber: {
|
||||||
type: Number as PropType<number>,
|
type: Number as PropType<number>,
|
||||||
default: Infinity
|
default: Infinity,
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
type: Number as PropType<number>,
|
type: Number as PropType<number>,
|
||||||
default: 104
|
default: 104,
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
type: Number as PropType<number>,
|
type: Number as PropType<number>,
|
||||||
default: 104 //建议不小于这个尺寸 太小页面可能显示有异常
|
default: 104, //建议不小于这个尺寸 太小页面可能显示有异常
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export interface BasicProps<T = any> {
|
export interface BasicProps {
|
||||||
title?: string,
|
title?: string;
|
||||||
dataSource: Function,
|
dataSource: Function;
|
||||||
columns: any[],
|
columns: any[];
|
||||||
pagination: object,
|
pagination: object;
|
||||||
showPagination: boolean
|
showPagination: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
86
src/directives/clickOutside.ts
Normal file
86
src/directives/clickOutside.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { on } from '@/utils/domUtils';
|
||||||
|
import { isServer } from '@/utils/is';
|
||||||
|
import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue';
|
||||||
|
|
||||||
|
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
|
||||||
|
|
||||||
|
type FlushList = Map<
|
||||||
|
HTMLElement,
|
||||||
|
{
|
||||||
|
documentHandler: DocumentHandler;
|
||||||
|
bindingFn: (...args: unknown[]) => unknown;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
const nodeList: FlushList = new Map();
|
||||||
|
|
||||||
|
let startClick: MouseEvent;
|
||||||
|
|
||||||
|
if (!isServer) {
|
||||||
|
on(document, 'mousedown', (e: MouseEvent) => (startClick = e));
|
||||||
|
on(document, 'mouseup', (e: MouseEvent) => {
|
||||||
|
for (const { documentHandler } of nodeList.values()) {
|
||||||
|
documentHandler(e, startClick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
|
||||||
|
let excludes: HTMLElement[] = [];
|
||||||
|
if (Array.isArray(binding.arg)) {
|
||||||
|
excludes = binding.arg;
|
||||||
|
} else {
|
||||||
|
// due to current implementation on binding type is wrong the type casting is necessary here
|
||||||
|
excludes.push(binding.arg as unknown as HTMLElement);
|
||||||
|
}
|
||||||
|
return function (mouseup, mousedown) {
|
||||||
|
const popperRef = (
|
||||||
|
binding.instance as ComponentPublicInstance<{
|
||||||
|
popperRef: Nullable<HTMLElement>;
|
||||||
|
}>
|
||||||
|
).popperRef;
|
||||||
|
const mouseUpTarget = mouseup.target as Node;
|
||||||
|
const mouseDownTarget = mousedown.target as Node;
|
||||||
|
const isBound = !binding || !binding.instance;
|
||||||
|
const isTargetExists = !mouseUpTarget || !mouseDownTarget;
|
||||||
|
const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
|
||||||
|
const isSelf = el === mouseUpTarget;
|
||||||
|
|
||||||
|
const isTargetExcluded =
|
||||||
|
(excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
|
||||||
|
(excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
|
||||||
|
const isContainedByPopper =
|
||||||
|
popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
|
||||||
|
if (
|
||||||
|
isBound ||
|
||||||
|
isTargetExists ||
|
||||||
|
isContainedByEl ||
|
||||||
|
isSelf ||
|
||||||
|
isTargetExcluded ||
|
||||||
|
isContainedByPopper
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
binding.value();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClickOutside: ObjectDirective = {
|
||||||
|
beforeMount(el, binding) {
|
||||||
|
nodeList.set(el, {
|
||||||
|
documentHandler: createDocumentHandler(el, binding),
|
||||||
|
bindingFn: binding.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updated(el, binding) {
|
||||||
|
nodeList.set(el, {
|
||||||
|
documentHandler: createDocumentHandler(el, binding),
|
||||||
|
bindingFn: binding.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unmounted(el) {
|
||||||
|
nodeList.delete(el);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClickOutside;
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { ObjectDirective } from 'vue'
|
import { ObjectDirective } from 'vue';
|
||||||
import { usePermission } from "@/hooks/web/usePermission";
|
import { usePermission } from '@/hooks/web/usePermission';
|
||||||
|
|
||||||
export const permission: ObjectDirective = {
|
export const permission: ObjectDirective = {
|
||||||
mounted(el: HTMLButtonElement, binding, vnode) {
|
mounted(el: HTMLButtonElement, binding) {
|
||||||
if (binding.value == undefined) return
|
if (binding.value == undefined) return;
|
||||||
const { action, effect } = binding.value
|
const { action, effect } = binding.value;
|
||||||
const { hasPermission } = usePermission()
|
const { hasPermission } = usePermission();
|
||||||
if (!hasPermission(action)) {
|
if (!hasPermission(action)) {
|
||||||
if (effect == 'disabled') {
|
if (effect == 'disabled') {
|
||||||
el.disabled = true
|
el.disabled = true;
|
||||||
el.style["disabled"] = 'disabled'
|
el.style['disabled'] = 'disabled';
|
||||||
el.classList.add("n-button--disabled")
|
el.classList.add('n-button--disabled');
|
||||||
} else {
|
} else {
|
||||||
el.remove()
|
el.remove();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
// token key
|
// token key
|
||||||
export const TOKEN_KEY = 'TOKEN'
|
export const TOKEN_KEY = 'TOKEN';
|
||||||
|
|
||||||
// user info key
|
// user info key
|
||||||
export const USER_INFO_KEY = 'USER__INFO__'
|
export const USER_INFO_KEY = 'USER__INFO__';
|
||||||
|
|
||||||
// role info key
|
// role info key
|
||||||
export const ROLES_KEY = 'ROLES__KEY__'
|
export const ROLES_KEY = 'ROLES__KEY__';
|
||||||
|
|
||||||
// project config key
|
// project config key
|
||||||
export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'
|
export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';
|
||||||
|
|
||||||
// lock info
|
// lock info
|
||||||
export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'
|
export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
|
||||||
|
|
||||||
// base global local key
|
// base global local key
|
||||||
export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'
|
export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__';
|
||||||
|
|
||||||
// base global session key
|
// base global session key
|
||||||
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'
|
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export enum ResultEnum {
|
|||||||
SUCCESS = 200,
|
SUCCESS = 200,
|
||||||
ERROR = -1,
|
ERROR = -1,
|
||||||
TIMEOUT = 10042,
|
TIMEOUT = 10042,
|
||||||
TYPE = 'success'
|
TYPE = 'success',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,7 +16,7 @@ export enum RequestEnum {
|
|||||||
POST = 'POST',
|
POST = 'POST',
|
||||||
PATCH = 'PATCH',
|
PATCH = 'PATCH',
|
||||||
PUT = 'PUT',
|
PUT = 'PUT',
|
||||||
DELETE = 'DELETE'
|
DELETE = 'DELETE',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,5 +30,5 @@ export enum ContentTypeEnum {
|
|||||||
// form-data 一般配合qs
|
// form-data 一般配合qs
|
||||||
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
// form-data 上传
|
// form-data 上传
|
||||||
FORM_DATA = 'multipart/form-data;charset=UTF-8'
|
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export enum PageEnum {
|
|||||||
REDIRECT_NAME = 'Redirect',
|
REDIRECT_NAME = 'Redirect',
|
||||||
// 首页
|
// 首页
|
||||||
BASE_HOME = '/dashboard',
|
BASE_HOME = '/dashboard',
|
||||||
|
//首页跳转默认路由
|
||||||
|
BASE_HOME_REDIRECT = '/dashboard/console',
|
||||||
// 错误
|
// 错误
|
||||||
ERROR_PAGE_NAME = 'ErrorPage',
|
ERROR_PAGE_NAME = 'ErrorPage',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ export enum RoleEnum {
|
|||||||
ADMIN = 'admin',
|
ADMIN = 'admin',
|
||||||
|
|
||||||
// 普通用户
|
// 普通用户
|
||||||
NORMAL = 'normal'
|
NORMAL = 'normal',
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/hooks/event/useWindowSizeFn.ts
Normal file
36
src/hooks/event/useWindowSizeFn.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
|
||||||
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
|
interface WindowSizeOptions {
|
||||||
|
once?: boolean;
|
||||||
|
immediate?: boolean;
|
||||||
|
listenerOptions?: AddEventListenerOptions | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
|
||||||
|
let handler = () => {
|
||||||
|
fn();
|
||||||
|
};
|
||||||
|
const handleSize = useDebounceFn(handler, wait);
|
||||||
|
handler = handleSize;
|
||||||
|
|
||||||
|
const start = () => {
|
||||||
|
if (options && options.immediate) {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
window.removeEventListener('resize', handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
tryOnMounted(() => {
|
||||||
|
start();
|
||||||
|
});
|
||||||
|
|
||||||
|
tryOnUnmounted(() => {
|
||||||
|
stop();
|
||||||
|
});
|
||||||
|
return [start, stop];
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import { useAsync } from './use-async'
|
import { useAsync } from './use-async';
|
||||||
|
|
||||||
export { useAsync }
|
export { useAsync };
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||||||
VITE_GLOB_API_URL_PREFIX,
|
VITE_GLOB_API_URL_PREFIX,
|
||||||
VITE_GLOB_UPLOAD_URL,
|
VITE_GLOB_UPLOAD_URL,
|
||||||
VITE_GLOB_PROD_MOCK,
|
VITE_GLOB_PROD_MOCK,
|
||||||
VITE_GLOB_IMG_URL
|
VITE_GLOB_IMG_URL,
|
||||||
} = getAppEnvConfig();
|
} = getAppEnvConfig();
|
||||||
|
|
||||||
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
||||||
@@ -28,8 +28,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||||||
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
||||||
uploadUrl: VITE_GLOB_UPLOAD_URL,
|
uploadUrl: VITE_GLOB_UPLOAD_URL,
|
||||||
prodMock: VITE_GLOB_PROD_MOCK,
|
prodMock: VITE_GLOB_PROD_MOCK,
|
||||||
imgUrl: VITE_GLOB_IMG_URL
|
imgUrl: VITE_GLOB_IMG_URL,
|
||||||
|
|
||||||
};
|
};
|
||||||
return glob as Readonly<GlobConfig>;
|
return glob as Readonly<GlobConfig>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export function useDesignSetting() {
|
|||||||
return {
|
return {
|
||||||
getDarkTheme,
|
getDarkTheme,
|
||||||
getAppTheme,
|
getAppTheme,
|
||||||
getAppThemeList
|
getAppThemeList,
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { computed } from 'vue';
|
|||||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
|
|
||||||
export function useProjectSetting() {
|
export function useProjectSetting() {
|
||||||
|
|
||||||
const projectStore = useProjectSettingStore();
|
const projectStore = useProjectSettingStore();
|
||||||
|
|
||||||
const getNavMode = computed(() => projectStore.navMode);
|
const getNavMode = computed(() => projectStore.navMode);
|
||||||
@@ -29,6 +28,6 @@ export function useProjectSetting() {
|
|||||||
getMenuSetting,
|
getMenuSetting,
|
||||||
getCrumbsSetting,
|
getCrumbsSetting,
|
||||||
getPermissionMode,
|
getPermissionMode,
|
||||||
getShowFooter
|
getShowFooter,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Ref, isReactive, isRef } from 'vue'
|
import { isReactive, isRef } from 'vue';
|
||||||
|
|
||||||
function setLoading(loading, val) {
|
function setLoading(loading, val) {
|
||||||
if (loading != undefined && isRef(loading)) {
|
if (loading != undefined && isRef(loading)) {
|
||||||
loading.value = val
|
loading.value = val;
|
||||||
} else if (loading != undefined && isReactive(loading)) {
|
} else if (loading != undefined && isReactive(loading)) {
|
||||||
loading.loading = val
|
loading.loading = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAsync = async (func: Promise<any>, loading: any): Promise<any> => {
|
export const useAsync = async (func: Promise<any>, loading: any): Promise<any> => {
|
||||||
setLoading(loading, true)
|
setLoading(loading, true);
|
||||||
|
|
||||||
return await func.finally(() => setLoading(loading, false))
|
return await func.finally(() => setLoading(loading, false));
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { computed, onMounted, reactive, toRefs } from 'vue'
|
import { computed, onMounted, reactive, toRefs } from 'vue';
|
||||||
|
|
||||||
interface Battery {
|
interface Battery {
|
||||||
charging: boolean // 当前电池是否正在充电
|
charging: boolean; // 当前电池是否正在充电
|
||||||
chargingTime: number // 距离充电完毕还需多少秒,如果为0则充电完毕
|
chargingTime: number; // 距离充电完毕还需多少秒,如果为0则充电完毕
|
||||||
dischargingTime: number // 代表距离电池耗电至空且挂起需要多少秒
|
dischargingTime: number; // 代表距离电池耗电至空且挂起需要多少秒
|
||||||
level: number // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
|
level: number; // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
|
||||||
[key: string]: any
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBattery = () => {
|
export const useBattery = () => {
|
||||||
@@ -14,56 +14,56 @@ export const useBattery = () => {
|
|||||||
charging: false,
|
charging: false,
|
||||||
chargingTime: 0,
|
chargingTime: 0,
|
||||||
dischargingTime: 0,
|
dischargingTime: 0,
|
||||||
level: 100
|
level: 100,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// 更新电池使用状态
|
// 更新电池使用状态
|
||||||
const updateBattery = (target) => {
|
const updateBattery = (target) => {
|
||||||
for (const key in state.battery) {
|
for (const key in state.battery) {
|
||||||
state.battery[key] = target[key]
|
state.battery[key] = target[key];
|
||||||
}
|
|
||||||
state.battery.level = state.battery.level * 100
|
|
||||||
}
|
}
|
||||||
|
state.battery.level = state.battery.level * 100;
|
||||||
|
};
|
||||||
|
|
||||||
// 计算电池剩余可用时间
|
// 计算电池剩余可用时间
|
||||||
const calcDischargingTime = computed(() => {
|
const calcDischargingTime = computed(() => {
|
||||||
const hour = state.battery.dischargingTime / 3600
|
const hour = state.battery.dischargingTime / 3600;
|
||||||
const minute = (state.battery.dischargingTime / 60) % 60
|
const minute = (state.battery.dischargingTime / 60) % 60;
|
||||||
return `${ ~~hour }小时${ ~~minute }分钟`
|
return `${~~hour}小时${~~minute}分钟`;
|
||||||
})
|
});
|
||||||
|
|
||||||
// 电池状态
|
// 电池状态
|
||||||
const batteryStatus = computed(() => {
|
const batteryStatus = computed(() => {
|
||||||
if (state.battery.charging && state.battery.level >= 100) {
|
if (state.battery.charging && state.battery.level >= 100) {
|
||||||
return '已充满'
|
return '已充满';
|
||||||
} else if (state.battery.charging) {
|
} else if (state.battery.charging) {
|
||||||
return '充电中'
|
return '充电中';
|
||||||
} else {
|
} else {
|
||||||
return '已断开电源'
|
return '已断开电源';
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const BatteryManager: Battery = await (window.navigator as any).getBattery()
|
const BatteryManager: Battery = await (window.navigator as any).getBattery();
|
||||||
updateBattery(BatteryManager)
|
updateBattery(BatteryManager);
|
||||||
|
|
||||||
// 电池充电状态更新时被调用
|
// 电池充电状态更新时被调用
|
||||||
BatteryManager.onchargingchange = ({ target }) => {
|
BatteryManager.onchargingchange = ({ target }) => {
|
||||||
updateBattery(target)
|
updateBattery(target);
|
||||||
}
|
};
|
||||||
// 电池充电时间更新时被调用
|
// 电池充电时间更新时被调用
|
||||||
BatteryManager.onchargingtimechange = ({ target }) => {
|
BatteryManager.onchargingtimechange = ({ target }) => {
|
||||||
updateBattery(target)
|
updateBattery(target);
|
||||||
}
|
};
|
||||||
// 电池断开充电时间更新时被调用
|
// 电池断开充电时间更新时被调用
|
||||||
BatteryManager.ondischargingtimechange = ({ target }) => {
|
BatteryManager.ondischargingtimechange = ({ target }) => {
|
||||||
updateBattery(target)
|
updateBattery(target);
|
||||||
}
|
};
|
||||||
// 电池电量更新时被调用
|
// 电池电量更新时被调用
|
||||||
BatteryManager.onlevelchange = ({ target }) => {
|
BatteryManager.onlevelchange = ({ target }) => {
|
||||||
updateBattery(target)
|
updateBattery(target);
|
||||||
}
|
};
|
||||||
|
|
||||||
// new Intl.DateTimeFormat('zh', {
|
// new Intl.DateTimeFormat('zh', {
|
||||||
// year: 'numeric',
|
// year: 'numeric',
|
||||||
@@ -74,11 +74,11 @@ export const useBattery = () => {
|
|||||||
// second: '2-digit',
|
// second: '2-digit',
|
||||||
// hour12: false
|
// hour12: false
|
||||||
// }).format(new Date())
|
// }).format(new Date())
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
batteryStatus,
|
batteryStatus,
|
||||||
calcDischargingTime
|
calcDischargingTime,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* description: 获取页面宽度
|
* description: 获取页面宽度
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function useDomWidth() {
|
export function useDomWidth() {
|
||||||
const domWidth = ref(window.innerWidth)
|
const domWidth = ref(window.innerWidth);
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
domWidth.value = document.body.clientWidth
|
domWidth.value = document.body.clientWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', debounce(resize, 80))
|
window.addEventListener('resize', debounce(resize, 80));
|
||||||
})
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', resize)
|
window.removeEventListener('resize', resize);
|
||||||
})
|
});
|
||||||
|
|
||||||
return domWidth
|
return domWidth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 用户网络是否可用
|
* @description 用户网络是否可用
|
||||||
* */
|
* */
|
||||||
export function useOnline() {
|
export function useOnline() {
|
||||||
const online = ref(true)
|
const online = ref(true);
|
||||||
|
|
||||||
const showStatus = (val) => {
|
const showStatus = (val) => {
|
||||||
online.value = typeof val == 'boolean' ? val : val.target.online
|
online.value = typeof val == 'boolean' ? val : val.target.online;
|
||||||
}
|
};
|
||||||
|
|
||||||
// 在页面加载后,设置正确的网络状态
|
// 在页面加载后,设置正确的网络状态
|
||||||
navigator.onLine ? showStatus(true) : showStatus(false)
|
navigator.onLine ? showStatus(true) : showStatus(false);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 开始监听网络状态的变化
|
// 开始监听网络状态的变化
|
||||||
window.addEventListener('online', showStatus)
|
window.addEventListener('online', showStatus);
|
||||||
|
|
||||||
window.addEventListener('offline', showStatus)
|
window.addEventListener('offline', showStatus);
|
||||||
})
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 移除监听网络状态的变化
|
// 移除监听网络状态的变化
|
||||||
window.removeEventListener('online', showStatus)
|
window.removeEventListener('online', showStatus);
|
||||||
|
|
||||||
window.removeEventListener('offline', showStatus)
|
window.removeEventListener('offline', showStatus);
|
||||||
})
|
});
|
||||||
|
|
||||||
return { online }
|
return { online };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取本地时间
|
* @description 获取本地时间
|
||||||
*/
|
*/
|
||||||
export function useTime() {
|
export function useTime() {
|
||||||
let timer // 定时器
|
let timer; // 定时器
|
||||||
const year = ref(0) // 年份
|
const year = ref(0); // 年份
|
||||||
const month = ref(0) // 月份
|
const month = ref(0); // 月份
|
||||||
const week = ref('') // 星期几
|
const week = ref(''); // 星期几
|
||||||
const day = ref(0) // 天数
|
const day = ref(0); // 天数
|
||||||
const hour = ref<number | string>(0) // 小时
|
const hour = ref<number | string>(0); // 小时
|
||||||
const minute = ref<number | string>(0) // 分钟
|
const minute = ref<number | string>(0); // 分钟
|
||||||
const second = ref(0) // 秒
|
const second = ref(0); // 秒
|
||||||
|
|
||||||
// 更新时间
|
// 更新时间
|
||||||
const updateTime = () => {
|
const updateTime = () => {
|
||||||
const date = new Date()
|
const date = new Date();
|
||||||
year.value = date.getFullYear()
|
year.value = date.getFullYear();
|
||||||
month.value = date.getMonth() + 1
|
month.value = date.getMonth() + 1;
|
||||||
week.value = '日一二三四五六'.charAt(date.getDay())
|
week.value = '日一二三四五六'.charAt(date.getDay());
|
||||||
day.value = date.getDate()
|
day.value = date.getDate();
|
||||||
hour.value =
|
hour.value =
|
||||||
(date.getHours() + '')?.padStart(2, '0') ||
|
(date.getHours() + '')?.padStart(2, '0') ||
|
||||||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours())
|
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours());
|
||||||
minute.value =
|
minute.value =
|
||||||
(date.getMinutes() + '')?.padStart(2, '0') ||
|
(date.getMinutes() + '')?.padStart(2, '0') ||
|
||||||
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes())
|
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes());
|
||||||
second.value = date.getSeconds()
|
second.value = date.getSeconds();
|
||||||
}
|
};
|
||||||
|
|
||||||
// 原生时间格式化
|
// 原生时间格式化
|
||||||
// new Intl.DateTimeFormat('zh', {
|
// new Intl.DateTimeFormat('zh', {
|
||||||
@@ -40,16 +40,16 @@ export function useTime() {
|
|||||||
// hour12: false
|
// hour12: false
|
||||||
// }).format(new Date())
|
// }).format(new Date())
|
||||||
|
|
||||||
updateTime()
|
updateTime();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
clearInterval(timer)
|
clearInterval(timer);
|
||||||
timer = setInterval(() => updateTime(), 1000)
|
timer = setInterval(() => updateTime(), 1000);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearInterval(timer)
|
clearInterval(timer);
|
||||||
})
|
});
|
||||||
|
|
||||||
return { month, day, hour, minute, second, week }
|
return { month, day, hour, minute, second, week };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,18 +12,16 @@ import echarts from '@/utils/lib/echarts';
|
|||||||
|
|
||||||
// import { useRootSetting } from '@/hooks/setting/useRootSetting';
|
// import { useRootSetting } from '@/hooks/setting/useRootSetting';
|
||||||
|
|
||||||
|
|
||||||
export function useECharts(
|
export function useECharts(
|
||||||
elRef: Ref<HTMLDivElement>,
|
elRef: Ref<HTMLDivElement>,
|
||||||
theme: 'light' | 'dark' | 'default' = 'light'
|
theme: 'light' | 'dark' | 'default' = 'light'
|
||||||
) {
|
) {
|
||||||
// const { getDarkMode } = useRootSetting();
|
// const { getDarkMode } = useRootSetting();
|
||||||
const getDarkMode = 'light'
|
const getDarkMode = 'light';
|
||||||
let chartInstance: echarts.ECharts | null = null;
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
let resizeFn: Fn = resize;
|
let resizeFn: Fn = resize;
|
||||||
const cacheOptions = ref<EChartsOption>({});
|
const cacheOptions = ref<EChartsOption>({});
|
||||||
let removeResizeFn: Fn = () => {
|
let removeResizeFn: Fn = () => {};
|
||||||
};
|
|
||||||
|
|
||||||
resizeFn = useDebounceFn(resize, 200);
|
resizeFn = useDebounceFn(resize, 200);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
|
||||||
export function usePermission() {
|
export function usePermission() {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -8,10 +8,10 @@ export function usePermission() {
|
|||||||
* @param accesses
|
* @param accesses
|
||||||
*/
|
*/
|
||||||
function _someRoles(accesses: string[]) {
|
function _someRoles(accesses: string[]) {
|
||||||
return userStore.getRoles.some(item => {
|
return userStore.getRoles.some((item) => {
|
||||||
const { value }: any = item
|
const { value }: any = item;
|
||||||
return accesses.includes(value)
|
return accesses.includes(value);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,8 +19,8 @@ export function usePermission() {
|
|||||||
* 可用于 v-if 显示逻辑
|
* 可用于 v-if 显示逻辑
|
||||||
* */
|
* */
|
||||||
function hasPermission(accesses: string[]): boolean {
|
function hasPermission(accesses: string[]): boolean {
|
||||||
if (!accesses.length) return true
|
if (!accesses || !accesses.length) return true;
|
||||||
return _someRoles(accesses)
|
return _someRoles(accesses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,11 +28,11 @@ export function usePermission() {
|
|||||||
* @param accesses
|
* @param accesses
|
||||||
*/
|
*/
|
||||||
function hasEveryPermission(accesses: string[]): boolean {
|
function hasEveryPermission(accesses: string[]): boolean {
|
||||||
const rolesList = userStore.getRoles
|
const rolesList = userStore.getRoles;
|
||||||
if (Array.isArray(accesses)) {
|
if (Array.isArray(accesses)) {
|
||||||
return accesses.every((access) => !!rolesList[access])
|
return accesses.every((access) => !!rolesList[access]);
|
||||||
}
|
}
|
||||||
throw new Error(`[hasEveryPermission]: ${ accesses } should be a array !`)
|
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,11 +41,11 @@ export function usePermission() {
|
|||||||
* @param accessMap
|
* @param accessMap
|
||||||
*/
|
*/
|
||||||
function hasSomePermission(accesses: string[]): boolean {
|
function hasSomePermission(accesses: string[]): boolean {
|
||||||
const rolesList = userStore.getRoles
|
const rolesList = userStore.getRoles;
|
||||||
if (Array.isArray(accesses)) {
|
if (Array.isArray(accesses)) {
|
||||||
return accesses.some((access) => !!rolesList[access])
|
return accesses.some((access) => !!rolesList[access]);
|
||||||
}
|
}
|
||||||
throw new Error(`[hasSomePermission]: ${ accesses } should be a array !`)
|
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { hasPermission, hasEveryPermission, hasSomePermission };
|
return { hasPermission, hasEveryPermission, hasSomePermission };
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import PageFooter from './index.vue'
|
import PageFooter from './index.vue';
|
||||||
|
|
||||||
export { PageFooter }
|
export { PageFooter };
|
||||||
|
|||||||
@@ -1,48 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-footer">
|
<div class="page-footer">
|
||||||
<div class="page-footer-link">
|
<div class="page-footer-link">
|
||||||
<a href="https://github.com/jekip/naive-ui-admin" target="_blank">
|
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 官网 </a>
|
||||||
官网
|
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 社区 </a>
|
||||||
</a>
|
<a href="https://github.com/jekip/naive-ui-admin/issues" target="_blank"> 交流 </a>
|
||||||
<a href="https://github.com/jekip/naive-ui-admin" target="_blank">
|
|
||||||
社区
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/jekip/naive-ui-admin/issues" target="_blank">
|
|
||||||
交流
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="copyright">
|
<div class="copyright"> naive-ui-admin 1.4 · Made by Ah jung </div>
|
||||||
naive-ui-admin 1.3 · Made by Ah jung
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { GithubOutlined, CopyrightOutlined } from '@vicons/antd'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PageFooter',
|
name: 'PageFooter',
|
||||||
components: { GithubOutlined, CopyrightOutlined },
|
components: {},
|
||||||
props: {
|
props: {
|
||||||
collapsed: {
|
collapsed: {
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.page-footer {
|
.page-footer {
|
||||||
margin: 48px 0 24px 0;
|
//margin: 28px 0 24px 0;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #808695;
|
color: #808695;
|
||||||
-webkit-transition: all .2s ease-in-out;
|
-webkit-transition: all 0.2s ease-in-out;
|
||||||
transition: all .2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #515a6e;
|
color: #515a6e;
|
||||||
|
|||||||
@@ -7,17 +7,28 @@
|
|||||||
<div class="drawer-setting-item justify-center dark-switch">
|
<div class="drawer-setting-item justify-center dark-switch">
|
||||||
<n-tooltip placement="bottom">
|
<n-tooltip placement="bottom">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-switch v-model:value="designStore.darkTheme"/>
|
<n-switch v-model:value="designStore.darkTheme" class="dark-theme-switch">
|
||||||
|
<template #checked>
|
||||||
|
<n-icon size="14" color="#ffd93b">
|
||||||
|
<SunnySharp />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
<template #unchecked>
|
||||||
|
<n-icon size="14" color="#ffd93b">
|
||||||
|
<Moon />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-switch>
|
||||||
</template>
|
</template>
|
||||||
<span>深色主题</span>
|
<span>深色主题</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-divider title-placement="center">系统主题</n-divider>
|
<n-divider title-placement="center">系统主题</n-divider>
|
||||||
|
|
||||||
<div class="drawer-setting-item align-items-top">
|
<div class="drawer-setting-item align-items-top">
|
||||||
<span class="theme-item"
|
<span
|
||||||
|
class="theme-item"
|
||||||
v-for="(item, index) in appThemeList"
|
v-for="(item, index) in appThemeList"
|
||||||
:key="index"
|
:key="index"
|
||||||
:style="{ 'background-color': item }"
|
:style="{ 'background-color': item }"
|
||||||
@@ -53,7 +64,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<n-divider title-placement="center">导航栏风格</n-divider>
|
<n-divider title-placement="center">导航栏风格</n-divider>
|
||||||
|
|
||||||
<div class="drawer-setting-item align-items-top">
|
<div class="drawer-setting-item align-items-top">
|
||||||
@@ -82,7 +92,10 @@
|
|||||||
<div class="drawer-setting-item-style">
|
<div class="drawer-setting-item-style">
|
||||||
<n-tooltip placement="top">
|
<n-tooltip placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<img src="~@/assets/images/header-theme-dark.svg" @click="togNavTheme('header-dark')"/>
|
<img
|
||||||
|
src="~@/assets/images/header-theme-dark.svg"
|
||||||
|
@click="togNavTheme('header-dark')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span>暗色顶栏</span>
|
<span>暗色顶栏</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
@@ -93,9 +106,7 @@
|
|||||||
<n-divider title-placement="center">界面功能</n-divider>
|
<n-divider title-placement="center">界面功能</n-divider>
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<div class="drawer-setting-item-title">
|
<div class="drawer-setting-item-title"> 固定顶栏 </div>
|
||||||
固定顶栏
|
|
||||||
</div>
|
|
||||||
<div class="drawer-setting-item-action">
|
<div class="drawer-setting-item-action">
|
||||||
<n-switch v-model:value="settingStore.headerSetting.fixed" />
|
<n-switch v-model:value="settingStore.headerSetting.fixed" />
|
||||||
</div>
|
</div>
|
||||||
@@ -111,9 +122,7 @@
|
|||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<div class="drawer-setting-item-title">
|
<div class="drawer-setting-item-title"> 固定多页签 </div>
|
||||||
固定多页签
|
|
||||||
</div>
|
|
||||||
<div class="drawer-setting-item-action">
|
<div class="drawer-setting-item-action">
|
||||||
<n-switch v-model:value="settingStore.multiTabsSetting.fixed" />
|
<n-switch v-model:value="settingStore.multiTabsSetting.fixed" />
|
||||||
</div>
|
</div>
|
||||||
@@ -122,127 +131,116 @@
|
|||||||
<n-divider title-placement="center">界面显示</n-divider>
|
<n-divider title-placement="center">界面显示</n-divider>
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<div class="drawer-setting-item-title">
|
<div class="drawer-setting-item-title"> 显示重载页面按钮 </div>
|
||||||
显示重载页面按钮
|
|
||||||
</div>
|
|
||||||
<div class="drawer-setting-item-action">
|
<div class="drawer-setting-item-action">
|
||||||
<n-switch v-model:value="settingStore.headerSetting.isReload" />
|
<n-switch v-model:value="settingStore.headerSetting.isReload" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<div class="drawer-setting-item-title">
|
<div class="drawer-setting-item-title"> 显示面包屑导航 </div>
|
||||||
显示面包屑导航
|
|
||||||
</div>
|
|
||||||
<div class="drawer-setting-item-action">
|
<div class="drawer-setting-item-action">
|
||||||
<n-switch v-model:value="settingStore.crumbsSetting.show" />
|
<n-switch v-model:value="settingStore.crumbsSetting.show" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<div class="drawer-setting-item-title">
|
<div class="drawer-setting-item-title"> 显示面包屑显示图标 </div>
|
||||||
显示面包屑显示图标
|
|
||||||
</div>
|
|
||||||
<div class="drawer-setting-item-action">
|
<div class="drawer-setting-item-action">
|
||||||
<n-switch v-model:value="settingStore.crumbsSetting.showIcon" />
|
<n-switch v-model:value="settingStore.crumbsSetting.showIcon" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<div class="drawer-setting-item-title">
|
<div class="drawer-setting-item-title"> 显示多页签 </div>
|
||||||
显示多页签
|
|
||||||
</div>
|
|
||||||
<div class="drawer-setting-item-action">
|
<div class="drawer-setting-item-action">
|
||||||
<n-switch v-model:value="settingStore.multiTabsSetting.show" />
|
<n-switch v-model:value="settingStore.multiTabsSetting.show" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!--1.15废弃,没啥用,占用操作空间-->
|
||||||
<div class="drawer-setting-item">
|
<!-- <div class="drawer-setting-item">-->
|
||||||
<div class="drawer-setting-item-title">
|
<!-- <div class="drawer-setting-item-title"> 显示页脚 </div>-->
|
||||||
显示页脚
|
<!-- <div class="drawer-setting-item-action">-->
|
||||||
</div>
|
<!-- <n-switch v-model:value="settingStore.showFooter" />-->
|
||||||
<div class="drawer-setting-item-action">
|
<!-- </div>-->
|
||||||
<n-switch v-model:value="settingStore.showFooter"/>
|
<!-- </div>-->
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<n-alert type="warning" :showIcon="false">
|
<n-alert type="warning" :showIcon="false">
|
||||||
<p>{{ alertText }}</p>
|
<p>{{ alertText }}</p>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</n-drawer-content>
|
</n-drawer-content>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, watch, createVNode, computed, unref } from 'vue'
|
import { defineComponent, reactive, toRefs, watch } from 'vue';
|
||||||
import { useProjectSettingStore } from "@/store/modules/projectSetting";
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
import { useDesignSettingStore } from "@/store/modules/designSetting";
|
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||||
import { CheckOutlined } from '@vicons/antd'
|
import { CheckOutlined } from '@vicons/antd';
|
||||||
import { darkTheme } from 'naive-ui'
|
import { Moon, SunnySharp } from '@vicons/ionicons5';
|
||||||
|
import { darkTheme } from 'naive-ui';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ProjectSetting',
|
name: 'ProjectSetting',
|
||||||
|
components: { CheckOutlined, Moon, SunnySharp },
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '项目配置'
|
default: '项目配置',
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 280
|
default: 280,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { CheckOutlined },
|
setup(props) {
|
||||||
setup(props, { emit }) {
|
const settingStore = useProjectSettingStore();
|
||||||
const settingStore = useProjectSettingStore()
|
const designStore = useDesignSettingStore();
|
||||||
const designStore = useDesignSettingStore()
|
|
||||||
const { width, title } = props
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
width,
|
width: props.width,
|
||||||
title,
|
title: props.title,
|
||||||
isDrawer: false,
|
isDrawer: false,
|
||||||
placement: "right",
|
placement: 'right',
|
||||||
alertText: '该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
|
alertText:
|
||||||
appThemeList: designStore.appThemeList
|
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
|
||||||
})
|
appThemeList: designStore.appThemeList,
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => designStore.darkTheme,
|
() => designStore.darkTheme,
|
||||||
(to) => {
|
(to) => {
|
||||||
settingStore.navTheme = to ? 'header-dark' : 'dark'
|
settingStore.navTheme = to ? 'header-dark' : 'dark';
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
function openDrawer(isDrawer) {
|
function openDrawer() {
|
||||||
state.isDrawer = true
|
state.isDrawer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDrawer() {
|
function closeDrawer() {
|
||||||
state.isDrawer = false
|
state.isDrawer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function togNavTheme(theme) {
|
function togNavTheme(theme) {
|
||||||
settingStore.navTheme = theme
|
settingStore.navTheme = theme;
|
||||||
if (settingStore.navMode === 'horizontal' && theme === 'light') {
|
if (settingStore.navMode === 'horizontal' && theme === 'light') {
|
||||||
designStore.navTheme = 'dark'
|
settingStore.navTheme = 'dark';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function togTheme(color) {
|
function togTheme(color) {
|
||||||
designStore.appTheme = color
|
designStore.appTheme = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
function togNavMode(mode) {
|
function togNavMode(mode) {
|
||||||
settingStore.navMode = mode
|
settingStore.navMode = mode;
|
||||||
if (mode === 'horizontal') {
|
if (mode === 'horizontal') {
|
||||||
settingStore.setNavTheme('light')
|
settingStore.setNavTheme('light');
|
||||||
} else {
|
} else {
|
||||||
settingStore.setNavTheme('dark')
|
settingStore.setNavTheme('dark');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,9 +254,9 @@ export default defineComponent({
|
|||||||
darkTheme,
|
darkTheme,
|
||||||
openDrawer,
|
openDrawer,
|
||||||
closeDrawer,
|
closeDrawer,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -301,7 +299,7 @@ export default defineComponent({
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.n-icon {
|
.n-icon {
|
||||||
color: #fff
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,8 +312,7 @@ export default defineComponent({
|
|||||||
.justify-center {
|
.justify-center {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.dark-switch .n-switch {
|
||||||
.dark-switch .n-switch--active {
|
|
||||||
::v-deep(.n-switch__rail) {
|
::v-deep(.n-switch__rail) {
|
||||||
background-color: #000e1c;
|
background-color: #000e1c;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
CheckOutlined
|
CheckOutlined,
|
||||||
} from '@vicons/antd'
|
} from '@vicons/antd';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
@@ -27,5 +27,5 @@ export default {
|
|||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
CheckOutlined
|
CheckOutlined,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import PageHeader from './index.vue'
|
import PageHeader from './index.vue';
|
||||||
|
|
||||||
export { PageHeader }
|
export { PageHeader };
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
<!--左侧菜单-->
|
<!--左侧菜单-->
|
||||||
<div class="layout-header-left" v-else>
|
<div class="layout-header-left" v-else>
|
||||||
<!-- 菜单收起 -->
|
<!-- 菜单收起 -->
|
||||||
<div class="ml-1 layout-header-trigger layout-header-trigger-min"
|
<div
|
||||||
@click="() => $emit('update:collapsed', !collapsed)">
|
class="ml-1 layout-header-trigger layout-header-trigger-min"
|
||||||
|
@click="() => $emit('update:collapsed', !collapsed)"
|
||||||
|
>
|
||||||
<n-icon size="18" v-if="collapsed">
|
<n-icon size="18" v-if="collapsed">
|
||||||
<MenuUnfoldOutlined />
|
<MenuUnfoldOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -17,8 +19,11 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
<!-- 刷新 -->
|
<!-- 刷新 -->
|
||||||
<div class="mr-1 layout-header-trigger layout-header-trigger-min" v-if="headerSetting.isReload"
|
<div
|
||||||
@click="reloadPage">
|
class="mr-1 layout-header-trigger layout-header-trigger-min"
|
||||||
|
v-if="headerSetting.isReload"
|
||||||
|
@click="reloadPage"
|
||||||
|
>
|
||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<ReloadOutlined />
|
<ReloadOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -33,12 +38,18 @@
|
|||||||
@select="dropdownSelect"
|
@select="dropdownSelect"
|
||||||
>
|
>
|
||||||
<span class="link-text">
|
<span class="link-text">
|
||||||
<component v-if="crumbsSetting.showIcon && routeItem.meta.icon" :is="routeItem.meta.icon"></component>
|
<component
|
||||||
|
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
|
||||||
|
:is="routeItem.meta.icon"
|
||||||
|
/>
|
||||||
{{ routeItem.meta.title }}
|
{{ routeItem.meta.title }}
|
||||||
</span>
|
</span>
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
<span class="link-text" v-else>
|
<span class="link-text" v-else>
|
||||||
<component v-if="crumbsSetting.showIcon && routeItem.meta.icon" :is="routeItem.meta.icon"></component>
|
<component
|
||||||
|
v-if="crumbsSetting.showIcon && routeItem.meta.icon"
|
||||||
|
:is="routeItem.meta.icon"
|
||||||
|
/>
|
||||||
{{ routeItem.meta.title }}
|
{{ routeItem.meta.title }}
|
||||||
</span>
|
</span>
|
||||||
</n-breadcrumb-item>
|
</n-breadcrumb-item>
|
||||||
@@ -46,7 +57,11 @@
|
|||||||
</n-breadcrumb>
|
</n-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-header-right">
|
<div class="layout-header-right">
|
||||||
<div class="layout-header-trigger layout-header-trigger-min" v-for="item in iconList" :key="item.icon.name">
|
<div
|
||||||
|
class="layout-header-trigger layout-header-trigger-min"
|
||||||
|
v-for="item in iconList"
|
||||||
|
:key="item.icon.name"
|
||||||
|
>
|
||||||
<n-tooltip placement="bottom">
|
<n-tooltip placement="bottom">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
@@ -71,7 +86,7 @@
|
|||||||
<div class="layout-header-trigger layout-header-trigger-min">
|
<div class="layout-header-trigger layout-header-trigger-min">
|
||||||
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<n-avatar>
|
<n-avatar round>
|
||||||
{{ username }}
|
{{ username }}
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<UserOutlined />
|
<UserOutlined />
|
||||||
@@ -98,42 +113,36 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue'
|
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import components from './components'
|
import components from './components';
|
||||||
import { NDialogProvider, useDialog, useMessage, useNotification } from 'naive-ui'
|
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
|
||||||
import { TABS_ROUTES } from '@/store/mutation-types'
|
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { useLockscreenStore } from '@/store/modules/lockscreen'
|
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||||
import ProjectSetting from './ProjectSetting.vue'
|
import ProjectSetting from './ProjectSetting.vue';
|
||||||
import { AsideMenu } from '@/layout/components/Menu'
|
import { AsideMenu } from '@/layout/components/Menu';
|
||||||
import { useProjectSetting } from "@/hooks/setting/useProjectSetting";
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PageHeader',
|
name: 'PageHeader',
|
||||||
components: { ...components, NDialogProvider, ProjectSetting, AsideMenu },
|
components: { ...components, NDialogProvider, ProjectSetting, AsideMenu },
|
||||||
props: {
|
props: {
|
||||||
collapsed: {
|
collapsed: {
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore();
|
||||||
const useLockscreen = useLockscreenStore()
|
const useLockscreen = useLockscreenStore();
|
||||||
const message = useMessage()
|
const message = useMessage();
|
||||||
const notification = useNotification()
|
const dialog = useDialog();
|
||||||
const dialog = useDialog()
|
const { getNavMode, getNavTheme, getHeaderSetting, getMenuSetting, getCrumbsSetting } =
|
||||||
const {
|
useProjectSetting();
|
||||||
getNavMode,
|
|
||||||
getNavTheme,
|
|
||||||
getHeaderSetting,
|
|
||||||
getMenuSetting,
|
|
||||||
getCrumbsSetting
|
|
||||||
} = useProjectSetting()
|
|
||||||
|
|
||||||
const { username } = userStore?.info || {}
|
const { username } = userStore?.info || {};
|
||||||
|
|
||||||
const drawerSetting = ref()
|
const drawerSetting = ref();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
username: username || '',
|
username: username || '',
|
||||||
@@ -142,51 +151,51 @@ export default defineComponent({
|
|||||||
navTheme: getNavTheme,
|
navTheme: getNavTheme,
|
||||||
headerSetting: getHeaderSetting,
|
headerSetting: getHeaderSetting,
|
||||||
crumbsSetting: getCrumbsSetting,
|
crumbsSetting: getCrumbsSetting,
|
||||||
})
|
});
|
||||||
|
|
||||||
const getChangeStyle = computed(() => {
|
const getChangeStyle = computed(() => {
|
||||||
const { collapsed } = props
|
const { collapsed } = props;
|
||||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting)
|
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
||||||
return {
|
return {
|
||||||
'left': collapsed ? `${ minMenuWidth }px` : `${ menuWidth }px`,
|
left: collapsed ? `${minMenuWidth}px` : `${menuWidth}px`,
|
||||||
'width': `calc(100% - ${ collapsed ? `${ minMenuWidth }px` : `${ menuWidth }px` })`
|
width: `calc(100% - ${collapsed ? `${minMenuWidth}px` : `${menuWidth}px`})`,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
|
|
||||||
const generator: any = (routerMap, parent) => {
|
const generator: any = (routerMap) => {
|
||||||
return routerMap.map((item, key) => {
|
return routerMap.map((item) => {
|
||||||
const currentMenu = {
|
const currentMenu = {
|
||||||
...item,
|
...item,
|
||||||
label: item.meta.title,
|
label: item.meta.title,
|
||||||
key: item.name,
|
key: item.name,
|
||||||
disabled: item.path === '/',
|
disabled: item.path === '/',
|
||||||
}
|
};
|
||||||
// 是否有子菜单,并递归处理
|
// 是否有子菜单,并递归处理
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
// Recursion
|
// Recursion
|
||||||
currentMenu.children = generator(item.children, currentMenu)
|
currentMenu.children = generator(item.children, currentMenu);
|
||||||
}
|
|
||||||
return currentMenu
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return currentMenu;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const breadcrumbList = computed(() => {
|
const breadcrumbList = computed(() => {
|
||||||
return generator(route.matched)
|
return generator(route.matched);
|
||||||
})
|
});
|
||||||
|
|
||||||
const dropdownSelect = (key) => {
|
const dropdownSelect = (key) => {
|
||||||
router.push({ name: key })
|
router.push({ name: key });
|
||||||
}
|
};
|
||||||
|
|
||||||
// 刷新页面
|
// 刷新页面
|
||||||
const reloadPage = () => {
|
const reloadPage = () => {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/redirect' + unref(route).fullPath
|
path: '/redirect' + unref(route).fullPath,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// 退出登录
|
// 退出登录
|
||||||
const doLogout = () => {
|
const doLogout = () => {
|
||||||
@@ -196,92 +205,90 @@ export default defineComponent({
|
|||||||
positiveText: '确定',
|
positiveText: '确定',
|
||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
userStore.logout().then((res) => {
|
userStore.logout().then(() => {
|
||||||
message.success('成功退出登录')
|
message.success('成功退出登录');
|
||||||
// 移除标签页
|
// 移除标签页
|
||||||
localStorage.removeItem(TABS_ROUTES)
|
localStorage.removeItem(TABS_ROUTES);
|
||||||
router
|
router
|
||||||
.replace({
|
.replace({
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
query: {
|
query: {
|
||||||
redirect: route.fullPath
|
redirect: route.fullPath,
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => location.reload())
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
onNegativeClick: () => {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
.finally(() => location.reload());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 切换全屏图标
|
// 切换全屏图标
|
||||||
const toggleFullscreenIcon = () =>
|
const toggleFullscreenIcon = () =>
|
||||||
(state.fullscreenIcon =
|
(state.fullscreenIcon =
|
||||||
document.fullscreenElement !== null ? 'FullscreenExitOutlined' : 'FullscreenOutlined')
|
document.fullscreenElement !== null ? 'FullscreenExitOutlined' : 'FullscreenOutlined');
|
||||||
|
|
||||||
// 监听全屏切换事件
|
// 监听全屏切换事件
|
||||||
document.addEventListener('fullscreenchange', toggleFullscreenIcon)
|
document.addEventListener('fullscreenchange', toggleFullscreenIcon);
|
||||||
|
|
||||||
// 全屏切换
|
// 全屏切换
|
||||||
const toggleFullScreen = () => {
|
const toggleFullScreen = () => {
|
||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
document.documentElement.requestFullscreen()
|
document.documentElement.requestFullscreen();
|
||||||
} else {
|
} else {
|
||||||
if (document.exitFullscreen) {
|
if (document.exitFullscreen) {
|
||||||
document.exitFullscreen()
|
document.exitFullscreen();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 图标列表
|
// 图标列表
|
||||||
const iconList = [
|
const iconList = [
|
||||||
{
|
{
|
||||||
icon: 'SearchOutlined',
|
icon: 'SearchOutlined',
|
||||||
tips: '搜索'
|
tips: '搜索',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'GithubOutlined',
|
icon: 'GithubOutlined',
|
||||||
tips: 'github',
|
tips: 'github',
|
||||||
eventObject: {
|
eventObject: {
|
||||||
click: () => window.open('https://github.com/jekip/naive-ui-admin')
|
click: () => window.open('https://github.com/jekip/naive-ui-admin'),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'LockOutlined',
|
icon: 'LockOutlined',
|
||||||
tips: '锁屏',
|
tips: '锁屏',
|
||||||
eventObject: {
|
eventObject: {
|
||||||
click: () => useLockscreen.setLock(true)
|
click: () => useLockscreen.setLock(true),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
const avatarOptions = [
|
const avatarOptions = [
|
||||||
{
|
{
|
||||||
label: '个人设置',
|
label: '个人设置',
|
||||||
key: 1
|
key: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '退出登录',
|
label: '退出登录',
|
||||||
key: 2
|
key: 2,
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
//头像下拉菜单
|
//头像下拉菜单
|
||||||
const avatarSelect = (key) => {
|
const avatarSelect = (key) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 1:
|
case 1:
|
||||||
router.push({ name: 'Setting' })
|
router.push({ name: 'Setting' });
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
doLogout()
|
doLogout();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function openSetting() {
|
function openSetting() {
|
||||||
const { openDrawer } = drawerSetting.value
|
const { openDrawer } = drawerSetting.value;
|
||||||
openDrawer()
|
openDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -298,9 +305,9 @@ export default defineComponent({
|
|||||||
reloadPage,
|
reloadPage,
|
||||||
drawerSetting,
|
drawerSetting,
|
||||||
openSetting,
|
openSetting,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -311,7 +318,7 @@ export default defineComponent({
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
height: @header-height;
|
height: @header-height;
|
||||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||||
transition: all .2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
//color: #fff;
|
//color: #fff;
|
||||||
@@ -369,7 +376,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: hsla(0, 0%, 100%, .08);
|
background: hsla(0, 0%, 100%, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
@@ -389,7 +396,7 @@ export default defineComponent({
|
|||||||
color: #515a6e;
|
color: #515a6e;
|
||||||
|
|
||||||
.n-icon {
|
.n-icon {
|
||||||
color: #515a6e
|
color: #515a6e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-header-left {
|
.layout-header-left {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import Logo from './index.vue'
|
import Logo from './index.vue';
|
||||||
|
|
||||||
export { Logo }
|
export { Logo };
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export default {
|
|||||||
name: 'Index',
|
name: 'Index',
|
||||||
props: {
|
props: {
|
||||||
collapsed: {
|
collapsed: {
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import MainView from './index.vue'
|
import MainView from './index.vue';
|
||||||
|
|
||||||
export { MainView }
|
export { MainView };
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent, computed } from 'vue'
|
import { defineComponent, computed } from 'vue';
|
||||||
import { useAsyncRouteStore } from '@/store/modules/asyncRoute'
|
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'MainView',
|
name: 'MainView',
|
||||||
@@ -21,23 +21,22 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
notNeedKey: {
|
notNeedKey: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
},
|
},
|
||||||
animate: {
|
animate: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const asyncRouteStore = useAsyncRouteStore()
|
const asyncRouteStore = useAsyncRouteStore();
|
||||||
// 需要缓存的路由组件
|
// 需要缓存的路由组件
|
||||||
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents)
|
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
|
||||||
return {
|
return {
|
||||||
keepAliveComponents
|
keepAliveComponents,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped></style>
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import AsideMenu from './index.vue'
|
import AsideMenu from './index.vue';
|
||||||
|
|
||||||
export { AsideMenu }
|
export { AsideMenu };
|
||||||
|
|||||||
@@ -6,20 +6,20 @@
|
|||||||
:collapsed="collapsed"
|
:collapsed="collapsed"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:collapsed-icon-size="20"
|
:collapsed-icon-size="20"
|
||||||
|
:indent="28"
|
||||||
:expanded-keys="openKeys"
|
:expanded-keys="openKeys"
|
||||||
v-model:value="selectedKeys"
|
v-model:value="selectedKeys"
|
||||||
@update:value="clickMenuItem"
|
@update:value="clickMenuItem"
|
||||||
@update:expanded-keys="menuExpanded"
|
@update:expanded-keys="menuExpanded"
|
||||||
>
|
/>
|
||||||
</NMenu>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, computed, watch, toRefs, ref } from 'vue'
|
import { defineComponent, reactive, computed, watch, toRefs, unref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useAsyncRouteStore } from '@/store/modules/asyncRoute'
|
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
||||||
import { generatorMenu } from '@/utils/index'
|
import { generatorMenu } from '@/utils/index';
|
||||||
import { useProjectSettingStore } from "@/store/modules/projectSetting";
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Menu',
|
name: 'Menu',
|
||||||
@@ -28,84 +28,93 @@ export default defineComponent({
|
|||||||
mode: {
|
mode: {
|
||||||
// 菜单模式
|
// 菜单模式
|
||||||
type: String,
|
type: String,
|
||||||
default: 'vertical'
|
default: 'vertical',
|
||||||
},
|
},
|
||||||
collapsed: {
|
collapsed: {
|
||||||
// 侧边栏菜单是否收起
|
// 侧边栏菜单是否收起
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// 当前路由
|
// 当前路由
|
||||||
const currentRoute = useRoute()
|
const currentRoute = useRoute();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const asyncRouteStore = useAsyncRouteStore()
|
const asyncRouteStore = useAsyncRouteStore();
|
||||||
const settingStore = useProjectSettingStore()
|
const settingStore = useProjectSettingStore();
|
||||||
const { mode } = props
|
|
||||||
|
|
||||||
// 获取当前打开的子菜单
|
// 获取当前打开的子菜单
|
||||||
const matched = currentRoute.matched
|
const matched = currentRoute.matched;
|
||||||
|
|
||||||
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : []
|
const getOpenKeys = matched && matched.length ? matched.map((item) => item.name) : [];
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
openKeys: getOpenKeys,
|
openKeys: getOpenKeys,
|
||||||
selectedKeys: currentRoute.name,
|
selectedKeys: currentRoute.name,
|
||||||
})
|
});
|
||||||
|
|
||||||
const inverted = computed(() => {
|
const inverted = computed(() => {
|
||||||
return ['dark', 'header-dark'].includes(settingStore.navTheme)
|
return ['dark', 'header-dark'].includes(settingStore.navTheme);
|
||||||
})
|
});
|
||||||
|
|
||||||
const menus = computed(() => {
|
const menus = computed(() => {
|
||||||
return generatorMenu(asyncRouteStore.getMenus)
|
return generatorMenu(asyncRouteStore.getMenus);
|
||||||
})
|
});
|
||||||
|
|
||||||
// 监听菜单收缩状态
|
// 监听菜单收缩状态
|
||||||
watch(
|
watch(
|
||||||
() => props.collapsed,
|
() => props.collapsed,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
state.openKeys = newVal ? [] : getOpenKeys
|
state.openKeys = newVal ? [] : getOpenKeys;
|
||||||
state.selectedKeys = currentRoute.name
|
state.selectedKeys = currentRoute.name;
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
// 跟随页面路由变化,切换菜单选中状态
|
// 跟随页面路由变化,切换菜单选中状态
|
||||||
watch(
|
watch(
|
||||||
() => currentRoute.fullPath,
|
() => currentRoute.fullPath,
|
||||||
() => {
|
() => {
|
||||||
const matched = currentRoute.matched
|
const matched = currentRoute.matched;
|
||||||
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : []
|
state.openKeys = matched.map((item) => item.name);
|
||||||
state.openKeys = getOpenKeys
|
state.selectedKeys = currentRoute.name;
|
||||||
state.selectedKeys = currentRoute.name
|
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
// 点击菜单
|
// 点击菜单
|
||||||
function clickMenuItem(key: string) {
|
function clickMenuItem(key: string) {
|
||||||
if (/http(s)?:/.test(key)) {
|
if (/http(s)?:/.test(key)) {
|
||||||
window.open(key)
|
window.open(key);
|
||||||
} else {
|
} else {
|
||||||
router.push({ name: key })
|
router.push({ name: key });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//展开菜单
|
//展开菜单
|
||||||
function menuExpanded(openKeys: string[]) {
|
function menuExpanded(openKeys: string[]) {
|
||||||
console.log(openKeys)
|
if (!openKeys) return;
|
||||||
if (!openKeys) return
|
const latestOpenKey = openKeys.find((key) => state.openKeys.indexOf(key) === -1);
|
||||||
const latestOpenKey = openKeys.pop();
|
const isExistChildren = findChildrenLen(latestOpenKey as string);
|
||||||
state.openKeys = latestOpenKey ? [latestOpenKey] : []
|
state.openKeys = isExistChildren ? (latestOpenKey ? [latestOpenKey] : []) : openKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
//查找是否存在子路由
|
||||||
|
function findChildrenLen(key: string) {
|
||||||
|
if (!key) return false;
|
||||||
|
const subRouteChildren: string[] = [];
|
||||||
|
for (const { children, key } of unref(menus)) {
|
||||||
|
if (children && children.length > 0) {
|
||||||
|
subRouteChildren.push(key as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subRouteChildren.includes(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
inverted,
|
inverted,
|
||||||
menus,
|
menus,
|
||||||
mode,
|
|
||||||
clickMenuItem,
|
clickMenuItem,
|
||||||
menuExpanded
|
menuExpanded,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
VerticalRightOutlined,
|
VerticalRightOutlined,
|
||||||
VerticalLeftOutlined,
|
VerticalLeftOutlined,
|
||||||
ColumnWidthOutlined,
|
ColumnWidthOutlined,
|
||||||
MinusOutlined
|
MinusOutlined,
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue';
|
||||||
import { Dropdown, Tabs, Card } from 'ant-design-vue'
|
import { Dropdown, Tabs, Card } from 'ant-design-vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
[Tabs.name]: Tabs,
|
[Tabs.name]: Tabs,
|
||||||
@@ -20,5 +20,5 @@ export default {
|
|||||||
CloseOutlined,
|
CloseOutlined,
|
||||||
VerticalRightOutlined,
|
VerticalRightOutlined,
|
||||||
VerticalLeftOutlined,
|
VerticalLeftOutlined,
|
||||||
ColumnWidthOutlined
|
ColumnWidthOutlined,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import TabsView from './index.vue'
|
import TabsView from './index.vue';
|
||||||
|
|
||||||
export { TabsView }
|
export { TabsView };
|
||||||
|
|||||||
@@ -1,33 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tabs-view"
|
<div
|
||||||
|
class="tabs-view"
|
||||||
:class="{
|
:class="{
|
||||||
'tabs-view-fix': multiTabsSetting.fixed,
|
'tabs-view-fix': multiTabsSetting.fixed,
|
||||||
'tabs-view-fixed-header': isMultiHeaderFixed,
|
'tabs-view-fixed-header': isMultiHeaderFixed,
|
||||||
'tabs-view-default-background':getDarkTheme === false
|
'tabs-view-default-background': getDarkTheme === false,
|
||||||
}"
|
}"
|
||||||
:style="getChangeStyle">
|
:style="getChangeStyle"
|
||||||
|
>
|
||||||
<div class="tabs-view-main">
|
<div class="tabs-view-main">
|
||||||
<div ref="navWrap" class="tabs-card" :class="{ 'tabs-card-scrollable': scrollable }">
|
<div ref="navWrap" class="tabs-card" :class="{ 'tabs-card-scrollable': scrollable }">
|
||||||
<span class="tabs-card-prev" :class="{'tabs-card-prev-hide': !scrollable }" @click="scrollPrev">
|
<span
|
||||||
|
class="tabs-card-prev"
|
||||||
|
:class="{ 'tabs-card-prev-hide': !scrollable }"
|
||||||
|
@click="scrollPrev"
|
||||||
|
>
|
||||||
<n-icon size="16" color="#515a6e">
|
<n-icon size="16" color="#515a6e">
|
||||||
<LeftOutlined />
|
<LeftOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</span>
|
</span>
|
||||||
<span class="tabs-card-next" :class="{'tabs-card-next-hide': !scrollable }" @click="scrollNext">
|
<span
|
||||||
|
class="tabs-card-next"
|
||||||
|
:class="{ 'tabs-card-next-hide': !scrollable }"
|
||||||
|
@click="scrollNext"
|
||||||
|
>
|
||||||
<n-icon size="16" color="#515a6e">
|
<n-icon size="16" color="#515a6e">
|
||||||
<RightOutlined />
|
<RightOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</span>
|
</span>
|
||||||
<div ref="navScroll" class="tabs-card-scroll">
|
<div ref="navScroll" class="tabs-card-scroll">
|
||||||
<div ref="navRef" class="tabs-card-nav" :style="getNavStyle">
|
<div ref="navRef" class="tabs-card-nav" :style="getNavStyle">
|
||||||
<Draggable :list="tabsList" animation="300" item-key="fullPath">
|
<Draggable :list="tabsList" animation="300" item-key="fullPath" class="flex">
|
||||||
<template #item="{ element }">
|
<template #item="{ element }">
|
||||||
<div class="tabs-card-scroll-item"
|
<div
|
||||||
|
class="tabs-card-scroll-item"
|
||||||
:class="{ 'active-item': activeKey === element.path }"
|
:class="{ 'active-item': activeKey === element.path }"
|
||||||
@click.stop="goPage(element)"
|
@click.stop="goPage(element)"
|
||||||
@contextmenu="handleContextMenu">
|
@contextmenu="handleContextMenu($event, element)"
|
||||||
|
>
|
||||||
<span>{{ element.meta.title }}</span>
|
<span>{{ element.meta.title }}</span>
|
||||||
<n-icon size="14" @click.stop="closeTabItem(element)">
|
<n-icon
|
||||||
|
size="14"
|
||||||
|
@click.stop="closeTabItem(element)"
|
||||||
|
v-if="element.path != baseHome"
|
||||||
|
>
|
||||||
<CloseOutlined />
|
<CloseOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,7 +53,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabs-close">
|
<div class="tabs-close">
|
||||||
<n-dropdown trigger="hover" @select="closeHandleSelect" placement="bottom-end" :options="TabsMenuOptions">
|
<n-dropdown
|
||||||
|
trigger="hover"
|
||||||
|
@select="closeHandleSelect"
|
||||||
|
placement="bottom-end"
|
||||||
|
:options="TabsMenuOptions"
|
||||||
|
>
|
||||||
<div class="tabs-close-btn" @click.prevent>
|
<div class="tabs-close-btn" @click.prevent>
|
||||||
<n-icon size="16" color="#515a6e">
|
<n-icon size="16" color="#515a6e">
|
||||||
<DownOutlined />
|
<DownOutlined />
|
||||||
@@ -45,8 +66,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<n-dropdown :show="showDropdown" :x="dropdownX" :y="dropdownY" @clickoutside="onClickOutside"
|
<n-dropdown
|
||||||
placement="bottom-start" @select="closeHandleSelect" :options="TabsMenuOptions"/>
|
:show="showDropdown"
|
||||||
|
:x="dropdownX"
|
||||||
|
:y="dropdownY"
|
||||||
|
@clickoutside="onClickOutside"
|
||||||
|
placement="bottom-start"
|
||||||
|
@select="closeHandleSelect"
|
||||||
|
:options="TabsMenuOptions"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -63,18 +91,19 @@ import {
|
|||||||
provide,
|
provide,
|
||||||
watch,
|
watch,
|
||||||
onMounted,
|
onMounted,
|
||||||
nextTick
|
nextTick,
|
||||||
} from 'vue'
|
} from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { storage } from '@/utils/Storage'
|
import { storage } from '@/utils/Storage';
|
||||||
import { TABS_ROUTES } from '@/store/mutation-types'
|
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||||
import { useTabsViewStore } from '@/store/modules/tabsView'
|
import { useTabsViewStore } from '@/store/modules/tabsView';
|
||||||
import { useAsyncRouteStore } from '@/store/modules/asyncRoute'
|
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
||||||
import { RouteItem } from '@/store/modules/tabsView'
|
import { RouteItem } from '@/store/modules/tabsView';
|
||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting'
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage } from 'naive-ui';
|
||||||
import Draggable from 'vuedraggable/src/vuedraggable'
|
// @ts-ignore
|
||||||
import { PageEnum } from '@/enums/pageEnum'
|
import Draggable from 'vuedraggable/src/vuedraggable';
|
||||||
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
import {
|
import {
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
@@ -82,334 +111,347 @@ import {
|
|||||||
ColumnWidthOutlined,
|
ColumnWidthOutlined,
|
||||||
MinusOutlined,
|
MinusOutlined,
|
||||||
LeftOutlined,
|
LeftOutlined,
|
||||||
RightOutlined
|
RightOutlined,
|
||||||
} from '@vicons/antd'
|
} from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index'
|
import { renderIcon } from '@/utils/index';
|
||||||
import elementResizeDetectorMaker from 'element-resize-detector'
|
import elementResizeDetectorMaker from 'element-resize-detector';
|
||||||
import { useProjectSettingStore } from "@/store/modules/projectSetting";
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TabsView',
|
name: 'TabsView',
|
||||||
components: {
|
components: {
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
ReloadOutlined,
|
|
||||||
CloseOutlined,
|
CloseOutlined,
|
||||||
ColumnWidthOutlined,
|
|
||||||
MinusOutlined,
|
|
||||||
LeftOutlined,
|
LeftOutlined,
|
||||||
RightOutlined,
|
RightOutlined,
|
||||||
Draggable
|
Draggable,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
collapsed: {
|
collapsed: {
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { getDarkTheme } = useDesignSetting()
|
const { getDarkTheme } = useDesignSetting();
|
||||||
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } = useProjectSetting()
|
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } =
|
||||||
|
useProjectSetting();
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage();
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const tabsViewStore = useTabsViewStore()
|
const tabsViewStore = useTabsViewStore();
|
||||||
const asyncRouteStore = useAsyncRouteStore()
|
const asyncRouteStore = useAsyncRouteStore();
|
||||||
const navRef: any = ref(null)
|
const navRef: any = ref(null);
|
||||||
const navScroll: any = ref(null)
|
const navScroll: any = ref(null);
|
||||||
const navWrap: any = ref(null)
|
const navWrap: any = ref(null);
|
||||||
|
const isCurrent = ref(false);
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
activeKey: route.fullPath,
|
activeKey: route.fullPath,
|
||||||
scrollable: false,
|
scrollable: false,
|
||||||
navStyle: {
|
navStyle: {
|
||||||
transform: ''
|
transform: '',
|
||||||
},
|
},
|
||||||
dropdownX: 0,
|
dropdownX: 0,
|
||||||
dropdownY: 0,
|
dropdownY: 0,
|
||||||
showDropdown: false,
|
showDropdown: false,
|
||||||
isMultiHeaderFixed: false,
|
isMultiHeaderFixed: false,
|
||||||
multiTabsSetting: getMultiTabsSetting,
|
multiTabsSetting: getMultiTabsSetting,
|
||||||
})
|
});
|
||||||
|
|
||||||
// 获取简易的路由对象
|
// 获取简易的路由对象
|
||||||
const getSimpleRoute = (route): RouteItem => {
|
const getSimpleRoute = (route): RouteItem => {
|
||||||
const { fullPath, hash, meta, name, params, path, query } = route
|
const { fullPath, hash, meta, name, params, path, query } = route;
|
||||||
return { fullPath, hash, meta, name, params, path, query }
|
return { fullPath, hash, meta, name, params, path, query };
|
||||||
}
|
};
|
||||||
|
|
||||||
//动态组装样式 菜单缩进
|
//动态组装样式 菜单缩进
|
||||||
const getChangeStyle = computed(() => {
|
const getChangeStyle = computed(() => {
|
||||||
const { collapsed } = props
|
const { collapsed } = props;
|
||||||
const navMode = unref(getNavMode)
|
const navMode = unref(getNavMode);
|
||||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting)
|
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
||||||
const { fixed }: any = unref(getMultiTabsSetting)
|
const { fixed }: any = unref(getMultiTabsSetting);
|
||||||
let lenNum = navMode === 'horizontal' ? '0px' : collapsed ? `${ minMenuWidth }px` : `${ menuWidth }px`
|
let lenNum =
|
||||||
|
navMode === 'horizontal' ? '0px' : collapsed ? `${minMenuWidth}px` : `${menuWidth}px`;
|
||||||
return {
|
return {
|
||||||
left: lenNum,
|
left: lenNum,
|
||||||
width: `calc(100% - ${ !fixed ? '0px' : lenNum })`
|
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
//tags 右侧下拉菜单
|
//tags 右侧下拉菜单
|
||||||
const TabsMenuOptions = [
|
const TabsMenuOptions = computed(() => {
|
||||||
|
const isDisabled = unref(tabsList).length <= 1 ? true : false;
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
label: '刷新当前',
|
label: '刷新当前',
|
||||||
key: '1',
|
key: '1',
|
||||||
icon: renderIcon(ReloadOutlined)
|
icon: renderIcon(ReloadOutlined),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '关闭当前',
|
label: `关闭当前`,
|
||||||
key: '2',
|
key: '2',
|
||||||
icon: renderIcon(CloseOutlined)
|
disabled: unref(isCurrent) || isDisabled,
|
||||||
|
icon: renderIcon(CloseOutlined),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '关闭其他',
|
label: '关闭其他',
|
||||||
key: '3',
|
key: '3',
|
||||||
icon: renderIcon(ColumnWidthOutlined)
|
disabled: isDisabled,
|
||||||
|
icon: renderIcon(ColumnWidthOutlined),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '关闭全部',
|
label: '关闭全部',
|
||||||
key: '4',
|
key: '4',
|
||||||
icon: renderIcon(MinusOutlined)
|
disabled: isDisabled,
|
||||||
}
|
icon: renderIcon(MinusOutlined),
|
||||||
]
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
let routes: RouteItem[] = []
|
let routes: RouteItem[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const routesStr = storage.get(TABS_ROUTES) as string | null | undefined
|
const routesStr = storage.get(TABS_ROUTES) as string | null | undefined;
|
||||||
routes = routesStr ? JSON.parse(routesStr) : [getSimpleRoute(route)]
|
routes = routesStr ? JSON.parse(routesStr) : [getSimpleRoute(route)];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
routes = [getSimpleRoute(route)]
|
routes = [getSimpleRoute(route)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化标签页
|
// 初始化标签页
|
||||||
tabsViewStore.initTabs(routes)
|
tabsViewStore.initTabs(routes);
|
||||||
|
|
||||||
|
|
||||||
//监听滚动条
|
//监听滚动条
|
||||||
function onScroll(e) {
|
function onScroll(e) {
|
||||||
let scrollTop = e.target.scrollTop || (document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop); // 滚动条偏移量
|
let scrollTop =
|
||||||
|
e.target.scrollTop ||
|
||||||
|
document.documentElement.scrollTop ||
|
||||||
|
window.pageYOffset ||
|
||||||
|
document.body.scrollTop; // 滚动条偏移量
|
||||||
if (!getHeaderSetting.fixed && getMultiTabsSetting.fixed && scrollTop >= 64) {
|
if (!getHeaderSetting.fixed && getMultiTabsSetting.fixed && scrollTop >= 64) {
|
||||||
state.isMultiHeaderFixed = true
|
state.isMultiHeaderFixed = true;
|
||||||
} else {
|
} else {
|
||||||
state.isMultiHeaderFixed = false
|
state.isMultiHeaderFixed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('scroll', onScroll, true)
|
window.addEventListener('scroll', onScroll, true);
|
||||||
|
|
||||||
// 移除缓存组件名称
|
// 移除缓存组件名称
|
||||||
const delKeepAliveCompName = () => {
|
const delKeepAliveCompName = () => {
|
||||||
if (route.meta.keepAlive) {
|
if (route.meta.keepAlive) {
|
||||||
const name = router.currentRoute.value.matched.find((item) => item.name == route.name)
|
const name = router.currentRoute.value.matched.find((item) => item.name == route.name)
|
||||||
?.components?.default.name
|
?.components?.default.name;
|
||||||
if (name) {
|
if (name) {
|
||||||
asyncRouteStore.keepAliveComponents = asyncRouteStore.keepAliveComponents.filter(
|
asyncRouteStore.keepAliveComponents = asyncRouteStore.keepAliveComponents.filter(
|
||||||
(item) => item != name
|
(item) => item != name
|
||||||
)
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 标签页列表
|
// 标签页列表
|
||||||
const tabsList: any = computed(() => tabsViewStore.tabsList)
|
const tabsList: any = computed(() => tabsViewStore.tabsList);
|
||||||
const whiteList: string[] = [PageEnum.BASE_LOGIN_NAME, PageEnum.REDIRECT_NAME, PageEnum.ERROR_PAGE_NAME]
|
const whiteList: string[] = [
|
||||||
|
PageEnum.BASE_LOGIN_NAME,
|
||||||
|
PageEnum.REDIRECT_NAME,
|
||||||
|
PageEnum.ERROR_PAGE_NAME,
|
||||||
|
];
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.fullPath,
|
() => route.fullPath,
|
||||||
(to) => {
|
(to) => {
|
||||||
if (whiteList.includes(route.name as string)) return
|
if (whiteList.includes(route.name as string)) return;
|
||||||
state.activeKey = to
|
state.activeKey = to;
|
||||||
tabsViewStore.addTabs(getSimpleRoute(route))
|
tabsViewStore.addTabs(getSimpleRoute(route));
|
||||||
updateNavScroll()
|
updateNavScroll();
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
);
|
||||||
|
|
||||||
// 在页面关闭或刷新之前,保存数据
|
// 在页面关闭或刷新之前,保存数据
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
storage.set(TABS_ROUTES, JSON.stringify(tabsList.value))
|
storage.set(TABS_ROUTES, JSON.stringify(tabsList.value));
|
||||||
})
|
});
|
||||||
|
|
||||||
// 关闭当前页面
|
// 关闭当前页面
|
||||||
const removeTab = (route) => {
|
const removeTab = (route) => {
|
||||||
if (tabsList.value.length === 1) {
|
if (tabsList.value.length === 1) {
|
||||||
return message.warning('这已经是最后一页,不能再关闭了!')
|
return message.warning('这已经是最后一页,不能再关闭了!');
|
||||||
}
|
}
|
||||||
delKeepAliveCompName()
|
delKeepAliveCompName();
|
||||||
tabsViewStore.closeCurrentTab(route)
|
tabsViewStore.closeCurrentTab(route);
|
||||||
// 如果关闭的是当前页
|
// 如果关闭的是当前页
|
||||||
if (state.activeKey === route.fullPath) {
|
if (state.activeKey === route.fullPath) {
|
||||||
const currentRoute = tabsList.value[Math.max(0, tabsList.value.length - 1)]
|
const currentRoute = tabsList.value[Math.max(0, tabsList.value.length - 1)];
|
||||||
state.activeKey = currentRoute.fullPath
|
state.activeKey = currentRoute.fullPath;
|
||||||
router.push(currentRoute)
|
router.push(currentRoute);
|
||||||
}
|
|
||||||
updateNavScroll()
|
|
||||||
}
|
}
|
||||||
|
updateNavScroll();
|
||||||
|
};
|
||||||
|
|
||||||
// 刷新页面
|
// 刷新页面
|
||||||
const reloadPage = () => {
|
const reloadPage = () => {
|
||||||
delKeepAliveCompName()
|
delKeepAliveCompName();
|
||||||
router.push({
|
router.push({
|
||||||
path: '/redirect' + unref(route).fullPath
|
path: '/redirect' + unref(route).fullPath,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// 注入刷新页面方法
|
// 注入刷新页面方法
|
||||||
provide('reloadPage', reloadPage)
|
provide('reloadPage', reloadPage);
|
||||||
|
|
||||||
// 关闭左侧
|
// 关闭左侧
|
||||||
const closeLeft = (route) => {
|
const closeLeft = (route) => {
|
||||||
tabsViewStore.closeLeftTabs(route)
|
tabsViewStore.closeLeftTabs(route);
|
||||||
state.activeKey = route.fullPath
|
state.activeKey = route.fullPath;
|
||||||
router.replace(route.fullPath)
|
router.replace(route.fullPath);
|
||||||
updateNavScroll()
|
updateNavScroll();
|
||||||
}
|
};
|
||||||
|
|
||||||
// 关闭右侧
|
// 关闭右侧
|
||||||
const closeRight = (route) => {
|
const closeRight = (route) => {
|
||||||
tabsViewStore.closeRightTabs(route)
|
tabsViewStore.closeRightTabs(route);
|
||||||
state.activeKey = route.fullPath
|
state.activeKey = route.fullPath;
|
||||||
router.replace(route.fullPath)
|
router.replace(route.fullPath);
|
||||||
updateNavScroll()
|
updateNavScroll();
|
||||||
}
|
};
|
||||||
|
|
||||||
// 关闭其他
|
// 关闭其他
|
||||||
const closeOther = (route) => {
|
const closeOther = (route) => {
|
||||||
tabsViewStore.closeOtherTabs(route)
|
tabsViewStore.closeOtherTabs(route);
|
||||||
state.activeKey = route.fullPath
|
state.activeKey = route.fullPath;
|
||||||
router.replace(route.fullPath)
|
router.replace(route.fullPath);
|
||||||
updateNavScroll()
|
updateNavScroll();
|
||||||
}
|
};
|
||||||
|
|
||||||
// 关闭全部
|
// 关闭全部
|
||||||
const closeAll = () => {
|
const closeAll = () => {
|
||||||
localStorage.removeItem('routes')
|
localStorage.removeItem('routes');
|
||||||
tabsViewStore.closeAllTabs()
|
tabsViewStore.closeAllTabs();
|
||||||
router.replace('/')
|
router.replace(PageEnum.BASE_HOME);
|
||||||
updateNavScroll()
|
updateNavScroll();
|
||||||
}
|
};
|
||||||
|
|
||||||
//tab 操作
|
//tab 操作
|
||||||
const closeHandleSelect = (key) => {
|
const closeHandleSelect = (key) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
//刷新
|
//刷新
|
||||||
case '1':
|
case '1':
|
||||||
reloadPage()
|
reloadPage();
|
||||||
break
|
break;
|
||||||
//关闭
|
//关闭
|
||||||
case '2':
|
case '2':
|
||||||
removeTab(route)
|
removeTab(route);
|
||||||
break
|
break;
|
||||||
//关闭其他
|
//关闭其他
|
||||||
case '3':
|
case '3':
|
||||||
closeOther(route)
|
closeOther(route);
|
||||||
break
|
break;
|
||||||
//关闭所有
|
//关闭所有
|
||||||
case '4':
|
case '4':
|
||||||
closeAll()
|
closeAll();
|
||||||
break
|
break;
|
||||||
}
|
|
||||||
updateNavScroll()
|
|
||||||
}
|
}
|
||||||
|
updateNavScroll();
|
||||||
|
};
|
||||||
|
|
||||||
function getCurrentScrollOffset() {
|
function getCurrentScrollOffset() {
|
||||||
const { navStyle } = state
|
const { navStyle } = state;
|
||||||
const transform: any = toRaw(navStyle.transform)
|
const transform: any = toRaw(navStyle.transform);
|
||||||
return transform ? Number(transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1]) : 0
|
return transform ? Number(transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1]) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOffset(value) {
|
function setOffset(value) {
|
||||||
state.navStyle.transform = `translateX(-${ value }px)`
|
state.navStyle.transform = `translateX(-${value}px)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollPrev() {
|
function scrollPrev() {
|
||||||
const containerWidth = navScroll.value.offsetWidth
|
const containerWidth = navScroll.value.offsetWidth;
|
||||||
const currentOffset = getCurrentScrollOffset()
|
const currentOffset = getCurrentScrollOffset();
|
||||||
if (!currentOffset) return
|
if (!currentOffset) return;
|
||||||
let newOffset = currentOffset > containerWidth ? currentOffset - containerWidth : 0
|
let newOffset = currentOffset > containerWidth ? currentOffset - containerWidth : 0;
|
||||||
setOffset(newOffset)
|
setOffset(newOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollNext() {
|
function scrollNext() {
|
||||||
const navWidth = navRef.value.scrollWidth
|
const navWidth = navRef.value.scrollWidth;
|
||||||
const containerWidth = navScroll.value.offsetWidth
|
const containerWidth = navScroll.value.offsetWidth;
|
||||||
const currentOffset = getCurrentScrollOffset()
|
const currentOffset = getCurrentScrollOffset();
|
||||||
if (navWidth - currentOffset <= containerWidth) return
|
if (navWidth - currentOffset <= containerWidth) return;
|
||||||
|
|
||||||
let newOffset =
|
let newOffset =
|
||||||
navWidth - currentOffset > containerWidth * 2
|
navWidth - currentOffset > containerWidth * 2
|
||||||
? currentOffset + containerWidth
|
? currentOffset + containerWidth
|
||||||
: navWidth - containerWidth
|
: navWidth - containerWidth;
|
||||||
|
|
||||||
setOffset(newOffset)
|
setOffset(newOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNavScroll() {
|
function updateNavScroll() {
|
||||||
if (!navRef.value) return
|
if (!navRef.value) return;
|
||||||
let navWidth = navRef.value.scrollWidth
|
let navWidth = navRef.value.scrollWidth;
|
||||||
let containerWidth = navScroll.value.offsetWidth
|
let containerWidth = navScroll.value.offsetWidth;
|
||||||
const currentOffset = getCurrentScrollOffset()
|
const currentOffset = getCurrentScrollOffset();
|
||||||
if (containerWidth < navWidth) {
|
if (containerWidth < navWidth) {
|
||||||
state.scrollable = true
|
state.scrollable = true;
|
||||||
if (navWidth - currentOffset < containerWidth) {
|
if (navWidth - currentOffset < containerWidth) {
|
||||||
setOffset(navWidth - containerWidth)
|
setOffset(navWidth - containerWidth);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.scrollable = false
|
state.scrollable = false;
|
||||||
if (currentOffset > 0) {
|
if (currentOffset > 0) {
|
||||||
setOffset(0)
|
setOffset(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
updateNavScroll()
|
updateNavScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNavStyle = computed(() => {
|
const getNavStyle = computed(() => {
|
||||||
return state.navStyle
|
return state.navStyle;
|
||||||
})
|
});
|
||||||
|
|
||||||
function handleContextMenu(e) {
|
function handleContextMenu(e, item) {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
state.showDropdown = false
|
isCurrent.value = PageEnum.BASE_HOME_REDIRECT === item.path;
|
||||||
|
state.showDropdown = false;
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
state.showDropdown = true
|
state.showDropdown = true;
|
||||||
state.dropdownX = e.clientX
|
state.dropdownX = e.clientX;
|
||||||
state.dropdownY = e.clientY
|
state.dropdownY = e.clientY;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickOutside() {
|
function onClickOutside() {
|
||||||
state.showDropdown = false
|
state.showDropdown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//tags 跳转页面
|
//tags 跳转页面
|
||||||
function goPage(e) {
|
function goPage(e) {
|
||||||
const { fullPath } = e
|
const { fullPath } = e;
|
||||||
if (fullPath === route.fullPath) return
|
if (fullPath === route.fullPath) return;
|
||||||
state.activeKey = fullPath
|
state.activeKey = fullPath;
|
||||||
router.push({ path: fullPath })
|
router.push({ path: fullPath });
|
||||||
}
|
}
|
||||||
|
|
||||||
//删除tab
|
//删除tab
|
||||||
function closeTabItem(e) {
|
function closeTabItem(e) {
|
||||||
const { fullPath } = e
|
const { fullPath } = e;
|
||||||
const routeInfo = tabsList.value.find((item) => item.fullPath == fullPath)
|
const routeInfo = tabsList.value.find((item) => item.fullPath == fullPath);
|
||||||
removeTab(routeInfo)
|
removeTab(routeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onElementResize()
|
onElementResize();
|
||||||
})
|
});
|
||||||
|
|
||||||
function onElementResize() {
|
function onElementResize() {
|
||||||
let observer
|
let observer;
|
||||||
observer = elementResizeDetectorMaker()
|
observer = elementResizeDetectorMaker();
|
||||||
observer.listenTo(navWrap.value, handleResize)
|
observer.listenTo(navWrap.value, handleResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -419,6 +461,7 @@ export default defineComponent({
|
|||||||
navScroll,
|
navScroll,
|
||||||
route,
|
route,
|
||||||
tabsList,
|
tabsList,
|
||||||
|
baseHome: PageEnum.BASE_HOME_REDIRECT,
|
||||||
goPage,
|
goPage,
|
||||||
closeTabItem,
|
closeTabItem,
|
||||||
closeLeft,
|
closeLeft,
|
||||||
@@ -434,10 +477,10 @@ export default defineComponent({
|
|||||||
getNavStyle,
|
getNavStyle,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
getDarkTheme
|
getDarkTheme,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -545,7 +588,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.active-item {
|
.active-item {
|
||||||
color: #2d8cf0
|
color: #2d8cf0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<NLayout class="layout" :position="fixedMenu" has-sider>
|
<NLayout class="layout" :position="fixedMenu" has-sider>
|
||||||
|
|
||||||
<NLayoutSider
|
<NLayoutSider
|
||||||
v-if="navMode === 'vertical'"
|
v-if="navMode === 'vertical'"
|
||||||
show-trigger
|
show-trigger
|
||||||
@@ -12,48 +11,60 @@
|
|||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:width="leftMenuWidth"
|
:width="leftMenuWidth"
|
||||||
:native-scrollbar="false"
|
:native-scrollbar="false"
|
||||||
:inverted="inverted" class="layout-sider">
|
:inverted="inverted"
|
||||||
|
class="layout-sider"
|
||||||
|
>
|
||||||
<Logo :collapsed="collapsed" />
|
<Logo :collapsed="collapsed" />
|
||||||
<AsideMenu v-model:collapsed="collapsed" />
|
<AsideMenu v-model:collapsed="collapsed" />
|
||||||
</NLayoutSider>
|
</NLayoutSider>
|
||||||
|
|
||||||
<NLayout :inverted="inverted">
|
<NLayout :inverted="inverted">
|
||||||
|
|
||||||
<NLayoutHeader :inverted="inverted" :position="fixedHeader">
|
<NLayoutHeader :inverted="inverted" :position="fixedHeader">
|
||||||
<PageHeader v-model:collapsed="collapsed" />
|
<PageHeader v-model:collapsed="collapsed" />
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
|
|
||||||
<NLayoutContent class="layout-content" :class="{'layout-default-background':getDarkTheme === false}">
|
<NLayoutContent
|
||||||
|
class="layout-content"
|
||||||
<div class="layout-content-main"
|
:class="{ 'layout-default-background': getDarkTheme === false }"
|
||||||
:class="{'layout-content-main-fix':fixedMulti,'fluid-header':fixedHeader === 'static'}">
|
>
|
||||||
|
<div
|
||||||
|
class="layout-content-main"
|
||||||
|
:class="{
|
||||||
|
'layout-content-main-fix': fixedMulti,
|
||||||
|
'fluid-header': fixedHeader === 'static',
|
||||||
|
}"
|
||||||
|
>
|
||||||
<TabsView v-if="isMultiTabs" v-model:collapsed="collapsed" />
|
<TabsView v-if="isMultiTabs" v-model:collapsed="collapsed" />
|
||||||
<div class="main-view" :class="{'main-view-fix':fixedMulti,'noMultiTabs':!isMultiTabs,'mt-3':!isMultiTabs}">
|
<div
|
||||||
|
class="main-view"
|
||||||
|
:class="{
|
||||||
|
'main-view-fix': fixedMulti,
|
||||||
|
noMultiTabs: !isMultiTabs,
|
||||||
|
'mt-3': !isMultiTabs,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<MainView />
|
<MainView />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!--1.15废弃,没啥用,占用操作空间-->
|
||||||
<NLayoutFooter v-if="getShowFooter">
|
<!-- <NLayoutFooter v-if="getShowFooter">-->
|
||||||
<PageFooter/>
|
<!-- <PageFooter />-->
|
||||||
</NLayoutFooter>
|
<!-- </NLayoutFooter>-->
|
||||||
|
|
||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
|
|
||||||
</NLayout>
|
</NLayout>
|
||||||
|
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, unref, h, watch, computed, onMounted } from 'vue'
|
import { defineComponent, ref, unref, computed, onMounted } from 'vue';
|
||||||
import { Logo } from './components/Logo'
|
import { Logo } from './components/Logo';
|
||||||
import { TabsView } from './components/TagsView'
|
import { TabsView } from './components/TagsView';
|
||||||
import { MainView } from './components/Main'
|
import { MainView } from './components/Main';
|
||||||
import { AsideMenu } from './components/Menu'
|
import { AsideMenu } from './components/Menu';
|
||||||
import { PageHeader } from './components/Header'
|
import { PageHeader } from './components/Header';
|
||||||
import { PageFooter } from './components/Footer'
|
import { PageFooter } from './components/Footer';
|
||||||
import { useProjectSetting } from "@/hooks/setting/useProjectSetting";
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
import { useDesignSetting } from "@/hooks/setting/useDesignSetting";
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Layout',
|
name: 'Layout',
|
||||||
@@ -63,10 +74,10 @@ export default defineComponent({
|
|||||||
PageHeader,
|
PageHeader,
|
||||||
AsideMenu,
|
AsideMenu,
|
||||||
Logo,
|
Logo,
|
||||||
PageFooter
|
PageFooter,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { getDarkTheme } = useDesignSetting()
|
const { getDarkTheme } = useDesignSetting();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getShowFooter,
|
getShowFooter,
|
||||||
@@ -74,57 +85,57 @@ export default defineComponent({
|
|||||||
getNavTheme,
|
getNavTheme,
|
||||||
getHeaderSetting,
|
getHeaderSetting,
|
||||||
getMenuSetting,
|
getMenuSetting,
|
||||||
getMultiTabsSetting
|
getMultiTabsSetting,
|
||||||
} = useProjectSetting()
|
} = useProjectSetting();
|
||||||
|
|
||||||
const navMode = getNavMode
|
const navMode = getNavMode;
|
||||||
|
|
||||||
const collapsed = ref<boolean>(false)
|
const collapsed = ref<boolean>(false);
|
||||||
|
|
||||||
const fixedHeader = computed(() => {
|
const fixedHeader = computed(() => {
|
||||||
const { fixed } = unref(getHeaderSetting)
|
const { fixed } = unref(getHeaderSetting);
|
||||||
return fixed ? 'absolute' : 'static'
|
return fixed ? 'absolute' : 'static';
|
||||||
})
|
});
|
||||||
|
|
||||||
const fixedMenu = computed(() => {
|
const fixedMenu = computed(() => {
|
||||||
const { fixed } = unref(getHeaderSetting)
|
const { fixed } = unref(getHeaderSetting);
|
||||||
return fixed ? 'absolute' : 'static'
|
return fixed ? 'absolute' : 'static';
|
||||||
})
|
});
|
||||||
|
|
||||||
const isMultiTabs = computed(() => {
|
const isMultiTabs = computed(() => {
|
||||||
return unref(getMultiTabsSetting).show
|
return unref(getMultiTabsSetting).show;
|
||||||
})
|
});
|
||||||
|
|
||||||
const fixedMulti = computed(() => {
|
const fixedMulti = computed(() => {
|
||||||
return unref(getMultiTabsSetting).fixed
|
return unref(getMultiTabsSetting).fixed;
|
||||||
})
|
});
|
||||||
|
|
||||||
const inverted = computed(() => {
|
const inverted = computed(() => {
|
||||||
return ['dark', 'header-dark'].includes(unref(getNavTheme))
|
return ['dark', 'header-dark'].includes(unref(getNavTheme));
|
||||||
})
|
});
|
||||||
|
|
||||||
const leftMenuWidth = computed(() => {
|
const leftMenuWidth = computed(() => {
|
||||||
const { minMenuWidth, menuWidth } = unref(getMenuSetting)
|
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
||||||
return collapsed.value ? minMenuWidth : menuWidth
|
return collapsed.value ? minMenuWidth : menuWidth;
|
||||||
})
|
});
|
||||||
|
|
||||||
const getChangeStyle = computed(() => {
|
const getChangeStyle = computed(() => {
|
||||||
const { minMenuWidth, menuWidth } = unref(getMenuSetting)
|
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
||||||
return {
|
return {
|
||||||
'padding-left': collapsed.value ? `${ minMenuWidth }px` : `${ menuWidth }px`
|
'padding-left': collapsed.value ? `${minMenuWidth}px` : `${menuWidth}px`,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
function watchWidth() {
|
function watchWidth() {
|
||||||
const Width = document.body.clientWidth
|
const Width = document.body.clientWidth;
|
||||||
if (Width <= 950) {
|
if (Width <= 950) {
|
||||||
collapsed.value = true
|
collapsed.value = true;
|
||||||
} else collapsed.value = false
|
} else collapsed.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', watchWidth);
|
window.addEventListener('resize', watchWidth);
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fixedMenu,
|
fixedMenu,
|
||||||
@@ -137,10 +148,10 @@ export default defineComponent({
|
|||||||
getChangeStyle,
|
getChangeStyle,
|
||||||
navMode,
|
navMode,
|
||||||
getShowFooter,
|
getShowFooter,
|
||||||
getDarkTheme
|
getDarkTheme,
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -158,7 +169,7 @@ export default defineComponent({
|
|||||||
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
|
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 13;
|
z-index: 13;
|
||||||
transition: all .2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-sider-fix {
|
.layout-sider-fix {
|
||||||
@@ -192,7 +203,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.layout-content-main {
|
.layout-content-main {
|
||||||
margin: 0 10px 10px;
|
margin: 0 10px 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
3
src/layout/parentLayout.vue
Normal file
3
src/layout/parentLayout.vue
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
42
src/main.ts
42
src/main.ts
@@ -1,34 +1,40 @@
|
|||||||
import './styles/tailwind.css'
|
import './styles/tailwind.css';
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue'
|
import App from './App.vue';
|
||||||
import router, { setupRouter } from './router'
|
import router, { setupRouter } from './router';
|
||||||
import { setupStore } from '@/store'
|
import { setupStore } from '@/store';
|
||||||
import MakeitCaptcha from 'makeit-captcha'
|
import MakeitCaptcha from 'makeit-captcha';
|
||||||
import 'makeit-captcha/dist/captcha.min.css'
|
import 'makeit-captcha/dist/captcha.min.css';
|
||||||
import { setupNaive, setupDirectives, setupGlobalMethods, setupCustomComponents } from '@/plugins'
|
import { setupNaive, setupDirectives } from '@/plugins';
|
||||||
|
import { AppProvider } from '@/components/Application';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = createApp(App)
|
const appProvider = createApp(AppProvider);
|
||||||
|
|
||||||
app.use(MakeitCaptcha)
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(MakeitCaptcha);
|
||||||
|
|
||||||
// 注册全局常用的 naive-ui 组件
|
// 注册全局常用的 naive-ui 组件
|
||||||
setupNaive(app)
|
setupNaive(app);
|
||||||
|
|
||||||
// 注册全局自定义组件,如:<svg-icon />
|
// 注册全局自定义组件
|
||||||
setupCustomComponents(app)
|
//setupCustomComponents();
|
||||||
|
|
||||||
// 注册全局自定义指令,如:v-permission权限指令
|
// 注册全局自定义指令,如:v-permission权限指令
|
||||||
setupDirectives(app)
|
setupDirectives(app);
|
||||||
|
|
||||||
// 注册全局方法,如:app.config.globalProperties.$message = message
|
// 注册全局方法,如:app.config.globalProperties.$message = message
|
||||||
setupGlobalMethods(app)
|
//setupGlobalMethods(app);
|
||||||
|
|
||||||
// 挂载状态管理
|
// 挂载状态管理
|
||||||
setupStore(app)
|
setupStore(app);
|
||||||
|
|
||||||
|
//优先挂载一下 Provider 解决路由守卫,Axios中可使用,Dialog,Message 等之类组件
|
||||||
|
appProvider.mount('#appProvider', true);
|
||||||
|
|
||||||
// 挂载路由
|
// 挂载路由
|
||||||
await setupRouter(app)
|
await setupRouter(app);
|
||||||
|
|
||||||
// 路由准备就绪后挂载APP实例
|
// 路由准备就绪后挂载APP实例
|
||||||
await router.isReady();
|
await router.isReady();
|
||||||
@@ -36,4 +42,4 @@ async function bootstrap() {
|
|||||||
app.mount('#app', true);
|
app.mount('#app', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bootstrap()
|
void bootstrap();
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { App } from 'vue'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局注册自定义组件 待完善
|
* 全局注册自定义组件 待完善
|
||||||
* @param app
|
* @param app
|
||||||
*/
|
*/
|
||||||
export function setupCustomComponents(app: App) {
|
export function setupCustomComponents() {
|
||||||
// app.component()
|
// app.component()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { App } from 'vue'
|
import { App } from 'vue';
|
||||||
|
|
||||||
import { permission } from '@/directives/permission'
|
import { permission } from '@/directives/permission';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册全局自定义指令
|
* 注册全局自定义指令
|
||||||
@@ -8,5 +8,5 @@ import { permission } from '@/directives/permission'
|
|||||||
*/
|
*/
|
||||||
export function setupDirectives(app: App) {
|
export function setupDirectives(app: App) {
|
||||||
// 权限控制指令(演示)
|
// 权限控制指令(演示)
|
||||||
app.directive('permission', permission)
|
app.directive('permission', permission);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { App } from 'vue'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册全局方法 待完善
|
* 注册全局方法 待完善
|
||||||
* @param app
|
* @param app
|
||||||
*/
|
*/
|
||||||
export function setupGlobalMethods(app: App) {
|
export function setupGlobalMethods() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { setupNaive } from '@/plugins/naive'
|
export { setupNaive } from '@/plugins/naive';
|
||||||
export { setupDirectives } from '@/plugins/directives'
|
export { setupDirectives } from '@/plugins/directives';
|
||||||
export { setupCustomComponents } from '@/plugins/customComponents'
|
export { setupCustomComponents } from '@/plugins/customComponents';
|
||||||
export { setupGlobalMethods } from '@/plugins/globalMethods'
|
export { setupGlobalMethods } from '@/plugins/globalMethods';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue';
|
||||||
import {
|
import {
|
||||||
create,
|
create,
|
||||||
NConfigProvider,
|
NConfigProvider,
|
||||||
@@ -60,8 +60,10 @@ import {
|
|||||||
NInputNumber,
|
NInputNumber,
|
||||||
NLoadingBarProvider,
|
NLoadingBarProvider,
|
||||||
NModal,
|
NModal,
|
||||||
NUpload
|
NUpload,
|
||||||
} from 'naive-ui'
|
NTree,
|
||||||
|
NSpin,
|
||||||
|
} from 'naive-ui';
|
||||||
|
|
||||||
const naive = create({
|
const naive = create({
|
||||||
components: [
|
components: [
|
||||||
@@ -124,10 +126,12 @@ const naive = create({
|
|||||||
NInputNumber,
|
NInputNumber,
|
||||||
NLoadingBarProvider,
|
NLoadingBarProvider,
|
||||||
NModal,
|
NModal,
|
||||||
NUpload
|
NUpload,
|
||||||
]
|
NTree,
|
||||||
})
|
NSpin,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export function setupNaive(app: App<Element>) {
|
export function setupNaive(app: App<Element>) {
|
||||||
app.use(naive)
|
app.use(naive);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export const ErrorPage = () => import('@/views/exception/404.vue');
|
|||||||
|
|
||||||
export const Layout = () => import('@/layout/index.vue');
|
export const Layout = () => import('@/layout/index.vue');
|
||||||
|
|
||||||
|
export const ParentLayout = () => import('@/layout/parentLayout.vue');
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { RouterView } from 'vue-router'
|
import { renderIcon } from '@/utils/index';
|
||||||
import { renderIcon } from '@/utils/index'
|
import { DashboardOutlined } from '@vicons/antd';
|
||||||
import { DashboardOutlined } from '@vicons/antd'
|
|
||||||
// import { RouterTransition } from '@/components/transition'
|
// import { RouterTransition } from '@/components/transition'
|
||||||
|
|
||||||
//前端路由映射表
|
//前端路由映射表
|
||||||
export const constantRouterComponents = {
|
export const constantRouterComponents = {
|
||||||
'Layout': () => import('@/layout/index.vue'), //布局
|
Layout: () => import('@/layout/index.vue'), //布局
|
||||||
'DashboardConsole': () => import('@/views/dashboard/console/console.vue'), // 主控台
|
DashboardConsole: () => import('@/views/dashboard/console/console.vue'), // 主控台
|
||||||
'DashboardMonitor': () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页
|
DashboardMonitor: () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页
|
||||||
'DashboardWorkplace': () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台
|
DashboardWorkplace: () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台
|
||||||
}
|
};
|
||||||
|
|
||||||
//前端路由图标映射表
|
//前端路由图标映射表
|
||||||
export const constantRouterIcon = {
|
export const constantRouterIcon = {
|
||||||
'DashboardOutlined': renderIcon(DashboardOutlined)
|
DashboardOutlined: renderIcon(DashboardOutlined),
|
||||||
}
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user