160 Commits

Author SHA1 Message Date
yuanzbz
10ac766489 feat: 更改配置环境文件 2024-01-03 19:40:19 +08:00
yuanzbz
d05ac59983 feat: 更改配置环境文件 2024-01-03 19:39:36 +08:00
yuanzbz
0793f9fb58 修复: 修复 git hooks配置 2023-12-24 11:50:28 +08:00
yuanzbz
7103c121f3 特性: 增加 husky 2023-12-22 17:46:27 +08:00
yuanzbz
3c1001dd76 x 2023-12-22 16:08:10 +08:00
yuanzbz
17f45c1c99 x 2023-12-22 16:07:21 +08:00
yuanzbz
3d80604f11 x 2023-12-22 16:05:51 +08:00
yuanzbz
66c0644019 x 2023-12-22 16:03:22 +08:00
yuanzbz
c9f6fe57fb x 2023-12-22 16:01:05 +08:00
yuanzbz
8113b0b11e x 2023-12-22 15:46:59 +08:00
yuanzbz
bab5825081 修复: 修复配置文件 2023-12-22 15:35:19 +08:00
yuanzbz
7c11af66a8 替换二维码 2023-12-06 22:09:56 +08:00
yuanzbz
a0de373666 修复: tsconfig.json 2023-12-06 22:07:22 +08:00
yuanzbz
39de7fe1ac 修复: 修复表格搜索问题 2023-11-16 21:49:36 +08:00
yuanzbz
3b0a68c739 Merge branch 'master' of https://github.com/zouzhibin/zb-admin
# Conflicts:
#	src/components/Table/PropTable/index.vue
2023-10-22 16:50:59 +08:00
yuanzbz
8fa9c3c4a2 修复: 修复表单 2023-10-22 16:25:30 +08:00
yuanzbz
1d57dd78b8 特性: 新增表单 2023-10-22 13:31:07 +08:00
yuanzbz
2925c5d832 修复: 增加二维码 2023-10-14 20:17:27 +08:00
zouzhibin
5ab9b4ec08 Merge pull request #11 from BaobeierB/master
修复表格分页pageSize无效的bug
2023-08-25 13:50:01 +08:00
BaobeierB
3baf83c09b 修复表格分页pageSize无效的bug 2023-08-17 10:00:51 +08:00
zouzhibing
fda5a40864 Merge remote-tracking branch 'origin/master' 2023-05-29 11:05:12 +08:00
yuanzbz
e7459bc460 y 2023-05-28 22:12:29 +08:00
yuanzbz
0563748bf6 y 2023-05-28 22:03:23 +08:00
zouzhibing
b5684d1d59 文档: readme 修改 2023-03-02 10:52:35 +08:00
zouzhibing
321931f6de 性能: 优化判断 2023-03-02 10:10:35 +08:00
zouzhibing
b45c0f4b08 重构: 重构菜单,样式 2023-03-02 10:08:16 +08:00
zouzhibing
c53834bcaa 性能: echarts图表优化、布局优化 2023-02-28 14:19:13 +08:00
zouzhibing
5f181fae03 特性: 优化菜单 2023-02-27 19:39:54 +08:00
zouzhibing
66a3c584df Merge branch 'todo1' of https://gitee.com/yuanzbz/vue-admin-perfect 2023-02-27 18:54:44 +08:00
zouzhibing
8ac3bc681a feat:修改二维码 2023-02-27 18:54:24 +08:00
zouzhibing
f5f1d28435 feat:新增分栏 2023-02-27 17:44:14 +08:00
zouzhibing
6c87ac2508 fix:todo 2023-02-27 14:20:39 +08:00
zouzhibing
bdfd41c4d6 fix:修改二维码 2023-02-22 21:00:10 +08:00
zouzhibing
c037e12157 fix:修改页面 2023-02-20 09:22:32 +08:00
zouzhibing
5d8383c519 fix:修改二维码图片 2023-02-01 11:16:56 +08:00
zouzhibing
1361525f95 特性: 增加字典表和文本截断 2023-01-09 16:36:15 +08:00
zouzhibing
795ece2225 特性: 增加新功能字典管理和信息列表 2023-01-07 23:34:28 +08:00
zouzhibing
2768c33f7f 修复: 图片问题 2022-12-15 21:35:17 +08:00
zouzhibing
9361d9ccad 重构: 去掉一些不必要的代码 2022-12-15 21:28:19 +08:00
zouzhibing
a0ac6f9424 refactor: 去掉多余的东西 2022-12-12 15:03:40 +08:00
zouzhibing
1caf9733e2 feat: 💥 新增多表单验证以及重构代码结构 2022-12-05 09:10:25 +08:00
zouzhibing
22cd6da835 feat: 💥 新增tsconfig path映射 2022-12-04 22:09:49 +08:00
zouzhibing
014eee5584 docs: 📝 修改readme.md 2022-12-02 22:22:28 +08:00
zouzhibing
3921bd2780 refactor: 重构代码结构目录,优化部分代码 2022-12-02 22:14:18 +08:00
zouzhibing
ea52fb0fec fix: 📌 去掉多余得包,目录名字修改 2022-12-02 20:41:01 +08:00
zouzhibing
03a104ac52 feat: 💥 优化菜单栏显示 2022-11-30 22:49:48 +08:00
zouzhibing
1cf1c0364a perf: 🔥 优化滚动 2022-11-27 21:29:56 +08:00
zouzhibing
eeb54d1726 feat: 💥 增加dom全屏功能和增删表单功能,及优化部分模块 2022-11-27 20:54:11 +08:00
zouzhibing
2fa87b524a docs: 📝 package.json 运行命令修改 2022-11-26 21:48:18 +08:00
zouzhibing
09206b884e ci: 🔖 增加配置git配置文件 2022-11-26 21:45:44 +08:00
zouzhibing
cc467db879 test: 测试 2022-11-26 21:38:16 +08:00
zouzhibing
092fbe5bd5 feat: 测试 2022-11-26 15:02:48 +08:00
zouzhibing
4049c625b6 s 2022-11-26 14:58:04 +08:00
zouzhibing
949fc6f25b feat(增加提交cz 提交): 增加提交 cz 2022-11-26 14:54:06 +08:00
zouzhibing
8a26b162f9 refactor: 🔥 重构纵向横向布局,组件分离,增强可扩展性 2022-11-26 14:32:11 +08:00
zouzhibing
dcbcc775b6 refactor: 🔥 重构纵向横向布局,组件分离,增强可扩展性 2022-11-26 14:31:35 +08:00
zouzhibing
accd569fba feat: 🔥 优化菜单栏 2022-11-25 22:14:06 +08:00
zouzhibing
afe261072d feat: 🔥 优化样式 2022-11-25 12:33:57 +08:00
zouzhibing
e41942b55b feat: 🔥 优化样式 2022-11-25 12:33:38 +08:00
zouzhibing
ffc8856c96 feat: 🔥 优化样式 2022-11-25 12:33:08 +08:00
zouzhibing
da47b1eeaf feat: 🔥 发布新包 2022-11-25 12:18:34 +08:00
zouzhibing
543b221a72 feat: 🔥 发布新包 2022-11-25 12:16:01 +08:00
zouzhibing
6d8c9278e5 feat: 🔥 增加部门管理 2022-11-24 22:54:54 +08:00
zouzhibing
418a8e77d8 提交 2022-11-20 22:10:03 +08:00
zouzhibing
4699093e6d feat: 🔥 优化结构 2022-11-20 19:53:13 +08:00
zouzhibing
a981538a69 refactor 📌 登录代码进行重构 2022-11-20 01:26:06 +08:00
zouzhibing
fab8d0df4e feat 增加工具栏函数 2022-11-19 23:19:03 +08:00
zouzhibing
97275bf98d fix: 更新element-plus 版本,优化部分样式 2022-11-16 19:46:47 +08:00
zouzhibing
7ff2dc0108 doc:📝 更新readme.md 2022-11-15 23:45:08 +08:00
zouzhibing
8baf570b8b doc:📝 更新readme.md 2022-11-15 23:37:54 +08:00
zouzhibing
ea18a476e1 fix: 修复路由权限问题,增加微信二维码进群 2022-11-15 23:15:55 +08:00
zouzhibing
1943e2339a feat:🚀 新增加路由 2022-11-14 22:37:06 +08:00
zouzhibing
78e38f9e92 feat:🚀 升级vue-router && 优化menu 多级嵌套 警告问题 2022-11-14 00:38:28 +08:00
zouzhibing
294f2458e4 fix:修复退出登录问题 2022-11-13 20:15:41 +08:00
zouzhibing
bb848895f7 fix:修复用户管理和角色管理部分问题 2022-11-13 13:50:20 +08:00
zouzhibing
1e4d2633bc feat:增加echarts 和调整目录结构 2022-11-13 00:01:29 +08:00
zouzhibing
b6c0f49f01 feat:增加echarts 图表,调整目录结构 2022-11-13 00:00:02 +08:00
zouzhibing
00acb21c8f feat:增加外部链接 2022-11-12 22:31:16 +08:00
zouzhibing
55bba0f121 feat:增加iframe,优化裁剪组件 2022-11-12 21:44:06 +08:00
zouzhibing
940d9af6fb fix:修复暗黑模式失效,优化样式结构,组件进行分离 2022-11-11 23:58:05 +08:00
zouzhibing
f7dac95732 feat:优化代码结构 2022-11-10 14:14:28 +08:00
zouzhibing
6fbf1d3514 feat:优化代码结构 2022-11-10 13:12:12 +08:00
zouzhibing
00ea15f56b fix:优化再暗黑模式部分情况下样式错乱 2022-11-04 09:27:07 +08:00
zouzhibing
870792f6ec refactor:重构代码和修复 tab 关闭问题 2022-11-03 23:02:04 +08:00
zouzhibing
3da2172bce refactor:重构代码 2022-11-03 12:57:30 +08:00
zouzhibing
8f00712c43 fix:删掉一些文件 2022-10-23 21:48:48 +08:00
zouzhibing
ba685d1753 Merge branch 'cha' of https://github.com/zouzhibin/vue-admin-perfect 2022-10-23 21:45:28 +08:00
zouzhibing
75254dd17f perf:文件名字修改 2022-10-08 11:14:30 +08:00
zouzhibing
af9cb48659 perf:文件名字修改 2022-10-08 11:07:47 +08:00
zouzhibing
8d21343575 perf:文件名字修改 2022-09-30 12:56:51 +08:00
zouzhibing
0c9e11ce2a perf:去掉不必要的代码 2022-09-30 12:44:02 +08:00
zouzhibing
25cb558601 perf:重构目录结构 2022-09-29 22:04:17 +08:00
zouzhibing
15d7e0c555 perf:优化缓存,布局,样式 2022-09-28 22:06:33 +08:00
zouzhibing
a985df0af7 perf:优化缓存,布局,样式 2022-09-28 17:08:12 +08:00
zouzhibing
4784ff379e doc:修改文档 2022-09-18 12:30:17 +08:00
zouzhibing
a67e302453 feat:增加可收缩表单 2022-09-18 12:17:47 +08:00
zouzhibing
5b463cb88b feat:增加可收缩表单 2022-09-16 08:52:25 +08:00
zouzhibing
00fe12a91c feat:增加阿里图标库,移除部分文件,优化样式 2022-09-15 13:25:55 +08:00
zouzhibing
d7572ec856 feat:增加阿里图标库,移除部分文件,优化样式 2022-09-15 13:18:29 +08:00
zouzhibing
e2dd6c47f5 refactor:文件目录重构 2022-09-14 17:05:39 +08:00
zouzhibing
619fe79c27 refactor:去掉多余的部分 2022-09-14 09:37:17 +08:00
zouzhibing
70b640af4f update:表单进行提取 2022-09-14 08:46:24 +08:00
zouzhibing
cdbca16c87 feat:新增主题功能 2022-09-13 21:18:36 +08:00
zouzhibing
16096a2e83 feat:readme修改 2022-09-13 19:44:08 +08:00
zouzhibing
bb6ddbe5a9 update:删除一些文件 2022-09-13 18:34:54 +08:00
zouzhibing
d00b9c385f update:优化滚动,样式 2022-09-13 00:11:45 +08:00
zouzhibing
3cfb256281 update:优化滚动,样式 2022-09-12 20:31:21 +08:00
zouzhibin
b624ebdfe0 Merge branch 'echarts' of https://github.com/zouzhibin/vue-admin-perfect 2022-09-02 21:28:57 +08:00
zouzhibin
e6e126d775 fix:当处于移动端效果优化 2022-09-02 21:28:40 +08:00
zouzhibin
48979c5b31 提交数据 2022-09-02 00:14:09 +08:00
zouzhibin
e0352e4333 修改名字 2022-09-01 22:41:52 +08:00
zouzhibing
dcb72392f2 fix:恢复emojis文件 2022-09-01 16:59:52 +08:00
zouzhibing
3e5bab6097 update:更新log,引入入场加载Loading,增加文档注释 2022-09-01 16:43:46 +08:00
zouzhibin
fe9a1fa88e 修改tagsView 2022-08-31 22:07:41 +08:00
zouzhibing
bde9954e83 update:更新readme.md 2022-08-31 11:41:57 +08:00
zouzhibing
2fc9dc57d5 feature:去掉原来的store 2022-08-31 11:02:49 +08:00
zouzhibing
d5e600d856 feature:全局优化增加pinia并且数据持久化 2022-08-31 11:01:44 +08:00
zouzhibing
2c7d94bac5 feature:增加pinia并且数据持久化 2022-08-30 19:17:40 +08:00
zouzhibing
89e305f002 Merge remote-tracking branch 'origin/master' 2022-08-30 09:30:01 +08:00
zouzhibin
96e067ff6a updata:readme增加说明 2022-08-29 21:01:46 +08:00
zouzhibing
2ab66b6c1a feature:优化细节 2022-08-29 16:57:53 +08:00
zouzhibing
08a7e57728 优化代码 2022-08-29 16:08:26 +08:00
zouzhibin
6c40c8c1ec 提交 2022-08-28 22:11:05 +08:00
zouzhibin
0902a65fc8 提交 2022-08-27 23:34:05 +08:00
zouzhibing
51c1b78f6a feature:优化细节 2022-08-26 11:02:00 +08:00
zouzhibing
a91124d87e feature:优化细节 2022-08-24 18:56:24 +08:00
zouzhibing
264806b275 feature:优化细节 2022-08-24 18:53:05 +08:00
zouzhibing
f945e92e08 Merge branch 'updata' of https://github.com/zouzhibin/vue-admin-perfect 2022-08-24 17:54:19 +08:00
zouzhibing
9d82a1e6f8 feature:调整目录结构 2022-08-24 17:53:08 +08:00
zouzhibing
e3c773a0fa fix:修复路由bug 2022-08-24 16:51:04 +08:00
zouzhibing
5b9bd73340 进行优化 2022-08-24 16:48:03 +08:00
zouzhibing
5740774c2b 进行优化 2022-08-22 16:42:33 +08:00
zouzhibing
99634a49d7 进行优化 2022-08-22 14:46:05 +08:00
zouzhibing
7cfca66ac4 更新readme.md 2022-08-22 10:00:01 +08:00
zouzhibing
efa0fcab52 feat:增加eslint+prettier 代码校验格式化 2022-08-05 16:04:07 +08:00
zouzhibing
759763c875 修改部分结构 2022-08-05 10:56:51 +08:00
zouzhibing
d97c6245b4 修改部分结构 2022-08-05 10:40:50 +08:00
zouzhibing
2c58db5885 调整目录结构 2022-08-04 18:20:56 +08:00
zouzhibing
0ac3969348 修改导出 2022-08-04 17:08:05 +08:00
zouzhibing
18e0c9c18a 增加权限判断 2022-08-04 17:03:02 +08:00
zouzhibing
6f290604a2 增加权限判断 2022-08-04 16:48:40 +08:00
zouzhibing
416773aaab vite 版本上传 2022-08-03 18:49:04 +08:00
zouzhibing
e8e11be678 vite 版本上传 2022-08-03 18:43:24 +08:00
zouzhibin
a113097fae 提交数据 2022-06-19 15:53:27 +08:00
zouzhibing
0992ef57b6 提交数据 2022-06-17 18:02:23 +08:00
zouzhibing
4e1e0e7ce2 增加水印功能 2022-06-17 14:27:56 +08:00
zouzhibing
01828a12ae 提交数据 2022-06-16 14:48:48 +08:00
zouzhibing
c9947cf164 优化样式登录 2022-06-13 16:42:26 +08:00
zouzhibing
2707d63005 新增路由嵌套 2022-06-13 16:02:50 +08:00
zouzhibing
5eb7aba333 新增水印 2022-06-10 17:40:15 +08:00
zouzhibing
0e2da2d401 优化报错 2022-06-10 15:09:53 +08:00
zouzhibing
acbc11794a 提交数据 2022-06-10 15:04:07 +08:00
zouzhibing
03444338c9 提交 2022-06-10 15:00:38 +08:00
zouzhibing
014e5f6804 优化keep-alive 2022-06-10 12:03:11 +08:00
zouzhibing
3096dccd05 重构tagview 2022-06-10 11:01:39 +08:00
zouzhibing
fea0add957 提交数据 2022-06-08 15:58:26 +08:00
zouzhibing
5be0ef087d 进行优化 2022-06-08 12:02:22 +08:00
zouzhibing
8537161a08 优化数度 2022-06-08 11:41:28 +08:00
zouzhibing
c72188cc91 重构tagview 2022-06-07 17:54:21 +08:00
zouzhibing
14358031e7 优化 样式 2022-05-31 16:52:37 +08:00
404 changed files with 47308 additions and 30882 deletions

