Compare commits

...

88 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 af9cb48659 perf:文件名字修改 2022-10-08 11:07:47 +08:00
345 changed files with 41089 additions and 16995 deletions

View File

@ -2,4 +2,4 @@
NODE_ENV = 'development'
# 本地环境接口地址
VUE_APP_BASE_API = '/api'
VITE_APP_BASE_API = '/api'

View File

@ -2,4 +2,4 @@
NODE_ENV = "production"
# 线上环境接口地址
VUE_APP_BASE_API = '/api'
VITE_APP_BASE_API = '/api'

View File

@ -3,4 +3,4 @@ NODE_ENV = "test"
# 测试环境接口地址
VUE_APP_BASE_API = '/api'
VITE_APP_BASE_API = '/api'

View File

@ -1,6 +1,11 @@
*.sh
node_modules
*.css
*.jpg
*.jpeg
*.png
*.gif
*.md
*.woff
*.ttf

View File

@ -1,25 +0,0 @@
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node":true
},
"extends": [
"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"
},
"plugins": [
"vue",
"@typescript-eslint"
],
"rules": {
}
}

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: '^_',
},
],
},
}

2
.gitignore vendored
View File

@ -8,8 +8,8 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
dist
*.local
# Editor directories and files

4
.husky/pre-commit Normal file
View File

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

View File

