Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10ac766489 | ||
|
|
d05ac59983 | ||
|
|
0793f9fb58 | ||
|
|
7103c121f3 | ||
|
|
3c1001dd76 | ||
|
|
17f45c1c99 | ||
|
|
3d80604f11 | ||
|
|
66c0644019 | ||
|
|
c9f6fe57fb | ||
|
|
8113b0b11e | ||
|
|
bab5825081 | ||
|
|
7c11af66a8 | ||
|
|
a0de373666 | ||
|
|
39de7fe1ac | ||
|
|
3b0a68c739 | ||
|
|
8fa9c3c4a2 | ||
|
|
1d57dd78b8 | ||
|
|
2925c5d832 | ||
|
|
5ab9b4ec08 | ||
|
|
3baf83c09b | ||
|
|
fda5a40864 | ||
|
|
e7459bc460 | ||
|
|
0563748bf6 | ||
|
|
b5684d1d59 | ||
|
|
321931f6de | ||
|
|
b45c0f4b08 | ||
|
|
c53834bcaa | ||
|
|
5f181fae03 | ||
|
|
66a3c584df | ||
|
|
8ac3bc681a | ||
|
|
f5f1d28435 | ||
|
|
6c87ac2508 | ||
|
|
bdfd41c4d6 | ||
|
|
c037e12157 | ||
|
|
5d8383c519 | ||
|
|
1361525f95 | ||
|
|
795ece2225 | ||
|
|
2768c33f7f | ||
|
|
9361d9ccad | ||
|
|
a0ac6f9424 | ||
|
|
1caf9733e2 | ||
|
|
22cd6da835 | ||
|
|
014eee5584 | ||
|
|
3921bd2780 | ||
|
|
ea52fb0fec | ||
|
|
03a104ac52 | ||
|
|
1cf1c0364a | ||
|
|
eeb54d1726 | ||
|
|
2fa87b524a | ||
|
|
09206b884e | ||
|
|
cc467db879 | ||
|
|
092fbe5bd5 | ||
|
|
4049c625b6 | ||
|
|
949fc6f25b | ||
|
|
8a26b162f9 | ||
|
|
dcbcc775b6 | ||
|
|
accd569fba | ||
|
|
afe261072d | ||
|
|
e41942b55b | ||
|
|
ffc8856c96 | ||
|
|
da47b1eeaf | ||
|
|
543b221a72 | ||
|
|
6d8c9278e5 | ||
|
|
418a8e77d8 | ||
|
|
4699093e6d | ||
|
|
a981538a69 | ||
|
|
fab8d0df4e | ||
|
|
97275bf98d | ||
|
|
7ff2dc0108 | ||
|
|
8baf570b8b | ||
|
|
ea18a476e1 | ||
|
|
1943e2339a | ||
|
|
78e38f9e92 | ||
|
|
294f2458e4 | ||
|
|
bb848895f7 | ||
|
|
1e4d2633bc | ||
|
|
b6c0f49f01 | ||
|
|
00acb21c8f | ||
|
|
55bba0f121 | ||
|
|
940d9af6fb | ||
|
|
f7dac95732 | ||
|
|
6fbf1d3514 | ||
|
|
00ea15f56b | ||
|
|
870792f6ec | ||
|
|
3da2172bce | ||
|
|
8f00712c43 | ||
|
|
ba685d1753 | ||
|
|
75254dd17f | ||
|
|
af9cb48659 | ||
|
|
8d21343575 | ||
|
|
0c9e11ce2a | ||
|
|
25cb558601 | ||
|
|
15d7e0c555 | ||
|
|
a985df0af7 | ||
|
|
4784ff379e | ||
|
|
a67e302453 | ||
|
|
5b463cb88b | ||
|
|
00fe12a91c | ||
|
|
d7572ec856 | ||
|
|
e2dd6c47f5 | ||
|
|
619fe79c27 | ||
|
|
70b640af4f | ||
|
|
cdbca16c87 | ||
|
|
16096a2e83 | ||
|
|
bb6ddbe5a9 | ||
|
|
d00b9c385f | ||
|
|
3cfb256281 | ||
|
|
b624ebdfe0 | ||
|
|
e6e126d775 | ||
|
|
48979c5b31 | ||
|
|
e0352e4333 | ||
|
|
dcb72392f2 | ||
|
|
3e5bab6097 | ||
|
|
fe9a1fa88e | ||
|
|
bde9954e83 | ||
|
|
2fc9dc57d5 | ||
|
|
d5e600d856 | ||
|
|
2c7d94bac5 | ||
|
|
89e305f002 | ||
|
|
96e067ff6a | ||
|
|
2ab66b6c1a | ||
|
|
08a7e57728 | ||
|
|
6c40c8c1ec | ||
|
|
0902a65fc8 | ||
|
|
51c1b78f6a | ||
|
|
a91124d87e | ||
|
|
264806b275 | ||
|
|
f945e92e08 | ||
|
|
9d82a1e6f8 | ||
|
|
e3c773a0fa | ||
|
|
5b9bd73340 | ||
|
|
5740774c2b | ||
|
|
99634a49d7 | ||
|
|
7cfca66ac4 | ||
|
|
efa0fcab52 | ||
|
|
759763c875 | ||
|
|
d97c6245b4 | ||
|
|
2c58db5885 | ||
|
|
0ac3969348 | ||
|
|
18e0c9c18a | ||
|
|
6f290604a2 | ||
|
|
416773aaab | ||
|
|
e8e11be678 | ||
|
|
a113097fae | ||
|
|
0992ef57b6 | ||
|
|
4e1e0e7ce2 | ||
|
|
01828a12ae | ||
|
|
c9947cf164 | ||
|
|
2707d63005 | ||
|
|
5eb7aba333 | ||
|
|
0e2da2d401 | ||
|
|
acbc11794a | ||
|
|
03444338c9 | ||
|
|
014e5f6804 | ||
|
|
3096dccd05 | ||
|
|
fea0add957 | ||
|
|
5be0ef087d | ||
|
|
8537161a08 | ||
|
|
c72188cc91 | ||
|
|
14358031e7 |
@@ -1,3 +0,0 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
5
.env.development
Normal file
@@ -0,0 +1,5 @@
|
||||
# 本地环境
|
||||
NODE_ENV = 'development'
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_APP_BASE_API = '/api'
|
||||
5
.env.production
Normal file
@@ -0,0 +1,5 @@
|
||||
# 线上环境
|
||||
NODE_ENV = "production"
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_APP_BASE_API = '/api'
|
||||
6
.env.test
Normal file
@@ -0,0 +1,6 @@
|
||||
# 测试环境
|
||||
NODE_ENV = "test"
|
||||
|
||||
|
||||
# 测试环境接口地址
|
||||
VITE_APP_BASE_API = '/api'
|
||||
20
.eslintignore
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
*.sh
|
||||
node_modules
|
||||
*.css
|
||||
*.jpg
|
||||
*.jpeg
|
||||
*.png
|
||||
*.gif
|
||||
*.md
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
/public
|
||||
/docs
|
||||
.husky
|
||||
.local
|
||||
/bin
|
||||
Dockerfile
|
||||
66
.eslintrc.js
Normal file
@@ -0,0 +1,66 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-recommended',
|
||||
'eslint:recommended',
|
||||
'plugin:vue/vue3-essential',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
plugins: ['vue', '@typescript-eslint'],
|
||||
globals: {
|
||||
defineProps: 'readonly',
|
||||
defineEmits: 'readonly',
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'off', // 禁止调用console对象的方法。
|
||||
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-empty-function': ['off'], // 关闭空函数警告
|
||||
'@typescript-eslint/no-inferrable-types': 'off', // 可以轻松推断的显式类型可能会增加不必要的冗长
|
||||
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间
|
||||
'@typescript-eslint/ban-types': 'off', // 禁止使用特定类型
|
||||
'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词
|
||||
'vue/no-v-html': 'off', // 禁止使用 v-html
|
||||
'no-undef': 'off',
|
||||
'no-redeclare': 'off',
|
||||
'no-self-assign': 'off',
|
||||
'no-sparse-arrays': 'off',
|
||||
'vue/valid-v-for': 'off',
|
||||
'vue/no-unused-vars': 'off',
|
||||
'vue/require-v-for-key': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'vue/require-explicit-emits': 'off',
|
||||
'no-case-declarations': 'off', // 不允许在 case 子句中使用词法声明
|
||||
//禁止非空断言非空断言是在变量后面添加一个感叹号(!),表示该变量一定存在,不会为 null 或 undefined
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'vue/require-prop-types': 'off',
|
||||
'@typescript-eslint/no-this-alias': 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'vue/no-template-shadow': 'off',
|
||||
'vue/require-default-prop': 'off', // 此规则要求为每个 prop 为必填时,必须提供默认值
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
// // 禁止定义未使用的变量
|
||||
'off',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
22
.gitignore
vendored
@@ -1,20 +1,22 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/docs
|
||||
dist
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist-ssr
|
||||
dist
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.vscode
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
|
||||
4
.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
9
.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
||||
/dist/*
|
||||
.local
|
||||
.output.js
|
||||
/node_modules/**
|
||||
|
||||
**/*.svg
|
||||
**/*.sh
|
||||
|
||||
/public/*
|
||||
16
.prettierrc.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
// 一行的字符数,如果超过会进行换行,默认为80
|
||||
printWidth: 140,
|
||||
// 缩进制表符宽度 | 空格数
|
||||
tabWidth: 2,
|
||||
// 行位是否使用分号,默认为true
|
||||
semi: false,
|
||||
vueIndentScriptAndStyle: true,
|
||||
// 字符串是否使用单引号,默认为false,使用双引号
|
||||
singleQuote: true,
|
||||
// 是否使用尾逗号,有三个可选值"<none|es5|all>"
|
||||
trailingComma: 'all',
|
||||
proseWrap: 'never',
|
||||
htmlWhitespaceSensitivity: 'strict',
|
||||
endOfLine: 'auto',
|
||||
};
|
||||
108
AxiosRequestConfig.md
Normal file
@@ -0,0 +1,108 @@
|
||||
```
|
||||
export interface AxiosRequestConfig {
|
||||
// `url` 是用于请求的服务器 URL
|
||||
url?: string;
|
||||
|
||||
// `method` 是创建请求时使用的方法
|
||||
method?: Method;
|
||||
|
||||
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
|
||||
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
|
||||
baseURL?: string;
|
||||
|
||||
// `transformRequest` 允许在向服务器发送前,修改请求数据
|
||||
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
|
||||
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
|
||||
transformRequest?: AxiosTransformer | AxiosTransformer[];
|
||||
|
||||
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
|
||||
transformResponse?: AxiosTransformer | AxiosTransformer[];
|
||||
|
||||
// `headers` 是即将被发送的自定义请求头
|
||||
headers?: any;
|
||||
|
||||
// `params` 是即将与请求一起发送的 URL 参数
|
||||
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
|
||||
params?: any;
|
||||
|
||||
// `paramsSerializer` 是一个负责 `params` 序列化的函数
|
||||
paramsSerializer?: (params: any) => string;
|
||||
|
||||
// `data` 是作为请求主体被发送的数据
|
||||
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
|
||||
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
|
||||
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
|
||||
// - 浏览器专属:FormData, File, Blob
|
||||
// - Node 专属: Stream
|
||||
data?: any;
|
||||
|
||||
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
|
||||
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
|
||||
timeout?: number;
|
||||
|
||||
// 超时提示消息
|
||||
timeoutErrorMessage?: string;
|
||||
|
||||
// `withCredentials` 表示跨域请求时是否需要使用凭证
|
||||
withCredentials?: boolean;
|
||||
|
||||
// `adapter` 允许自定义处理请求,以使测试更轻松
|
||||
adapter?: AxiosAdapter;
|
||||
|
||||
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
|
||||
auth?: AxiosBasicCredentials;
|
||||
|
||||
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
|
||||
responseType?: ResponseType;
|
||||
|
||||
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
|
||||
xsrfCookieName?: string;
|
||||
|
||||
// `xsrfHeaderName` 是携带 xsrf 令牌值的 http 标头的名称
|
||||
xsrfHeaderName?: string;
|
||||
|
||||
// `onUploadProgress` 允许为上传处理进度事件
|
||||
onUploadProgress?: (progressEvent: any) => void;
|
||||
|
||||
// `onDownloadProgress` 允许为下载处理进度事件
|
||||
onDownloadProgress?: (progressEvent: any) => void;
|
||||
|
||||
// `maxContentLength` 定义允许的响应内容的最大尺寸
|
||||
maxContentLength?: number;
|
||||
|
||||
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。
|
||||
// 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
|
||||
// promise 将被 resolve; 否则,promise 将被 rejecte
|
||||
validateStatus?: ((status: number) => boolean) | null;
|
||||
|
||||
// 请求体最大尺寸
|
||||
maxBodyLength?: number;
|
||||
|
||||
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
|
||||
// 如果设置为0,将不会 follow 任何重定向
|
||||
maxRedirects?: number;
|
||||
|
||||
// `socketPath` 定义了一个在 node.js 中使用的 UNIX Socket。
|
||||
// 只能指定 `socketPath` 或 `proxy`。
|
||||
// 如果两者都指定,则使用 `socketPath`。
|
||||
socketPath?: string | null;
|
||||
|
||||
// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。
|
||||
httpAgent?: any;
|
||||
httpsAgent?: any;
|
||||
|
||||
// 'proxy' 定义代理服务器的主机名称和端口
|
||||
proxy?: AxiosProxyConfig | false;
|
||||
|
||||
// `cancelToken` 指定用于取消请求的 cancel token
|
||||
cancelToken?: CancelToken;
|
||||
|
||||
// 将其设置为`false`,它将不会解压缩您的响应,而是保留原始的Content-Encoding头。
|
||||
// 默认是true
|
||||
decompress?: boolean;
|
||||
|
||||
// 控制响应数据是否转换
|
||||
transitional?: TransitionalOptions
|
||||
}
|
||||
|
||||
```
|
||||
159
README.md
@@ -1,75 +1,114 @@
|
||||
# 简介
|
||||
vue-element-perfect 是一个后台前端解决方案, 基于Vue3.0+TS+Element-plus实现。它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件。
|
||||
## 简介
|
||||
vue-element-perfect 是一个后台前端解决方案,它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件,企业级中后台解决方案,可免费商用,同时支持PC、平板、手机
|
||||
本项目也参考了很多开源的项目、
|
||||
### 在线预览
|
||||
- gitee国内访问地址:https://yuanzbz.gitee.io/vue-admin-perfect/#/home
|
||||
- github site : https://zouzhibin.github.io/vue-admin-perfect/
|
||||
|
||||
## 效果预览
|
||||
- 基础功能版本预览:https://yuanzbz.gitee.io/vue-admin-simple
|
||||
|
||||
### 在线预览点击 —— [企业级、通用型中后台前端解决方案 ](http://182.61.5.190:8889/)
|
||||
|
||||
### 附上github地址点击跳转 [vue-admin-perfect](https://github.com/zouzhibin/vue-admin-perfect)
|
||||
|
||||
## 目录结构介绍
|
||||
### git仓库(欢迎 Star⭐)
|
||||
- Gitee: —— [https://gitee.com/yuanzbz/vue-admin-perfect](https://gitee.com/yuanzbz/vue-admin-perfect)
|
||||
- GitHub: —— [https://github.com/zouzhibin/vue-admin-perfect](https://github.com/zouzhibin/vue-admin-perfect)
|
||||
- 基础功能版本: —— [https://gitee.com/yuanzbz/vue-admin-simple](https://gitee.com/yuanzbz/vue-admin-simple)
|
||||
|
||||
## 项目功能
|
||||
- 使用Vue3.0开发,单文件组件采用<script setup>
|
||||
- 采用 Vite3 作为项目开发、打包工具(配置了 Gzip 打包、TSX 语法、跨域代理)
|
||||
- 整个项目集成了 TypeScript
|
||||
- 登录逻辑,使用vue-router进行路由权限拦截,判断,路由懒加载
|
||||
- 使用 keep-alive 对整个页面进行缓存,支持多级嵌套页面
|
||||
- 侧边栏导航菜单栏动态的显示
|
||||
- 各种可视化地图组件
|
||||
- 使用 Pinia替代 Vuex,轻量、简单、易用
|
||||
- 导出excel,自定义样式导出excel、多表头导出
|
||||
- 表单、表格、水印、多标签显示、打印功能,图片打印、表格打印、普通打印、二维码、拖拽、markdown、头像裁剪、图片上传...
|
||||
- 使用 Prettier 统一格式化代码,集成 Eslint、代码校验规范
|
||||
|
||||
## 安装
|
||||
```
|
||||
# GitHub
|
||||
git clone https://github.com/zouzhibin/vue-admin-perfect.git
|
||||
# Gitee
|
||||
git clone https://gitee.com/yuanzbz/vue-admin-perfect.git
|
||||
```
|
||||
|
||||
├─assets 放置一些静态文件
|
||||
├─components 放置组件
|
||||
├─layout 首页布局
|
||||
│ ├─components
|
||||
│ │ ├─Sidebar 侧边栏配置
|
||||
│ │ ├─TagsView 标签栏配置
|
||||
│ │ └─UHeader 头部内容配置
|
||||
│ └─hooks
|
||||
├─router 路由配置
|
||||
├─store 数据存储
|
||||
├─styles 样式文件
|
||||
├─utils 工具方法
|
||||
└─views 页面配置方法
|
||||
└─permission 路由拦截 权限配置
|
||||
## 分支管理
|
||||
- master 技术采用 vite + vue3.0 + Typescript + pinia
|
||||
- vue-admin-simple 简易版本
|
||||
- vite-vuex vite + vue3.0 + Typescript + vuex
|
||||
- vue-i18n 语言切换版本
|
||||
- webpack 技术采用 webpack + vue3.0 + Typescript + vuex
|
||||
- uniapp uniapp版本 uniapp +vuex +element scss
|
||||
```
|
||||
# 本地开发 启动项目
|
||||
借助hbuilder工具运行浏览器启动
|
||||
```
|
||||
|
||||
## 下载依赖
|
||||
```
|
||||
npm install
|
||||
cnpm install
|
||||
yarn
|
||||
# npm install 安装失败,请升级 nodejs 到 16 以上,或尝试使用以下命令:
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
```
|
||||
## 运行打包
|
||||
```
|
||||
npm run dev
|
||||
npm run build
|
||||
```
|
||||
## eslint+prettier
|
||||
```
|
||||
# eslint 检测代码
|
||||
npm run lint
|
||||
|
||||
#prettier 格式化代码
|
||||
npm run lint:prettier
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
vue3.0 + Typescript + vuex + vue-router + Element-Plus scss
|
||||
|
||||
## 实现功能
|
||||
- 登录逻辑
|
||||
- 权限校验
|
||||
- 侧边栏导航显示
|
||||
- 打印
|
||||
- 地图
|
||||
- 头像裁剪
|
||||
- 无限滚动
|
||||
- 多标签显示
|
||||
- 退出
|
||||
- 其他各组件
|
||||
|
||||
|
||||
## 项目 master 分支 基于vue3.0 + Typescript + vuex + vue-router + Element-Plus scss
|
||||
## 文件目录结构
|
||||
```
|
||||
# 克隆项目
|
||||
git clone https://github.com/zouzhibin/vue-admin-perfect.git
|
||||
|
||||
# 安装依赖
|
||||
yarn
|
||||
|
||||
# 本地开发 启动项目
|
||||
yarn serve
|
||||
vue-admin-perfect
|
||||
├─ public # 静态资源文件(忽略打包)
|
||||
├─ src
|
||||
│ ├─ api # API 接口管理
|
||||
│ ├─ assets # 静态资源文件
|
||||
│ ├─ components # 全局组件
|
||||
│ ├─ config # 全局配置项
|
||||
│ ├─ hooks # 常用 Hooks
|
||||
│ ├─ language # 语言国际化
|
||||
│ ├─ layout # 框架布局
|
||||
│ ├─ routers # 路由管理
|
||||
│ ├─ store # pinia store
|
||||
│ ├─ styles # 全局样式
|
||||
│ ├─ utils # 工具库
|
||||
│ ├─ views # 项目所有页面
|
||||
│ ├─ App.vue # 入口页面
|
||||
│ └─ main.ts # 入口文件
|
||||
├─ .env # vite 常用配置
|
||||
├─ .env.development # 开发环境配置
|
||||
├─ .env.production # 生产环境配置
|
||||
├─ .env.test # 测试环境配置
|
||||
├─ .eslintignore # 忽略 Eslint 校验
|
||||
├─ .eslintrc.cjs # Eslint 校验配置
|
||||
├─ .gitignore # git 提交忽略
|
||||
├─ .prettierignore # 忽略 prettier 格式化
|
||||
├─ .prettierrc.config.js # prettier 配置
|
||||
├─ index.html # 入口 html
|
||||
├─ yarn.lock # 依赖包包版本锁
|
||||
├─ package.json # 依赖包管理
|
||||
├─ README.md # README 介绍
|
||||
├─ tsconfig.json # typescript 全局配置
|
||||
└─ vite.config.ts # vite 配置
|
||||
```
|
||||
|
||||
|
||||
## 项目 uniapp 分支 基于 uniapp +vuex +element scss
|
||||
|
||||
```
|
||||
# 克隆项目 切换 uniapp分支
|
||||
git clone https://github.com/zouzhibin/vue-admin-perfect.git
|
||||
|
||||
# 安装依赖
|
||||
yarn
|
||||
|
||||
# 本地开发 启动项目
|
||||
hbuilder运行浏览器启动
|
||||
```
|
||||
|
||||
|
||||
### 微信交流群
|
||||
| 微信二维码 |
|
||||
| :----------------------------------------------------------------------------------: |
|
||||
| <img src="http://182.61.5.190:8889/we.png" width=170/> |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
84
commitlint.config.js
Normal 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: '',
|
||||
},
|
||||
}
|
||||
18
deploy.sh
@@ -1,18 +0,0 @@
|
||||
#强制推送
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
npm run build
|
||||
cd dist
|
||||
touch .nojekyll
|
||||
git init
|
||||
git add -A
|
||||
git commit -m 'deploy'
|
||||
git push -f "https://${access_token}@gitee.com/yuanzbz/vue-admin-perfect.git" master:gh-pages
|
||||
git push -f "https://${access_token}@github.com/zouzhibin/vue-admin-perfect.git" master:gh-pages
|
||||
|
||||
cd -
|
||||
exec /bin/bash
|
||||
|
||||
|
||||
|
||||
|
||||
122
index.html
Normal file
@@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" >
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vue admin perfect</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<style>
|
||||
.first-loading-wrp {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 90vh;
|
||||
min-height: 90vh;
|
||||
}
|
||||
.first-loading-wrp > h1 {
|
||||
font-size: 30px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
.first-loading-wrp .loading-wrp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 98px;
|
||||
}
|
||||
.dot {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
font-size: 64px;
|
||||
transform: rotate(45deg);
|
||||
animation: antRotate 1.2s infinite linear;
|
||||
}
|
||||
|
||||
.dot i {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 100%;
|
||||
opacity: 0.3;
|
||||
transform: scale(0.75);
|
||||
transform-origin: 50% 50%;
|
||||
animation: antSpinMove 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
.dot i:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.dot i:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(3) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(4) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
-webkit-animation-delay: 1.2s;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
@keyframes antRotate {
|
||||
to {
|
||||
-webkit-transform: rotate(405deg);
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes antRotate {
|
||||
to {
|
||||
-webkit-transform: rotate(405deg);
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes antSpinMove {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes antSpinMove {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="vue-admin-perfect">
|
||||
<div class="first-loading-wrp">
|
||||
<div class="loading-wrp">
|
||||
<span class="dot dot-spin">
|
||||
<i></i>
|
||||
<i></i>
|
||||
<i></i>
|
||||
<i></i>
|
||||
</span>
|
||||
</div>
|
||||
<h1>vue-admin-perfect</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
23609
package-lock.json
generated
93
package.json
@@ -1,49 +1,106 @@
|
||||
{
|
||||
"name": "vue-admin-perfect",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "npm run serve",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
"dev": "vite",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:test": "vite build --mode test",
|
||||
"build:prod": "vite build --mode production",
|
||||
"commit": "git add -A && czg && git push",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"build:ts": "vue-tsc --noEmit --skipLibCheck && vite build",
|
||||
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx",
|
||||
"lint:fix": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
|
||||
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-scroll/core": "^2.4.2",
|
||||
"@vueuse/core": "^9.1.1",
|
||||
"@wangeditor/editor": "^5.1.14",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"axios": "^0.27.2",
|
||||
"clipboard": "^2.0.10",
|
||||
"codemirror": "^5.65.9",
|
||||
"core-js": "^3.6.5",
|
||||
"dayjs": "^1.11.4",
|
||||
"echarts": "^5.3.1",
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"element-plus": "^2.1.4",
|
||||
"element-plus": "^2.2.28",
|
||||
"exceljs": "^4.3.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"fuse.js": "^6.6.2",
|
||||
"jszip": "^3.9.1",
|
||||
"mavon-editor": "^2.10.4",
|
||||
"md-editor-v3": "^1.11.3",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"pinia": "^2.0.21",
|
||||
"pinia-plugin-persistedstate": "^2.1.1",
|
||||
"print-js": "^1.6.0",
|
||||
"raf": "^3.4.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sass": "^1.54.0",
|
||||
"splitpanes": "^3.1.1",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"vue": "^3.0.0",
|
||||
"vue": "^3.2.39",
|
||||
"vue-cropper": "^1.0.3",
|
||||
"vue-cropperjs": "^5.0.0",
|
||||
"vue-fuse": "^4.1.1",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-qr": "^4.0.6",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-splitpane": "^1.0.6",
|
||||
"vue3-text-clamp": "^0.1.1",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.0-0",
|
||||
"wangeditor": "^4.7.12",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.8",
|
||||
"@vue/cli-plugin-router": "~4.5.8",
|
||||
"@vue/cli-plugin-typescript": "~4.5.8",
|
||||
"@vue/cli-plugin-vuex": "~4.5.8",
|
||||
"@vue/cli-service": "~4.5.8",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"typescript": "~4.1.5"
|
||||
"@commitlint/cli": "^17.3.0",
|
||||
"@commitlint/config-conventional": "^17.3.0",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.32.0",
|
||||
"@typescript-eslint/parser": "^5.32.0",
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"commitizen": "^4.2.5",
|
||||
"consola": "^2.15.3",
|
||||
"cz-git": "^1.3.12",
|
||||
"czg": "^1.3.12",
|
||||
"dart-sass": "^1.25.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.3.4",
|
||||
"mrm": "^3.0.10",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.6.4",
|
||||
"unplugin-auto-import": "^0.10.3",
|
||||
"unplugin-vue-components": "^0.21.2",
|
||||
"unplugin-vue-define-options": "^0.7.3",
|
||||
"vite": "^3.0.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||
"vue-tsc": "^0.38.4"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-git"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{vue,js,ts,jsx,tsx}": [
|
||||
"npm run lint:prettier",
|
||||
"npm run lint:fix",
|
||||
"git add ."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
7640
pnpm-lock.yaml
generated
Normal file
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
BIN
public/static/screen/bg.jpg
Normal file
|
After Width: | Height: | Size: 978 KiB |
BIN
public/static/screen/bg.png
Normal file
|
After Width: | Height: | Size: 895 KiB |
BIN
public/static/screen/footer2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/static/screen/header-bg.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
public/static/screen/server-bg.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
13
push.sh
@@ -1,13 +0,0 @@
|
||||
#强制推送
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
git init
|
||||
git add -A
|
||||
git commit -m '🎉 feat: init project'
|
||||
git push -f "https://${access_token}@gitee.com/yuanzbz/vue-admin-perfect.git" master
|
||||
git push -f "https://${access_token}@github.com/zouzhibin/vue-admin-perfect.git" master
|
||||
exec /bin/bash
|
||||
|
||||
|
||||
|
||||
|
||||
55
src/App.vue
@@ -1,28 +1,37 @@
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component"/>
|
||||
</router-view>
|
||||
<el-config-provider :size="globalComSize" :locale="zhCn">
|
||||
<router-view></router-view>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/*background: #f6f8f9;*/
|
||||
/*background: goldenrod;*/
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
}
|
||||
html,body{
|
||||
height: 100%;
|
||||
background: #f6f8f9;
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
// 配置element中文
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
|
||||
}
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
const SettingStore = useSettingStore()
|
||||
// 配置全局组件大小
|
||||
const globalComSize = computed((): string => SettingStore.themeConfig.globalComSize)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: Avenir, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
}
|
||||
.el-pager li:focus {
|
||||
border: none;
|
||||
}
|
||||
.el-dropdown:focus {
|
||||
border: none;
|
||||
}
|
||||
.svg-icon:focus {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
|
||||
<template>
|
||||
<div class="page_search">
|
||||
<el-button type="primary" size="small">新增</el-button>
|
||||
|
||||
<div class="search">
|
||||
<el-input
|
||||
v-model="search"
|
||||
placeholder="请输入名称"
|
||||
size="small"
|
||||
class="input-with-select"
|
||||
>
|
||||
<template #append>
|
||||
<el-button icon="el-icon-search"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-button type="primary" class="search" icon="el-icon-share" @click="exportExcel">导出</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref } from "vue";
|
||||
import Excel from "exceljs";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const search = ref("");
|
||||
|
||||
const downloadBlob=(arrayList,type,fileName)=>{
|
||||
var blob=new Blob(arrayList,{type:type});
|
||||
var a = document.createElement('a'); //创建a标签用于下载
|
||||
a.download = fileName;
|
||||
a.href = URL.createObjectURL(blob);
|
||||
document.getElementsByTagName("body")[0].appendChild(a);
|
||||
a.click();
|
||||
document.getElementsByTagName("body")[0].removeChild(a);
|
||||
};
|
||||
|
||||
const exportExcel=async()=>{
|
||||
let workbook = new Excel.Workbook();
|
||||
let worksheet = workbook.addWorksheet('报表-威尔森',{views: [{showGridLines: true}]});
|
||||
let alignObj={alignment:{vertical:'middle',horizontal:'center'}};
|
||||
let columns=[{header:"",key:"A",style:alignObj},
|
||||
{header:"",key:"B",width:12,style:alignObj},
|
||||
{header:"",key:"C",style:alignObj},
|
||||
{header:"",key:"D",style:alignObj}];
|
||||
worksheet.columns = columns;
|
||||
|
||||
// 开始添加数据
|
||||
let data=[
|
||||
{A:"COD",B:'',C:"氨氮",D:''},
|
||||
{A:"进水",B:"出水",C:"进水",D:"出水"},
|
||||
{A:1,B:2,C:3,D:4},
|
||||
{A:11,B:2,C:3,D:4},
|
||||
{A:1,B:22,C:3,D:4}
|
||||
];
|
||||
for(let i in data) {
|
||||
worksheet.addRow(data[i]).commit();
|
||||
}
|
||||
|
||||
worksheet.mergeCells('A2:B2');
|
||||
worksheet.mergeCells('C2:D2');
|
||||
|
||||
worksheet.getRow(2).border={top: {style:'thin'},left: {style:'thin'},bottom: {style:'thin'},right: {style:'thin'}}
|
||||
|
||||
//await worksheet.commit();
|
||||
|
||||
// write to a new buffer
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
//console.log(buffer);
|
||||
downloadBlob([buffer],"application/octet-stream","data.xlsx");
|
||||
};
|
||||
|
||||
return {
|
||||
search,exportExcel,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page_search {
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.search {
|
||||
margin-left: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,200 +0,0 @@
|
||||
const ExcelJS = require("exceljs");
|
||||
|
||||
const autoWidthAction = (val,width=10)=>{
|
||||
if (val == null) {
|
||||
width = 10;
|
||||
} else if (val.toString().charCodeAt(0) > 255) {
|
||||
/*if chinese*/
|
||||
width = val.toString().length * 2;
|
||||
} else {
|
||||
width = val.toString().length;
|
||||
}
|
||||
return width
|
||||
}
|
||||
export const exportExcel = ({column,data,filename,autoWidth})=>{
|
||||
// 创建excel
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
// 设置信息
|
||||
workbook.creator = "Me";
|
||||
workbook.title = filename;
|
||||
workbook.created = new Date();
|
||||
workbook.modified = new Date();
|
||||
// 创建工作表
|
||||
const worksheet = workbook.addWorksheet(filename);
|
||||
// 设置列名
|
||||
let columnsName = [];
|
||||
column.forEach((item,index)=>{
|
||||
let obj = {
|
||||
header: item.label, key:item.name, width: null
|
||||
}
|
||||
if(autoWidth){
|
||||
let maxArr = [autoWidthAction(item.label)]
|
||||
data.forEach(ite=>{
|
||||
let str = ite[item.name] ||''
|
||||
if(str){
|
||||
maxArr.push(autoWidthAction(str))
|
||||
}
|
||||
})
|
||||
obj.width = Math.max(...maxArr)+5
|
||||
}
|
||||
// 设置列名、键和宽度
|
||||
columnsName.push(obj);
|
||||
})
|
||||
worksheet.columns = columnsName;
|
||||
// 添加行
|
||||
worksheet.addRows(data);
|
||||
// 写入文件
|
||||
workbook.xlsx.writeBuffer().then((data) => {
|
||||
const blob = new Blob([data, { type: "application/vnd.ms-excel" }]);
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
// msSaveOrOpenBlob方法返回boolean值
|
||||
navigator.msSaveBlob(blob, filename + ".xlsx");
|
||||
// 本地保存
|
||||
} else {
|
||||
const link = document.createElement("a"); // a标签下载
|
||||
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接
|
||||
link.download = filename + ".xlsx"; // dowload属性指定文件名
|
||||
link.click(); // click()事件触发下载
|
||||
window.URL.revokeObjectURL(link.href); // 释放内存
|
||||
}
|
||||
});
|
||||
}
|
||||
// 默认的列宽
|
||||
export const DEFAULT_COLUMN_WIDTH = 20;
|
||||
|
||||
function getColumnNumber(width: number) {
|
||||
// 需要的列数,四舍五入
|
||||
return Math.round(width / DEFAULT_COLUMN_WIDTH);
|
||||
}
|
||||
|
||||
|
||||
function addData(worksheet,headerKeys,headers,data){
|
||||
|
||||
}
|
||||
|
||||
export const exportMultiHeaderExcel = ({column,data,filename,autoWidth})=>{
|
||||
// 创建excel
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
// 创建工作表
|
||||
const worksheet = workbook.addWorksheet(filename);
|
||||
|
||||
// 第一行表头
|
||||
const names1= [];
|
||||
// 第二行表头
|
||||
const names2= [];
|
||||
|
||||
// 用于匹配数据的 keys
|
||||
const headerKeys= [];
|
||||
|
||||
let headers = [];
|
||||
column.forEach((item,index)=>{
|
||||
let obj = {
|
||||
header: item.label, key:item.name, width: null
|
||||
}
|
||||
let maxArr = [autoWidthAction(item.label)]
|
||||
data.forEach(ite=>{
|
||||
let str = ite[item.name] ||''
|
||||
if(str){
|
||||
maxArr.push(autoWidthAction(str))
|
||||
}
|
||||
})
|
||||
obj.width = Math.max(...maxArr)+5
|
||||
// 设置列名、键和宽度
|
||||
headers.push(obj);
|
||||
})
|
||||
|
||||
column.forEach(item => {
|
||||
if (item.children) {
|
||||
// 有 children 说明是多级表头,header name 需要两行
|
||||
item.children.forEach(child => {
|
||||
names1.push(item.label);
|
||||
names2.push(child.label);
|
||||
headerKeys.push(child.name);
|
||||
});
|
||||
} else {
|
||||
names1.push(item.label);
|
||||
names2.push(item.label);
|
||||
headerKeys.push(item.name);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// 判断是否有 children, 有的话是两行表头
|
||||
const isMultiHeader = column.some(item => item.children);
|
||||
if(isMultiHeader){
|
||||
const rowHeader1 = worksheet.addRow(names1);
|
||||
const rowHeader2 = worksheet.addRow(names2);
|
||||
|
||||
console.log('rowHeader1====',names1,names2,rowHeader1,rowHeader2)
|
||||
mergeColumnCell(headers, rowHeader1, rowHeader2, names1, names2, worksheet)
|
||||
// return
|
||||
|
||||
}
|
||||
|
||||
data.forEach((item: any) => {
|
||||
const rowData = headerKeys?.map(key => item[key]);
|
||||
const row = worksheet.addRow(rowData);
|
||||
// console.log('row--------',row)
|
||||
})
|
||||
// 写入文件
|
||||
workbook.xlsx.writeBuffer().then((data) => {
|
||||
const blob = new Blob([data, { type: "application/vnd.ms-excel" }]);
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
// msSaveOrOpenBlob方法返回boolean值
|
||||
navigator.msSaveBlob(blob, filename + ".xlsx");
|
||||
// 本地保存
|
||||
} else {
|
||||
const link = document.createElement("a"); // a标签下载
|
||||
link.href = window.URL.createObjectURL(blob); // href属性指定下载链接
|
||||
link.download = filename + ".xlsx"; // dowload属性指定文件名
|
||||
link.click(); // click()事件触发下载
|
||||
window.URL.revokeObjectURL(link.href); // 释放内存
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function mergeColumnCell(headers, rowHeader1, rowHeader2, nameRow1, nameRow2, worksheet){
|
||||
// 当前 index 的指针
|
||||
let pointer = -1;
|
||||
nameRow1.forEach((name, index) => {
|
||||
// 当 index 小于指针时,说明这一列已经被合并过了,不能再合并
|
||||
if (index <= pointer) return;
|
||||
// 是否应该列合并
|
||||
const shouldVerticalMerge = name === nameRow2[index];
|
||||
|
||||
|
||||
// 是否应该行合并
|
||||
const shouldHorizontalMerge = index !== nameRow1.lastIndexOf(name);
|
||||
|
||||
console.log('==',name,nameRow2[index],index,nameRow1.lastIndexOf(name),shouldVerticalMerge,shouldHorizontalMerge)
|
||||
|
||||
pointer = nameRow1.lastIndexOf(name);
|
||||
if (shouldVerticalMerge && shouldHorizontalMerge) {
|
||||
// 两个方向都合并
|
||||
worksheet.mergeCells(
|
||||
Number(rowHeader1.number),
|
||||
index + 1,
|
||||
Number(rowHeader2.number),
|
||||
nameRow1.lastIndexOf(name) + 1,
|
||||
);
|
||||
console.log('==')
|
||||
} else if (shouldVerticalMerge && !shouldHorizontalMerge) {
|
||||
// 只在垂直方向上同一列的两行合并
|
||||
worksheet.mergeCells(Number(rowHeader1.number), index + 1, Number(rowHeader2.number), index + 1);
|
||||
} else if (!shouldVerticalMerge && shouldHorizontalMerge) {
|
||||
// 只有水平方向同一行的多列合并
|
||||
worksheet.mergeCells(
|
||||
Number(rowHeader1.number),
|
||||
index + 1,
|
||||
Number(rowHeader1.number),
|
||||
nameRow1.lastIndexOf(name) + 1,
|
||||
);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
const cell = rowHeader1.getCell(index + 1);
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
44
src/api/errorCodeType.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export const errorCodeType = function (code: string): string {
|
||||
let errMessage = '未知错误'
|
||||
switch (code) {
|
||||
case 400:
|
||||
errMessage = '请求失败!请您稍后重试'
|
||||
break
|
||||
case 401:
|
||||
errMessage = '未授权,请重新登录'
|
||||
break
|
||||
case 403:
|
||||
errMessage = '当前账号无权限访问!'
|
||||
break
|
||||
case 404:
|
||||
errMessage = '你所访问的资源不存在!'
|
||||
break
|
||||
case 405:
|
||||
errMessage = '请求方式错误!请您稍后重试'
|
||||
break
|
||||
case 408:
|
||||
errMessage = '请求超时!请您稍后重试'
|
||||
break
|
||||
case 500:
|
||||
errMessage = '服务器端出错'
|
||||
break
|
||||
case 501:
|
||||
errMessage = '网络未实现'
|
||||
break
|
||||
case 502:
|
||||
errMessage = '网络错误'
|
||||
break
|
||||
case 503:
|
||||
errMessage = '服务不可用'
|
||||
break
|
||||
case 504:
|
||||
errMessage = '网络超时'
|
||||
break
|
||||
case 505:
|
||||
errMessage = 'http版本不支持该请求'
|
||||
break
|
||||
default:
|
||||
errMessage = `其他连接错误 --${code}`
|
||||
}
|
||||
return errMessage
|
||||
}
|
||||
62
src/api/request.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
// 创建axios实例 进行基本参数配置
|
||||
const service = axios.create({
|
||||
// 默认请求地址,根据环境的不同可在.env 文件中进行修改
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
// 设置接口访问超时时间
|
||||
timeout: 3000000, // request timeout,
|
||||
// 跨域时候允许携带凭证
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
// request interceptor 接口请求拦截
|
||||
service.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
/**
|
||||
* 用户登录之后获取服务端返回的token,后面每次请求都在请求头中带上token进行JWT校验
|
||||
* token 存储在本地储存中(storage)、vuex、pinia
|
||||
*/
|
||||
const userStore = useUserStore()
|
||||
const token: string = userStore.token
|
||||
// 自定义请求头
|
||||
if (token) {
|
||||
config.headers['Authorization'] = token
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
// 请求错误,这里可以用全局提示框进行提示
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
// response interceptor 接口响应拦截
|
||||
service.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
// 直接返回res,当然你也可以只返回res.data
|
||||
// 系统如果有自定义code也可以在这里处理
|
||||
return response
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
// /**
|
||||
// * @description 显示错误消息
|
||||
// * opt 传入参数
|
||||
// * err 错误信息
|
||||
// * type 消息类型
|
||||
// * duration 消息持续时间
|
||||
// */
|
||||
// function showErrMessage(opt, err, type: any = 'error', duration = 5000) {
|
||||
// ElMessage({
|
||||
// message: err.msg,
|
||||
// type: type,
|
||||
// duration: duration,
|
||||
// });
|
||||
// }
|
||||
|
||||
export default service
|
||||
9
src/api/user.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from './request'
|
||||
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: '/vue-element-perfect/user/login',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
BIN
src/assets/3.png
|
Before Width: | Height: | Size: 634 KiB |
BIN
src/assets/403_images/403.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
src/assets/403_images/403_cloud.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
BIN
src/assets/404_images/404_bg.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
src/assets/404_images/404_cloud.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
113
src/assets/iconfont/iconfont.css
Normal file
@@ -0,0 +1,113 @@
|
||||
@font-face {
|
||||
font-family: 'iconfont'; /* Project id 3641732 */
|
||||
src: url('./iconfont.woff2?t=1663216428515') format('woff2'), url('./iconfont.woff?t=1663216428515') format('woff'),
|
||||
url('./iconfont.ttf?t=1663216428515') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: 'iconfont' !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-yin:before {
|
||||
content: '\e6a0';
|
||||
}
|
||||
|
||||
.icon-dabaoyu:before {
|
||||
content: '\e6a7';
|
||||
}
|
||||
|
||||
.icon-shuaxin1:before {
|
||||
content: '\e627';
|
||||
}
|
||||
|
||||
.icon-shuaxin:before {
|
||||
content: '\e629';
|
||||
}
|
||||
|
||||
.icon-duoyun-2-copy:before {
|
||||
content: '\e68d';
|
||||
}
|
||||
|
||||
.icon-duoyun-1:before {
|
||||
content: '\e679';
|
||||
}
|
||||
|
||||
.icon-daxue:before {
|
||||
content: '\e67a';
|
||||
}
|
||||
|
||||
.icon-dayu:before {
|
||||
content: '\e67b';
|
||||
}
|
||||
|
||||
.icon-feng:before {
|
||||
content: '\e67c';
|
||||
}
|
||||
|
||||
.icon-duoyun-3:before {
|
||||
content: '\e67d';
|
||||
}
|
||||
|
||||
.icon-duoyun-2:before {
|
||||
content: '\e67e';
|
||||
}
|
||||
|
||||
.icon-leiyujiaojia:before {
|
||||
content: '\e67f';
|
||||
}
|
||||
|
||||
.icon-duoyun:before {
|
||||
content: '\e680';
|
||||
}
|
||||
|
||||
.icon-zhongyu:before {
|
||||
content: '\e681';
|
||||
}
|
||||
|
||||
.icon-wu:before {
|
||||
content: '\e682';
|
||||
}
|
||||
|
||||
.icon-xiaoyu:before {
|
||||
content: '\e683';
|
||||
}
|
||||
|
||||
.icon-xiaoxue:before {
|
||||
content: '\e684';
|
||||
}
|
||||
|
||||
.icon-shandian:before {
|
||||
content: '\e685';
|
||||
}
|
||||
|
||||
.icon-xue:before {
|
||||
content: '\e686';
|
||||
}
|
||||
|
||||
.icon-zhongxue:before {
|
||||
content: '\e687';
|
||||
}
|
||||
|
||||
.icon-yangchen:before {
|
||||
content: '\e688';
|
||||
}
|
||||
|
||||
.icon-yueliang:before {
|
||||
content: '\e689';
|
||||
}
|
||||
|
||||
.icon-yujiaxue:before {
|
||||
content: '\e68a';
|
||||
}
|
||||
|
||||
.icon-qing:before {
|
||||
content: '\e68b';
|
||||
}
|
||||
|
||||
.icon-mai:before {
|
||||
content: '\e68c';
|
||||
}
|
||||
66
src/assets/iconfont/iconfont.js
Normal file
184
src/assets/iconfont/iconfont.json
Normal file
@@ -0,0 +1,184 @@
|
||||
{
|
||||
"id": "3641732",
|
||||
"name": "weather",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "28557556",
|
||||
"name": "阴",
|
||||
"font_class": "yin",
|
||||
"unicode": "e6a0",
|
||||
"unicode_decimal": 59040
|
||||
},
|
||||
{
|
||||
"icon_id": "28557770",
|
||||
"name": "大暴雨",
|
||||
"font_class": "dabaoyu",
|
||||
"unicode": "e6a7",
|
||||
"unicode_decimal": 59047
|
||||
},
|
||||
{
|
||||
"icon_id": "7724796",
|
||||
"name": "刷新",
|
||||
"font_class": "shuaxin1",
|
||||
"unicode": "e627",
|
||||
"unicode_decimal": 58919
|
||||
},
|
||||
{
|
||||
"icon_id": "6999224",
|
||||
"name": "刷新",
|
||||
"font_class": "shuaxin",
|
||||
"unicode": "e629",
|
||||
"unicode_decimal": 58921
|
||||
},
|
||||
{
|
||||
"icon_id": "31772217",
|
||||
"name": "多云-2-copy",
|
||||
"font_class": "duoyun-2-copy",
|
||||
"unicode": "e68d",
|
||||
"unicode_decimal": 59021
|
||||
},
|
||||
{
|
||||
"icon_id": "29265274",
|
||||
"name": "多云-1",
|
||||
"font_class": "duoyun-1",
|
||||
"unicode": "e679",
|
||||
"unicode_decimal": 59001
|
||||
},
|
||||
{
|
||||
"icon_id": "29265275",
|
||||
"name": "大雪",
|
||||
"font_class": "daxue",
|
||||
"unicode": "e67a",
|
||||
"unicode_decimal": 59002
|
||||
},
|
||||
{
|
||||
"icon_id": "29265276",
|
||||
"name": "大雨",
|
||||
"font_class": "dayu",
|
||||
"unicode": "e67b",
|
||||
"unicode_decimal": 59003
|
||||
},
|
||||
{
|
||||
"icon_id": "29265277",
|
||||
"name": "风",
|
||||
"font_class": "feng",
|
||||
"unicode": "e67c",
|
||||
"unicode_decimal": 59004
|
||||
},
|
||||
{
|
||||
"icon_id": "29265278",
|
||||
"name": "多云-3",
|
||||
"font_class": "duoyun-3",
|
||||
"unicode": "e67d",
|
||||
"unicode_decimal": 59005
|
||||
},
|
||||
{
|
||||
"icon_id": "29265279",
|
||||
"name": "多云-2",
|
||||
"font_class": "duoyun-2",
|
||||
"unicode": "e67e",
|
||||
"unicode_decimal": 59006
|
||||
},
|
||||
{
|
||||
"icon_id": "29265280",
|
||||
"name": "雷雨交加",
|
||||
"font_class": "leiyujiaojia",
|
||||
"unicode": "e67f",
|
||||
"unicode_decimal": 59007
|
||||
},
|
||||
{
|
||||
"icon_id": "29265281",
|
||||
"name": "多云",
|
||||
"font_class": "duoyun",
|
||||
"unicode": "e680",
|
||||
"unicode_decimal": 59008
|
||||
},
|
||||
{
|
||||
"icon_id": "29265282",
|
||||
"name": "中雨",
|
||||
"font_class": "zhongyu",
|
||||
"unicode": "e681",
|
||||
"unicode_decimal": 59009
|
||||
},
|
||||
{
|
||||
"icon_id": "29265283",
|
||||
"name": "雾",
|
||||
"font_class": "wu",
|
||||
"unicode": "e682",
|
||||
"unicode_decimal": 59010
|
||||
},
|
||||
{
|
||||
"icon_id": "29265284",
|
||||
"name": "小雨",
|
||||
"font_class": "xiaoyu",
|
||||
"unicode": "e683",
|
||||
"unicode_decimal": 59011
|
||||
},
|
||||
{
|
||||
"icon_id": "29265285",
|
||||
"name": "小雪",
|
||||
"font_class": "xiaoxue",
|
||||
"unicode": "e684",
|
||||
"unicode_decimal": 59012
|
||||
},
|
||||
{
|
||||
"icon_id": "29265286",
|
||||
"name": "闪电",
|
||||
"font_class": "shandian",
|
||||
"unicode": "e685",
|
||||
"unicode_decimal": 59013
|
||||
},
|
||||
{
|
||||
"icon_id": "29265287",
|
||||
"name": "雪",
|
||||
"font_class": "xue",
|
||||
"unicode": "e686",
|
||||
"unicode_decimal": 59014
|
||||
},
|
||||
{
|
||||
"icon_id": "29265288",
|
||||
"name": "中雪",
|
||||
"font_class": "zhongxue",
|
||||
"unicode": "e687",
|
||||
"unicode_decimal": 59015
|
||||
},
|
||||
{
|
||||
"icon_id": "29265289",
|
||||
"name": "扬尘",
|
||||
"font_class": "yangchen",
|
||||
"unicode": "e688",
|
||||
"unicode_decimal": 59016
|
||||
},
|
||||
{
|
||||
"icon_id": "29265290",
|
||||
"name": "月亮",
|
||||
"font_class": "yueliang",
|
||||
"unicode": "e689",
|
||||
"unicode_decimal": 59017
|
||||
},
|
||||
{
|
||||
"icon_id": "29265291",
|
||||
"name": "雨夹雪",
|
||||
"font_class": "yujiaxue",
|
||||
"unicode": "e68a",
|
||||
"unicode_decimal": 59018
|
||||
},
|
||||
{
|
||||
"icon_id": "29265292",
|
||||
"name": "晴",
|
||||
"font_class": "qing",
|
||||
"unicode": "e68b",
|
||||
"unicode_decimal": 59019
|
||||
},
|
||||
{
|
||||
"icon_id": "29265293",
|
||||
"name": "霾",
|
||||
"font_class": "mai",
|
||||
"unicode": "e68c",
|
||||
"unicode_decimal": 59020
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
src/assets/iconfont/iconfont.ttf
Normal file
BIN
src/assets/iconfont/iconfont.woff
Normal file
BIN
src/assets/iconfont/iconfont.woff2
Normal file
BIN
src/assets/image/allow.png
Normal file
|
After Width: | Height: | Size: 533 B |
BIN
src/assets/image/avatar.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/image/center-inner1.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/image/charts/1-1-bg.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/assets/image/circle-bg.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/image/cro-avatar.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 53 KiB |
BIN
src/assets/image/login/qrcode-icon.png
Normal file
|
After Width: | Height: | Size: 653 B |
BIN
src/assets/image/login/side-logo.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
BIN
src/assets/image/we.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
85
src/components/AvatarCropper/index.vue
Normal 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>
|
||||
@@ -1,155 +0,0 @@
|
||||
<template>
|
||||
<div :id="id" :class="className" :style="{height:height,width:width}" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
import resize from './mixins/resize'
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.getElementById(this.id))
|
||||
|
||||
const xAxisData = []
|
||||
const data = []
|
||||
const data2 = []
|
||||
for (let i = 0; i < 50; i++) {
|
||||
xAxisData.push(i)
|
||||
data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
|
||||
data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
|
||||
}
|
||||
this.chart.setOption({
|
||||
backgroundColor: '#08263a',
|
||||
grid: {
|
||||
left: '5%',
|
||||
right: '5%'
|
||||
},
|
||||
xAxis: [{
|
||||
show: false,
|
||||
data: xAxisData
|
||||
}, {
|
||||
show: false,
|
||||
data: xAxisData
|
||||
}],
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 50,
|
||||
dimension: 0,
|
||||
inRange: {
|
||||
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#4a657a'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#08263f'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'back',
|
||||
type: 'bar',
|
||||
data: data2,
|
||||
z: 1,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0.4,
|
||||
barBorderRadius: 5,
|
||||
shadowBlur: 3,
|
||||
shadowColor: '#111'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'Simulate Shadow',
|
||||
type: 'line',
|
||||
data,
|
||||
z: 2,
|
||||
showSymbol: false,
|
||||
animationDelay: 0,
|
||||
animationEasing: 'linear',
|
||||
animationDuration: 1200,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: 'transparent'
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: '#08263a',
|
||||
shadowBlur: 50,
|
||||
shadowColor: '#000'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'front',
|
||||
type: 'bar',
|
||||
data,
|
||||
xAxisIndex: 1,
|
||||
z: 3,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderRadius: 5
|
||||
}
|
||||
}
|
||||
}],
|
||||
animationEasing: 'elasticOut',
|
||||
animationEasingUpdate: 'elasticOut',
|
||||
animationDelay(idx) {
|
||||
return idx * 20
|
||||
},
|
||||
animationDelayUpdate(idx) {
|
||||
return idx * 20
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,230 +0,0 @@
|
||||
<template>
|
||||
<div :id="id" :class="className" :style="{height:height,width:width}" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from 'echarts'
|
||||
import {computed, nextTick, onMounted, ref, watch} from "vue";
|
||||
import {EChartsType} from "echarts/core";
|
||||
import {useStore} from "vuex";
|
||||
// 在setup中获取store
|
||||
const store = useStore()
|
||||
let props = defineProps({
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
})
|
||||
const isCollapse = computed(()=>{
|
||||
return store.state.app.isCollapse
|
||||
})
|
||||
|
||||
let chart:EChartsType;
|
||||
|
||||
const initChart =()=> {
|
||||
let chart = echarts.init(document.getElementById(props.id))
|
||||
chart.setOption({
|
||||
backgroundColor: '#394056',
|
||||
title: {
|
||||
top: 20,
|
||||
text: 'Requests',
|
||||
textStyle: {
|
||||
fontWeight: 'normal',
|
||||
fontSize: 16,
|
||||
color: '#F1F1F3'
|
||||
},
|
||||
left: '1%'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
top: 20,
|
||||
icon: 'rect',
|
||||
itemWidth: 14,
|
||||
itemHeight: 5,
|
||||
itemGap: 13,
|
||||
data: ['CMCC', 'CTCC', 'CUCC'],
|
||||
right: '4%',
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#F1F1F3'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 100,
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
bottom: '2%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
},
|
||||
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
name: '(%)',
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
margin: 10,
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
}
|
||||
}],
|
||||
series: [{
|
||||
name: 'CMCC',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(137, 189, 27, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(137, 189, 27, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(137,189,27)',
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
|
||||
}
|
||||
},
|
||||
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
|
||||
}, {
|
||||
name: 'CTCC',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(0, 136, 212, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(0, 136, 212, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(0,136,212)',
|
||||
borderColor: 'rgba(0,136,212,0.2)',
|
||||
borderWidth: 12
|
||||
|
||||
}
|
||||
},
|
||||
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
|
||||
}, {
|
||||
name: 'CUCC',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(219, 50, 51, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(219, 50, 51, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(219,50,51)',
|
||||
borderColor: 'rgba(219,50,51,0.2)',
|
||||
borderWidth: 12
|
||||
}
|
||||
},
|
||||
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
|
||||
}]
|
||||
})
|
||||
return chart
|
||||
}
|
||||
|
||||
watch(isCollapse,()=>{
|
||||
setTimeout(()=>{
|
||||
chart&&chart.resize()
|
||||
},300)
|
||||
})
|
||||
|
||||
|
||||
onMounted(()=>{
|
||||
chart = initChart()
|
||||
window.addEventListener('resize',function (){
|
||||
chart&&chart.resize()
|
||||
})
|
||||
|
||||
})
|
||||
</script>
|
||||
@@ -1,271 +0,0 @@
|
||||
<template>
|
||||
<div :id="id" :class="className" :style="{height:height,width:width}" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
import resize from './mixins/resize'
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.getElementById(this.id))
|
||||
const xData = (function() {
|
||||
const data = []
|
||||
for (let i = 1; i < 13; i++) {
|
||||
data.push(i + 'month')
|
||||
}
|
||||
return data
|
||||
}())
|
||||
this.chart.setOption({
|
||||
backgroundColor: '#344b58',
|
||||
title: {
|
||||
text: 'statistics',
|
||||
x: '20',
|
||||
top: '20',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: '22'
|
||||
},
|
||||
subtextStyle: {
|
||||
color: '#90979c',
|
||||
fontSize: '16'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
borderWidth: 0,
|
||||
top: 150,
|
||||
bottom: 95,
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
x: '5%',
|
||||
top: '10%',
|
||||
textStyle: {
|
||||
color: '#90979c'
|
||||
},
|
||||
data: ['female', 'male', 'average']
|
||||
},
|
||||
calculable: true,
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#90979c'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitArea: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
|
||||
},
|
||||
data: xData
|
||||
}],
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#90979c'
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
},
|
||||
splitArea: {
|
||||
show: false
|
||||
}
|
||||
}],
|
||||
dataZoom: [{
|
||||
show: true,
|
||||
height: 30,
|
||||
xAxisIndex: [
|
||||
0
|
||||
],
|
||||
bottom: 30,
|
||||
start: 10,
|
||||
end: 80,
|
||||
handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
|
||||
handleSize: '110%',
|
||||
handleStyle: {
|
||||
color: '#d3dee5'
|
||||
|
||||
},
|
||||
textStyle: {
|
||||
color: '#fff' },
|
||||
borderColor: '#90979c'
|
||||
|
||||
}, {
|
||||
type: 'inside',
|
||||
show: true,
|
||||
height: 15,
|
||||
start: 1,
|
||||
end: 35
|
||||
}],
|
||||
series: [{
|
||||
name: 'female',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
barMaxWidth: 35,
|
||||
barGap: '10%',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgba(255,144,128,1)',
|
||||
label: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
},
|
||||
position: 'insideTop',
|
||||
formatter(p) {
|
||||
return p.value > 0 ? p.value : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
709,
|
||||
1917,
|
||||
2455,
|
||||
2610,
|
||||
1719,
|
||||
1433,
|
||||
1544,
|
||||
3285,
|
||||
5208,
|
||||
3372,
|
||||
2484,
|
||||
4078
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
name: 'male',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgba(0,191,183,1)',
|
||||
barBorderRadius: 0,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter(p) {
|
||||
return p.value > 0 ? p.value : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
327,
|
||||
1776,
|
||||
507,
|
||||
1200,
|
||||
800,
|
||||
482,
|
||||
204,
|
||||
1390,
|
||||
1001,
|
||||
951,
|
||||
381,
|
||||
220
|
||||
]
|
||||
}, {
|
||||
name: 'average',
|
||||
type: 'line',
|
||||
stack: 'total',
|
||||
symbolSize: 10,
|
||||
symbol: 'circle',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgba(252,230,48,1)',
|
||||
barBorderRadius: 0,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter(p) {
|
||||
return p.value > 0 ? p.value : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
1036,
|
||||
3693,
|
||||
2962,
|
||||
3810,
|
||||
2519,
|
||||
1915,
|
||||
1748,
|
||||
4675,
|
||||
6209,
|
||||
4323,
|
||||
2865,
|
||||
4298
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
120
src/components/CodeMirror/index.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<textarea ref="codeEditor" placeholder="请输入..."></textarea>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
|
||||
// codemirror基础资源引入
|
||||
import _CodeMirror from 'codemirror'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
// language
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
|
||||
// theme css
|
||||
import 'codemirror/theme/monokai.css'
|
||||
// 折叠资源引入:开始
|
||||
import 'codemirror/addon/fold/foldgutter.css'
|
||||
import 'codemirror/addon/fold/foldcode.js'
|
||||
import 'codemirror/addon/fold/brace-fold.js'
|
||||
import 'codemirror/addon/fold/comment-fold.js'
|
||||
import 'codemirror/addon/fold/indent-fold.js'
|
||||
import 'codemirror/addon/fold/foldgutter.js'
|
||||
// 折叠资源引入:结束
|
||||
|
||||
// 搜索资源引入:开始
|
||||
import 'codemirror/addon/scroll/annotatescrollbar.js'
|
||||
import 'codemirror/addon/search/matchesonscrollbar.js'
|
||||
import 'codemirror/addon/search/match-highlighter.js'
|
||||
import 'codemirror/addon/search/jump-to-line.js'
|
||||
|
||||
import 'codemirror/addon/dialog/dialog.js'
|
||||
import 'codemirror/addon/dialog/dialog.css'
|
||||
import 'codemirror/addon/search/searchcursor.js'
|
||||
import 'codemirror/addon/search/search.js'
|
||||
// 搜索资源引入:结束
|
||||
|
||||
// 启用placeholder
|
||||
import 'codemirror/addon/display/placeholder.js'
|
||||
|
||||
import 'codemirror/addon/selection/active-line.js' //光标行背景高亮,配置里面也需要styleActiveLine设置为true
|
||||
|
||||
const CodeMirror = window.CodeMirror || _CodeMirror
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: String,
|
||||
defaultValue: String,
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, context) {
|
||||
const { modelValue, defaultValue, readOnly } = toRefs(props)
|
||||
const codeEditor = ref()
|
||||
let editor
|
||||
watch(modelValue, () => {
|
||||
if (null != editor && modelValue.value && modelValue.value !== editor.getValue()) {
|
||||
// 触发v-model的双向绑定
|
||||
editor.setValue(modelValue.value)
|
||||
}
|
||||
})
|
||||
watch(readOnly, () => {
|
||||
if (null != editor) {
|
||||
editor.setOption('readOnly', readOnly.value)
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
editor = CodeMirror.fromTextArea(codeEditor.value, {
|
||||
value: modelValue.value,
|
||||
// mime: "text/javascript",
|
||||
mode: 'application/json',
|
||||
indentWithTabs: false, // 在缩进时,是否需要把 n*tab宽度个空格替换成n个tab字符,默认为false
|
||||
smartIndent: true, // 自动缩进,设置是否根据上下文自动缩进(和上一行相同的缩进量)。默认为true
|
||||
lineNumbers: true, // 是否在编辑器左侧显示行号
|
||||
matchBrackets: true, // 括号匹配
|
||||
readOnly: readOnly.value,
|
||||
// 启用代码折叠相关功能:开始
|
||||
foldGutter: true,
|
||||
lineWrapping: true, //是否自动换行
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
|
||||
// 启用代码折叠相关功能:结束
|
||||
styleActiveLine: false, // 光标行高亮
|
||||
})
|
||||
// 监听编辑器的change事件
|
||||
editor.on('change', () => {
|
||||
// 触发v-model的双向绑定
|
||||
context.emit('update:modelValue', editor.getValue())
|
||||
})
|
||||
if (defaultValue.value) {
|
||||
editor.setValue(defaultValue.value)
|
||||
}
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
if (null !== editor) {
|
||||
editor.toTextArea()
|
||||
editor = null
|
||||
}
|
||||
})
|
||||
return { codeEditor }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss">
|
||||
.CodeMirror-wrap {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
min-height: 100%;
|
||||
white-space: nowrap;
|
||||
background-color: transparent;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
</style>
|
||||
5
src/components/CountTo/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import CountTo from './index.vue'
|
||||
export default CountTo
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
window.Vue.component('count-to', CountTo)
|
||||
}
|
||||
191
src/components/CountTo/index.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<span>
|
||||
{{ displayValue }}
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame.js'
|
||||
export default {
|
||||
props: {
|
||||
startVal: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
endVal: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 2017,
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 3000,
|
||||
},
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
decimals: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
validator(value) {
|
||||
return value >= 0
|
||||
},
|
||||
},
|
||||
decimal: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '.',
|
||||
},
|
||||
separator: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ',',
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
easingFn: {
|
||||
type: Function,
|
||||
default(t, b, c, d) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
printVal: null,
|
||||
paused: false,
|
||||
localDuration: this.duration,
|
||||
startTime: null,
|
||||
timestamp: null,
|
||||
remaining: null,
|
||||
rAF: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
if (this.autoplay) {
|
||||
this.start()
|
||||
}
|
||||
},
|
||||
endVal() {
|
||||
if (this.autoplay) {
|
||||
this.start()
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.autoplay) {
|
||||
this.start()
|
||||
}
|
||||
this.$emit('mountedCallback')
|
||||
},
|
||||
unmounted() {
|
||||
cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
this.localStartVal = this.startVal
|
||||
this.startTime = null
|
||||
this.localDuration = this.duration
|
||||
this.paused = false
|
||||
this.rAF = requestAnimationFrame(this.count)
|
||||
},
|
||||
pauseResume() {
|
||||
if (this.paused) {
|
||||
this.resume()
|
||||
this.paused = false
|
||||
} else {
|
||||
this.pause()
|
||||
this.paused = true
|
||||
}
|
||||
},
|
||||
pause() {
|
||||
cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
resume() {
|
||||
this.startTime = null
|
||||
this.localDuration = +this.remaining
|
||||
this.localStartVal = +this.printVal
|
||||
requestAnimationFrame(this.count)
|
||||
},
|
||||
reset() {
|
||||
this.startTime = null
|
||||
cancelAnimationFrame(this.rAF)
|
||||
this.displayValue = this.formatNumber(this.startVal)
|
||||
},
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp
|
||||
this.timestamp = timestamp
|
||||
const progress = timestamp - this.startTime
|
||||
this.remaining = this.localDuration - progress
|
||||
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
|
||||
} else {
|
||||
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
|
||||
} else {
|
||||
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal
|
||||
} else {
|
||||
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal
|
||||
}
|
||||
|
||||
this.displayValue = this.formatNumber(this.printVal)
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = requestAnimationFrame(this.count)
|
||||
} else {
|
||||
this.$emit('callback')
|
||||
}
|
||||
},
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val))
|
||||
},
|
||||
formatNumber(num) {
|
||||
num = num.toFixed(this.decimals)
|
||||
num += ''
|
||||
const x = num.split('.')
|
||||
let x1 = x[0]
|
||||
const x2 = x.length > 1 ? this.decimal + x[1] : ''
|
||||
const rgx = /(\d+)(\d{3})/
|
||||
if (this.separator && !this.isNumber(this.separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, '$1' + this.separator + '$2')
|
||||
}
|
||||
}
|
||||
return this.prefix + x1 + x2 + this.suffix
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -6,19 +6,21 @@ let cancelAnimationFrame
|
||||
|
||||
const isServer = typeof window === 'undefined'
|
||||
if (isServer) {
|
||||
requestAnimationFrame = function() {
|
||||
requestAnimationFrame = function () {
|
||||
return
|
||||
}
|
||||
cancelAnimationFrame = function() {
|
||||
cancelAnimationFrame = function () {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
requestAnimationFrame = window.requestAnimationFrame
|
||||
cancelAnimationFrame = window.cancelAnimationFrame
|
||||
let prefix
|
||||
// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
|
||||
// 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
|
||||
for (let i = 0; i < prefixes.length; i++) {
|
||||
if (requestAnimationFrame && cancelAnimationFrame) { break }
|
||||
if (requestAnimationFrame && cancelAnimationFrame) {
|
||||
break
|
||||
}
|
||||
prefix = prefixes[i]
|
||||
requestAnimationFrame = requestAnimationFrame || window[prefix + 'RequestAnimationFrame']
|
||||
cancelAnimationFrame = cancelAnimationFrame || window[prefix + 'CancelAnimationFrame'] || window[prefix + 'CancelRequestAnimationFrame']
|
||||
@@ -26,7 +28,7 @@ if (isServer) {
|
||||
|
||||
// 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout
|
||||
if (!requestAnimationFrame || !cancelAnimationFrame) {
|
||||
requestAnimationFrame = function(callback) {
|
||||
requestAnimationFrame = function (callback) {
|
||||
const currTime = new Date().getTime()
|
||||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||||
const timeToCall = Math.max(0, 16 - (currTime - lastTime))
|
||||
@@ -37,7 +39,7 @@ if (isServer) {
|
||||
return id
|
||||
}
|
||||
|
||||
cancelAnimationFrame = function(id) {
|
||||
cancelAnimationFrame = function (id) {
|
||||
window.clearTimeout(id)
|
||||
}
|
||||
}
|
||||
269
src/components/DataScreen/Multiline/index.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div ref="chartsRef" class="echarts" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from 'echarts'
|
||||
import { EChartsType } from 'echarts/core'
|
||||
import { onMounted, ref } from 'vue'
|
||||
const chartsRef = ref<HTMLElement | null>()
|
||||
|
||||
let colorList = ['#46ea91', '#2ba0ff', '#ed593b', '#7357ff', '#f2d750']
|
||||
const options = {
|
||||
legend: {
|
||||
icon: 'circle',
|
||||
top: '5%',
|
||||
right: '5%',
|
||||
itemWidth: 6,
|
||||
itemGap: 5,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
padding: [3, 0, 0, 0],
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
grid: {
|
||||
top: '14%',
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: ['1', '2', '3', '4', '5', '6', '7', '8'],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#33BBFF',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
textStyle: {
|
||||
color: '#5FBBEB',
|
||||
},
|
||||
// 默认x轴字体大小
|
||||
fontSize: 12,
|
||||
// margin:文字到x轴的距离
|
||||
margin: 10,
|
||||
},
|
||||
axisPointer: {
|
||||
label: {
|
||||
// padding: [11, 5, 7],
|
||||
padding: [0, 0, 0, 0],
|
||||
// 这里的margin和axisLabel的margin要一致!
|
||||
margin: 10,
|
||||
// 移入时的字体大小
|
||||
fontSize: 12,
|
||||
backgroundColor: 'rgba(0,0,0,0)',
|
||||
},
|
||||
},
|
||||
boundaryGap: false,
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
name: '单位/件',
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#05D5FF',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#5FBBEB',
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '咨询',
|
||||
type: 'line',
|
||||
data: [100, 20, 30, 102, 15, 30, 20, 18],
|
||||
symbolSize: 1,
|
||||
symbol: 'circle',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#90ffc6',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#46ea91',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(144, 255, 198, .3)',
|
||||
shadowBlur: 5,
|
||||
shadowOffsetY: 5,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[0],
|
||||
borderColor: colorList[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '求助',
|
||||
type: 'line',
|
||||
data: [20, 12, 11, 14, 25, 16, 10, 20],
|
||||
symbolSize: 1,
|
||||
symbol: 'circle',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#67bcfc',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#2ba0ff',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(105, 188, 252,.3)',
|
||||
shadowBlur: 5,
|
||||
shadowOffsetY: 5,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[1],
|
||||
borderColor: colorList[1],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '无效',
|
||||
type: 'line',
|
||||
data: [150, 120, 170, 140, 100, 160, 110, 110],
|
||||
symbolSize: 1,
|
||||
symbol: 'circle',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#fc937e ',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#ed593b',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgb(252, 147, 126,.3)',
|
||||
shadowBlur: 2,
|
||||
shadowOffsetY: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[2],
|
||||
borderColor: colorList[2],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '投诉举报',
|
||||
type: 'line',
|
||||
data: [200, 80, 100, 30, 60, 50, 110, 20],
|
||||
symbolSize: 1,
|
||||
symbol: 'circle',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#a390ff',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#7357ff',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(115, 87, 255, .1)',
|
||||
shadowBlur: 5,
|
||||
shadowOffsetY: 5,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[3],
|
||||
borderColor: colorList[3],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '建议',
|
||||
type: 'line',
|
||||
data: [20, 80, 150, 30, 60, 50, 50, 20],
|
||||
symbolSize: 1,
|
||||
symbol: 'circle',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#ffeb86',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#f2d750',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(255, 235, 134, .5)',
|
||||
shadowBlur: 5,
|
||||
shadowOffsetY: 5,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[4],
|
||||
borderColor: colorList[4],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let chart: EChartsType
|
||||
const initChart = () => {
|
||||
const chart = echarts.init(chartsRef.value)
|
||||
chart.setOption(options)
|
||||
return chart
|
||||
}
|
||||
onMounted(() => {
|
||||
chart = initChart()
|
||||
window.addEventListener('resize', function () {
|
||||
chart && chart.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.echarts {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
117
src/components/DataScreen/barEcharts/index.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div ref="chartsRef" class="echarts" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import BarCharts from './components/bar.vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { EChartsType } from 'echarts/core'
|
||||
import { onMounted, ref, reactive } from 'vue'
|
||||
const chartsRef = ref<HTMLElement | null>()
|
||||
const data = [154, 230, 224, 218, 135, 147, 260]
|
||||
const color = ['#fa796f', '#54c1fb', '#ca6cd4', '#59dcc1', '#09a4ea', '#e98f4d', '#ea8e49']
|
||||
const dataOptions = []
|
||||
|
||||
data.forEach((item, index) => {
|
||||
let obj = {
|
||||
value: data[index],
|
||||
itemStyle: {
|
||||
color: color[index],
|
||||
},
|
||||
}
|
||||
dataOptions.push(obj)
|
||||
})
|
||||
|
||||
const options = {
|
||||
color,
|
||||
grid: {
|
||||
top: '10%',
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
borderWidth: 0,
|
||||
borderColor: 'rgba(0,0,0,0.7)',
|
||||
formatter: (name, val) => {
|
||||
const tipHtml = `
|
||||
<div class="m-info" style=" opacity: 0.95;font-size: 12px; color: white;" >
|
||||
<div class="title" ></div>
|
||||
<div class="title" >完成占比${name[0].value}</div>
|
||||
</div>`
|
||||
return tipHtml
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
// 设置坐标轴的 文字样式
|
||||
axisLabel: {
|
||||
color: '#bbdaff',
|
||||
margin: 20, // 刻度标签与轴线之间的距离。
|
||||
},
|
||||
axisTick: {
|
||||
// 取消坐标轴刻度线
|
||||
show: false,
|
||||
},
|
||||
// 坐标轴轴线相关设置。
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#2d5baf',
|
||||
},
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
// 坐标轴轴线相关设置。
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#2d5baf',
|
||||
},
|
||||
},
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
axisLabel: {
|
||||
// 设置坐标轴的 文字样式
|
||||
color: '#bbdaff',
|
||||
margin: 20, // 刻度标签与轴线之间的距离。
|
||||
},
|
||||
axisTick: {
|
||||
// 取消坐标轴刻度线
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: dataOptions,
|
||||
type: 'bar',
|
||||
barMaxWidth: 18,
|
||||
markLine: {
|
||||
silent: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let chart: EChartsType
|
||||
const initChart = () => {
|
||||
const chart = echarts.init(chartsRef.value)
|
||||
chart.setOption(options)
|
||||
return chart
|
||||
}
|
||||
onMounted(() => {
|
||||
chart = initChart()
|
||||
window.addEventListener('resize', function () {
|
||||
chart && chart.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.echarts {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
111
src/components/DataScreen/lineEcharts/index.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div ref="chartsRef" class="echarts" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import BarCharts from './components/bar.vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { EChartsType } from 'echarts/core'
|
||||
import { onMounted, ref, reactive } from 'vue'
|
||||
const chartsRef = ref<HTMLElement | null>()
|
||||
|
||||
const options = {
|
||||
grid: {
|
||||
top: '10%',
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%',
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
borderWidth: 0,
|
||||
borderColor: 'rgba(0,0,0,0.7)',
|
||||
formatter: (name, val) => {
|
||||
const tipHtml = `
|
||||
<div class="m-info" style=" opacity: 0.95;font-size: 12px; color: white;" >
|
||||
<div class="title" ></div>
|
||||
<div class="title" >完成占比${name[0].data}</div>
|
||||
</div>`
|
||||
return tipHtml
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
// 设置坐标轴的 文字样式
|
||||
axisLabel: {
|
||||
color: '#bbdaff',
|
||||
margin: 20, // 刻度标签与轴线之间的距离。
|
||||
},
|
||||
// 坐标轴轴线相关设置。
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#2d5baf',
|
||||
},
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
// 坐标轴轴线相关设置。
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#2d5baf',
|
||||
},
|
||||
},
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
axisLabel: {
|
||||
// 设置坐标轴的 文字样式
|
||||
color: '#bbdaff',
|
||||
margin: 20, // 刻度标签与轴线之间的距离。
|
||||
},
|
||||
boundaryGap: false, // 设置坐标轴两边的留白 ,从刻度原点开始,
|
||||
axisTick: {
|
||||
// 取消坐标轴刻度线
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [154, 230, 224, 218, 135, 147, 260],
|
||||
type: 'line',
|
||||
// smooth:false, //关键点,为true是不支持虚线的,实线就用true
|
||||
symbolSize: 12, // 拐点圆的大小
|
||||
symbol: 'circle',
|
||||
markLine: {
|
||||
silent: true,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#920783', // 设置 symbol的颜色
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#920783',
|
||||
type: 'solid', // 'dotted'虚线 'solid'实线
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let chart: EChartsType
|
||||
const initChart = () => {
|
||||
const chart = echarts.init(chartsRef.value)
|
||||
chart.setOption(options)
|
||||
return chart
|
||||
}
|
||||
onMounted(() => {
|
||||
chart = initChart()
|
||||
window.addEventListener('resize', function () {
|
||||
chart && chart.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.echarts {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
38
src/components/DataScreen/migrationEcharts/data.js
Normal file
@@ -0,0 +1,38 @@
|
||||
export const cityIconData = [
|
||||
{
|
||||
adcode: 650000,
|
||||
name: '新疆维吾尔自治区',
|
||||
value: [88.999903, 43.607078],
|
||||
},
|
||||
{
|
||||
// adcode: 150000,
|
||||
name: '内蒙古自治区',
|
||||
value: [119.24787, 42.205741],
|
||||
},
|
||||
,
|
||||
{
|
||||
// adcode: 150000,
|
||||
name: '内蒙古自治区',
|
||||
value: [110.627161, 41.16628],
|
||||
},
|
||||
{
|
||||
// adcode: 540000,
|
||||
name: '西藏自治区',
|
||||
value: [89.483714, 30.251951],
|
||||
},
|
||||
{
|
||||
// adcode: 630000,
|
||||
name: '青海省',
|
||||
value: [102.064693, 37.084008],
|
||||
},
|
||||
{
|
||||
// adcode: 530000,
|
||||
name: '云南省',
|
||||
value: [102.187665, 25.083688],
|
||||
},
|
||||
{
|
||||
// adcode: 610000,
|
||||
name: '陕西省',
|
||||
value: [109.20663, 35.018625],
|
||||
},
|
||||
]
|
||||
332
src/components/DataScreen/migrationEcharts/index.vue
Normal file
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div :id="id" :class="className" :style="{ height: height, width: width }" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { geoJson } from './map.js'
|
||||
import * as echarts from 'echarts'
|
||||
import { EChartsType } from 'echarts/core'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { cityIconData } from './data.js'
|
||||
import logo from '@/assets/image/logo.png'
|
||||
const props = defineProps({
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart',
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: 'chart',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px',
|
||||
},
|
||||
})
|
||||
|
||||
var geoGpsMap = [109.1162, 34.2004]
|
||||
var geoCoordMap = {
|
||||
江苏: [118.8062, 31.9208],
|
||||
内蒙古: [110.3467, 41.4899],
|
||||
辽宁: [123.1238, 42.1216],
|
||||
陕西: [109.1162, 34.2004],
|
||||
甘肃: [103.5901, 36.3043],
|
||||
青海: [101.4038, 36.8207],
|
||||
新疆: [87.9236, 43.5883],
|
||||
|
||||
河南: [113.4668, 34.6234],
|
||||
西藏: [91.091762, 30.037072],
|
||||
浙江: [119.5313, 29.8773],
|
||||
福建: [119.4543, 25.9222],
|
||||
湖南: [113.0823, 28.2568],
|
||||
四川: [113.0823, 28.2568],
|
||||
云南: [102.9199, 25.4663],
|
||||
广东: [113.12244, 23.009505],
|
||||
海南: [110.3893, 19.8516],
|
||||
}
|
||||
|
||||
var value = {
|
||||
江苏: 10000,
|
||||
内蒙古: 10000,
|
||||
辽宁: 10000,
|
||||
陕西: 10000,
|
||||
福建: 10000,
|
||||
甘肃: 10000,
|
||||
青海: 10000,
|
||||
新疆: 10000,
|
||||
湖北: 10000,
|
||||
浙江: 10000,
|
||||
河南: 10000,
|
||||
湖南: 10000,
|
||||
云南: 10000,
|
||||
广东: 10000,
|
||||
海南: 10000,
|
||||
西藏: 10000,
|
||||
}
|
||||
var colors = '#f9b207'
|
||||
|
||||
var year = ['长春', '长春', '青岛', '青岛', '成都', '成都']
|
||||
var mapData = []
|
||||
|
||||
/* 柱子Y名称 */
|
||||
var categoryData = []
|
||||
var barData = []
|
||||
|
||||
for (var key in geoCoordMap) {
|
||||
mapData.push({
|
||||
year: '陕西',
|
||||
name: key,
|
||||
value: value[key] / 100,
|
||||
value1: value[key] / 100,
|
||||
})
|
||||
}
|
||||
|
||||
mapData.sort(function sortNumber(a, b) {
|
||||
return a.value - b.value
|
||||
})
|
||||
for (var j = 0; j < mapData.length; j++) {
|
||||
barData.push(mapData[j].value1)
|
||||
categoryData.push(mapData[j].name)
|
||||
}
|
||||
|
||||
echarts.registerMap('china', geoJson)
|
||||
var convertData = function (data) {
|
||||
var res = []
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var geoCoord = geoCoordMap[data[i].name]
|
||||
if (geoCoord) {
|
||||
res.push({
|
||||
name: data[i].name,
|
||||
value: geoCoord.concat(data[i].value),
|
||||
})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
var convertToLineData = function (data, gps) {
|
||||
var res = []
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var dataItem = data[i]
|
||||
var toCoord = geoCoordMap[dataItem.name]
|
||||
// debugger;
|
||||
var fromCoord = gps // 郑州
|
||||
// var toCoord = geoGps[Math.random()*3];
|
||||
if (fromCoord && toCoord) {
|
||||
res.push([
|
||||
{
|
||||
coord: fromCoord,
|
||||
value: dataItem.value,
|
||||
},
|
||||
{
|
||||
coord: toCoord,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const options = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter(val) {
|
||||
// console.log('val==========',val)
|
||||
},
|
||||
},
|
||||
// backgroundColor: '#001540',// 设置背景色
|
||||
geo: {
|
||||
show: true,
|
||||
map: 'china',
|
||||
roam: true,
|
||||
zoom: 1,
|
||||
center: [101.4038, 36.8207],
|
||||
label: {
|
||||
emphasis: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
borderColor: 'rgba(147, 235, 248, 1)',
|
||||
borderWidth: 1,
|
||||
areaColor: {
|
||||
type: 'radial',
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
r: 0.8,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(147, 235, 248, 0)', // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(147, 235, 248, .2)', // 100% 处的颜色
|
||||
},
|
||||
],
|
||||
globalCoord: false, // 缺省为 false
|
||||
},
|
||||
shadowColor: 'rgba(128, 217, 248, 1)',
|
||||
// shadowColor: 'rgba(255, 255, 255, 1)',
|
||||
shadowOffsetX: -2,
|
||||
shadowOffsetY: 2,
|
||||
shadowBlur: 10,
|
||||
},
|
||||
emphasis: {
|
||||
areaColor: '#389BB7',
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
// 地图?
|
||||
{
|
||||
type: 'map',
|
||||
map: 'china',
|
||||
geoIndex: 0,
|
||||
aspectScale: 0.75, // 长宽比
|
||||
showLegendSymbol: false, // 存在legend时显示
|
||||
label: {
|
||||
normal: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
show: false,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
roam: true,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaColor: '#031525',
|
||||
borderColor: '#FFFFFF',
|
||||
},
|
||||
emphasis: {
|
||||
areaColor: '#2B91B7',
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
},
|
||||
// 地图点的动画效果
|
||||
{
|
||||
// name: 'Top 5',
|
||||
type: 'effectScatter',
|
||||
coordinateSystem: 'geo',
|
||||
data: convertData(
|
||||
mapData
|
||||
.sort(function (a, b) {
|
||||
return b.value - a.value
|
||||
})
|
||||
.slice(0, 20),
|
||||
),
|
||||
symbolSize: function (val) {
|
||||
return val[2] / 10
|
||||
},
|
||||
showEffectOn: 'render',
|
||||
rippleEffect: {
|
||||
brushType: 'stroke',
|
||||
},
|
||||
hoverAnimation: true,
|
||||
label: {
|
||||
normal: {
|
||||
formatter: '{b}',
|
||||
position: 'right',
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colors,
|
||||
shadowBlur: 10,
|
||||
shadowColor: colors,
|
||||
},
|
||||
},
|
||||
zlevel: 1,
|
||||
},
|
||||
// 地图线的动画效果
|
||||
{
|
||||
type: 'lines',
|
||||
zlevel: 2,
|
||||
effect: {
|
||||
show: true,
|
||||
period: 4, // 箭头指向速度,值越小速度越快
|
||||
trailLength: 0.02, // 特效尾迹长度[0,1]值越大,尾迹越长重
|
||||
symbol: 'arrow', // 箭头图标
|
||||
symbolSize: 3, // 图标大小
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: colors,
|
||||
width: 0.1, // 尾迹线条宽度
|
||||
opacity: 0.5, // 尾迹线条透明度
|
||||
curveness: 0.3, // 尾迹线条曲直度
|
||||
},
|
||||
},
|
||||
data: convertToLineData(mapData, geoGpsMap),
|
||||
},
|
||||
{
|
||||
type: 'scatter',
|
||||
zlevel: 16,
|
||||
coordinateSystem: 'geo',
|
||||
symbolSize: 30,
|
||||
symbol: `image://${logo}`,
|
||||
data: cityIconData,
|
||||
rippleEffect: {
|
||||
period: 4,
|
||||
brushType: 'stroke',
|
||||
scale: 4,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
padding: 0,
|
||||
borderColor: 'black',
|
||||
background: 'rgba(0,0,0,0.7)',
|
||||
textStyle: {
|
||||
fontSize: 20,
|
||||
},
|
||||
formatter(val) {
|
||||
console.log('val=======', val)
|
||||
let tipHtml = `
|
||||
<div class="m-info" style=" opacity: 0.95;background: rgba(25,27,29,1);" >
|
||||
<div class="title" style="padding-left: 12px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
background: rgba(25,27,29,1);font-size: 20px;color: white;padding-right: 20px">${val.name}</div>
|
||||
<!-- <div class="content" style=" padding: 12px; background: rgba(0,2,4,1);">-->
|
||||
<!-- <div style=" font-size: 22px; color: #ff0000;">258944</div>-->
|
||||
<!-- </div>-->
|
||||
</div>`
|
||||
return tipHtml
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
let chart: EChartsType
|
||||
const initChart = () => {
|
||||
const chart = echarts.init(document.getElementById(props.id))
|
||||
chart.setOption(options)
|
||||
return chart
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
chart && chart.dispose()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
chart = initChart()
|
||||
window.addEventListener('resize', function () {
|
||||
chart && chart.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
3043
src/components/DataScreen/migrationEcharts/map.js
Normal file
183
src/components/DataScreen/pie/index.vue
Normal 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>
|
||||
18
src/components/PageWrapLayout/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
13
src/components/PageWrapLayout/index.vue
Normal 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>
|
||||
111
src/components/RightClickMenu/index.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div v-if="isShow" ref="rightMenu" class="g-right-click-menu" :style="style">
|
||||
<div v-for="(item, index) in data" :key="index" class="operating" @click.stop="operatingRightAction($event, item)">{{
|
||||
item.label
|
||||
}}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
left: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
dataInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
isViewInfo: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
let clientHeight = document.body.clientHeight
|
||||
let y = this.top
|
||||
if (clientHeight - y < 100) {
|
||||
return `left:${this.left}px;bottom:${clientHeight - y}px`
|
||||
} else {
|
||||
return `left:${this.left}px;top:${this.top}px`
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
left: {
|
||||
handler(newName, oldName) {
|
||||
if (newName) {
|
||||
this.isShow = true
|
||||
}
|
||||
},
|
||||
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
|
||||
// immediate: true
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
let _self = this
|
||||
window.addEventListener('click', function () {
|
||||
_self.isShow = false
|
||||
})
|
||||
window.addEventListener('mousedown', function (e) {
|
||||
if (e.which === 3) {
|
||||
_self.isShow = false
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @func 点击操作
|
||||
* @param val 1、置顶/取消置顶 2、开启/关闭免打扰 3、开启/关闭星标 4、删除会话
|
||||
*/
|
||||
operatingRightAction($event, val) {
|
||||
this.$emit('ok', $event, val)
|
||||
this.isShow = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.g-right-click-menu {
|
||||
left: 0;
|
||||
background: white;
|
||||
width: 148px;
|
||||
height: auto;
|
||||
position: fixed;
|
||||
//border: 1px solid #c4c4c4;
|
||||
z-index: 9;
|
||||
//box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||
.operating {
|
||||
font-size: 12px;
|
||||
padding-left: 23px;
|
||||
cursor: pointer;
|
||||
line-height: 27px;
|
||||
}
|
||||
.operating:hover {
|
||||
background: #e2e2e2;
|
||||
}
|
||||
|
||||
.last-delete {
|
||||
border-top: 1px solid #ededed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
48
src/components/SearchForm/components/BaseFormItem.vue
Normal 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>
|
||||
117
src/components/SearchForm/index.vue
Normal 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>
|
||||
@@ -4,33 +4,32 @@
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from "@vue/reactivity";
|
||||
export default {
|
||||
name: "baseSvgIcon",
|
||||
props: {
|
||||
iconClass: { type: String },
|
||||
className: { type: String },
|
||||
},
|
||||
setup(props) {
|
||||
const iconName = computed(() => {
|
||||
return props.iconClass ? `#icon-${props.iconClass}` : "#icon";
|
||||
});
|
||||
const svgClass = computed(() => {
|
||||
return props.className ? "svg-icon " + props.className : "svg-icon";
|
||||
});
|
||||
return { iconName, svgClass };
|
||||
},
|
||||
};
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue'
|
||||
export default {
|
||||
name: 'BaseSvgIcon',
|
||||
props: {
|
||||
iconClass: { type: String },
|
||||
className: { type: String },
|
||||
},
|
||||
setup(props) {
|
||||
const iconName = computed(() => {
|
||||
return props.iconClass ? `#icon-${props.iconClass}` : '#icon'
|
||||
})
|
||||
const svgClass = computed(() => {
|
||||
return props.className ? 'svg-icon ' + props.className : 'svg-icon'
|
||||
})
|
||||
return { iconName, svgClass }
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
20
src/components/SwitchDark/index.vue
Normal 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>
|
||||
251
src/components/Table/EditableProTable/index.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="m-edit-table">
|
||||
<div v-if="mode !== 'hide' && mode !== 'bottom'" style="margin-top: 15px; margin-bottom: 15px">
|
||||
<el-button style="width: 100%" @click="add">
|
||||
<el-icon style="margin-right: 4px"><plus /></el-icon> 添加一行数据</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table :data="transData" style="width: 100%" row-key="id" border>
|
||||
<template v-for="item in columns">
|
||||
<el-table-column
|
||||
v-if="item.type"
|
||||
:type="item.type"
|
||||
:width="item.width"
|
||||
:align="item.align"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
/>
|
||||
<el-table-column v-else :prop="item.name" :width="item.width" :align="item.align" :fixed="item.fixed" :label="item.label">
|
||||
<template #default="scope">
|
||||
<template v-if="!item.slot">
|
||||
<template v-if="item.readonly">
|
||||
{{ scope.row[item.name] }}
|
||||
</template>
|
||||
<template v-else-if="item.valueType === 'select'">
|
||||
<el-select v-if="scope.row.edit" v-model="scope.row[item.name]" clearable :placeholder="`请选择`">
|
||||
<el-option v-for="ite in item.options" :key="ite.value" :label="ite.label" :value="ite.value" />
|
||||
</el-select>
|
||||
<span v-else>{{ filterOption(item, scope) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.valueType === 'date'">
|
||||
<el-date-picker
|
||||
v-if="scope.row.edit"
|
||||
v-model="scope.row[item.name]"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
/>
|
||||
<span v-else>{{ scope.row[item.name] || '--' }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input v-if="scope.row.edit" v-model="scope.row[item.name]" clearable placeholder="请输入"></el-input>
|
||||
<span v-else>{{ scope.row[item.name] || '--' }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<slot v-else :name="item.name" :item="item" :row="scope.row"></slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column prop="operator" label="操作" width="250px" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.edit" type="primary" size="small" icon="CircleCheckFilled" @click="confirmEdit(scope.row)">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button v-else type="primary" size="small" icon="Edit" @click="scope.row.edit = !scope.row.edit"> 编辑 </el-button>
|
||||
<el-popover v-model:visible="scope.row.visible" trigger="click" placement="top" :width="160">
|
||||
<p style="display: flex; align-items: center; margin-bottom: 10px">
|
||||
<el-icon color="#faad14" style="margin-right: 10px"><warning-filled /></el-icon>
|
||||
删除此行?</p
|
||||
>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button size="small" @click="scope.row.visible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="deleteAction(scope.row)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button icon="Delete" type="danger" size="small" @click="deleteCurrent(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button v-if="scope.row.edit" type="primary" size="small" icon="Edit" @click="cancelEdit(scope.row)"> 取消 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="mode !== 'hide' && mode !== 'top'" style="margin-top: 15px">
|
||||
<el-button style="width: 100%" @click="add">
|
||||
<el-icon style="margin-right: 4px"><plus /></el-icon> 添加一行数据</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { deepObjClone } from '@/utils/index'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { reactive } from 'vue'
|
||||
const emit = defineEmits(['del', 'add', 'onChange'])
|
||||
let transData = ref([])
|
||||
|
||||
let props = defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
editableKeys: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
},
|
||||
})
|
||||
|
||||
// 删除当前此行
|
||||
const deleteCurrent = (row) => {
|
||||
// console.log('----------',row)
|
||||
// row.visible = true
|
||||
}
|
||||
|
||||
const getData = () => {
|
||||
let arr = deepObjClone(transData.value)
|
||||
for (let item of arr) {
|
||||
for (let attr in item) {
|
||||
if (attr.includes('te__mp')) {
|
||||
delete item[attr]
|
||||
}
|
||||
}
|
||||
}
|
||||
emit('onChange', arr)
|
||||
}
|
||||
|
||||
let obj = {}
|
||||
for (let item of props.columns) {
|
||||
props.data.forEach((it) => {
|
||||
if (!obj[item.name]) {
|
||||
obj[item.name] = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
const reset = () => {
|
||||
transData.value = props.data
|
||||
for (let item of transData.value) {
|
||||
if (props.editableKeys.includes(item.id)) {
|
||||
item.edit = true
|
||||
}
|
||||
}
|
||||
getData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.data,
|
||||
(val) => {
|
||||
// // 转换数据
|
||||
transData.value = deepObjClone(val)
|
||||
// 存储一个临时变量
|
||||
for (let item of transData.value) {
|
||||
if (props.editableKeys.includes(item.id)) {
|
||||
item.edit = true
|
||||
}
|
||||
for (let attr in item) {
|
||||
let temp = `${attr}te__mp`
|
||||
item[temp] = item[attr]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
console.log(`${val} items per page`)
|
||||
}
|
||||
|
||||
const listLoading = ref(false)
|
||||
|
||||
// 保存
|
||||
const confirmEdit = (row) => {
|
||||
row.edit = false
|
||||
for (let attr in row) {
|
||||
if (attr.includes('te__mp')) {
|
||||
row[attr] = row[attr.replace('te__mp', '')]
|
||||
}
|
||||
}
|
||||
getData()
|
||||
}
|
||||
// 取消
|
||||
const cancelEdit = (row) => {
|
||||
row.edit = !row.edit
|
||||
for (let attr in row) {
|
||||
if (attr !== 'edit') {
|
||||
if (!attr.includes('te__mp')) {
|
||||
row[attr] = row[attr + 'te__mp']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAction = (row) => {
|
||||
row.visible = false
|
||||
transData.value = transData.value.filter((item) => item.id !== row.id)
|
||||
emit('del', row)
|
||||
}
|
||||
|
||||
// 添加
|
||||
const add = () => {
|
||||
let id = ~~(Math.random() * 1000000).toFixed(0)
|
||||
let obj1 = Object.assign({}, obj, {
|
||||
id: id,
|
||||
edit: true,
|
||||
visible: false,
|
||||
})
|
||||
for (let attr in obj1) {
|
||||
let temp = `${attr}te__mp`
|
||||
obj1[temp] = obj1[attr]
|
||||
}
|
||||
|
||||
if (props.mode === 'bottom') {
|
||||
transData.value.push(obj1)
|
||||
}
|
||||
if (props.mode === 'top') {
|
||||
transData.value.unshift(obj1)
|
||||
}
|
||||
}
|
||||
|
||||
const filterOption = (item, scope) => {
|
||||
let obj = item.options.find((ite) => ite.value === scope.row[item.name])
|
||||
if (obj) {
|
||||
return obj.label
|
||||
}
|
||||
return '--'
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reset,
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.edit-input {
|
||||
padding-right: 100px;
|
||||
}
|
||||
.cancel-btn {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
}
|
||||
.inline-edit-table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
206
src/components/Table/PropTable/index.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="zb-pro-table">
|
||||
<div class="header">
|
||||
<SearchForm :columns="baseFormColumns" @submit="onSubmit" />
|
||||
</div>
|
||||
|
||||
<!----------底部---------------------->
|
||||
<div class="footer">
|
||||
<!-----------工具栏操作工具----------------->
|
||||
<div class="operator">
|
||||
<slot name="btn"></slot>
|
||||
</div>
|
||||
|
||||
<!-- ------------表格--------------->
|
||||
<div class="table">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
class="zb-table"
|
||||
:data="list"
|
||||
:border="true"
|
||||
@selection-change="(val) => emit('selection-change', val)"
|
||||
>
|
||||
<template v-for="item in columns">
|
||||
<el-table-column v-if="item.slot" v-bind="{ ...item, ...{ prop: item.name } }">
|
||||
<template #default="scope">
|
||||
<slot :name="item.name" :item="item" :row="scope.row"></slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-else v-bind="{ ...item, ...{ prop: item.name } }" />
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- ------------分页--------------->
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
v-model:currentPage="pagination.currentPage"
|
||||
:page-size="10"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="data.length"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import SearchForm from '@/components/SearchForm/index.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
const ruleFormRef = ref<FormInstance>()
|
||||
const emit = defineEmits(['reset', 'onSubmit', 'selection-change'])
|
||||
let props = defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 过滤调需要进行搜索选择的
|
||||
const baseFormColumns = computed(() => {
|
||||
return props.columns.filter((item) => item.valueType && item.search)
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
|
||||
const currentPage = ref(1)
|
||||
// 收缩展开
|
||||
const isExpand = ref(false)
|
||||
const handleSizeChange = (val: number) => {
|
||||
console.log(`${val} items per page`)
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
console.log(`current page: ${val}`)
|
||||
pagination.currentPage = val
|
||||
}
|
||||
|
||||
const list = computed(() => {
|
||||
let arr = JSON.parse(JSON.stringify(props.data))
|
||||
return arr.splice((pagination.currentPage - 1) * 10, 10)
|
||||
})
|
||||
|
||||
const listLoading = ref(false)
|
||||
const confirmEdit = (row) => {
|
||||
row.edit = false
|
||||
}
|
||||
const cancelEdit = (row) => {
|
||||
row.edit = false
|
||||
}
|
||||
|
||||
import { reactive } from 'vue'
|
||||
|
||||
let obj = {}
|
||||
let search = []
|
||||
for (let item of props.columns) {
|
||||
if (item.inSearch) {
|
||||
obj[item.name] = null
|
||||
}
|
||||
if (item.inSearch) {
|
||||
search.push(item)
|
||||
}
|
||||
}
|
||||
const formSearchData = ref(search)
|
||||
const formInline = reactive(obj)
|
||||
|
||||
const onSubmit = () => {
|
||||
emit('onSubmit', formInline)
|
||||
}
|
||||
|
||||
const reset = (formEl: FormInstance | undefined) => {
|
||||
formSearchData.value.forEach((item) => {
|
||||
formInline[item.name] = null
|
||||
})
|
||||
emit('reset')
|
||||
}
|
||||
const deleteAction = (row) => {
|
||||
ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
draggable: true,
|
||||
})
|
||||
.then(() => {
|
||||
list.value = list.value.filter((item) => item.id !== row.id)
|
||||
ElMessage.success('删除成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.edit-input {
|
||||
padding-right: 100px;
|
||||
}
|
||||
.cancel-btn {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
}
|
||||
.zb-pro-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 16px 16px 0 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
||||
:deep(.advancedForm) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
||||
min-height: 300px;
|
||||
.operator {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.table {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
.zb-table {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
::v-deep {
|
||||
.el-table__header th {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #252525;
|
||||
}
|
||||
}
|
||||
.pagination {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
210
src/components/Theme/index.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="m-setting-fix">
|
||||
<div class="item">
|
||||
<div class="item-child" @click="operator(1)">
|
||||
<el-icon size="30" color="#3698fd" style="margin-bottom: 8px"><brush /></el-icon>
|
||||
主题配置
|
||||
</div>
|
||||
<div class="item-child item-child2" @click="operator(2)">
|
||||
<el-icon size="30" color="#3698fd" style="margin-bottom: 8px"><Link /></el-icon>
|
||||
源码拷贝
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer v-model="drawer" title="主题配置" size="300px">
|
||||
<div class="theme-item">
|
||||
<label>导航栏布局</label>
|
||||
<el-select v-model="layout" placeholder="请选择" style="width: 150px" @change="(val) => changeSwitch('mode', val)">
|
||||
<el-option label="纵向" value="vertical"></el-option>
|
||||
<el-option label="横向" value="horizontal"></el-option>
|
||||
<el-option label="分栏" value="columns"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>主题颜色</label>
|
||||
<el-color-picker v-model="primary" :predefine="predefineColor" @change="changePrimary" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>暗黑模式</label>
|
||||
<switch-dark></switch-dark>
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>灰色模式</label>
|
||||
<el-switch v-model="gray" @change="(val) => changeGrayWeak('gray', val)" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>色弱模式</label>
|
||||
<el-switch v-model="weak" @change="(val) => changeGrayWeak('weak', val)" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>标签栏</label>
|
||||
<el-switch v-model="showTag" @change="(val) => changeSwitch('showTag', val)" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>侧边栏 Logo</label>
|
||||
<el-switch v-model="showLogo" @change="(val) => changeSwitch('showLogo', val)" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>保持一个子菜单的展开</label>
|
||||
<el-switch v-model="uniqueOpened" @change="(val) => changeSwitch('uniqueOpened', val)" />
|
||||
</div>
|
||||
<div class="theme-item">
|
||||
<label>固定header</label>
|
||||
<el-switch v-model="fixedHeader" @change="(val) => changeSwitch('fixedHeader', val)" />
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { openLoading, closeLoading } from '@/utils/element'
|
||||
import SwitchDark from '@/components/SwitchDark/index.vue'
|
||||
import { PRIMARY_COLOR } from '@/config/index'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
|
||||
const SettingStore = useSettingStore()
|
||||
const layout = ref(SettingStore.themeConfig.mode)
|
||||
const showTag = ref(SettingStore.themeConfig.showTag)
|
||||
const showLogo = ref(SettingStore.themeConfig.showLogo)
|
||||
const uniqueOpened = ref(SettingStore.themeConfig.uniqueOpened)
|
||||
const primary = ref(SettingStore.themeConfig.primary)
|
||||
const fixedHeader = ref(SettingStore.themeConfig.fixedHeader)
|
||||
const gray = ref(SettingStore.themeConfig.gray)
|
||||
const weak = ref(SettingStore.themeConfig.weak)
|
||||
|
||||
const drawer = computed({
|
||||
get() {
|
||||
return SettingStore.themeConfig.showSetting
|
||||
},
|
||||
set() {
|
||||
changeSwitch('showSetting', !SettingStore.themeConfig.showSetting)
|
||||
},
|
||||
})
|
||||
|
||||
// 预定义主题颜色
|
||||
const predefineColor = ['#409EFF', '#1890ff', '#304156', '#212121', '#11a983', '#13c2c2', '#6959CD', '#f5222d']
|
||||
|
||||
const operator = (type) => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
drawer.value = true
|
||||
return
|
||||
case 2:
|
||||
window.open('https://github.com/zouzhibin/vue-admin-perfect')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 进行配置
|
||||
const changeSwitch = (key, val) => {
|
||||
SettingStore.setThemeConfig({ key, val })
|
||||
if (key === 'mode') {
|
||||
openLoading()
|
||||
setTimeout(() => {
|
||||
closeLoading()
|
||||
}, 600)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听布局变化
|
||||
watch(
|
||||
() => layout.value,
|
||||
() => {
|
||||
const body = document.body as HTMLElement
|
||||
body.setAttribute('class', `layout-${layout.value}`)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 修改主题颜色
|
||||
const changePrimary = (val) => {
|
||||
if (!val) {
|
||||
primary.value = val = PRIMARY_COLOR
|
||||
ElMessage({ type: 'success', message: `主题颜色已重置为 ${PRIMARY_COLOR}` })
|
||||
}
|
||||
document.documentElement.style.setProperty('--el-color-primary', val)
|
||||
changeSwitch('primary', val)
|
||||
}
|
||||
|
||||
// 修改灰色模式
|
||||
const changeGrayWeak = (type, val) => {
|
||||
const body = document.documentElement as HTMLElement
|
||||
if (!val) return body.setAttribute('style', '')
|
||||
if (type === 'gray') body.setAttribute('style', 'filter: grayscale(1)')
|
||||
if (type === 'weak') body.setAttribute('style', 'filter: invert(80%)')
|
||||
changeSwitch(type, val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep(.el-drawer__header) {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding: 15px 20px 14px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.m-setting-fix {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
padding: 10px 0 0 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-top-left-radius: 5.5px;
|
||||
border-bottom-left-radius: 5.5px;
|
||||
box-shadow: 0 0 50px 0 rgb(82 63 105 / 15%);
|
||||
transform: translateY(-50%);
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
padding: 0 8px 10px 10px;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.item-child {
|
||||
color: #3698fd;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
/*padding-top: 10px;*/
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f6f8f9;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5.5px;
|
||||
font-size: 12px;
|
||||
background: #ebf5ff;
|
||||
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
.item-child2 {
|
||||
margin-top: 10px;
|
||||
color: #b37feb;
|
||||
background: #f7f2fd;
|
||||
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-drawer__title) {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
}
|
||||
.theme-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: black;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
88
src/components/Upload/index.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<el-upload
|
||||
action=""
|
||||
:before-upload="beforeUploadAction"
|
||||
:list-type="listType"
|
||||
:multiple="multiple"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-remove="handleRemove"
|
||||
:file-list="fileList"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
|
||||
<el-dialog v-model="dialogVisible" width="50%" top="80px">
|
||||
<img w-full :src="dialogImageUrl" alt="预览图片" style="width: 100%" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
|
||||
import type { UploadProps, UploadUserFile } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
let emit = defineEmits(['update'])
|
||||
let props = defineProps({
|
||||
modelValue: Array,
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
listType: {
|
||||
type: String,
|
||||
default: 'picture-card',
|
||||
},
|
||||
showFileList: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
let fileList = ref([])
|
||||
|
||||
const dialogImageUrl = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
|
||||
console.log(uploadFile, uploadFiles)
|
||||
}
|
||||
|
||||
const beforeUploadAction = (file, fileLi) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var reader = new FileReader()
|
||||
let reg = /\.jpg$|\.jpeg$|\.gif$|\.png$/i
|
||||
reader.readAsDataURL(file)
|
||||
let name = file.name
|
||||
if (reg.test(name)) {
|
||||
reader.onload = (e: FileReader) => {
|
||||
fileList.value.push({
|
||||
name: name,
|
||||
url: e.target.result,
|
||||
})
|
||||
emit('update', fileList.value)
|
||||
resolve(e.target.result)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('请上传图片')
|
||||
reject()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
||||
dialogImageUrl.value = uploadFile.url!
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
fileList.value = value
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
})
|
||||
</script>
|
||||
75
src/components/WangEdior/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="m-wangEditor">
|
||||
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
|
||||
<Editor
|
||||
v-model="valueHtml"
|
||||
class="editor-content'"
|
||||
style="height: 300px; overflow-y: hidden"
|
||||
:default-config="editorConfig"
|
||||
:mode="mode"
|
||||
@on-created="handleCreated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// 引入 wangEditor
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||
import { onBeforeUnmount, onMounted, watch, shallowRef, ref, computed } from 'vue'
|
||||
let editors = null
|
||||
// 编辑器实例,必须用 shallowRef
|
||||
const editorRef = shallowRef()
|
||||
const toolbarConfig = {}
|
||||
const editorConfig = { placeholder: '请输入内容...' }
|
||||
|
||||
// 内容 HTML
|
||||
const mode = ref('default')
|
||||
let emit = defineEmits(['update:modelValue'])
|
||||
let props = defineProps({
|
||||
modelValue: String,
|
||||
})
|
||||
|
||||
const getEditorData = () => {
|
||||
// 通过代码获取编辑器内容
|
||||
let data = editors.txt.html()
|
||||
alert(data)
|
||||
}
|
||||
|
||||
const handleCreated = (editor) => {
|
||||
editorRef.value = editor // 记录 editor 实例,重要!
|
||||
}
|
||||
|
||||
const valueHtml = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(val) {
|
||||
// 防止富文本内容为空时,校验失败
|
||||
if (editorRef.value.isEmpty()) val = ''
|
||||
emit('update:modelValue', val)
|
||||
},
|
||||
})
|
||||
|
||||
// 组件销毁时,也及时销毁编辑器
|
||||
onBeforeUnmount(() => {
|
||||
// 调用销毁 API 对当前编辑器实例进行销毁
|
||||
const editor = editorRef.value
|
||||
if (editor == null) {
|
||||
return
|
||||
}
|
||||
editor.destroy()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.m-wangEditor {
|
||||
z-index: 99;
|
||||
width: 100%;
|
||||
border: 1px solid #cccccc;
|
||||
.editor-toolbar {
|
||||
border-bottom: 1px solid #cccccc;
|
||||
}
|
||||
.editor-content {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="zb-pipeline">
|
||||
<zb-pipeline-start/>
|
||||
<zb-pipeline-start />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ZbPipelineStart from './zb-pipeline-start'
|
||||
import ZbPipelineStart from './zb-pipeline-start'
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="zb-pipeline-start-wrapper">
|
||||
<div class="zb-pipeline-start" v-bind:class="control === value ? 'active' : ''" v-on:click="handleClick">
|
||||
<div class="zb-pipeline-start" :class="control === value ? 'active' : ''" @click="handleClick">
|
||||
<div class="zb-pipeline-start-header">
|
||||
<zb-icon type="play-filled" />
|
||||
</div>
|
||||
@@ -8,14 +8,14 @@
|
||||
<div class="zb-pipeline-start-title">开始</div>
|
||||
<div class="zb-pipeline-start-tooltip">
|
||||
<el-tooltip placement="top-start" content="点击进行构建基础设置">
|
||||
<el-icon >
|
||||
<el-icon>
|
||||
<Help />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<!-- <zb-tooltip placement="right">-->
|
||||
<!-- <zb-icon type="help" />-->
|
||||
<!-- <div slot="content">点击进行构建基础设置</div>-->
|
||||
<!-- </zb-tooltip>-->
|
||||
<!-- <zb-tooltip placement="right">-->
|
||||
<!-- <zb-icon type="help" />-->
|
||||
<!-- <div slot="content">点击进行构建基础设置</div>-->
|
||||
<!-- </zb-tooltip>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,41 +23,128 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
Help
|
||||
} from '@element-plus/icons-vue'
|
||||
import { Help } from '@element-plus/icons-vue'
|
||||
</script>
|
||||
<style>
|
||||
/* zb-pipeline-start-wrapper */
|
||||
.zb-pipeline-start-wrapper { position:relative; padding:0 40px; }
|
||||
/* zb-pipeline-start-wrapper */
|
||||
.zb-pipeline-start-wrapper {
|
||||
position: relative;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
/* zb-pipeline-start */
|
||||
.zb-pipeline-start { cursor:pointer; user-select:none; position:relative; display:flex; justify-content:flex-start; align-items:stretch; width:200px; height:40px; border-radius:2px; background-color:#fff; }
|
||||
.zb-pipeline-start:after { content:""; position:absolute; top:20px; right:-40px; display:block; width:40px; height:1px; background-color:#2d8cf0; }
|
||||
.zb-pipeline-start .zb-pipeline-start-header { display:flex; justify-content:center; align-items:center; width:40px; border-width:1px 0 1px 1px; border-style:solid; border-color:#2d8cf0; border-radius:2px 0 0 2px; background-color:#2d8cf0; color:#fff; font-size:14px; font-weight:600; transition:all 0.2s cubic-bezier(0.23,1,0.32,1); }
|
||||
.zb-pipeline-start .zb-pipeline-start-body { flex:1; display:flex; border-width:1px 1px 1px 0; border-style:solid; border-color:#e3e8f0; border-radius:0 2px 2px 0; padding:0 8px; overflow:hidden; transition:all 0.2s cubic-bezier(0.23,1,0.32,1); }
|
||||
.zb-pipeline-start .zb-pipeline-start-title { flex:1; display:block; overflow:hidden; color:#262626; font-size:14px; font-weight:600; line-height:38px; white-space:nowrap; text-overflow:ellipsis; }
|
||||
.zb-pipeline-start .zb-pipeline-start-tooltip { cursor:pointer; display:flex; justify-content:center; align-items:center; margin-left:8px; }
|
||||
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-tooltip { display:block; }
|
||||
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-icon { display:block; color:#595959; font-size:14px; }
|
||||
/* zb-pipeline-start */
|
||||
.zb-pipeline-start {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
width: 200px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.zb-pipeline-start:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: -40px;
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 1px;
|
||||
background-color: #2d8cf0;
|
||||
}
|
||||
.zb-pipeline-start .zb-pipeline-start-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
border-width: 1px 0 1px 1px;
|
||||
border-style: solid;
|
||||
border-color: #2d8cf0;
|
||||
border-radius: 2px 0 0 2px;
|
||||
background-color: #2d8cf0;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.zb-pipeline-start .zb-pipeline-start-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
border-width: 1px 1px 1px 0;
|
||||
border-style: solid;
|
||||
border-color: #e3e8f0;
|
||||
border-radius: 0 2px 2px 0;
|
||||
padding: 0 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.zb-pipeline-start .zb-pipeline-start-title {
|
||||
flex: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: #262626;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 38px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.zb-pipeline-start .zb-pipeline-start-tooltip {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-tooltip {
|
||||
display: block;
|
||||
}
|
||||
.zb-pipeline-start .zb-pipeline-start-tooltip .zb-icon {
|
||||
display: block;
|
||||
color: #595959;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* zb-pipeline-start hover */
|
||||
.zb-pipeline-start:hover { }
|
||||
.zb-pipeline-start:hover:after { }
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-header { border-color:#2d8cf0; }
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-body { border-color:#2d8cf0; }
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-title { }
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-tooltip { }
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-tooltip { }
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-icon { }
|
||||
/* zb-pipeline-start hover */
|
||||
.zb-pipeline-start:hover {
|
||||
}
|
||||
.zb-pipeline-start:hover:after {
|
||||
}
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-header {
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-body {
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-title {
|
||||
}
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-tooltip {
|
||||
}
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-tooltip {
|
||||
}
|
||||
.zb-pipeline-start:hover .zb-pipeline-start-tooltip .zb-icon {
|
||||
}
|
||||
|
||||
/* zb-pipeline-start active */
|
||||
.zb-pipeline-start.active { }
|
||||
.zb-pipeline-start.active:after { }
|
||||
.zb-pipeline-start.active .zb-pipeline-start-header { border-color:#2d8cf0; }
|
||||
.zb-pipeline-start.active .zb-pipeline-start-body { border-color:#2d8cf0; }
|
||||
.zb-pipeline-start.active .zb-pipeline-start-title { }
|
||||
.zb-pipeline-start.active .zb-pipeline-start-tooltip { }
|
||||
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-tooltip { }
|
||||
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-icon { }
|
||||
/* zb-pipeline-start active */
|
||||
.zb-pipeline-start.active {
|
||||
}
|
||||
.zb-pipeline-start.active:after {
|
||||
}
|
||||
.zb-pipeline-start.active .zb-pipeline-start-header {
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
.zb-pipeline-start.active .zb-pipeline-start-body {
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
.zb-pipeline-start.active .zb-pipeline-start-title {
|
||||
}
|
||||
.zb-pipeline-start.active .zb-pipeline-start-tooltip {
|
||||
}
|
||||
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-tooltip {
|
||||
}
|
||||
.zb-pipeline-start.active .zb-pipeline-start-tooltip .zb-icon {
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group>
|
||||
<el-breadcrumb-item v-for="(item,index) in obj.levelList" :key="item.path">
|
||||
<span v-if="item.redirect==='noRedirect'||index==obj.levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import {onMounted, reactive, watch} from "vue";
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
const obj = reactive({levelList:{}})
|
||||
const route = useRoute()
|
||||
|
||||
// 获取面包屑
|
||||
const getBreadcrumb = ()=>{
|
||||
let matched = route.matched.filter(item => item.meta && item.meta.title)
|
||||
const first = matched[0]
|
||||
obj.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
}
|
||||
onMounted(()=>{
|
||||
getBreadcrumb()
|
||||
watch(route,()=>{
|
||||
if (route.path.startsWith('/redirect/')) {
|
||||
return
|
||||
}
|
||||
getBreadcrumb()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
margin-left: 10px;
|
||||
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
|
||||
.no-redirect {
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
src/components/u-container-layout/index.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="m-container-layout">
|
||||
<div class="m-container-layout-inner">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.m-container-layout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
padding: 10px 12px;
|
||||
box-sizing: border-box;
|
||||
.m-container-layout-inner {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +0,0 @@
|
||||
import CountTo from './vue-countTo.vue';
|
||||
export default CountTo;
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
window.Vue.component('count-to', CountTo);
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
<template>
|
||||
<span>
|
||||
{{displayValue}}
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
import { requestAnimationFrame, cancelAnimationFrame } from './requestAnimationFrame.js'
|
||||
export default {
|
||||
props: {
|
||||
startVal: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
endVal: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 2017
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 3000
|
||||
},
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
decimals: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
validator(value) {
|
||||
return value >= 0
|
||||
}
|
||||
},
|
||||
decimal: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '.'
|
||||
},
|
||||
separator: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
easingFn: {
|
||||
type: Function,
|
||||
default(t, b, c, d) {
|
||||
return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
printVal: null,
|
||||
paused: false,
|
||||
localDuration: this.duration,
|
||||
startTime: null,
|
||||
timestamp: null,
|
||||
remaining: null,
|
||||
rAF: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
if (this.autoplay) {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
endVal() {
|
||||
if (this.autoplay) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.autoplay) {
|
||||
this.start();
|
||||
}
|
||||
this.$emit('mountedCallback')
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
this.localStartVal = this.startVal;
|
||||
this.startTime = null;
|
||||
this.localDuration = this.duration;
|
||||
this.paused = false;
|
||||
this.rAF = requestAnimationFrame(this.count);
|
||||
},
|
||||
pauseResume() {
|
||||
if (this.paused) {
|
||||
this.resume();
|
||||
this.paused = false;
|
||||
} else {
|
||||
this.pause();
|
||||
this.paused = true;
|
||||
}
|
||||
},
|
||||
pause() {
|
||||
cancelAnimationFrame(this.rAF);
|
||||
},
|
||||
resume() {
|
||||
this.startTime = null;
|
||||
this.localDuration = +this.remaining;
|
||||
this.localStartVal = +this.printVal;
|
||||
requestAnimationFrame(this.count);
|
||||
},
|
||||
reset() {
|
||||
this.startTime = null;
|
||||
cancelAnimationFrame(this.rAF);
|
||||
this.displayValue = this.formatNumber(this.startVal);
|
||||
},
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp;
|
||||
this.timestamp = timestamp;
|
||||
const progress = timestamp - this.startTime;
|
||||
this.remaining = this.localDuration - progress;
|
||||
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
|
||||
} else {
|
||||
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - ((this.localStartVal - this.endVal) * (progress / this.localDuration));
|
||||
} else {
|
||||
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
|
||||
} else {
|
||||
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
|
||||
}
|
||||
|
||||
this.displayValue = this.formatNumber(this.printVal)
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = requestAnimationFrame(this.count);
|
||||
} else {
|
||||
this.$emit('callback');
|
||||
}
|
||||
},
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val))
|
||||
},
|
||||
formatNumber(num) {
|
||||
num = num.toFixed(this.decimals);
|
||||
num += '';
|
||||
const x = num.split('.');
|
||||
let x1 = x[0];
|
||||
const x2 = x.length > 1 ? this.decimal + x[1] : '';
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
if (this.separator && !this.isNumber(this.separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, '$1' + this.separator + '$2');
|
||||
}
|
||||
}
|
||||
return this.prefix + x1 + x2 + this.suffix;
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
cancelAnimationFrame(this.rAF)
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="m-info">
|
||||
<el-popover
|
||||
width="200px"
|
||||
placement="bottom"
|
||||
>
|
||||
<template #reference>
|
||||
<el-badge :value="3" class="item-info-pop">
|
||||
<el-icon><Bell /></el-icon>
|
||||
</el-badge>
|
||||
</template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane label="通知" name="first">
|
||||
<div class="item-child"> github开源地址:<el-button type="text" @click="toGitHub">点我</el-button></div>
|
||||
<div class="item-child"> github开源地址:<el-button type="text" @click="toGitHub">点我</el-button></div>
|
||||
<div class="item-child"> github开源地址:<el-button type="text" @click="toGitHub">点我</el-button></div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
|
||||
const activeName = ref('first')
|
||||
const toGitHub = ()=>{
|
||||
window.open('https://github.com/zouzhibin/vue-admin-perfect')
|
||||
}
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.m-info{
|
||||
width: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all .3s;
|
||||
.item-info-pop{
|
||||
// width:100% ;
|
||||
//height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.item-child{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +0,0 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
</script>
|
||||
@@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<div class="g-right-click-menu" :style="style" v-if="isShow" ref="rightMenu">
|
||||
<div
|
||||
v-for="item,index in data"
|
||||
:key="index"
|
||||
class="operating" @click.stop="operatingRightAction($event,item)">{{item.label}}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
left: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
dataInfo: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isViewInfo: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
left: {
|
||||
handler (newName, oldName) {
|
||||
if (newName) {
|
||||
this.isShow = true
|
||||
}
|
||||
}
|
||||
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
|
||||
// immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style () {
|
||||
let clientHeight = document.body.clientHeight
|
||||
let y = this.top
|
||||
if (clientHeight - y < 100) {
|
||||
return `left:${this.left}px;bottom:${clientHeight - y}px`
|
||||
} else {
|
||||
return `left:${this.left}px;top:${this.top}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isShow: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @func 点击操作
|
||||
* @param val 1、置顶/取消置顶 2、开启/关闭免打扰 3、开启/关闭星标 4、删除会话
|
||||
*/
|
||||
operatingRightAction ($event, val) {
|
||||
this.$emit('ok', $event, val)
|
||||
this.isShow = false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let _self = this
|
||||
window.addEventListener('click', function () {
|
||||
_self.isShow = false
|
||||
})
|
||||
window.addEventListener('mousedown', function (e) {
|
||||
if (e.which === 3) {
|
||||
_self.isShow = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.g-right-click-menu {
|
||||
left: 0;
|
||||
background: white;
|
||||
width: 148px;
|
||||
height: auto;
|
||||
position: fixed;
|
||||
//border: 1px solid #c4c4c4;
|
||||
z-index: 9;
|
||||
//box-shadow: 0 1px 2px 0px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||
.operating {
|
||||
font-size: 12px;
|
||||
padding-left: 23px;
|
||||
cursor: pointer;
|
||||
line-height: 27px;
|
||||
}
|
||||
.operating:hover {
|
||||
background: #e2e2e2;
|
||||
}
|
||||
|
||||
.last-delete {
|
||||
border-top: 1px solid #ededed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,157 +0,0 @@
|
||||
/* eslint-disable promise/prefer-await-to-then */
|
||||
|
||||
const methodMap = [
|
||||
[
|
||||
'requestFullscreen',
|
||||
'exitFullscreen',
|
||||
'fullscreenElement',
|
||||
'fullscreenEnabled',
|
||||
'fullscreenchange',
|
||||
'fullscreenerror',
|
||||
],
|
||||
// New WebKit
|
||||
[
|
||||
'webkitRequestFullscreen',
|
||||
'webkitExitFullscreen',
|
||||
'webkitFullscreenElement',
|
||||
'webkitFullscreenEnabled',
|
||||
'webkitfullscreenchange',
|
||||
'webkitfullscreenerror',
|
||||
|
||||
],
|
||||
// Old WebKit
|
||||
[
|
||||
'webkitRequestFullScreen',
|
||||
'webkitCancelFullScreen',
|
||||
'webkitCurrentFullScreenElement',
|
||||
'webkitCancelFullScreen',
|
||||
'webkitfullscreenchange',
|
||||
'webkitfullscreenerror',
|
||||
|
||||
],
|
||||
[
|
||||
'mozRequestFullScreen',
|
||||
'mozCancelFullScreen',
|
||||
'mozFullScreenElement',
|
||||
'mozFullScreenEnabled',
|
||||
'mozfullscreenchange',
|
||||
'mozfullscreenerror',
|
||||
],
|
||||
[
|
||||
'msRequestFullscreen',
|
||||
'msExitFullscreen',
|
||||
'msFullscreenElement',
|
||||
'msFullscreenEnabled',
|
||||
'MSFullscreenChange',
|
||||
'MSFullscreenError',
|
||||
],
|
||||
];
|
||||
|
||||
const nativeAPI = (() => {
|
||||
const unprefixedMethods = methodMap[0];
|
||||
const returnValue = {};
|
||||
|
||||
for (const methodList of methodMap) {
|
||||
const exitFullscreenMethod = methodList?.[1];
|
||||
if (exitFullscreenMethod in document) {
|
||||
for (const [index, method] of methodList.entries()) {
|
||||
returnValue[unprefixedMethods[index]] = method;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})();
|
||||
|
||||
const eventNameMap = {
|
||||
change: nativeAPI.fullscreenchange,
|
||||
error: nativeAPI.fullscreenerror,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
let screenfull = {
|
||||
// eslint-disable-next-line default-param-last
|
||||
request(element = document.documentElement, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFullScreenEntered = () => {
|
||||
screenfull.off('change', onFullScreenEntered);
|
||||
resolve();
|
||||
};
|
||||
|
||||
screenfull.on('change', onFullScreenEntered);
|
||||
|
||||
const returnPromise = element[nativeAPI.requestFullscreen](options);
|
||||
|
||||
if (returnPromise instanceof Promise) {
|
||||
returnPromise.then(onFullScreenEntered).catch(reject);
|
||||
}
|
||||
});
|
||||
},
|
||||
exit() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!screenfull.isFullscreen) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const onFullScreenExit = () => {
|
||||
screenfull.off('change', onFullScreenExit);
|
||||
resolve();
|
||||
};
|
||||
|
||||
screenfull.on('change', onFullScreenExit);
|
||||
|
||||
const returnPromise = document[nativeAPI.exitFullscreen]();
|
||||
|
||||
if (returnPromise instanceof Promise) {
|
||||
returnPromise.then(onFullScreenExit).catch(reject);
|
||||
}
|
||||
});
|
||||
},
|
||||
toggle(element, options) {
|
||||
return screenfull.isFullscreen ? screenfull.exit() : screenfull.request(element, options);
|
||||
},
|
||||
onchange(callback) {
|
||||
screenfull.on('change', callback);
|
||||
},
|
||||
onerror(callback) {
|
||||
screenfull.on('error', callback);
|
||||
},
|
||||
on(event, callback) {
|
||||
const eventName = eventNameMap[event];
|
||||
if (eventName) {
|
||||
document.addEventListener(eventName, callback, false);
|
||||
}
|
||||
},
|
||||
off(event, callback) {
|
||||
const eventName = eventNameMap[event];
|
||||
if (eventName) {
|
||||
document.removeEventListener(eventName, callback, false);
|
||||
}
|
||||
},
|
||||
raw: nativeAPI,
|
||||
};
|
||||
|
||||
Object.defineProperties(screenfull, {
|
||||
isFullscreen: {
|
||||
get: () => Boolean(document[nativeAPI.fullscreenElement]),
|
||||
},
|
||||
element: {
|
||||
enumerable: true,
|
||||
get: () => document[nativeAPI.fullscreenElement] ?? undefined,
|
||||
},
|
||||
isEnabled: {
|
||||
enumerable: true,
|
||||
// Coerce to boolean in case of old WebKit.
|
||||
get: () => Boolean(document[nativeAPI.fullscreenEnabled]),
|
||||
},
|
||||
});
|
||||
|
||||
if (!nativeAPI) {
|
||||
screenfull = {isEnabled: false};
|
||||
}
|
||||
|
||||
|
||||
export default screenfull;
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="m-screenful">
|
||||
<el-icon
|
||||
title="全屏"
|
||||
@click.stop="click"
|
||||
style="margin-right: 10px;cursor: pointer" ><full-screen /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import screenfull from './index'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {onBeforeUnmount, onMounted, ref} from "vue";
|
||||
|
||||
|
||||
let isFullscreen = ref(false)
|
||||
const click = ()=>{
|
||||
if (!screenfull.isEnabled) {
|
||||
ElMessage({
|
||||
message: '你的浏览器不支持',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
screenfull.toggle()
|
||||
}
|
||||
const change = ()=>{
|
||||
isFullscreen.value = screenfull.isFullscreen
|
||||
}
|
||||
|
||||
const init = ()=>{
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.on('change', change)
|
||||
}
|
||||
}
|
||||
|
||||
const destroy = ()=>{
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.off('change', change)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
init()
|
||||
})
|
||||
|
||||
onBeforeUnmount(()=>{
|
||||
destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.m-screenful{
|
||||
width: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all .3s;
|
||||
}
|
||||
</style>
|
||||
@@ -1,126 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="m-setting-fix">
|
||||
<div class="item">
|
||||
<div class="item-child" @click="operator(1)">
|
||||
<el-icon size="30" color="#3698fd" style="margin-bottom: 8px"><brush /></el-icon>
|
||||
主题配置
|
||||
</div>
|
||||
<div class="item-child item-child2" @click="operator(2)">
|
||||
<el-icon size="30"
|
||||
color="#3698fd"
|
||||
style="margin-bottom: 8px"><Link /></el-icon>
|
||||
源码拷贝
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer v-model="drawer" title="主题配置" size="300px">
|
||||
<div class="drawer-item">
|
||||
<label>布局</label>
|
||||
<el-select v-model="layout" placeholder="请选择" style="width: 150px" @change="(val)=>changeSwitch(val,2)">
|
||||
<el-option label="纵向" value="vertical"></el-option>
|
||||
<el-option label="横向" value="horizontal"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="drawer-item">
|
||||
<label>标签</label>
|
||||
<el-switch v-model="tag" @change="(val)=>changeSwitch(val,1)"/>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
const drawer = ref(false)
|
||||
const tag = ref(true)
|
||||
const layout = ref('vertical')
|
||||
import {useStore} from "vuex";
|
||||
|
||||
const store = useStore()
|
||||
const operator = (type)=>{
|
||||
switch (type) {
|
||||
case 1:
|
||||
drawer.value = true
|
||||
return
|
||||
case 2:
|
||||
window.open('https://github.com/zouzhibin/vue-admin-perfect')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const changeSwitch =(val,type)=>{
|
||||
switch (type) {
|
||||
// 是否显示tag
|
||||
case 1:
|
||||
store.dispatch('setting/setTag',val)
|
||||
return
|
||||
case 2:
|
||||
store.dispatch('setting/setMode',val)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.m-setting-fix{
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
z-index: 1997;
|
||||
padding: 10px 0 0 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-top-left-radius: 5.5px;
|
||||
border-bottom-left-radius: 5.5px;
|
||||
box-shadow: 0 0 50px 0 rgb(82 63 105 / 15%);
|
||||
transform: translateY(-50%);
|
||||
.item{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
padding: 0 8px 10px 10px;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.item-child{
|
||||
color: #3698fd;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
/*padding-top: 10px;*/
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f6f8f9;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5.5px;
|
||||
font-size: 12px;
|
||||
background: #ebf5ff;
|
||||
transition: color .15s ease,background-color .15s ease,border-color .15s ease,box-shadow .15s ease;
|
||||
|
||||
}
|
||||
.item-child2{
|
||||
margin-top: 10px;
|
||||
color: #b37feb;
|
||||
background: #f7f2fd;
|
||||
transition: color .15s ease,background-color .15s ease,border-color .15s ease,box-shadow .15s ease;
|
||||
}
|
||||
|
||||
}
|
||||
.drawer-item{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@@ -1,86 +0,0 @@
|
||||
<template>
|
||||
<el-upload
|
||||
action=""
|
||||
:before-upload="beforeUploadAction"
|
||||
:list-type="listType"
|
||||
:multiple="multiple"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-remove="handleRemove"
|
||||
:file-list="fileList"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-upload>
|
||||
|
||||
<el-dialog v-model="dialogVisible" width="50%" top="80px">
|
||||
<img w-full :src="dialogImageUrl" alt="预览图片" style="width: 100%"/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, ref, watch} from 'vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
|
||||
import type { UploadProps, UploadUserFile } from 'element-plus'
|
||||
import {ElMessage} from "element-plus";
|
||||
let emit = defineEmits(['update'])
|
||||
let props = defineProps({
|
||||
modelValue:Array,
|
||||
multiple:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
listType:{
|
||||
type:String,
|
||||
default:'picture-card'
|
||||
},
|
||||
showFileList:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
}
|
||||
})
|
||||
let fileList = ref([
|
||||
|
||||
])
|
||||
|
||||
const dialogImageUrl = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
|
||||
console.log(uploadFile, uploadFiles)
|
||||
}
|
||||
|
||||
const beforeUploadAction = (file, fileLi)=>{
|
||||
return new Promise((resolve, reject) => {
|
||||
var reader = new FileReader()
|
||||
let reg = (/\.jpg$|\.jpeg$|\.gif$|\.png$/i)
|
||||
reader.readAsDataURL(file)
|
||||
let name = file.name
|
||||
if(reg.test(name)){
|
||||
reader.onload = (e:FileReader)=>{
|
||||
fileList.value.push({
|
||||
name:name,
|
||||
url:e.target.result
|
||||
})
|
||||
emit('update',fileList.value)
|
||||
resolve(e.target.result)
|
||||
}
|
||||
}else{
|
||||
ElMessage.error('请上传图片')
|
||||
reject()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
||||
dialogImageUrl.value = uploadFile.url!
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
watch(()=>props.modelValue,(value)=>{
|
||||
fileList.value = value
|
||||
},{
|
||||
immediate:true
|
||||
})
|
||||
})
|
||||
</script>
|
||||