init
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
||||
gciplay.js
|
||||
gciplay.monitor.js
|
||||
libvideo.wasm
|
||||
libvideoflv.wasm
|
||||
30
.eslintrc.js
Normal file
@ -0,0 +1,30 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
'vue/setup-compiler-macros': true
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-recommended',
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended'
|
||||
],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 2020,
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
},
|
||||
plugins: ['vue', '@typescript-eslint'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'vue/no-unused-vars': 'warn',
|
||||
'no-debugger': 'warn'
|
||||
}
|
||||
};
|
||||
36
.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Lock files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# yalc
|
||||
.yalc
|
||||
yalc.lock
|
||||
|
||||
dist.zip
|
||||
stats.html
|
||||
4
.husky/commit-msg
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no -- commitlint --edit "$1"
|
||||
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
1
.npmrc
Normal file
@ -0,0 +1 @@
|
||||
registry = http://10.91.137.86:8002/repository/npm-group
|
||||
10
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"esbenp.prettier-vscode",
|
||||
"csstools.postcss"
|
||||
]
|
||||
}
|
||||
32
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"eslint.format.enable": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"git.autofetch": true,
|
||||
"[yaml]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"code-runner.runInTerminal": true
|
||||
}
|
||||
147
README.md
Normal file
@ -0,0 +1,147 @@
|
||||
# 成都环投项目web前端工程需知
|
||||
|
||||
该模板基于 `vite` + `vue3` + `typescript` ,集成内容如下:
|
||||
|
||||
1. `eslint` + `prettier` 代码规范格式化
|
||||
2. `husky` + `lint-staged` git代码提交门禁
|
||||
3. `axios` 接口通信 fetch
|
||||
4. `pinia(vuex)` 状态管理工具
|
||||
5. `less` 样式预处理 (已被删除,有些ui库的主题化使用sass/scss)
|
||||
|
||||
- 等待修复内容
|
||||
https://github.com/element-plus/element-plus/pull/14074
|
||||
|
||||
成都环投项目web项目背景:
|
||||
|
||||
1. 不考虑大屏
|
||||
2. 不考虑移动端
|
||||
3. 不需兼容ie项目
|
||||
|
||||
## 开发人员须知
|
||||
|
||||
开发人员初次加载该工程时,应注意事项
|
||||
|
||||
### 应有开发环境
|
||||
|
||||
`node > 16.0 & pnpm = 8.2.0 & git`
|
||||
|
||||
### vscode 应装插件
|
||||
|
||||
1. 必备插件
|
||||
|
||||
- Name: TypeScript Vue Plugin (Volar)
|
||||
- Name: Vue Language Features (Volar)
|
||||
- Name: Prettier - Code formatter
|
||||
- Name: ESLint
|
||||
|
||||
2. 其他插件
|
||||
|
||||
- Name: Error Lens
|
||||
- Name: GitLens — Git supercharged
|
||||
- Name: Tailwind CSS IntelliSense
|
||||
|
||||
## 项目启动
|
||||
|
||||
1. 开发编译
|
||||
|
||||
```shell
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
2. 构建打包
|
||||
|
||||
```shell
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
3. 打包内容预览
|
||||
|
||||
```shell
|
||||
pnpm run preview
|
||||
```
|
||||
|
||||
## npm 仓库
|
||||
|
||||
该工程 `npm` 仓库指向公司内网私服仓库,详情可见 `.npmrc` 文件配置。如仓库变动请更改 `registry` 参数
|
||||
|
||||
## 工程环境环境
|
||||
|
||||
一般环境变量是处于工程的根目录,但如果环境过多可能出现根目录文件冗杂的问题,因此本人将环境变量配置文件至 `configs/env` 文件夹进行管理。环境变量标头可自行配置:
|
||||
|
||||
```ts
|
||||
envDir: './configs/env',
|
||||
envPrefix: ['GCI_', 'VITE_'],
|
||||
```
|
||||
|
||||
一般会出现三个环境
|
||||
|
||||
1. 开发环境
|
||||
2. 测试环境
|
||||
3. 生产环境(\*N)
|
||||
|
||||
## 工程目录规范
|
||||
|
||||
为防止出现工程文件管理位置混乱的情况,建议如下:
|
||||
|
||||
1. 路由路径对应 `views` 文件夹下路径层级
|
||||
|
||||
## UI库选型
|
||||
|
||||
- element-plus
|
||||
`element-plus` 底层写法为 `vue` 核心 + `scss`, 写法符合 `vue` 开发思路。社区活跃,拥有主题化配置和国际化配置功能
|
||||
|
||||
### css预处理选型
|
||||
|
||||
参考ui库选型选择 `scss`
|
||||
|
||||
其他选择:`tailwindcss` 原子化css开发
|
||||
|
||||
https://www.tailwindcss.com/docs
|
||||
|
||||
### Mock[废弃]
|
||||
|
||||
在后端接口未出来,而前端需要进行接口调整时,mock的作用就开始发挥了 ×
|
||||
|
||||
### 事件车 eventBus
|
||||
|
||||
由于状态管理工具用于跨组件通信可能会引起代码混乱,推荐引入事件车模型进行通信
|
||||
|
||||
详情请看 `src\hooks\useEventBus\index.ts`
|
||||
|
||||
### 字体库
|
||||
|
||||
为防止不同设备之间显示效果差异,推荐引入
|
||||
|
||||
1. 阿里巴巴普惠体
|
||||
2. 思源黑体
|
||||
3. 其他
|
||||
|
||||
### 异常性能监控[废弃]
|
||||
|
||||
可不考虑
|
||||
|
||||
### CI/CD[废弃]
|
||||
|
||||
jenkins/git action
|
||||
|
||||
https://doc.weixin.qq.com/doc/w3_AcwArQY_AM4X1Wywm5mTjqKqdQtbI?scode=AC4AwgfLAAovcTVrldAcwArQY_AM4&version=4.1.9.6012&platform=win
|
||||
|
||||
### 工具类
|
||||
|
||||
### lodash
|
||||
|
||||
按需引入
|
||||
|
||||
- 错误的使用方式
|
||||
|
||||
```ts
|
||||
import { forEach } from 'lodash';
|
||||
```
|
||||
|
||||
- 正确的使用方式
|
||||
|
||||
```ts
|
||||
import forEach from 'lodash/forEach';
|
||||
```
|
||||
|
||||
### vue-use
|
||||
3
commitlint.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional']
|
||||
};
|
||||
38
configs/env/.env
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# 后端服务地址,请以 http 或 https 开始,以端口号结束
|
||||
VITE_APP_API_URL=http://10.91.123.10:8091
|
||||
# 成都
|
||||
# VITE_APP_API_URL=http://10.105.148.2:19098
|
||||
# 成都2
|
||||
# VITE_APP_API_URL=http://10.105.148.7:8080
|
||||
# 文龙
|
||||
# VITE_APP_API_URL=http://10.180.22.80:8089
|
||||
# 作恒
|
||||
# VITE_APP_API_URL=http://10.180.12.174:8089
|
||||
# 书汉
|
||||
# VITE_APP_API_URL=http://10.91.123.10:8082
|
||||
# 玉赞
|
||||
# VITE_APP_API_URL=http://10.180.22.227:8089
|
||||
# 产线
|
||||
# VITE_APP_API_URL=http://182.140.140.63:19098
|
||||
|
||||
# 外网
|
||||
VITE_APP_API_URL_OUTER=http://10.91.123.10:8091
|
||||
|
||||
|
||||
# 文件资源地址
|
||||
VITE_APP_RESOURCE_URL=http://10.91.134.242:9000/highway
|
||||
# VITE_APP_RESOURCE_URL=http://10.105.148.2:9000/htfile/static
|
||||
|
||||
# 服务相对路径,请以 / 开始,以 路径名结束
|
||||
VITE_APP_API_PATH=/cdenv
|
||||
VITE_APP_BASE_URL=/
|
||||
# 开发接口代理设置 OPEN 为打开,其余为关闭
|
||||
VITE_APP_PROXY=OFF
|
||||
|
||||
# 项目信息
|
||||
VITE_APP_NAME=成都环投智慧管理平台
|
||||
VITE_APP_COPYRIGHT=广州交信投科技股份有限公司
|
||||
|
||||
# 高德配置
|
||||
VITE_AMAP_KEY=6b3f532e02c14efb49439d47c6b9841f
|
||||
VITE_AMAP_TOKEN=6aa62b732ad825890c5f2f3bc9a29a56
|
||||
1
configs/env/.env.development
vendored
Normal file
@ -0,0 +1 @@
|
||||
VITE_APP_PROXY=ON
|
||||
22
configs/env/.env.production
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# # 后端服务地址,请以 http 或 https 开始,以端口号结束
|
||||
# 内网
|
||||
# VITE_APP_API_URL=http://10.105.148.2:19098
|
||||
# 外网
|
||||
# VITE_APP_API_URL_OUTER=http://182.140.140.63:19098
|
||||
|
||||
# # 文件资源地址
|
||||
# VITE_APP_RESOURCE_URL=https://zuche.cdenvironment.com:25000/htfile/static
|
||||
|
||||
# # 服务相对路径,请以 / 开始,以 路径名结束
|
||||
# VITE_APP_API_PATH=/cdenv
|
||||
# VITE_APP_BASE_URL=/cdhtManage
|
||||
# # 开发接口代理设置 OPEN 为打开,其余为关闭
|
||||
# VITE_APP_PROXY=OFF
|
||||
|
||||
# # 项目信息
|
||||
# VITE_APP_NAME=成都环投智慧管理平台
|
||||
# VITE_APP_COPYRIGHT=广州交信投科技股份有限公司
|
||||
|
||||
# # 高德配置
|
||||
# VITE_AMAP_KEY=6b3f532e02c14efb49439d47c6b9841f
|
||||
# VITE_AMAP_TOKEN=6aa62b732ad825890c5f2f3bc9a29a56
|
||||
1
configs/mock/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// mock
|
||||
70
docs/gci标准web模板说明.md
Normal file
@ -0,0 +1,70 @@
|
||||
# 项目初始模板
|
||||
|
||||
这是 web 端项目初始模板。
|
||||
|
||||
## 模板搭建思路
|
||||
|
||||
整体来说,模板是基于 [Vite](https://v3.vitejs.dev/) 脚手架生成的,在此基础上做少量改动,以及加入自定义的基建。
|
||||
|
||||
### 技术栈
|
||||
|
||||
#### 类型系统
|
||||
|
||||
中大型项目如果缺少类型系统,则难以维护。[TypeScript](https://www.typescriptlang.org/) 作为 JavaScript 的超集,很好的弥补了类型系统的缺失。它是现代前端项目的标配之一。
|
||||
|
||||
#### UI library
|
||||
|
||||
现代前端业界 UI 库有不少,耳熟能详的有 [Angular](https://angular.io/)、[React](https://react.dev/)、[Vue](https://vuejs.org/)、[Sevlte](https://svelte.dev/)、[Solid](https://www.solidjs.com/)。我们是使用 Vue,版本 3.0 以上,理由如下:
|
||||
|
||||
- UI 库最好是采用主流方案,而 React 与 Vue 是国内最普及的方案
|
||||
- 公司项目大多都使用 Vue,继续使用 Vue 不会产生额外学习成本
|
||||
- 相比 React,Vue 更简单容易上手
|
||||
- 目前 Vue 的最新版本是 3.3,比起 Vue2 有了许多改进优化的地方
|
||||
|
||||
#### router
|
||||
|
||||
前端应用一般都需要路由,而 [vue-router](https://router.vuejs.org/) 是 Vue 的官方路由库,所以它成为项目模板的路由方案。
|
||||
|
||||
#### store
|
||||
|
||||
前端应用大多都需要 store 共享状态。Vue2 的 store 方案是 [Vuex](https://vuex.vuejs.org/),而 Vue3 的官方推荐是使用 [pinia](https://pinia.vuejs.org/)。所以 pinia 成为项目模板中的 store 方案。
|
||||
|
||||
#### http 请求库
|
||||
|
||||
前端应用基本都需要通过 http 请求与服务端通信,[axios](https://github.com/axios/axios) 是使用率最高的请求库,所以它成为项目模板的网络请求库。
|
||||
|
||||
### 基建
|
||||
|
||||
项目除了包含业务功能外,我们还需要一些手段来保证项目的可维护性、可靠性、效率,这些手段可被笼统地成为基建。本项目模板的基建遵循[前端开发文档](https://doc.weixin.qq.com/doc/w3_AaUAwAZPAME0KYWjEqVQamQlNXouh?scode=AC4AwgfLAAo1jbQ6noAaUAwAZPAME)的要求。
|
||||
|
||||
#### 代码提交规范
|
||||
|
||||
代码提交须遵循前端开发文档要求的 Angular 提交规范,相应的约束工具则是 [commitlint](https://www.npmjs.com/package/@commitlint/cli)。
|
||||
|
||||
#### 编码规范
|
||||
|
||||
编码规范是根据项目技术栈具体而定。对于 Vue3 web 端项目,一般遵循 `eslint:recommended`、`plugin:vue/vue3-essential`、`@vue/typescript/recommended`。相应的校验工具则是 [eslint](https://eslint.org/)。
|
||||
|
||||
#### 代码格式化
|
||||
|
||||
代码格式须遵循前端开发文档的格式规范,相应的约束工具是 [prettier](https://prettier.io/),而配置文件是作为 npm 包,发布至内网仓库中,以供复用:[@gci/prettier-config](http://10.91.120.21:18888/#browse/browse:npm-hosted:%40gci%2Fprettier-config)。
|
||||
|
||||
#### 包管理器
|
||||
|
||||
根据前端开发文档,包管理器统一使用 pnpm。同时,为了强制使用 pnpm,在 `package.json` script 中增加 preinstall 钩子,校验包管理器。
|
||||
|
||||
#### 代码提交门禁
|
||||
|
||||
为保证代码的提交质量,在提交前须进行一些校验活动:提交规范、编码规范校验、代码格式化,如果校验失败,则不允许提交。使用了 [husky](https://typicode.github.io/husky/) 及 [lint-staged](https://www.npmjs.com/package/lint-staged) 完成这部分的工作。
|
||||
|
||||
#### 打包工具
|
||||
|
||||
[Vite](https://v3.vitejs.dev/) 是近年来新出的打包工具,也是开发 Vue 项目的最佳搭配之一。
|
||||
|
||||
#### CI/CD
|
||||
|
||||
TODO 待补充。
|
||||
|
||||
#### 异常性能监控
|
||||
|
||||
TODO 待补充。
|
||||
20
index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="./favicon.ico" type ="image/x-icon">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<%- seoScript %>
|
||||
<%- gciPlayer %>
|
||||
|
||||
<title><%- title %></title>
|
||||
</head>
|
||||
<body class="dark:bg-primary-dark-color">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<script>
|
||||
window.gciplayerMonitor = gciplayerMonitor
|
||||
window.gciplayer = gciplayer
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
4
lint-staged.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
'*.{vue,js,ts,jsx,tsx}': ['eslint --fix', 'prettier --write'],
|
||||
'*.md': ['prettier --write']
|
||||
};
|
||||
58
package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "gci-boot-template",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "vite",
|
||||
"prop": "vite --mode production",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@amap/amap-jsapi-types": "^0.0.13",
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"axios": "^1.4.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.9",
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.3.9",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.1.6",
|
||||
"swiper": "^11.0.3",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue3-print-nb": "^0.1.4",
|
||||
"xgplayer": "^3.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.6.7",
|
||||
"@commitlint/config-conventional": "^17.6.7",
|
||||
"@gci/prettier-config": "^1.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||
"@typescript-eslint/parser": "^6.2.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.2",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^13.2.3",
|
||||
"postcss": "^8.4.28",
|
||||
"prettier": "^3.0.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.66.0",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vue-tsc": "^1.8.5"
|
||||
}
|
||||
}
|
||||
4621
pnpm-lock.yaml
generated
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
1
prettier.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('@gci/prettier-config');
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
10
src/App.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeMode } from '@/hooks/useSystem/index';
|
||||
|
||||
const { defineMode } = useThemeMode();
|
||||
defineMode();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
BIN
src/assets/font/AlibabaPuHuiTi-Regular.ttf
Normal file
9
src/assets/font/index.css
Normal file
@ -0,0 +1,9 @@
|
||||
@font-face {
|
||||
font-family: Alibaba_PuHuiTi;
|
||||
src: url('./AlibabaPuHuiTi-Regular.ttf');
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: Alibaba_PuHuiTi;
|
||||
}
|
||||
12
src/assets/images/arrow-down.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>arrow-down</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-1869.000000, -1004.000000)" fill-rule="nonzero">
|
||||
<g id="arrow-down" transform="translate(1879.000000, 1014.000000) scale(1, -1) translate(-1879.000000, -1014.000000) translate(1869.000000, 1004.000000)">
|
||||
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M10,13.9583398 C9.83333984,13.9583398 9.66666016,13.9166602 9.54166016,13.7916602 L2.875,7.125 C2.625,6.875 2.625,6.5 2.875,6.25 C3.125,6 3.5,6 3.75,6.25 L10,12.4583398 L16.2083398,6.20833984 C16.4583398,5.95833984 16.8333398,5.95833984 17.0833398,6.20833984 C17.3333398,6.45833984 17.3333398,6.83333984 17.0833398,7.08333984 L10.4166602,13.75 C10.3333398,13.9166602 10.1666602,13.9583398 10,13.9583398 Z" id="路径" fill="#666666"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
12
src/assets/images/arrow-up.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>arrow-down</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控展开列表" transform="translate(-1870.000000, -625.000000)" fill-rule="nonzero">
|
||||
<g id="arrow-down" transform="translate(1870.000000, 625.000000)">
|
||||
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M10,13.9583398 C9.83333984,13.9583398 9.66666016,13.9166602 9.54166016,13.7916602 L2.875,7.125 C2.625,6.875 2.625,6.5 2.875,6.25 C3.125,6 3.5,6 3.75,6.25 L10,12.4583398 L16.2083398,6.20833984 C16.4583398,5.95833984 16.8333398,5.95833984 17.0833398,6.20833984 C17.3333398,6.45833984 17.3333398,6.83333984 17.0833398,7.08333984 L10.4166602,13.75 C10.3333398,13.9166602 10.1666602,13.9583398 10,13.9583398 Z" id="路径" fill="#666666"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/background.png
Normal file
|
After Width: | Height: | Size: 541 KiB |
15
src/assets/images/close_play.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="26px" height="26px" viewBox="0 0 26 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 4</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="轨迹回放" transform="translate(-1850.000000, -138.000000)">
|
||||
<g id="编组-4" transform="translate(1850.000000, 138.000000)">
|
||||
<circle id="椭圆形备份-7" stroke="#2C6AF3" fill="#FFFFFF" cx="13" cy="13" r="12.5"></circle>
|
||||
<g id="关闭" transform="translate(6.000000, 6.000000)" fill="#2C6AF3" fill-rule="nonzero">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="14" height="14"></rect>
|
||||
<path d="M7.50575,6.887125 L12.105625,2.2876875 C12.2161317,2.17718079 12.2592895,2.01611374 12.2188412,1.86515876 C12.178393,1.71420379 12.0604837,1.59629453 11.9095287,1.55584626 C11.7585738,1.515398 11.5975067,1.55855578 11.487,1.6690625 L6.887125,6.2685 L2.2876875,1.6690625 C2.17718079,1.55855579 2.01611374,1.515398 1.86515877,1.55584627 C1.71420379,1.59629453 1.59629453,1.71420379 1.55584627,1.86515877 C1.515398,2.01611374 1.55855579,2.17718079 1.6690625,2.2876875 L6.2685,6.887125 L1.6690625,11.4865625 C1.49823392,11.6573911 1.49823392,11.9343589 1.6690625,12.1051875 C1.83989108,12.2760161 2.11685892,12.2760161 2.2876875,12.1051875 L6.887125,7.50575 L11.4865625,12.105625 C11.5970692,12.2161317 11.7581363,12.2592895 11.9090912,12.2188412 C12.0600462,12.178393 12.1779555,12.0604837 12.2184037,11.9095287 C12.258852,11.7585738 12.2156942,11.5975067 12.1051875,11.487 L7.50575,6.887125 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/dashboard_marker.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/images/default-user-avatar.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/assets/images/end_flag.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/images/engine_control.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/images/engine_tem.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/images/home.png
Normal file
|
After Width: | Height: | Size: 393 KiB |
BIN
src/assets/images/icon_question.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/lock_button_icon.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/loginlogo.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/images/logintitle.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
src/assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
src/assets/images/logo_car.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/logo_car_history.png
Normal file
|
After Width: | Height: | Size: 705 B |
BIN
src/assets/images/logo_car_location.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/images/logo_car_location_normal.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/logo_car_location_normal_iswork.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
17
src/assets/images/logo_car_monitor.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>车辆 (3)</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-1207.000000, -592.000000)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<g id="编组-7" transform="translate(1018.000000, 207.000000)">
|
||||
<g id="车辆-(3)" transform="translate(189.000000, 385.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M8.91,19.3333588 C8.83,19.3333588 8.74666016,19.3133398 8.67333984,19.2733398 L5.16,17.3933398 L1.82333984,19.27 C1.66666016,19.3566602 1.48,19.3533398 1.32666016,19.2666797 C1.17388392,19.177339 1.07999053,19.0136413 1.07999053,18.8366602 L1.07999053,2.50333984 C1.07667969,1.49 1.94666016,0.666660156 3.01666016,0.666660156 L14.8066602,0.666660156 C15.8766602,0.666660156 16.7433398,1.49 16.7433398,2.5 L16.7433398,12.0833398 C16.7433398,12.36 16.52,12.5833398 16.2433398,12.5833398 C15.9666797,12.5833398 15.7433398,12.36 15.7433398,12.0833398 L15.7433398,2.5 C15.7433398,2.04 15.3233398,1.66666016 14.8066602,1.66666016 L3.01666016,1.66666016 C2.5,1.66666016 2.07666016,2.04 2.07666016,2.5 L2.07666016,17.98 L4.91,16.39 C5.06,16.3066602 5.24,16.3033398 5.39,16.3866797 L8.89332031,18.2633398 L9.74666016,17.7533398 C9.98435852,17.6130665 10.2906839,17.6903841 10.4333398,17.9266602 C10.5733398,18.1633398 10.4966602,18.47 10.26,18.6133398 L9.16332031,19.2666602 C9.09,19.31 9,19.3333588 8.91,19.3333588 L8.91,19.3333588 Z" id="路径" stroke="#FFFFFF" stroke-width="0.5"></path>
|
||||
<path d="M13.0433398,5.68039426 L4.77666016,5.68039426 C4.5,5.68039426 4.27666016,5.33564708 4.27666016,4.90852721 C4.27666016,4.48140734 4.5,4.13666016 4.77666016,4.13666016 L13.0433594,4.13666016 C13.3200195,4.13666016 13.5433594,4.48143749 13.5433594,4.90852721 C13.5433594,5.33561693 13.3200195,5.68039426 13.0433594,5.68039426 L13.0433398,5.68039426 Z M13.0433398,10.1366602 L4.77666016,10.1366602 C4.5,10.1366602 4.27666016,9.79188282 4.27666016,9.3647931 C4.27666016,8.93770338 4.5,8.59292605 4.77666016,8.59292605 L13.0433594,8.59292605 C13.3200195,8.59292605 13.5433594,8.93767323 13.5433594,9.3647931 C13.5433594,9.79191297 13.3200195,10.1366602 13.0433594,10.1366602 L13.0433398,10.1366602 Z M11.39,16.9333398 C11.6466602,16.9333398 11.8566602,16.7233398 11.8566602,16.4666602 C11.8566602,16.2099805 11.6466797,16 11.39,16 C11.1333203,16 10.9233398,16.21 10.9233398,16.4666602 C10.9233398,16.7233203 11.1333398,16.9333398 11.39,16.9333398 L11.39,16.9333398 Z M16.5,16.9333398 C16.7566602,16.9333398 16.9666602,16.7233398 16.9666602,16.4666602 C16.9666602,16.2099805 16.76,16 16.5,16 C16.2433398,16 16.0333398,16.21 16.0333398,16.4666602 C16.0333398,16.7233203 16.2433398,16.9333398 16.5,16.9333398 L16.5,16.9333398 Z" id="形状"></path>
|
||||
<path d="M16.4066602,19.2966797 C15.76,19.2966797 15.2133398,18.9 15.0566602,18.3633398 L12.8333398,18.3633398 C12.6766602,18.9 12.13,19.2966797 11.4833398,19.2966797 C10.8366797,19.2966797 10.29,18.9 10.1333398,18.3633398 C9.49,18.36 8.97,17.8866602 8.97,17.3033398 L8.97,15.57 C8.97,14.97 9.35,14.4166797 9.95,14.14 L10.4033203,12.5233398 C10.5566602,11.97 11.1133398,11.58 11.7566602,11.58 L16.1633398,11.58 C16.8133398,11.58 17.37,11.9733398 17.52,12.5333203 L17.95,14.14 C18.5433398,14.42 18.9233398,14.9733398 18.9233398,15.57 L18.9233398,17.3033398 C18.9233398,17.8866602 18.4033398,18.36 17.76,18.3633398 C17.6033398,18.9 17.0566797,19.2966797 16.4066797,19.2966797 L16.4066602,19.2966797 Z M12.38,17.3633398 L15.5133398,17.3633398 C15.79,17.3633398 16.0133398,17.5866602 16.0133398,17.8633398 L16.0133398,18.05 C16.0133398,18.1666602 16.1833398,18.2966797 16.41,18.2966797 C16.6366602,18.2966797 16.8066797,18.1666602 16.8066797,18.05 L16.8066797,17.8633398 C16.8066797,17.5866602 17.03,17.3633398 17.3066797,17.3633398 L17.7533398,17.3633398 C17.8533398,17.3633398 17.9166602,17.3166602 17.9266602,17.2966602 L17.9266602,15.5733398 C17.9266602,15.3566602 17.7566602,15.1433398 17.4933398,15.0333398 L17.34,14.97 C17.1966602,14.91 17.09,14.7866602 17.05,14.6366602 L16.5566602,12.7966602 C16.53,12.6933398 16.3766602,12.5833398 16.1666602,12.5833398 L11.7533398,12.5833398 C11.5466602,12.5833398 11.3933398,12.69 11.3633398,12.7933398 L10.8466602,14.64 C10.8066602,14.7866797 10.7,14.9066602 10.5566602,14.9666602 L10.4033398,15.03 C10.14,15.14 9.96666016,15.3533398 9.96666016,15.57 L9.96666016,17.3033398 C9.97333984,17.3133398 10.0366602,17.3633398 10.1366602,17.3633398 L10.5833398,17.3633398 C10.86,17.3633398 11.0833398,17.5866602 11.0833398,17.8633398 L11.0833398,18.05 C11.0833398,18.1666602 11.2533398,18.2966797 11.48,18.2966797 C11.7066602,18.2966797 11.8733398,18.1666602 11.8733398,18.05 L11.8733398,17.8633398 C11.88,17.59 12.1033398,17.3633398 12.38,17.3633398 L12.38,17.3633398 Z" id="形状"></path>
|
||||
<path d="M16.1833398,15.5666602 L11.71,15.5666602 C11.5533398,15.5666602 11.4066797,15.4933398 11.3133203,15.37 C11.22,15.2466602 11.1866602,15.0866602 11.2266602,14.9366602 L11.5966602,13.55 C11.7,13.16 12.08,12.8866602 12.5166602,12.8866602 L15.38,12.8866602 C15.8166602,12.8866602 16.1966602,13.16 16.3,13.55 L16.6666602,14.94 C16.7066602,15.09 16.6733398,15.25 16.58,15.3733398 C16.4866602,15.4966797 16.34,15.5666602 16.1833203,15.5666602 L16.1833398,15.5666602 Z M12.36,14.5666602 L15.5333398,14.5666602 L15.3533398,13.89 L12.54,13.89 L12.36,14.5666602 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
12
src/assets/images/logo_car_state_0.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>车辆管理</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-265.000000, -293.000000)" fill="#B7B7B7" fill-rule="nonzero">
|
||||
<g id="车辆管理" transform="translate(265.000000, 293.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M18.2386719,7.15078125 L16.6105469,7.15078125 C16.5357184,7.15163115 16.4623812,7.17181572 16.3976563,7.209375 L15.6640625,4.97695312 C15.4296875,3.70742187 14.54375,3.09570312 13.4570313,3.09570312 L6.56054688,3.09570312 C5.31191406,3.09570312 4.55390625,3.91757812 4.35351562,4.97617188 L3.61660156,7.21542969 C3.5485776,7.17401 3.47065258,7.15167824 3.39101562,7.15078125 L1.76210937,7.15078125 C1.64127721,7.15129805 1.52560237,7.19980807 1.44054584,7.28563385 C1.3554893,7.37145963 1.30802268,7.48756652 1.30858866,7.60839844 L1.30858866,8.00605469 C1.30772852,8.25751573 1.51065016,8.46218518 1.76210938,8.46347656 L2.37910156,8.56992187 C2.18378906,8.95410156 2.07285156,9.39355469 2.07285156,9.90449219 L1.84511719,12.5390625 C1.84543543,12.6168988 1.8504576,12.6946447 1.86015625,12.771875 C1.85056163,12.8207823 1.84552702,12.8704746 1.84511719,12.9203125 L1.84511719,16.0878906 C1.84418277,16.3034832 1.92893149,16.5106167 2.08071918,16.663723 C2.23250687,16.8168293 2.43889956,16.9033665 2.65449219,16.9042969 L3.88964844,16.9042969 C4.10511982,16.9032635 4.31135231,16.816662 4.46296258,16.6635499 C4.61457285,16.5104378 4.69913734,16.3033617 4.69804687,16.0878906 L4.69804687,15.0697266 L15.3167969,15.0697266 L15.3167969,16.0878906 C15.3158625,16.3034832 15.4006112,16.5106167 15.5523989,16.663723 C15.7041866,16.8168293 15.9105792,16.9033665 16.1261719,16.9042969 L17.3615234,16.9042969 C17.5770983,16.9033666 17.783471,16.8168232 17.9352277,16.6637112 C18.0869843,16.5105992 18.1716895,16.3034652 18.1707031,16.0878906 L18.1707031,12.9195313 C18.1702576,12.8696953 18.1652233,12.8200063 18.1556641,12.7710938 C18.1649744,12.693827 18.1699951,12.6161037 18.1707031,12.5382813 L17.9419922,9.90449219 C17.9419922,9.39179688 17.8308594,8.95253906 17.6351562,8.56679688 L18.2386719,8.46347656 C18.4900374,8.46207802 18.6927042,8.25722545 18.6914184,8.00585938 L18.6914184,7.60839844 C18.6922898,7.48760749 18.6450313,7.37143796 18.5600763,7.28556668 C18.4751213,7.19969539 18.3594654,7.15119348 18.2386719,7.15078125 Z M4.896875,6.68203125 L5.43574219,5.23378906 L5.44355469,5.2 C5.49726562,4.90703125 5.6171875,4.85527344 5.79375,4.62871094 L14.2324219,4.62871094 C14.4115234,4.86132812 14.5226562,4.92050781 14.5714844,5.19667969 L15.1183594,6.68105469 L15.31875,7.48789063 C15.2738281,8.07871094 14.5708984,8.54472656 13.9857422,8.54472656 L6.0296875,8.54472656 C5.44375,8.54472656 4.740625,8.07871094 4.69570312,7.48789063 L4.896875,6.68203125 Z M5.27480469,13.5691406 C4.59804688,13.5691406 4.04980469,13.0152344 4.04980469,12.3322266 C4.04980469,11.6492187 4.59804688,11.0953125 5.27480469,11.0953125 C5.9515625,11.0953125 6.49960937,11.6482422 6.49960937,12.3322266 C6.49960937,13.0162109 5.95136719,13.5691406 5.27480469,13.5691406 Z M12.1183627,12.65625 C12.1187746,12.7534586 12.0805029,12.8468394 12.0119856,12.9157964 C11.9434684,12.9847533 11.8503342,13.0236215 11.753125,13.0238281 L8.315625,13.0238281 C8.11373991,13.0226448 7.95097956,12.8581362 7.95194878,12.65625 L7.95194878,11.6210938 C7.95164187,11.5241419 7.98987094,11.4310401 8.05822485,11.3622833 C8.12657877,11.2935264 8.21945422,11.2547508 8.31640625,11.2544922 L11.7539063,11.2544922 C11.9557144,11.255246 12.1187918,11.4192847 12.1183627,11.6210938 L12.1183627,12.65625 Z M14.7410156,13.5689453 C14.0642578,13.5689453 13.5154297,13.0150391 13.5154297,12.3320312 C13.5154297,11.6490234 14.0642578,11.0951172 14.7410156,11.0951172 C15.4177734,11.0951172 15.9660156,11.6480469 15.9660156,12.3320312 C15.9660156,13.0160156 15.4177734,13.5689453 14.7410156,13.5689453 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
12
src/assets/images/logo_car_state_1.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>车辆管理</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-265.000000, -327.000000)" fill="#53CB24" fill-rule="nonzero">
|
||||
<g id="车辆管理" transform="translate(265.000000, 327.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M18.2386719,7.15078125 L16.6105469,7.15078125 C16.5357184,7.15163115 16.4623812,7.17181572 16.3976563,7.209375 L15.6640625,4.97695312 C15.4296875,3.70742187 14.54375,3.09570312 13.4570313,3.09570312 L6.56054688,3.09570312 C5.31191406,3.09570312 4.55390625,3.91757812 4.35351562,4.97617188 L3.61660156,7.21542969 C3.5485776,7.17401 3.47065258,7.15167824 3.39101562,7.15078125 L1.76210937,7.15078125 C1.64127721,7.15129805 1.52560237,7.19980807 1.44054584,7.28563385 C1.3554893,7.37145963 1.30802268,7.48756652 1.30858866,7.60839844 L1.30858866,8.00605469 C1.30772852,8.25751573 1.51065016,8.46218518 1.76210938,8.46347656 L2.37910156,8.56992187 C2.18378906,8.95410156 2.07285156,9.39355469 2.07285156,9.90449219 L1.84511719,12.5390625 C1.84543543,12.6168988 1.8504576,12.6946447 1.86015625,12.771875 C1.85056163,12.8207823 1.84552702,12.8704746 1.84511719,12.9203125 L1.84511719,16.0878906 C1.84418277,16.3034832 1.92893149,16.5106167 2.08071918,16.663723 C2.23250687,16.8168293 2.43889956,16.9033665 2.65449219,16.9042969 L3.88964844,16.9042969 C4.10511982,16.9032635 4.31135231,16.816662 4.46296258,16.6635499 C4.61457285,16.5104378 4.69913734,16.3033617 4.69804687,16.0878906 L4.69804687,15.0697266 L15.3167969,15.0697266 L15.3167969,16.0878906 C15.3158625,16.3034832 15.4006112,16.5106167 15.5523989,16.663723 C15.7041866,16.8168293 15.9105792,16.9033665 16.1261719,16.9042969 L17.3615234,16.9042969 C17.5770983,16.9033666 17.783471,16.8168232 17.9352277,16.6637112 C18.0869843,16.5105992 18.1716895,16.3034652 18.1707031,16.0878906 L18.1707031,12.9195313 C18.1702576,12.8696953 18.1652233,12.8200063 18.1556641,12.7710938 C18.1649744,12.693827 18.1699951,12.6161037 18.1707031,12.5382813 L17.9419922,9.90449219 C17.9419922,9.39179688 17.8308594,8.95253906 17.6351562,8.56679688 L18.2386719,8.46347656 C18.4900374,8.46207802 18.6927042,8.25722545 18.6914184,8.00585938 L18.6914184,7.60839844 C18.6922898,7.48760749 18.6450313,7.37143796 18.5600763,7.28556668 C18.4751213,7.19969539 18.3594654,7.15119348 18.2386719,7.15078125 Z M4.896875,6.68203125 L5.43574219,5.23378906 L5.44355469,5.2 C5.49726562,4.90703125 5.6171875,4.85527344 5.79375,4.62871094 L14.2324219,4.62871094 C14.4115234,4.86132812 14.5226562,4.92050781 14.5714844,5.19667969 L15.1183594,6.68105469 L15.31875,7.48789063 C15.2738281,8.07871094 14.5708984,8.54472656 13.9857422,8.54472656 L6.0296875,8.54472656 C5.44375,8.54472656 4.740625,8.07871094 4.69570312,7.48789063 L4.896875,6.68203125 Z M5.27480469,13.5691406 C4.59804688,13.5691406 4.04980469,13.0152344 4.04980469,12.3322266 C4.04980469,11.6492187 4.59804688,11.0953125 5.27480469,11.0953125 C5.9515625,11.0953125 6.49960937,11.6482422 6.49960937,12.3322266 C6.49960937,13.0162109 5.95136719,13.5691406 5.27480469,13.5691406 Z M12.1183627,12.65625 C12.1187746,12.7534586 12.0805029,12.8468394 12.0119856,12.9157964 C11.9434684,12.9847533 11.8503342,13.0236215 11.753125,13.0238281 L8.315625,13.0238281 C8.11373991,13.0226448 7.95097956,12.8581362 7.95194878,12.65625 L7.95194878,11.6210938 C7.95164187,11.5241419 7.98987094,11.4310401 8.05822485,11.3622833 C8.12657877,11.2935264 8.21945422,11.2547508 8.31640625,11.2544922 L11.7539063,11.2544922 C11.9557144,11.255246 12.1187918,11.4192847 12.1183627,11.6210938 L12.1183627,12.65625 Z M14.7410156,13.5689453 C14.0642578,13.5689453 13.5154297,13.0150391 13.5154297,12.3320312 C13.5154297,11.6490234 14.0642578,11.0951172 14.7410156,11.0951172 C15.4177734,11.0951172 15.9660156,11.6480469 15.9660156,12.3320312 C15.9660156,13.0160156 15.4177734,13.5689453 14.7410156,13.5689453 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
12
src/assets/images/logo_car_state_2.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>车辆管理</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-265.000000, -463.000000)" fill="#EA4955" fill-rule="nonzero">
|
||||
<g id="车辆管理" transform="translate(265.000000, 463.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M18.2386719,7.15078125 L16.6105469,7.15078125 C16.5357184,7.15163115 16.4623812,7.17181572 16.3976563,7.209375 L15.6640625,4.97695312 C15.4296875,3.70742187 14.54375,3.09570312 13.4570313,3.09570312 L6.56054688,3.09570312 C5.31191406,3.09570312 4.55390625,3.91757812 4.35351562,4.97617188 L3.61660156,7.21542969 C3.5485776,7.17401 3.47065258,7.15167824 3.39101562,7.15078125 L1.76210937,7.15078125 C1.64127721,7.15129805 1.52560237,7.19980807 1.44054584,7.28563385 C1.3554893,7.37145963 1.30802268,7.48756652 1.30858866,7.60839844 L1.30858866,8.00605469 C1.30772852,8.25751573 1.51065016,8.46218518 1.76210938,8.46347656 L2.37910156,8.56992187 C2.18378906,8.95410156 2.07285156,9.39355469 2.07285156,9.90449219 L1.84511719,12.5390625 C1.84543543,12.6168988 1.8504576,12.6946447 1.86015625,12.771875 C1.85056163,12.8207823 1.84552702,12.8704746 1.84511719,12.9203125 L1.84511719,16.0878906 C1.84418277,16.3034832 1.92893149,16.5106167 2.08071918,16.663723 C2.23250687,16.8168293 2.43889956,16.9033665 2.65449219,16.9042969 L3.88964844,16.9042969 C4.10511982,16.9032635 4.31135231,16.816662 4.46296258,16.6635499 C4.61457285,16.5104378 4.69913734,16.3033617 4.69804687,16.0878906 L4.69804687,15.0697266 L15.3167969,15.0697266 L15.3167969,16.0878906 C15.3158625,16.3034832 15.4006112,16.5106167 15.5523989,16.663723 C15.7041866,16.8168293 15.9105792,16.9033665 16.1261719,16.9042969 L17.3615234,16.9042969 C17.5770983,16.9033666 17.783471,16.8168232 17.9352277,16.6637112 C18.0869843,16.5105992 18.1716895,16.3034652 18.1707031,16.0878906 L18.1707031,12.9195313 C18.1702576,12.8696953 18.1652233,12.8200063 18.1556641,12.7710938 C18.1649744,12.693827 18.1699951,12.6161037 18.1707031,12.5382813 L17.9419922,9.90449219 C17.9419922,9.39179688 17.8308594,8.95253906 17.6351562,8.56679688 L18.2386719,8.46347656 C18.4900374,8.46207802 18.6927042,8.25722545 18.6914184,8.00585938 L18.6914184,7.60839844 C18.6922898,7.48760749 18.6450313,7.37143796 18.5600763,7.28556668 C18.4751213,7.19969539 18.3594654,7.15119348 18.2386719,7.15078125 Z M4.896875,6.68203125 L5.43574219,5.23378906 L5.44355469,5.2 C5.49726562,4.90703125 5.6171875,4.85527344 5.79375,4.62871094 L14.2324219,4.62871094 C14.4115234,4.86132812 14.5226562,4.92050781 14.5714844,5.19667969 L15.1183594,6.68105469 L15.31875,7.48789063 C15.2738281,8.07871094 14.5708984,8.54472656 13.9857422,8.54472656 L6.0296875,8.54472656 C5.44375,8.54472656 4.740625,8.07871094 4.69570312,7.48789063 L4.896875,6.68203125 Z M5.27480469,13.5691406 C4.59804688,13.5691406 4.04980469,13.0152344 4.04980469,12.3322266 C4.04980469,11.6492187 4.59804688,11.0953125 5.27480469,11.0953125 C5.9515625,11.0953125 6.49960937,11.6482422 6.49960937,12.3322266 C6.49960937,13.0162109 5.95136719,13.5691406 5.27480469,13.5691406 Z M12.1183627,12.65625 C12.1187746,12.7534586 12.0805029,12.8468394 12.0119856,12.9157964 C11.9434684,12.9847533 11.8503342,13.0236215 11.753125,13.0238281 L8.315625,13.0238281 C8.11373991,13.0226448 7.95097956,12.8581362 7.95194878,12.65625 L7.95194878,11.6210938 C7.95164187,11.5241419 7.98987094,11.4310401 8.05822485,11.3622833 C8.12657877,11.2935264 8.21945422,11.2547508 8.31640625,11.2544922 L11.7539063,11.2544922 C11.9557144,11.255246 12.1187918,11.4192847 12.1183627,11.6210938 L12.1183627,12.65625 Z M14.7410156,13.5689453 C14.0642578,13.5689453 13.5154297,13.0150391 13.5154297,12.3320312 C13.5154297,11.6490234 14.0642578,11.0951172 14.7410156,11.0951172 C15.4177734,11.0951172 15.9660156,11.6480469 15.9660156,12.3320312 C15.9660156,13.0160156 15.4177734,13.5689453 14.7410156,13.5689453 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
BIN
src/assets/images/logo_car_state_3.jpg
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
14
src/assets/images/logo_car_track.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>轨迹-01</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-1048.000000, -592.000000)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<g id="编组-7" transform="translate(1018.000000, 207.000000)">
|
||||
<g id="轨迹-01" transform="translate(30.000000, 385.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M16.48,14.88 L3.52,14.88 C3.2,14.88 2.92,14.64 2.92,14.28 L2.92,14.04 C2.92,13.72 3.16,13.44 3.52,13.44 L6.04,13.44 C6.44,13.44 6.8,13.12 6.8,12.68 C6.8,12.24 6.48,12 6.08,12 L3.52,12 C2.36,12 1.44,12.92 1.44,14.08 L1.44,14.32 C1.44,15.48 2.36,16.4 3.52,16.4 L16.44,16.4 C16.76,16.4 17.04,16.64 17.04,17 L17.04,17.2 C17.04,17.52 16.8,17.8 16.44,17.8 L5.76,17.8 C5.36,17.8 5,18.12 5,18.56 C5,18.96 5.32,19.32 5.76,19.32 L16.44,19.32 C17.6,19.32 18.52,18.4 18.52,17.24 L18.52,17 C18.56,15.8 17.6,14.88 16.48,14.88 Z M10,0.72 C7.32,0.72 5.16,2.88 5.16,5.56 C5.16,8.24 8.44,12.96 10,12.96 C12,12.96 14.84,8.24 14.84,5.56 C14.84,2.88 12.64,0.72 10,0.72 Z M10,8 C8.64,8 7.52,6.88 7.52,5.52 C7.52,4.16 8.64,3.04 10,3.04 C11.36,3.04 12.48,4.16 12.48,5.52 C12.48,6.88 11.36,8 10,8 L10,8 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/logo_flag.png
Normal file
|
After Width: | Height: | Size: 319 B |
14
src/assets/images/logo_list.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>单列列表</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-252.000000, -1006.000000)" fill-rule="nonzero">
|
||||
<g id="编组-3" transform="translate(248.000000, 1002.000000)">
|
||||
<g id="单列列表" transform="translate(4.500000, 4.500000)">
|
||||
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="15" height="15"></rect>
|
||||
<path d="M12.4379883,1.875 L2.56201172,1.875 C2.18261719,1.875 1.875,2.18261719 1.875,2.56201172 L1.875,3.06298828 C1.875,3.44238281 2.18261719,3.75 2.56201172,3.75 L12.4379883,3.75 C12.8173828,3.75 13.125,3.44238281 13.125,3.06298828 L13.125,2.56201172 C13.125,2.18261719 12.8173828,1.875 12.4379883,1.875 L12.4379883,1.875 Z M12.4379883,11.25 L2.56201172,11.25 C2.18261719,11.25 1.875,11.5576172 1.875,11.9370117 L1.875,12.4379883 C1.875,12.8173828 2.18261719,13.125 2.56201172,13.125 L12.4379883,13.125 C12.8173828,13.125 13.125,12.8173828 13.125,12.4379883 L13.125,11.9370117 C13.125,11.5576172 12.8173828,11.25 12.4379883,11.25 Z M12.4379883,6.5625 L2.56201172,6.5625 C2.18261719,6.5625 1.875,6.87011719 1.875,7.24951172 L1.875,7.75048828 C1.875,8.12988281 2.18261719,8.4375 2.56201172,8.4375 L12.4379883,8.4375 C12.8173828,8.4375 13.125,8.12988281 13.125,7.75048828 L13.125,7.24951172 C13.125,6.87011719 12.8173828,6.5625 12.4379883,6.5625 L12.4379883,6.5625 Z" id="形状" fill="#9CB1E3"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
12
src/assets/images/logo_location.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>轨迹-01</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控展开列表" transform="translate(-233.000000, -717.000000)" fill="#2C6AF3" fill-rule="nonzero">
|
||||
<g id="轨迹-01" transform="translate(233.000000, 717.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M16.48,14.88 L3.52,14.88 C3.2,14.88 2.92,14.64 2.92,14.28 L2.92,14.04 C2.92,13.72 3.16,13.44 3.52,13.44 L6.04,13.44 C6.44,13.44 6.8,13.12 6.8,12.68 C6.8,12.24 6.48,12 6.08,12 L3.52,12 C2.36,12 1.44,12.92 1.44,14.08 L1.44,14.32 C1.44,15.48 2.36,16.4 3.52,16.4 L16.44,16.4 C16.76,16.4 17.04,16.64 17.04,17 L17.04,17.2 C17.04,17.52 16.8,17.8 16.44,17.8 L5.76,17.8 C5.36,17.8 5,18.12 5,18.56 C5,18.96 5.32,19.32 5.76,19.32 L16.44,19.32 C17.6,19.32 18.52,18.4 18.52,17.24 L18.52,17 C18.56,15.8 17.6,14.88 16.48,14.88 Z M10,0.72 C7.32,0.72 5.16,2.88 5.16,5.56 C5.16,8.24 8.44,12.96 10,12.96 C12,12.96 14.84,8.24 14.84,5.56 C14.84,2.88 12.64,0.72 10,0.72 Z M10,8 C8.64,8 7.52,6.88 7.52,5.52 C7.52,4.16 8.64,3.04 10,3.04 C11.36,3.04 12.48,4.16 12.48,5.52 C12.48,6.88 11.36,8 10,8 L10,8 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
15
src/assets/images/logo_panel_close.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>关闭</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控" transform="translate(-1309.000000, -216.000000)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<g id="编组-7" transform="translate(1018.000000, 207.000000)">
|
||||
<g id="关闭" transform="translate(291.000000, 9.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M9.98697266,2.48697266 C11.0000195,2.48697266 11.9820898,2.68509766 12.9059375,3.07583984 C13.7988867,3.45351562 14.6010937,3.99447266 15.2902734,4.68367187 C15.9794727,5.37285156 16.5204102,6.17505859 16.8981055,7.06800781 C17.2888477,7.99183594 17.4869727,8.97392578 17.4869727,9.98697266 C17.4869727,11.0000195 17.2888477,11.9820898 16.8981055,12.9059375 C16.5204297,13.7988867 15.9794727,14.6010938 15.2902734,15.2902734 C14.6010937,15.9794727 13.7988867,16.5204102 12.9059375,16.8981055 C11.9821094,17.2888477 11.0000195,17.4869727 9.98697266,17.4869727 C8.97392578,17.4869727 7.99185547,17.2888477 7.06800781,16.8981055 C6.17505859,16.5204297 5.37285156,15.9794727 4.68367187,15.2902734 C3.99447266,14.6010938 3.45353516,13.7988867 3.07583984,12.9059375 C2.68509766,11.9821094 2.48697266,11.0000195 2.48697266,9.98697266 C2.48697266,8.97392578 2.68509766,7.99185547 3.07583984,7.06800781 C3.45351562,6.17505859 3.99447266,5.37285156 4.68367187,4.68367187 C5.37285156,3.99447266 6.17505859,3.45353516 7.06800781,3.07583984 C7.99185547,2.68509766 8.97392578,2.48697266 9.98697266,2.48697266 M9.98697266,1.23697266 C5.15447266,1.23697266 1.23697266,5.15447266 1.23697266,9.98697266 C1.23697266,14.8194727 5.15447266,18.7369727 9.98697266,18.7369727 C14.8194727,18.7369727 18.7369727,14.8194727 18.7369727,9.98697266 C18.7369727,5.15447266 14.8194727,1.23697266 9.98697266,1.23697266 L9.98697266,1.23697266 Z" id="形状"></path>
|
||||
<path d="M10.8838672,10 L13.7622461,7.12162109 C14.0063281,6.87753906 14.0063281,6.48181641 13.7622461,6.23773438 C13.5181836,5.99365234 13.1224414,5.99365234 12.8783789,6.23773438 L10,9.11613281 L7.12162109,6.23775391 C6.87753906,5.99367187 6.48181641,5.99367187 6.23773438,6.23775391 C5.99365234,6.48183594 5.99365234,6.87755859 6.23773438,7.12164062 L9.11613281,10 L6.23775391,12.8783789 C5.99367187,13.1224414 5.99367187,13.5181836 6.23775391,13.7622461 C6.35978516,13.8842969 6.51974609,13.9453125 6.6796875,13.9453125 C6.83962891,13.9453125 6.99958984,13.8842969 7.12162109,13.7622461 L10,10.8838672 L12.8783789,13.7622461 C13.0004102,13.8842969 13.1603711,13.9453125 13.3203125,13.9453125 C13.4802539,13.9453125 13.6402148,13.8842969 13.7622461,13.7622461 C14.0063281,13.5181836 14.0063281,13.1224414 13.7622461,12.8783789 L10.8838672,10 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
14
src/assets/images/logo_play_reset.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="26px" height="26px" viewBox="0 0 26 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 4备份</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="轨迹回放" transform="translate(-1853.000000, -135.000000)">
|
||||
<g id="编组-9" transform="translate(1626.000000, 123.000000)">
|
||||
<g id="编组-4备份" transform="translate(227.000000, 12.000000)">
|
||||
<circle id="椭圆形" stroke="#2C6AF3" fill="#FFFFFF" cx="13" cy="13" r="12.5"></circle>
|
||||
<rect id="矩形" fill="#2C6AF3" x="8" y="8" width="10" height="10" rx="2"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 818 B |
BIN
src/assets/images/logo_power.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/images/logo_power_history.png
Normal file
|
After Width: | Height: | Size: 740 B |
12
src/assets/images/logo_send_message.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon_zhanneixin_solid备份 3</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="全局监控展开列表" transform="translate(-269.000000, -717.000000)" fill="#2C6AF3" fill-rule="nonzero">
|
||||
<g id="icon_zhanneixin_solid备份-3" transform="translate(269.000000, 717.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="19.9610136" height="19.9610136"></rect>
|
||||
<path d="M18.5425341,2.50136452 L1.51578947,9.19703704 C1.36900833,9.26929645 1.26910226,9.41129984 1.25067515,9.57386235 C1.23224803,9.73642486 1.29783434,9.89718749 1.42471735,10.0004678 L4.13692008,12.1213255 L15.5546199,5.31711501 L5.89598441,13.4562183 L5.89598441,16.7547758 C5.88552611,16.8551277 5.93418424,16.952444 6.02074074,17.0042885 C6.10423606,17.0538017 6.20886907,17.050395 6.28896686,16.9955556 L8.48592593,15.3163353 L11.4152047,17.3847953 C11.5061435,17.4561425 11.6249463,17.4814687 11.737076,17.4534113 C11.85009,17.4262945 11.9449174,17.3497911 11.9953216,17.2450682 L18.697232,2.71968811 C18.757115,2.60366472 18.6548148,2.46393762 18.5425341,2.50136452 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
13
src/assets/images/logo_time.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>时间</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="轨迹回放" transform="translate(-432.000000, -275.000000)" fill="#2C6AF3" fill-rule="nonzero">
|
||||
<g id="时间" transform="translate(432.000000, 275.000000)">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="14" height="14"></rect>
|
||||
<path d="M6.99088086,1.74088086 C7.70001367,1.74088086 8.38746289,1.87956836 9.03415625,2.15308789 C9.6592207,2.41746094 10.2207656,2.79613086 10.7031914,3.27857031 C11.1856309,3.76099609 11.5642871,4.32254102 11.8286738,4.94760547 C12.1021934,5.59428516 12.2408809,6.28174805 12.2408809,6.99088086 C12.2408809,7.70001367 12.1021934,8.38746289 11.8286738,9.03415625 C11.5643008,9.6592207 11.1856309,10.2207656 10.7031914,10.7031914 C10.2207656,11.1856309 9.6592207,11.5642871 9.03415625,11.8286738 C8.38747656,12.1021934 7.70001367,12.2408809 6.99088086,12.2408809 C6.28174805,12.2408809 5.59429883,12.1021934 4.94760547,11.8286738 C4.32254102,11.5643008 3.76099609,11.1856309 3.27857031,10.7031914 C2.79613086,10.2207656 2.41747461,9.6592207 2.15308789,9.03415625 C1.87956836,8.38747656 1.74088086,7.70001367 1.74088086,6.99088086 C1.74088086,6.28174805 1.87956836,5.59429883 2.15308789,4.94760547 C2.41746094,4.32254102 2.79613086,3.76099609 3.27857031,3.27857031 C3.76099609,2.79613086 4.32254102,2.41747461 4.94760547,2.15308789 C5.59429883,1.87956836 6.28174805,1.74088086 6.99088086,1.74088086 M6.99088086,0.865880859 C3.60813086,0.865880859 0.865880859,3.60813086 0.865880859,6.99088086 C0.865880859,10.3736309 3.60813086,13.1158809 6.99088086,13.1158809 C10.3736309,13.1158809 13.1158809,10.3736309 13.1158809,6.99088086 C13.1158809,3.60813086 10.3736309,0.865880859 6.99088086,0.865880859 L6.99088086,0.865880859 Z" id="形状"></path>
|
||||
<path d="M9.60935938,7.42838086 L6.99088086,7.42838086 C6.74925781,7.42838086 6.55338086,7.23251758 6.55338086,6.99088086 L6.55338086,3.07074414 C6.55338086,2.82912109 6.74925781,2.63324414 6.99088086,2.63324414 C7.23250391,2.63324414 7.42838086,2.82912109 7.42838086,3.07074414 L7.42838086,6.55338086 L9.6093457,6.55338086 C9.85096875,6.55338086 10.0468457,6.74925781 10.0468457,6.99088086 C10.0468457,7.23250391 9.85098242,7.42838086 9.60935938,7.42838086 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
39
src/assets/images/menu-horizontal.svg
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 5</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
|
||||
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="setting-copy-2" transform="translate(-1254.000000, -337.000000)">
|
||||
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
|
||||
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 338.000000)">
|
||||
<mask id="mask-3" fill="white">
|
||||
<use xlink:href="#path-2"></use>
|
||||
</mask>
|
||||
<g id="Rectangle-18">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
|
||||
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
<rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
40
src/assets/images/menu-vertical.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 5 Copy 5</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
|
||||
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
|
||||
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
|
||||
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
|
||||
<mask id="mask-3" fill="white">
|
||||
<use xlink:href="#path-2"></use>
|
||||
</mask>
|
||||
<g id="Rectangle-18">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
|
||||
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
|
||||
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/images/start_flag.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
15
src/assets/images/start_play.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="26px" height="26px" viewBox="0 0 26 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 5</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="轨迹回放点击行驶轨迹" transform="translate(-1853.000000, -135.000000)">
|
||||
<g id="编组-5" transform="translate(1853.000000, 135.000000)">
|
||||
<circle id="椭圆形" fill="#2C6AF3" cx="13" cy="13" r="13"></circle>
|
||||
<g id="暂停" transform="translate(3.000000, 3.000000)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<rect id="矩形" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M5.25335938,2.23275391 C6.68328125,2.23275391 7.84242188,3.39191406 7.84242188,4.82183594 L7.84242188,15.1781445 C7.84242188,16.6080859 6.68328125,17.7672266 5.25335938,17.7672266 C3.8234375,17.7672266 2.66429687,16.6080859 2.66429687,15.1781641 L2.66429687,4.82183594 C2.66429687,3.39189453 3.8234375,2.23275391 5.25335938,2.23275391 L5.25335938,2.23275391 Z M14.7466406,2.23275391 C16.1765625,2.23275391 17.3357031,3.39191406 17.3357031,4.82183594 L17.3357031,15.1781445 C17.3357031,16.6080859 16.1765625,17.7672266 14.7466406,17.7672266 C13.3167188,17.7672266 12.1575781,16.6080859 12.1575781,15.1781641 L12.1575781,4.82183594 C12.1575781,3.39189453 13.3167188,2.23275391 14.7466406,2.23275391 L14.7466406,2.23275391 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
15
src/assets/images/stop_play.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="26px" height="26px" viewBox="0 0 26 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组 4</title>
|
||||
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="轨迹回放" transform="translate(-1816.000000, -138.000000)">
|
||||
<g id="编组-4" transform="translate(1816.000000, 138.000000)">
|
||||
<circle id="椭圆形" fill="#2C6AF3" cx="13" cy="13" r="13"></circle>
|
||||
<g id="播放器-播放_44" transform="translate(4.000000, 3.000000)" fill-rule="nonzero">
|
||||
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||
<path d="M14.5454492,10.7727344 L6.36363281,15.5454492 C5.77273438,15.909082 5,15.4545508 5,14.7727344 L5,5.22726562 C5,4.54544922 5.77273438,4.09091797 6.36363281,4.45455078 L14.5454688,9.22726562 C15.1363672,9.54544922 15.1363672,10.4545508 14.5454688,10.7727344 L14.5454492,10.7727344 Z" id="路径" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/track_button_icon.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/images/vedio_play.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/images/wifi-online.png
Normal file
|
After Width: | Height: | Size: 284 B |
BIN
src/assets/images/wifi.png
Normal file
|
After Width: | Height: | Size: 275 B |
BIN
src/assets/images/working_car.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/working_car_history.png
Normal file
|
After Width: | Height: | Size: 681 B |
46
src/assets/style/css/base.theme.css
Normal file
@ -0,0 +1,46 @@
|
||||
@charset "UTF-8";
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: Alibaba_PuHuiTi, sans-serif, -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: scroll;
|
||||
overflow: overlay;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* 滚动条的宽高 */
|
||||
/*定义滚动条轨道*/
|
||||
/* 滚动条的滑块 */
|
||||
}
|
||||
html *::-webkit-scrollbar,
|
||||
body *::-webkit-scrollbar,
|
||||
#app *::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 6px;
|
||||
}
|
||||
html *::-webkit-scrollbar-track,
|
||||
body *::-webkit-scrollbar-track,
|
||||
#app *::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
border-radius: 2em;
|
||||
}
|
||||
html *::-webkit-scrollbar-thumb,
|
||||
body *::-webkit-scrollbar-thumb,
|
||||
#app *::-webkit-scrollbar-thumb {
|
||||
opacity: 1;
|
||||
background-color: #cecece;
|
||||
-webkit-transition: opacity 800ms linear;
|
||||
transition: opacity 800ms linear;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.amap-copyright, .amap-logo {
|
||||
display: none !important;
|
||||
}/*# sourceMappingURL=base.theme.css.map */
|
||||
1
src/assets/style/css/base.theme.css.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"sources":["base.theme.css","../theme/base.theme.scss"],"names":[],"mappings":"AAAA,gBAAgB;ACmBhB;EACE,sBAAA;EACA,gMApBA;ADGF;;ACoBA;EACE,gBAAA;EACA,iBAAA;ADjBF;;ACoBA;;;EAGE,YAAA;EACA,SAAA;EACA,UAAA;EACA,WAAA;EAMA,UAAA;EAQA,WAAA;AD7BF;ACgBE;;;EACE,UAvBa;EAwBb,WAvBc;ADWlB;ACgBE;;;EACE,6BAAA;EAGA,kBAAA;ADZJ;ACgBE;;;EACE,UAAA;EACA,yBApCa;EAqCb,wCAAA;EAAA,gCAAA;EACA,YAAA;ADZJ;;ACgBA;EACE,wBAAA;ADbF","file":"base.theme.css"}
|
||||
3
src/assets/style/tailwind.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
71
src/assets/style/theme/base.theme.scss
Normal file
@ -0,0 +1,71 @@
|
||||
$main-fontFamily:
|
||||
Alibaba_PuHuiTi,
|
||||
sans-serif,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Helvetica Neue,
|
||||
PingFang SC,
|
||||
Microsoft YaHei,
|
||||
Source Han Sans SC,
|
||||
Noto Sans CJK SC,
|
||||
WenQuanYi Micro Hei,
|
||||
sans-serif;
|
||||
|
||||
// 滚动条配置
|
||||
$scroller-width: 6px;
|
||||
$scroller-height: 6px;
|
||||
$scroller-color: #9093994c;
|
||||
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: $main-fontFamily;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: scroll;
|
||||
overflow: overlay;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* 滚动条的宽高 */
|
||||
*::-webkit-scrollbar {
|
||||
width: $scroller-width;
|
||||
height: $scroller-height;
|
||||
}
|
||||
|
||||
/*定义滚动条轨道*/
|
||||
*::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
-webkit-border-radius: 2em;
|
||||
-moz-border-radius: 2em;
|
||||
border-radius: 2em;
|
||||
}
|
||||
|
||||
/* 滚动条的滑块 */
|
||||
*::-webkit-scrollbar-thumb {
|
||||
opacity: 1;
|
||||
background-color: $scroller-color;
|
||||
transition: opacity 800ms linear;
|
||||
border-radius: 12px;
|
||||
z-index: 200;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #90939981;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.amap-copyright, .amap-logo {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.host-main-header__user-popover {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
40
src/assets/style/theme/element.dark.scss
Normal file
@ -0,0 +1,40 @@
|
||||
// 暗黑模式
|
||||
@forward 'element-plus/theme-chalk/src/dark/var.scss' with (
|
||||
$bg-color: (
|
||||
'': #3d3d3d,
|
||||
'overlay': #313131
|
||||
),
|
||||
$colors: (
|
||||
'primary': (
|
||||
'base': #2c6af3
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *;
|
||||
|
||||
html.dark {
|
||||
.el-dialog {
|
||||
--el-dialog-bg-color: #333;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--el-table-header-text-color: #fff;
|
||||
}
|
||||
|
||||
.host-main-body__aside .el-sub-menu {
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.el-popper.is-dark {
|
||||
background-color: #061a3c;
|
||||
border-color: rgba(23, 154, 255, 0.4);
|
||||
|
||||
}
|
||||
|
||||
.el-popper.is-dark .el-popper__arrow::before {
|
||||
background-color: #061a3c;
|
||||
border-color: rgba(23, 154, 255, 0.4);
|
||||
}
|
||||
|
||||
107
src/assets/style/theme/element.theme.scss
Normal file
@ -0,0 +1,107 @@
|
||||
// @style/element.style.scss
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'primary': (
|
||||
'base': #2c6af3
|
||||
)
|
||||
),
|
||||
$fill-color: (
|
||||
'light': #f4f7ff
|
||||
),
|
||||
$bg-color: (
|
||||
'': '#f5f7fb',
|
||||
'overlay': '#fff'
|
||||
),
|
||||
$dialog: (
|
||||
'bg-color': '#fff'
|
||||
)
|
||||
);
|
||||
|
||||
// 导入所有样式:
|
||||
@use 'element-plus/theme-chalk/src/index.scss' as *;
|
||||
|
||||
.el-dialog {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.el-popover.el-popper {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.el-table__body-wrapper tr td.el-table-fixed-column--left,
|
||||
.el-table__body-wrapper tr td.el-table-fixed-column--right,
|
||||
.el-table__body-wrapper tr th.el-table-fixed-column--left,
|
||||
.el-table__body-wrapper tr th.el-table-fixed-column--right,
|
||||
.el-table__footer-wrapper tr td.el-table-fixed-column--left,
|
||||
.el-table__footer-wrapper tr td.el-table-fixed-column--right,
|
||||
.el-table__footer-wrapper tr th.el-table-fixed-column--left,
|
||||
.el-table__footer-wrapper tr th.el-table-fixed-column--right {
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--el-table-header-text-color: rgba(96, 98, 102, 1);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
--system-margin-padding-width: 8px;
|
||||
|
||||
.el-card__body {
|
||||
padding: var(--system-margin-padding-width);
|
||||
}
|
||||
|
||||
.gci-search .el-form-item {
|
||||
margin-bottom: var(--system-margin-padding-width);
|
||||
}
|
||||
}
|
||||
|
||||
.gci-search.gci-search-button__no-margin .el-button {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.gci-search > .el-card__body {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.gci-search.el-card {
|
||||
margin-bottom: var(--system-margin-padding-width);
|
||||
}
|
||||
.gci-search .el-button {
|
||||
margin-bottom: var(--system-margin-padding-width);
|
||||
}
|
||||
|
||||
.data-table-grid-header {
|
||||
margin-bottom: var(--system-margin-padding-width);
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.el-popconfirm__action .el-button.is-text {
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.host-main-body__aside .el-sub-menu {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.el-button.is-link.is-disabled {
|
||||
color: #ced1d8;
|
||||
}
|
||||
|
||||
.el-table thead.is-group th.el-table__cell {
|
||||
background: var(--el-table-header-bg-color);
|
||||
}
|
||||
|
||||
.el-message {
|
||||
white-space: pre;
|
||||
align-items: flex-start;
|
||||
line-height: 1em;
|
||||
}
|
||||
108
src/components/CarSelect/CarTree.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-tree
|
||||
v-loading="pending"
|
||||
:data="treeData"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
:props="defaultProps"
|
||||
@current-change="handleNodeClick"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<img
|
||||
v-if="typeof data.isOnline === 'number'"
|
||||
:src="
|
||||
data.alarmStatus === 1
|
||||
? carStateImageMap[2]
|
||||
: carStateImageMap[data.isOnline]
|
||||
"
|
||||
class="mr-1"
|
||||
alt="car_state_logo"
|
||||
/>
|
||||
<div
|
||||
class="flex w-full"
|
||||
:class="{
|
||||
'text-[#1D2129]': data.children instanceof Array,
|
||||
'text-[#175EFF]': nowSelectNode && nowSelectNode === data.plateNumber
|
||||
}"
|
||||
>
|
||||
<div>{{ data.plateNumber || data.projectName || '-' }}</div>
|
||||
<div
|
||||
v-if="data.children && data.children instanceof Array"
|
||||
class="ml-auto text-[#888] mr-1"
|
||||
>
|
||||
{{ data.children.filter(item => item.isOnline === 1).length }}/{{
|
||||
data.children.length
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { getCarMonitorTree } from '@/services/api/vehicleMonitoring/monitoring/index';
|
||||
import { getImages } from '@/utils/utils';
|
||||
import { computed, watch } from 'vue';
|
||||
|
||||
const carStateImageMap = {
|
||||
0: getImages('logo_car_state_0', 'svg'),
|
||||
1: getImages('logo_car_state_1', 'svg'),
|
||||
2: getImages('logo_car_state_2', 'svg')
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | undefined;
|
||||
plateNumber?: string;
|
||||
}>();
|
||||
const Emits = defineEmits(['update:modelValue', 'change']);
|
||||
|
||||
const nowSelectNode = computed({
|
||||
get: () => {
|
||||
// 在每次获取弹窗显示状态时,自动修改宽度
|
||||
// changeDialogWidth();
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
const handleNodeClick = (data: any) => {
|
||||
if (data.children instanceof Array) {
|
||||
nowSelectNode.value = undefined;
|
||||
} else {
|
||||
nowSelectNode.value = data.plateNumber || '';
|
||||
Emits('change', data.plateNumber);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'plateNumber'
|
||||
};
|
||||
|
||||
// 树形图
|
||||
const {
|
||||
pending,
|
||||
data: treeData,
|
||||
run: getGlobalTreeData
|
||||
} = useRequest(
|
||||
async () => {
|
||||
const response = await getCarMonitorTree(props.plateNumber);
|
||||
return response.data.result || [];
|
||||
},
|
||||
{
|
||||
initialData: []
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.plateNumber,
|
||||
() => {
|
||||
getGlobalTreeData();
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
52
src/components/CarSelect/index.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<script setup lang="ts" name="CarSelect">
|
||||
import CarTree from './CarTree.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
const props = defineProps<{
|
||||
modelValue: string | undefined;
|
||||
}>();
|
||||
|
||||
const Emits = defineEmits(['update:modelValue', 'confirm', 'cancel']);
|
||||
const inputRef = ref();
|
||||
const selectCar = computed({
|
||||
get: () => {
|
||||
// 在每次获取弹窗显示状态时,自动修改宽度
|
||||
// changeDialogWidth();
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const searchValue = ref(props.modelValue);
|
||||
|
||||
const popoverRef = ref();
|
||||
const changeSearchValue = (value: string) => {
|
||||
searchValue.value = value;
|
||||
popoverRef.value.hide();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
placement="bottom"
|
||||
:width="310"
|
||||
trigger="click"
|
||||
:show-arrow="false"
|
||||
>
|
||||
<template #reference>
|
||||
<el-input
|
||||
ref="inputRef"
|
||||
v-model="searchValue"
|
||||
placeholder="请输入车牌号"
|
||||
suffix-icon="Search"
|
||||
></el-input>
|
||||
</template>
|
||||
<CarTree
|
||||
v-model="selectCar"
|
||||
:plate-number="searchValue"
|
||||
@change="changeSearchValue"
|
||||
></CarTree>
|
||||
</el-popover>
|
||||
</template>
|
||||
180
src/components/Check/Check.vue
Normal file
@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<CustomDialog
|
||||
v-model="dialogVisible"
|
||||
destroy-on-close
|
||||
:width="Number(width)"
|
||||
:top="top"
|
||||
class="check-dialog"
|
||||
:show-footer="false"
|
||||
:title="checkOptions.title"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<div class="flex w-full items-center">
|
||||
<div :id="titleId" class="text-sm font-bold" :class="titleClass">
|
||||
查看{{ checkOptions.title }}
|
||||
</div>
|
||||
<div class="ml-auto cursor-pointer" @click="close">
|
||||
<el-icon><Close /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-for="(option, index) in checkOptions.data" :key="index">
|
||||
<div class="group">
|
||||
<div v-show="option.childTitle" class="group-title">
|
||||
{{ option.childTitle }}
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col
|
||||
v-for="(item, idx) in option.childData"
|
||||
:key="idx"
|
||||
:span="item.span"
|
||||
class="item"
|
||||
>
|
||||
<template v-if="item.slotName" #default>
|
||||
<slot :name="item.slotName" :value="form"></slot>
|
||||
</template>
|
||||
<div>
|
||||
<span class="item-title">{{ item.label }}:</span>
|
||||
<span
|
||||
v-if="form[item.prop] !== null && form[item.prop] !== ''"
|
||||
class="item-content"
|
||||
>{{ translateFn(form[item.prop], item.translate) }}</span
|
||||
>
|
||||
<span v-else class="item-content">/</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<slot name="default" :value="form"></slot>
|
||||
</CustomDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import CustomDialog from '@/components/CustomDialog/index.vue';
|
||||
// import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
checkOptions: any; // 表单配置
|
||||
checkData: any; // 表单字段
|
||||
width?: number | string;
|
||||
top?: string;
|
||||
}>(),
|
||||
{
|
||||
checkOptions: {},
|
||||
checkData: {},
|
||||
width: '53%',
|
||||
top: '15vh'
|
||||
}
|
||||
);
|
||||
|
||||
let form = reactive(props.checkData);
|
||||
// 控制弹窗显隐
|
||||
const dialogVisible = ref(false);
|
||||
const openDialog = (type: string) => {
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 关闭后调用
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
emit('close');
|
||||
};
|
||||
// 翻译
|
||||
const translateFn = (target, translate) => {
|
||||
if (translate) {
|
||||
return translate.fn(target, translate.options);
|
||||
} else return target;
|
||||
};
|
||||
onMounted(() => {
|
||||
// console.log(form);
|
||||
});
|
||||
const emit = defineEmits(['close']);
|
||||
// 暴露方法和数据
|
||||
defineExpose({ openDialog });
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Check'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.check-dialog > .el-dialog__header {
|
||||
margin-right: 0px;
|
||||
padding: 12px 20px;
|
||||
background: var(--el-dialog-header-bg-color);
|
||||
overflow: hidden;
|
||||
border-top-right-radius: 8px;
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
.check-dialog > .el-dialog__footer {
|
||||
margin-right: 0px;
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid var(--el-dialog-footer-border-color);
|
||||
}
|
||||
|
||||
.check-dialog .el-dialog {
|
||||
--el-dialog-header-bg-color: #f9f9f9;
|
||||
--el-text-color-primary: #6c6c6c;
|
||||
--el-dialog-footer-border-color: #e0e0e0;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.check-dialog .el-dialog {
|
||||
--el-dialog-header-bg-color: #4a4a4a;
|
||||
--el-text-color-primary: #dedede;
|
||||
--el-dialog-footer-border-color: #7a7a7a;
|
||||
}
|
||||
}
|
||||
.check-dialog {
|
||||
.group {
|
||||
.group-title {
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c6af3;
|
||||
line-height: 22px;
|
||||
margin-left: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: linear-gradient(180deg, #327dfe 0%, #2c6af3 100%);
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
left: -6px;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.item-title {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
// display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #888888;
|
||||
}
|
||||
.item-content {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
// display: inline-block;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
170
src/components/CustomDialog/index.vue
Normal file
@ -0,0 +1,170 @@
|
||||
<script lang="ts" setup name="CustomDialog">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '弹窗'
|
||||
},
|
||||
destroyOnClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取 消'
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确 认'
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 700
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
noPadding: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
noPaddingTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
closeOnClickModal: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const Emits = defineEmits(['update:modelValue', 'confirm', 'cancel']);
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => {
|
||||
// 在每次获取弹窗显示状态时,自动修改宽度
|
||||
// changeDialogWidth();
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const confirmClick = () => {
|
||||
Emits('confirm');
|
||||
};
|
||||
|
||||
const cancelClick = () => {
|
||||
Emits('cancel');
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:draggable="draggable"
|
||||
:destroy-on-close="destroyOnClose"
|
||||
:width="width"
|
||||
:append-to-body="true"
|
||||
:lock-scroll="false"
|
||||
class="gci-component__custom-dialog"
|
||||
:class="{
|
||||
'gci-component__custom-dialog-nopadding': noPadding,
|
||||
'gci-component__custom-dialog-nopaddingTop': noPaddingTop
|
||||
}"
|
||||
:before-close="cancelClick"
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
style="--el-dialog-title-font-size: 1rem; --el-dialog-border-radius: 8px"
|
||||
>
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<div class="flex w-full items-center">
|
||||
<div :id="titleId" class="text-sm font-bold" :class="titleClass">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="ml-auto cursor-pointer" @click="close">
|
||||
<el-icon><Close /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<template v-if="showFooter" #footer>
|
||||
<slot name="footer">
|
||||
<div class="flex items-center">
|
||||
<div class="mr-auto">
|
||||
<slot name="footer-suffix"></slot>
|
||||
</div>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cancelClick">{{ cancelText }}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
@click="confirmClick"
|
||||
>{{ confirmText }}</el-button
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.gci-component__custom-dialog > .el-dialog__header {
|
||||
margin-right: 0px;
|
||||
padding: 12px 20px;
|
||||
background: var(--el-dialog-header-bg-color);
|
||||
overflow: hidden;
|
||||
border-top-right-radius: 8px;
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
.gci-component__custom-dialog > .el-dialog__footer {
|
||||
margin-right: 0px;
|
||||
padding: 12px 20px;
|
||||
border-top: 1px solid var(--el-dialog-footer-border-color);
|
||||
}
|
||||
|
||||
.gci-component__custom-dialog.el-dialog {
|
||||
--el-dialog-header-bg-color: #f9f9f9;
|
||||
--el-text-color-primary: rgba(96, 98, 102, 1);
|
||||
--el-dialog-footer-border-color: #e0e0e0;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.gci-component__custom-dialog.el-dialog {
|
||||
--el-dialog-header-bg-color: #4a4a4a;
|
||||
--el-text-color-primary: #dedede;
|
||||
--el-dialog-footer-border-color: #7a7a7a;
|
||||
}
|
||||
}
|
||||
|
||||
.gci-component__custom-dialog-nopadding .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
.gci-component__custom-dialog-nopaddingTop .el-dialog__body {
|
||||
padding-top: 0;
|
||||
}
|
||||
</style>
|
||||
69
src/components/CustomerSelect/index.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<script setup lang="ts" name="UserSelect">
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { getCustomerSelectList } from '@/services/api/base/ClientManage/index.ts';
|
||||
import { computed } from 'vue';
|
||||
|
||||
/**
|
||||
* 客户选择下拉
|
||||
*/
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Array<string> | string | number | undefined;
|
||||
placeholder?: string;
|
||||
multiple?: boolean;
|
||||
}>();
|
||||
|
||||
const Emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const selectedValue = computed({
|
||||
get: () => {
|
||||
// 在每次获取弹窗显示状态时,自动修改宽度
|
||||
// changeDialogWidth();
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
// const queryParams = reactive({
|
||||
// project: undefined
|
||||
// });
|
||||
|
||||
const {
|
||||
data: customerOptions,
|
||||
pending,
|
||||
run: getCustomerOptions
|
||||
} = useRequest(
|
||||
async () => {
|
||||
const response = await getCustomerSelectList();
|
||||
|
||||
return response.data.result.records || [];
|
||||
},
|
||||
{
|
||||
initialData: []
|
||||
}
|
||||
);
|
||||
getCustomerOptions();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-select
|
||||
v-model="selectedValue"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
:multiple="multiple"
|
||||
filterable
|
||||
autocomplete="off"
|
||||
:placeholder="placeholder || '请选择'"
|
||||
:loading="pending"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
553
src/components/DataFormGrid/index.vue
Normal file
@ -0,0 +1,553 @@
|
||||
<script setup lang="ts">
|
||||
import DictSelect from '@/components/DictSelect/index.vue';
|
||||
import DictCheckbox from '@/components/DictCheckBox/index.vue';
|
||||
import DictRadio from '@/components/DictRadio/index.vue';
|
||||
import RemoteSelect from '@/components/RemoteSelect/index.vue';
|
||||
import UploadImage from '@/components/UploadImage/index.vue';
|
||||
import UploadFiles from '@/components/UploadFiles/index.vue';
|
||||
import type { FormGridConfig } from './types';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any;
|
||||
configs: Array<FormGridConfig>;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'left' | 'right' | 'top';
|
||||
disabled?: boolean;
|
||||
formName?: string;
|
||||
type?: 'text' | 'form';
|
||||
}>();
|
||||
|
||||
const Emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const formValue = computed({
|
||||
get: () => {
|
||||
return props.modelValue || {};
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
const rules = ref({} as any);
|
||||
const formRef = ref();
|
||||
|
||||
/**
|
||||
* 选择类型
|
||||
*/
|
||||
const selectType = [
|
||||
'select',
|
||||
'remoteSelect',
|
||||
'dictSelect',
|
||||
'dictCheckbox',
|
||||
'dictRadio',
|
||||
'checkbox',
|
||||
'radio',
|
||||
'date',
|
||||
'datetime',
|
||||
'datetimerange',
|
||||
'daterange',
|
||||
'year',
|
||||
'yearrange',
|
||||
'month',
|
||||
'monthrange',
|
||||
'week',
|
||||
'weekrange'
|
||||
];
|
||||
|
||||
watch(
|
||||
() => props.configs,
|
||||
newConfigs => {
|
||||
rules.value = {};
|
||||
newConfigs.forEach(item => {
|
||||
if (item.required) {
|
||||
rules.value[item.key] = item.rules || {
|
||||
required: true,
|
||||
message:
|
||||
item.requiredText ||
|
||||
item.placeholder ||
|
||||
(item.type && selectType.includes(item.type)
|
||||
? '请选择'
|
||||
: '请输入') + item.name,
|
||||
trigger: ['blur', 'change']
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (formRef.value) {
|
||||
// formRef.value.resetFields();
|
||||
|
||||
formRef.value.clearValidate();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const formValidate = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof formValue.value === 'object') {
|
||||
for (const key in formValue.value) {
|
||||
if (typeof formValue.value[key] === 'string') {
|
||||
formValue.value[key] = formValue.value[key].trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formRef.value?.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
resolve(formValue);
|
||||
} else {
|
||||
console.error(valid, fields);
|
||||
if (typeof fields === 'object') {
|
||||
const errorMessages = [] as Array<string>;
|
||||
for (const key in fields) {
|
||||
if (Object.prototype.hasOwnProperty.call(fields, key)) {
|
||||
const rules = fields[key];
|
||||
if (rules instanceof Array) {
|
||||
rules.forEach(rule => {
|
||||
errorMessages.push(rule.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ElMessage.warning(errorMessages.join('\n'));
|
||||
} else {
|
||||
ElMessage.warning({
|
||||
message: '请完善' + (props.formName || '') + '表单'
|
||||
});
|
||||
}
|
||||
|
||||
reject(valid);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 时间类型
|
||||
*/
|
||||
const timeType = [
|
||||
'date',
|
||||
'datetime',
|
||||
'daterange',
|
||||
'year',
|
||||
'month',
|
||||
'week',
|
||||
'datetimerange'
|
||||
];
|
||||
|
||||
/**
|
||||
* 时间格式化
|
||||
*/
|
||||
const timeFormat = {
|
||||
date: 'YYYY-MM-DD',
|
||||
datetime: 'YYYY-MM-DD HH:mm:ss',
|
||||
daterange: 'YYYY-MM-DD',
|
||||
year: 'YYYY',
|
||||
month: 'MM',
|
||||
datetimerange: 'YYYY-MM-DD HH:mm:ss'
|
||||
};
|
||||
|
||||
const formReset = () => {
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
const formItemRefs = {} as any;
|
||||
const setFormItemRef = (itemKey, itemRef) => {
|
||||
formItemRefs[itemKey] = itemRef;
|
||||
};
|
||||
|
||||
/**
|
||||
* 重新校验表单
|
||||
* @param itemKey
|
||||
*/
|
||||
const reValidateKey = (itemKey: string) => {
|
||||
if (formItemRefs[itemKey]) {
|
||||
formItemRefs[itemKey].clearValidate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 字典类型组件
|
||||
*/
|
||||
const dictTextType = [
|
||||
'select',
|
||||
'remoteSelect',
|
||||
'dictSelect',
|
||||
'dictCheckbox',
|
||||
'dictRadio'
|
||||
];
|
||||
|
||||
const localOptionType = ['select', 'radio', 'checkbox'];
|
||||
|
||||
/**
|
||||
* 特殊组件
|
||||
*/
|
||||
const ignoreType = ['uploadImage', 'uploadFiles'];
|
||||
|
||||
defineExpose({
|
||||
formValidate,
|
||||
formReset
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
:label-width="labelWidth"
|
||||
:label-position="labelPosition"
|
||||
:class="{
|
||||
'data-form-grid__text': type === 'text'
|
||||
}"
|
||||
scroll-to-error
|
||||
:validate-on-rule-change="false"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col
|
||||
v-for="item in configs"
|
||||
:key="item.key"
|
||||
v-auth="item.auth"
|
||||
:sm="24"
|
||||
:md="
|
||||
item.mdSpan ||
|
||||
(item.type === 'title'
|
||||
? 24
|
||||
: item.span && item.span > 12
|
||||
? item.span
|
||||
: 12)
|
||||
"
|
||||
:lg="item.span || 24"
|
||||
>
|
||||
<template v-if="!item.hidden">
|
||||
<template v-if="item.type === 'null' || item.type === 'slot'">
|
||||
<div v-auth="item.auth">
|
||||
<slot v-if="item.type === 'slot'" :name="item.key"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'title'">
|
||||
<div
|
||||
v-auth="item.auth"
|
||||
class="mb-4 text-primary-light-color font-bold flex items-center"
|
||||
:style="{
|
||||
marginBottom:
|
||||
type === 'text' || item.disabledText ? '8px' : undefined
|
||||
}"
|
||||
>
|
||||
{{ item.name }}
|
||||
<slot
|
||||
v-if="item.options && item.options[0].hasSuffix"
|
||||
:name="item.key"
|
||||
></slot>
|
||||
</div>
|
||||
</template>
|
||||
<el-form-item
|
||||
v-else
|
||||
:ref="el => setFormItemRef(item.key, el)"
|
||||
v-auth="item.auth"
|
||||
:label="item.name + ':'"
|
||||
:prop="item.key"
|
||||
:class="{
|
||||
'data-form-grid__text-item': type === 'text' || item.disabledText
|
||||
}"
|
||||
:label-width="item.labelWidth"
|
||||
>
|
||||
<slot :name="item.key" :value-key="item.key">
|
||||
<template
|
||||
v-if="
|
||||
(type !== 'text' && !item.disabledText) ||
|
||||
ignoreType.includes(item.type as any)
|
||||
"
|
||||
>
|
||||
<template v-if="item.type === 'checkbox' && item.options">
|
||||
<el-checkbox-group
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="option in item.options"
|
||||
:key="option.value"
|
||||
:label="option.value"
|
||||
>{{ option.label }}</el-checkbox
|
||||
>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'radio' && item.options">
|
||||
<el-radio-group
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
>
|
||||
<el-radio
|
||||
v-for="option in item.options"
|
||||
:key="option.value"
|
||||
:label="option.value"
|
||||
>{{ option.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
item.type === 'dictRadio' &&
|
||||
item.options &&
|
||||
item.options[0].dictCode
|
||||
"
|
||||
>
|
||||
<DictRadio
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:dict-code="item.options[0].dictCode"
|
||||
:text-key="item.options[0].label"
|
||||
:value-key="item.options[0].value"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
></DictRadio>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
item.type === 'dictCheckbox' &&
|
||||
item.options &&
|
||||
item.options[0].dictCode
|
||||
"
|
||||
>
|
||||
<DictCheckbox
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:dict-code="item.options[0].dictCode"
|
||||
:text-key="item.options[0].label"
|
||||
:value-key="item.options[0].value"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
></DictCheckbox>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'select' && item.options">
|
||||
<el-select
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:placeholder="
|
||||
item.placeholder ? item.placeholder : '请选择' + item.name
|
||||
"
|
||||
class="w-full"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in item.options"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
item.type === 'dictSelect' &&
|
||||
item.options &&
|
||||
item.options[0].dictCode
|
||||
"
|
||||
>
|
||||
<DictSelect
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:dict-code="item.options[0].dictCode"
|
||||
:text-key="item.options[0].label"
|
||||
:value-key="item.options[0].value"
|
||||
:disabled="disabled || item.disabledText"
|
||||
:placeholder="
|
||||
item.placeholder ? item.placeholder : '请选择' + item.name
|
||||
"
|
||||
class="w-full"
|
||||
clearable
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
></DictSelect>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="item.type === 'remoteSelect' && item.options"
|
||||
>
|
||||
<RemoteSelect
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:url="item.options[0].url"
|
||||
:option-label="item.options[0].label"
|
||||
:option-value="item.options[0].value"
|
||||
:placeholder="
|
||||
item.placeholder ? item.placeholder : '请选择' + item.name
|
||||
"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
></RemoteSelect>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="item.type === 'uploadImage' && item.options"
|
||||
>
|
||||
<UploadImage
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:url="item.options[0].url || ''"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
@change="reValidateKey(item.key)"
|
||||
v-on="item.eventOptions || {}"
|
||||
></UploadImage>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="item.type === 'uploadFiles' && item.options"
|
||||
>
|
||||
<UploadFiles
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:url="item.options[0].url || ''"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
:accept="item.accept || ''"
|
||||
@change="reValidateKey(item.key)"
|
||||
v-on="item.eventOptions || {}"
|
||||
></UploadFiles>
|
||||
</template>
|
||||
<template v-else-if="item.type && timeType.includes(item.type)">
|
||||
<el-date-picker
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:type="item.type"
|
||||
class="w-full"
|
||||
:placeholder="
|
||||
item.placeholder ? item.placeholder : '请选择日期'
|
||||
"
|
||||
:format="timeFormat[item.type] || 'YYYY-MM-DD'"
|
||||
:value-format="timeFormat[item.type] || 'YYYY-MM-DD'"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'textarea'">
|
||||
<el-input
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
type="textarea"
|
||||
:placeholder="
|
||||
item.placeholder ? item.placeholder : '请输入' + item.name
|
||||
"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
></el-input>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'numberInput'">
|
||||
<el-input
|
||||
v-model.number="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
type="number"
|
||||
style="width: 100%"
|
||||
:placeholder="
|
||||
item.placeholder ? item.placeholder : '请输入' + item.name
|
||||
"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
></el-input>
|
||||
</template>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="formValue[item.key]"
|
||||
v-auth="item.auth"
|
||||
:type="item.type !== 'input' ? item.type : undefined"
|
||||
:placeholder="
|
||||
item.placeholder ? item.placeholder : '请输入' + item.name
|
||||
"
|
||||
:disabled="disabled || item.disabledText"
|
||||
v-bind="item.componentOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
></el-input>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="dictTextType.includes(item.type as any)"
|
||||
v-auth="item.auth"
|
||||
class="text-[#888]"
|
||||
style="white-space: break-spaces"
|
||||
>
|
||||
{{
|
||||
typeof formValue[
|
||||
item.dictTextName
|
||||
? item.dictTextName
|
||||
: `${item.key}_dictText`
|
||||
] === 'undefined' || formValue[item.key] === null
|
||||
? '-'
|
||||
: formValue[
|
||||
item.dictTextName
|
||||
? item.dictTextName
|
||||
: `${item.key}_dictText`
|
||||
]
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="
|
||||
item.type &&
|
||||
localOptionType.includes(item.type) &&
|
||||
item.options
|
||||
"
|
||||
>
|
||||
{{
|
||||
typeof formValue[item.key] === 'undefined'
|
||||
? '-'
|
||||
: item.options.find(opItem => {
|
||||
return opItem.value === formValue[item.key];
|
||||
})?.label
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
v-auth="item.auth"
|
||||
class="text-[#888]"
|
||||
style="white-space: break-spaces"
|
||||
>
|
||||
{{
|
||||
typeof formValue[item.key] === 'undefined' ||
|
||||
formValue[item.key] === null
|
||||
? '-'
|
||||
: formValue[item.key] +
|
||||
(item.componentOptions &&
|
||||
item.componentOptions.suffixIcon
|
||||
? ' ' + item.componentOptions.suffixIcon.render()
|
||||
: '')
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
</slot>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-date-editor.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.data-form-grid__text :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.data-form-grid__text-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
56
src/components/DataFormGrid/types.ts
Normal file
@ -0,0 +1,56 @@
|
||||
export interface FormGridConfig {
|
||||
required?: boolean; // 是否必填
|
||||
requiredText?: string; // 必填校验显示字符
|
||||
name: string; // 显示名称
|
||||
key: string; // 取值key
|
||||
span?: number; // 栅格占据
|
||||
mdSpan?: number;
|
||||
type?: // 类型
|
||||
| 'select'
|
||||
| 'remoteSelect'
|
||||
| 'dictSelect'
|
||||
| 'dictCheckbox'
|
||||
| 'dictRadio'
|
||||
| 'checkbox'
|
||||
| 'radio'
|
||||
| 'input'
|
||||
| 'numberInput'
|
||||
| 'uploadImage'
|
||||
| 'uploadFiles'
|
||||
| 'textarea'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'datetimerange'
|
||||
| 'daterange'
|
||||
| 'year'
|
||||
| 'yearrange'
|
||||
| 'month'
|
||||
| 'monthrange'
|
||||
| 'week'
|
||||
| 'weekrange'
|
||||
| 'title'
|
||||
| 'null'
|
||||
| 'itemSlot'
|
||||
| 'slot';
|
||||
placeholder?: string; // 悬浮显示内容
|
||||
labelWidth?: string; // 显示名称宽度
|
||||
disabledText?: boolean;
|
||||
dictTextName?: string; // 查看禁用文本取值
|
||||
hidden?: boolean;
|
||||
textFormatter?: (data) => string;
|
||||
options?: Array<{
|
||||
// 选项,字典类型则取第一个option
|
||||
label?: string;
|
||||
value?: any;
|
||||
dictCode?: string;
|
||||
url?: string;
|
||||
hasSuffix?: boolean;
|
||||
}>;
|
||||
rules?: any;
|
||||
auth?: { action: string; type?: any; description?: string } | string;
|
||||
componentOptions?: any;
|
||||
eventOptions?: any;
|
||||
accept?: string;
|
||||
}
|
||||
|
||||
export default FormGridConfig;
|
||||
85
src/components/DataPagination/index.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-pagination
|
||||
v-model:current-page="data_currentPage"
|
||||
v-model:page-size="data_pageSize"
|
||||
:page-sizes="[5, 10, 20, 50, 100, 200, 500]"
|
||||
:small="small"
|
||||
:pager-count="5"
|
||||
:disabled="disabled"
|
||||
:background="background"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="DataPagination" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
// 当前页
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 每页占据数量
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
// 是否为 small 样式
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否有深色背景
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 总计数目
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
|
||||
// 触发方法返给上一级
|
||||
const Emits = defineEmits([
|
||||
'size-change',
|
||||
'current-change',
|
||||
'update:currentPage',
|
||||
'update:pageSize'
|
||||
]);
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
Emits('size-change', val);
|
||||
};
|
||||
const handleCurrentChange = (val: number) => {
|
||||
Emits('current-change', val);
|
||||
};
|
||||
|
||||
// 监听数据 - 以支持 v-model
|
||||
const data_currentPage = computed({
|
||||
get: () => props.currentPage,
|
||||
set: val => {
|
||||
Emits('update:currentPage', val);
|
||||
}
|
||||
});
|
||||
const data_pageSize = computed({
|
||||
get: () => props.pageSize,
|
||||
set: val => {
|
||||
Emits('update:pageSize', val);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
260
src/components/DataTable/index.vue
Normal file
@ -0,0 +1,260 @@
|
||||
<script lang="ts" setup name="DataTable">
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { ColumnType, SpanMethodProps, CellMethodProps } from './types';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
/**
|
||||
* DataTable -- 基于 element-plus 封装
|
||||
* 2023年8月23日
|
||||
*
|
||||
* @author Charles Chan 陈裕峰
|
||||
*/
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data?: Array<any>;
|
||||
columns?: Array<ColumnType>;
|
||||
height?: string | number;
|
||||
maxHeight?: string | number;
|
||||
rowKey?: string | ((row: any) => any);
|
||||
treeOptions?: {
|
||||
defaultExpandAll: boolean;
|
||||
lazy?: boolean;
|
||||
load?: (row: any, treeNode: any, resolve: any) => void;
|
||||
props?: {
|
||||
hasChildren: string;
|
||||
children: string;
|
||||
};
|
||||
};
|
||||
defaultSelections?: Array<any>;
|
||||
stripe?: boolean;
|
||||
spanMethod?: (spanArgs: SpanMethodProps) => any;
|
||||
noMutiple?: boolean;
|
||||
tableAttrs?: any;
|
||||
cellMouseEnter?: (spanArgs: CellMethodProps) => any;
|
||||
cellMouseLeave?: (spanArgs: CellMethodProps) => any;
|
||||
rowClassName?: (row: any, rowIndex: number) => any;
|
||||
eventMethods?: any;
|
||||
emptyStyle?: any;
|
||||
}>(),
|
||||
{
|
||||
data: () => [],
|
||||
columns: () => [],
|
||||
height: undefined,
|
||||
rowKey: undefined,
|
||||
maxHeight: '100vh',
|
||||
treeOptions: () => ({
|
||||
defaultExpandAll: false
|
||||
}),
|
||||
defaultSelections: () => [],
|
||||
stripe: true,
|
||||
spanMethod: undefined,
|
||||
tableAttrs: () => ({}),
|
||||
cellMouseEnter: undefined,
|
||||
cellMouseLeave: undefined,
|
||||
rowClassName: undefined,
|
||||
eventMethods: () => ({}),
|
||||
emptyStyle: undefined
|
||||
}
|
||||
);
|
||||
|
||||
const tableRef = ref();
|
||||
const keySelections = ref(cloneDeep(props.defaultSelections) as Array<any>);
|
||||
const totalKeySelections = ref([] as Array<any>);
|
||||
|
||||
const resetKeySelection = () => {
|
||||
totalKeySelections.value = [];
|
||||
keySelections.value = cloneDeep(props.defaultSelections) as Array<any>;
|
||||
|
||||
if (typeof props.rowKey === 'function') {
|
||||
props.data.forEach(item => {
|
||||
const keyValueFunction = props.rowKey as any;
|
||||
if (keySelections.value.includes(keyValueFunction(item))) {
|
||||
tableRef.value.toggleRowSelection(item, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const Emits = defineEmits([
|
||||
'selection-change',
|
||||
'key-selection-change',
|
||||
'page-selection-change'
|
||||
]);
|
||||
|
||||
// 选择项改变时,调用方法
|
||||
const emitSelectionChange = (selection: Array<any>) => {
|
||||
Emits('selection-change', selection);
|
||||
};
|
||||
|
||||
const selectChange = (selection, row) => {
|
||||
if (typeof props.rowKey === 'function') {
|
||||
const keyValueFunction = props.rowKey;
|
||||
const selectState = selection.includes(row);
|
||||
if (selectState && !keySelections.value.includes(keyValueFunction(row))) {
|
||||
if (props.noMutiple) {
|
||||
tableRef.value.clearSelection();
|
||||
|
||||
keySelections.value = [keyValueFunction(row)];
|
||||
totalKeySelections.value = [cloneDeep(row)];
|
||||
tableRef.value.toggleRowSelection(row, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
keySelections.value.push(keyValueFunction(row));
|
||||
|
||||
totalKeySelections.value.push(cloneDeep(row));
|
||||
} else if (
|
||||
!selectState &&
|
||||
keySelections.value.includes(keyValueFunction(row))
|
||||
) {
|
||||
if (props.noMutiple) {
|
||||
tableRef.value.clearSelection();
|
||||
keySelections.value = [];
|
||||
totalKeySelections.value = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
keySelections.value.splice(
|
||||
keySelections.value.indexOf(keyValueFunction(row)),
|
||||
1
|
||||
);
|
||||
|
||||
totalKeySelections.value.splice(
|
||||
totalKeySelections.value.findIndex(
|
||||
item => keyValueFunction(item) === keyValueFunction(row)
|
||||
),
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const allSelectChange = selection => {
|
||||
if (props.noMutiple && typeof props.rowKey === 'function') {
|
||||
tableRef.value.clearSelection();
|
||||
keySelections.value = [];
|
||||
totalKeySelections.value = [];
|
||||
return;
|
||||
}
|
||||
if (props.data instanceof Array) {
|
||||
props.data.forEach(row => {
|
||||
selectChange(selection, row);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
newValue => {
|
||||
if (typeof props.rowKey === 'function') {
|
||||
const keyValueFunction = props.rowKey;
|
||||
const nowPageSelections = newValue.filter(item =>
|
||||
keySelections.value.includes(keyValueFunction(item))
|
||||
);
|
||||
|
||||
nextTick(() => {
|
||||
nowPageSelections.forEach(item => {
|
||||
tableRef.value.toggleRowSelection(item, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => keySelections.value,
|
||||
newValue => {
|
||||
Emits('key-selection-change', newValue);
|
||||
Emits('page-selection-change', totalKeySelections.value);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
tableRef,
|
||||
keySelections,
|
||||
totalKeySelections,
|
||||
resetKeySelection
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="data"
|
||||
:height="height"
|
||||
:max-height="maxHeight"
|
||||
scrollbar-always-on
|
||||
:stripe="stripe"
|
||||
:default-expand-all="treeOptions.defaultExpandAll"
|
||||
:row-key="rowKey"
|
||||
:lazy="treeOptions.lazy"
|
||||
:load="treeOptions.load"
|
||||
:span-method="spanMethod"
|
||||
:tree-props="treeOptions.props"
|
||||
v-bind="tableAttrs"
|
||||
:row-class-name="rowClassName"
|
||||
@select-all="allSelectChange"
|
||||
@select="selectChange"
|
||||
@selection-change="emitSelectionChange"
|
||||
@cell-mouse-enter="cellMouseEnter"
|
||||
@cell-mouse-leave="cellMouseLeave"
|
||||
v-on="eventMethods || {}"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="item in columns"
|
||||
:key="item.prop"
|
||||
:fixed="item.fixed"
|
||||
:type="item.type"
|
||||
:show-overflow-tooltip="!item.slotName"
|
||||
:property="item.prop"
|
||||
:label="item.name"
|
||||
:min-width="item.type === 'selection' ? undefined : item.width || 120"
|
||||
:width="
|
||||
item.type === 'selection' ? 50 : item.fixed ? item.width : undefined
|
||||
"
|
||||
:align="item.align || 'center'"
|
||||
:render-header="item.renderHeader"
|
||||
:formatter="item.formatter"
|
||||
:selectable="item.selectable"
|
||||
:sortable="item.sortable"
|
||||
v-bind="item.columnOptions"
|
||||
v-on="item.eventOptions || {}"
|
||||
>
|
||||
<template v-if="item.slotName" #default="scope">
|
||||
<slot
|
||||
:name="item.slotName"
|
||||
:row="scope.row"
|
||||
:index="scope.$index"
|
||||
></slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<el-empty :style="emptyStyle" description="没有发现相关数据" />
|
||||
</slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 合并单元格的背景颜色
|
||||
.el-table {
|
||||
:deep(.el-table__body .el-table__row.hover-bg td) {
|
||||
background-color: #f5f7fb;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
src/components/DataTable/types.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export interface ColumnType {
|
||||
prop: string; // 表头返回字段参数
|
||||
name?: string; // 表头显示名称
|
||||
type?: string; // 可以设置为 selection
|
||||
width?: number | string; // 宽度占比
|
||||
fixed?: boolean | string; // 固定位置
|
||||
align?: string; // 文本居中布局
|
||||
slotName?: string; // 添加插槽名自定义
|
||||
formatter?: (row?: any, column?: any, cellValue?: any, index?: number) => any; // 格式化显示数据
|
||||
renderHeader?: (column?: any) => any; // 表头自定义渲染
|
||||
selectable?: (row?: any, index?: number) => any;
|
||||
sortable?: boolean | 'custom';
|
||||
columnOptions?: any;
|
||||
eventOptions?: any;
|
||||
}
|
||||
|
||||
export interface SpanMethodProps {
|
||||
row: any;
|
||||
column: any;
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}
|
||||
|
||||
export interface CellMethodProps {
|
||||
row: any;
|
||||
column: any;
|
||||
cell: any;
|
||||
event: any;
|
||||
}
|
||||
320
src/components/DataTableGrid/index.vue
Normal file
@ -0,0 +1,320 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import {
|
||||
ColumnType,
|
||||
SpanMethodProps,
|
||||
CellMethodProps
|
||||
} from '@/components/DataTable/types';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import DataPagination from '@/components/DataPagination/index.vue';
|
||||
import DataTable from '@/components/DataTable/index.vue';
|
||||
import { getTableData } from '@/services/api/table/index';
|
||||
|
||||
const dataTableRef = ref();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
url?: string; // 接口
|
||||
data?: Array<any>;
|
||||
loading?: boolean;
|
||||
columns?: Array<ColumnType>; // 表格设置
|
||||
queryParams?: any; // 接口额外请求参数
|
||||
keepAliveParams?: any; // 默认参数
|
||||
height?: string | number; // 高度
|
||||
maxHeight?: string | number; // 最大高度
|
||||
rowKey?: string | ((row: any) => any); // 列表唯一值,tree和保留分页多选时候必传
|
||||
treeOptions?: {
|
||||
//树形选项
|
||||
defaultExpandAll: boolean;
|
||||
lazy?: boolean;
|
||||
load?: (row: any, treeNode: any, resolve: any) => void;
|
||||
props?: {
|
||||
hasChildren: string;
|
||||
children: string;
|
||||
};
|
||||
};
|
||||
dataKey?: string;
|
||||
defaultSelections?: Array<any>;
|
||||
stripe?: boolean;
|
||||
spanMethod?: (spanArgs: SpanMethodProps) => any;
|
||||
noMutiple?: boolean;
|
||||
hasFooter?: boolean;
|
||||
tableAttrs?: any;
|
||||
cellMouseEnter?: (spanArgs: CellMethodProps) => any;
|
||||
cellMouseLeave?: (spanArgs: CellMethodProps) => any;
|
||||
rowClassName?: (row: any, rowIndex: number) => any;
|
||||
eventMethods?: any;
|
||||
}>(),
|
||||
{
|
||||
url: undefined,
|
||||
queryParams: {},
|
||||
keepAliveParams: {},
|
||||
columns: () => [],
|
||||
height: undefined,
|
||||
maxHeight: undefined,
|
||||
stripe: true,
|
||||
rowKey: undefined,
|
||||
treeOptions: () => ({
|
||||
defaultExpandAll: false
|
||||
}),
|
||||
dataKey: undefined,
|
||||
data: undefined,
|
||||
defaultSelections: () => [],
|
||||
spanMethod: undefined,
|
||||
hasFooter: true,
|
||||
tableAttrs: () => ({}),
|
||||
cellMouseEnter: undefined,
|
||||
cellMouseLeave: undefined,
|
||||
rowClassName: undefined,
|
||||
eventMethods: undefined
|
||||
}
|
||||
);
|
||||
|
||||
const Emits = defineEmits([
|
||||
'update:queryParams',
|
||||
'selection-change',
|
||||
'key-selection-change',
|
||||
'page-selection-change',
|
||||
'data-change',
|
||||
'loading'
|
||||
]);
|
||||
|
||||
// 请求参数分页传参
|
||||
const paginations = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
|
||||
export interface PageReponse {
|
||||
size: number;
|
||||
total: number;
|
||||
pages: number;
|
||||
current: number;
|
||||
}
|
||||
|
||||
// 接口返回的页码设置
|
||||
const pageSetting: PageReponse = reactive({
|
||||
size: 10,
|
||||
total: 1,
|
||||
pages: 1,
|
||||
current: 1
|
||||
});
|
||||
|
||||
/**
|
||||
* 列表数据请求
|
||||
*/
|
||||
const {
|
||||
pending,
|
||||
data: tableData,
|
||||
run: searchUserList
|
||||
} = useRequest(
|
||||
async ({ pageNo = 1, pageSize = 10, ...queryParams }) => {
|
||||
if (props.data instanceof Array) {
|
||||
paginations.pageNo = pageNo;
|
||||
paginations.pageSize = pageSize;
|
||||
|
||||
pageSetting.current = pageNo;
|
||||
pageSetting.pages = props.data.length % pageSize;
|
||||
pageSetting.total = props.data.length;
|
||||
pageSetting.size = pageSize;
|
||||
|
||||
return props.data.slice(
|
||||
(pageNo - 1) * pageSize,
|
||||
(pageNo - 1) * pageSize + pageSize
|
||||
);
|
||||
} else if (typeof props.url === 'string') {
|
||||
paginations.pageNo = pageNo;
|
||||
paginations.pageSize = pageSize;
|
||||
|
||||
const response = await getTableData(props.url, {
|
||||
pageNo,
|
||||
pageSize,
|
||||
...queryParams,
|
||||
...props.keepAliveParams
|
||||
});
|
||||
Emits('update:queryParams');
|
||||
|
||||
pageSetting.current = response.data?.result.current || 1;
|
||||
pageSetting.pages = response.data?.result.pages || 1;
|
||||
pageSetting.total = response.data?.result.total || 0;
|
||||
pageSetting.size = response.data?.result.size || paginations.pageSize;
|
||||
|
||||
return props.dataKey
|
||||
? response.data?.[props.dataKey]
|
||||
: response.data?.result.records;
|
||||
}
|
||||
},
|
||||
{ initialData: [] }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
newValue => {
|
||||
if (newValue instanceof Array) {
|
||||
loadTableData();
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const slotNames = computed(
|
||||
() =>
|
||||
props.columns
|
||||
.filter(item => typeof item.slotName === 'string')
|
||||
.map(item => item.slotName) as Array<string>
|
||||
);
|
||||
|
||||
/**
|
||||
* 加载表单数据
|
||||
*/
|
||||
const loadTableData = () => {
|
||||
searchUserList({
|
||||
...paginations,
|
||||
...props.queryParams
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置表单搜索
|
||||
*/
|
||||
const resetTableData = () => {
|
||||
paginations.pageNo = 1;
|
||||
paginations.pageSize = 10;
|
||||
searchUserList({
|
||||
...paginations,
|
||||
...props.queryParams
|
||||
});
|
||||
|
||||
if (dataTableRef.value) {
|
||||
resetKeySelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置表单选择框
|
||||
*/
|
||||
const resetKeySelection = () => {
|
||||
dataTableRef.value.resetKeySelection();
|
||||
};
|
||||
|
||||
/**
|
||||
* 切换分页功能
|
||||
* @param page
|
||||
*/
|
||||
const changeCurrentPage = (page: number) => {
|
||||
paginations.pageNo = page;
|
||||
loadTableData();
|
||||
};
|
||||
const changePageSize = (size: number) => {
|
||||
paginations.pageSize = size;
|
||||
paginations.pageNo = 1;
|
||||
loadTableData();
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择项变化
|
||||
* @param selections
|
||||
*/
|
||||
const emitSelectionChange = (selections: Array<any>) => {
|
||||
Emits('selection-change', selections);
|
||||
};
|
||||
|
||||
loadTableData();
|
||||
|
||||
const keySelections = ref([] as Array<string>);
|
||||
|
||||
const totalKeySelections = ref([] as Array<any>);
|
||||
|
||||
const emitKeySelectionChange = newKeySelections => {
|
||||
keySelections.value = newKeySelections || [];
|
||||
Emits('key-selection-change', keySelections.value);
|
||||
};
|
||||
|
||||
const emitPageSelectionChange = newPageSelections => {
|
||||
totalKeySelections.value = newPageSelections || [];
|
||||
Emits('page-selection-change', totalKeySelections.value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => pending.value,
|
||||
newValue => {
|
||||
Emits('loading', !!newValue);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => tableData.value,
|
||||
newValue => {
|
||||
Emits('data-change', newValue);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
pending,
|
||||
tableData,
|
||||
loadTableData,
|
||||
resetTableData,
|
||||
keySelections,
|
||||
totalKeySelections,
|
||||
resetKeySelection
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header>
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
|
||||
<section style="position: relative">
|
||||
<DataTable
|
||||
ref="dataTableRef"
|
||||
v-loading="pending || loading"
|
||||
:data="tableData"
|
||||
:default-selections="defaultSelections"
|
||||
:columns="columns"
|
||||
:height="height"
|
||||
:row-key="rowKey"
|
||||
:no-mutiple="noMutiple"
|
||||
:max-height="maxHeight"
|
||||
:span-method="spanMethod"
|
||||
:stripe="stripe"
|
||||
:tree-options="treeOptions"
|
||||
:table-attrs="tableAttrs"
|
||||
:row-class-name="rowClassName"
|
||||
:cell-mouse-enter="cellMouseEnter"
|
||||
:cell-mouse-leave="cellMouseLeave"
|
||||
:event-methods="eventMethods"
|
||||
@key-selection-change="emitKeySelectionChange"
|
||||
@page-selection-change="emitPageSelectionChange"
|
||||
@selection-change="emitSelectionChange"
|
||||
>
|
||||
<template
|
||||
v-for="item in slotNames"
|
||||
:key="item"
|
||||
#[item]="{ row, index }"
|
||||
>
|
||||
<slot :name="item" :row="row" :index="index"></slot>
|
||||
</template>
|
||||
</DataTable>
|
||||
<slot name="middle"></slot>
|
||||
</section>
|
||||
|
||||
<footer v-if="hasFooter">
|
||||
<DataPagination
|
||||
v-model:current-page="pageSetting.current"
|
||||
v-model:page-size="pageSetting.size"
|
||||
:total="pageSetting.total"
|
||||
style="margin-top: 0.8em"
|
||||
class="flex justify-end"
|
||||
@current-change="changeCurrentPage"
|
||||
@size-change="changePageSize"
|
||||
></DataPagination>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
@ -0,0 +1,63 @@
|
||||
<script setup lang="ts" name="DepartmentTreeSelect">
|
||||
import { computed, watch } from 'vue';
|
||||
import { getDepartmentSubTrees } from '@/services/api/system/department';
|
||||
import type { DepartmentTree } from '@/services/api/system/department/types';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string | string[];
|
||||
options?: DepartmentTree[];
|
||||
orgCode?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value?: string | string[]);
|
||||
}>();
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: nv => {
|
||||
emit('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const { data: departmentTrees, run: getDepartmentTrees } = useRequest(
|
||||
async () => {
|
||||
const response = await getDepartmentSubTrees(props.orgCode);
|
||||
return response.data.result || [];
|
||||
},
|
||||
{ initialData: [] }
|
||||
);
|
||||
|
||||
const options = computed(() => props.options ?? departmentTrees.value);
|
||||
|
||||
if (!props.options) {
|
||||
getDepartmentTrees();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.orgCode,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue !== oldValue) {
|
||||
getDepartmentTrees();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-tree-select
|
||||
v-model="modelValue"
|
||||
:data="options"
|
||||
placeholder="请选择部门/机构"
|
||||
:expand-on-click-node="false"
|
||||
node-key="orgCode"
|
||||
check-strictly
|
||||
default-expand-all
|
||||
:props="{
|
||||
label: 'departName',
|
||||
children: 'children'
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</template>
|
||||
54
src/components/DepartmentTreeSelect/index.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<script setup lang="ts" name="DepartmentTreeSelect">
|
||||
import { computed } from 'vue';
|
||||
import { getDepartmentTrees as getDepartmentTreesAPI } from '@/services/api/system/department';
|
||||
import type { DepartmentTree } from '@/services/api/system/department/types';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string | string[];
|
||||
options?: DepartmentTree[];
|
||||
orgCategory?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value?: string | string[]);
|
||||
}>();
|
||||
|
||||
const modelValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: nv => {
|
||||
emit('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const { data: departmentTrees, run: getDepartmentTrees } = useRequest(
|
||||
async () => {
|
||||
const response = await getDepartmentTreesAPI(undefined, props.orgCategory);
|
||||
return response.data.result || [];
|
||||
},
|
||||
{ initialData: [] }
|
||||
);
|
||||
|
||||
const options = computed(() => props.options ?? departmentTrees.value);
|
||||
|
||||
if (!props.options) {
|
||||
getDepartmentTrees();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-tree-select
|
||||
v-model="modelValue"
|
||||
:data="options"
|
||||
placeholder="请选择部门/机构"
|
||||
:expand-on-click-node="false"
|
||||
node-key="id"
|
||||
check-strictly
|
||||
default-expand-all
|
||||
:props="{
|
||||
label: 'departName',
|
||||
children: 'children'
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</template>
|
||||
113
src/components/DialogFormGrid/index.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<script setup lang="ts" name="DialogFormGrid">
|
||||
import CustomDialog from '@/components/CustomDialog/index.vue';
|
||||
import DataFormGrid from '@/components/DataFormGrid/index.vue';
|
||||
import type { FormGridConfig } from '@/components/DataFormGrid/types';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const formRef = ref();
|
||||
const props = defineProps<{
|
||||
type: 'new' | 'edit' | 'detail';
|
||||
modelValue: boolean;
|
||||
formValue: any;
|
||||
configs: Array<FormGridConfig>;
|
||||
submitApi: any;
|
||||
width?: number;
|
||||
title: string;
|
||||
noMessage?: boolean;
|
||||
}>();
|
||||
const Emits = defineEmits(['update:modelValue', 'update:formValue', 'reload']);
|
||||
const TitleMap = {
|
||||
new: '新增',
|
||||
edit: '编辑',
|
||||
detail: '查看'
|
||||
};
|
||||
const dialogTitle = computed(() => TitleMap[props.type] + props.title);
|
||||
const dialogVisible = computed({
|
||||
get: () => {
|
||||
// 在每次获取弹窗显示状态时,自动修改宽度
|
||||
// changeDialogWidth();
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
const formData = computed({
|
||||
get: () => {
|
||||
// 在每次获取弹窗显示状态时,自动修改宽度
|
||||
// changeDialogWidth();
|
||||
return props.formValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:formValue', nv);
|
||||
}
|
||||
});
|
||||
const isDisabled = computed(() => props.type === 'detail');
|
||||
const loading = ref(false);
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
const submitForm = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
await formRef.value.formValidate();
|
||||
|
||||
const response = await props.submitApi(formData.value);
|
||||
Emits('reload');
|
||||
if (!props.noMessage) {
|
||||
ElMessage.success(response.data.message || '操作成功!');
|
||||
}
|
||||
loading.value = false;
|
||||
dialogVisible.value = false;
|
||||
} catch (error) {
|
||||
loading.value = false;
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const formConfigSlots = computed(() => {
|
||||
return props.configs.filter(
|
||||
item =>
|
||||
item.type === 'slot' ||
|
||||
item.type === 'itemSlot' ||
|
||||
(item.type === 'title' && item.options && item.options[0].hasSuffix)
|
||||
);
|
||||
});
|
||||
|
||||
const cancelSubmit = () => {
|
||||
formData.value = {};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CustomDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:width="width"
|
||||
destroy-on-close
|
||||
:loading="loading"
|
||||
:show-footer="type === 'detail' ? false : true"
|
||||
@confirm="submitForm"
|
||||
@cancel="cancelSubmit"
|
||||
>
|
||||
<slot></slot>
|
||||
<DataFormGrid
|
||||
ref="formRef"
|
||||
v-model="formData"
|
||||
v-bind="$attrs"
|
||||
:configs="configs"
|
||||
:type="type === 'detail' ? 'text' : 'form'"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
<template v-for="configSlot in formConfigSlots" #[configSlot.key]>
|
||||
<slot
|
||||
:name="configSlot.key"
|
||||
:value-key="configSlot.key"
|
||||
:disabled="isDisabled"
|
||||
:type="type"
|
||||
></slot>
|
||||
</template>
|
||||
</DataFormGrid>
|
||||
</CustomDialog>
|
||||
</template>
|
||||
61
src/components/DictCheckBox/index.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { getDiciByCode } from '@/services/api/system/dict/index';
|
||||
|
||||
const props = defineProps<{
|
||||
dictCode: string; // 字典编码 或 表名
|
||||
valueKey?: string; // 若dictCode为表名,则需传入返回option取得value 数据库表头key
|
||||
textKey?: string; // 若dictCode为表名,则需传入返回option显示label 数据库表头key
|
||||
filter?: string; // 若dictCode为表名,则可输入过滤方法,例如 username='admin'
|
||||
modelValue: any;
|
||||
disabled?: boolean; // 禁用
|
||||
min?: number; // 最少勾选数量
|
||||
max?: number; // 最多勾选数量
|
||||
border?: boolean; // 是否显示边框
|
||||
}>();
|
||||
|
||||
const Emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const bindValue = computed({
|
||||
get: () => {
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const { data: options, run: getSelectOptions } = useRequest(
|
||||
async () => {
|
||||
const response = await getDiciByCode(
|
||||
`${props.dictCode}${props.textKey ? `,${props.textKey}` : ''}${
|
||||
props.valueKey ? `,${props.valueKey}` : ''
|
||||
}${props.filter ? `,${props.filter}` : ''}`
|
||||
);
|
||||
|
||||
return response.data.result || [];
|
||||
},
|
||||
{
|
||||
initialData: []
|
||||
}
|
||||
);
|
||||
|
||||
getSelectOptions();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-checkbox-group
|
||||
v-model="bindValue"
|
||||
:disabled="disabled"
|
||||
:min="min"
|
||||
:max="max"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
>{{ item.text }}</el-checkbox
|
||||
>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
57
src/components/DictRadio/index.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { getDiciByCode } from '@/services/api/system/dict/index';
|
||||
|
||||
const props = defineProps<{
|
||||
dictCode: string; // 字典编码 或 表名
|
||||
valueKey?: string; // 若dictCode为表名,则需传入返回option取得value 数据库表头key
|
||||
textKey?: string; // 若dictCode为表名,则需传入返回option显示label 数据库表头key
|
||||
filter?: string; // 若dictCode为表名,则可输入过滤方法,例如 username='admin'
|
||||
modelValue: any;
|
||||
disabled?: boolean; // 禁用
|
||||
border?: boolean; // 是否显示边框
|
||||
}>();
|
||||
|
||||
const Emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const bindValue = computed({
|
||||
get: () => {
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
pending,
|
||||
data: options,
|
||||
run: getSelectOptions
|
||||
} = useRequest(
|
||||
async () => {
|
||||
const response = await getDiciByCode(
|
||||
`${props.dictCode}${props.textKey ? `,${props.textKey}` : ''}${
|
||||
props.valueKey ? `,${props.valueKey}` : ''
|
||||
}${props.filter ? `,${props.filter}` : ''}`
|
||||
);
|
||||
|
||||
return response.data.result || [];
|
||||
},
|
||||
{
|
||||
initialData: []
|
||||
}
|
||||
);
|
||||
|
||||
getSelectOptions();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-radio-group v-model="bindValue" v-loading="pending" :disabled="disabled">
|
||||
<slot :options="options">
|
||||
<el-radio v-for="item in options" :key="item.value" :label="item.value">{{
|
||||
item.text
|
||||
}}</el-radio>
|
||||
</slot>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
80
src/components/DictSelect/index.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { getDiciByCode } from '@/services/api/system/dict/index';
|
||||
|
||||
const props = defineProps<{
|
||||
dictCode: string; // 字典编码 或 表名
|
||||
valueKey?: string; // 若dictCode为表名,则需传入返回option取得value 数据库表头key
|
||||
textKey?: string; // 若dictCode为表名,则需传入返回option显示label 数据库表头key
|
||||
filter?: string; // 若dictCode为表名,则可输入过滤方法,例如 username='admin'
|
||||
modelValue: any;
|
||||
placeholder?: string; // 悬浮提示
|
||||
filterable?: boolean; // 可搜索
|
||||
multiple?: boolean; // 多选
|
||||
disabled?: boolean; // 禁用
|
||||
filterMethod?: () => void; // 可自定义搜索方法
|
||||
clearable?: boolean; // 可清空
|
||||
}>();
|
||||
|
||||
const Emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const bindValue = computed({
|
||||
get: () => {
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
pending,
|
||||
data: options,
|
||||
run: getSelectOptions
|
||||
} = useRequest(
|
||||
async (forceReload = false) => {
|
||||
const response = await getDiciByCode(
|
||||
`${props.dictCode}${props.textKey ? `,${props.textKey}` : ''}${
|
||||
props.valueKey ? `,${props.valueKey}` : ''
|
||||
}${props.filter ? `,${props.filter}` : ''}`,
|
||||
forceReload
|
||||
);
|
||||
|
||||
return response.data.result || [];
|
||||
},
|
||||
{
|
||||
initialData: []
|
||||
}
|
||||
);
|
||||
|
||||
getSelectOptions();
|
||||
|
||||
const forceReloadOptions = async () => {
|
||||
await getSelectOptions(true);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
reload: forceReloadOptions
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-select
|
||||
v-model="bindValue"
|
||||
:filterable="filterable"
|
||||
:clearable="clearable"
|
||||
:multiple="multiple"
|
||||
:placeholder="placeholder"
|
||||
:loading="pending"
|
||||
:filter-method="filterMethod"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.text"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
445
src/components/Forms/Forms.vue
Normal file
@ -0,0 +1,445 @@
|
||||
<template>
|
||||
<CustomDialog
|
||||
v-model="dialogVisible"
|
||||
confirm-text="提 交"
|
||||
:title="
|
||||
title || `${formType == 'add' ? '新增' : '编辑'}${formOptions.title}`
|
||||
"
|
||||
destroy-on-close
|
||||
:width="width"
|
||||
:top="top"
|
||||
:show-footer="showFooter"
|
||||
:loading="loading"
|
||||
class="form-dialog"
|
||||
@cancel="handleClose"
|
||||
@confirm="submitForm(formRef)"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" label-position="right" @submit.prevent>
|
||||
<template v-for="(option, index) in formOptions.data" :key="index">
|
||||
<div class="group">
|
||||
<div v-show="option.childTitle" class="group-title">
|
||||
{{ option.childTitle }}
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col
|
||||
v-for="(item, idx) in option.childData"
|
||||
:key="idx"
|
||||
:span="item.span"
|
||||
>
|
||||
<slot
|
||||
v-if="item.type === 'slot' && item.slotName"
|
||||
:name="item.slotName"
|
||||
:value="form"
|
||||
></slot>
|
||||
<el-form-item
|
||||
v-else
|
||||
:name="item.prop"
|
||||
:rules="item.rule"
|
||||
:prop="item.prop"
|
||||
:label-width="item.labelWidth || '110px'"
|
||||
class="form-item"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type == 'input'"
|
||||
v-model="form[item.prop]"
|
||||
autocomplete="off"
|
||||
placeholder="请输入"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
>
|
||||
<template v-if="item.append" #append>{{
|
||||
item.append
|
||||
}}</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-if="item.type == 'password'"
|
||||
v-model="form[item.prop]"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
placeholder="请输入"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
>
|
||||
<template v-if="item.append" #append>{{
|
||||
item.append
|
||||
}}</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-else-if="item.type == 'number'"
|
||||
v-model="form[item.prop]"
|
||||
autocomplete="off"
|
||||
placeholder="请输入"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
:min="item.min"
|
||||
:max="item.max"
|
||||
@input="regStr(item.prop, item.integer)"
|
||||
@blur="inputNumber(item.prop, item)"
|
||||
>
|
||||
<template v-if="item.append" #append>{{
|
||||
item.append
|
||||
}}</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-else-if="item.type == 'textarea'"
|
||||
v-model="form[item.prop]"
|
||||
:rows="item.options ? item.options.row : 2"
|
||||
type="textarea"
|
||||
:placeholder="
|
||||
item.options ? item.options.placeholder : '请输入'
|
||||
"
|
||||
:maxlength="item.options ? item.options.maxlength : ''"
|
||||
:show-word-limit="item.options?.maxlength ? true : false"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="item.type === 'select'"
|
||||
v-model="form[item.prop]"
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
:multiple="item.multiple || false"
|
||||
:collapse-tags="item.collapseTags || false"
|
||||
:collapse-tags-tooltip="item.collapseTagsTooltip || false"
|
||||
:max-collapse-tags="item.maxCollapseTags || 1"
|
||||
filterable
|
||||
clearable
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
>
|
||||
<el-option
|
||||
v-for="val in item.options"
|
||||
:key="val[item.selectItem.value]"
|
||||
:label="val[item.selectItem.title]"
|
||||
:value="val[item.selectItem.value]"
|
||||
/>
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-else-if="item.type === 'date' || item.type === 'datetime'"
|
||||
v-model="form[item.prop]"
|
||||
:type="item.type"
|
||||
placeholder="选择日期"
|
||||
style="width: 100%"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-else-if="
|
||||
item.type === 'daterange' || item.type === 'datetimerange'
|
||||
"
|
||||
v-model="form[item.prop]"
|
||||
:type="item.type"
|
||||
range-separator="到"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
/>
|
||||
<el-radio-group
|
||||
v-else-if="item.type === 'radio'"
|
||||
v-model="form[item.prop]"
|
||||
class="ml-4"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
>
|
||||
<el-radio
|
||||
v-for="(i, rIndex) in item.radioItem"
|
||||
:key="rIndex"
|
||||
:label="i.value"
|
||||
>{{ i.title }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
<DictSelect
|
||||
v-else-if="item.type === 'dict'"
|
||||
v-model="form[item.prop]"
|
||||
:dict-code="item.dict.dictCdoe"
|
||||
:text-key="item.dict.textKey || ''"
|
||||
:value-key="item.dict.valueKey || ''"
|
||||
:filter="item.dict.filter || ''"
|
||||
:placeholder="item.dict.placeholder"
|
||||
style="width: 100%"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
filterable
|
||||
clearable
|
||||
></DictSelect>
|
||||
<DictCheckBox
|
||||
v-else-if="item.type === 'dictCheckBox'"
|
||||
v-model="form[item.prop]"
|
||||
:dict-code="item.dict.dictCdoe"
|
||||
:text-key="item.dict.textKey || ''"
|
||||
:value-key="item.dict.valueKey || ''"
|
||||
style="width: 100%"
|
||||
:disabled="item.disabled || disabledKeys.includes(item.prop)"
|
||||
:min="item.dict.min"
|
||||
:max="item.dict.max"
|
||||
:border="item.dict.border"
|
||||
></DictCheckBox>
|
||||
<DictRadio
|
||||
v-else-if="item.type === 'dictRadio'"
|
||||
v-model="form[item.prop]"
|
||||
:dict-code="item.dict.dictCdoe"
|
||||
:text-key="item.dict.textKey || ''"
|
||||
:value-key="item.dict.valueKey || ''"
|
||||
></DictRadio>
|
||||
<DepartmentTreeSelect
|
||||
v-else-if="item.type === 'departmentTreeSelect'"
|
||||
v-model="form[item.prop]"
|
||||
v-bind="item.componentProps ?? {}"
|
||||
/>
|
||||
<template v-if="item.slotName" #default>
|
||||
<slot
|
||||
:name="item.slotName"
|
||||
:value="form"
|
||||
:disabled="
|
||||
item.disabled || disabledKeys.includes(item.prop)
|
||||
"
|
||||
></slot>
|
||||
</template>
|
||||
<template #label>
|
||||
<span
|
||||
:class="item && item.rule ? 'requestName' : 'labelName'"
|
||||
>{{ item.label + ':' }}</span
|
||||
>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<slot name="default" :value="form"></slot>
|
||||
</el-form>
|
||||
</CustomDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { ElMessage, type FormInstance } from 'element-plus';
|
||||
import DictSelect from '@/components/DictSelect/index.vue';
|
||||
import DictCheckBox from '@/components/DictCheckBox/index.vue';
|
||||
import CustomDialog from '@/components/CustomDialog/index.vue';
|
||||
import DepartmentTreeSelect from '@/components/DepartmentTreeSelect/index.vue';
|
||||
import DictRadio from '@/components/DictRadio/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
// 表单配置
|
||||
formOptions: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
// 表单字段
|
||||
formData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
disabledKeys: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// margin-top
|
||||
top: {
|
||||
type: String,
|
||||
default: () => '15vh'
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: () => 1000
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: () => null
|
||||
},
|
||||
closeOnClickModal: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['submit', 'reset']);
|
||||
interface FormOptionItem {
|
||||
label: string;
|
||||
prop: string;
|
||||
span: number;
|
||||
type: string;
|
||||
append?: string; // input后缀
|
||||
}
|
||||
// 表单
|
||||
const formRef = ref<FormInstance>();
|
||||
let formType = ''; // 控制form是新增还是编辑
|
||||
let form = reactive(props.formData);
|
||||
// 控制弹窗显隐
|
||||
const dialogVisible = ref(false);
|
||||
const openDialog = (type: string) => {
|
||||
dialogVisible.value = true;
|
||||
formType = type;
|
||||
};
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
let params = cloneDeep(form);
|
||||
// 带出数据和当前模式,用于判断调用新增还是编辑
|
||||
props.formOptions.data.forEach(el => {
|
||||
el.childData.forEach((item: FormOptionItem) => {
|
||||
// 修改时间格式
|
||||
if (params[item.prop]) {
|
||||
if (item.type === 'daterange') {
|
||||
params[item.prop][0] = dayjs(params[item.prop][0]).format(
|
||||
'YYYY-MM-DD'
|
||||
);
|
||||
params[item.prop][1] = dayjs(params[item.prop][1]).format(
|
||||
'YYYY-MM-DD'
|
||||
);
|
||||
} else if (item.type === 'datetimerange') {
|
||||
params[item.prop][0] = dayjs(params[item.prop][0]).format(
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
);
|
||||
params[item.prop][1] = dayjs(params[item.prop][1]).format(
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
);
|
||||
} else if (item.type === 'date') {
|
||||
params[item.prop] = dayjs(params[item.prop]).format('YYYY-MM-DD');
|
||||
} else if (item.type === 'datetime') {
|
||||
params[item.prop] = dayjs(params[item.prop]).format(
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
emit('submit', params, formType);
|
||||
// handleClose();
|
||||
} else {
|
||||
console.log('未完成填写', fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
// 过滤非数字
|
||||
const regStr = (prop: any, integer: boolean) => {
|
||||
let value = form[prop];
|
||||
if (!integer) {
|
||||
value = value.replace(/[^\d.]/g, ''); // 只能输入数字和.
|
||||
value = value.replace(/^\./g, ''); //第一个字符不能是.
|
||||
value = value.replace(/\.{2,}/g, '.'); // 不能连续输入.
|
||||
value = value.replace(/(\.\d+)\./g, '$1'); // .后面不能再输入.
|
||||
value = value.replace(/^0+(\d)/, '$1');
|
||||
}
|
||||
if (integer) {
|
||||
value = value.replace(/[^\d]/g, ''); // 只能输入数字
|
||||
value = value.replace(/^0+(\d)/, '$1');
|
||||
}
|
||||
form[prop] = value;
|
||||
};
|
||||
// 数字输入做出限制
|
||||
const inputNumber = (prop, data) => {
|
||||
if (form[prop] != '') {
|
||||
form[prop] = Number(form[prop]);
|
||||
let num = form[prop];
|
||||
// 保留小数
|
||||
if (data.toFixed) {
|
||||
form[prop] = Number(form[prop].toFixed(data.toFixed));
|
||||
}
|
||||
// 最大值
|
||||
if (data.max && num > data.max) {
|
||||
form[prop] = data.max;
|
||||
ElMessage.warning(`输入值不可超过最大值${data.max}`);
|
||||
}
|
||||
// 最小值
|
||||
if (data.min !== undefined && num < data.min) {
|
||||
form[prop] = data.min;
|
||||
ElMessage.warning(`输入值不可低于最小值${data.min}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
// 关闭后调用
|
||||
const handleClose = () => {
|
||||
resetForm(formRef.value);
|
||||
dialogVisible.value = false;
|
||||
emit('reset');
|
||||
};
|
||||
// 重置校验和已填写数据
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
};
|
||||
// 表单校验
|
||||
const validate = async () => {
|
||||
let res;
|
||||
await formRef.value?.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
res = true;
|
||||
} else res = false;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
const validateField = async props => {
|
||||
let res;
|
||||
await formRef.value?.validateField(props, (valid, fields) => {
|
||||
if (valid) {
|
||||
res = true;
|
||||
} else res = false;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
// 暴露方法和数据
|
||||
defineExpose({ openDialog, form, handleClose, validate, validateField });
|
||||
</script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Forms'
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.form-dialog {
|
||||
.form-item {
|
||||
margin-right: 10px;
|
||||
|
||||
.el-form-item__label {
|
||||
padding: 0 6px 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.el-form-item__label {
|
||||
padding-right: 0;
|
||||
}
|
||||
.el-input-group__append {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
.group {
|
||||
.group-title {
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c6af3;
|
||||
line-height: 22px;
|
||||
margin-left: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: linear-gradient(180deg, #327dfe 0%, #2c6af3 100%);
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
left: -6px;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.labelName {
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.requestName {
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
src/components/IconSelect/index.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const iconRefs = ref([] as Array<string>);
|
||||
|
||||
for (const [key] of Object.entries(ElementPlusIconsVue)) {
|
||||
iconRefs.value.push(key);
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string;
|
||||
}>();
|
||||
|
||||
const Emits = defineEmits(['update:modelValue']);
|
||||
|
||||
const nowSelectIcon = computed({
|
||||
get: () => {
|
||||
// 在每次获取弹窗显示状态时,自动修改宽度
|
||||
// changeDialogWidth();
|
||||
return props.modelValue;
|
||||
},
|
||||
set: nv => {
|
||||
Emits('update:modelValue', nv);
|
||||
}
|
||||
});
|
||||
|
||||
const changeSelectIcon = (icon: string) => {
|
||||
if (icon !== nowSelectIcon.value) {
|
||||
nowSelectIcon.value = icon;
|
||||
} else {
|
||||
nowSelectIcon.value = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
nowSelectIcon
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-icon
|
||||
v-for="item in iconRefs"
|
||||
:key="item"
|
||||
:size="16"
|
||||
:color="
|
||||
nowSelectIcon && item.toLowerCase() === nowSelectIcon.toLowerCase()
|
||||
? '#fff'
|
||||
: undefined
|
||||
"
|
||||
style="font-size: 1.2em; padding: 4px; width: 1.5em; height: 1.5em"
|
||||
:class="{
|
||||
'mr-1': true,
|
||||
'cursor-pointer': true,
|
||||
'bg-primary-light-color':
|
||||
nowSelectIcon && item.toLowerCase() === nowSelectIcon.toLowerCase(),
|
||||
'rounded-sm': true
|
||||
}"
|
||||
@click="changeSelectIcon(item)"
|
||||
>
|
||||
<component :is="item"></component>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
43
src/components/InfoCard/index.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="info-card w-full">
|
||||
<header class="info-card__header">
|
||||
<div class="info-card__prefix mx-2"></div>
|
||||
<div class="h-full text-base" style="line-height: 34px">{{ title }}</div>
|
||||
</header>
|
||||
<section class="min-h-[100px] p-2 pt-0">
|
||||
<slot></slot>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.info-card {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-card__header {
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
background: #f5f7fa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-card__prefix {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
background: linear-gradient(180deg, #327dfe 0%, #2c6af3 100%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
104
src/components/InfoCard/infoTable.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
data: any;
|
||||
configs: Array<{
|
||||
name: string;
|
||||
key: string;
|
||||
formatter?: (data: any, valueKey: string) => any;
|
||||
alarmType?: any;
|
||||
}>;
|
||||
darkmode?: boolean; // 暗黑模式兼容
|
||||
noStrike?: boolean; // 斑马纹
|
||||
noColon?: boolean; // 自带冒号
|
||||
labelWidth?: string;
|
||||
minWidth?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="info-table"
|
||||
:class="{
|
||||
'dark-mode': darkmode,
|
||||
'has-strike': !noStrike,
|
||||
'has-border': noStrike
|
||||
}"
|
||||
:style="{
|
||||
minWidth
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-for="item in configs"
|
||||
:key="item.key"
|
||||
class="flex items-center min-h-[28px] p-1"
|
||||
>
|
||||
<div
|
||||
class="info-table__label shrink-0"
|
||||
:style="{
|
||||
width: labelWidth
|
||||
}"
|
||||
>
|
||||
{{ item.name }}<span v-if="!noColon">:</span>
|
||||
</div>
|
||||
<div class="ml-auto break-all text-[#2C6AF3]">
|
||||
<slot :value-key="item.key" :name="item.key" :data="data">
|
||||
<span
|
||||
class="info-table-item__value"
|
||||
:class="{
|
||||
alarm:
|
||||
typeof item.alarmType === 'function'
|
||||
? item.alarmType(data, item.key)
|
||||
: typeof item.alarmType !== 'undefined' &&
|
||||
data[item.key] === item.alarmType
|
||||
}"
|
||||
>{{
|
||||
item.formatter
|
||||
? item.formatter(data, item.key)
|
||||
: data[item.key] || '-'
|
||||
}}</span
|
||||
>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.info-table {
|
||||
font-size: 14px;
|
||||
|
||||
&.has-strike {
|
||||
& > div:nth-child(2n) {
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-border {
|
||||
& > div {
|
||||
border-bottom: 1px solid #dbdfe6;
|
||||
}
|
||||
|
||||
& > div:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.info-table.has-strike.dark-mode {
|
||||
& > div:nth-child(2n) {
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-table__label {
|
||||
width: 84px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info-table-item__value.alarm {
|
||||
color: #ea4955;
|
||||
}
|
||||
</style>
|
||||
720
src/components/MenuLayout/MenuLayout.vue
Normal file
@ -0,0 +1,720 @@
|
||||
<script setup lang="ts">
|
||||
import { ClickOutside as vClickOutside } from 'element-plus';
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
||||
import usePreferenceStore from '@/stores/preference';
|
||||
import useAccountStore from '@/stores/account';
|
||||
import PreferenceSetting from './PreferenceSetting.vue';
|
||||
import defaultUserAvatar from '@/assets/images/default-user-avatar.webp';
|
||||
import { onBeforeRouteUpdate, useRoute } from 'vue-router';
|
||||
import useMenuTab from './useMenuTab';
|
||||
import SubMenu from './SubMenu.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import PasswordEdit from '@/views/system/user/PasswordEdit.vue';
|
||||
import CustomDialog from '@/components/CustomDialog/index.vue';
|
||||
import { changeMyPassword } from '@/services/api/system/user/index';
|
||||
import { aesEncrypt } from '@/views/login/utils';
|
||||
import {
|
||||
getCaptchaImage as getCaptchaImageAPI,
|
||||
getEncryptString as getEncryptStringAPI
|
||||
} from '@/services/api/auth';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
const preferenceStore = usePreferenceStore();
|
||||
const accountStore = useAccountStore();
|
||||
const menuData = computed(() => {
|
||||
const getAllowMenus = accountStore.rawPermissions?.menu || [];
|
||||
return getAllowMenus;
|
||||
});
|
||||
|
||||
const avatar = computed(() => defaultUserAvatar);
|
||||
// const avatar = computed(() => accountStore.avatar || defaultUserAvatar);
|
||||
const showPreferenceSetting = ref(false);
|
||||
const route = useRoute();
|
||||
const nowRoute = reactive({
|
||||
path: route.path,
|
||||
query: route.query,
|
||||
params: route.params,
|
||||
fullPath: route.fullPath,
|
||||
name: route.name,
|
||||
meta: route.meta
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(to => {
|
||||
nowRoute.path = to.path;
|
||||
nowRoute.name = to.name;
|
||||
nowRoute.meta = to.meta;
|
||||
nowRoute.query = to.query;
|
||||
nowRoute.params = to.params;
|
||||
nowRoute.fullPath = to.fullPath;
|
||||
});
|
||||
|
||||
// 当前路由路径
|
||||
const routePath = computed(() => nowRoute.path);
|
||||
const {
|
||||
menuTabs,
|
||||
routeLoading,
|
||||
keepAliveNames,
|
||||
closeMenuTab,
|
||||
closeAllTabs,
|
||||
closeTabLeft,
|
||||
closeTabRight,
|
||||
onMenuTabKeyChange
|
||||
} = useMenuTab(nowRoute as any);
|
||||
|
||||
// 展示系统设置
|
||||
const showSystemSetting = () => {
|
||||
showPreferenceSetting.value = true;
|
||||
};
|
||||
|
||||
if (accountStore.token) {
|
||||
accountStore.getUser().catch(error => {
|
||||
ElMessage({
|
||||
message: error.response.data?.message,
|
||||
type: 'error'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
|
||||
// accountStore.getUserPermissions();
|
||||
|
||||
const passwordEditForm = ref();
|
||||
const showUserPassword = ref(false);
|
||||
const changeEditPassword = () => {
|
||||
showUserPassword.value = true;
|
||||
};
|
||||
|
||||
const { pending: passwordEditLoading, run: submitEditUserPassword } =
|
||||
useRequest(async () => {
|
||||
if (passwordEditForm.value) {
|
||||
const submitParams = await passwordEditForm.value.validateForm();
|
||||
// 增加AES加密
|
||||
const newPassword = aesEncrypt(
|
||||
submitParams.password,
|
||||
encryptParams.value.key,
|
||||
encryptParams.value.iv
|
||||
);
|
||||
const oldPassword = aesEncrypt(
|
||||
submitParams.oldPassword,
|
||||
encryptParams.value.key,
|
||||
encryptParams.value.iv
|
||||
);
|
||||
let parasm = cloneDeep(submitParams);
|
||||
parasm.oldPassword = oldPassword;
|
||||
parasm.password = newPassword;
|
||||
parasm.confirmPassword = newPassword;
|
||||
const response = await changeMyPassword(parasm);
|
||||
|
||||
ElMessage({
|
||||
message: response.data.message || '操作成功!',
|
||||
type: 'success'
|
||||
});
|
||||
showUserPassword.value = false;
|
||||
} else {
|
||||
ElMessage({
|
||||
message: '请完善表单',
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
accountStore.token &&
|
||||
accountStore.PermissionsRoute instanceof Array &&
|
||||
accountStore.PermissionsRoute.length === 0
|
||||
) {
|
||||
accountStore.getUserPermissions();
|
||||
}
|
||||
|
||||
const { data: encryptParams, run: getEncryptString } = useRequest(
|
||||
async () => {
|
||||
const response = await getEncryptStringAPI();
|
||||
const result = response.data?.result;
|
||||
return { iv: result?.iv, key: result?.key };
|
||||
},
|
||||
{ initialData: { iv: '', key: '' } }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
routeLoading.value = false;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
routeLoading.value = false;
|
||||
const horizontalScroll = document.querySelector(
|
||||
'.host-main-header__menu-horizontal'
|
||||
);
|
||||
if (horizontalScroll) {
|
||||
horizontalScroll.addEventListener('wheel', function (e: any) {
|
||||
e.preventDefault();
|
||||
// 滚动页面的水平位置
|
||||
horizontalScroll.scrollLeft += e.deltaY;
|
||||
});
|
||||
}
|
||||
getEncryptString();
|
||||
});
|
||||
/**
|
||||
* 伸缩菜单
|
||||
*/
|
||||
const autoHide = computed(() => {
|
||||
if (route.query && route.query.autoHide) {
|
||||
return route.query.autoHide === 'true';
|
||||
}
|
||||
return preferenceStore.autoHide;
|
||||
});
|
||||
/**
|
||||
* 伸缩菜单状态记录
|
||||
*/
|
||||
const autoHideShowState = ref(false);
|
||||
/**
|
||||
* 布局模式
|
||||
*/
|
||||
const layoutMenuMode = computed(() => {
|
||||
if (route.query && route.query.layoutMenuMode) {
|
||||
return route.query.layoutMenuMode;
|
||||
}
|
||||
return preferenceStore.layoutMenuMode;
|
||||
});
|
||||
/**
|
||||
* 标签页隐藏
|
||||
*/
|
||||
const tabsHide = computed(() => {
|
||||
if (route.query && route.query.tabsHide) {
|
||||
return route.query.tabsHide === 'true';
|
||||
}
|
||||
return preferenceStore.tabsHide;
|
||||
});
|
||||
|
||||
const routeViewBackground = computed(() => {
|
||||
if (route.query && typeof route.query.routeViewBackground === 'string') {
|
||||
return route.query.routeViewBackground;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// 标签右键功能
|
||||
const tabActions = [
|
||||
{
|
||||
name: '关闭页面',
|
||||
icon: 'DocumentDelete',
|
||||
handler: () => {
|
||||
closeMenuTab(contextMenuTabId.value);
|
||||
showTabContextMenu.value = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '关闭所有',
|
||||
icon: 'FolderDelete',
|
||||
handler: () => {
|
||||
closeAllTabs();
|
||||
showTabContextMenu.value = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '关闭左侧',
|
||||
icon: 'DArrowLeft',
|
||||
handler: () => {
|
||||
closeTabLeft(contextMenuTabId.value);
|
||||
showTabContextMenu.value = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '关闭右侧',
|
||||
icon: 'DArrowRight',
|
||||
handler: () => {
|
||||
closeTabRight(contextMenuTabId.value);
|
||||
showTabContextMenu.value = false;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const showTabContextMenu = ref(false);
|
||||
const contextMenuTabId = ref();
|
||||
const tabContextMenuPosition = reactive({
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
const openContextMenu = evt => {
|
||||
if (evt.srcElement.id) {
|
||||
// console.error(evt, evt.srcElement.id);
|
||||
contextMenuTabId.value = evt.srcElement.id.split('-')[1];
|
||||
if (contextMenuTabId.value !== '/home') {
|
||||
showTabContextMenu.value = true;
|
||||
tabContextMenuPosition.x = evt.clientX;
|
||||
tabContextMenuPosition.y = evt.clientY + 10;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="host-main-layout__container">
|
||||
<header
|
||||
class="host-main-header__container bg-primary-light-color dark:bg-primary-dark-color border-b border-b-border-color dark:border-b-border-dark-color"
|
||||
:class="{
|
||||
'auto-hide': autoHide,
|
||||
'auto-hide__visible': autoHideShowState
|
||||
}"
|
||||
>
|
||||
<img class="host-main-header__logo" src="@/assets/images/logo.png" />
|
||||
|
||||
<div class="host-main-header__menu-horizontal h-full relative">
|
||||
<el-menu
|
||||
v-if="layoutMenuMode === 'horizontal'"
|
||||
mode="horizontal"
|
||||
class="w-full h-full"
|
||||
:default-active="routePath"
|
||||
router
|
||||
style="
|
||||
--el-menu-bg-color: transparent;
|
||||
--el-menu-hover-bg-color: transparent;
|
||||
--el-menu-border-color: transparent;
|
||||
--el-bg-color-overlay: transparent;
|
||||
--el-menu-text-color: white;
|
||||
--el-menu-hover-text-color: #f5f5f5;
|
||||
--el-menu-active-color: white;
|
||||
"
|
||||
ellipsis
|
||||
>
|
||||
<SubMenu :data="menuData"></SubMenu>
|
||||
</el-menu>
|
||||
</div>
|
||||
|
||||
<div class="host-main-header__right">
|
||||
<el-dropdown
|
||||
:tabindex="3000"
|
||||
popper-class="host-main-header__user-popover"
|
||||
>
|
||||
<div class="host-main-header__user">
|
||||
<img class="host-main-header__user-avatar" :src="avatar" />
|
||||
<button
|
||||
style="white-space: nowrap"
|
||||
class="host-main-header__user-label host-header-button hidden md:inline-block"
|
||||
>
|
||||
欢迎您,{{
|
||||
accountStore.realname || accountStore.username || '访客用户'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-if="!!accountStore.token"
|
||||
@click="changeEditPassword"
|
||||
>修改密码</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click="showSystemSetting"
|
||||
>系统偏好设置</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<!-- 弹窗组件 -->
|
||||
<CustomDialog
|
||||
v-model="showUserPassword"
|
||||
confirm-text="提 交"
|
||||
title="修改密码"
|
||||
destroy-on-close
|
||||
:loading="passwordEditLoading"
|
||||
@confirm="submitEditUserPassword"
|
||||
>
|
||||
<PasswordEdit
|
||||
ref="passwordEditForm"
|
||||
type="user"
|
||||
:data="{
|
||||
username: accountStore.rawUser?.username
|
||||
}"
|
||||
></PasswordEdit>
|
||||
</CustomDialog>
|
||||
|
||||
<el-drawer
|
||||
v-model="showPreferenceSetting"
|
||||
title="个人化设置"
|
||||
:size="360"
|
||||
:with-header="false"
|
||||
>
|
||||
<PreferenceSetting></PreferenceSetting>
|
||||
</el-drawer>
|
||||
|
||||
<button
|
||||
class="host-header-button ml-2 md:ml-5"
|
||||
style="white-space: nowrap"
|
||||
@click="accountStore.logout(true)"
|
||||
>
|
||||
<span class="icon iconfont gci-export" style="margin-right: 5px" />
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<button
|
||||
v-if="autoHide"
|
||||
class="host-main-auto-hide__button bg-primary-light-color dark:bg-primary-dark-color border-l border-b border-border-color dark:border-border-dark-color"
|
||||
:style="{
|
||||
top: autoHideShowState ? (tabsHide ? '60px' : '103px') : '0px'
|
||||
}"
|
||||
@click="autoHideShowState = !autoHideShowState"
|
||||
>
|
||||
<span class="host-main-auto-hide__text">
|
||||
{{ autoHideShowState ? '收起菜单' : '展开菜单' }}
|
||||
</span>
|
||||
</button>
|
||||
<section class="host-main-body__container">
|
||||
<aside
|
||||
:class="[
|
||||
'host-main-body__aside',
|
||||
{
|
||||
'host-main-body__aside--visible': layoutMenuMode === 'vertical',
|
||||
'auto-hide': autoHide,
|
||||
'auto-hide__visible': autoHideShowState
|
||||
}
|
||||
]"
|
||||
>
|
||||
<el-menu style="height: 100%" router :default-active="routePath">
|
||||
<SubMenu :data="menuData"></SubMenu>
|
||||
</el-menu>
|
||||
</aside>
|
||||
<main class="host-main-body__main relative">
|
||||
<div
|
||||
v-show="showTabContextMenu"
|
||||
v-click-outside="() => (showTabContextMenu = false)"
|
||||
class="host-menu-tab__contextmenu"
|
||||
:style="{
|
||||
left: tabContextMenuPosition.x + 'px',
|
||||
top: tabContextMenuPosition.y + 'px'
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-for="tabAction in tabActions"
|
||||
:key="tabAction.name"
|
||||
class="py-1 pr-2 pl-1 hover:bg-primary-light-color/80 hover:text-white cursor-pointer flex items-center justify-center"
|
||||
@click="tabAction.handler"
|
||||
>
|
||||
<el-icon><component :is="tabAction.icon" /></el-icon>
|
||||
<span class="ml-1">
|
||||
{{ tabAction.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header
|
||||
v-if="!tabsHide"
|
||||
class="host-main-body__tab overflow-hidden bg-white dark:bg-primary-dark-color"
|
||||
:class="{
|
||||
'auto-hide': autoHide,
|
||||
'auto-hide__visible': autoHideShowState
|
||||
}"
|
||||
:style="{
|
||||
width:
|
||||
autoHide && layoutMenuMode === 'vertical'
|
||||
? 'calc(100vw - 200px)'
|
||||
: '100%',
|
||||
left: autoHide && layoutMenuMode === 'vertical' ? '200px' : '0'
|
||||
}"
|
||||
>
|
||||
<el-tabs
|
||||
:model-value="routePath"
|
||||
type="card"
|
||||
style="width: 100%; margin-top: 2px"
|
||||
@tab-remove="closeMenuTab"
|
||||
@tab-change="onMenuTabKeyChange"
|
||||
@contextmenu.prevent="openContextMenu($event)"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="(item, index) in menuTabs"
|
||||
:key="item.path"
|
||||
:label="item.meta?.title || '未知菜单标题'"
|
||||
:name="item.path"
|
||||
:closable="index !== 0"
|
||||
>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</header>
|
||||
<section
|
||||
v-loading="routeLoading"
|
||||
class="host-main-body__content bg-[#f1f2f6] dark:bg-[#404040]"
|
||||
:class="{
|
||||
'no-tabs': tabsHide,
|
||||
'auto-hide': autoHide
|
||||
}"
|
||||
:style="{
|
||||
backgroundColor: routeViewBackground
|
||||
? `#${routeViewBackground}`
|
||||
: undefined
|
||||
}"
|
||||
>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="keepAliveNames">
|
||||
<component :is="Component" :key="$route.fullPath" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
<!-- <RouterView :key="$route.fullPath"></RouterView> -->
|
||||
</section>
|
||||
</main>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$header-height: 60px;
|
||||
$tab-height: 34px;
|
||||
.host-main-layout__container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.host-main-header__container {
|
||||
padding: 0 20px;
|
||||
height: $header-height;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.host-main-header__container.auto-hide {
|
||||
position: fixed;
|
||||
top: -60px;
|
||||
width: 100vw;
|
||||
z-index: 2099;
|
||||
transition: top 0.3s;
|
||||
}
|
||||
.host-main-header__container.auto-hide__visible {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.host-main-header__menu-horizontal {
|
||||
width: calc(100vw - 748px);
|
||||
margin-left: 32px;
|
||||
margin-right: auto;
|
||||
overflow: hidden;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.host-main-header__logo {
|
||||
// width: 150px;
|
||||
height: 32px;
|
||||
}
|
||||
.host-main-header__user-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.host-main-header__right,
|
||||
.host-main-header__user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.host-main-header__user-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.host-main-body__container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.host-main-body__aside {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
transition: width 0.2s;
|
||||
border-right: solid 1px var(--el-menu-border-color, #dcdfe6);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
&.host-main-body__aside--visible {
|
||||
// border-right: 1px solid var(--host-border-color);
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
&:deep(.el-menu) {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.host-main-body__aside.auto-hide {
|
||||
position: fixed;
|
||||
left: -200px;
|
||||
top: $header-height;
|
||||
height: calc(100vh - $header-height);
|
||||
z-index: 2080;
|
||||
transition: left 0.3s;
|
||||
}
|
||||
.host-main-body__aside.auto-hide__visible {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.host-main-body__main {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.host-main-body__tab {
|
||||
// height: $tab-height;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// background-color: var(--host-content-background-color);
|
||||
|
||||
&
|
||||
:deep(
|
||||
.n-tabs .n-tabs-nav.n-tabs-nav--card-type .n-tabs-tab.n-tabs-tab--active
|
||||
) {
|
||||
background-color: var(--n-tab-color);
|
||||
border-bottom: 1px solid var(--n-tab-border-color);
|
||||
}
|
||||
|
||||
&
|
||||
:deep(
|
||||
.n-tabs .n-tabs-nav.n-tabs-nav--card-type .n-tabs-tab.n-tabs-tab--closable
|
||||
) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
& :deep(.n-tabs .n-tabs-nav) {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
& :deep(.n-tabs .n-tabs-nav.n-tabs-nav--card-type .n-tabs-pad) {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
& :deep(.n-tabs .n-tabs-nav.n-tabs-nav--card-type .n-tabs-tab-pad) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.host-main-body__tab.auto-hide {
|
||||
position: fixed;
|
||||
top: -42px;
|
||||
transition: top 0.3s;
|
||||
z-index: 2079;
|
||||
}
|
||||
.host-main-body__tab.auto-hide__visible {
|
||||
top: 60px;
|
||||
}
|
||||
|
||||
.host-main-body__content-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.host-main-body__content {
|
||||
width: 100%;
|
||||
height: calc(100% - $tab-height);
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
// &::-webkit-scrollbar {
|
||||
// display: none;
|
||||
// }
|
||||
}
|
||||
.host-main-body__content.auto-hide {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.host-main-body__content.no-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#host-menu-ellipsis {
|
||||
cursor: pointer;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#host-main-body__tab_box {
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
// border-top: 1px solid var(--host-border-color);
|
||||
// border-bottom: 1px solid var(--host-border-color);
|
||||
box-sizing: content-box;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.host-main-auto-hide__button {
|
||||
position: fixed;
|
||||
right: 0px;
|
||||
// width: 28px;
|
||||
width: 72px;
|
||||
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0px;
|
||||
border-bottom-left-radius: 8px;
|
||||
font-size: 12px;
|
||||
border-top: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: top 0.3s;
|
||||
z-index: 2096;
|
||||
transition: width 0.3s;
|
||||
color: #f5f5f5;
|
||||
|
||||
.host-main-auto-hide__text {
|
||||
// white-space: nowrap;
|
||||
// display: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// &:hover {
|
||||
// width: 72px;
|
||||
|
||||
// .host-main-auto-hide__text {
|
||||
// display: inline-block;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.host-header-button {
|
||||
color: #f5f5f5;
|
||||
transition: all 0.3s;
|
||||
line-height: 16px;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
host-main-header__user:hover .host-header-button {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-overlay > div.el-drawer) {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.host-menu-tab__contextmenu {
|
||||
min-width: 100px;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: fixed;
|
||||
list-style-type: none;
|
||||
// padding: 4px;
|
||||
margin: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #4a4a4a;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
131
src/components/MenuLayout/PreferenceSetting.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<section style="position: relative">
|
||||
<div>
|
||||
<h4>导航模式</h4>
|
||||
<ul class="host-preference-list">
|
||||
<li
|
||||
:class="[
|
||||
'host-preference-list__item',
|
||||
'host-preference-list__item-vertical',
|
||||
{ 'host-preference-list__item--checked': isMenuVertical }
|
||||
]"
|
||||
@click="setVerticalMenuMode"
|
||||
/>
|
||||
<li
|
||||
:class="[
|
||||
'host-preference-list__item',
|
||||
'host-preference-list__item-horizontal',
|
||||
{ 'host-preference-list__item--checked': isMenuHorizontal }
|
||||
]"
|
||||
@click="setHorizontalMenuMode"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 32px">
|
||||
<h4>其他设置</h4>
|
||||
<div class="host-setting-item">
|
||||
<div class="host-setting-item-label">暗黑模式</div>
|
||||
<el-switch
|
||||
:model-value="isDarkTheme"
|
||||
size="small"
|
||||
:disabled="true || preferenceStore.preferenceDisabled"
|
||||
@change="toggleTheme"
|
||||
/>
|
||||
</div>
|
||||
<div class="host-setting-item">
|
||||
<div class="host-setting-item-label">伸缩菜单</div>
|
||||
<el-switch
|
||||
v-model="preferenceStore.autoHide"
|
||||
:disabled="preferenceStore.preferenceDisabled"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="host-setting-item">
|
||||
<div class="host-setting-item-label">隐藏标签页</div>
|
||||
<el-switch
|
||||
v-model="preferenceStore.tabsHide"
|
||||
:disabled="preferenceStore.preferenceDisabled"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import usePreferenceStore from '@/stores/preference';
|
||||
|
||||
const preferenceStore = usePreferenceStore();
|
||||
const isMenuHorizontal = computed(
|
||||
() => preferenceStore.layoutMenuMode === 'horizontal'
|
||||
);
|
||||
const isMenuVertical = computed(
|
||||
() => preferenceStore.layoutMenuMode === 'vertical'
|
||||
);
|
||||
|
||||
const isDarkTheme = computed(() => preferenceStore.theme === 'dark');
|
||||
const setVerticalMenuMode = () => {
|
||||
preferenceStore.setLayoutMenuMode('vertical');
|
||||
};
|
||||
const setHorizontalMenuMode = () => {
|
||||
preferenceStore.setLayoutMenuMode('horizontal');
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
preferenceStore.setTheme(isDarkTheme.value ? 'light' : 'dark');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.host-preference-list {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
.host-preference-list__item {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
transition: all 0.2s;
|
||||
&.host-preference-list__item-vertical {
|
||||
background-image: url('@/assets/images/menu-vertical.svg');
|
||||
}
|
||||
&.host-preference-list__item-horizontal {
|
||||
background-image: url('@/assets/images/menu-horizontal.svg');
|
||||
}
|
||||
&.host-preference-list__item--checked {
|
||||
border: 1px solid #3a97f9;
|
||||
}
|
||||
}
|
||||
|
||||
.host-setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 0.4em 0;
|
||||
|
||||
.host-setting-item-label {
|
||||
font-size: 0.88em;
|
||||
width: 120px;
|
||||
opacity: 0.75;
|
||||
// text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.host-preference-tip {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.host-preference-tip__item {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||