@ -1,6 +1,8 @@
module.exports = {
// 一行的字符数如果超过会进行换行默认为80
printWidth: 100,
printWidth: 140,
// 缩进制表符宽度 | 空格数
tabWidth: 2,
// 行位是否使用分号默认为true
semi: false,
vueIndentScriptAndStyle: true,

View File

@ -1,39 +1,43 @@
## 简介
vue-element-perfect 是一个后台前端解决方案, 基于Vue3.0+TS+Element-plus实现。它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件
vue-element-perfect 是一个后台前端解决方案,它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件,企业级中后台解决方案可免费商用同时支持PC、平板、手机
本项目也参考了很多开源的项目、
### 在线预览
- link —— [http://182.61.5.190:8889/ ](http://182.61.5.190:8889/)
- gitee国内访问地址https://yuanzbz.gitee.io/vue-admin-perfect/#/home
- github site : https://zouzhibin.github.io/vue-admin-perfect/
### git仓库
- 基础功能版本预览https://yuanzbz.gitee.io/vue-admin-simple
### 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、代码校验规范
- 其他各组件
## 安装
- Clone
```
# 克隆项目
# GitHub
git clone https://github.com/zouzhibin/vue-admin-perfect.git
# Gitee
git clone https://gitee.com/yuanzbz/vue-admin-perfect.git
```
## 分支管理
- master 技术采用 vite + vue3.0 + Typescript + pinia
```
注意:使用 Vite 构建工具,需要 Node.js 版本 >= 12.0.0 查看 Node.js 版本 node-v
```
- vue-admin-simple 简易版本
- vite-vuex vite + vue3.0 + Typescript + vuex
- vue-i18n 语言切换版本
- webpack 技术采用 webpack + vue3.0 + Typescript + vuex
@ -48,9 +52,12 @@ vue-element-perfect 是一个后台前端解决方案, 基于Vue3.0+TS+Element-p
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
@ -98,6 +105,10 @@ vue-admin-perfect
└─ vite.config.ts # vite 配置
```
### 微信交流群
| 微信二维码 |
| :----------------------------------------------------------------------------------: |
| <img src="http://182.61.5.190:8889/we.png" width=170/> |

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,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<html lang="en" >
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico">
@ -9,60 +9,112 @@
<body>
<div id="app">
<style>
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
.first-loading-wrp {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
min-height: 90vh;
}
.init-loading-wrap{
width: 100%;
height: 100%;
.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;
}
.init-loading-inner {
width: 20px;
height: 50px;
background: #1890ff;
float: left;
margin: 0 3px;
animation: init-loading-inner linear 1s infinite;
-webkit-animation: init-loading-inner linear 1s infinite;
.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;
}
.init-loading-inner:nth-child(1){
animation-delay:0s;
.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;
}
.init-loading-inner:nth-child(2){
animation-delay:0.15s;
.dot i:nth-child(1) {
top: 0;
left: 0;
}
.init-loading-inner:nth-child(3){
animation-delay:0.3s;
.dot i:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.init-loading-inner:nth-child(4){
animation-delay:0.45s;
.dot i:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.init-loading-inner:nth-child(5){
animation-delay:0.6s;
.dot i:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
@keyframes init-loading-inner{
0%,60%,100% {transform: scale(1);}
30% {transform: scaleY(3);}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes init-loading-inner{
0%,60%,100% {transform: scale(1);}
30% {transform: scaleY(3);}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
</style>
<div class="init-loading-wrap">
<div class="init-loading-inner"></div>
<div class="init-loading-inner"></div>
<div class="init-loading-inner"></div>
<div class="init-loading-inner"></div>
<div class="init-loading-inner"></div>
<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>

16902
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,19 @@
"name": "vue-admin-perfect",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"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 --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\""
"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",
@ -21,12 +23,11 @@
"@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",
"default-passive-events": "^2.0.0",
"echarts": "^5.3.1",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.2.16",
"element-plus": "^2.2.28",
"exceljs": "^4.3.0",
"file-saver": "^2.0.5",
"fuse.js": "^6.6.2",
@ -37,9 +38,10 @@
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.21",
"pinia-persistedstate-plugin": "^0.1.0",
"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",
@ -49,25 +51,34 @@
"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",
"vxe-table": "^4.3.5",
"xe-utils": "^3.5.6",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@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",
@ -79,5 +90,17 @@
"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 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="">
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -5,14 +5,14 @@
</template>
<script lang="ts" setup>
import {computed} from "vue";
import {useSettingStore} from "@/store/modules/setting"
import { computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
// element
import zhCn from 'element-plus/es/locale/lang/zh-cn'
const SettingStore = useSettingStore()
//
const globalComSize = computed(():string=>SettingStore.themeConfig.globalComSize)
const globalComSize = computed((): string => SettingStore.themeConfig.globalComSize)
</script>
<style lang="scss">

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
}

View File

@ -1,44 +0,0 @@
export const errorCodeType = function(code:string):string{
let errMessage:string = "未知错误"
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
}

View File

@ -1,57 +1,62 @@
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from "element-plus";
import {useUserStore} from "@/store/modules/user"
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.VUE_APP_BASE_API,
// 设置接口访问超时时间
timeout: 3000000, // request timeout
// 跨域时候允许携带凭证
withCredentials: true
// 默认请求地址,根据环境的不同可在.env 文件中进行修改
baseURL: import.meta.env.VITE_APP_BASE_API,
// 设置接口访问超时时间
timeout: 3000000, // request timeout
// 跨域时候允许携带凭证
withCredentials: true,
})
// request interceptor 接口请求拦截
service.interceptors.request.use((config:AxiosRequestConfig)=>{
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
/**
* token,token进行JWT校验
* token storagevuexpinia
*/
const userStore = useUserStore();
const token: string = userStore.token;
const userStore = useUserStore()
const token: string = userStore.token
// 自定义请求头
if(token){ config.headers['Authorization'] = token}
if (token) {
config.headers['Authorization'] = token
}
return config
},(error: AxiosError) => {
},
(error: AxiosError) => {
// 请求错误,这里可以用全局提示框进行提示
return Promise.reject(error);
})
return Promise.reject(error)
},
)
// response interceptor 接口响应拦截
service.interceptors.response.use((response: AxiosResponse) =>{
service.interceptors.response.use(
(response: AxiosResponse) => {
// 直接返回res当然你也可以只返回res.data
// 系统如果有自定义code也可以在这里处理
return response
},(error: AxiosError) => {
},
(error: AxiosError) => {
return Promise.reject(error)
})
},
)
/**
* @description
* opt
* err
* type
* duration
*/
function showErrMessage (opt, err, type:any= 'error', duration:number = 5000){
ElMessage({
message: err.msg,
type:type,
duration: duration
})
}
// /**
// * @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

View File

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

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

@ -1,539 +0,0 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@ -1,763 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=3641732" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe6a0;</span>
<div class="name"></div>
<div class="code-name">&amp;#xe6a0;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6a7;</span>
<div class="name">大暴雨</div>
<div class="code-name">&amp;#xe6a7;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe627;</span>
<div class="name">刷新</div>
<div class="code-name">&amp;#xe627;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe629;</span>
<div class="name">刷新</div>
<div class="code-name">&amp;#xe629;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe68d;</span>
<div class="name">多云-2-copy</div>
<div class="code-name">&amp;#xe68d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe679;</span>
<div class="name">多云-1</div>
<div class="code-name">&amp;#xe679;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe67a;</span>
<div class="name">大雪</div>
<div class="code-name">&amp;#xe67a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe67b;</span>
<div class="name">大雨</div>
<div class="code-name">&amp;#xe67b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe67c;</span>
<div class="name"></div>
<div class="code-name">&amp;#xe67c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe67d;</span>
<div class="name">多云-3</div>
<div class="code-name">&amp;#xe67d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe67e;</span>
<div class="name">多云-2</div>
<div class="code-name">&amp;#xe67e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe67f;</span>
<div class="name">雷雨交加</div>
<div class="code-name">&amp;#xe67f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe680;</span>
<div class="name">多云</div>
<div class="code-name">&amp;#xe680;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe681;</span>
<div class="name">中雨</div>
<div class="code-name">&amp;#xe681;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe682;</span>
<div class="name"></div>
<div class="code-name">&amp;#xe682;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe683;</span>
<div class="name">小雨</div>
<div class="code-name">&amp;#xe683;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe684;</span>
<div class="name">小雪</div>
<div class="code-name">&amp;#xe684;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe685;</span>
<div class="name">闪电</div>
<div class="code-name">&amp;#xe685;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe686;</span>
<div class="name"></div>
<div class="code-name">&amp;#xe686;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe687;</span>
<div class="name">中雪</div>
<div class="code-name">&amp;#xe687;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe688;</span>
<div class="name">扬尘</div>
<div class="code-name">&amp;#xe688;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe689;</span>
<div class="name">月亮</div>
<div class="code-name">&amp;#xe689;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe68a;</span>
<div class="name">雨夹雪</div>
<div class="code-name">&amp;#xe68a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe68b;</span>
<div class="name"></div>
<div class="code-name">&amp;#xe68b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe68c;</span>
<div class="name"></div>
<div class="code-name">&amp;#xe68c;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1663216428515') format('woff2'),
url('iconfont.woff?t=1663216428515') format('woff'),
url('iconfont.ttf?t=1663216428515') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-yin"></span>
<div class="name">
</div>
<div class="code-name">.icon-yin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-dabaoyu"></span>
<div class="name">
大暴雨
</div>
<div class="code-name">.icon-dabaoyu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shuaxin1"></span>
<div class="name">
刷新
</div>
<div class="code-name">.icon-shuaxin1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shuaxin"></span>
<div class="name">
刷新
</div>
<div class="code-name">.icon-shuaxin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-duoyun-2-copy"></span>
<div class="name">
多云-2-copy
</div>
<div class="code-name">.icon-duoyun-2-copy
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-duoyun-1"></span>
<div class="name">
多云-1
</div>
<div class="code-name">.icon-duoyun-1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-daxue"></span>
<div class="name">
大雪
</div>
<div class="code-name">.icon-daxue
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-dayu"></span>
<div class="name">
大雨
</div>
<div class="code-name">.icon-dayu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-feng"></span>
<div class="name">
</div>
<div class="code-name">.icon-feng
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-duoyun-3"></span>
<div class="name">
多云-3
</div>
<div class="code-name">.icon-duoyun-3
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-duoyun-2"></span>
<div class="name">
多云-2
</div>
<div class="code-name">.icon-duoyun-2
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-leiyujiaojia"></span>
<div class="name">
雷雨交加
</div>
<div class="code-name">.icon-leiyujiaojia
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-duoyun"></span>
<div class="name">
多云
</div>
<div class="code-name">.icon-duoyun
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zhongyu"></span>
<div class="name">
中雨
</div>
<div class="code-name">.icon-zhongyu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-wu"></span>
<div class="name">
</div>
<div class="code-name">.icon-wu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiaoyu"></span>
<div class="name">
小雨
</div>
<div class="code-name">.icon-xiaoyu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiaoxue"></span>
<div class="name">
小雪
</div>
<div class="code-name">.icon-xiaoxue
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shandian"></span>
<div class="name">
闪电
</div>
<div class="code-name">.icon-shandian
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xue"></span>
<div class="name">
</div>
<div class="code-name">.icon-xue
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zhongxue"></span>
<div class="name">
中雪
</div>
<div class="code-name">.icon-zhongxue
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-yangchen"></span>
<div class="name">
扬尘
</div>
<div class="code-name">.icon-yangchen
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-yueliang"></span>
<div class="name">
月亮
</div>
<div class="code-name">.icon-yueliang
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-yujiaxue"></span>
<div class="name">
雨夹雪
</div>
<div class="code-name">.icon-yujiaxue
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-qing"></span>
<div class="name">
</div>
<div class="code-name">.icon-qing
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-mai"></span>
<div class="name">
</div>
<div class="code-name">.icon-mai
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yin"></use>
</svg>
<div class="name"></div>
<div class="code-name">#icon-yin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-dabaoyu"></use>
</svg>
<div class="name">大暴雨</div>
<div class="code-name">#icon-dabaoyu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shuaxin1"></use>
</svg>
<div class="name">刷新</div>
<div class="code-name">#icon-shuaxin1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shuaxin"></use>
</svg>
<div class="name">刷新</div>
<div class="code-name">#icon-shuaxin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-duoyun-2-copy"></use>
</svg>
<div class="name">多云-2-copy</div>
<div class="code-name">#icon-duoyun-2-copy</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-duoyun-1"></use>
</svg>
<div class="name">多云-1</div>
<div class="code-name">#icon-duoyun-1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-daxue"></use>
</svg>
<div class="name">大雪</div>
<div class="code-name">#icon-daxue</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-dayu"></use>
</svg>
<div class="name">大雨</div>
<div class="code-name">#icon-dayu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-feng"></use>
</svg>
<div class="name"></div>
<div class="code-name">#icon-feng</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-duoyun-3"></use>
</svg>
<div class="name">多云-3</div>
<div class="code-name">#icon-duoyun-3</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-duoyun-2"></use>
</svg>
<div class="name">多云-2</div>
<div class="code-name">#icon-duoyun-2</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-leiyujiaojia"></use>
</svg>
<div class="name">雷雨交加</div>
<div class="code-name">#icon-leiyujiaojia</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-duoyun"></use>
</svg>
<div class="name">多云</div>
<div class="code-name">#icon-duoyun</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zhongyu"></use>
</svg>
<div class="name">中雨</div>
<div class="code-name">#icon-zhongyu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-wu"></use>
</svg>
<div class="name"></div>
<div class="code-name">#icon-wu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiaoyu"></use>
</svg>
<div class="name">小雨</div>
<div class="code-name">#icon-xiaoyu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiaoxue"></use>
</svg>
<div class="name">小雪</div>
<div class="code-name">#icon-xiaoxue</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shandian"></use>
</svg>
<div class="name">闪电</div>
<div class="code-name">#icon-shandian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xue"></use>
</svg>
<div class="name"></div>
<div class="code-name">#icon-xue</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zhongxue"></use>
</svg>
<div class="name">中雪</div>
<div class="code-name">#icon-zhongxue</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yangchen"></use>
</svg>
<div class="name">扬尘</div>
<div class="code-name">#icon-yangchen</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yueliang"></use>
</svg>
<div class="name">月亮</div>
<div class="code-name">#icon-yueliang</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yujiaxue"></use>
</svg>
<div class="name">雨夹雪</div>
<div class="code-name">#icon-yujiaxue</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-qing"></use>
</svg>
<div class="name"></div>
<div class="code-name">#icon-qing</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-mai"></use>
</svg>
<div class="name"></div>
<div class="code-name">#icon-mai</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@ -1,12 +1,11 @@
@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');
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-family: 'iconfont' !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
@ -14,102 +13,101 @@
}
.icon-yin:before {
content: "\e6a0";
content: '\e6a0';
}
.icon-dabaoyu:before {
content: "\e6a7";
content: '\e6a7';
}
.icon-shuaxin1:before {
content: "\e627";
content: '\e627';
}
.icon-shuaxin:before {
content: "\e629";
content: '\e629';
}
.icon-duoyun-2-copy:before {
content: "\e68d";
content: '\e68d';
}
.icon-duoyun-1:before {
content: "\e679";
content: '\e679';
}
.icon-daxue:before {
content: "\e67a";
content: '\e67a';
}
.icon-dayu:before {
content: "\e67b";
content: '\e67b';
}
.icon-feng:before {
content: "\e67c";
content: '\e67c';
}
.icon-duoyun-3:before {
content: "\e67d";
content: '\e67d';
}
.icon-duoyun-2:before {
content: "\e67e";
content: '\e67e';
}
.icon-leiyujiaojia:before {
content: "\e67f";
content: '\e67f';
}
.icon-duoyun:before {
content: "\e680";
content: '\e680';
}
.icon-zhongyu:before {
content: "\e681";
content: '\e681';
}
.icon-wu:before {
content: "\e682";
content: '\e682';
}
.icon-xiaoyu:before {
content: "\e683";
content: '\e683';
}
.icon-xiaoxue:before {
content: "\e684";
content: '\e684';
}
.icon-shandian:before {
content: "\e685";
content: '\e685';
}
.icon-xue:before {
content: "\e686";
content: '\e686';
}
.icon-zhongxue:before {
content: "\e687";
content: '\e687';
}
.icon-yangchen:before {
content: "\e688";
content: '\e688';
}
.icon-yueliang:before {
content: "\e689";
content: '\e689';
}
.icon-yujiaxue:before {
content: "\e68a";
content: '\e68a';
}
.icon-qing:before {
content: "\e68b";
content: '\e68b';
}
.icon-mai:before {
content: "\e68c";
content: '\e68c';
}

File diff suppressed because one or more lines are too long

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

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

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,162 +0,0 @@
<template>
<div :id="id" :class="className" :style="{ height: height, width: width }" />
</template>
<script lang="ts">
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,274 +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'
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,238 +0,0 @@
<template>
<div :id="id" :class="className" :style="{ height: height, width: width }" />
</template>
<script lang="ts">
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' //styleActiveLinetrue
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*tabntabfalse
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

@ -103,6 +103,9 @@
}
this.$emit('mountedCallback')
},
unmounted() {
cancelAnimationFrame(this.rAF)
},
methods: {
start() {
this.localStartVal = this.startVal
@ -142,26 +145,15 @@
if (this.useEasing) {
if (this.countDown) {
this.printVal =
this.localStartVal -
this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
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,
)
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)
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
} else {
this.printVal =
this.localStartVal +
(this.endVal - this.localStartVal) * (progress / this.localDuration)
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
}
}
if (this.countDown) {
@ -195,8 +187,5 @@
return this.prefix + x1 + x2 + this.suffix
},
},
destroyed() {
cancelAnimationFrame(this.rAF)
},
}
</script>

View File

@ -23,10 +23,7 @@ if (isServer) {
}
prefix = prefixes[i]
requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame']
cancelAnimationFrame =
cancelAnimationFrame ||
window[prefix + 'CancelAnimationFrame'] ||
window[prefix + 'CancelRequestAnimationFrame']
cancelAnimationFrame = cancelAnimationFrame || window[prefix + 'CancelAnimationFrame'] || window[prefix + 'CancelRequestAnimationFrame']
}
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame则会退到setTimeout

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],
// marginaxisLabelmargin!
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

@ -1,11 +1,12 @@
<template>
<div class="item-charts">
<bar-charts width="100%" height="100%" :config="lineConfig" />
</div>
<div ref="chartsRef" class="echarts" />
</template>
<script setup lang="ts">
import BarCharts from './components/bar.vue'
import { reactive } from '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 = []
@ -20,7 +21,7 @@
dataOptions.push(obj)
})
const lineConfig = reactive({
const options = {
color,
grid: {
top: '10%',
@ -44,6 +45,7 @@
},
},
yAxis: {
type: 'value',
//
axisLabel: {
color: '#bbdaff',
@ -61,6 +63,7 @@
},
},
xAxis: {
type: 'category',
splitLine: {
show: false,
},
@ -70,14 +73,12 @@
color: '#2d5baf',
},
},
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisLabel: {
//
color: '#bbdaff',
margin: 20, // 线
},
axisTick: {
// 线
show: false,
@ -93,10 +94,23 @@
},
},
],
}
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>
.item-charts {
.echarts {
height: 100%;
width: 100%;
}

View File

@ -1,13 +1,14 @@
<template>
<div class="item-charts">
<line-charts width="100%" height="100%" :config="lineConfig" />
</div>
<div ref="chartsRef" class="echarts" />
</template>
<script setup lang="ts">
import LineCharts from './components/line.vue'
import { reactive } from 'vue'
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 lineConfig = reactive({
const options = {
grid: {
top: '10%',
left: '3%',
@ -87,10 +88,23 @@
},
},
],
}
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>
.item-charts {
.echarts {
height: 100%;
width: 100%;
}

View File

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

View File

@ -2,12 +2,12 @@
<div :id="id" :class="className" :style="{ height: height, width: width }" />
</template>
<script lang="ts" setup>
import { geoJson } from './get.js'
import { geoJson } from './map.js'
import * as echarts from 'echarts'
import { EChartsType } from 'echarts/core'
import { onMounted } from 'vue'
import { cityIconData } from '../js/data.js'
import logo from '@/assets/logo.png'
import { onMounted, onUnmounted } from 'vue'
import { cityIconData } from './data.js'
import logo from '@/assets/image/logo.png'
const props = defineProps({
className: {
type: String,
@ -318,6 +318,11 @@
chart.setOption(options)
return chart
}
onUnmounted(() => {
chart && chart.dispose()
})
onMounted(() => {
chart = initChart()
window.addEventListener('resize', function () {

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

@ -1,12 +1,8 @@
<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 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">
@ -37,16 +33,10 @@
default: true,
},
},
watch: {
left: {
handler(newName, oldName) {
if (newName) {
this.isShow = true
}
},
// wacthfirstNamehandler
// immediate: true
},
data() {
return {
isShow: false,
}
},
computed: {
style() {
@ -59,19 +49,15 @@
}
},
},
data() {
return {
isShow: false,
}
},
methods: {
/**
* @func 点击操作
* @param val 1置顶/取消置顶 2开启/关闭免打扰 3开启/关闭星标 4删除会话
*/
operatingRightAction($event, val) {
this.$emit('ok', $event, val)
this.isShow = false
watch: {
left: {
handler(newName, oldName) {
if (newName) {
this.isShow = true
}
},
// wacthfirstNamehandler
// immediate: true
},
},
mounted() {
@ -85,6 +71,16 @@
}
})
},
methods: {
/**
* @func 点击操作
* @param val 1置顶/取消置顶 2开启/关闭免打扰 3开启/关闭星标 4删除会话
*/
operatingRightAction($event, val) {
this.$emit('ok', $event, val)
this.isShow = false
},
},
}
</script>
<style lang="scss" scoped>

View File

@ -1,120 +0,0 @@
<template>
<div class="advancedForm">
<el-form
ref="ruleFormRef"
:inline="true"
:label-position="'right'"
:model="formInline"
class="form-inline">
<el-row
:class="{
'not-show':byHeight&&!isExpand
}"
:gutter="gutterWidth">
<el-col :span="item.span"
v-for="item,index in columns"
:key="item.name"
v-show="byHeight?true:(index< (showRow*3)||isExpand)">
<el-form-item :label="item.title" :label-width="labelWidth" v-if="item.type==='input'">
<el-input
clearable
v-model="formInline[item.name]" :placeholder="item.placeholder" />
</el-form-item>
<template v-else-if="item.type==='date'">
<el-form-item :label="item.title" :label-width="labelWidth" >
<el-date-picker
value-format="YYYY-MM-DD"
v-model="formInline[item.name]"
type="date"
:placeholder="item.placeholder"
/>
</el-form-item>
</template>
</el-col>
</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 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 {reactive, ref} from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
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 formInline = reactive({
})
for(let item of props.columns){
formInline[item.name] = null
}
const onSubmit = () => {
emit('submit',formInline)
}
const resetForm = (formEl: FormInstance | undefined) => {
console.log('formEl',formEl)
if (!formEl) return
formEl.resetFields()
const keys = Object.keys(formInline);
keys.forEach(key => {
formInline[key] = null;
});
emit("reset", formInline);
}
</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

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

@ -7,7 +7,7 @@
<script lang="ts">
import { computed } from 'vue'
export default {
name: 'baseSvgIcon',
name: 'BaseSvgIcon',
props: {
iconClass: { type: String },
className: { type: String },

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

@ -1,6 +1,6 @@
<template>
<div class="m-edit-table">
<div style="margin-top: 15px; margin-bottom: 15px" v-if="mode !== 'hide' && mode !== 'bottom'">
<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
>
@ -8,61 +8,39 @@
<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"
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"
>
<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
clearable
:placeholder="`请选择`"
v-model="scope.row[item.name]"
v-if="scope.row.edit"
>
<el-option
v-for="ite in item.options"
:key="ite.value"
:label="ite.label"
:value="ite.value"
/>
<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-model="scope.row[item.name]"
type="date"
value-format="YYYY-MM-DD"
clearable
placeholder="请选择"
v-if="scope.row.edit"
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
clearable
placeholder="请输入"
v-model="scope.row[item.name]"
v-if="scope.row.edit"
></el-input>
<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>
@ -72,59 +50,28 @@
</template>
<el-table-column prop="operator" label="操作" width="250px" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.edit"
type="success"
size="small"
icon="CircleCheckFilled"
@click="confirmEdit(scope.row)"
>
<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
trigger="click"
v-model:visible="scope.row.visible"
placement="top"
:width="160"
>
<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
>
<el-button size="small" type="primary" @click="deleteAction(scope.row)">确定</el-button>
</div>
<template #reference>
<el-button icon="Delete" @click="deleteCurrent(scope.row)" type="danger" size="small"
>删除</el-button
>
<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>
<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 style="margin-top: 15px" v-if="mode !== 'hide' && mode !== 'top'">
<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
>
@ -132,72 +79,72 @@
</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([])
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',
},
})
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 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]
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)
}
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
}
let obj = {}
for (let item of props.columns) {
props.data.forEach((it) => {
if (!obj[item.name]) {
obj[item.name] = null
}
})
}
getData()
}
onMounted(() => {
watch(
//
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) => {
// //
@ -217,88 +164,88 @@ onMounted(() => {
immediate: true,
deep: true,
},
)
})
)
})
const visible = ref(false)
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', '')]
}
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
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 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 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,
//
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,
})
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%;
}
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
.inline-edit-table {
width: 100%;
}
</style>

View File

@ -1,240 +1,206 @@
<template>
<div class="zb-pro-table">
<div class="header">
<el-form :inline="true" :model="formInline" class="demo-form-inline search-form" ref="ruleFormRef" >
<template v-for="(item, index) in formSearchData" :key="index">
<el-form-item :label="item.label" v-show="isExpand ? isExpand : index < 2">
<template v-if="item.valueType === 'input'">
<el-input v-model="formInline[item.name]" :placeholder="`请输入${item.label}`" />
</template>
<template v-if="item.valueType === 'select'">
<el-select v-model="formInline[item.name]" :placeholder="`请选择${item.label}`">
<el-option
v-for="ite in item.options"
:key="ite.value"
:label="ite.label"
:value="ite.value"
/>
</el-select>
</template>
</el-form-item>
</template>
</el-form>
<div class="search">
<el-button type="primary" @click="onSubmit" :icon="Search">查询</el-button>
<el-button @click="reset(ruleFormRef)">重置</el-button>
<el-button link type="primary" @click="isExpand = !isExpand">{{ isExpand ? '合并' : '展开'}}<el-icon>
<arrow-down v-if="!isExpand" />
<arrow-up v-else /> </el-icon
></el-button>
</div>
<SearchForm :columns="baseFormColumns" @submit="onSubmit" />
</div>
<!----------底部---------------------->
<div class="footer">
<!-----------工具栏操作工具----------------->
<div class="operator">
<slot name="btn"></slot>
</div>
<!-- ------------表格--------------->
<div class="table">
<el-table
class="zb-table"
v-loading="loading"
@selection-change="(val) => emit('selection-change', val)"
:data="list"
:border="true"
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.type"
:type="item.type"
:width="item.width"
:align="item.align!=null?item.align:'center'"
:fixed="item.fixed"
:label="item.label"
/>
<el-table-column
v-else
:prop="item.name"
:width="item.width"
:align="item.align!=null?item.align:'center'"
:fixed="item.fixed"
:label="item.label"
>
<el-table-column v-if="item.slot" v-bind="{ ...item, ...{ prop: item.name } }">
<template #default="scope">
<span v-if="!item.slot">{{ scope.row[item.name] }}</span>
<slot v-else :name="item.name" :item="item" :row="scope.row"></slot>
<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="currentPage1"
:page-size="10"
background
layout="total, sizes, prev, pager, next, jumper"
:total="data.length"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
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 {Search } from '@element-plus/icons-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 currentPage1 = 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}`)
currentPage1.value = val
}
const list = computed(() => {
let arr = JSON.parse(JSON.stringify(props.data))
return arr.splice((currentPage1.value - 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 = () => {
console.log('submit!', formInline)
emit('onSubmit', formInline)
}
const reset = (formEl: FormInstance | undefined) => {
formSearchData.value.forEach((item) => {
formInline[item.name] = null
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,
},
})
emit('reset')
}
const deleteAction = (row) => {
ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
//
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%);
.edit-input {
padding-right: 100px;
}
.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%);
.operator{
margin-bottom: 15px
}
.table{
position: relative;
flex: 1;
}
.zb-table{
position: absolute;
height: 100%;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
.search-form{
flex: 1;
}
.search{
white-space: nowrap;
}
::v-deep{
.el-table__header th{
font-size: 15px;
font-weight: 700;
color: #252525;
}
}
.pagination{
.zb-pro-table {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
padding-top: 20px;
box-sizing: border-box;
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

@ -1,16 +0,0 @@
<template>
<el-switch @change="(val) => changeSwitch(val)" v-model="isDark"/>
</template>
<script setup lang="ts" name="switchDark">
import {computed, ref} from "vue";
import {useSettingStore} from "@/store/modules/setting"
import {toggleDark} from '../../../hooks/dark'
const SettingStore = useSettingStore()
//
const isDark = ref(SettingStore.themeConfig.isDark)
const changeSwitch = ()=>{
toggleDark()
}
</script>

View File

@ -12,18 +12,13 @@
</div>
</div>
</div>
<el-drawer
v-model="drawer" title="主题配置" size="300px">
<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-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">
@ -36,39 +31,40 @@
</div>
<div class="theme-item">
<label>灰色模式</label>
<el-switch v-model="gray" @change="(val) => changeGrayWeak('gray',val)" />
<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)" />
<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)" />
<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)" />
<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)" />
<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)" />
<el-switch v-model="fixedHeader" @change="(val) => changeSwitch('fixedHeader', val)" />
</div>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import {computed, ref} from 'vue'
import SwitchDark from './components/switchDark.vue'
import {ElMessage} from "element-plus";
import {PRIMARY_COLOR} from "@/config/index";
import {useSettingStore} from "@/store/modules/setting"
import {getDarkColor,getLightColor} from '@/utils/index'
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)
@ -81,17 +77,15 @@
const drawer = computed({
get() {
return SettingStore.themeConfig.showSetting;
return SettingStore.themeConfig.showSetting
},
set() {
changeSwitch('showSetting',!SettingStore.themeConfig.showSetting)
}
changeSwitch('showSetting', !SettingStore.themeConfig.showSetting)
},
})
//
const predefineColor = [
'#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d'
];
const predefineColor = ['#409EFF', '#1890ff', '#304156', '#212121', '#11a983', '#13c2c2', '#6959CD', '#f5222d']
const operator = (type) => {
switch (type) {
@ -103,45 +97,54 @@
return
}
}
const changeSwitch = (key,val) => {
SettingStore.setThemeConfig({key, val})
//
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)=>{
const changePrimary = (val) => {
if (!val) {
primary.value = val = PRIMARY_COLOR;
ElMessage({ type: "success", message: `主题颜色已重置为 ${PRIMARY_COLOR}` });
primary.value = val = PRIMARY_COLOR
ElMessage({ type: 'success', message: `主题颜色已重置为 ${PRIMARY_COLOR}` })
}
//
document.documentElement.style.setProperty("--el-color-primary-dark-2", `${getDarkColor(val, 0.1)}`);
document.documentElement.style.setProperty("--el-color-primary", val);
//
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(
`--el-color-primary-light-${i}`,
`${getLightColor(val, i / 10)}`
);
}
changeSwitch('primary',val)
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)
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;
}
::v-deep(.el-drawer__header) {
border-bottom: 1px solid #ebeef5;
padding: 15px 20px 14px;
margin-bottom: 0;
}
.m-setting-fix {
position: fixed;
top: 50%;
@ -181,19 +184,17 @@
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;
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;
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
}
::v-deep(.el-drawer__title) {
:deep(.el-drawer__title) {
font-weight: bold;
color: black;
}
@ -205,6 +206,5 @@
font-size: 14px;
color: black;
justify-content: space-between;
}
</style>