View File

@@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

5
.env Normal file
View File

@@ -0,0 +1,5 @@
# port
VITE_PORT = 8100

5
.env.development Normal file
View File

@@ -0,0 +1,5 @@
# 本地环境
NODE_ENV = 'development'
# 本地环境接口地址
VITE_APP_BASE_API = '/api'

5
.env.production Normal file
View File

@@ -0,0 +1,5 @@
# 线上环境
NODE_ENV = "production"
# 线上环境接口地址
VITE_APP_BASE_API = '/api'

6
.env.test Normal file
View File

@@ -0,0 +1,6 @@
# 测试环境
NODE_ENV = "test"
# 测试环境接口地址
VITE_APP_BASE_API = '/api'

20
.eslintignore Normal file
View File

@@ -0,0 +1,20 @@
*.sh
node_modules
*.css
*.jpg
*.jpeg
*.png
*.gif
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

66
.eslintrc.js Normal file
View File

@@ -0,0 +1,66 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'plugin:vue/vue3-recommended',
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
plugins: ['vue', '@typescript-eslint'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
},
rules: {
'no-console': 'off', // 禁止调用console对象的方法。
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-empty-function': ['off'], // 关闭空函数警告
'@typescript-eslint/no-inferrable-types': 'off', // 可以轻松推断的显式类型可能会增加不必要的冗长
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间
'@typescript-eslint/ban-types': 'off', // 禁止使用特定类型
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
'vue/no-v-html': 'off', // 禁止使用 v-html
'no-undef': 'off',
'no-redeclare': 'off',
'no-self-assign': 'off',
'no-sparse-arrays': 'off',
'vue/valid-v-for': 'off',
'vue/no-unused-vars': 'off',
'vue/require-v-for-key': 'off',
'no-useless-escape': 'off',
'vue/require-explicit-emits': 'off',
'no-case-declarations': 'off', // 不允许在 case 子句中使用词法声明
//禁止非空断言非空断言是在变量后面添加一个感叹号(!),表示该变量一定存在,不会为 null 或 undefined
'@typescript-eslint/no-non-null-assertion': 'off',
'vue/require-prop-types': 'off',
'@typescript-eslint/no-this-alias': 'off',
'no-async-promise-executor': 'off',
'vue/no-template-shadow': 'off',
'vue/require-default-prop': 'off', // 此规则要求为每个 prop 为必填时,必须提供默认值
'@typescript-eslint/no-unused-vars': [
// // 禁止定义未使用的变量
'off',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
}

22
.gitignore vendored
View File

@@ -1,20 +1,22 @@
.DS_Store
node_modules
/docs
dist
# local env files
.env.local
.env.*.local
# Log files
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist-ssr
dist
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.vscode
.DS_Store
*.suo
*.ntvs*
*.njsproj

4
.husky/pre-commit Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

9
.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*

16
.prettierrc.js Normal file
View File

@@ -0,0 +1,16 @@
module.exports = {
// 一行的字符数如果超过会进行换行默认为80
printWidth: 140,
// 缩进制表符宽度 | 空格数
tabWidth: 2,
// 行位是否使用分号默认为true
semi: false,
vueIndentScriptAndStyle: true,
// 字符串是否使用单引号默认为false使用双引号
singleQuote: true,
// 是否使用尾逗号,有三个可选值"<none|es5|all>"
trailingComma: 'all',
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
};

108
AxiosRequestConfig.md Normal file
View File

@@ -0,0 +1,108 @@
```
export interface AxiosRequestConfig {
// `url` 是用于请求的服务器 URL
url?: string;
// `method` 是创建请求时使用的方法
method?: Method;
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL?: string;
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer或 Stream
transformRequest?: AxiosTransformer | AxiosTransformer[];
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse?: AxiosTransformer | AxiosTransformer[];
// `headers` 是即将被发送的自定义请求头
headers?: any;
// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params?: any;
// `paramsSerializer` 是一个负责 `params` 序列化的函数
paramsSerializer?: (params: any) => string;
// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属FormData, File, Blob
// - Node 专属: Stream
data?: any;
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout?: number;
// 超时提示消息
timeoutErrorMessage?: string;
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials?: boolean;
// `adapter` 允许自定义处理请求,以使测试更轻松
adapter?: AxiosAdapter;
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
auth?: AxiosBasicCredentials;
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType?: ResponseType;
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
xsrfCookieName?: string;
// `xsrfHeaderName` 是携带 xsrf 令牌值的 http 标头的名称
xsrfHeaderName?: string;
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress?: (progressEvent: any) => void;
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress?: (progressEvent: any) => void;
// `maxContentLength` 定义允许的响应内容的最大尺寸
maxContentLength?: number;
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。
// 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`)
// promise 将被 resolve; 否则promise 将被 rejecte
validateStatus?: ((status: number) => boolean) | null;
// 请求体最大尺寸
maxBodyLength?: number;
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
// 如果设置为0将不会 follow 任何重定向
maxRedirects?: number;
// `socketPath` 定义了一个在 node.js 中使用的 UNIX Socket。
// 只能指定 `socketPath` 或 `proxy`。
// 如果两者都指定,则使用 `socketPath`。
socketPath?: string | null;
// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。
httpAgent?: any;
httpsAgent?: any;
// 'proxy' 定义代理服务器的主机名称和端口
proxy?: AxiosProxyConfig | false;
// `cancelToken` 指定用于取消请求的 cancel token
cancelToken?: CancelToken;
// 将其设置为`false`它将不会解压缩您的响应而是保留原始的Content-Encoding头。
// 默认是true
decompress?: boolean;
// 控制响应数据是否转换
transitional?: TransitionalOptions
}
```

159
README.md
View File

@@ -1,75 +1,114 @@
# 简介
vue-element-perfect 是一个后台前端解决方案, 基于Vue3.0+TS+Element-plus实现。它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件
## 简介
vue-element-perfect 是一个后台前端解决方案,它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件,企业级中后台解决方案可免费商用同时支持PC、平板、手机
本项目也参考了很多开源的项目、
### 在线预览
- gitee国内访问地址https://yuanzbz.gitee.io/vue-admin-perfect/#/home
- github site : https://zouzhibin.github.io/vue-admin-perfect/
## 效果预览
- 基础功能版本预览https://yuanzbz.gitee.io/vue-admin-simple
### 在线预览点击 —— [企业级、通用型中后台前端解决方案 ](http://182.61.5.190:8889/)
### 附上github地址点击跳转 [vue-admin-perfect](https://github.com/zouzhibin/vue-admin-perfect)
## 目录结构介绍
### git仓库(欢迎 Star⭐)
- Gitee —— [https://gitee.com/yuanzbz/vue-admin-perfect](https://gitee.com/yuanzbz/vue-admin-perfect)
- GitHub —— [https://github.com/zouzhibin/vue-admin-perfect](https://github.com/zouzhibin/vue-admin-perfect)
- 基础功能版本: —— [https://gitee.com/yuanzbz/vue-admin-simple](https://gitee.com/yuanzbz/vue-admin-simple)
## 项目功能
- 使用Vue3.0开发单文件组件采用script setup
- 采用 Vite3 作为项目开发、打包工具(配置了 Gzip 打包、TSX 语法、跨域代理)
- 整个项目集成了 TypeScript
- 登录逻辑使用vue-router进行路由权限拦截判断路由懒加载
- 使用 keep-alive 对整个页面进行缓存,支持多级嵌套页面
- 侧边栏导航菜单栏动态的显示
- 各种可视化地图组件
- 使用 Pinia替代 Vuex轻量、简单、易用
- 导出excel,自定义样式导出excel、多表头导出
- 表单、表格、水印、多标签显示、打印功能图片打印、表格打印、普通打印、二维码、拖拽、markdown、头像裁剪、图片上传...
- 使用 Prettier 统一格式化代码,集成 Eslint、代码校验规范
## 安装
```
# GitHub
git clone https://github.com/zouzhibin/vue-admin-perfect.git
# Gitee
git clone https://gitee.com/yuanzbz/vue-admin-perfect.git
```
├─assets 放置一些静态文件
├─components 放置组件
├─layout 首页布局
│ ├─components
│ │ ├─Sidebar 侧边栏配置
│ │ ├─TagsView 标签栏配置
│ │ └─UHeader 头部内容配置
└─hooks
├─router 路由配置
├─store 数据存储
├─styles 样式文件
├─utils 工具方法
└─views 页面配置方法
└─permission 路由拦截 权限配置
## 分支管理
- master 技术采用 vite + vue3.0 + Typescript + pinia
- vue-admin-simple 简易版本
- vite-vuex vite + vue3.0 + Typescript + vuex
- vue-i18n 语言切换版本
- webpack 技术采用 webpack + vue3.0 + Typescript + vuex
- uniapp uniapp版本 uniapp +vuex +element scss
```
# 本地开发 启动项目
借助hbuilder工具运行浏览器启动
```
## 下载依赖
```
npm install
cnpm install
yarn
# npm install 安装失败,请升级 nodejs 到 16 以上,或尝试使用以下命令:
npm install --registry=https://registry.npm.taobao.org
```
## 运行打包
```
npm run dev
npm run build
```
## eslint+prettier
```
# eslint 检测代码
npm run lint
#prettier 格式化代码
npm run lint:prettier
```
## 技术栈
vue3.0 + Typescript + vuex + vue-router + Element-Plus scss
## 实现功能
- 登录逻辑
- 权限校验
- 侧边栏导航显示
- 打印
- 地图
- 头像裁剪
- 无限滚动
- 多标签显示
- 退出
- 其他各组件
## 项目 master 分支 基于vue3.0 + Typescript + vuex + vue-router + Element-Plus scss
## 文件目录结构
```
# 克隆项目
git clone https://github.com/zouzhibin/vue-admin-perfect.git
# 安装依赖
yarn
# 本地开发 启动项目
yarn serve
vue-admin-perfect
├─ public # 静态资源文件(忽略打包)
├─ src
│ ├─ api # API 接口管理
│ ├─ assets # 静态资源文件
│ ├─ components # 全局组件
│ ├─ config # 全局配置项
│ ├─ hooks # 常用 Hooks
│ ├─ language # 语言国际化
│ ├─ layout # 框架布局
│ ├─ routers # 路由管理
│ ├─ store # pinia store
│ ├─ styles # 全局样式
│ ├─ utils # 工具库
│ ├─ views # 项目所有页面
│ ├─ App.vue # 入口页面
│ └─ main.ts # 入口文件
├─ .env # vite 常用配置
├─ .env.development # 开发环境配置
├─ .env.production # 生产环境配置
├─ .env.test # 测试环境配置
├─ .eslintignore # 忽略 Eslint 校验
├─ .eslintrc.cjs # Eslint 校验配置
├─ .gitignore # git 提交忽略
├─ .prettierignore # 忽略 prettier 格式化
├─ .prettierrc.config.js # prettier 配置
├─ index.html # 入口 html
├─ yarn.lock # 依赖包包版本锁
├─ package.json # 依赖包管理
├─ README.md # README 介绍
├─ tsconfig.json # typescript 全局配置
└─ vite.config.ts # vite 配置
```
## 项目 uniapp 分支 基于 uniapp +vuex +element scss
```
# 克隆项目 切换 uniapp分支
git clone https://github.com/zouzhibin/vue-admin-perfect.git
# 安装依赖
yarn
# 本地开发 启动项目
hbuilder运行浏览器启动
```
### 微信交流群
| 微信二维码 |
| :----------------------------------------------------------------------------------: |
| <img src="http://182.61.5.190:8889/we.png" width=170/> |

View File

@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

84
commitlint.config.js Normal file
View File

@@ -0,0 +1,84 @@
// 文档参考https://cz-git.qbb.sh/zh/config/
// cz.config.js kk
/** @type {import('cz-git').CommitizenGitOptions} */
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
// alias: { fd: 'docs: fix typos' },
// messages: {
// type: 'Select the type of change that you\'re committing:',
// scope: 'Denote the SCOPE of this change (optional):',
// customScope: 'Denote the SCOPE of this chang e:',
// subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
// body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
// breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n',
// footerPrefixsSelect: 'Select the ISSUES type of changeList by this change (optional):',
// customFooterPrefixs: 'Input ISSUES prefix:',
// footer: 'List any ISSUES by this change. E.g.: #31, #34:\n',
// confirmCommit: 'Are you sure you want to proceed with the commit above?'
// },
prompt: {
// 中英文对照版
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixesSelect: '选择关联issue前缀可选:',
customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?',
},
types: [
{ value: '特性', name: '特性: 新增功能' },
{ value: '修复', name: '修复: 修复缺陷' },
{ value: '文档', name: '文档: 文档变更' },
{ value: '格式', name: '格式: 代码格式(不影响功能,例如空格、分号等格式修正)' },
{ value: '重构', name: '重构: 代码重构(不包括 bug 修复、功能新增)' },
{ value: '性能', name: '性能: 性能优化' },
{ value: '测试', name: '测试: 添加疏漏测试或已有测试改动' },
{
value: '构建',
name: '构建: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)',
},
{ value: '集成', name: '集成: 修改 CI 配置、脚本' },
{ value: '回退', name: '回退: 回滚 commit' },
{ value: '其他', name: '其他: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
useEmoji: true,
// emojiAlign: 'center',
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixs: [{ value: 'closed', name: 'closed: ISSUES has been processed' }],
customIssuePrefixsAlign: 'top',
emptyIssuePrefixsAlias: 'skip',
customIssuePrefixsAlias: 'custom',
allowCustomIssuePrefixs: true,
allowEmptyIssuePrefixs: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: '',
},
}

View File

@@ -1,18 +0,0 @@
#强制推送
#!/usr/bin/env bash
set -e
npm run build
cd dist
touch .nojekyll
git init
git add -A
git commit -m 'deploy'
git push -f "https://${access_token}@gitee.com/yuanzbz/vue-admin-perfect.git" master:gh-pages
git push -f "https://${access_token}@github.com/zouzhibin/vue-admin-perfect.git" master:gh-pages
cd -
exec /bin/bash

122
index.html Normal file
View File

@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue admin perfect</title>
</head>
<body>
<div id="app">
<style>
.first-loading-wrp {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
min-height: 90vh;
}
.first-loading-wrp > h1 {
font-size: 30px;
font-weight: bolder;
}
.first-loading-wrp .loading-wrp {
display: flex;
align-items: center;
justify-content: center;
padding: 98px;
}
.dot {
position: relative;
box-sizing: border-box;
display: inline-block;
width: 64px;
height: 64px;
font-size: 64px;
transform: rotate(45deg);
animation: antRotate 1.2s infinite linear;
}
.dot i {
position: absolute;
display: block;
width: 28px;
height: 28px;
background-color: #1890ff;
border-radius: 100%;
opacity: 0.3;
transform: scale(0.75);
transform-origin: 50% 50%;
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: 0.4s;
animation-delay: 0.4s;
}
.dot i:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.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>
<div id="vue-admin-perfect">
<div class="first-loading-wrp">
<div class="loading-wrp">
<span class="dot dot-spin">
<i></i>
<i></i>
<i></i>
<i></i>
</span>
</div>
<h1>vue-admin-perfect</h1>
</div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

23609
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,106 @@
{
"name": "vue-admin-perfect",
"version": "0.1.0",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "npm run serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
"dev": "vite",
"build:dev": "vite build --mode development",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode production",
"commit": "git add -A && czg && git push",
"build": "vite build",
"preview": "vite preview",
"build:ts": "vue-tsc --noEmit --skipLibCheck && vite build",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx",
"lint:fix": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"prepare": "husky install"
},
"dependencies": {
"@better-scroll/core": "^2.4.2",
"@vueuse/core": "^9.1.1",
"@wangeditor/editor": "^5.1.14",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^0.27.2",
"clipboard": "^2.0.10",
"codemirror": "^5.65.9",
"core-js": "^3.6.5",
"dayjs": "^1.11.4",
"echarts": "^5.3.1",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.1.4",
"element-plus": "^2.2.28",
"exceljs": "^4.3.0",
"file-saver": "^2.0.5",
"fuse.js": "^6.6.2",
"jszip": "^3.9.1",
"mavon-editor": "^2.10.4",
"md-editor-v3": "^1.11.3",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.21",
"pinia-plugin-persistedstate": "^2.1.1",
"print-js": "^1.6.0",
"raf": "^3.4.1",
"resize-observer-polyfill": "^1.5.1",
"sass": "^1.54.0",
"splitpanes": "^3.1.1",
"svg-sprite-loader": "^6.0.11",
"vue": "^3.0.0",
"vue": "^3.2.39",
"vue-cropper": "^1.0.3",
"vue-cropperjs": "^5.0.0",
"vue-fuse": "^4.1.1",
"vue-mugen-scroll": "^0.2.6",
"vue-qr": "^4.0.6",
"vue-router": "^4.0.0-0",
"vue-router": "^4.1.6",
"vue-splitpane": "^1.0.6",
"vue3-text-clamp": "^0.1.1",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.0-0",
"wangeditor": "^4.7.12",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.8",
"@vue/cli-plugin-router": "~4.5.8",
"@vue/cli-plugin-typescript": "~4.5.8",
"@vue/cli-plugin-vuex": "~4.5.8",
"@vue/cli-service": "~4.5.8",
"@vue/compiler-sfc": "^3.0.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "~4.1.5"
"@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0",
"@element-plus/icons-vue": "^2.3.1",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"@vitejs/plugin-vue": "^3.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"commitizen": "^4.2.5",
"consola": "^2.15.3",
"cz-git": "^1.3.12",
"czg": "^1.3.12",
"dart-sass": "^1.25.0",
"eslint": "^8.21.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.3.0",
"fast-glob": "^3.2.11",
"husky": "^7.0.4",
"lint-staged": "^12.3.4",
"mrm": "^3.0.10",
"prettier": "^2.7.1",
"typescript": "^4.6.4",
"unplugin-auto-import": "^0.10.3",
"unplugin-vue-components": "^0.21.2",
"unplugin-vue-define-options": "^0.7.3",
"vite": "^3.0.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-style-import": "^2.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^0.38.4"
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
},
"lint-staged": {
"src/**/*.{vue,js,ts,jsx,tsx}": [
"npm run lint:prettier",
"npm run lint:fix",
"git add ."
]
}
}

7640
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="">
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="icon" href="/favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>

BIN
public/static/screen/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 KiB

BIN
public/static/screen/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

13
push.sh
View File

@@ -1,13 +0,0 @@
#强制推送
#!/usr/bin/env bash
set -e
git init
git add -A
git commit -m '🎉 feat: init project'
git push -f "https://${access_token}@gitee.com/yuanzbz/vue-admin-perfect.git" master
git push -f "https://${access_token}@github.com/zouzhibin/vue-admin-perfect.git" master
exec /bin/bash

View File

@@ -1,28 +1,37 @@
<template>
<router-view v-slot="{ Component }">
<component :is="Component"/>
</router-view>
<el-config-provider :size="globalComSize" :locale="zhCn">
<router-view></router-view>
</el-config-provider>
</template>
<style lang="scss">
#app {
position: relative;
width: 100%;
height: 100%;
/*background: #f6f8f9;*/
/*background: goldenrod;*/
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
html,body{
height: 100%;
background: #f6f8f9;
<script lang="ts" setup>
import { computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
// 配置element中文
import zhCn from 'element-plus/es/locale/lang/zh-cn'
}
*{
margin: 0;
padding: 0;
}
const SettingStore = useSettingStore()
// 配置全局组件大小
const globalComSize = computed((): string => SettingStore.themeConfig.globalComSize)
</script>
<style lang="scss">
#app {
position: relative;
width: 100%;
height: 100%;
font-family: Avenir, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.el-pager li:focus {
border: none;
}
.el-dropdown:focus {
border: none;
}
.svg-icon:focus {
border: none;
}
</style>

View File

@@ -1,92 +0,0 @@
<template>
<div class="page_search">
<el-button type="primary" size="small">新增</el-button>
<div class="search">
<el-input
v-model="search"
placeholder="请输入名称"
size="small"
class="input-with-select"
>
<template #append>
<el-button icon="el-icon-search"></el-button>
</template>
</el-input>
</div>
<el-button type="primary" class="search" icon="el-icon-share" @click="exportExcel">导出</el-button>
</div>
</template>
<script>
import { defineComponent, ref } from "vue";
import Excel from "exceljs";
export default {
setup() {
const search = ref("");
const downloadBlob=(arrayList,type,fileName)=>{
var blob=new Blob(arrayList,{type:type});
var a = document.createElement('a'); //创建a标签用于下载
a.download = fileName;
a.href = URL.createObjectURL(blob);
document.getElementsByTagName("body")[0].appendChild(a);
a.click();
document.getElementsByTagName("body")[0].removeChild(a);
};
const exportExcel=async()=>{
let workbook = new Excel.Workbook();
let worksheet = workbook.addWorksheet('报表-威尔森',{views: [{showGridLines: true}]});
let alignObj={alignment:{vertical:'middle',horizontal:'center'}};
let columns=[{header:"",key:"A",style:alignObj},
{header:"",key:"B",width:12,style:alignObj},
{header:"",key:"C",style:alignObj},
{header:"",key:"D",style:alignObj}];
worksheet.columns = columns;
// 开始添加数据
let data=[
{A:"COD",B:'',C:"氨氮",D:''},
{A:"进水",B:"出水",C:"进水",D:"出水"},
{A:1,B:2,C:3,D:4},
{A:11,B:2,C:3,D:4},
{A:1,B:22,C:3,D:4}
];
for(let i in data) {
worksheet.addRow(data[i]).commit();
}
worksheet.mergeCells('A2:B2');
worksheet.mergeCells('C2:D2');
worksheet.getRow(2).border={top: {style:'thin'},left: {style:'thin'},bottom: {style:'thin'},right: {style:'thin'}}
//await worksheet.commit();
// write to a new buffer
const buffer = await workbook.xlsx.writeBuffer();
//console.log(buffer);
downloadBlob([buffer],"application/octet-stream","data.xlsx");
};
return {
search,exportExcel,
};
},
};
</script>
<style scoped>
.page_search {
margin: 10px;
display: flex;
align-items: center;
}
.search {
margin-left: 10px;
width: 200px;
}
</style>

View File

@@ -1,200 +0,0 @@
const ExcelJS = require("exceljs");
const autoWidthAction = (val,width=10)=>{
if (val == null) {
width = 10;
} else if (val.toString().charCodeAt(0) > 255) {
/*if chinese*/
width = val.toString().length * 2;
} else {
width = val.toString().length;
}
return width
}
export const exportExcel = ({column,data,filename,autoWidth})=>{
// 创建excel
const workbook = new ExcelJS.Workbook();
// 设置信息
workbook.creator = "Me";
workbook.title = filename;
workbook.created = new Date();
workbook.modified = new Date();
// 创建工作表
const worksheet = workbook.addWorksheet(filename);
// 设置列名
let columnsName = [];
column.forEach((item,index)=>{
let obj = {
header: item.label, key:item.name, width: null
}
if(autoWidth){
let maxArr = [autoWidthAction(item.label)]
data.forEach(ite=>{
let str = ite[item.name] ||''
if(str){
maxArr.push(autoWidthAction(str))
}
})
obj.width = Math.max(...maxArr)+5
}
// 设置列名、键和宽度
columnsName.push(obj);
})
worksheet.columns = columnsName;
// 添加行
worksheet.addRows(data);
// 写入文件
workbook.xlsx.writeBuffer().then((data) => {
const blob = new Blob([data, { type: "application/vnd.ms-excel" }]);
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + ".xlsx");
// 本地保存
} else {
const link = document.createElement("a"); // a标签下载
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接
link.download = filename + ".xlsx"; // dowload属性指定文件名
link.click(); // click()事件触发下载
window.URL.revokeObjectURL(link.href); // 释放内存
}
});
}
// 默认的列宽
export const DEFAULT_COLUMN_WIDTH = 20;
function getColumnNumber(width: number) {
// 需要的列数,四舍五入
return Math.round(width / DEFAULT_COLUMN_WIDTH);
}
function addData(worksheet,headerKeys,headers,data){
}
export const exportMultiHeaderExcel = ({column,data,filename,autoWidth})=>{
// 创建excel
const workbook = new ExcelJS.Workbook();
// 创建工作表
const worksheet = workbook.addWorksheet(filename);
// 第一行表头
const names1= [];
// 第二行表头
const names2= [];
// 用于匹配数据的 keys
const headerKeys= [];
let headers = [];
column.forEach((item,index)=>{
let obj = {
header: item.label, key:item.name, width: null
}
let maxArr = [autoWidthAction(item.label)]
data.forEach(ite=>{
let str = ite[item.name] ||''
if(str){
maxArr.push(autoWidthAction(str))
}
})
obj.width = Math.max(...maxArr)+5
// 设置列名、键和宽度
headers.push(obj);
})
column.forEach(item => {
if (item.children) {
// 有 children 说明是多级表头header name 需要两行
item.children.forEach(child => {
names1.push(item.label);
names2.push(child.label);
headerKeys.push(child.name);
});
} else {
names1.push(item.label);
names2.push(item.label);
headerKeys.push(item.name);
}
});
// 判断是否有 children, 有的话是两行表头
const isMultiHeader = column.some(item => item.children);
if(isMultiHeader){
const rowHeader1 = worksheet.addRow(names1);
const rowHeader2 = worksheet.addRow(names2);
console.log('rowHeader1====',names1,names2,rowHeader1,rowHeader2)
mergeColumnCell(headers, rowHeader1, rowHeader2, names1, names2, worksheet)
// return
}
data.forEach((item: any) => {
const rowData = headerKeys?.map(key => item[key]);
const row = worksheet.addRow(rowData);
// console.log('row--------',row)
})
// 写入文件
workbook.xlsx.writeBuffer().then((data) => {
const blob = new Blob([data, { type: "application/vnd.ms-excel" }]);
if (window.navigator.msSaveOrOpenBlob) {
// msSaveOrOpenBlob方法返回boolean值
navigator.msSaveBlob(blob, filename + ".xlsx");
// 本地保存
} else {
const link = document.createElement("a"); // a标签下载
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接
link.download = filename + ".xlsx"; // dowload属性指定文件名
link.click(); // click()事件触发下载
window.URL.revokeObjectURL(link.href); // 释放内存
}
});
}
function mergeColumnCell(headers, rowHeader1, rowHeader2, nameRow1, nameRow2, worksheet){
// 当前 index 的指针
let pointer = -1;
nameRow1.forEach((name, index) => {
// 当 index 小于指针时,说明这一列已经被合并过了,不能再合并
if (index <= pointer) return;
// 是否应该列合并
const shouldVerticalMerge = name === nameRow2[index];
// 是否应该行合并
const shouldHorizontalMerge = index !== nameRow1.lastIndexOf(name);
console.log('==',name,nameRow2[index],index,nameRow1.lastIndexOf(name),shouldVerticalMerge,shouldHorizontalMerge)
pointer = nameRow1.lastIndexOf(name);
if (shouldVerticalMerge && shouldHorizontalMerge) {
// 两个方向都合并
worksheet.mergeCells(
Number(rowHeader1.number),
index + 1,
Number(rowHeader2.number),
nameRow1.lastIndexOf(name) + 1,
);
console.log('==')
} else if (shouldVerticalMerge && !shouldHorizontalMerge) {
// 只在垂直方向上同一列的两行合并
worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader2.number), index + 1);
} else if (!shouldVerticalMerge && shouldHorizontalMerge) {
// 只有水平方向同一行的多列合并
worksheet.mergeCells(
Number(rowHeader1.number),
index + 1,
Number(rowHeader1.number),
nameRow1.lastIndexOf(name) + 1,
);
// eslint-disable-next-line no-param-reassign
const cell = rowHeader1.getCell(index + 1);
cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
}
});
}

44
src/api/errorCodeType.ts Normal file
View File

@@ -0,0 +1,44 @@
export const errorCodeType = function (code: string): string {
let errMessage = '未知错误'
switch (code) {
case 400:
errMessage = '请求失败!请您稍后重试'
break
case 401:
errMessage = '未授权,请重新登录'
break
case 403:
errMessage = '当前账号无权限访问!'
break
case 404:
errMessage = '你所访问的资源不存在!'
break
case 405:
errMessage = '请求方式错误!请您稍后重试'
break
case 408:
errMessage = '请求超时!请您稍后重试'
break
case 500:
errMessage = '服务器端出错'
break
case 501:
errMessage = '网络未实现'
break
case 502:
errMessage = '网络错误'
break
case 503:
errMessage = '服务不可用'
break
case 504:
errMessage = '网络超时'
break
case 505:
errMessage = 'http版本不支持该请求'
break
default:
errMessage = `其他连接错误 --${code}`
}
return errMessage
}

62
src/api/request.ts Normal file
View File

@@ -0,0 +1,62 @@
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
// 创建axios实例 进行基本参数配置
const service = axios.create({
// 默认请求地址,根据环境的不同可在.env 文件中进行修改
baseURL: import.meta.env.VITE_APP_BASE_API,
// 设置接口访问超时时间
timeout: 3000000, // request timeout
// 跨域时候允许携带凭证
withCredentials: true,
})
// request interceptor 接口请求拦截
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
/**
* 用户登录之后获取服务端返回的token,后面每次请求都在请求头中带上token进行JWT校验
* token 存储在本地储存中storage、vuex、pinia
*/
const userStore = useUserStore()
const token: string = userStore.token
// 自定义请求头
if (token) {
config.headers['Authorization'] = token
}
return config
},
(error: AxiosError) => {
// 请求错误,这里可以用全局提示框进行提示
return Promise.reject(error)
},
)
// response interceptor 接口响应拦截
service.interceptors.response.use(
(response: AxiosResponse) => {
// 直接返回res当然你也可以只返回res.data
// 系统如果有自定义code也可以在这里处理
return response
},
(error: AxiosError) => {
return Promise.reject(error)
},
)
// /**
// * @description 显示错误消息
// * opt 传入参数
// * err 错误信息
// * type 消息类型
// * duration 消息持续时间
// */
// function showErrMessage(opt, err, type: any = 'error', duration = 5000) {
// ElMessage({
// message: err.msg,
// type: type,
// duration: duration,
// });
// }
export default service

9
src/api/user.ts Normal file
View File

@@ -0,0 +1,9 @@
import request from './request'
export function login(data) {
return request({
url: '/vue-element-perfect/user/login',
method: 'post',
data,
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,113 @@
@font-face {
font-family: 'iconfont'; /* Project id 3641732 */
src: url('./iconfont.woff2?t=1663216428515') format('woff2'), url('./iconfont.woff?t=1663216428515') format('woff'),
url('./iconfont.ttf?t=1663216428515') format('truetype');
}
.iconfont {
font-family: 'iconfont' !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-yin:before {
content: '\e6a0';
}
.icon-dabaoyu:before {
content: '\e6a7';
}
.icon-shuaxin1:before {
content: '\e627';
}
.icon-shuaxin:before {
content: '\e629';
}
.icon-duoyun-2-copy:before {
content: '\e68d';
}
.icon-duoyun-1:before {
content: '\e679';
}
.icon-daxue:before {
content: '\e67a';
}
.icon-dayu:before {
content: '\e67b';
}
.icon-feng:before {
content: '\e67c';
}
.icon-duoyun-3:before {
content: '\e67d';
}
.icon-duoyun-2:before {
content: '\e67e';
}
.icon-leiyujiaojia:before {
content: '\e67f';
}
.icon-duoyun:before {
content: '\e680';
}
.icon-zhongyu:before {
content: '\e681';
}
.icon-wu:before {
content: '\e682';
}
.icon-xiaoyu:before {
content: '\e683';
}
.icon-xiaoxue:before {
content: '\e684';
}
.icon-shandian:before {
content: '\e685';
}
.icon-xue:before {
content: '\e686';
}
.icon-zhongxue:before {
content: '\e687';
}
.icon-yangchen:before {
content: '\e688';
}
.icon-yueliang:before {
content: '\e689';
}
.icon-yujiaxue:before {
content: '\e68a';
}
.icon-qing:before {
content: '\e68b';
}
.icon-mai:before {
content: '\e68c';
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,184 @@
{
"id": "3641732",
"name": "weather",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "28557556",
"name": "阴",
"font_class": "yin",
"unicode": "e6a0",
"unicode_decimal": 59040
},
{
"icon_id": "28557770",
"name": "大暴雨",
"font_class": "dabaoyu",
"unicode": "e6a7",
"unicode_decimal": 59047
},
{
"icon_id": "7724796",
"name": "刷新",
"font_class": "shuaxin1",
"unicode": "e627",
"unicode_decimal": 58919
},
{
"icon_id": "6999224",
"name": "刷新",
"font_class": "shuaxin",
"unicode": "e629",
"unicode_decimal": 58921
},
{
"icon_id": "31772217",
"name": "多云-2-copy",
"font_class": "duoyun-2-copy",
"unicode": "e68d",
"unicode_decimal": 59021
},
{
"icon_id": "29265274",
"name": "多云-1",
"font_class": "duoyun-1",
"unicode": "e679",
"unicode_decimal": 59001
},
{
"icon_id": "29265275",
"name": "大雪",
"font_class": "daxue",
"unicode": "e67a",
"unicode_decimal": 59002
},
{
"icon_id": "29265276",
"name": "大雨",
"font_class": "dayu",
"unicode": "e67b",
"unicode_decimal": 59003
},
{
"icon_id": "29265277",
"name": "风",
"font_class": "feng",
"unicode": "e67c",
"unicode_decimal": 59004
},
{
"icon_id": "29265278",
"name": "多云-3",
"font_class": "duoyun-3",
"unicode": "e67d",
"unicode_decimal": 59005
},
{
"icon_id": "29265279",
"name": "多云-2",
"font_class": "duoyun-2",
"unicode": "e67e",
"unicode_decimal": 59006
},
{
"icon_id": "29265280",
"name": "雷雨交加",
"font_class": "leiyujiaojia",
"unicode": "e67f",
"unicode_decimal": 59007
},
{
"icon_id": "29265281",
"name": "多云",
"font_class": "duoyun",
"unicode": "e680",
"unicode_decimal": 59008
},
{
"icon_id": "29265282",
"name": "中雨",
"font_class": "zhongyu",
"unicode": "e681",
"unicode_decimal": 59009
},
{
"icon_id": "29265283",
"name": "雾",
"font_class": "wu",
"unicode": "e682",
"unicode_decimal": 59010
},
{
"icon_id": "29265284",
"name": "小雨",
"font_class": "xiaoyu",
"unicode": "e683",
"unicode_decimal": 59011
},
{
"icon_id": "29265285",
"name": "小雪",
"font_class": "xiaoxue",
"unicode": "e684",
"unicode_decimal": 59012
},
{
"icon_id": "29265286",
"name": "闪电",
"font_class": "shandian",
"unicode": "e685",
"unicode_decimal": 59013
},
{
"icon_id": "29265287",
"name": "雪",
"font_class": "xue",
"unicode": "e686",
"unicode_decimal": 59014
},
{
"icon_id": "29265288",
"name": "中雪",
"font_class": "zhongxue",
"unicode": "e687",
"unicode_decimal": 59015
},
{
"icon_id": "29265289",
"name": "扬尘",
"font_class": "yangchen",
"unicode": "e688",
"unicode_decimal": 59016
},
{
"icon_id": "29265290",
"name": "月亮",
"font_class": "yueliang",
"unicode": "e689",
"unicode_decimal": 59017
},
{
"icon_id": "29265291",
"name": "雨夹雪",
"font_class": "yujiaxue",
"unicode": "e68a",
"unicode_decimal": 59018
},
{
"icon_id": "29265292",
"name": "晴",
"font_class": "qing",
"unicode": "e68b",
"unicode_decimal": 59019
},
{
"icon_id": "29265293",
"name": "霾",
"font_class": "mai",
"unicode": "e68c",
"unicode_decimal": 59020
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/image/allow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

BIN
src/assets/image/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/assets/image/we.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View File

@@ -0,0 +1,85 @@
<template>
<vue-cropper
ref="cropper"
:img="avatarUrl"
:output-size="defaultOptions.outputSize"
:output-type="defaultOptions.outputType"
:info="defaultOptions.info"
:full="defaultOptions.full"
:fixed="defaultOptions.fixed"
:auto-crop-width="defaultOptions.autoCropWidth"
:auto-crop-height="defaultOptions.autoCropHeight"
:fixed-box="defaultOptions.fixedBox"
:auto-crop="defaultOptions.autoCrop"
:center-box="defaultOptions.centerBox"
@real-time="realTime"
/>
</template>
<script lang="ts" setup>
import { defineExpose, reactive, ref } from 'vue'
import { VueCropper } from 'vue-cropper'
import 'vue-cropper/dist/index.css'
const emit = defineEmits(['change'])
let props = defineProps({
avatarUrl: {
type: String,
},
options: {
type: Object,
default: () => {},
},
})
let cropper = ref()
const defaultOptions = reactive({
outputSize: 0.8, // 裁剪生成图片的质量
outputType: 'png', // 生成图片的格式
info: true, // 裁剪框的大小信息
fixed: true, // 是否开启截图框宽高固定比例
autoCrop: true, // 是否默认生成截图框
anMoveBox: true, // 截图框能否拖动
original: false, // 上传图片按照原始比例渲染
autoCropWidth: 300, // 默认生成截图框宽度
autoCropHeight: 300, // 默认生成截图框高度
// 只有自动截图开启 宽度高度才生效
centerBox: false, // 截图框是否被限制在图片里面
high: true, // 是否按照dpr设备比例图片
fixedBox: false, // 固定截图框大小 不允许改变
full: false, // 是否输出原图比例的截图
...props.options,
})
const getBase64 = () => {
return new Promise((resolve) => {
cropper.value.getCropData((data) => {
resolve(data)
})
})
}
const rotateLeft = () => {
cropper.value.rotateLeft()
}
const rotateRight = () => {
cropper.value.rotateRight()
}
const zoom = (percent) => {
cropper.value.changeScale(percent)
}
// 实时预览图片
const realTime = (data) => {
emit('change', data)
}
defineExpose({
getBase64,
rotateLeft,
rotateRight,
zoom,
})
</script>

View File

@@ -1,155 +0,0 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xAxisData = []
const data = []
const data2 = []
for (let i = 0; i < 50; i++) {
xAxisData.push(i)
data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
}
this.chart.setOption({
backgroundColor: '#08263a',
grid: {
left: '5%',
right: '5%'
},
xAxis: [{
show: false,
data: xAxisData
}, {
show: false,
data: xAxisData
}],
visualMap: {
show: false,
min: 0,
max: 50,
dimension: 0,
inRange: {
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
}
},
yAxis: {
axisLine: {
show: false
},
axisLabel: {
textStyle: {
color: '#4a657a'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#08263f'
}
},
axisTick: {
show: false
}
},
series: [{
name: 'back',
type: 'bar',
data: data2,
z: 1,
itemStyle: {
normal: {
opacity: 0.4,
barBorderRadius: 5,
shadowBlur: 3,
shadowColor: '#111'
}
}
}, {
name: 'Simulate Shadow',
type: 'line',
data,
z: 2,
showSymbol: false,
animationDelay: 0,
animationEasing: 'linear',
animationDuration: 1200,
lineStyle: {
normal: {
color: 'transparent'
}
},
areaStyle: {
normal: {
color: '#08263a',
shadowBlur: 50,
shadowColor: '#000'
}
}
}, {
name: 'front',
type: 'bar',
data,
xAxisIndex: 1,
z: 3,
itemStyle: {
normal: {
barBorderRadius: 5
}
}
}],
animationEasing: 'elasticOut',
animationEasingUpdate: 'elasticOut',
animationDelay(idx) {
return idx * 20
},
animationDelayUpdate(idx) {
return idx * 20
}
})
}
}
}
</script>

View File

@@ -1,230 +0,0 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import {computed, nextTick, onMounted, ref, watch} from "vue";
import {EChartsType} from "echarts/core";
import {useStore} from "vuex";
// 在setup中获取store
const store = useStore()
let props = defineProps({
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
})
const isCollapse = computed(()=>{
return store.state.app.isCollapse
})
let chart:EChartsType;
const initChart =()=> {
let chart = echarts.init(document.getElementById(props.id))
chart.setOption({
backgroundColor: '#394056',
title: {
top: 20,
text: 'Requests',
textStyle: {
fontWeight: 'normal',
fontSize: 16,
color: '#F1F1F3'
},
left: '1%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
legend: {
top: 20,
icon: 'rect',
itemWidth: 14,
itemHeight: 5,
itemGap: 13,
data: ['CMCC', 'CTCC', 'CUCC'],
right: '4%',
textStyle: {
fontSize: 12,
color: '#F1F1F3'
}
},
grid: {
top: 100,
left: '2%',
right: '2%',
bottom: '2%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#57617B'
}
},
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
}],
yAxis: [{
type: 'value',
name: '(%)',
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#57617B'
}
},
axisLabel: {
margin: 10,
textStyle: {
fontSize: 14
}
},
splitLine: {
lineStyle: {
color: '#57617B'
}
}
}],
series: [{
name: 'CMCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
}, {
name: 'CTCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(0, 136, 212, 0.3)'
}, {
offset: 0.8,
color: 'rgba(0, 136, 212, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(0,136,212)',
borderColor: 'rgba(0,136,212,0.2)',
borderWidth: 12
}
},
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
}, {
name: 'CUCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(219, 50, 51, 0.3)'
}, {
offset: 0.8,
color: 'rgba(219, 50, 51, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(219,50,51)',
borderColor: 'rgba(219,50,51,0.2)',
borderWidth: 12
}
},
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
}]
})
return chart
}
watch(isCollapse,()=>{
setTimeout(()=>{
chart&&chart.resize()
},300)
})
onMounted(()=>{
chart = initChart()
window.addEventListener('resize',function (){
chart&&chart.resize()
})
})
</script>

View File

@@ -1,271 +0,0 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xData = (function() {
const data = []
for (let i = 1; i < 13; i++) {
data.push(i + 'month')
}
return data
}())
this.chart.setOption({
backgroundColor: '#344b58',
title: {
text: 'statistics',
x: '20',
top: '20',
textStyle: {
color: '#fff',
fontSize: '22'
},
subtextStyle: {
color: '#90979c',
fontSize: '16'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
textStyle: {
color: '#fff'
}
}
},
grid: {
left: '5%',
right: '5%',
borderWidth: 0,
top: 150,
bottom: 95,
textStyle: {
color: '#fff'
}
},
legend: {
x: '5%',
top: '10%',
textStyle: {
color: '#90979c'
},
data: ['female', 'male', 'average']
},
calculable: true,
xAxis: [{
type: 'category',
axisLine: {
lineStyle: {
color: '#90979c'
}
},
splitLine: {
show: false
},
axisTick: {
show: false
},
splitArea: {
show: false
},
axisLabel: {
interval: 0
},
data: xData
}],
yAxis: [{
type: 'value',
splitLine: {
show: false
},
axisLine: {
lineStyle: {
color: '#90979c'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: 0
},
splitArea: {
show: false
}
}],
dataZoom: [{
show: true,
height: 30,
xAxisIndex: [
0
],
bottom: 30,
start: 10,
end: 80,
handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
handleSize: '110%',
handleStyle: {
color: '#d3dee5'
},
textStyle: {
color: '#fff' },
borderColor: '#90979c'
}, {
type: 'inside',
show: true,
height: 15,
start: 1,
end: 35
}],
series: [{
name: 'female',
type: 'bar',
stack: 'total',
barMaxWidth: 35,
barGap: '10%',
itemStyle: {
normal: {
color: 'rgba(255,144,128,1)',
label: {
show: true,
textStyle: {
color: '#fff'
},
position: 'insideTop',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
709,
1917,
2455,
2610,
1719,
1433,
1544,
3285,
5208,
3372,
2484,
4078
]
},
{
name: 'male',
type: 'bar',
stack: 'total',
itemStyle: {
normal: {
color: 'rgba(0,191,183,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
327,
1776,
507,
1200,
800,
482,
204,
1390,
1001,
951,
381,
220
]
}, {
name: 'average',
type: 'line',
stack: 'total',
symbolSize: 10,
symbol: 'circle',
itemStyle: {
normal: {
color: 'rgba(252,230,48,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
1036,
3693,
2962,
3810,
2519,
1915,
1748,
4675,
6209,
4323,
2865,
4298
]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,120 @@
<template>
<textarea ref="codeEditor" placeholder="请输入..."></textarea>
</template>
<script>
import { defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
// codemirror基础资源引入
import _CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// language
import 'codemirror/mode/javascript/javascript.js'
// theme css
import 'codemirror/theme/monokai.css'
// 折叠资源引入:开始
import 'codemirror/addon/fold/foldgutter.css'
import 'codemirror/addon/fold/foldcode.js'
import 'codemirror/addon/fold/brace-fold.js'
import 'codemirror/addon/fold/comment-fold.js'
import 'codemirror/addon/fold/indent-fold.js'
import 'codemirror/addon/fold/foldgutter.js'
// 折叠资源引入:结束
// 搜索资源引入:开始
import 'codemirror/addon/scroll/annotatescrollbar.js'
import 'codemirror/addon/search/matchesonscrollbar.js'
import 'codemirror/addon/search/match-highlighter.js'
import 'codemirror/addon/search/jump-to-line.js'
import 'codemirror/addon/dialog/dialog.js'
import 'codemirror/addon/dialog/dialog.css'
import 'codemirror/addon/search/searchcursor.js'
import 'codemirror/addon/search/search.js'
// 搜索资源引入:结束
// 启用placeholder
import 'codemirror/addon/display/placeholder.js'
import 'codemirror/addon/selection/active-line.js' //光标行背景高亮配置里面也需要styleActiveLine设置为true
const CodeMirror = window.CodeMirror || _CodeMirror
export default defineComponent({
props: {
modelValue: String,
defaultValue: String,
readOnly: {
type: Boolean,
default: false,
},
},
setup(props, context) {
const { modelValue, defaultValue, readOnly } = toRefs(props)
const codeEditor = ref()
let editor
watch(modelValue, () => {
if (null != editor && modelValue.value && modelValue.value !== editor.getValue()) {
// 触发v-model的双向绑定
editor.setValue(modelValue.value)
}
})
watch(readOnly, () => {
if (null != editor) {
editor.setOption('readOnly', readOnly.value)
}
})
onMounted(() => {
editor = CodeMirror.fromTextArea(codeEditor.value, {
value: modelValue.value,
// mime: "text/javascript",
mode: 'application/json',
indentWithTabs: false, // 在缩进时,是否需要把 n*tab宽度个空格替换成n个tab字符默认为false
smartIndent: true, // 自动缩进设置是否根据上下文自动缩进和上一行相同的缩进量。默认为true
lineNumbers: true, // 是否在编辑器左侧显示行号
matchBrackets: true, // 括号匹配
readOnly: readOnly.value,
// 启用代码折叠相关功能:开始
foldGutter: true,
lineWrapping: true, //是否自动换行
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
// 启用代码折叠相关功能:结束
styleActiveLine: false, // 光标行高亮
})
// 监听编辑器的change事件
editor.on('change', () => {
// 触发v-model的双向绑定
context.emit('update:modelValue', editor.getValue())
})
if (defaultValue.value) {
editor.setValue(defaultValue.value)
}
})
onBeforeUnmount(() => {
if (null !== editor) {
editor.toTextArea()
editor = null
}
})
return { codeEditor }
},
})
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss">
.CodeMirror-wrap {
height: 100%;
}
.CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
white-space: nowrap;
background-color: transparent;
border-right: 1px solid #ddd;
}
</style>

View File

@@ -0,0 +1,5 @@
import CountTo from './index.vue'
export default CountTo
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.component('count-to', CountTo)
}

View File

@@ -0,0 +1,191 @@
<template>
<span>
{{ displayValue }}
</span>
</template>
<script lang="ts">
import { requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame.js'
export default {
props: {
startVal: {
type: Number,
required: false,
default: 0,
},
endVal: {
type: Number,
required: false,
default: 2017,
},
duration: {
type: Number,
required: false,
default: 3000,
},
autoplay: {
type: Boolean,
required: false,
default: true,
},
decimals: {
type: Number,
required: false,
default: 0,
validator(value) {
return value >= 0
},
},
decimal: {
type: String,
required: false,
default: '.',
},
separator: {
type: String,
required: false,
default: ',',
},
prefix: {
type: String,
required: false,
default: '',
},
suffix: {
type: String,
required: false,
default: '',
},
useEasing: {
type: Boolean,
required: false,
default: true,
},
easingFn: {
type: Function,
default(t, b, c, d) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
},
},
},
data() {
return {
localStartVal: this.startVal,
displayValue: this.formatNumber(this.startVal),
printVal: null,
paused: false,
localDuration: this.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null,
}
},
computed: {
countDown() {
return this.startVal > this.endVal
},
},
watch: {
startVal() {
if (this.autoplay) {
this.start()
}
},
endVal() {
if (this.autoplay) {
this.start()
}
},
},
mounted() {
if (this.autoplay) {
this.start()
}
this.$emit('mountedCallback')
},
unmounted() {
cancelAnimationFrame(this.rAF)
},
methods: {
start() {
this.localStartVal = this.startVal
this.startTime = null
this.localDuration = this.duration
this.paused = false
this.rAF = requestAnimationFrame(this.count)
},
pauseResume() {
if (this.paused) {
this.resume()
this.paused = false
} else {
this.pause()
this.paused = true
}
},
pause() {
cancelAnimationFrame(this.rAF)
},
resume() {
this.startTime = null
this.localDuration = +this.remaining
this.localStartVal = +this.printVal
requestAnimationFrame(this.count)
},
reset() {
this.startTime = null
cancelAnimationFrame(this.rAF)
this.displayValue = this.formatNumber(this.startVal)
},
count(timestamp) {
if (!this.startTime) this.startTime = timestamp
this.timestamp = timestamp
const progress = timestamp - this.startTime
this.remaining = this.localDuration - progress
if (this.useEasing) {
if (this.countDown) {
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
} else {
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
}
} else {
if (this.countDown) {
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
} else {
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
}
}
if (this.countDown) {
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal
} else {
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal
}
this.displayValue = this.formatNumber(this.printVal)
if (progress < this.localDuration) {
this.rAF = requestAnimationFrame(this.count)
} else {
this.$emit('callback')
}
},
isNumber(val) {
return !isNaN(parseFloat(val))
},
formatNumber(num) {
num = num.toFixed(this.decimals)
num += ''
const x = num.split('.')
let x1 = x[0]
const x2 = x.length > 1 ? this.decimal + x[1] : ''
const rgx = /(\d+)(\d{3})/
if (this.separator && !this.isNumber(this.separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + this.separator + '$2')
}
}
return this.prefix + x1 + x2 + this.suffix
},
},
}
</script>

View File

@@ -6,19 +6,21 @@ let cancelAnimationFrame
const isServer = typeof window === 'undefined'
if (isServer) {
requestAnimationFrame = function() {
requestAnimationFrame = function () {
return
}
cancelAnimationFrame = function() {
cancelAnimationFrame = function () {
return
}
} else {
requestAnimationFrame = window.requestAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame
let prefix
// 通过遍历各浏览器前缀来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
// 通过遍历各浏览器前缀来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
for (let i = 0; i < prefixes.length; i++) {
if (requestAnimationFrame && cancelAnimationFrame) { break }
if (requestAnimationFrame && cancelAnimationFrame) {
break
}
prefix = prefixes[i]
requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame']
cancelAnimationFrame = cancelAnimationFrame || window[prefix + 'CancelAnimationFrame'] || window[prefix + 'CancelRequestAnimationFrame']
@@ -26,7 +28,7 @@ if (isServer) {
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame则会退到setTimeout
if (!requestAnimationFrame || !cancelAnimationFrame) {
requestAnimationFrame = function(callback) {
requestAnimationFrame = function (callback) {
const currTime = new Date().getTime()
// 为了使setTimteout的尽可能的接近每秒60帧的效果
const timeToCall = Math.max(0, 16 - (currTime - lastTime))
@@ -37,7 +39,7 @@ if (isServer) {
return id
}
cancelAnimationFrame = function(id) {
cancelAnimationFrame = function (id) {
window.clearTimeout(id)
}
}

View File

@@ -0,0 +1,269 @@
<template>
<div ref="chartsRef" class="echarts" />
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core'
import { onMounted, ref } from 'vue'
const chartsRef = ref<HTMLElement | null>()
let colorList = ['#46ea91', '#2ba0ff', '#ed593b', '#7357ff', '#f2d750']
const options = {
legend: {
icon: 'circle',
top: '5%',
right: '5%',
itemWidth: 6,
itemGap: 5,
textStyle: {
color: '#fff',
padding: [3, 0, 0, 0],
},
},
tooltip: {
trigger: 'axis',
},
grid: {
top: '14%',
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true,
},
xAxis: [
{
type: 'category',
data: ['1', '2', '3', '4', '5', '6', '7', '8'],
axisLine: {
lineStyle: {
color: '#33BBFF',
},
},
axisTick: {
show: false,
},
axisLabel: {
interval: 0,
textStyle: {
color: '#5FBBEB',
},
// 默认x轴字体大小
fontSize: 12,
// margin:文字到x轴的距离
margin: 10,
},
axisPointer: {
label: {
// padding: [11, 5, 7],
padding: [0, 0, 0, 0],
// 这里的margin和axisLabel的margin要一致!
margin: 10,
// 移入时的字体大小
fontSize: 12,
backgroundColor: 'rgba(0,0,0,0)',
},
},
boundaryGap: false,
},
],
yAxis: [
{
name: '单位/件',
axisTick: {
show: false,
},
axisLine: {
show: true,
lineStyle: {
color: '#05D5FF',
},
},
axisLabel: {
textStyle: {
color: '#5FBBEB',
},
},
splitLine: {
show: false,
},
},
],
series: [
{
name: '咨询',
type: 'line',
data: [100, 20, 30, 102, 15, 30, 20, 18],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#90ffc6',
},
{
offset: 1,
color: '#46ea91',
},
]),
shadowColor: 'rgba(144, 255, 198, .3)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[0],
borderColor: colorList[0],
},
},
},
{
name: '求助',
type: 'line',
data: [20, 12, 11, 14, 25, 16, 10, 20],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#67bcfc',
},
{
offset: 1,
color: '#2ba0ff',
},
]),
shadowColor: 'rgba(105, 188, 252,.3)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[1],
borderColor: colorList[1],
},
},
},
{
name: '无效',
type: 'line',
data: [150, 120, 170, 140, 100, 160, 110, 110],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#fc937e ',
},
{
offset: 1,
color: '#ed593b',
},
]),
shadowColor: 'rgb(252, 147, 126,.3)',
shadowBlur: 2,
shadowOffsetY: 2,
},
itemStyle: {
normal: {
color: colorList[2],
borderColor: colorList[2],
},
},
},
{
name: '投诉举报',
type: 'line',
data: [200, 80, 100, 30, 60, 50, 110, 20],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#a390ff',
},
{
offset: 1,
color: '#7357ff',
},
]),
shadowColor: 'rgba(115, 87, 255, .1)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[3],
borderColor: colorList[3],
},
},
},
{
name: '建议',
type: 'line',
data: [20, 80, 150, 30, 60, 50, 50, 20],
symbolSize: 1,
symbol: 'circle',
smooth: true,
showSymbol: false,
lineStyle: {
width: 2,
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
{
offset: 0,
color: '#ffeb86',
},
{
offset: 1,
color: '#f2d750',
},
]),
shadowColor: 'rgba(255, 235, 134, .5)',
shadowBlur: 5,
shadowOffsetY: 5,
},
itemStyle: {
normal: {
color: colorList[4],
borderColor: colorList[4],
},
},
},
],
}
let chart: EChartsType
const initChart = () => {
const chart = echarts.init(chartsRef.value)
chart.setOption(options)
return chart
}
onMounted(() => {
chart = initChart()
window.addEventListener('resize', function () {
chart && chart.resize()
})
})
</script>
<style lang="scss" scoped>
.echarts {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,117 @@
<template>
<div ref="chartsRef" class="echarts" />
</template>
<script setup lang="ts">
import BarCharts from './components/bar.vue'
import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core'
import { onMounted, ref, reactive } from 'vue'
const chartsRef = ref<HTMLElement | null>()
const data = [154, 230, 224, 218, 135, 147, 260]
const color = ['#fa796f', '#54c1fb', '#ca6cd4', '#59dcc1', '#09a4ea', '#e98f4d', '#ea8e49']
const dataOptions = []
data.forEach((item, index) => {
let obj = {
value: data[index],
itemStyle: {
color: color[index],
},
}
dataOptions.push(obj)
})
const options = {
color,
grid: {
top: '10%',
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.7)',
borderWidth: 0,
borderColor: 'rgba(0,0,0,0.7)',
formatter: (name, val) => {
const tipHtml = `
<div class="m-info" style=" opacity: 0.95;font-size: 12px; color: white;" >
<div class="title" ></div>
<div class="title" >完成占比${name[0].value}</div>
</div>`
return tipHtml
},
},
yAxis: {
type: 'value',
// 设置坐标轴的 文字样式
axisLabel: {
color: '#bbdaff',
margin: 20, // 刻度标签与轴线之间的距离。
},
axisTick: {
// 取消坐标轴刻度线
show: false,
},
// 坐标轴轴线相关设置。
splitLine: {
lineStyle: {
color: '#2d5baf',
},
},
},
xAxis: {
type: 'category',
splitLine: {
show: false,
},
// 坐标轴轴线相关设置。
axisLine: {
lineStyle: {
color: '#2d5baf',
},
},
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisLabel: {
// 设置坐标轴的 文字样式
color: '#bbdaff',
margin: 20, // 刻度标签与轴线之间的距离。
},
axisTick: {
// 取消坐标轴刻度线
show: false,
},
},
series: [
{
data: dataOptions,
type: 'bar',
barMaxWidth: 18,
markLine: {
silent: true,
},
},
],
}
let chart: EChartsType
const initChart = () => {
const chart = echarts.init(chartsRef.value)
chart.setOption(options)
return chart
}
onMounted(() => {
chart = initChart()
window.addEventListener('resize', function () {
chart && chart.resize()
})
})
</script>
<style lang="scss" scoped>
.echarts {
height: 100%;
width: 100%;
}
</style>

View File

@@ -0,0 +1,111 @@
<template>
<div ref="chartsRef" class="echarts" />
</template>
<script setup lang="ts">
import BarCharts from './components/bar.vue'
import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core'
import { onMounted, ref, reactive } from 'vue'
const chartsRef = ref<HTMLElement | null>()
const options = {
grid: {
top: '10%',
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.7)',
borderWidth: 0,
borderColor: 'rgba(0,0,0,0.7)',
formatter: (name, val) => {
const tipHtml = `
<div class="m-info" style=" opacity: 0.95;font-size: 12px; color: white;" >
<div class="title" ></div>
<div class="title" >完成占比${name[0].data}</div>
</div>`
return tipHtml
},
},
yAxis: {
// 设置坐标轴的 文字样式
axisLabel: {
color: '#bbdaff',
margin: 20, // 刻度标签与轴线之间的距离。
},
// 坐标轴轴线相关设置。
splitLine: {
lineStyle: {
color: '#2d5baf',
},
},
},
xAxis: {
splitLine: {
show: false,
},
// 坐标轴轴线相关设置。
axisLine: {
lineStyle: {
color: '#2d5baf',
},
},
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisLabel: {
// 设置坐标轴的 文字样式
color: '#bbdaff',
margin: 20, // 刻度标签与轴线之间的距离。
},
boundaryGap: false, // 设置坐标轴两边的留白 ,从刻度原点开始,
axisTick: {
// 取消坐标轴刻度线
show: false,
},
},
series: [
{
data: [154, 230, 224, 218, 135, 147, 260],
type: 'line',
// smooth:false, //关键点为true是不支持虚线的实线就用true
symbolSize: 12, // 拐点圆的大小
symbol: 'circle',
markLine: {
silent: true,
},
itemStyle: {
normal: {
color: '#920783', // 设置 symbol的颜色
lineStyle: {
width: 3,
color: '#920783',
type: 'solid', // 'dotted'虚线 'solid'实线
},
},
},
},
],
}
let chart: EChartsType
const initChart = () => {
const chart = echarts.init(chartsRef.value)
chart.setOption(options)
return chart
}
onMounted(() => {
chart = initChart()
window.addEventListener('resize', function () {
chart && chart.resize()
})
})
</script>
<style lang="scss" scoped>
.echarts {
height: 100%;
width: 100%;
}
</style>

View File

@@ -0,0 +1,38 @@
export const cityIconData = [
{
adcode: 650000,
name: '新疆维吾尔自治区',
value: [88.999903, 43.607078],
},
{
// adcode: 150000,
name: '内蒙古自治区',
value: [119.24787, 42.205741],
},
,
{
// adcode: 150000,
name: '内蒙古自治区',
value: [110.627161, 41.16628],
},
{
// adcode: 540000,
name: '西藏自治区',
value: [89.483714, 30.251951],
},
{
// adcode: 630000,
name: '青海省',
value: [102.064693, 37.084008],
},
{
// adcode: 530000,
name: '云南省',
value: [102.187665, 25.083688],
},
{
// adcode: 610000,
name: '陕西省',
value: [109.20663, 35.018625],
},
]

View File

@@ -0,0 +1,332 @@
<template>
<div :id="id" :class="className" :style="{ height: height, width: width }" />
</template>
<script lang="ts" setup>
import { geoJson } from './map.js'
import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core'
import { onMounted, onUnmounted } from 'vue'
import { cityIconData } from './data.js'
import logo from '@/assets/image/logo.png'
const props = defineProps({
className: {
type: String,
default: 'chart',
},
config: {
type: Object,
default: () => {},
},
id: {
type: String,
default: 'chart',
},
width: {
type: String,
default: '200px',
},
height: {
type: String,
default: '200px',
},
})
var geoGpsMap = [109.1162, 34.2004]
var geoCoordMap = {
江苏: [118.8062, 31.9208],
内蒙古: [110.3467, 41.4899],
辽宁: [123.1238, 42.1216],
陕西: [109.1162, 34.2004],
甘肃: [103.5901, 36.3043],
青海: [101.4038, 36.8207],
新疆: [87.9236, 43.5883],
河南: [113.4668, 34.6234],
西藏: [91.091762, 30.037072],
浙江: [119.5313, 29.8773],
福建: [119.4543, 25.9222],
湖南: [113.0823, 28.2568],
四川: [113.0823, 28.2568],
云南: [102.9199, 25.4663],
广东: [113.12244, 23.009505],
海南: [110.3893, 19.8516],
}
var value = {
江苏: 10000,
内蒙古: 10000,
辽宁: 10000,
陕西: 10000,
福建: 10000,
甘肃: 10000,
青海: 10000,
新疆: 10000,
湖北: 10000,
浙江: 10000,
河南: 10000,
湖南: 10000,
云南: 10000,
广东: 10000,
海南: 10000,
西藏: 10000,
}
var colors = '#f9b207'
var year = ['长春', '长春', '青岛', '青岛', '成都', '成都']
var mapData = []
/* 柱子Y名称 */
var categoryData = []
var barData = []
for (var key in geoCoordMap) {
mapData.push({
year: '陕西',
name: key,
value: value[key] / 100,
value1: value[key] / 100,
})
}
mapData.sort(function sortNumber(a, b) {
return a.value - b.value
})
for (var j = 0; j < mapData.length; j++) {
barData.push(mapData[j].value1)
categoryData.push(mapData[j].name)
}
echarts.registerMap('china', geoJson)
var convertData = function (data) {
var res = []
for (var i = 0; i < data.length; i++) {
var geoCoord = geoCoordMap[data[i].name]
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value),
})
}
}
return res
}
var convertToLineData = function (data, gps) {
var res = []
for (var i = 0; i < data.length; i++) {
var dataItem = data[i]
var toCoord = geoCoordMap[dataItem.name]
// debugger;
var fromCoord = gps // 郑州
// var toCoord = geoGps[Math.random()*3];
if (fromCoord && toCoord) {
res.push([
{
coord: fromCoord,
value: dataItem.value,
},
{
coord: toCoord,
},
])
}
}
return res
}
const options = {
tooltip: {
trigger: 'item',
formatter(val) {
// console.log('val==========',val)
},
},
// backgroundColor: '#001540',// 设置背景色
geo: {
show: true,
map: 'china',
roam: true,
zoom: 1,
center: [101.4038, 36.8207],
label: {
emphasis: {
show: false,
},
},
itemStyle: {
normal: {
borderColor: 'rgba(147, 235, 248, 1)',
borderWidth: 1,
areaColor: {
type: 'radial',
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [
{
offset: 0,
color: 'rgba(147, 235, 248, 0)', // 0% 处的颜色
},
{
offset: 1,
color: 'rgba(147, 235, 248, .2)', // 100% 处的颜色
},
],
globalCoord: false, // 缺省为 false
},
shadowColor: 'rgba(128, 217, 248, 1)',
// shadowColor: 'rgba(255, 255, 255, 1)',
shadowOffsetX: -2,
shadowOffsetY: 2,
shadowBlur: 10,
},
emphasis: {
areaColor: '#389BB7',
borderWidth: 0,
},
},
},
series: [
// 地图?
{
type: 'map',
map: 'china',
geoIndex: 0,
aspectScale: 0.75, // 长宽比
showLegendSymbol: false, // 存在legend时显示
label: {
normal: {
show: false,
},
emphasis: {
show: false,
textStyle: {
color: '#fff',
},
},
},
roam: true,
itemStyle: {
normal: {
areaColor: '#031525',
borderColor: '#FFFFFF',
},
emphasis: {
areaColor: '#2B91B7',
},
},
animation: false,
},
// 地图点的动画效果
{
// name: 'Top 5',
type: 'effectScatter',
coordinateSystem: 'geo',
data: convertData(
mapData
.sort(function (a, b) {
return b.value - a.value
})
.slice(0, 20),
),
symbolSize: function (val) {
return val[2] / 10
},
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
},
hoverAnimation: true,
label: {
normal: {
formatter: '{b}',
position: 'right',
show: true,
},
},
itemStyle: {
normal: {
color: colors,
shadowBlur: 10,
shadowColor: colors,
},
},
zlevel: 1,
},
// 地图线的动画效果
{
type: 'lines',
zlevel: 2,
effect: {
show: true,
period: 4, // 箭头指向速度,值越小速度越快
trailLength: 0.02, // 特效尾迹长度[0,1]值越大,尾迹越长重
symbol: 'arrow', // 箭头图标
symbolSize: 3, // 图标大小
},
lineStyle: {
normal: {
color: colors,
width: 0.1, // 尾迹线条宽度
opacity: 0.5, // 尾迹线条透明度
curveness: 0.3, // 尾迹线条曲直度
},
},
data: convertToLineData(mapData, geoGpsMap),
},
{
type: 'scatter',
zlevel: 16,
coordinateSystem: 'geo',
symbolSize: 30,
symbol: `image://${logo}`,
data: cityIconData,
rippleEffect: {
period: 4,
brushType: 'stroke',
scale: 4,
},
tooltip: {
trigger: 'item',
padding: 0,
borderColor: 'black',
background: 'rgba(0,0,0,0.7)',
textStyle: {
fontSize: 20,
},
formatter(val) {
console.log('val=======', val)
let tipHtml = `
<div class="m-info" style=" opacity: 0.95;background: rgba(25,27,29,1);" >
<div class="title" style="padding-left: 12px;
padding-top: 10px;
padding-bottom: 10px;
background: rgba(25,27,29,1);font-size: 20px;color: white;padding-right: 20px">${val.name}</div>
<!-- <div class="content" style=" padding: 12px; background: rgba(0,2,4,1);">-->
<!-- <div style=" font-size: 22px; color: #ff0000;">258944</div>-->
<!-- </div>-->
</div>`
return tipHtml
},
},
},
],
}
let chart: EChartsType
const initChart = () => {
const chart = echarts.init(document.getElementById(props.id))
chart.setOption(options)
return chart
}
onUnmounted(() => {
chart && chart.dispose()
})
onMounted(() => {
chart = initChart()
window.addEventListener('resize', function () {
chart && chart.resize()
})
})
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
<template>
<div ref="chartsRef" class="echarts" />
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core'
import { onMounted, ref } from 'vue'
const chartsRef = ref<HTMLElement | null>()
var trafficWay = [
{
name: 'Ⅰ类',
value: 20,
},
{
name: 'Ⅱ类',
value: 20,
},
{
name: 'Ⅲ类',
value: 20,
},
{
name: 'Ⅳ类',
value: 20,
},
{ name: 'Ⅴ类', value: 20 },
{ name: '劣Ⅴ类', value: 20 },
]
var data = []
var color = ['#fd566a', '#9787ff', '#fdb36a', '#fdd56a', '#6da7ff', '#63e1f2', '#ff3000']
for (var i = 0; i < trafficWay.length; i++) {
data.push(
{
value: trafficWay[i].value,
name: trafficWay[i].name,
itemStyle: {
normal: {
borderWidth: 5,
shadowBlur: 20,
borderColor: color[i],
shadowColor: color[i],
},
},
},
{
value: 2,
name: '',
itemStyle: {
normal: {
label: {
show: false,
},
labelLine: {
show: false,
},
color: 'rgba(0, 0, 0, 0)',
borderColor: 'rgba(0, 0, 0, 0)',
borderWidth: 0,
},
},
},
)
}
var seriesOption = [
{
name: '',
type: 'pie',
clockWise: false,
radius: [105, 109],
hoverAnimation: false,
itemStyle: {
normal: {
label: {
show: true,
position: 'outside',
formatter: function (params) {
var percent = 0
var total = 0
for (var i = 0; i < trafficWay.length; i++) {
total += trafficWay[i].value
}
percent = ((params.value / total) * 100).toFixed(0)
if (params.name !== '') {
return params.name + '\t' + percent + '%'
} else {
return ''
}
},
},
labelLine: {
length: 10,
length2: 20,
show: true,
color: '#00ffff',
},
},
},
data: data,
},
]
let options = {
color: color,
title: [
{
text: '水质监测',
top: '44%',
textAlign: 'center',
left: '49.50%',
backgroundColor: '#163253',
borderRadius: 100,
textStyle: {
color: '#fff',
fontSize: 20,
fontWeight: '400',
},
},
{
text: '水环境监测站',
top: '49%',
textAlign: 'center',
left: '49.50%',
textStyle: {
color: '#fff',
fontSize: 20,
fontWeight: '400',
},
},
{
text: '9',
top: '53%',
textAlign: 'center',
left: '48%',
textStyle: {
color: '#f6ea2f',
fontSize: 25,
fontWeight: '800',
fontStyle: 'italic',
},
},
{
text: '个',
top: '53.5%',
textAlign: 'center',
left: '50.5%',
textStyle: {
color: '#fff',
fontSize: 16,
fontWeight: '400',
},
},
],
tooltip: {
show: false,
},
toolbox: {
show: false,
},
series: seriesOption,
}
let chart: EChartsType
const initChart = () => {
const chart = echarts.init(chartsRef.value)
chart.setOption(options)
return chart
}
onMounted(() => {
chart = initChart()
window.addEventListener('resize', function () {
chart && chart.resize()
})
})
</script>
<style lang="scss" scoped>
.echarts {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,18 @@
.m-container-layout {
width: 100%;
height: 100%;
display: flex;
padding: 10px 12px;
box-sizing: border-box;
.m-container-layout-inner {
flex: 1;
display: flex;
flex-direction: column;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
background: white;
padding: 20px;
width: 100%;
height: 100%;
box-sizing: border-box;
}
}

View File

@@ -0,0 +1,13 @@
<template>
<div class="m-container-layout">
<div class="m-container-layout-inner">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@@ -0,0 +1,111 @@
<template>
<div v-if="isShow" ref="rightMenu" class="g-right-click-menu" :style="style">
<div v-for="(item, index) in data" :key="index" class="operating" @click.stop="operatingRightAction($event, item)">{{
item.label
}}</div>
</div>
</template>
<script lang="ts">
export default {
props: {
data: {
type: Array,
default: () => [],
},
left: {
type: Number,
default: 0,
},
type: {
type: Number,
default: 1,
},
dataInfo: {
type: Object,
default: () => {},
},
top: {
type: Number,
default: 0,
},
isViewInfo: {
type: Boolean,
default: true,
},
},
data() {
return {
isShow: false,
}
},
computed: {
style() {
let clientHeight = document.body.clientHeight
let y = this.top
if (clientHeight - y < 100) {
return `left:${this.left}px;bottom:${clientHeight - y}px`
} else {
return `left:${this.left}px;top:${this.top}px`
}
},
},
watch: {
left: {
handler(newName, oldName) {
if (newName) {
this.isShow = true
}
},
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
// immediate: true
},
},
mounted() {
let _self = this
window.addEventListener('click', function () {
_self.isShow = false
})
window.addEventListener('mousedown', function (e) {
if (e.which === 3) {
_self.isShow = false
}
})
},
methods: {
/**
* @func 点击操作
* @param val 1、置顶/取消置顶 2、开启/关闭免打扰 3、开启/关闭星标 4、删除会话
*/
operatingRightAction($event, val) {
this.$emit('ok', $event, val)
this.isShow = false
},
},
}
</script>
<style lang="scss" scoped>
.g-right-click-menu {
left: 0;
background: white;
width: 148px;
height: auto;
position: fixed;
//border: 1px solid #c4c4c4;
z-index: 9;
//box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
.operating {
font-size: 12px;
padding-left: 23px;
cursor: pointer;
line-height: 27px;
}
.operating:hover {
background: #e2e2e2;
}
.last-delete {
border-top: 1px solid #ededed;
}
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<el-form-item v-if="config.valueType === 'input'" :label="config?.label" style="width: 100%">
<el-input v-model="value" v-bind="$attrs" />
</el-form-item>
<el-form-item v-if="config.valueType === 'select'" :label="config?.label" style="width: 100%">
<el-select v-model="value" v-bind="$attrs" style="width: 100%">
<el-option v-for="item in config.options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item v-if="config.valueType === 'date-picker'" :label="config?.label" style="width: 100%">
<el-date-picker v-model="value" v-bind="$attrs" style="width: 100%" />
</el-form-item>
<el-form-item v-if="config.valueType === 'cascader'" :label="config?.label" style="width: 100%">
<el-cascader v-model="value" v-bind="$attrs" style="width: 100%" />
</el-form-item>
<el-form-item v-if="config.valueType === 'time-select'" :label="config?.label" style="width: 100%">
<el-time-select v-model="value" v-bind="$attrs" style="width: 100%" />
</el-form-item>
</template>
<script setup lang="ts">
import { computed } from 'vue'
type ConfigType = {
modelValue?: any
config?: any
}
const props = defineProps<ConfigType>()
const emits = defineEmits<{
(e: 'update:modelValue', value: any): void
}>()
const value = computed({
get() {
return props?.modelValue
},
set(value) {
emits('update:modelValue', value)
},
})
</script>
<script lang="ts">
export default {
inheritAttrs: false,
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,117 @@
<template>
<div class="advancedForm">
<el-form ref="ruleFormRef" :inline="true" :label-position="'right'" :model="formParams" class="form-inline">
<el-row :class="{ 'not-show': byHeight && !isExpand }" :gutter="gutterWidth">
<template v-for="(item, index) in columns">
<el-col v-if="item.valueType" v-show="byHeight ? true : index < showRow * 3 || isExpand" :span="item.span">
<BaseFormItem :key="index" v-bind="item.attrs" v-model="item.value" :config="item" />
</el-col>
</template>
</el-row>
</el-form>
<div class="search-btn">
<el-button type="primary" @click="onSubmit">查询</el-button>
<el-button @click="resetForm(ruleFormRef)">重置</el-button>
<el-button v-if="columns.length > 3" link type="primary" @click="isExpand = !isExpand">
{{ isExpand ? '合并' : '展开' }}
<el-icon>
<arrow-down v-if="!isExpand" />
<arrow-up v-else />
</el-icon>
</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue'
import type { FormInstance } from 'element-plus'
import BaseFormItem from './components/BaseFormItem.vue'
const ruleFormRef = ref<FormInstance>()
let props = defineProps({
// 宽度
labelWidth: {
default: '100px',
},
gutterWidth: {
type: Number,
default: 24,
},
showRow: {
type: Number,
default: 1,
},
columns: {
type: Array,
default: () => [],
},
byHeight: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['submit', 'reset'])
// 收缩展开
const isExpand = ref(false)
const formParams = reactive({})
const initFormParams = () => {
for (let item of props.columns) {
formParams[item.name] = item?.value
}
}
// 进行遍历获取参数
const getFormParams = () => {
let searchParams = {}
for (let item of props.columns) {
searchParams[item.name] = item?.value
}
return searchParams
}
onMounted(() => {
initFormParams()
})
// 获取参数进行组装为 obj
const onSubmit = () => {
let searchParams = getFormParams()
emit('submit', searchParams)
}
// 重置参数
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
const keys = Object.keys(formParams)
keys.forEach((key) => {
let itemColums = props.columns.find((item) => item.name === key)
itemColums.value = formParams[key]
})
let searchParams = getFormParams()
emit('reset', searchParams)
}
</script>
<style lang="scss" scoped>
.advancedForm {
display: flex;
justify-content: space-between;
.form-inline {
flex: 1;
}
.el-form--inline .el-form-item {
width: 100%;
margin-right: 10px;
}
.search-btn {
margin-left: 40px;
}
.not-show {
height: 40px;
overflow: hidden;
}
}
</style>

View File

@@ -4,33 +4,32 @@
</svg>
</template>
<script>
import { computed } from "@vue/reactivity";
export default {
name: "baseSvgIcon",
props: {
iconClass: { type: String },
className: { type: String },
},
setup(props) {
const iconName = computed(() => {
return props.iconClass ? `#icon-${props.iconClass}` : "#icon";
});
const svgClass = computed(() => {
return props.className ? "svg-icon " + props.className : "svg-icon";
});
return { iconName, svgClass };
},
};
<script lang="ts">
import { computed } from 'vue'
export default {
name: 'BaseSvgIcon',
props: {
iconClass: { type: String },
className: { type: String },
},
setup(props) {
const iconName = computed(() => {
return props.iconClass ? `#icon-${props.iconClass}` : '#icon'
})
const svgClass = computed(() => {
return props.className ? 'svg-icon ' + props.className : 'svg-icon'
})
return { iconName, svgClass }
},
}
</script>
<style scoped lang="scss">
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<el-switch v-model="themeConfig.isDark" inline-prompt :active-icon="Sunny" :inactive-icon="Moon" @change="switchDark" />
</template>
<script setup lang="ts" name="switchDark">
import { Sunny, Moon } from '@element-plus/icons-vue'
import { computed, ref } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
// 设置信息
const themeConfig = computed(() => SettingStore.themeConfig)
// 切换暗黑模式
const switchDark = () => {
const body = document.documentElement as HTMLElement
if (themeConfig.value.isDark) body.setAttribute('class', 'dark')
else body.setAttribute('class', '')
}
</script>

View File

@@ -0,0 +1,251 @@
<template>
<div class="m-edit-table">
<div v-if="mode !== 'hide' && mode !== 'bottom'" style="margin-top: 15px; margin-bottom: 15px">
<el-button style="width: 100%" @click="add">
<el-icon style="margin-right: 4px"><plus /></el-icon> 添加一行数据</el-button
>
</div>
<el-table :data="transData" style="width: 100%" row-key="id" border>
<template v-for="item in columns">
<el-table-column
v-if="item.type"
:type="item.type"
:width="item.width"
:align="item.align"
:fixed="item.fixed"
:label="item.label"
/>
<el-table-column v-else :prop="item.name" :width="item.width" :align="item.align" :fixed="item.fixed" :label="item.label">
<template #default="scope">
<template v-if="!item.slot">
<template v-if="item.readonly">
{{ scope.row[item.name] }}
</template>
<template v-else-if="item.valueType === 'select'">
<el-select v-if="scope.row.edit" v-model="scope.row[item.name]" clearable :placeholder="`请选择`">
<el-option v-for="ite in item.options" :key="ite.value" :label="ite.label" :value="ite.value" />
</el-select>
<span v-else>{{ filterOption(item, scope) }}</span>
</template>
<template v-else-if="item.valueType === 'date'">
<el-date-picker
v-if="scope.row.edit"
v-model="scope.row[item.name]"
type="date"
value-format="YYYY-MM-DD"
clearable
placeholder="请选择"
/>
<span v-else>{{ scope.row[item.name] || '--' }}</span>
</template>
<template v-else>
<el-input v-if="scope.row.edit" v-model="scope.row[item.name]" clearable placeholder="请输入"></el-input>
<span v-else>{{ scope.row[item.name] || '--' }}</span>
</template>
</template>
<slot v-else :name="item.name" :item="item" :row="scope.row"></slot>
</template>
</el-table-column>
</template>
<el-table-column prop="operator" label="操作" width="250px" fixed="right">
<template #default="scope">
<el-button v-if="scope.row.edit" type="primary" size="small" icon="CircleCheckFilled" @click="confirmEdit(scope.row)">
保存
</el-button>
<el-button v-else type="primary" size="small" icon="Edit" @click="scope.row.edit = !scope.row.edit"> 编辑 </el-button>
<el-popover v-model:visible="scope.row.visible" trigger="click" placement="top" :width="160">
<p style="display: flex; align-items: center; margin-bottom: 10px">
<el-icon color="#faad14" style="margin-right: 10px"><warning-filled /></el-icon>
删除此行</p
>
<div style="text-align: right; margin: 0">
<el-button size="small" @click="scope.row.visible = false">取消</el-button>
<el-button size="small" type="primary" @click="deleteAction(scope.row)">确定</el-button>
</div>
<template #reference>
<el-button icon="Delete" type="danger" size="small" @click="deleteCurrent(scope.row)">删除</el-button>
</template>
</el-popover>
<el-button v-if="scope.row.edit" type="primary" size="small" icon="Edit" @click="cancelEdit(scope.row)"> 取消 </el-button>
</template>
</el-table-column>
</el-table>
<div v-if="mode !== 'hide' && mode !== 'top'" style="margin-top: 15px">
<el-button style="width: 100%" @click="add">
<el-icon style="margin-right: 4px"><plus /></el-icon> 添加一行数据</el-button
>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue'
import { deepObjClone } from '@/utils/index'
import { ElMessage, ElMessageBox } from 'element-plus'
import { reactive } from 'vue'
const emit = defineEmits(['del', 'add', 'onChange'])
let transData = ref([])
let props = defineProps({
columns: {
type: Array,
default: () => [],
},
data: {
type: Array,
default: () => [],
},
editableKeys: {
type: Array,
default: () => [],
},
mode: {
type: String,
default: 'bottom',
},
})
// 删除当前此行
const deleteCurrent = (row) => {
// console.log('----------',row)
// row.visible = true
}
const getData = () => {
let arr = deepObjClone(transData.value)
for (let item of arr) {
for (let attr in item) {
if (attr.includes('te__mp')) {
delete item[attr]
}
}
}
emit('onChange', arr)
}
let obj = {}
for (let item of props.columns) {
props.data.forEach((it) => {
if (!obj[item.name]) {
obj[item.name] = null
}
})
}
// 重置数据
const reset = () => {
transData.value = props.data
for (let item of transData.value) {
if (props.editableKeys.includes(item.id)) {
item.edit = true
}
}
getData()
}
onMounted(() => {
watch(
() => props.data,
(val) => {
// // 转换数据
transData.value = deepObjClone(val)
// 存储一个临时变量
for (let item of transData.value) {
if (props.editableKeys.includes(item.id)) {
item.edit = true
}
for (let attr in item) {
let temp = `${attr}te__mp`
item[temp] = item[attr]
}
}
},
{
immediate: true,
deep: true,
},
)
})
const visible = ref(false)
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
const listLoading = ref(false)
// 保存
const confirmEdit = (row) => {
row.edit = false
for (let attr in row) {
if (attr.includes('te__mp')) {
row[attr] = row[attr.replace('te__mp', '')]
}
}
getData()
}
// 取消
const cancelEdit = (row) => {
row.edit = !row.edit
for (let attr in row) {
if (attr !== 'edit') {
if (!attr.includes('te__mp')) {
row[attr] = row[attr + 'te__mp']
}
}
}
}
const deleteAction = (row) => {
row.visible = false
transData.value = transData.value.filter((item) => item.id !== row.id)
emit('del', row)
}
// 添加
const add = () => {
let id = ~~(Math.random() * 1000000).toFixed(0)
let obj1 = Object.assign({}, obj, {
id: id,
edit: true,
visible: false,
})
for (let attr in obj1) {
let temp = `${attr}te__mp`
obj1[temp] = obj1[attr]
}
if (props.mode === 'bottom') {
transData.value.push(obj1)
}
if (props.mode === 'top') {
transData.value.unshift(obj1)
}
}
const filterOption = (item, scope) => {
let obj = item.options.find((ite) => ite.value === scope.row[item.name])
if (obj) {
return obj.label
}
return '--'
}
defineExpose({
reset,
})
</script>
<style scoped>
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
.inline-edit-table {
width: 100%;
}
</style>

View File

@@ -0,0 +1,206 @@
<template>
<div class="zb-pro-table">
<div class="header">
<SearchForm :columns="baseFormColumns" @submit="onSubmit" />
</div>
<!----------底部---------------------->
<div class="footer">
<!-----------工具栏操作工具----------------->
<div class="operator">
<slot name="btn"></slot>
</div>
<!-- ------------表格--------------->
<div class="table">
<el-table
v-loading="loading"
class="zb-table"
:data="list"
:border="true"
@selection-change="(val) => emit('selection-change', val)"
>
<template v-for="item in columns">
<el-table-column v-if="item.slot" v-bind="{ ...item, ...{ prop: item.name } }">
<template #default="scope">
<slot :name="item.name" :item="item" :row="scope.row"></slot>
</template>
</el-table-column>
<el-table-column v-else v-bind="{ ...item, ...{ prop: item.name } }" />
</template>
</el-table>
</div>
<!-- ------------分页--------------->
<div class="pagination">
<el-pagination
v-model:currentPage="pagination.currentPage"
:page-size="10"
background
layout="total, sizes, prev, pager, next, jumper"
:total="data.length"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
const ruleFormRef = ref<FormInstance>()
const emit = defineEmits(['reset', 'onSubmit', 'selection-change'])
let props = defineProps({
columns: {
type: Array,
default: () => [],
},
data: {
type: Array,
default: () => [],
},
loading: {
type: Boolean,
default: false,
},
})
// 过滤调需要进行搜索选择的
const baseFormColumns = computed(() => {
return props.columns.filter((item) => item.valueType && item.search)
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
})
const currentPage = ref(1)
// 收缩展开
const isExpand = ref(false)
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
pagination.currentPage = val
}
const list = computed(() => {
let arr = JSON.parse(JSON.stringify(props.data))
return arr.splice((pagination.currentPage - 1) * 10, 10)
})
const listLoading = ref(false)
const confirmEdit = (row) => {
row.edit = false
}
const cancelEdit = (row) => {
row.edit = false
}
import { reactive } from 'vue'
let obj = {}
let search = []
for (let item of props.columns) {
if (item.inSearch) {
obj[item.name] = null
}
if (item.inSearch) {
search.push(item)
}
}
const formSearchData = ref(search)
const formInline = reactive(obj)
const onSubmit = () => {
emit('onSubmit', formInline)
}
const reset = (formEl: FormInstance | undefined) => {
formSearchData.value.forEach((item) => {
formInline[item.name] = null
})
emit('reset')
}
const deleteAction = (row) => {
ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
})
.then(() => {
list.value = list.value.filter((item) => item.id !== row.id)
ElMessage.success('删除成功')
})
.catch(() => {})
}
</script>
<style scoped lang="scss">
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
.zb-pro-table {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.header {
display: flex;
padding: 16px 16px 0 16px;
margin-bottom: 16px;
border-radius: 4px;
background: white;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
:deep(.advancedForm) {
flex: 1;
}
}
.footer {
flex: 1;
display: flex;
padding: 16px;
flex-direction: column;
border-radius: 4px;
overflow: hidden;
background: white;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
min-height: 300px;
.operator {
margin-bottom: 15px;
}
.table {
position: relative;
flex: 1;
}
.zb-table {
position: absolute;
height: 100%;
}
}
::v-deep {
.el-table__header th {
font-size: 15px;
font-weight: 700;
color: #252525;
}
}
.pagination {
width: 100%;
display: flex;
justify-content: center;
padding-top: 20px;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,210 @@
<template>
<div>
<div class="m-setting-fix">
<div class="item">
<div class="item-child" @click="operator(1)">
<el-icon size="30" color="#3698fd" style="margin-bottom: 8px"><brush /></el-icon>
主题配置
</div>
<div class="item-child item-child2" @click="operator(2)">
<el-icon size="30" color="#3698fd" style="margin-bottom: 8px"><Link /></el-icon>
源码拷贝
</div>
</div>
</div>
<el-drawer v-model="drawer" title="主题配置" size="300px">
<div class="theme-item">
<label>导航栏布局</label>
<el-select v-model="layout" placeholder="请选择" style="width: 150px" @change="(val) => changeSwitch('mode', val)">
<el-option label="纵向" value="vertical"></el-option>
<el-option label="横向" value="horizontal"></el-option>
<el-option label="分栏" value="columns"></el-option>
</el-select>
</div>
<div class="theme-item">
<label>主题颜色</label>
<el-color-picker v-model="primary" :predefine="predefineColor" @change="changePrimary" />
</div>
<div class="theme-item">
<label>暗黑模式</label>
<switch-dark></switch-dark>
</div>
<div class="theme-item">
<label>灰色模式</label>
<el-switch v-model="gray" @change="(val) => changeGrayWeak('gray', val)" />
</div>
<div class="theme-item">
<label>色弱模式</label>
<el-switch v-model="weak" @change="(val) => changeGrayWeak('weak', val)" />
</div>
<div class="theme-item">
<label>标签栏</label>
<el-switch v-model="showTag" @change="(val) => changeSwitch('showTag', val)" />
</div>
<div class="theme-item">
<label>侧边栏 Logo</label>
<el-switch v-model="showLogo" @change="(val) => changeSwitch('showLogo', val)" />
</div>
<div class="theme-item">
<label>保持一个子菜单的展开</label>
<el-switch v-model="uniqueOpened" @change="(val) => changeSwitch('uniqueOpened', val)" />
</div>
<div class="theme-item">
<label>固定header</label>
<el-switch v-model="fixedHeader" @change="(val) => changeSwitch('fixedHeader', val)" />
</div>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { openLoading, closeLoading } from '@/utils/element'
import SwitchDark from '@/components/SwitchDark/index.vue'
import { PRIMARY_COLOR } from '@/config/index'
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
const layout = ref(SettingStore.themeConfig.mode)
const showTag = ref(SettingStore.themeConfig.showTag)
const showLogo = ref(SettingStore.themeConfig.showLogo)
const uniqueOpened = ref(SettingStore.themeConfig.uniqueOpened)
const primary = ref(SettingStore.themeConfig.primary)
const fixedHeader = ref(SettingStore.themeConfig.fixedHeader)
const gray = ref(SettingStore.themeConfig.gray)
const weak = ref(SettingStore.themeConfig.weak)
const drawer = computed({
get() {
return SettingStore.themeConfig.showSetting
},
set() {
changeSwitch('showSetting', !SettingStore.themeConfig.showSetting)
},
})
// 预定义主题颜色
const predefineColor = ['#409EFF', '#1890ff', '#304156', '#212121', '#11a983', '#13c2c2', '#6959CD', '#f5222d']
const operator = (type) => {
switch (type) {
case 1:
drawer.value = true
return
case 2:
window.open('https://github.com/zouzhibin/vue-admin-perfect')
return
}
}
// 进行配置
const changeSwitch = (key, val) => {
SettingStore.setThemeConfig({ key, val })
if (key === 'mode') {
openLoading()
setTimeout(() => {
closeLoading()
}, 600)
}
}
// 监听布局变化
watch(
() => layout.value,
() => {
const body = document.body as HTMLElement
body.setAttribute('class', `layout-${layout.value}`)
},
{ immediate: true },
)
// 修改主题颜色
const changePrimary = (val) => {
if (!val) {
primary.value = val = PRIMARY_COLOR
ElMessage({ type: 'success', message: `主题颜色已重置为 ${PRIMARY_COLOR}` })
}
document.documentElement.style.setProperty('--el-color-primary', val)
changeSwitch('primary', val)
}
// 修改灰色模式
const changeGrayWeak = (type, val) => {
const body = document.documentElement as HTMLElement
if (!val) return body.setAttribute('style', '')
if (type === 'gray') body.setAttribute('style', 'filter: grayscale(1)')
if (type === 'weak') body.setAttribute('style', 'filter: invert(80%)')
changeSwitch(type, val)
}
</script>
<style lang="scss" scoped>
::v-deep(.el-drawer__header) {
border-bottom: 1px solid #ebeef5;
padding: 15px 20px 14px;
margin-bottom: 0;
}
.m-setting-fix {
position: fixed;
top: 50%;
right: 0;
z-index: 999;
padding: 10px 0 0 0;
margin: 0;
text-align: center;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
border-top-left-radius: 5.5px;
border-bottom-left-radius: 5.5px;
box-shadow: 0 0 50px 0 rgb(82 63 105 / 15%);
transform: translateY(-50%);
.item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 8px 10px 10px;
margin: 0;
list-style: none;
}
.item-child {
color: #3698fd;
width: 60px;
height: 60px;
/*padding-top: 10px;*/
text-align: center;
display: flex;
flex-direction: column;
background: #f6f8f9;
align-items: center;
justify-content: center;
border-radius: 5.5px;
font-size: 12px;
background: #ebf5ff;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.item-child2 {
margin-top: 10px;
color: #b37feb;
background: #f7f2fd;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
}
:deep(.el-drawer__title) {
font-weight: bold;
color: black;
}
.theme-item {
width: 100%;
display: flex;
margin-bottom: 15px;
align-items: center;
font-size: 14px;
color: black;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<el-upload
action=""
:before-upload="beforeUploadAction"
:list-type="listType"
:multiple="multiple"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="fileList"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible" width="50%" top="80px">
<img w-full :src="dialogImageUrl" alt="预览图片" style="width: 100%" />
</el-dialog>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps, UploadUserFile } from 'element-plus'
import { ElMessage } from 'element-plus'
let emit = defineEmits(['update'])
let props = defineProps({
modelValue: Array,
multiple: {
type: Boolean,
default: true,
},
listType: {
type: String,
default: 'picture-card',
},
showFileList: {
type: Boolean,
default: true,
},
})
let fileList = ref([])
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
console.log(uploadFile, uploadFiles)
}
const beforeUploadAction = (file, fileLi) => {
return new Promise((resolve, reject) => {
var reader = new FileReader()
let reg = /\.jpg$|\.jpeg$|\.gif$|\.png$/i
reader.readAsDataURL(file)
let name = file.name
if (reg.test(name)) {
reader.onload = (e: FileReader) => {
fileList.value.push({
name: name,
url: e.target.result,
})
emit('update', fileList.value)
resolve(e.target.result)
}
} else {
ElMessage.error('请上传图片')
reject()
}
})
}
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
dialogImageUrl.value = uploadFile.url!
dialogVisible.value = true
}
onMounted(() => {
watch(
() => props.modelValue,
(value) => {
fileList.value = value
},
{
immediate: true,
},
)
})
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div class="m-wangEditor">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
<Editor
v-model="valueHtml"
class="editor-content'"
style="height: 300px; overflow-y: hidden"
:default-config="editorConfig"
:mode="mode"
@on-created="handleCreated"
/>
</div>
</template>
<script lang="ts" setup>
// 引入 wangEditor
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, onMounted, watch, shallowRef, ref, computed } from 'vue'
let editors = null
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
const toolbarConfig = {}
const editorConfig = { placeholder: '请输入内容...' }
// 内容 HTML
const mode = ref('default')
let emit = defineEmits(['update:modelValue'])
let props = defineProps({
modelValue: String,
})
const getEditorData = () => {
// 通过代码获取编辑器内容
let data = editors.txt.html()
alert(data)
}
const handleCreated = (editor) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
const valueHtml = computed({
get() {
return props.modelValue
},
set(val) {
// 防止富文本内容为空时,校验失败
if (editorRef.value.isEmpty()) val = ''
emit('update:modelValue', val)
},
})
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
// 调用销毁 API 对当前编辑器实例进行销毁
const editor = editorRef.value
if (editor == null) {
return
}
editor.destroy()
})
</script>
<style lang="scss" scoped>
.m-wangEditor {
z-index: 99;
width: 100%;
border: 1px solid #cccccc;
.editor-toolbar {
border-bottom: 1px solid #cccccc;
}
.editor-content {
overflow-y: hidden;
}
}
</style>