View File

@ -1,26 +1,21 @@
<template>
<div class="m-wangEditor">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
<Editor
class="editor-content'"
style="height: 300px; overflow-y: hidden;"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
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 { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css' // css
import {onBeforeUnmount, onMounted, watch, shallowRef, ref, computed} from 'vue'
import { onBeforeUnmount, onMounted, watch, shallowRef, ref, computed } from 'vue'
let editors = null
// shallowRef
const editorRef = shallowRef()
@ -45,18 +40,18 @@
}
const valueHtml = computed({
get(){
get() {
return props.modelValue
},
set(val){
set(val) {
//
if (editorRef.value.isEmpty()) val = "";
if (editorRef.value.isEmpty()) val = ''
emit('update:modelValue', val)
}
},
})
//
onBeforeUnmount(()=>{
onBeforeUnmount(() => {
// API
const editor = editorRef.value
if (editor == null) {
@ -66,15 +61,15 @@
})
</script>
<style lang="scss" scoped>
.m-wangEditor{
z-index: 99;
width: 100%;
border: 1px solid #cccccc;
.editor-toolbar {
border-bottom: 1px solid #cccccc;
.m-wangEditor {
z-index: 99;
width: 100%;
border: 1px solid #cccccc;
.editor-toolbar {
border-bottom: 1px solid #cccccc;
}
.editor-content {
overflow-y: hidden;
}
}
.editor-content {
overflow-y: hidden;
}
}
</style>

View File

@ -1,10 +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>

View File

@ -1,2 +1,2 @@
// * 默认主题颜色
export const PRIMARY_COLOR: string = "#409eff";
export const PRIMARY_COLOR = '#409eff'

View File

@ -1,4 +0,0 @@
import { useDark, useToggle } from '@vueuse/core'
export const isDark = useDark()
export const toggleDark = useToggle(isDark)

112
src/hooks/useFullscreen.ts Normal file
View File

@ -0,0 +1,112 @@
/**
* @description
* @vueuse/core useFullscreen useFullscreen
*
*/
import { ElMessage } from 'element-plus'
import { onMounted } from 'vue'
export const useFullscreen = () => {
/**
* @description: +
*/
const isFullscreen = () => {
let prefixName = '' // 浏览器前缀
let fullscreenEnabled = false
// 判断浏览器前缀
if (document.fullscreenEnabled) {
fullscreenEnabled = document.fullscreenEnabled
// webkit
} else if (document.webkitFullscreenEnabled) {
fullscreenEnabled = document.webkitFullscreenEnabled
prefixName = 'webkit'
// moz
} else if (document.mozFullScreenEnabled) {
fullscreenEnabled = document.mozFullScreenEnabled
prefixName = 'moz'
// ms
} else if (document.msFullscreenEnabled) {
fullscreenEnabled = document.msFullscreenEnabled
prefixName = 'ms'
}
return {
fullscreenEnabled,
prefixName,
}
}
/**
* @description:
* @return
*/
const isElementFullScreen = () => {
const fullscreenElement =
document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement
if (fullscreenElement === null) {
return false // 当前没有元素在全屏状态
} else {
return true // 有元素在全屏状态
}
}
/**
* @description:
* @param {String} domName dom名称
*/
const Fullscreen = (target) => {
const targetRef = target || (document == null ? void 0 : document.querySelector('html'))
const { prefixName } = isFullscreen()
const methodName = prefixName === '' ? 'requestFullscreen' : `${prefixName}RequestFullScreen`
targetRef[methodName]()
}
// 退出全屏
const exitFullscreen = () => {
const { prefixName } = isFullscreen()
const methodName = prefixName === '' ? 'exitFullscreen' : `${prefixName}ExitFullscreen`
document[methodName]()
}
/**
* @description: ,,
* @param {Function} enterErrorFn
*/
const screenError = () => {
const { prefixName } = isFullscreen()
const methodName = `on${prefixName}fullscreenerror`
document[methodName] = (e) => {
ElMessage.error('进入全屏失败')
}
}
/**
* @description: /
* @param {Function} enter
* @param {Function} quit
*/
const screenChange = (enter, quit) => {
const { fullscreenEnabled, prefixName } = isFullscreen()
if (!fullscreenEnabled) return
const methodName = `on${prefixName}fullscreenchange`
document[methodName] = (e) => {
if (isElementFullScreen()) {
enter && enter(e) // 进入全屏回调
} else {
quit && quit(e) // 离开全屏的回调
}
}
}
onMounted(() => {
screenError()
})
return {
isFullscreen,
isElementFullScreen,
Fullscreen,
exitFullscreen,
screenChange,
}
}

View File

@ -0,0 +1,39 @@
import ResizeObserver from 'resize-observer-polyfill'
import { onBeforeUnmount } from 'vue'
import requestAnimationFrameThrottle from '@/utils/requestAnimationFrameThrottle'
export const useResizeElement = (chart, chartsRef) => {
let observer = null
let widthW = 0
let heightW = 0
const handleResize = (entries) => {
const { contentRect } = entries[0]
let { width, height } = contentRect
width = Math.floor(width)
height = Math.floor(height)
if (widthW !== width || heightW !== height) {
widthW = width
heightW = height
chart && chart.resize()
}
}
const addObserver = () => {
observer = new ResizeObserver(requestAnimationFrameThrottle(handleResize))
observer.observe(chartsRef)
}
const removeObserver = () => {
if (observer) {
observer.disconnect()
observer = null
}
chart && chart.dispose()
}
onBeforeUnmount(() => {
removeObserver()
})
return {
addObserver,
}
}

View File

@ -0,0 +1,66 @@
import { useSettingStore } from '@/store/modules/setting'
import { computed, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
const { body } = document
const WIDTH = 800 // refer to Bootstrap's responsive design
const MAX_WIDTH = 1200
export const useResizeHandler = () => {
const SettingStore = useSettingStore()
const route = useRoute()
const device = computed(() => {
return SettingStore.device
})
function $_isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - 1 < WIDTH
}
function collapse() {
const rect = body.getBoundingClientRect()
if (rect.width - 1 > MAX_WIDTH) {
return true
} else {
return false
}
}
function $_resizeHandler() {
if (!document.hidden) {
// bool型表示页面是否处于隐藏状态。页面隐藏包括页面在后台标签页或者浏览器最小化
const isMobile = $_isMobile()
const isCollapse = collapse()
SettingStore.toggleDevice(isMobile ? 'mobile' : 'desktop')
if (isMobile) {
SettingStore.closeSideBar({ withoutAnimation: true })
}
if (!isMobile) {
SettingStore.setCollapse(isCollapse)
}
}
}
onMounted(() => {
const isMobile = $_isMobile()
if (isMobile) {
SettingStore.toggleDevice('mobile')
SettingStore.closeSideBar({ withoutAnimation: true })
}
window.addEventListener('resize', $_resizeHandler)
watch(route, () => {
if (device.value === 'mobile' && SettingStore.isCollapse) {
SettingStore.closeSideBar({ withoutAnimation: false })
}
})
})
onUnmounted(() => {
window.removeEventListener('resize', $_resizeHandler)
})
return { device }
}

View File

@ -0,0 +1,23 @@
// 自定义name的壳的集合
import { h } from 'vue'
const wrapperMap = new Map()
export const useWrapComponents = (Component, route) => {
let wrapper
if (Component) {
const wrapperName = route.name
if (wrapperMap.has(wrapperName)) {
wrapper = wrapperMap.get(wrapperName)
} else {
wrapper = {
name: wrapperName,
render() {
return h('div', { className: 'app-main-inner' }, Component)
},
}
wrapperMap.set(wrapperName, wrapper)
}
return h(wrapper)
}
}

View File

@ -1,7 +1,6 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon' // svg component
// const req = require.context('./svg', false, /\.svg$/)
const req = import.meta.globEager('./svg/*.svg')

View File

@ -1,30 +0,0 @@
<template>
<div class="hamburger-container" @click="handleCollapse">
<el-icon class="icon" v-if="isCollapse" ><expand /></el-icon>
<el-icon class="icon" v-else><fold /></el-icon>
</div>
</template>
<script lang="ts" setup>
import {useSettingStore} from "@/store/modules/setting"
import {computed} from "_vue@3.2.40@vue";
const SettingStore = useSettingStore()
const isCollapse = computed(() =>!SettingStore.isCollapse)
const handleCollapse = () => {
SettingStore.setCollapse(isCollapse.value)
}
</script>
<style lang="scss" scoped>
.hamburger-container{
padding: 0px 15px;
height: 100%;
display: flex;
align-items: center;
&:hover {
background: rgba(0, 0, 0, .025)
}
.icon {
font-size: 24px;
cursor: pointer;
}
}
</style>

View File

@ -1,171 +0,0 @@
<template>
<div class="m-headerSearch">
<el-tooltip effect="dark" content="菜单搜索" placement="bottom">
<el-icon class="bell" style="font-size: 22px;" @click="handleSearch"><Search /></el-icon>
</el-tooltip>
<el-dialog v-model="isShowSearch" width="600px" destroy-on-close :show-close="false">
<el-select
style="width: 100%"
ref="headerSearchSelect"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="菜单搜索 :支持菜单名称、路径"
class="header-search-select"
@change="change"
>
<el-option v-for="item in options" :key="item.item.path" :value="item.item.path" :label="item&&item.item.title&&item.item.title.length&&item.item.title.join(' > ') " >
</el-option>
</el-select>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import {computed, onMounted, ref, watch} from 'vue'
import path from 'path-browserify'
import Fuse from 'fuse.js'
import { useVueFuse } from 'vue-fuse'
import {useRouter} from "vue-router";
const router = useRouter()
const isShowSearch = ref(false);
const options = ref([]);
const searchPool = ref([]);
const search = ref('');
const fuse = ref(null);
import {usePermissionStore} from "@/store/modules/permission"
const PermissionStore = usePermissionStore()
const routes = computed(() => PermissionStore.routes)
const handleSearch = () => {
isShowSearch.value = true
}
const initFuse = (list)=> {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
}
watch(searchPool,(list)=>{
initFuse(list)
})
// 线
const generateRoutes = (routes, basePath = '/', prefixTitle = [])=>{
let res = []
for (const router of routes) {
//
if (router.hidden) { continue }
const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle]
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
if (router.redirect !== 'noRedirect') {
//
//
res.push(data)
}
}
//
if (router.children) {
const tempRoutes = generateRoutes(router.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
}
const change=(val)=> {
if(val){
router.push({
path: val,
})
}
options.value = []
search.value = ''
isShowSearch.value = false
}
onMounted(()=>{
searchPool.value = generateRoutes(JSON.parse(JSON.stringify(routes.value)))
})
const querySearch=(query)=> {
if (query !== '') {
options.value = fuse.value.search(query)
} else {
options.value = []
}
}
</script>
<style lang="scss" scoped>
.m-headerSearch {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.item-info-pop {
display: flex;
align-items: center;
}
.bell{
color: black;
}
.item-child {
display: flex;
align-items: center;
font-size: 13px;
}
}
.transverseMenu {
.bell {
color: white;
}
}
.header-search-select{
}
/* 菜单搜索样式 */
.m-headerSearch {
:deep(.el-dialog) {
.el-dialog__header {
display: none;
}
.el-dialog__body{
padding: 0;
}
}
.header-search-select{
height: 50px;
:deep(.el-input__wrapper){
height: 50px;
}
}
}
</style>

View File

@ -1,26 +0,0 @@
<template>
<div class="m-screenful">
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="toggle" class="full-screen"/>
</div>
</template>
<script lang="ts" setup>
import { useFullscreen } from "@vueuse/core";
const { toggle, isFullscreen } = useFullscreen();
</script>
<style lang="scss" scoped>
.m-screenful {
display: flex;
align-items: center;
padding-right: 0;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
}
.transverseMenu {
.full-screen {
color: white;
}
}
</style>

View File

@ -1,45 +0,0 @@
<template>
<div class="m-setting">
<el-tooltip effect="dark" content="主题设置" placement="bottom">
<el-icon style="font-size: 20px;" class="bell"><Setting @click="changeSwitch('showSetting',true)"/></el-icon>
</el-tooltip>
</div>
</template>
<script lang="ts" setup>
import {useSettingStore} from "@/store/modules/setting"
const SettingStore = useSettingStore()
const changeSwitch = (key,val) => {
SettingStore.setThemeConfig({key, val})
}
</script>
<style lang="scss" scoped>
.m-setting {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.item-info-pop {
display: flex;
align-items: center;
}
.bell{
color: black;
}
.item-child {
display: flex;
align-items: center;
font-size: 13px;
}
}
.transverseMenu {
.bell {
color: white;
}
}
</style>

View File

@ -1,41 +0,0 @@
<template>
<el-dropdown trigger="click" @command="setAssemblySize">
<svg-icon class-name="size-icon" icon-class="size" style="font-size: 20px;"/>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in assemblySizeList" :key="item" :disabled="globalComSize === item" :command="item">
{{ assemblySizeListCh[item] }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
import { reactive, computed } from "vue";
import {useSettingStore} from "@/store/modules/setting"
const SettingStore = useSettingStore()
const globalComSize = computed(():string=>SettingStore.themeConfig.globalComSize)
const assemblySizeListCh = reactive<{ [key: string]: any }>({
default: "默认",
large: "大型",
small: "小型"
});
const assemblySizeList = reactive<string[]>(["default", "large", "small"]);
const setAssemblySize = (item: string) => {
if (globalComSize.value === item) return;
SettingStore.setThemeConfig({key:'globalComSize', val:item})
};
</script>
<style scoped lang="scss">
.transverseMenu {
.size-icon {
color: white;
}
}
</style>

View File

@ -1,204 +0,0 @@
<template>
<div
class="m-layout-header"
:class="[
SettingStore.themeConfig.fixedHeader?'zb-fixed-header':'zb-no-fixed-header',
mode === 'horizontal'?'':isCollapse?'fixed-header-collapse':'fixed-header-no-collapse'
]"
>
<div
class="header"
:class="{
transverseMenu: mode === 'horizontal',
}"
>
<UMenu v-if="mode === 'horizontal'" />
<div class="left" v-if="mode === 'vertical'">
<CollapseIcon/>
<Hamburger />
</div>
<div class="right">
<GlobalComSize class="right-item-menu"/>
<HeaderSearch class="right-item-menu"/>
<Remind class="right-item-menu"/>
<ScreenFull class="right-item-menu"/>
<Setting class="right-item-menu"/>
<el-dropdown @command="commandAction">
<span class="el-dropdown-link">
<el-avatar :icon="UserFilled" :size="30" style="margin-right: 6px" />{{
userInfo.username
}}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="2" >
<el-icon><Edit /></el-icon></el-dropdown-item>
<el-dropdown-item :command="1" divided>
<el-icon><SwitchButton /></el-icon>退</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<tag-views v-if="showTag" />
<personal ref="person" />
</div>
</template>
<script lang="ts" setup>
import { UserFilled } from '@element-plus/icons-vue'
import Personal from './components/Personal.vue'
import TagViews from '../TagsView/index.vue'
import GlobalComSize from './components/globalComSize.vue'
import Hamburger from '@/components/Hamburger/index.vue'
import Setting from './components/Setting.vue'
import ScreenFull from './components/ScreenFull.vue'
import Remind from './components/Remind'
import HeaderSearch from './components/HeaderSearch'
import CollapseIcon from './components/CollapseIcon'
import UMenu from '../Sidebar/components/Menu.vue'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessageBox, ElMessage } from 'element-plus'
import {useSettingStore} from "@/store/modules/setting"
import {useUserStore} from "@/store/modules/user"
import {usePermissionStore} from "@/store/modules/permission"
import {useTagsViewStore} from "@/store/modules/tagsView"
const person = ref()
const router = useRouter()
const SettingStore = useSettingStore()
const UserStore = useUserStore()
const PermissionStore = usePermissionStore()
const TagsViewStore = useTagsViewStore()
const isCollapse = computed(() =>!SettingStore.isCollapse)
// menu
const mode = computed(() => SettingStore.themeConfig.mode)
// tag
const showTag = computed(() =>SettingStore.themeConfig.showTag)
//
const userInfo = computed(() => UserStore.userInfo)
const logOut = async () => {
ElMessageBox.confirm('确定退出登录吗?', '退出登录', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
router.push({ path: '/login' })
await UserStore.logout()
PermissionStore.clearRoutes()
TagsViewStore.clearVisitedView()
ElMessage({
type: "success",
message: "退出登录成功!"
});
} catch (e) {}
})
.catch(() => {})
}
const commandAction = (key: number) => {
switch (key) {
case 1:
logOut()
break
case 2:
person.value.show()
break
}
}
const handleCollapse = () => {
SettingStore.setCollapse(isCollapse.value)
}
</script>
<style lang="scss" scoped>
.mobile {
.m-layout-header {
left: 0 !important;
width: 100%!important;
}
}
.header {
height: 50px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
padding: 0 10px 0 0;
box-sizing: border-box;
justify-content: space-between;
.left {
display: flex;
align-items: center;
height: 100%;
}
.right {
display: flex;
align-items: center;
.right-item-menu{
margin-right: 22px;
}
}
}
.zb-fixed-header{
position: fixed;
top: 0;
right: 0;
z-index: 9;
}
.zb-no-fixed-header{
width: 100%!important;;
}
.m-layout-header {
width: 100%;
background: white;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
}
.fixed-header-collapse{
width: calc(100% - 60px);
}
.fixed-header-no-collapse{
width: calc(100% - 210px);
}
.el-dropdown {
display: flex;
height: 100%;
align-items: center;
}
.el-dropdown-link {
cursor: pointer;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
.transverseMenu{
display: flex;
.el-menu{
overflow: hidden;
}
.right{
display: flex;
justify-content: flex-end;
min-width:300px ;
flex-shrink: 0;
}
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<div class="main-columns">
<div class="layout-columns-aside">
<div class="logo flex-center">
<img src="@/assets/image/logo.png" alt="logo" />
</div>
<el-scrollbar>
<div class="menu-wrap">
<div
v-for="item in menusRoutes"
:key="item.path"
class="item-menu-wrap"
:class="{
'active-menu': activeCurrentMenu === item.path,
}"
@click="handleChangeMenu(item)"
>
<el-icon :size="20">
<component :is="item?.meta?.icon"></component>
</el-icon>
<span class="title">{{ item?.meta?.title }}</span>
</div>
</div>
</el-scrollbar>
</div>
<div class="layout-columns-sub" :style="{ width: isCollapse ? '60px' : '210px' }">
<div class="logo flex-center">
<span v-show="subMenus.length">{{ isCollapse ? 'Vue' : 'Vue Admin Perfect' }}</span>
</div>
<el-scrollbar>
<el-menu
:collapse="isCollapse"
:router="false"
:default-active="activeMenu"
:unique-opened="SettingStore.themeConfig.uniqueOpened"
:collapse-transition="false"
class="menu-columns"
>
<SubMenu :menu-list="subMenus" />
</el-menu>
</el-scrollbar>
</div>
<div class="container">
<div class="layout-header">
<div class="header-tool">
<HeaderToolLeft />
<HeaderToolRight />
</div>
<TagsView v-if="themeConfig.showTag" />
</div>
<Main />
<Footer />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
import { useSettingStore } from '@/store/modules/setting'
import Footer from '../components/Footer/index.vue'
import SubMenu from '../components/SubMenu/SubMenu.vue'
import TagsView from '../components/TagsView/index.vue'
const PermissionStore = usePermissionStore()
const SettingStore = useSettingStore()
const route = useRoute()
const router = useRouter()
import HeaderToolRight from '../components/Header/ToolRight.vue'
import HeaderToolLeft from '../components/Header/ToolLeft.vue'
import Main from '../components/Main/index.vue'
//
const permission_routes = computed(() => PermissionStore.permission_routes)
//
const menusRoutes = computed(() => {
return PermissionStore.permission_routes.filter((item) => !item.hidden)
})
const activeCurrentMenu = ref('')
//
const themeConfig = computed(() => SettingStore.themeConfig)
const isCollapse = computed(() => !SettingStore.isCollapse)
const activeMenu = computed(() => {
const { meta, path } = route
return path
})
const basePath = ref<string>('/')
const subMenus = ref([])
watch(
() => [route],
() => {
if (!menusRoutes.value.length) return
const [firstMenu] = route.matched
activeCurrentMenu.value = firstMenu.path
let menuItem = menusRoutes.value.find((item) => firstMenu.path === item.path)
if (menuItem && menuItem.children?.length) {
subMenus.value = menuItem.children
} else {
subMenus.value = []
}
basePath.value = firstMenu.path
},
{
deep: true,
immediate: true,
},
)
const handleChangeMenu = (item) => {
router.push(item.path)
}
</script>
<style lang="scss" scoped>
.main-columns {
display: flex;
flex-direction: row !important;
height: 100%;
width: 100%;
}
.layout-columns-aside {
flex-shrink: 0;
width: 80px;
height: 100%;
background-color: #304156;
.el-scrollbar {
height: calc(100% - 55px);
}
.logo {
box-sizing: border-box;
height: 50px;
img {
width: 32px;
object-fit: contain;
}
}
.menu-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.item-menu-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 70px;
width: 70px;
cursor: pointer;
transition: all 0.3s ease;
}
.active-menu {
background: $primaryColor;
border-radius: 5px;
}
.title {
color: #e5eaf3;
}
.el-icon {
color: #e5eaf3;
}
}
}
.layout-columns-sub {
flex-shrink: 0;
width: 200px;
box-sizing: border-box;
flex-direction: column;
overflow: hidden;
transition: all 0.3s ease;
background: white;
border-right: 1px solid #eee;
.el-scrollbar {
height: calc(100vh - 50px);
}
.logo {
width: 100%;
box-sizing: border-box;
height: 50px;
border-bottom: 1px solid #eee;
span {
font-weight: bold;
white-space: nowrap;
}
}
::v-deep(.menu-columns) {
border-right: none;
}
}
.container {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.layout-header {
background: white;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
.header-tool {
height: 50px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
padding: 0 10px 0 0;
box-sizing: border-box;
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,34 @@
.m-layout-header {
width: 100%;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
.header-inner {
height: 50px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
background-color: $menuBg;
align-items: center;
padding: 0 10px 0 0;
box-sizing: border-box;
justify-content: space-between;
}
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
}
.menu-horizontal {
flex: 1;
overflow: hidden;
height: 100%;
:deep(.el-menu-item) {
height: 100%;
}
}

View File

@ -0,0 +1,61 @@
<template>
<!--纵向布局-->
<Height />
<div
class="m-layout-header"
:class="{
'fixed-header': themeConfig.fixedHeader,
}"
>
<div class="header-inner">
<el-menu
mode="horizontal"
:default-active="activeMenu"
background-color="#304156"
text-color="#bfcbd9"
:unique-opened="SettingStore.themeConfig.uniqueOpened"
:collapse-transition="false"
class="menu-horizontal"
>
<SubItem v-for="route in permission_routes" :key="route.path" :item="route" />
</el-menu>
<HeaderToolRight />
</div>
<TagsView v-if="themeConfig.showTag" />
</div>
</template>
<script lang="ts" setup>
//
import Height from '../../components/Header/components/Height.vue'
import HeaderToolRight from '../../components/Header/ToolRight.vue'
import TagsView from '../../components/TagsView/index.vue'
import SubItem from '../../components/SubMenu/SubItem.vue'
import { useRoute } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
const PermissionStore = usePermissionStore()
const route = useRoute()
//
const permission_routes = computed(() => PermissionStore.permission_routes)
import { computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
//
const themeConfig = computed(() => SettingStore.themeConfig)
const isCollapse = computed(() => !SettingStore.isCollapse)
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,24 @@
<template>
<div class="main-container">
<UHeader />
<Main />
<Footer />
</div>
</template>
<script lang="ts" setup>
import Sidebar from '../components/Sidebar/index.vue'
import UHeader from './HeaderHorizontal/index.vue'
import Main from '../components/Main/index.vue'
import Footer from '../components/Footer/index.vue'
</script>
<style lang="scss" scoped>
.main-container {
display: flex;
flex: 1;
box-sizing: border-box;
flex-direction: column;
min-height: 100%;
}
</style>

View File

@ -0,0 +1,67 @@
.mobile {
.m-layout-header {
left: 0 !important;
width: 100% !important;
}
}
.show-tag {
height: 90px;
}
.zb-no-fixed-header {
width: 100% !important;
}
.m-layout-header {
width: 100%;
background: white;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
.header-inner {
height: 50px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
padding: 0 10px 0 0;
box-sizing: border-box;
justify-content: space-between;
}
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
}
.collapse {
width: calc(100% - 60px);
}
.no-collapse {
width: calc(100% - 210px);
}
.el-dropdown {
display: flex;
height: 100%;
align-items: center;
}
.transverseMenu {
display: flex;
.el-menu {
overflow: hidden;
}
:deep(.el-menu-item) {
height: 100% !important;
}
.tool-bar-right {
display: flex;
justify-content: flex-end;
min-width: 300px;
flex-shrink: 0;
}
}

View File

@ -0,0 +1,38 @@
<template>
<!--纵向布局-->
<Height />
<div
class="m-layout-header"
:class="{
'fixed-header': themeConfig.fixedHeader,
collapse: themeConfig.fixedHeader && isCollapse,
'no-collapse': themeConfig.fixedHeader && !isCollapse,
}"
>
<div class="header-inner">
<HeaderToolLeft />
<HeaderToolRight />
</div>
<TagsView v-if="themeConfig.showTag" />
</div>
</template>
<script lang="ts" setup>
//
import Height from '../../components/Header/components/Height.vue'
import HeaderToolRight from '../../components/Header/ToolRight.vue'
import HeaderToolLeft from '../../components/Header/ToolLeft.vue'
import TagsView from '../../components/TagsView/index.vue'
import { computed } from 'vue'
import { useSettingStore } from '@/store/modules/setting'
const SettingStore = useSettingStore()
//
const themeConfig = computed(() => SettingStore.themeConfig)
const isCollapse = computed(() => !SettingStore.isCollapse)
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,46 @@
<template>
<!--纵向布局-->
<Sidebar />
<div class="main-container">
<HeaderVertical />
<Main />
<Footer />
</div>
</template>
<script lang="ts" setup>
import Sidebar from '../components/Sidebar/index.vue'
import HeaderVertical from './HeaderVertical/index.vue'
import Main from '../components/Main/index.vue'
import Footer from '../components/Footer/index.vue'
</script>
<style lang="scss" scoped>
.g-container-layout {
height: 100%;
width: 100%;
.main-container {
display: flex;
flex: 1;
box-sizing: border-box;
flex-direction: column;
}
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.sidebar-container {
display: flex;
flex-direction: column;
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 90;
}
</style>

View File

@ -1,36 +0,0 @@
<template>
<div class="app-main" v-if="isReload">
<router-view v-slot="{ Component, route }">
<transition name="fade-slide" mode="out-in" appear>
<keep-alive :include="cacheRoutes">
<component :is="Component" :key="route.path" />
</keep-alive>
</transition>
</router-view>
<Theme />
</div>
</template>
<script lang="ts" setup>
import Theme from '@/components/Theme/index.vue'
import { computed, ref } from 'vue'
import {useSettingStore} from "@/store/modules/setting"
import {usePermissionStore} from "@/store/modules/permission"
const SettingStore = useSettingStore()
const PermissionStor = usePermissionStore()
const cacheRoutes = computed(() =>PermissionStor.getCacheRoutes())
const isReload = computed(() => SettingStore.isReload)
</script>
<style lang="scss" scoped>
.app-main {
flex: 1;
display: flex;
overflow-x: hidden;
flex-direction: column;
width: 100%;
box-sizing: border-box;
}
</style>

View File

@ -1,50 +0,0 @@
<template>
<el-menu
:default-active="activeMenu"
background-color="#304156"
text-color="#bfcbd9"
:mode="mode"
:unique-opened="SettingStore.themeConfig.uniqueOpened"
:collapse-transition="false"
class="el-menu-vertical-demo"
:collapse="isCollapse"
>
<sub-item
v-for="route in permission_routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</template>
<script lang="ts" setup>
import SubItem from './SubItem.vue'
import { useRoute } from 'vue-router'
import {usePermissionStore} from "@/store/modules/permission"
import {useSettingStore} from "@/store/modules/setting"
import { computed } from 'vue'
// setupstore
const route = useRoute()
const PermissionStore = usePermissionStore()
const SettingStore = useSettingStore()
//
const permission_routes = computed(() => PermissionStore.permission_routes)
const activeMenu = computed(() => {
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
//
const isCollapse = computed(() => !SettingStore.isCollapse)
//
const mode = computed(() => SettingStore.themeConfig.mode)
</script>

View File

@ -1,79 +0,0 @@
<template>
<template v-if="!item.hidden">
<template v-if="!item.alwaysShow && hasOneShowingChild(item.children, item)">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)">
<el-icon :size="20">
<component :is="onlyOneChild?.meta.icon"></component>
</el-icon>
<template #title>{{ onlyOneChild.meta && onlyOneChild.meta.title }}</template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu :index="resolvePath(item.path)" v-else popper-append-to-body>
<template #title>
<el-icon :size="20"> <component :is="item.meta?.icon"></component></el-icon>
<span>{{ item.meta && item.meta.title }}</span>
</template>
<sub-item
v-for="child in item.children"
:key="child.path"
:item="child"
:base-path="resolvePath(child.path)"
/>
</el-sub-menu>
</template>
</template>
<script lang="ts" setup>
import { isExternal } from '@/utils/validate.js'
import AppLink from './Link.vue'
import path from 'path-browserify'
import { ref, computed } from 'vue'
const props = defineProps({
item: {
type: Object,
required: true,
},
basePath: {
type: String,
default: '',
},
})
const onlyOneChild = ref(null)
const hasOneShowingChild = (children = [], parent) => {
const showingChildren = children.filter((item) => {
//
if (item.hidden) {
return false
} else {
// 使
onlyOneChild.value = item
return true
}
})
//
if (showingChildren.length === 1) {
return true
}
//
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
}
const resolvePath = (routePath) => {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
return path.resolve(props.basePath, routePath)
}
</script>

View File

@ -1,34 +0,0 @@
<template>
<div :class="{ 'has-logo': themeConfig.showLogo }">
<logo :isCollapse="isCollapse" v-if="themeConfig.showLogo"/>
<el-scrollbar wrap-class="scrollbar-wrapper">
<u-menu/>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import UMenu from './components/Menu.vue'
import logo from './components/Logo.vue'
import {useSettingStore} from "@/store/modules/setting"
import { computed } from 'vue'
const SettingStore = useSettingStore()
//
const isCollapse = computed(() => !SettingStore.isCollapse)
//
const themeConfig = computed(() =>SettingStore.themeConfig )
</script>
<style lang="scss">
.el-menu-vertical-demo:not(.el-menu--collapse) {
//width: 200px;
height: 100%;
}
.crollbar-wrapper {
height: 100%;
.el-scrollbar__view {
height: 100%;
}
}
</style>

View File

@ -1,58 +0,0 @@
<template>
<div ref="bsWrap" class="tags-scroll-wrap">
<div ref="bsContent" class="tags-scroll">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { useElementSize } from '@vueuse/core'
import BScroll from '@better-scroll/core'
import type { Options } from '@better-scroll/core'
interface Props {
/** better-scroll的配置: https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html */
options: Options
}
const props = defineProps<Props>()
const bsWrap = ref<HTMLElement>()
const instance = ref<BScroll>()
const bsContent = ref<HTMLElement>()
function initBetterScroll() {
nextTick(() => {
instance.value = new BScroll(bsWrap.value, props.options)
})
}
// BS
const { width: wrapWidth } = useElementSize(bsWrap)
const { width, height } = useElementSize(bsContent)
watch([() => wrapWidth.value, () => width.value, () => height.value], () => {
if (instance.value) {
instance.value.refresh()
}
})
onMounted(() => {
initBetterScroll()
})
defineExpose({ instance })
</script>
<style scoped>
.tags-scroll-wrap {
width: 100%;
}
.tags-scroll-inner {
/*display: flex;*/
}
.tags-scroll {
display: inline-block;
}
</style>

View File

@ -1,329 +0,0 @@
<template>
<div class="tags-wrap-container">
<div class="tags-view" ref="scrollContainer">
<better-scroll :options="{ scrollX: true, scrollY: false }" ref="bsScroll">
<div class="tags-scroll-inner">
<div
v-for="tag in visitedViews"
:ref="setTagRef"
:path="tag.path"
:data-id="tag.path"
:fullPath="tag.fullPath"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
:key="tag.path"
class="item-tag-wrap"
:class="isActive(tag) ? 'active' : ''"
@click.stop="routerGo(tag)"
>
<div class="tags-view-item">{{ tag.title }}</div>
<el-icon
v-if="!isAffix(tag)"
@click.prevent.stop="(e) => closeSelectedTag(e, tag)" class="tag-icon">
<circle-close-filled
/></el-icon>
</div>
</div>
</better-scroll>
</div>
<el-dropdown trigger="click">
<div class="item-tag-wrap more">
<div class="tags-view-item">更多 <el-icon class="el-icon--right"><arrow-down /></el-icon></div>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="refresh">刷新当页</el-dropdown-item>
<el-dropdown-item @click="closeCurrentTab">关闭当前</el-dropdown-item>
<el-dropdown-item @click="closeOtherTab">关闭其他</el-dropdown-item>
<el-dropdown-item @click="closeAllTab">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script lang="ts" setup>
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
import betterScroll from './betterScroll.vue'
import { useRoute, useRouter } from 'vue-router'
import {useSettingStore} from "@/store/modules/setting"
import {useTagsViewStore} from "@/store/modules/tagsView"
import {usePermissionStore} from "@/store/modules/permission"
import path from 'path-browserify'
const route = useRoute()
const router = useRouter()
const SettingStore = useSettingStore()
const TagsViewStore = useTagsViewStore()
const PermissionStore = usePermissionStore()
const refresh = () => {
SettingStore.setReload()
}
const routes = computed(() => PermissionStore.routes)
const visitedViews = computed(() => TagsViewStore.visitedViews)
const bsScroll = ref<Expose.BetterScroll>()
let obj = new WeakMap()
let affixTags = ref([])
const tags = ref(new Map())
// ref tag
const setTagRef = (el) => {
if (el) {
if (!obj.get(el)) {
tags.value.set(el.dataset['id'], el)
}
obj.set(el, el)
}
}
const rollPane = ref()
const scrollContainer = ref()
function filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta },
})
}
if (route.children) {
const tempTags = filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
}
const initTags = () => {
let routesNew = routes.value
let affixTag = (affixTags.value = filterAffixTags(routesNew))
for (const tag of affixTag) {
if (tag.name) {
TagsViewStore.addVisitedView(tag)
}
}
}
const isActive = (rou) => {
return rou.path === route.path
}
const isAffix = (tag) => {
return tag.meta && tag.meta.affix
}
const addTags = () => {
const { name } = route
if (name === 'Login') {
return
}
if (name) {
TagsViewStore.addView(route)
}
return false
}
function toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
router.push(latestView.fullPath)
} else {
if (view.name === 'home') {
router.replace({ path: '/redirect' + view.fullPath })
} else {
router.push('/')
}
}
}
const closeSelectedTag = async (event, view) => {
if (tags.value.get(view.path)) {
tags.value.delete(view.path)
}
let { visitedViews } = await TagsViewStore.delView(view)
if (isActive(view)) {
toLastView(visitedViews, view)
}
}
//
const closeCurrentTab = (event)=>{
closeSelectedTag(event,route)
}
//
const closeOtherTab= async ()=>{
const { name } = route
for(let item of visitedViews.value){
if(item.name!==name){
await closeSelectedTag(null,item)
}
}
}
//
const closeAllTab = async ()=>{
let visitedViews = await TagsViewStore.delAllViews()
toLastView(visitedViews,route)
}
const routerGo = (tag) => {
router.push({
path: tag.path,
query: tag.query,
})
}
function handleScrollAction(currentTag) {
const scrollContainerRect = scrollContainer.value.getBoundingClientRect()
let { left: currx, width: currentWidth } = currentTag.getBoundingClientRect()
const clientX = currx + currentWidth / 2
const currentX = clientX - scrollContainerRect.left
const deltaX = currentX - scrollContainerRect.width / 2
if (bsScroll.value) {
const { maxScrollX, x: leftX } = bsScroll.value.instance
const rightX = maxScrollX - leftX
const update = deltaX > 0 ? Math.max(-deltaX, rightX) : Math.min(-deltaX, -leftX)
bsScroll.value?.instance.scrollBy(update, 0, 300)
}
}
function moveToCurrentTag() {
nextTick(() => {
for (const [key, tag] of tags.value) {
let path = tag.attributes.path.value
if (path === route.path) {
let fullPath = tag.attributes.fullPath.value
//
handleScrollAction(tag, tags.value)
if (fullPath !== route.fullPath) {
TagsViewStore.updateVisitedView(route)
}
break
}
}
})
}
onMounted(() => {
initTags()
addTags()
nextTick(() => {
setTimeout(() => {
moveToCurrentTag()
}, 50)
})
watch(route, () => {
addTags()
nextTick(() => {
setTimeout(() => {
moveToCurrentTag()
}, 100)
})
})
router.beforeEach(async (to, from, next) => {
if (
(from.fullPath === '/error/404' || from.fullPath === '/error/401') &&
to.fullPath === '/home'
) {
let whiteList = ['/error/404', '/error/401']
await TagsViewStore.removeView(whiteList)
}
next()
})
})
</script>
<style lang="scss" scoped>
.tags-wrap-container {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 10px;
.tags-view {
height: 30px;
background: white;
display: flex;
align-items: center;
flex: 1;
box-sizing: border-box;
overflow: hidden;
}
.refresh {
//margin-left: 20px;
cursor: pointer;
font-size: 22px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
.refresh-inner {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 100%;
}
//box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
}
//padding-right: 10px;
}
.item-tag-wrap {
position: relative;
display: inline-flex;
align-items: center;
padding: 4px 12px;
font-size: 14px;
cursor: pointer;
margin-right: 10px;
border: 1px solid #d8dce5;
&.active .tag-icon {
display: block;
}
&.active {
background-color: $primaryColor;
color: #fff;
border-color: $primaryColor;
}
}
.item-tag-wrap:hover {
border-color: $primaryColor;
}
.tags-scroll-inner {
display: flex;
flex-wrap: nowrap;
}
.tag-icon {
margin-left: 6px;
}
.tags-view-item {
position: relative;
z-index: 2;
white-space: nowrap;
font-size: 12px;
.tags-inner {
display: flex;
align-items: center;
white-space: nowrap;
}
}
.more{
background-color: $primaryColor;
color: white;
.tags-view-item{
display: flex;
align-items: center;
}
}
</style>

View File

@ -0,0 +1,19 @@
<template>
<div class="footer-layout">
<span href="/" target="_blank"> 2022 © VUE-ADMIN-PERFECT By ZB Technology. </span>
</div>
</template>
<style scoped lang="scss">
.footer-layout {
height: 40px;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
background: #ffffff;
border-top: 1px solid #e4e7ed;
flex-shrink: 0;
color: rgba(0, 0, 0, 0.45);
}
</style>

View File

@ -0,0 +1,19 @@
<template>
<div class="m-tool-left">
<CollapseIcon />
<Hamburger />
</div>
</template>
<script lang="ts" setup>
import CollapseIcon from './components/CollapseIcon.vue'
import Hamburger from './components/Hamburger.vue'
</script>
<style lang="scss" scoped>
.m-tool-left {
display: flex;
align-items: center;
height: 100%;
}
</style>

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