View File

@@ -1,8 +1,8 @@
<template>
<div class="zb-pipeline">
<zb-pipeline-start/>
<zb-pipeline-start />
</div>
</template>
<script lang="ts" setup>
import ZbPipelineStart from './zb-pipeline-start'
import ZbPipelineStart from './zb-pipeline-start'
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div class="zb-pipeline-start-wrapper">
<div class="zb-pipeline-start" v-bind:class="control === value ? 'active' : ''" v-on:click="handleClick">
<div class="zb-pipeline-start" :class="control === value ? 'active' : ''" @click="handleClick">
<div class="zb-pipeline-start-header">
<zb-icon type="play-filled" />
</div>
@@ -8,14 +8,14 @@
<div class="zb-pipeline-start-title">开始</div>
<div class="zb-pipeline-start-tooltip">
<el-tooltip placement="top-start" content="点击进行构建基础设置">
<el-icon >
<el-icon>
<Help />
</el-icon>
</el-tooltip>
<!-- <zb-tooltip placement="right">-->
<!-- <zb-icon type="help" />-->
<!-- <div slot="content">点击进行构建基础设置</div>-->
<!-- </zb-tooltip>-->
<!-- <zb-tooltip placement="right">-->
<!-- <zb-icon type="help" />-->
<!-- <div slot="content">点击进行构建基础设置</div>-->
<!-- </zb-tooltip>-->
</div>
</div>
</div>
@@ -23,41 +23,128 @@
</div>
</template>
<script lang="ts" setup>
import {
Help
} from '@element-plus/icons-vue'
import { Help } from '@element-plus/icons-vue'
</script>
<style>
/* zb-pipeline-start-wrapper */
.zb-pipeline-start-wrapper { position:relative; padding:0 40px; }
/* zb-pipeline-start-wrapper */
.zb-pipeline-start-wrapper {
position: relative;
padding: 0 40px;
}
/* zb-pipeline-start */
.zb-pipeline-start { cursor:pointer; user-select:none; position:relative; display:flex; justify-content:flex-start; align-items:stretch; width:200px; height:40px; border-radius:2px; background-color:#fff; }
.zb-pipeline-start:after { content:""; position:absolute; top:20px; right:-40px; display:block; width:40px; height:1px; background-color:#2d8cf0; }
.zb-pipeline-start .zb-pipeline-start-header { display:flex; justify-content:center; align-items:center; width:40px; border-width:1px 0 1px 1px; border-style:solid; border-color:#2d8cf0; border-radius:2px 0 0 2px; background-color:#2d8cf0; color:#fff; font-size:14px; font-weight:600; transition:all 0.2s cubic-bezier(0.23,1,0.32,1); }
.zb-pipeline-start .zb-pipeline-start-body { flex:1; display:flex; border-width:1px 1px 1px 0; border-style:solid; border-color:#e3e8f0; border-radius:0 2px 2px 0; padding:0 8px; overflow:hidden; transition:all 0.2s cubic-bezier(0.23,1,0.32,1); }
.zb-pipeline-start .zb-pipeline-start-title { flex:1; display:block; overflow:hidden; color:#262626; font-size:14px; font-weight:600; line-height:38px; white-space:nowrap; text-overflow:ellipsis; }
.zb-pipeline-start .zb-pipeline-start-tooltip { cursor:pointer; display:flex; justify-content:center; align-items:center; margin-left:8px; }
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-tooltip { display:block; }
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-icon { display:block; color:#595959; font-size:14px; }
/* zb-pipeline-start */
.zb-pipeline-start {
cursor: pointer;
user-select: none;
position: relative;
display: flex;
justify-content: flex-start;
align-items: stretch;
width: 200px;
height: 40px;
border-radius: 2px;
background-color: #fff;
}
.zb-pipeline-start:after {
content: '';
position: absolute;
top: 20px;
right: -40px;
display: block;
width: 40px;
height: 1px;
background-color: #2d8cf0;
}
.zb-pipeline-start .zb-pipeline-start-header {
display: flex;
justify-content: center;
align-items: center;
width: 40px;
border-width: 1px 0 1px 1px;
border-style: solid;
border-color: #2d8cf0;
border-radius: 2px 0 0 2px;
background-color: #2d8cf0;
color: #fff;
font-size: 14px;
font-weight: 600;
transition: all 0.2s cubic-bezier(0.23, 1, 0.32, 1);
}
.zb-pipeline-start .zb-pipeline-start-body {
flex: 1;
display: flex;
border-width: 1px 1px 1px 0;
border-style: solid;
border-color: #e3e8f0;
border-radius: 0 2px 2px 0;
padding: 0 8px;
overflow: hidden;
transition: all 0.2s cubic-bezier(0.23, 1, 0.32, 1);
}
.zb-pipeline-start .zb-pipeline-start-title {
flex: 1;
display: block;
overflow: hidden;
color: #262626;
font-size: 14px;
font-weight: 600;
line-height: 38px;
white-space: nowrap;
text-overflow: ellipsis;
}
.zb-pipeline-start .zb-pipeline-start-tooltip {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
margin-left: 8px;
}
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-tooltip {
display: block;
}
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-icon {
display: block;
color: #595959;
font-size: 14px;
}
/* zb-pipeline-start hover */
.zb-pipeline-start:hover { }
.zb-pipeline-start:hover:after { }
.zb-pipeline-start:hover .zb-pipeline-start-header { border-color:#2d8cf0; }
.zb-pipeline-start:hover .zb-pipeline-start-body { border-color:#2d8cf0; }
.zb-pipeline-start:hover .zb-pipeline-start-title { }
.zb-pipeline-start:hover .zb-pipeline-start-tooltip { }
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-tooltip { }
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-icon { }
/* zb-pipeline-start hover */
.zb-pipeline-start:hover {
}
.zb-pipeline-start:hover:after {
}
.zb-pipeline-start:hover .zb-pipeline-start-header {
border-color: #2d8cf0;
}
.zb-pipeline-start:hover .zb-pipeline-start-body {
border-color: #2d8cf0;
}
.zb-pipeline-start:hover .zb-pipeline-start-title {
}
.zb-pipeline-start:hover .zb-pipeline-start-tooltip {
}
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-tooltip {
}
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-icon {
}
/* zb-pipeline-start active */
.zb-pipeline-start.active { }
.zb-pipeline-start.active:after { }
.zb-pipeline-start.active .zb-pipeline-start-header { border-color:#2d8cf0; }
.zb-pipeline-start.active .zb-pipeline-start-body { border-color:#2d8cf0; }
.zb-pipeline-start.active .zb-pipeline-start-title { }
.zb-pipeline-start.active .zb-pipeline-start-tooltip { }
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-tooltip { }
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-icon { }
/* zb-pipeline-start active */
.zb-pipeline-start.active {
}
.zb-pipeline-start.active:after {
}
.zb-pipeline-start.active .zb-pipeline-start-header {
border-color: #2d8cf0;
}
.zb-pipeline-start.active .zb-pipeline-start-body {
border-color: #2d8cf0;
}
.zb-pipeline-start.active .zb-pipeline-start-title {
}
.zb-pipeline-start.active .zb-pipeline-start-tooltip {
}
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-tooltip {
}
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-icon {
}
</style>

View File

@@ -1,50 +0,0 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group>
<el-breadcrumb-item v-for="(item,index) in obj.levelList" :key="item.path">
<span v-if="item.redirect==='noRedirect'||index==obj.levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script lang="ts" setup>
import pathToRegexp from 'path-to-regexp'
import {onMounted, reactive, watch} from "vue";
import {useRoute} from 'vue-router'
const obj = reactive({levelList:{}})
const route = useRoute()
// 获取面包屑
const getBreadcrumb = ()=>{
let matched = route.matched.filter(item => item.meta && item.meta.title)
const first = matched[0]
obj.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}
onMounted(()=>{
getBreadcrumb()
watch(route,()=>{
if (route.path.startsWith('/redirect/')) {
return
}
getBreadcrumb()
})
})
</script>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
margin-left: 10px;
display: inline-block;
font-size: 14px;
margin-bottom: 4px;
.no-redirect {
cursor: text;
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div class="m-container-layout">
<div class="m-container-layout-inner">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.m-container-layout {
width: 100%;
height: 100%;
display: flex;
padding: 10px 12px;
box-sizing: border-box;
.m-container-layout-inner {
flex: 1;
display: flex;
flex-direction: column;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
background: white;
padding: 20px;
width: 100%;
height: 100%;
box-sizing: border-box;
}
}
</style>

View File

@@ -1,5 +0,0 @@
import CountTo from './vue-countTo.vue';
export default CountTo;
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.component('count-to', CountTo);
}

View File

@@ -1,191 +0,0 @@
<template>
<span>
{{displayValue}}
</span>
</template>
<script>
import { requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame.js'
export default {
props: {
startVal: {
type: Number,
required: false,
default: 0
},
endVal: {
type: Number,
required: false,
default: 2017
},
duration: {
type: Number,
required: false,
default: 3000
},
autoplay: {
type: Boolean,
required: false,
default: true
},
decimals: {
type: Number,
required: false,
default: 0,
validator(value) {
return value >= 0
}
},
decimal: {
type: String,
required: false,
default: '.'
},
separator: {
type: String,
required: false,
default: ','
},
prefix: {
type: String,
required: false,
default: ''
},
suffix: {
type: String,
required: false,
default: ''
},
useEasing: {
type: Boolean,
required: false,
default: true
},
easingFn: {
type: Function,
default(t, b, c, d) {
return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b;
}
}
},
data() {
return {
localStartVal: this.startVal,
displayValue: this.formatNumber(this.startVal),
printVal: null,
paused: false,
localDuration: this.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null
};
},
computed: {
countDown() {
return this.startVal > this.endVal
}
},
watch: {
startVal() {
if (this.autoplay) {
this.start();
}
},
endVal() {
if (this.autoplay) {
this.start();
}
}
},
mounted() {
if (this.autoplay) {
this.start();
}
this.$emit('mountedCallback')
},
methods: {
start() {
this.localStartVal = this.startVal;
this.startTime = null;
this.localDuration = this.duration;
this.paused = false;
this.rAF = requestAnimationFrame(this.count);
},
pauseResume() {
if (this.paused) {
this.resume();
this.paused = false;
} else {
this.pause();
this.paused = true;
}
},
pause() {
cancelAnimationFrame(this.rAF);
},
resume() {
this.startTime = null;
this.localDuration = +this.remaining;
this.localStartVal = +this.printVal;
requestAnimationFrame(this.count);
},
reset() {
this.startTime = null;
cancelAnimationFrame(this.rAF);
this.displayValue = this.formatNumber(this.startVal);
},
count(timestamp) {
if (!this.startTime) this.startTime = timestamp;
this.timestamp = timestamp;
const progress = timestamp - this.startTime;
this.remaining = this.localDuration - progress;
if (this.useEasing) {
if (this.countDown) {
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
} else {
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
}
} else {
if (this.countDown) {
this.printVal = this.localStartVal - ((this.localStartVal - this.endVal) * (progress / this.localDuration));
} else {
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
}
}
if (this.countDown) {
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
} else {
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
}
this.displayValue = this.formatNumber(this.printVal)
if (progress < this.localDuration) {
this.rAF = requestAnimationFrame(this.count);
} else {
this.$emit('callback');
}
},
isNumber(val) {
return !isNaN(parseFloat(val))
},
formatNumber(num) {
num = num.toFixed(this.decimals);
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? this.decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (this.separator && !this.isNumber(this.separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + this.separator + '$2');
}
}
return this.prefix + x1 + x2 + this.suffix;
}
},
destroyed() {
cancelAnimationFrame(this.rAF)
}
};
</script>

View File

@@ -1,61 +0,0 @@
<template>
<div class="m-info">
<el-popover
width="200px"
placement="bottom"
>
<template #reference>
<el-badge :value="3" class="item-info-pop">
<el-icon><Bell /></el-icon>
</el-badge>
</template>
<div>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="通知" name="first">
<div class="item-child"> github开源地址<el-button type="text" @click="toGitHub">点我</el-button></div>
<div class="item-child"> github开源地址<el-button type="text" @click="toGitHub">点我</el-button></div>
<div class="item-child"> github开源地址<el-button type="text" @click="toGitHub">点我</el-button></div>
</el-tab-pane>
</el-tabs>
</div>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
const activeName = ref('first')
const toGitHub = ()=>{
window.open('https://github.com/zouzhibin/vue-admin-perfect')
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event)
}
</script>
<style lang="scss" scoped>
.m-info{
width: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all .3s;
.item-info-pop{
// width:100% ;
//height: 100%;
display: flex;
align-items: center;
}
.item-child{
display: flex;
align-items: center;
font-size: 13px;
}
}
</style>

View File

@@ -1,7 +0,0 @@
<template>
</template>
<script lang="ts">
</script>

View File

@@ -1,112 +0,0 @@
<template>
<div class="g-right-click-menu" :style="style" v-if="isShow" ref="rightMenu">
<div
v-for="item,index in data"
:key="index"
class="operating" @click.stop="operatingRightAction($event,item)">{{item.label}}</div>
</div>
</template>
<script>
export default {
props: {
data:{
type:Array,
default:()=>[]
},
left: {
type: Number,
default: 0
},
type: {
type: Number,
default: 1
},
dataInfo: {
type: Object,
default: () => {}
},
top: {
type: Number,
default: 0
},
isViewInfo: {
type: Boolean,
default: true
}
},
watch: {
left: {
handler (newName, oldName) {
if (newName) {
this.isShow = true
}
}
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
// immediate: true
}
},
computed: {
style () {
let clientHeight = document.body.clientHeight
let y = this.top
if (clientHeight - y < 100) {
return `left:${this.left}px;bottom:${clientHeight - y}px`
} else {
return `left:${this.left}px;top:${this.top}px`
}
}
},
data () {
return {
isShow: false
}
},
methods: {
/**
* @func 点击操作
* @param val 1、置顶/取消置顶 2、开启/关闭免打扰 3、开启/关闭星标 4、删除会话
*/
operatingRightAction ($event, val) {
this.$emit('ok', $event, val)
this.isShow = false
}
},
mounted () {
let _self = this
window.addEventListener('click', function () {
_self.isShow = false
})
window.addEventListener('mousedown', function (e) {
if (e.which === 3) {
_self.isShow = false
}
})
}
}
</script>
<style lang="scss" scoped>
.g-right-click-menu {
left: 0;
background: white;
width: 148px;
height: auto;
position: fixed;
//border: 1px solid #c4c4c4;
z-index: 9;
//box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
.operating {
font-size: 12px;
padding-left: 23px;
cursor: pointer;
line-height: 27px;
}
.operating:hover {
background: #e2e2e2;
}
.last-delete {
border-top: 1px solid #ededed;
}
}
</style>

View File

@@ -1,157 +0,0 @@
/* eslint-disable promise/prefer-await-to-then */
const methodMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror',
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror',
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError',
],
];
const nativeAPI = (() => {
const unprefixedMethods = methodMap[0];
const returnValue = {};
for (const methodList of methodMap) {
const exitFullscreenMethod = methodList?.[1];
if (exitFullscreenMethod in document) {
for (const [index, method] of methodList.entries()) {
returnValue[unprefixedMethods[index]] = method;
}
return returnValue;
}
}
return false;
})();
const eventNameMap = {
change: nativeAPI.fullscreenchange,
error: nativeAPI.fullscreenerror,
};
// eslint-disable-next-line import/no-mutable-exports
let screenfull = {
// eslint-disable-next-line default-param-last
request(element = document.documentElement, options) {
return new Promise((resolve, reject) => {
const onFullScreenEntered = () => {
screenfull.off('change', onFullScreenEntered);
resolve();
};
screenfull.on('change', onFullScreenEntered);
const returnPromise = element[nativeAPI.requestFullscreen](options);
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenEntered).catch(reject);
}
});
},
exit() {
return new Promise((resolve, reject) => {
if (!screenfull.isFullscreen) {
resolve();
return;
}
const onFullScreenExit = () => {
screenfull.off('change', onFullScreenExit);
resolve();
};
screenfull.on('change', onFullScreenExit);
const returnPromise = document[nativeAPI.exitFullscreen]();
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenExit).catch(reject);
}
});
},
toggle(element, options) {
return screenfull.isFullscreen ? screenfull.exit() : screenfull.request(element, options);
},
onchange(callback) {
screenfull.on('change', callback);
},
onerror(callback) {
screenfull.on('error', callback);
},
on(event, callback) {
const eventName = eventNameMap[event];
if (eventName) {
document.addEventListener(eventName, callback, false);
}
},
off(event, callback) {
const eventName = eventNameMap[event];
if (eventName) {
document.removeEventListener(eventName, callback, false);
}
},
raw: nativeAPI,
};
Object.defineProperties(screenfull, {
isFullscreen: {
get: () => Boolean(document[nativeAPI.fullscreenElement]),
},
element: {
enumerable: true,
get: () => document[nativeAPI.fullscreenElement] ?? undefined,
},
isEnabled: {
enumerable: true,
// Coerce to boolean in case of old WebKit.
get: () => Boolean(document[nativeAPI.fullscreenEnabled]),
},
});
if (!nativeAPI) {
screenfull = {isEnabled: false};
}
export default screenfull;

View File

@@ -1,61 +0,0 @@
<template>
<div class="m-screenful">
<el-icon
title="全屏"
@click.stop="click"
style="margin-right: 10px;cursor: pointer" ><full-screen /></el-icon>
</div>
</template>
<script lang="ts" setup>
import screenfull from './index'
import { ElMessage } from 'element-plus'
import {onBeforeUnmount, onMounted, ref} from "vue";
let isFullscreen = ref(false)
const click = ()=>{
if (!screenfull.isEnabled) {
ElMessage({
message: '你的浏览器不支持',
type: 'warning'
})
return false
}
screenfull.toggle()
}
const change = ()=>{
isFullscreen.value = screenfull.isFullscreen
}
const init = ()=>{
if (screenfull.isEnabled) {
screenfull.on('change', change)
}
}
const destroy = ()=>{
if (screenfull.isEnabled) {
screenfull.off('change', change)
}
}
onMounted(()=>{
init()
})
onBeforeUnmount(()=>{
destroy()
})
</script>
<style lang="scss" scoped>
.m-screenful{
width: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all .3s;
}
</style>

View File

@@ -1,126 +0,0 @@
<template>
<div>
<div class="m-setting-fix">
<div class="item">
<div class="item-child" @click="operator(1)">
<el-icon size="30" color="#3698fd" style="margin-bottom: 8px"><brush /></el-icon>
主题配置
</div>
<div class="item-child item-child2" @click="operator(2)">
<el-icon size="30"
color="#3698fd"
style="margin-bottom: 8px"><Link /></el-icon>
源码拷贝
</div>
</div>
</div>
<el-drawer v-model="drawer" title="主题配置" size="300px">
<div class="drawer-item">
<label>布局</label>
<el-select v-model="layout" placeholder="请选择" style="width: 150px" @change="(val)=>changeSwitch(val,2)">
<el-option label="纵向" value="vertical"></el-option>
<el-option label="横向" value="horizontal"></el-option>
</el-select>
</div>
<div class="drawer-item">
<label>标签</label>
<el-switch v-model="tag" @change="(val)=>changeSwitch(val,1)"/>
</div>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const drawer = ref(false)
const tag = ref(true)
const layout = ref('vertical')
import {useStore} from "vuex";
const store = useStore()
const operator = (type)=>{
switch (type) {
case 1:
drawer.value = true
return
case 2:
window.open('https://github.com/zouzhibin/vue-admin-perfect')
return
}
}
const changeSwitch =(val,type)=>{
switch (type) {
// 是否显示tag
case 1:
store.dispatch('setting/setTag',val)
return
case 2:
store.dispatch('setting/setMode',val)
return
}
}
</script>
<style lang="scss" scoped>
.m-setting-fix{
position: fixed;
top: 50%;
right: 0;
z-index: 1997;
padding: 10px 0 0 0;
margin: 0;
text-align: center;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
border-top-left-radius: 5.5px;
border-bottom-left-radius: 5.5px;
box-shadow: 0 0 50px 0 rgb(82 63 105 / 15%);
transform: translateY(-50%);
.item{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 8px 10px 10px;
margin: 0;
list-style: none;
}
.item-child{
color: #3698fd;
width: 60px;
height: 60px;
/*padding-top: 10px;*/
text-align: center;
display: flex;
flex-direction: column;
background: #f6f8f9;
align-items: center;
justify-content: center;
border-radius: 5.5px;
font-size: 12px;
background: #ebf5ff;
transition: color .15s ease,background-color .15s ease,border-color .15s ease,box-shadow .15s ease;
}
.item-child2{
margin-top: 10px;
color: #b37feb;
background: #f7f2fd;
transition: color .15s ease,background-color .15s ease,border-color .15s ease,box-shadow .15s ease;
}
}
.drawer-item{
width: 100%;
display: flex;
margin-bottom: 15px;
align-items: center;
justify-content: space-between;
}
</style>

View File

@@ -1,86 +0,0 @@
<template>
<el-upload
action=""
:before-upload="beforeUploadAction"
:list-type="listType"
:multiple="multiple"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="fileList"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible" width="50%" top="80px">
<img w-full :src="dialogImageUrl" alt="预览图片" style="width: 100%"/>
</el-dialog>
</template>
<script lang="ts" setup>
import {onMounted, ref, watch} from 'vue'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps, UploadUserFile } from 'element-plus'
import {ElMessage} from "element-plus";
let emit = defineEmits(['update'])
let props = defineProps({
modelValue:Array,
multiple:{
type:Boolean,
default:true
},
listType:{
type:String,
default:'picture-card'
},
showFileList:{
type:Boolean,
default:true
}
})
let fileList = ref([
])
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
console.log(uploadFile, uploadFiles)
}
const beforeUploadAction = (file, fileLi)=>{
return new Promise((resolve, reject) => {
var reader = new FileReader()
let reg = (/\.jpg$|\.jpeg$|\.gif$|\.png$/i)
reader.readAsDataURL(file)
let name = file.name
if(reg.test(name)){
reader.onload = (e:FileReader)=>{
fileList.value.push({
name:name,
url:e.target.result
})
emit('update',fileList.value)
resolve(e.target.result)
}
}else{
ElMessage.error('请上传图片')
reject()
}
})
}
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
dialogImageUrl.value = uploadFile.url!
dialogVisible.value = true
}
onMounted(()=>{
watch(()=>props.modelValue,(value)=>{
fileList.value = value
},{
immediate:true
})
})
</script>

Some files were not shown because too many files have changed in this diff Show More