添加后台管理框架雏形
This commit is contained in:
		
							
								
								
									
										23
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/dist
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# local env files
 | 
			
		||||
.env.local
 | 
			
		||||
.env.*.local
 | 
			
		||||
 | 
			
		||||
# Log files
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
yarn-error.log*
 | 
			
		||||
pnpm-debug.log*
 | 
			
		||||
 | 
			
		||||
# Editor directories and files
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
*.suo
 | 
			
		||||
*.ntvs*
 | 
			
		||||
*.njsproj
 | 
			
		||||
*.sln
 | 
			
		||||
*.sw?
 | 
			
		||||
							
								
								
									
										21
									
								
								frontend/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/LICENSE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2016-2023 vue-manage-system
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										137
									
								
								frontend/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								frontend/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
# vue-manage-system
 | 
			
		||||
 | 
			
		||||
<a href="https://github.com/vuejs/vue">
 | 
			
		||||
    <img src="https://img.shields.io/badge/vue-3.1.2-brightgreen.svg" alt="vue">
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/vuejs/pinia">
 | 
			
		||||
    <img src="https://img.shields.io/badge/pinia-2.0.14-brightgreen.svg" alt="pinia">
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
 | 
			
		||||
    <img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/lin-xin/vue-manage-system/releases">
 | 
			
		||||
    <img src="https://img.shields.io/github/release/lin-xin/vue-manage-system.svg" alt="GitHub release">
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://lin-xin.gitee.io/example/work/#/donate">
 | 
			
		||||
    <img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
 | 
			
		||||
  </a>
 | 
			
		||||
 | 
			
		||||
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上地址](https://lin-xin.gitee.io/example/work/)
 | 
			
		||||
 | 
			
		||||
> Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0)
 | 
			
		||||
 | 
			
		||||
[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
 | 
			
		||||
 | 
			
		||||
## 赞助商
 | 
			
		||||
 | 
			
		||||
### 好问
 | 
			
		||||
 | 
			
		||||
[<img src="https://static.bestqa.net/logo/bestqa_haowen.png" width="220" height="100">](https://www.bestqa.net/home/index.html)
 | 
			
		||||
 | 
			
		||||
专业问卷服务,一对一客服,按需定制 
 | 
			
		||||
 | 
			
		||||
## 支持作者
 | 
			
		||||
 | 
			
		||||
请作者喝杯咖啡吧!(微信号:linxin_20)
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## 前言
 | 
			
		||||
 | 
			
		||||
该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript,引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。
 | 
			
		||||
 | 
			
		||||
## 功能
 | 
			
		||||
 | 
			
		||||
-   [x] Element Plus
 | 
			
		||||
-   [x] vite 3
 | 
			
		||||
-   [x] pinia
 | 
			
		||||
-   [x] typescript
 | 
			
		||||
-   [x] 登录/注销
 | 
			
		||||
-   [x] Dashboard
 | 
			
		||||
-   [x] 表格
 | 
			
		||||
-   [x] Tab 选项卡
 | 
			
		||||
-   [x] 表单
 | 
			
		||||
-   [x] 图表 :bar_chart:
 | 
			
		||||
-   [x] 富文本/markdown编辑器
 | 
			
		||||
-   [x] 图片拖拽/裁剪上传
 | 
			
		||||
-   [x] 权限管理
 | 
			
		||||
-   [x] 三级菜单
 | 
			
		||||
-   [x] 自定义图标
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 安装步骤
 | 
			
		||||
> 因为使用vite3,node版本需要 14.18+
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
git clone https://github.com/lin-xin/vue-manage-system.git      // 把模板下载到本地
 | 
			
		||||
cd vue-manage-system    // 进入模板目录
 | 
			
		||||
npm install         // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
 | 
			
		||||
 | 
			
		||||
// 运行
 | 
			
		||||
npm run dev
 | 
			
		||||
 | 
			
		||||
// 执行构建命令,生成的dist文件夹放在服务器下即可访问
 | 
			
		||||
npm run build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 组件使用说明与演示
 | 
			
		||||
 | 
			
		||||
### vue-schart
 | 
			
		||||
 | 
			
		||||
vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://github.com/lin-xin/vue-schart#/) 
 | 
			
		||||
 | 
			
		||||
<p><a href="https://www.npmjs.com/package/vue-schart"><img src="https://img.shields.io/npm/dm/vue-schart.svg" alt="Downloads"></a></p>
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import Schart from "vue-schart"; // 导入Schart组件
 | 
			
		||||
const options = ref({
 | 
			
		||||
    type: "bar",
 | 
			
		||||
    title: {
 | 
			
		||||
        text: "最近一周各品类销售图",
 | 
			
		||||
    },
 | 
			
		||||
    labels: ["周一", "周二", "周三", "周四", "周五"],
 | 
			
		||||
    datasets: [
 | 
			
		||||
        {
 | 
			
		||||
            label: "家电",
 | 
			
		||||
            data: [234, 278, 270, 190, 230],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: "百货",
 | 
			
		||||
            data: [164, 178, 190, 135, 160],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            label: "食品",
 | 
			
		||||
            data: [144, 198, 150, 235, 120],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
    .wrapper {
 | 
			
		||||
        width: 7rem;
 | 
			
		||||
        height: 5rem;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 项目截图
 | 
			
		||||
 | 
			
		||||
### 登录
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### 首页
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
 | 
			
		||||
							
								
								
									
										5
									
								
								frontend/auto-imports.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								frontend/auto-imports.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
// Generated by 'unplugin-auto-import'
 | 
			
		||||
export {}
 | 
			
		||||
declare global {
 | 
			
		||||
  const ElMessage: typeof import('element-plus/es')['ElMessage']
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								frontend/components.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								frontend/components.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
// generated by unplugin-vue-components
 | 
			
		||||
// We suggest you to commit this file into source control
 | 
			
		||||
// Read more: https://github.com/vuejs/core/pull/3399
 | 
			
		||||
import '@vue/runtime-core'
 | 
			
		||||
 | 
			
		||||
export {}
 | 
			
		||||
 | 
			
		||||
declare module '@vue/runtime-core' {
 | 
			
		||||
  export interface GlobalComponents {
 | 
			
		||||
    Calender: typeof import('./src/components/calender.vue')['default']
 | 
			
		||||
    ContextMenu: typeof import('./src/components/context-menu.vue')['default']
 | 
			
		||||
    ElAffix: typeof import('element-plus/es')['ElAffix']
 | 
			
		||||
    ElAvatar: typeof import('element-plus/es')['ElAvatar']
 | 
			
		||||
    ElButton: typeof import('element-plus/es')['ElButton']
 | 
			
		||||
    ElCard: typeof import('element-plus/es')['ElCard']
 | 
			
		||||
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
 | 
			
		||||
    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
 | 
			
		||||
    ElCol: typeof import('element-plus/es')['ElCol']
 | 
			
		||||
    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
 | 
			
		||||
    ElDialog: typeof import('element-plus/es')['ElDialog']
 | 
			
		||||
    ElDivider: typeof import('element-plus/es')['ElDivider']
 | 
			
		||||
    ElDropdown: typeof import('element-plus/es')['ElDropdown']
 | 
			
		||||
    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
 | 
			
		||||
    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
 | 
			
		||||
    ElEmpty: typeof import('element-plus/es')['ElEmpty']
 | 
			
		||||
    ElForm: typeof import('element-plus/es')['ElForm']
 | 
			
		||||
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
 | 
			
		||||
    ElIcon: typeof import('element-plus/es')['ElIcon']
 | 
			
		||||
    ElImage: typeof import('element-plus/es')['ElImage']
 | 
			
		||||
    ElInput: typeof import('element-plus/es')['ElInput']
 | 
			
		||||
    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
 | 
			
		||||
    ElMenu: typeof import('element-plus/es')['ElMenu']
 | 
			
		||||
    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
 | 
			
		||||
    ElOption: typeof import('element-plus/es')['ElOption']
 | 
			
		||||
    ElPagination: typeof import('element-plus/es')['ElPagination']
 | 
			
		||||
    ElProgress: typeof import('element-plus/es')['ElProgress']
 | 
			
		||||
    ElRadio: typeof import('element-plus/es')['ElRadio']
 | 
			
		||||
    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
 | 
			
		||||
    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
 | 
			
		||||
    ElRow: typeof import('element-plus/es')['ElRow']
 | 
			
		||||
    ElSelect: typeof import('element-plus/es')['ElSelect']
 | 
			
		||||
    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
 | 
			
		||||
    ElSwitch: typeof import('element-plus/es')['ElSwitch']
 | 
			
		||||
    ElTable: typeof import('element-plus/es')['ElTable']
 | 
			
		||||
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
 | 
			
		||||
    ElTabPane: typeof import('element-plus/es')['ElTabPane']
 | 
			
		||||
    ElTabs: typeof import('element-plus/es')['ElTabs']
 | 
			
		||||
    ElTag: typeof import('element-plus/es')['ElTag']
 | 
			
		||||
    ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
 | 
			
		||||
    ElTooltip: typeof import('element-plus/es')['ElTooltip']
 | 
			
		||||
    ElTree: typeof import('element-plus/es')['ElTree']
 | 
			
		||||
    ElUpload: typeof import('element-plus/es')['ElUpload']
 | 
			
		||||
    Header: typeof import('./src/components/header.vue')['default']
 | 
			
		||||
    Popover: typeof import('./src/components/popover.vue')['default']
 | 
			
		||||
    RouterLink: typeof import('vue-router')['RouterLink']
 | 
			
		||||
    RouterView: typeof import('vue-router')['RouterView']
 | 
			
		||||
    Sidebar: typeof import('./src/components/sidebar.vue')['default']
 | 
			
		||||
    Tags: typeof import('./src/components/tags.vue')['default']
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="utf-8">
 | 
			
		||||
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
 | 
			
		||||
  <title></title>
 | 
			
		||||
  <link rel="stylesheet" href="https://at.alicdn.com/t/font_830376_qzecyukz0s.css">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
  <noscript>
 | 
			
		||||
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
 | 
			
		||||
      Please enable it to continue.</strong>
 | 
			
		||||
  </noscript>
 | 
			
		||||
  <div id="app"></div>
 | 
			
		||||
  <script type="module" src="/src/main.ts"></script>
 | 
			
		||||
  <!-- built files will be auto injected -->
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										2603
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2603
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										40
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "vue-manage-system",
 | 
			
		||||
	"version": "5.3.0",
 | 
			
		||||
	"private": true,
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"dev": "vite",
 | 
			
		||||
		"build": "vue-tsc --noEmit && vite build",
 | 
			
		||||
		"serve": "vite preview"
 | 
			
		||||
	},
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"@element-plus/icons-vue": "^2.0.9",
 | 
			
		||||
		"axios": "^0.27.2",
 | 
			
		||||
		"echarts": "^5.4.1",
 | 
			
		||||
		"element-plus": "^2.2.14",
 | 
			
		||||
		"md-editor-v3": "^2.2.1",
 | 
			
		||||
		"pinia": "^2.0.20",
 | 
			
		||||
		"vue": "^3.2.37",
 | 
			
		||||
		"vue-cropperjs": "^5.0.0",
 | 
			
		||||
		"vue-router": "^4.1.3",
 | 
			
		||||
		"vue-schart": "^2.0.0",
 | 
			
		||||
		"wangeditor": "^4.7.15",
 | 
			
		||||
		"xlsx": "^0.18.5"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@vitejs/plugin-vue": "^3.0.0",
 | 
			
		||||
		"@vue/compiler-sfc": "^3.1.2",
 | 
			
		||||
		"typescript": "^4.6.4",
 | 
			
		||||
		"unplugin-auto-import": "^0.11.2",
 | 
			
		||||
		"unplugin-vue-components": "^0.22.4",
 | 
			
		||||
		"vite": "^3.0.0",
 | 
			
		||||
		"vite-plugin-cesium": "^1.2.22",
 | 
			
		||||
		"vite-plugin-vue-setup-extend": "^0.4.0",
 | 
			
		||||
		"vue-tsc": "^0.38.4"
 | 
			
		||||
	},
 | 
			
		||||
	"browserslist": [
 | 
			
		||||
		"> 1%",
 | 
			
		||||
		"last 2 versions",
 | 
			
		||||
		"not dead"
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								frontend/src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/src/App.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-config-provider :locale="zhCn">
 | 
			
		||||
        <router-view />
 | 
			
		||||
    </el-config-provider>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ElConfigProvider } from 'element-plus';
 | 
			
		||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
@import './assets/css/main.css';
 | 
			
		||||
@import './assets/css/color-dark.css';
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										8
									
								
								frontend/src/api/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/src/api/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
import request from '../utils/request';
 | 
			
		||||
 | 
			
		||||
export const fetchData = () => {
 | 
			
		||||
    return request({
 | 
			
		||||
        url: './table.json',
 | 
			
		||||
        method: 'get'
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										24
									
								
								frontend/src/assets/css/color-dark.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								frontend/src/assets/css/color-dark.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
.header{
 | 
			
		||||
    background-color: #242f42;
 | 
			
		||||
}
 | 
			
		||||
.login-wrap{
 | 
			
		||||
    /* background: #324157; */
 | 
			
		||||
    background: #051539;
 | 
			
		||||
}
 | 
			
		||||
.plugins-tips{
 | 
			
		||||
    background: #eef1f6;
 | 
			
		||||
}
 | 
			
		||||
.plugins-tips a{
 | 
			
		||||
    color: #20a0ff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-li.active {
 | 
			
		||||
    border: 1px solid #409EFF;
 | 
			
		||||
    background-color: #409EFF;
 | 
			
		||||
}
 | 
			
		||||
.message-title{
 | 
			
		||||
    color: #20a0ff;
 | 
			
		||||
}
 | 
			
		||||
.collapse-btn:hover{
 | 
			
		||||
    background: rgb(40,52,70);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								frontend/src/assets/css/icon.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								frontend/src/assets/css/icon.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
[class*=" el-icon-lx"],
 | 
			
		||||
[class^=el-icon-lx] {
 | 
			
		||||
    font-family: lx-iconfont !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								frontend/src/assets/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								frontend/src/assets/css/main.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
* {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html,
 | 
			
		||||
body,
 | 
			
		||||
#app,
 | 
			
		||||
.wrapper {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  font-family: "PingFang SC", "Helvetica Neue", Helvetica, "microsoft yahei",
 | 
			
		||||
    arial, STHeiTi, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content-box {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 200px;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  top: 70px;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  padding-bottom: 30px;
 | 
			
		||||
  -webkit-transition: left 0.3s ease-in-out;
 | 
			
		||||
  transition: left 0.3s ease-in-out;
 | 
			
		||||
  background: #f0f0f0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content {
 | 
			
		||||
  width: auto;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  overflow: none;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.content-collapse {
 | 
			
		||||
  left: 65px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 普通页面添加 class="container" 即包含滚动条 */
 | 
			
		||||
.container {
 | 
			
		||||
  padding: 30px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.crumbs {
 | 
			
		||||
  margin: 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-table th {
 | 
			
		||||
  background-color: #f5f7fa !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pagination {
 | 
			
		||||
  margin: 20px 0;
 | 
			
		||||
  text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.plugins-tips {
 | 
			
		||||
  padding: 20px 10px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-button + .el-tooltip {
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-table tr:hover {
 | 
			
		||||
  background: #f6faff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mgb20 {
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.move-enter-active,
 | 
			
		||||
.move-leave-active {
 | 
			
		||||
  transition: opacity 0.1s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.move-enter-from,
 | 
			
		||||
.move-leave-to {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*BaseForm*/
 | 
			
		||||
 | 
			
		||||
.form-box {
 | 
			
		||||
  width: 600px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-box .line {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-time-panel__content::after,
 | 
			
		||||
.el-time-panel__content::before {
 | 
			
		||||
  margin-top: -7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-time-spinner__wrapper
 | 
			
		||||
  .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
 | 
			
		||||
  padding-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[class*=" el-icon-"],
 | 
			
		||||
[class^="el-icon-"] {
 | 
			
		||||
  speak: none;
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  font-variant: normal;
 | 
			
		||||
  text-transform: none;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  vertical-align: baseline;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
}
 | 
			
		||||
.el-sub-menu [class^="el-icon-"] {
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  margin-right: 5px;
 | 
			
		||||
  width: 24px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[hidden] {
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/img/img.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/img/img.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 6.0 KiB  | 
							
								
								
									
										310
									
								
								frontend/src/components/calender.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								frontend/src/components/calender.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,310 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="calender-container">
 | 
			
		||||
        <div class="calender-toolbox">
 | 
			
		||||
            <div style="width: 100%;">
 | 
			
		||||
                <slot name="toolbox"></slot>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="calender-title">
 | 
			
		||||
            <div v-for="w in week">
 | 
			
		||||
                <p>{{ w }}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="calender-grid" :style="{ gridTemplateRows: `repeat(${rowCount}, ${Math.ceil(100 / rowCount)}fr)` }">
 | 
			
		||||
            <div v-for="item in dayItem" class="calender-grid-item" :class="item.class">
 | 
			
		||||
                <template v-if="item.type == 'day'">
 | 
			
		||||
                    <!-- 控制按钮 -->
 | 
			
		||||
                    <div class="calender-grid-item-ctrl-btn-container">
 | 
			
		||||
                        <div class="calender-grid-item-ctrl-btn">
 | 
			
		||||
                            <el-icon :size="20" @click="uploadFile(item)">
 | 
			
		||||
                                <UploadFilled />
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <!-- 日期编号 -->
 | 
			
		||||
                    <p class="calender-grid-item-day">{{ item.day }}</p>
 | 
			
		||||
                    <!-- 附件 -->
 | 
			
		||||
                    <div v-if="item.event.length > 0" class="calender-grid-item-attachment-container">
 | 
			
		||||
                        <div v-for="e in item.event" class="calender-grid-item-attachment">
 | 
			
		||||
                            <a class="download-link" :href="e.file.downloadLink" :download="e.file.displayName"
 | 
			
		||||
                                :title="e.file.displayName" @contextmenu.prevent.native="contextMenuRef.openMenu($event)"
 | 
			
		||||
                                @mouseover="showPopover($event, e)" @mouseleave="hidePopover()">
 | 
			
		||||
                                {{ e.file.displayName }}
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- 鼠标悬浮弹窗 -->
 | 
			
		||||
        <filePopover ref="filePopoverRef" />
 | 
			
		||||
 | 
			
		||||
        <!-- 鼠标右键菜单 -->
 | 
			
		||||
        <contextMenu ref="contextMenuRef" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, defineProps, onMounted, onBeforeUpdate, nextTick, defineExpose } from 'vue';
 | 
			
		||||
import contextMenu from './context-menu.vue';
 | 
			
		||||
import filePopover from '../components/file-popover.vue';
 | 
			
		||||
 | 
			
		||||
const filePopoverRef = ref()
 | 
			
		||||
const contextMenuRef = ref()
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    'year': {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    'month': {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    'events': {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        require: false,
 | 
			
		||||
        default: [],
 | 
			
		||||
    },
 | 
			
		||||
    'doUpload': {
 | 
			
		||||
        type: Function,
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const week = [
 | 
			
		||||
    "星期一",
 | 
			
		||||
    "星期二",
 | 
			
		||||
    "星期三",
 | 
			
		||||
    "星期四",
 | 
			
		||||
    "星期五",
 | 
			
		||||
    "星期六",
 | 
			
		||||
    "星期日",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const dayItem = ref([]);
 | 
			
		||||
 | 
			
		||||
const rowCount = ref(5);
 | 
			
		||||
 | 
			
		||||
let isMounted = false;
 | 
			
		||||
 | 
			
		||||
const filePopoverInfo = ref({
 | 
			
		||||
    visable: false,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 绘制月历
 | 
			
		||||
function getMonthCander(year, month, file) {
 | 
			
		||||
    console.log("getMonthCander", isMounted)
 | 
			
		||||
    console.log("file", file)
 | 
			
		||||
    let firstDay = new Date(year, month - 1, 1);
 | 
			
		||||
    let nextMonthFirstDay = new Date(year, (month + 1) - 1, 1);
 | 
			
		||||
 | 
			
		||||
    let dayOfWeek = firstDay.getDay();
 | 
			
		||||
    let space = (dayOfWeek - 1 + 7) % 7; // 前面填充的空白格
 | 
			
		||||
    // console.log("space", space);
 | 
			
		||||
 | 
			
		||||
    let dayCountInMonth = (nextMonthFirstDay - firstDay) / (1000 * 3600 * 24);
 | 
			
		||||
    // console.log("dayCountInMonth", dayCountInMonth);
 | 
			
		||||
 | 
			
		||||
    // 空白填充格
 | 
			
		||||
    dayItem.value.push(...(new Array(space)).fill({
 | 
			
		||||
        "type": "space",
 | 
			
		||||
        "class": "space-item",
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    // 日期格
 | 
			
		||||
    for (let i = 1; i <= dayCountInMonth; i++) {
 | 
			
		||||
        dayItem.value.push({
 | 
			
		||||
            "type": "day",
 | 
			
		||||
            "day": i,
 | 
			
		||||
            "class": "day-item",
 | 
			
		||||
            "event": file.filter((f) => f.day === i),
 | 
			
		||||
            "date": new Date(year, month - 1, i),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    // console.log(dayItem.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    console.log("calender onMounted.", "props:", props)
 | 
			
		||||
    getMonthCander(props.year, props.month, props.events);
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        console.log("calender onMounted nextTick.");
 | 
			
		||||
        isMounted = true;
 | 
			
		||||
    })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onBeforeUpdate(() => {
 | 
			
		||||
    console.log("calender onBeforeUpdate.", "props:", props)
 | 
			
		||||
    if (isMounted) {
 | 
			
		||||
        dayItem.value = [];
 | 
			
		||||
        getMonthCander(props.year, props.month, props.events);
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
function uploadFile(item) {
 | 
			
		||||
    console.log("uploadFile item:", item);
 | 
			
		||||
    if (typeof (props.doUpload) === "function") {
 | 
			
		||||
        props.doUpload(item.date);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 弹出鼠标悬浮窗
 | 
			
		||||
function showPopover($event, fileInfo) {
 | 
			
		||||
    // console.log("showPopover $event:", $event, "fileInfo:", fileInfo, $event.target);
 | 
			
		||||
    filePopoverRef.value.updateInfo({
 | 
			
		||||
        visable: true,
 | 
			
		||||
        left: $event.clientX - $event.offsetX + $event.target.offsetWidth / 2,
 | 
			
		||||
        top: $event.clientY - $event.offsetY + $event.target.offsetHeight + 5,
 | 
			
		||||
        width: 100,
 | 
			
		||||
        height: 100,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hidePopover() {
 | 
			
		||||
    filePopoverRef.value.updateInfo({
 | 
			
		||||
        visable: false,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除文件
 | 
			
		||||
// function removeFile(...args) {
 | 
			
		||||
//     console.log("removeFile args:", args)
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// defineExpose({
 | 
			
		||||
//     removeFile
 | 
			
		||||
// })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.calender-container {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 1200px) {
 | 
			
		||||
    .calender-container {
 | 
			
		||||
        width: 90%;
 | 
			
		||||
        margin: 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 2400px) {
 | 
			
		||||
    .calender-container {
 | 
			
		||||
        width: 80%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-toolbox {
 | 
			
		||||
    /* background-color: aqua; */
 | 
			
		||||
    height: 80px;
 | 
			
		||||
    display: grid;
 | 
			
		||||
    place-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-title {
 | 
			
		||||
    background-color: #A0CFFF;
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(7, 1fr);
 | 
			
		||||
    place-items: center;
 | 
			
		||||
    height: 38px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid {
 | 
			
		||||
    background-color: #C6E2FF;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(7, 1fr);
 | 
			
		||||
    gap: 8px;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item {
 | 
			
		||||
    max-height: 160px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-day {
 | 
			
		||||
    padding-top: 8px;
 | 
			
		||||
    padding-left: 10px;
 | 
			
		||||
    /* padding-bottom: 2px; */
 | 
			
		||||
 | 
			
		||||
    height: 30px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-ctrl-btn-container {
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-ctrl-btn {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 7px;
 | 
			
		||||
    top: 5px;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item:hover .calender-grid-item-ctrl-btn {
 | 
			
		||||
    opacity: .45;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item:hover .calender-grid-item-ctrl-btn:hover {
 | 
			
		||||
    opacity: 0.9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-attachment-container {
 | 
			
		||||
    padding: 0 3px;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    height: calc(100% - 30px);
 | 
			
		||||
    overflow-y: auto;
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
    padding-bottom: 5px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item.day-item {
 | 
			
		||||
    background-color: #ECF5FF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-attachment,
 | 
			
		||||
.download-link {
 | 
			
		||||
    background-color: #A0CFFF;
 | 
			
		||||
    color: black;
 | 
			
		||||
    border-radius: 50px;
 | 
			
		||||
    padding: 0.2px 5px;
 | 
			
		||||
    transition: all 0.12s;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    margin-bottom: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-attachment:hover,
 | 
			
		||||
.calender-grid-item-attachment:hover>.download-link {
 | 
			
		||||
    background-color: #409EFF;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.download-link {
 | 
			
		||||
    display: block;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 600px) {
 | 
			
		||||
 | 
			
		||||
    .calender-title,
 | 
			
		||||
    .calender-grid-item.space-item {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .calender-grid {
 | 
			
		||||
        grid-template-columns: repeat(1, 1fr);
 | 
			
		||||
        grid-template-rows: repeat(31, 120px) !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										79
									
								
								frontend/src/components/context-menu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								frontend/src/components/context-menu.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
 | 
			
		||||
      <li>菜单一</li>
 | 
			
		||||
      <li>菜单二</li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  components: {},
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      visible: false,
 | 
			
		||||
      top: 0,
 | 
			
		||||
      left: 0,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  expose: ['openMenu'],
 | 
			
		||||
  watch: {
 | 
			
		||||
    //   监听属性对象,newValue为新的值,也就是改变后的值
 | 
			
		||||
    visible(newValue, oldValue) {
 | 
			
		||||
      if (newValue) {
 | 
			
		||||
        //菜单显示的时候
 | 
			
		||||
        // document.body.addEventListener,document.body.removeEventListener它们都接受3个参数
 | 
			
		||||
        // ("事件名" , "事件处理函数" , "布尔值");
 | 
			
		||||
        // 在body上添加事件处理程序
 | 
			
		||||
        document.body.addEventListener("click", this.closeMenu);
 | 
			
		||||
      } else {
 | 
			
		||||
        //菜单隐藏的时候
 | 
			
		||||
        // 移除body上添加的事件处理程序
 | 
			
		||||
        document.body.removeEventListener("click", this.closeMenu);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    //右击
 | 
			
		||||
    openMenu(e) {
 | 
			
		||||
      var x = e.pageX; //这个应该是相对于整个浏览器页面的x坐标,左上角为坐标原点(0,0)
 | 
			
		||||
      var y = e.pageY; //这个应该是相对于整个浏览器页面的y坐标,左上角为坐标原点(0,0)
 | 
			
		||||
      this.top = y + 2;
 | 
			
		||||
      this.left = x + 2;
 | 
			
		||||
      this.visible = true; //显示菜单
 | 
			
		||||
    },
 | 
			
		||||
    //关闭菜单
 | 
			
		||||
    closeMenu() {
 | 
			
		||||
      this.visible = false; //关闭菜单
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
<style scoped>
 | 
			
		||||
.contextmenu {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  z-index: 3000;
 | 
			
		||||
  /* //关键样式设置固定定位 */
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
  padding: 5px 0;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  color: #333;
 | 
			
		||||
  box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextmenu li {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 7px 16px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextmenu li:hover {
 | 
			
		||||
  background: #eee;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										189
									
								
								frontend/src/components/header.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								frontend/src/components/header.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="header">
 | 
			
		||||
		<!-- 折叠按钮 -->
 | 
			
		||||
		<div class="collapse-btn" @click="collapseChage">
 | 
			
		||||
			<el-icon v-if="sidebar.collapse">
 | 
			
		||||
				<Expand />
 | 
			
		||||
			</el-icon>
 | 
			
		||||
			<el-icon v-else>
 | 
			
		||||
				<Fold />
 | 
			
		||||
			</el-icon>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="logo">{{ settings.siteFullTitle }}</div>
 | 
			
		||||
		<div class="header-right">
 | 
			
		||||
			<div class="header-user-con">
 | 
			
		||||
				<!-- 消息中心 -->
 | 
			
		||||
				<div class="btn-bell" @click="router.push('/tabs')">
 | 
			
		||||
					<el-tooltip effect="dark" :content="message ? `有${message}条未读消息` : `消息中心`" placement="bottom">
 | 
			
		||||
						<i class="el-icon-lx-notice"></i>
 | 
			
		||||
					</el-tooltip>
 | 
			
		||||
					<span class="btn-bell-badge" v-if="message"></span>
 | 
			
		||||
				</div>
 | 
			
		||||
				<!-- 用户头像 -->
 | 
			
		||||
				<el-avatar class="user-avator" :size="30" :src="imgurl" />
 | 
			
		||||
				<!-- 用户名下拉菜单 -->
 | 
			
		||||
				<el-dropdown class="user-name" trigger="click" @command="handleCommand">
 | 
			
		||||
					<span class="el-dropdown-link">
 | 
			
		||||
						{{ username }}
 | 
			
		||||
						<el-icon class="el-icon--right">
 | 
			
		||||
							<arrow-down />
 | 
			
		||||
						</el-icon>
 | 
			
		||||
					</span>
 | 
			
		||||
					<template #dropdown>
 | 
			
		||||
						<el-dropdown-menu>
 | 
			
		||||
							<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
 | 
			
		||||
								<el-dropdown-item>项目仓库</el-dropdown-item>
 | 
			
		||||
							</a>
 | 
			
		||||
							<el-dropdown-item command="user">个人中心</el-dropdown-item>
 | 
			
		||||
							<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
 | 
			
		||||
						</el-dropdown-menu>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-dropdown>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { onMounted } from 'vue';
 | 
			
		||||
import { useSidebarStore } from '../store/sidebar';
 | 
			
		||||
import { useTagsStore } from '../store/tags';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
import imgurl from '../assets/img/img.jpg';
 | 
			
		||||
import settings from '../utils/settings';
 | 
			
		||||
 | 
			
		||||
const username: string | null = localStorage.getItem('ms_username');
 | 
			
		||||
const message: number = 2;
 | 
			
		||||
 | 
			
		||||
const sidebar = useSidebarStore();
 | 
			
		||||
// 侧边栏折叠
 | 
			
		||||
const collapseChage = () => {
 | 
			
		||||
	sidebar.handleCollapse();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	// 网页打开时不折叠
 | 
			
		||||
	// if (document.body.clientWidth < 1500) {
 | 
			
		||||
	// 	collapseChage();
 | 
			
		||||
	// }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 用户名下拉菜单选择事件
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const handleCommand = (command: string) => {
 | 
			
		||||
	if (command == 'loginout') {
 | 
			
		||||
		// 发送退出登录请求
 | 
			
		||||
		send_request('v1/user/logout', "POST");
 | 
			
		||||
		// 关闭全部标签 (销毁页面对象)
 | 
			
		||||
		const tags = useTagsStore();
 | 
			
		||||
		tags.clearTags();
 | 
			
		||||
		// 清除本地 localStorage
 | 
			
		||||
		localStorage.clear();
 | 
			
		||||
		// localStorage.removeItem('ms_username');
 | 
			
		||||
		// localStorage.removeItem('ms_user_id');
 | 
			
		||||
		// localStorage.removeItem('ms_role_id');
 | 
			
		||||
		// 跳转到登录页面
 | 
			
		||||
		router.push({
 | 
			
		||||
			path: '/login',
 | 
			
		||||
			query: {
 | 
			
		||||
				redirectTo: router.currentRoute.value.path // window.location.href
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
		ElMessage.success('已退出登录');
 | 
			
		||||
	} else if (command == 'user') {
 | 
			
		||||
		router.push('/user');
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped>
 | 
			
		||||
.header {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 70px;
 | 
			
		||||
	font-size: 22px;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.collapse-btn {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	float: left;
 | 
			
		||||
	padding: 0 21px;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header .logo {
 | 
			
		||||
	float: left;
 | 
			
		||||
	/* width: 250px; */
 | 
			
		||||
	line-height: 70px;
 | 
			
		||||
	/* 系统名称不换行 */
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header-right {
 | 
			
		||||
	float: right;
 | 
			
		||||
	padding-right: 50px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header-user-con {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	height: 70px;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-fullscreen {
 | 
			
		||||
	transform: rotate(45deg);
 | 
			
		||||
	margin-right: 5px;
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-bell,
 | 
			
		||||
.btn-fullscreen {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	width: 30px;
 | 
			
		||||
	height: 30px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	border-radius: 15px;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-bell-badge {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	right: 4px;
 | 
			
		||||
	top: 0px;
 | 
			
		||||
	width: 8px;
 | 
			
		||||
	height: 8px;
 | 
			
		||||
	border-radius: 4px;
 | 
			
		||||
	background: #f56c6c;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-bell .el-icon-lx-notice {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-name {
 | 
			
		||||
	margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-avator {
 | 
			
		||||
	margin-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-dropdown-link {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-dropdown-menu__item {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										236
									
								
								frontend/src/components/sidebar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								frontend/src/components/sidebar.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,236 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="sidebar">
 | 
			
		||||
        <el-menu class="sidebar-el-menu" :default-active="onRoutes" :collapse="sidebar.collapse" background-color="#324157"
 | 
			
		||||
            text-color="#bfcbd9" active-text-color="#20a0ff" unique-opened router>
 | 
			
		||||
            <template v-for="item in items">
 | 
			
		||||
                <template v-if="item.subs">
 | 
			
		||||
                    <el-sub-menu :index="item.index" :key="item.index" v-permiss="item.permiss">
 | 
			
		||||
                        <template #title>
 | 
			
		||||
                            <el-icon>
 | 
			
		||||
                                <component :is="item.icon"></component>
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                            <span>{{ item.title }}</span>
 | 
			
		||||
                        </template>
 | 
			
		||||
                        <template v-for="subItem in item.subs">
 | 
			
		||||
                            <el-sub-menu v-if="subItem.subs" :index="subItem.index" :key="subItem.index"
 | 
			
		||||
                                v-permiss="item.permiss">
 | 
			
		||||
                                <template #title>{{ subItem.title }}</template>
 | 
			
		||||
                                <el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
 | 
			
		||||
                                    {{ threeItem.title }}
 | 
			
		||||
                                </el-menu-item>
 | 
			
		||||
                            </el-sub-menu>
 | 
			
		||||
                            <el-menu-item v-else :index="subItem.index" v-permiss="item.permiss">
 | 
			
		||||
                                {{ subItem.title }}
 | 
			
		||||
                            </el-menu-item>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-sub-menu>
 | 
			
		||||
                </template>
 | 
			
		||||
                <template v-else>
 | 
			
		||||
                    <el-menu-item :index="item.index" :key="item.index" v-permiss="item.permiss">
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                            <component :is="item.icon"></component>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                        <template #title>{{ item.title }}</template>
 | 
			
		||||
                    </el-menu-item>
 | 
			
		||||
                </template>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-menu>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { useSidebarStore } from '../store/sidebar';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import settings from '../utils/settings';
 | 
			
		||||
 | 
			
		||||
const items = [
 | 
			
		||||
    // 图标见:https://element-plus.gitee.io/zh-CN/component/icon.html
 | 
			
		||||
    {
 | 
			
		||||
        icon: 'HomeFilled',//'/assets/image/svg/alert_warning.svg',
 | 
			
		||||
        index: '/dashboard',
 | 
			
		||||
        title: '系统首页', // 站点基础信息
 | 
			
		||||
        permiss: 'dashboard',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        icon: 'Monitor',
 | 
			
		||||
        index: '/monitor-data',
 | 
			
		||||
        title: '监测数据',
 | 
			
		||||
        permiss: 'monitor-data',
 | 
			
		||||
        subs: [
 | 
			
		||||
        {
 | 
			
		||||
                index: '/monitor-data-view',
 | 
			
		||||
                title: '查看数据',
 | 
			
		||||
                permiss: 'monitor-data-view',
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        icon: 'BellFilled',
 | 
			
		||||
        index: '/warning',
 | 
			
		||||
        title: '预警信息',
 | 
			
		||||
        permiss: 'warning',
 | 
			
		||||
        subs: [
 | 
			
		||||
            {
 | 
			
		||||
                index: '/warning-view',
 | 
			
		||||
                title: '总览',
 | 
			
		||||
                permiss: 'warning-view',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                index: '/warning-setting',
 | 
			
		||||
                title: '预警设置',
 | 
			
		||||
                permiss: 'warning-setting',
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        icon: 'OfficeBuilding',
 | 
			
		||||
        index: '/equipment',
 | 
			
		||||
        title: '设备信息',
 | 
			
		||||
        permiss: 'equipment',
 | 
			
		||||
        subs: [
 | 
			
		||||
            {
 | 
			
		||||
                index: '/equipment-setting',
 | 
			
		||||
                title: '设备管理',
 | 
			
		||||
                permiss: 'equipment-setting',
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        icon: 'Avatar',
 | 
			
		||||
        index: '/privilege',
 | 
			
		||||
        title: '用户角色',
 | 
			
		||||
        permiss: 'privilege',
 | 
			
		||||
        subs: [
 | 
			
		||||
            {
 | 
			
		||||
                index: '/privilege-user-setting',
 | 
			
		||||
                title: '用户管理', // 列表 增删改
 | 
			
		||||
                permiss: 'privilege-user-setting',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                index: '/privilege-role-setting',
 | 
			
		||||
                title: '角色权限', // 列表 增删改<角色名;角色对应权限>
 | 
			
		||||
                permiss: 'privilege-role-setting',
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ... !settings.debugMode ? [] : [
 | 
			
		||||
        {
 | 
			
		||||
            icon: 'Odometer',
 | 
			
		||||
            index: '/',
 | 
			
		||||
            title: '——————————',
 | 
			
		||||
            permiss: 'default',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            icon: 'Calendar',
 | 
			
		||||
            index: '1',
 | 
			
		||||
            title: '表格相关',
 | 
			
		||||
            permiss: 'default',
 | 
			
		||||
            subs: [
 | 
			
		||||
                {
 | 
			
		||||
                    index: '/table',
 | 
			
		||||
                    title: '常用表格',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    index: '/import',
 | 
			
		||||
                    title: '导入Excel',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    index: '/export',
 | 
			
		||||
                    title: '导出Excel',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            icon: 'DocumentCopy',
 | 
			
		||||
            index: '/tabs',
 | 
			
		||||
            title: 'tab选项卡',
 | 
			
		||||
            permiss: 'default',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            icon: 'Edit',
 | 
			
		||||
            index: '3',
 | 
			
		||||
            title: '表单相关',
 | 
			
		||||
            permiss: 'default',
 | 
			
		||||
            subs: [
 | 
			
		||||
                {
 | 
			
		||||
                    index: '/form',
 | 
			
		||||
                    title: '基本表单',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    index: '/upload',
 | 
			
		||||
                    title: '文件上传',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    index: '4',
 | 
			
		||||
                    title: '三级菜单',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                    subs: [
 | 
			
		||||
                        {
 | 
			
		||||
                            index: '/editor',
 | 
			
		||||
                            title: '富文本编辑器',
 | 
			
		||||
                            permiss: 'default',
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            index: '/markdown',
 | 
			
		||||
                            title: 'markdown编辑器',
 | 
			
		||||
                            permiss: '9default',
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            icon: 'Setting',
 | 
			
		||||
            index: '/icon',
 | 
			
		||||
            title: '自定义图标',
 | 
			
		||||
            permiss: 'default',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            icon: 'PieChart',
 | 
			
		||||
            index: '/charts',
 | 
			
		||||
            title: 'schart图表',
 | 
			
		||||
            permiss: 'default',
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const onRoutes = computed(() => {
 | 
			
		||||
    return route.path;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const sidebar = useSidebarStore();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.sidebar {
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    top: 70px;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebar::-webkit-scrollbar {
 | 
			
		||||
    width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebar-el-menu:not(.el-menu--collapse) {
 | 
			
		||||
    width: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebar>ul {
 | 
			
		||||
    min-height: 100%;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										168
									
								
								frontend/src/components/tags.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								frontend/src/components/tags.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="tags" v-if="tags.show">
 | 
			
		||||
		<ul>
 | 
			
		||||
			<li
 | 
			
		||||
				class="tags-li"
 | 
			
		||||
				v-for="(item, index) in tags.list"
 | 
			
		||||
				:class="{ active: isActive(item.path) }"
 | 
			
		||||
				:key="index"
 | 
			
		||||
			>
 | 
			
		||||
				<router-link :to="item.path" class="tags-li-title">{{ item.title }}</router-link>
 | 
			
		||||
				<el-icon @click="closeTags(index)"><Close /></el-icon>
 | 
			
		||||
			</li>
 | 
			
		||||
		</ul>
 | 
			
		||||
		<div class="tags-close-box">
 | 
			
		||||
			<el-dropdown @command="handleTags">
 | 
			
		||||
				<el-button size="small" type="primary">
 | 
			
		||||
					标签选项
 | 
			
		||||
					<el-icon class="el-icon--right">
 | 
			
		||||
						<arrow-down />
 | 
			
		||||
					</el-icon>
 | 
			
		||||
				</el-button>
 | 
			
		||||
				<template #dropdown>
 | 
			
		||||
					<el-dropdown-menu size="small">
 | 
			
		||||
						<el-dropdown-item command="other">关闭其他</el-dropdown-item>
 | 
			
		||||
						<el-dropdown-item command="all">关闭所有</el-dropdown-item>
 | 
			
		||||
					</el-dropdown-menu>
 | 
			
		||||
				</template>
 | 
			
		||||
			</el-dropdown>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useTagsStore } from '../store/tags';
 | 
			
		||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const isActive = (path: string) => {
 | 
			
		||||
	return path === route.fullPath;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const tags = useTagsStore();
 | 
			
		||||
// 关闭单个标签
 | 
			
		||||
const closeTags = (index: number) => {
 | 
			
		||||
	const delItem = tags.list[index];
 | 
			
		||||
	tags.delTagsItem(index);
 | 
			
		||||
	const item = tags.list[index] ? tags.list[index] : tags.list[index - 1];
 | 
			
		||||
	if (item) {
 | 
			
		||||
		delItem.path === route.fullPath && router.push(item.path);
 | 
			
		||||
	} else {
 | 
			
		||||
		router.push('/');
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 设置标签
 | 
			
		||||
const setTags = (route: any) => {
 | 
			
		||||
	const isExist = tags.list.some(item => {
 | 
			
		||||
		return item.path === route.fullPath;
 | 
			
		||||
	});
 | 
			
		||||
	if (!isExist) {
 | 
			
		||||
		if (tags.list.length >= 8) tags.delTagsItem(0);
 | 
			
		||||
		tags.setTagsItem({
 | 
			
		||||
			name: route.name,
 | 
			
		||||
			title: route.meta.title,
 | 
			
		||||
			path: route.fullPath
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
setTags(route);
 | 
			
		||||
onBeforeRouteUpdate(to => {
 | 
			
		||||
	setTags(to);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 关闭全部标签
 | 
			
		||||
const closeAll = () => {
 | 
			
		||||
	tags.clearTags();
 | 
			
		||||
	router.push('/');
 | 
			
		||||
};
 | 
			
		||||
// 关闭其他标签
 | 
			
		||||
const closeOther = () => {
 | 
			
		||||
	const curItem = tags.list.filter(item => {
 | 
			
		||||
		return item.path === route.fullPath;
 | 
			
		||||
	});
 | 
			
		||||
	tags.closeTagsOther(curItem);
 | 
			
		||||
};
 | 
			
		||||
const handleTags = (command: string) => {
 | 
			
		||||
	command === 'other' ? closeOther() : closeAll();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 关闭当前页面的标签页
 | 
			
		||||
// tags.closeCurrentTag({
 | 
			
		||||
//     $router: router,
 | 
			
		||||
//     $route: route
 | 
			
		||||
// });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.tags {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	height: 30px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	padding-right: 120px;
 | 
			
		||||
	box-shadow: 0 5px 10px #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags ul {
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-li {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	float: left;
 | 
			
		||||
	margin: 3px 5px 2px 3px;
 | 
			
		||||
	border-radius: 3px;
 | 
			
		||||
	font-size: 12px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	height: 23px;
 | 
			
		||||
	border: 1px solid #e9eaec;
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	padding: 0 5px 0 12px;
 | 
			
		||||
	color: #666;
 | 
			
		||||
	-webkit-transition: all 0.3s ease-in;
 | 
			
		||||
	-moz-transition: all 0.3s ease-in;
 | 
			
		||||
	transition: all 0.3s ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-li:not(.active):hover {
 | 
			
		||||
	background: #f8f8f8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-li.active {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-li-title {
 | 
			
		||||
	float: left;
 | 
			
		||||
	max-width: 80px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
	white-space: nowrap;
 | 
			
		||||
	text-overflow: ellipsis;
 | 
			
		||||
	margin-right: 5px;
 | 
			
		||||
	color: #666;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-li.active .tags-li-title {
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-close-box {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	right: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	padding-top: 1px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	width: 110px;
 | 
			
		||||
	height: 30px;
 | 
			
		||||
	background: #fff;
 | 
			
		||||
	box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
 | 
			
		||||
	z-index: 10;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										28
									
								
								frontend/src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								frontend/src/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import { createApp } from 'vue';
 | 
			
		||||
import { createPinia } from 'pinia';
 | 
			
		||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
 | 
			
		||||
import App from './App.vue';
 | 
			
		||||
import router from './router';
 | 
			
		||||
import { usePermissStore } from './store/permiss';
 | 
			
		||||
import 'element-plus/dist/index.css';
 | 
			
		||||
import './assets/css/icon.css';
 | 
			
		||||
 | 
			
		||||
const app = createApp(App);
 | 
			
		||||
app.use(createPinia());
 | 
			
		||||
app.use(router);
 | 
			
		||||
 | 
			
		||||
// 注册elementplus图标
 | 
			
		||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
 | 
			
		||||
    app.component(key, component);
 | 
			
		||||
}
 | 
			
		||||
// 自定义权限指令
 | 
			
		||||
const permiss = usePermissStore();
 | 
			
		||||
app.directive('permiss', {
 | 
			
		||||
    mounted(el, binding) {
 | 
			
		||||
        if (!permiss.key.includes(String(binding.value))) {
 | 
			
		||||
            el['hidden'] = true;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.mount('#app');
 | 
			
		||||
							
								
								
									
										218
									
								
								frontend/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								frontend/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,218 @@
 | 
			
		||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
 | 
			
		||||
import { usePermissStore } from '../store/permiss';
 | 
			
		||||
import Home from '../views/home.vue';
 | 
			
		||||
import settings from '../utils/settings'
 | 
			
		||||
 | 
			
		||||
const routes: RouteRecordRaw[] = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '/',
 | 
			
		||||
        redirect: '/dashboard',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: '/',
 | 
			
		||||
        name: 'Home',
 | 
			
		||||
        component: Home,
 | 
			
		||||
        children: [
 | 
			
		||||
            {
 | 
			
		||||
                path: '/dashboard',
 | 
			
		||||
                name: 'dashboard',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '系统首页',
 | 
			
		||||
                    permiss: 'dashboard',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/dashboard.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/monitor-data-view',
 | 
			
		||||
                name: 'monitor-data-view',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '查看数据',
 | 
			
		||||
                    permiss: 'monitor-data-view',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/monitor-data-view.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/warning-view',
 | 
			
		||||
                name: 'warning-view',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '总览',
 | 
			
		||||
                    permiss: 'warning-view',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/warning-view.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/warning-setting',
 | 
			
		||||
                name: 'warning-setting',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '预警设置',
 | 
			
		||||
                    permiss: 'warning-setting',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/warning-setting.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/equipment-setting',
 | 
			
		||||
                name: 'equipment-setting',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '设备管理',
 | 
			
		||||
                    permiss: 'equipment-setting',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/equipment-setting.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/privilege-user-setting',
 | 
			
		||||
                name: 'privilege-user-setting',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '用户管理',
 | 
			
		||||
                    permiss: 'privilege-user-setting',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/privilege-user-setting.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/privilege-role-setting',
 | 
			
		||||
                name: 'privilege-role-setting',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '角色权限',
 | 
			
		||||
                    permiss: 'privilege-role-setting',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/privilege-role-setting.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/user',
 | 
			
		||||
                name: 'user',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '个人中心',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/user.vue'),
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            {
 | 
			
		||||
                path: '/table',
 | 
			
		||||
                name: 'basetable',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '表格',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/table.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/charts',
 | 
			
		||||
                name: 'basecharts',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '图表',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/charts.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/form',
 | 
			
		||||
                name: 'baseform',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '表单',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/form.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/tabs',
 | 
			
		||||
                name: 'tabs',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: 'tab标签',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/tabs.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/icon',
 | 
			
		||||
                name: 'icon',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '自定义图标',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/icon.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/editor',
 | 
			
		||||
                name: 'editor',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '富文本编辑器',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/editor.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/markdown',
 | 
			
		||||
                name: 'markdown',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: 'markdown编辑器',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/markdown.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/export',
 | 
			
		||||
                name: 'export',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '导出Excel',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/export.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/import',
 | 
			
		||||
                name: 'import',
 | 
			
		||||
                meta: {
 | 
			
		||||
                    title: '导入Excel',
 | 
			
		||||
                    permiss: 'default',
 | 
			
		||||
                },
 | 
			
		||||
                component: () => import('../views/demo/import.vue'),
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: '/login',
 | 
			
		||||
        name: 'Login',
 | 
			
		||||
        meta: {
 | 
			
		||||
            title: '登录',
 | 
			
		||||
        },
 | 
			
		||||
        component: () => import('../views/login.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: '/403',
 | 
			
		||||
        name: '403',
 | 
			
		||||
        meta: {
 | 
			
		||||
            title: '没有权限',
 | 
			
		||||
        },
 | 
			
		||||
        component: () => import('../views/error-page/403.vue'),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const router = createRouter({
 | 
			
		||||
    history: createWebHashHistory(),
 | 
			
		||||
    routes,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.beforeEach((to, from, next) => {
 | 
			
		||||
    document.title = `${to.meta.title} | ${settings.siteTitle}`;
 | 
			
		||||
    const role = localStorage.getItem('ms_username');
 | 
			
		||||
    const permiss = usePermissStore();
 | 
			
		||||
    if (!role && to.path !== '/login') {
 | 
			
		||||
        next({
 | 
			
		||||
            path: '/login',
 | 
			
		||||
            query: {
 | 
			
		||||
                redirectTo: router.currentRoute.value.path // window.location.href
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    } else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
 | 
			
		||||
        // 如果没有权限,则进入403
 | 
			
		||||
        next('/403');
 | 
			
		||||
    } else {
 | 
			
		||||
        next();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default router;
 | 
			
		||||
							
								
								
									
										22
									
								
								frontend/src/store/permiss.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								frontend/src/store/permiss.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
 | 
			
		||||
interface ObjectList {
 | 
			
		||||
	[key: string]: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const usePermissStore = defineStore('permiss', {
 | 
			
		||||
	state: () => {
 | 
			
		||||
		const keys = localStorage.getItem('ms_keys');
 | 
			
		||||
		const defaultList = localStorage.getItem('ms_default_list');
 | 
			
		||||
		return {
 | 
			
		||||
			key: keys ? JSON.parse(keys) : <string[]>[],
 | 
			
		||||
			defaultList: JSON.stringify(defaultList)
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	actions: {
 | 
			
		||||
		handleSet(val: string[]) {
 | 
			
		||||
			this.key = val;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										15
									
								
								frontend/src/store/sidebar.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/src/store/sidebar.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
 | 
			
		||||
export const useSidebarStore = defineStore('sidebar', {
 | 
			
		||||
	state: () => {
 | 
			
		||||
		return {
 | 
			
		||||
			collapse: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	getters: {},
 | 
			
		||||
	actions: {
 | 
			
		||||
		handleCollapse() {
 | 
			
		||||
			this.collapse = !this.collapse;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										53
									
								
								frontend/src/store/tags.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								frontend/src/store/tags.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import { defineStore } from 'pinia';
 | 
			
		||||
 | 
			
		||||
interface ListItem {
 | 
			
		||||
	name: string;
 | 
			
		||||
	path: string;
 | 
			
		||||
	title: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useTagsStore = defineStore('tags', {
 | 
			
		||||
	state: () => {
 | 
			
		||||
		return {
 | 
			
		||||
			list: <ListItem[]>[]
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	getters: {
 | 
			
		||||
		show: state => {
 | 
			
		||||
			return state.list.length > 0;
 | 
			
		||||
		},
 | 
			
		||||
		nameList: state => {
 | 
			
		||||
			return state.list.map(item => item.name);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	actions: {
 | 
			
		||||
		delTagsItem(index: number) {
 | 
			
		||||
			this.list.splice(index, 1);
 | 
			
		||||
		},
 | 
			
		||||
		setTagsItem(data: ListItem) {
 | 
			
		||||
			this.list.push(data);
 | 
			
		||||
		},
 | 
			
		||||
		clearTags() {
 | 
			
		||||
			this.list = [];
 | 
			
		||||
		},
 | 
			
		||||
		closeTagsOther(data: ListItem[]) {
 | 
			
		||||
			this.list = data;
 | 
			
		||||
		},
 | 
			
		||||
		closeCurrentTag(data: any) {
 | 
			
		||||
			for (let i = 0, len = this.list.length; i < len; i++) {
 | 
			
		||||
				const item = this.list[i];
 | 
			
		||||
				if (item.path === data.$route.fullPath) {
 | 
			
		||||
					if (i < len - 1) {
 | 
			
		||||
						data.$router.push(this.list[i + 1].path);
 | 
			
		||||
					} else if (i > 0) {
 | 
			
		||||
						data.$router.push(this.list[i - 1].path);
 | 
			
		||||
					} else {
 | 
			
		||||
						data.$router.push('/');
 | 
			
		||||
					}
 | 
			
		||||
					this.list.splice(i, 1);
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										31
									
								
								frontend/src/utils/request.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/src/utils/request.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import axios, {AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig} from 'axios';
 | 
			
		||||
 | 
			
		||||
const service:AxiosInstance = axios.create({
 | 
			
		||||
    timeout: 6000
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
service.interceptors.request.use(
 | 
			
		||||
    (config: AxiosRequestConfig) => {
 | 
			
		||||
        return config;
 | 
			
		||||
    },
 | 
			
		||||
    (error: AxiosError) => {
 | 
			
		||||
        console.log(error);
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
service.interceptors.response.use(
 | 
			
		||||
    (response: AxiosResponse) => {
 | 
			
		||||
        if (response.status === 200) {
 | 
			
		||||
            return response;
 | 
			
		||||
        } else {
 | 
			
		||||
            Promise.reject("response.status != 200");
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    (error: AxiosError) => {
 | 
			
		||||
        console.log(error);
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export default service;
 | 
			
		||||
							
								
								
									
										52
									
								
								frontend/src/utils/send_request.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/src/utils/send_request.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
import request from './request';
 | 
			
		||||
import settings from './settings';
 | 
			
		||||
 | 
			
		||||
async function send_request(url, method = "POST", params, callback) {
 | 
			
		||||
    if (!url) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let returnData = await request({
 | 
			
		||||
        baseURL: settings.backendHost,
 | 
			
		||||
        url: url,
 | 
			
		||||
        method: method,
 | 
			
		||||
        withCredentials: true,
 | 
			
		||||
        // POST 请求参数
 | 
			
		||||
        data: method.toUpperCase() == "POST" ? params : null,
 | 
			
		||||
        // GET 请求参数
 | 
			
		||||
        params: method.toUpperCase() == "GET" ? params : null,
 | 
			
		||||
    }).then((response) => {
 | 
			
		||||
        let result = response.data;
 | 
			
		||||
        // 判断后端是否处理成功
 | 
			
		||||
        if (!result.isSuccess) {
 | 
			
		||||
            // 用户未登录情况
 | 
			
		||||
            if (result.data && result.data.errCode == 20003) {
 | 
			
		||||
                ElMessage.error(result?.data?.errMsg || "用户未登录");
 | 
			
		||||
                localStorage.clear();
 | 
			
		||||
                // window.location.reload();
 | 
			
		||||
                // 如果同时发出多个请求,可能会多次进来,第二次及之后进入时,hash已经变成 #/login 了
 | 
			
		||||
                if (!window.location.hash.includes("/login")) {
 | 
			
		||||
                    let newUrl = '/#/login?redirectTo=' + encodeURIComponent(window.location.hash.substring(1).split('?')[0])
 | 
			
		||||
                    console.log("newUrl", newUrl)
 | 
			
		||||
                    window.location.href = newUrl;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                ElMessage.error(result?.data?.errMsg || "服务器错误");
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        let data = result.data;
 | 
			
		||||
        if (typeof (callback) === "function") {
 | 
			
		||||
            callback(data);
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }).catch((err) => {
 | 
			
		||||
        console.error(err);
 | 
			
		||||
        ElMessage.error(err.message);
 | 
			
		||||
        // ElMessage.error('请求超时,请检查网络连接');
 | 
			
		||||
        return false;
 | 
			
		||||
    })
 | 
			
		||||
    return returnData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default send_request;
 | 
			
		||||
							
								
								
									
										27
									
								
								frontend/src/utils/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								frontend/src/utils/settings.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
export default {
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否是调试模式
 | 
			
		||||
     * true:  开启调试
 | 
			
		||||
     * false: 关闭调试
 | 
			
		||||
     */
 | 
			
		||||
    debugMode: true,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 网站名称
 | 
			
		||||
     * (网页标题 / 登录页显示)
 | 
			
		||||
     */
 | 
			
		||||
    siteTitle: "社区疫情防控系统",
 | 
			
		||||
    siteFullTitle: "社区疫情防控系统 - 后台管理系统(社区管理员)",
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 开发公司名称
 | 
			
		||||
     * (留空则不显示)
 | 
			
		||||
     */
 | 
			
		||||
    companyName: "",
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 后端接口请求地址
 | 
			
		||||
     * (以 / 结尾)
 | 
			
		||||
     */
 | 
			
		||||
    backendHost: "http://epp.only4.work/",
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										301
									
								
								frontend/src/views/dashboard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								frontend/src/views/dashboard.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,301 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<el-row :gutter="20">
 | 
			
		||||
			<el-col :span="8">
 | 
			
		||||
				<el-card shadow="hover" class="mgb20" style="height: 252px">
 | 
			
		||||
					<div class="user-info">
 | 
			
		||||
						<el-avatar :size="120" :src="imgurl" />
 | 
			
		||||
						<div class="user-info-cont">
 | 
			
		||||
							<div class="user-info-name">{{ name }}</div>
 | 
			
		||||
							<div>{{ role }}</div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="user-info-list">
 | 
			
		||||
						上次登录时间:
 | 
			
		||||
						<span>2022-10-01</span>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="user-info-list">
 | 
			
		||||
						上次登录地点:
 | 
			
		||||
						<span>东莞</span>
 | 
			
		||||
					</div>
 | 
			
		||||
				</el-card>
 | 
			
		||||
				<el-card shadow="hover" style="height: 252px">
 | 
			
		||||
					<template #header>
 | 
			
		||||
						<div class="clearfix">
 | 
			
		||||
							<span>语言详情</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
					Vue
 | 
			
		||||
					<el-progress :percentage="79.4" color="#42b983"></el-progress>
 | 
			
		||||
					TypeScript
 | 
			
		||||
					<el-progress :percentage="14" color="#f1e05a"></el-progress>
 | 
			
		||||
					CSS
 | 
			
		||||
					<el-progress :percentage="5.6"></el-progress>
 | 
			
		||||
					HTML
 | 
			
		||||
					<el-progress :percentage="1" color="#f56c6c"></el-progress>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
			<el-col :span="16">
 | 
			
		||||
				<el-row :gutter="20" class="mgb20">
 | 
			
		||||
					<el-col :span="8">
 | 
			
		||||
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
			
		||||
							<div class="grid-content grid-con-1">
 | 
			
		||||
								<el-icon class="grid-con-icon"><User /></el-icon>
 | 
			
		||||
								<div class="grid-cont-right">
 | 
			
		||||
									<div class="grid-num">1234</div>
 | 
			
		||||
									<div>用户访问量</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</el-card>
 | 
			
		||||
					</el-col>
 | 
			
		||||
					<el-col :span="8">
 | 
			
		||||
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
			
		||||
							<div class="grid-content grid-con-2">
 | 
			
		||||
								<el-icon class="grid-con-icon"><ChatDotRound /></el-icon>
 | 
			
		||||
								<div class="grid-cont-right">
 | 
			
		||||
									<div class="grid-num">321</div>
 | 
			
		||||
									<div>系统消息</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</el-card>
 | 
			
		||||
					</el-col>
 | 
			
		||||
					<el-col :span="8">
 | 
			
		||||
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
			
		||||
							<div class="grid-content grid-con-3">
 | 
			
		||||
								<el-icon class="grid-con-icon"><Goods /></el-icon>
 | 
			
		||||
								<div class="grid-cont-right">
 | 
			
		||||
									<div class="grid-num">500</div>
 | 
			
		||||
									<div>商品数量</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</el-card>
 | 
			
		||||
					</el-col>
 | 
			
		||||
				</el-row>
 | 
			
		||||
				<el-card shadow="hover" style="height: 403px">
 | 
			
		||||
					<template #header>
 | 
			
		||||
						<div class="clearfix">
 | 
			
		||||
							<span>预警列表</span>
 | 
			
		||||
							<el-button style="float: right; padding: 3px 0" text>添加</el-button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
 | 
			
		||||
					<el-table :show-header="false" :data="todoList" style="width: 100%">
 | 
			
		||||
						<el-table-column width="40">
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<el-checkbox v-model="scope.row.status"></el-checkbox>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
						<el-table-column>
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<div
 | 
			
		||||
									class="todo-item"
 | 
			
		||||
									:class="{
 | 
			
		||||
										'todo-item-del': scope.row.status
 | 
			
		||||
									}"
 | 
			
		||||
								>
 | 
			
		||||
									{{ scope.row.title }}
 | 
			
		||||
								</div>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
					</el-table>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
		</el-row>
 | 
			
		||||
		<el-row :gutter="20">
 | 
			
		||||
			<el-col :span="12">
 | 
			
		||||
				<el-card shadow="hover">
 | 
			
		||||
					<schart ref="bar" class="schart" canvasId="bar" :options="options"></schart>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
			<el-col :span="12">
 | 
			
		||||
				<el-card shadow="hover">
 | 
			
		||||
					<schart ref="line" class="schart" canvasId="line" :options="options2"></schart>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
		</el-row>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="dashboard">
 | 
			
		||||
import Schart from 'vue-schart';
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
import imgurl from '../assets/img/img.jpg';
 | 
			
		||||
 | 
			
		||||
const name = localStorage.getItem('ms_username');
 | 
			
		||||
const role: string = name === 'admin' ? '超级管理员' : '普通用户';
 | 
			
		||||
 | 
			
		||||
const options = {
 | 
			
		||||
	type: 'bar',
 | 
			
		||||
	title: {
 | 
			
		||||
		text: '最近一周各品类销售图'
 | 
			
		||||
	},
 | 
			
		||||
	xRorate: 25,
 | 
			
		||||
	labels: ['周一', '周二', '周三', '周四', '周五'],
 | 
			
		||||
	datasets: [
 | 
			
		||||
		{
 | 
			
		||||
			label: '家电',
 | 
			
		||||
			data: [234, 278, 270, 190, 230]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '百货',
 | 
			
		||||
			data: [164, 178, 190, 135, 160]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '食品',
 | 
			
		||||
			data: [144, 198, 150, 235, 120]
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
const options2 = {
 | 
			
		||||
	type: 'line',
 | 
			
		||||
	title: {
 | 
			
		||||
		text: '最近几个月各品类销售趋势图'
 | 
			
		||||
	},
 | 
			
		||||
	labels: ['6月', '7月', '8月', '9月', '10月'],
 | 
			
		||||
	datasets: [
 | 
			
		||||
		{
 | 
			
		||||
			label: '家电',
 | 
			
		||||
			data: [234, 278, 270, 190, 230]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '百货',
 | 
			
		||||
			data: [164, 178, 150, 135, 160]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '食品',
 | 
			
		||||
			data: [74, 118, 200, 235, 90]
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
const todoList = reactive([
 | 
			
		||||
	{
 | 
			
		||||
		title: '今天要修复100个bug',
 | 
			
		||||
		status: false
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		title: '今天要修复100个bug',
 | 
			
		||||
		status: false
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		title: '今天要写100行代码加几个bug吧',
 | 
			
		||||
		status: false
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		title: '今天要修复100个bug',
 | 
			
		||||
		status: false
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		title: '今天要修复100个bug',
 | 
			
		||||
		status: true
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		title: '今天要写100行代码加几个bug吧',
 | 
			
		||||
		status: true
 | 
			
		||||
	}
 | 
			
		||||
]);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.el-row {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-content {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	height: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-cont-right {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
	color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-num {
 | 
			
		||||
	font-size: 30px;
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-icon {
 | 
			
		||||
	font-size: 50px;
 | 
			
		||||
	width: 100px;
 | 
			
		||||
	height: 100px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	line-height: 100px;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-1 .grid-con-icon {
 | 
			
		||||
	background: rgb(45, 140, 240);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-1 .grid-num {
 | 
			
		||||
	color: rgb(45, 140, 240);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-2 .grid-con-icon {
 | 
			
		||||
	background: rgb(100, 213, 114);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-2 .grid-num {
 | 
			
		||||
	color: rgb(100, 213, 114);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-3 .grid-con-icon {
 | 
			
		||||
	background: rgb(242, 94, 67);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-3 .grid-num {
 | 
			
		||||
	color: rgb(242, 94, 67);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	padding-bottom: 20px;
 | 
			
		||||
	border-bottom: 2px solid #ccc;
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-cont {
 | 
			
		||||
	padding-left: 50px;
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
	color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-cont div:first-child {
 | 
			
		||||
	font-size: 30px;
 | 
			
		||||
	color: #222;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-list {
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
	color: #999;
 | 
			
		||||
	line-height: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-list span {
 | 
			
		||||
	margin-left: 70px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mgb20 {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.todo-item {
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.todo-item-del {
 | 
			
		||||
	text-decoration: line-through;
 | 
			
		||||
	color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.schart {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 300px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										127
									
								
								frontend/src/views/demo/charts.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								frontend/src/views/demo/charts.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<div class="plugins-tips">
 | 
			
		||||
			vue-schart:vue.js封装sChart.js的图表组件。 访问地址:
 | 
			
		||||
			<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="schart-box">
 | 
			
		||||
			<div class="content-title">柱状图</div>
 | 
			
		||||
			<schart class="schart" canvasId="bar" :options="options1"></schart>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="schart-box">
 | 
			
		||||
			<div class="content-title">折线图</div>
 | 
			
		||||
			<schart class="schart" canvasId="line" :options="options2"></schart>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="schart-box">
 | 
			
		||||
			<div class="content-title">饼状图</div>
 | 
			
		||||
			<schart class="schart" canvasId="pie" :options="options3"></schart>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="schart-box">
 | 
			
		||||
			<div class="content-title">环形图</div>
 | 
			
		||||
			<schart class="schart" canvasId="ring" :options="options4"></schart>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="basecharts">
 | 
			
		||||
import Schart from 'vue-schart';
 | 
			
		||||
 | 
			
		||||
const options1 = {
 | 
			
		||||
	type: 'bar',
 | 
			
		||||
	title: {
 | 
			
		||||
		text: '最近一周各品类销售图'
 | 
			
		||||
	},
 | 
			
		||||
	bgColor: '#fbfbfb',
 | 
			
		||||
	labels: ['周一', '周二', '周三', '周四', '周五'],
 | 
			
		||||
	datasets: [
 | 
			
		||||
		{
 | 
			
		||||
			label: '家电',
 | 
			
		||||
			fillColor: 'rgba(241, 49, 74, 0.5)',
 | 
			
		||||
			data: [234, 278, 270, 190, 230]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '百货',
 | 
			
		||||
			data: [164, 178, 190, 135, 160]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '食品',
 | 
			
		||||
			data: [144, 198, 150, 235, 120]
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
const options2 = {
 | 
			
		||||
	type: 'line',
 | 
			
		||||
	title: {
 | 
			
		||||
		text: '最近几个月各品类销售趋势图'
 | 
			
		||||
	},
 | 
			
		||||
	bgColor: '#fbfbfb',
 | 
			
		||||
	labels: ['6月', '7月', '8月', '9月', '10月'],
 | 
			
		||||
	datasets: [
 | 
			
		||||
		{
 | 
			
		||||
			label: '家电',
 | 
			
		||||
			data: [234, 278, 270, 190, 230]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '百货',
 | 
			
		||||
			data: [164, 178, 150, 135, 160]
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			label: '食品',
 | 
			
		||||
			data: [114, 138, 200, 235, 190]
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
const options3 = {
 | 
			
		||||
	type: 'pie',
 | 
			
		||||
	title: {
 | 
			
		||||
		text: '服装品类销售饼状图'
 | 
			
		||||
	},
 | 
			
		||||
	legend: {
 | 
			
		||||
		position: 'left'
 | 
			
		||||
	},
 | 
			
		||||
	bgColor: '#fbfbfb',
 | 
			
		||||
	labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
 | 
			
		||||
	datasets: [
 | 
			
		||||
		{
 | 
			
		||||
			data: [334, 278, 190, 235, 260, 200, 141]
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
const options4 = {
 | 
			
		||||
	type: 'ring',
 | 
			
		||||
	title: {
 | 
			
		||||
		text: '环形三等分'
 | 
			
		||||
	},
 | 
			
		||||
	showValue: false,
 | 
			
		||||
	legend: {
 | 
			
		||||
		position: 'bottom',
 | 
			
		||||
		bottom: 40
 | 
			
		||||
	},
 | 
			
		||||
	bgColor: '#fbfbfb',
 | 
			
		||||
	labels: ['vue', 'react', 'angular'],
 | 
			
		||||
	datasets: [
 | 
			
		||||
		{
 | 
			
		||||
			data: [500, 500, 500]
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.schart-box {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	margin: 20px;
 | 
			
		||||
}
 | 
			
		||||
.schart {
 | 
			
		||||
	width: 600px;
 | 
			
		||||
	height: 400px;
 | 
			
		||||
}
 | 
			
		||||
.content-title {
 | 
			
		||||
	clear: both;
 | 
			
		||||
	font-weight: 400;
 | 
			
		||||
	line-height: 50px;
 | 
			
		||||
	margin: 10px 0;
 | 
			
		||||
	font-size: 22px;
 | 
			
		||||
	color: #1f2f3d;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										37
									
								
								frontend/src/views/demo/editor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								frontend/src/views/demo/editor.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<div class="plugins-tips">
 | 
			
		||||
			wangEditor:轻量级 web 富文本编辑器,配置方便,使用简单。 访问地址:
 | 
			
		||||
			<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="mgb20" ref="editor"></div>
 | 
			
		||||
		<el-button type="primary" @click="syncHTML">提交</el-button>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="editor">
 | 
			
		||||
import WangEditor from 'wangeditor';
 | 
			
		||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
 | 
			
		||||
 | 
			
		||||
const editor = ref(null);
 | 
			
		||||
const content = reactive({
 | 
			
		||||
	html: '',
 | 
			
		||||
	text: ''
 | 
			
		||||
});
 | 
			
		||||
let instance: any;
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	instance = new WangEditor(editor.value);
 | 
			
		||||
	instance.config.zIndex = 1;
 | 
			
		||||
	instance.create();
 | 
			
		||||
});
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
	instance.destroy();
 | 
			
		||||
	instance = null;
 | 
			
		||||
});
 | 
			
		||||
const syncHTML = () => {
 | 
			
		||||
	content.html = instance.txt.html();
 | 
			
		||||
	console.log(content.html);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style></style>
 | 
			
		||||
							
								
								
									
										98
									
								
								frontend/src/views/demo/export.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								frontend/src/views/demo/export.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <div class="handle-box">
 | 
			
		||||
                <el-button type="primary" @click="exportXlsx">导出Excel</el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-table :data="tableData" border class="table" header-cell-class-name="table-header">
 | 
			
		||||
                <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="姓名"></el-table-column>
 | 
			
		||||
                <el-table-column prop="sno" label="学号"></el-table-column>
 | 
			
		||||
                <el-table-column prop="class" label="班级"></el-table-column>
 | 
			
		||||
                <el-table-column prop="age" label="年龄"></el-table-column>
 | 
			
		||||
                <el-table-column prop="sex" label="性别"></el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="export">
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import * as XLSX from 'xlsx';
 | 
			
		||||
 | 
			
		||||
interface TableItem {
 | 
			
		||||
    id: number;
 | 
			
		||||
    name: string;
 | 
			
		||||
    sno: string;
 | 
			
		||||
    class: string;
 | 
			
		||||
    age: string;
 | 
			
		||||
    sex: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tableData = ref<TableItem[]>([]);
 | 
			
		||||
// 获取表格数据
 | 
			
		||||
const getData = () => {
 | 
			
		||||
    tableData.value = [
 | 
			
		||||
        {
 | 
			
		||||
            id: 1,
 | 
			
		||||
            name: '小明',
 | 
			
		||||
            sno: 'S001',
 | 
			
		||||
            class: '一班',
 | 
			
		||||
            age: '10',
 | 
			
		||||
            sex: '男',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            id: 2,
 | 
			
		||||
            name: '小红',
 | 
			
		||||
            sno: 'S002',
 | 
			
		||||
            class: '一班',
 | 
			
		||||
            age: '9',
 | 
			
		||||
            sex: '女',
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
const list = [['序号', '姓名', '学号', '班级', '年龄', '性别']];
 | 
			
		||||
const exportXlsx = () => {
 | 
			
		||||
    tableData.value.map((item: any, i: number) => {
 | 
			
		||||
        const arr: any[] = [i + 1];
 | 
			
		||||
        arr.push(...[item.name, item.sno, item.class, item.age, item.sex]);
 | 
			
		||||
        list.push(arr);
 | 
			
		||||
    });
 | 
			
		||||
    let WorkSheet = XLSX.utils.aoa_to_sheet(list);
 | 
			
		||||
    let new_workbook = XLSX.utils.book_new();
 | 
			
		||||
    XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页');
 | 
			
		||||
    XLSX.writeFile(new_workbook, `表格.xlsx`);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.handle-box {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-select {
 | 
			
		||||
    width: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-input {
 | 
			
		||||
    width: 300px;
 | 
			
		||||
}
 | 
			
		||||
.table {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
.red {
 | 
			
		||||
    color: #f56c6c;
 | 
			
		||||
}
 | 
			
		||||
.mr10 {
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
.table-td-thumb {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    width: 40px;
 | 
			
		||||
    height: 40px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										156
									
								
								frontend/src/views/demo/form.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								frontend/src/views/demo/form.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <div class="form-box">
 | 
			
		||||
            <el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
 | 
			
		||||
                <el-form-item label="表单名称" prop="name">
 | 
			
		||||
                    <el-input v-model="form.name"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="选择器" prop="region">
 | 
			
		||||
                    <el-select v-model="form.region" placeholder="请选择">
 | 
			
		||||
                        <el-option key="小明" label="小明" value="小明"></el-option>
 | 
			
		||||
                        <el-option key="小红" label="小红" value="小红"></el-option>
 | 
			
		||||
                        <el-option key="小白" label="小白" value="小白"></el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="日期时间">
 | 
			
		||||
                    <el-col :span="11">
 | 
			
		||||
                        <el-form-item prop="date1">
 | 
			
		||||
                            <el-date-picker
 | 
			
		||||
                                type="date"
 | 
			
		||||
                                placeholder="选择日期"
 | 
			
		||||
                                v-model="form.date1"
 | 
			
		||||
                                style="width: 100%"
 | 
			
		||||
                            ></el-date-picker>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                    <el-col class="line" :span="2">-</el-col>
 | 
			
		||||
                    <el-col :span="11">
 | 
			
		||||
                        <el-form-item prop="date2">
 | 
			
		||||
                            <el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%">
 | 
			
		||||
                            </el-time-picker>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="城市级联" prop="options">
 | 
			
		||||
                    <el-cascader :options="options" v-model="form.options"></el-cascader>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="选择开关" prop="delivery">
 | 
			
		||||
                    <el-switch v-model="form.delivery"></el-switch>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="多选框" prop="type">
 | 
			
		||||
                    <el-checkbox-group v-model="form.type">
 | 
			
		||||
                        <el-checkbox label="小明" name="type"></el-checkbox>
 | 
			
		||||
                        <el-checkbox label="小红" name="type"></el-checkbox>
 | 
			
		||||
                        <el-checkbox label="小白" name="type"></el-checkbox>
 | 
			
		||||
                    </el-checkbox-group>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="单选框" prop="resource">
 | 
			
		||||
                    <el-radio-group v-model="form.resource">
 | 
			
		||||
                        <el-radio label="小明"></el-radio>
 | 
			
		||||
                        <el-radio label="小红"></el-radio>
 | 
			
		||||
                        <el-radio label="小白"></el-radio>
 | 
			
		||||
                    </el-radio-group>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item label="文本框" prop="desc">
 | 
			
		||||
                    <el-input type="textarea" rows="5" v-model="form.desc"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
 | 
			
		||||
                    <el-button @click="onReset(formRef)">重置表单</el-button>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="baseform">
 | 
			
		||||
import { reactive, ref } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import type { FormInstance, FormRules } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
const options = [
 | 
			
		||||
    {
 | 
			
		||||
        value: 'guangdong',
 | 
			
		||||
        label: '广东省',
 | 
			
		||||
        children: [
 | 
			
		||||
            {
 | 
			
		||||
                value: 'guangzhou',
 | 
			
		||||
                label: '广州市',
 | 
			
		||||
                children: [
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'tianhe',
 | 
			
		||||
                        label: '天河区',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'haizhu',
 | 
			
		||||
                        label: '海珠区',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                value: 'dongguan',
 | 
			
		||||
                label: '东莞市',
 | 
			
		||||
                children: [
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'changan',
 | 
			
		||||
                        label: '长安镇',
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'humen',
 | 
			
		||||
                        label: '虎门镇',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        value: 'hunan',
 | 
			
		||||
        label: '湖南省',
 | 
			
		||||
        children: [
 | 
			
		||||
            {
 | 
			
		||||
                value: 'changsha',
 | 
			
		||||
                label: '长沙市',
 | 
			
		||||
                children: [
 | 
			
		||||
                    {
 | 
			
		||||
                        value: 'yuelu',
 | 
			
		||||
                        label: '岳麓区',
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
const rules: FormRules = {
 | 
			
		||||
    name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
 | 
			
		||||
};
 | 
			
		||||
const formRef = ref<FormInstance>();
 | 
			
		||||
const form = reactive({
 | 
			
		||||
    name: '',
 | 
			
		||||
    region: '',
 | 
			
		||||
    date1: '',
 | 
			
		||||
    date2: '',
 | 
			
		||||
    delivery: true,
 | 
			
		||||
    type: ['小明'],
 | 
			
		||||
    resource: '小红',
 | 
			
		||||
    desc: '',
 | 
			
		||||
    options: [],
 | 
			
		||||
});
 | 
			
		||||
// 提交
 | 
			
		||||
const onSubmit = (formEl: FormInstance | undefined) => {
 | 
			
		||||
    // 表单校验
 | 
			
		||||
    if (!formEl) return;
 | 
			
		||||
    formEl.validate((valid) => {
 | 
			
		||||
        if (valid) {
 | 
			
		||||
            console.log(form);
 | 
			
		||||
            ElMessage.success('提交成功!');
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
// 重置
 | 
			
		||||
const onReset = (formEl: FormInstance | undefined) => {
 | 
			
		||||
    if (!formEl) return;
 | 
			
		||||
    formEl.resetFields();
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										212
									
								
								frontend/src/views/demo/icon.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								frontend/src/views/demo/icon.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,212 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<h2>使用方法</h2>
 | 
			
		||||
		<p style="line-height: 50px">
 | 
			
		||||
			直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{ iconList.length }}个图标)
 | 
			
		||||
		</p>
 | 
			
		||||
		<p class="example-p">
 | 
			
		||||
			<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i>
 | 
			
		||||
			<span><i class="el-icon-lx-redpacket_fill"></i></span>
 | 
			
		||||
		</p>
 | 
			
		||||
		<p class="example-p">
 | 
			
		||||
			<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
 | 
			
		||||
			<span><i class="el-icon-lx-weibo"></i></span>
 | 
			
		||||
		</p>
 | 
			
		||||
		<p class="example-p">
 | 
			
		||||
			<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i>
 | 
			
		||||
			<span><i class="el-icon-lx-emojifill"></i></span>
 | 
			
		||||
		</p>
 | 
			
		||||
		<br />
 | 
			
		||||
		<h2>图标</h2>
 | 
			
		||||
		<div class="search-box">
 | 
			
		||||
			<el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input>
 | 
			
		||||
		</div>
 | 
			
		||||
		<ul>
 | 
			
		||||
			<li class="icon-li" v-for="(item, index) in list" :key="index">
 | 
			
		||||
				<div class="icon-li-content">
 | 
			
		||||
					<i :class="`el-icon-lx-${item}`"></i>
 | 
			
		||||
					<span>{{ item }}</span>
 | 
			
		||||
				</div>
 | 
			
		||||
			</li>
 | 
			
		||||
		</ul>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="icon">
 | 
			
		||||
import { computed, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
const iconList: Array<string> = [
 | 
			
		||||
	'attentionforbid',
 | 
			
		||||
	'attentionforbidfill',
 | 
			
		||||
	'attention',
 | 
			
		||||
	'attentionfill',
 | 
			
		||||
	'tag',
 | 
			
		||||
	'tagfill',
 | 
			
		||||
	'people',
 | 
			
		||||
	'peoplefill',
 | 
			
		||||
	'notice',
 | 
			
		||||
	'noticefill',
 | 
			
		||||
	'mobile',
 | 
			
		||||
	'mobilefill',
 | 
			
		||||
	'voice',
 | 
			
		||||
	'voicefill',
 | 
			
		||||
	'unlock',
 | 
			
		||||
	'lock',
 | 
			
		||||
	'home',
 | 
			
		||||
	'homefill',
 | 
			
		||||
	'delete',
 | 
			
		||||
	'deletefill',
 | 
			
		||||
	'notification',
 | 
			
		||||
	'notificationfill',
 | 
			
		||||
	'notificationforbidfill',
 | 
			
		||||
	'like',
 | 
			
		||||
	'likefill',
 | 
			
		||||
	'comment',
 | 
			
		||||
	'commentfill',
 | 
			
		||||
	'camera',
 | 
			
		||||
	'camerafill',
 | 
			
		||||
	'warn',
 | 
			
		||||
	'warnfill',
 | 
			
		||||
	'time',
 | 
			
		||||
	'timefill',
 | 
			
		||||
	'location',
 | 
			
		||||
	'locationfill',
 | 
			
		||||
	'favor',
 | 
			
		||||
	'favorfill',
 | 
			
		||||
	'skin',
 | 
			
		||||
	'skinfill',
 | 
			
		||||
	'news',
 | 
			
		||||
	'newsfill',
 | 
			
		||||
	'record',
 | 
			
		||||
	'recordfill',
 | 
			
		||||
	'emoji',
 | 
			
		||||
	'emojifill',
 | 
			
		||||
	'message',
 | 
			
		||||
	'messagefill',
 | 
			
		||||
	'goods',
 | 
			
		||||
	'goodsfill',
 | 
			
		||||
	'crown',
 | 
			
		||||
	'crownfill',
 | 
			
		||||
	'move',
 | 
			
		||||
	'add',
 | 
			
		||||
	'hot',
 | 
			
		||||
	'hotfill',
 | 
			
		||||
	'service',
 | 
			
		||||
	'servicefill',
 | 
			
		||||
	'present',
 | 
			
		||||
	'presentfill',
 | 
			
		||||
	'pic',
 | 
			
		||||
	'picfill',
 | 
			
		||||
	'rank',
 | 
			
		||||
	'rankfill',
 | 
			
		||||
	'male',
 | 
			
		||||
	'female',
 | 
			
		||||
	'down',
 | 
			
		||||
	'top',
 | 
			
		||||
	'recharge',
 | 
			
		||||
	'rechargefill',
 | 
			
		||||
	'forward',
 | 
			
		||||
	'forwardfill',
 | 
			
		||||
	'info',
 | 
			
		||||
	'infofill',
 | 
			
		||||
	'redpacket',
 | 
			
		||||
	'redpacket_fill',
 | 
			
		||||
	'roundadd',
 | 
			
		||||
	'roundaddfill',
 | 
			
		||||
	'friendadd',
 | 
			
		||||
	'friendaddfill',
 | 
			
		||||
	'cart',
 | 
			
		||||
	'cartfill',
 | 
			
		||||
	'more',
 | 
			
		||||
	'moreandroid',
 | 
			
		||||
	'back',
 | 
			
		||||
	'right',
 | 
			
		||||
	'shop',
 | 
			
		||||
	'shopfill',
 | 
			
		||||
	'question',
 | 
			
		||||
	'questionfill',
 | 
			
		||||
	'roundclose',
 | 
			
		||||
	'roundclosefill',
 | 
			
		||||
	'roundcheck',
 | 
			
		||||
	'roundcheckfill',
 | 
			
		||||
	'global',
 | 
			
		||||
	'mail',
 | 
			
		||||
	'punch',
 | 
			
		||||
	'exit',
 | 
			
		||||
	'upload',
 | 
			
		||||
	'read',
 | 
			
		||||
	'file',
 | 
			
		||||
	'link',
 | 
			
		||||
	'full',
 | 
			
		||||
	'group',
 | 
			
		||||
	'friend',
 | 
			
		||||
	'profile',
 | 
			
		||||
	'addressbook',
 | 
			
		||||
	'calendar',
 | 
			
		||||
	'text',
 | 
			
		||||
	'copy',
 | 
			
		||||
	'share',
 | 
			
		||||
	'wifi',
 | 
			
		||||
	'vipcard',
 | 
			
		||||
	'weibo',
 | 
			
		||||
	'remind',
 | 
			
		||||
	'refresh',
 | 
			
		||||
	'filter',
 | 
			
		||||
	'settings',
 | 
			
		||||
	'scan',
 | 
			
		||||
	'qrcode',
 | 
			
		||||
	'cascades',
 | 
			
		||||
	'apps',
 | 
			
		||||
	'sort',
 | 
			
		||||
	'searchlist',
 | 
			
		||||
	'search',
 | 
			
		||||
	'edit'
 | 
			
		||||
];
 | 
			
		||||
const keyword = ref('');
 | 
			
		||||
const list = computed(() => {
 | 
			
		||||
	return iconList.filter(item => {
 | 
			
		||||
		return item.indexOf(keyword.value) !== -1;
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.example-p {
 | 
			
		||||
	height: 45px;
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
}
 | 
			
		||||
.search-box {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
.search {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
}
 | 
			
		||||
ul,
 | 
			
		||||
li {
 | 
			
		||||
	list-style: none;
 | 
			
		||||
}
 | 
			
		||||
.icon-li {
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
	padding: 10px;
 | 
			
		||||
	width: 120px;
 | 
			
		||||
	height: 120px;
 | 
			
		||||
}
 | 
			
		||||
.icon-li-content {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.icon-li-content i {
 | 
			
		||||
	font-size: 36px;
 | 
			
		||||
	color: #606266;
 | 
			
		||||
}
 | 
			
		||||
.icon-li-content span {
 | 
			
		||||
	margin-top: 10px;
 | 
			
		||||
	color: #787878;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										118
									
								
								frontend/src/views/demo/import.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								frontend/src/views/demo/import.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <div class="handle-box">
 | 
			
		||||
                <el-upload
 | 
			
		||||
                    action="#"
 | 
			
		||||
                    :limit="1"
 | 
			
		||||
                    accept=".xlsx, .xls"
 | 
			
		||||
                    :show-file-list="false"
 | 
			
		||||
                    :before-upload="beforeUpload"
 | 
			
		||||
                    :http-request="handleMany"
 | 
			
		||||
                >
 | 
			
		||||
                    <el-button class="mr10" type="success">批量导入</el-button>
 | 
			
		||||
                </el-upload>
 | 
			
		||||
                <el-link href="/template.xlsx" target="_blank">下载模板</el-link>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-table :data="tableData" border class="table" header-cell-class-name="table-header">
 | 
			
		||||
                <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
 | 
			
		||||
                <el-table-column prop="name" label="姓名"></el-table-column>
 | 
			
		||||
                <el-table-column prop="sno" label="学号"></el-table-column>
 | 
			
		||||
                <el-table-column prop="class" label="班级"></el-table-column>
 | 
			
		||||
                <el-table-column prop="age" label="年龄"></el-table-column>
 | 
			
		||||
                <el-table-column prop="sex" label="性别"></el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="import">
 | 
			
		||||
import { UploadProps } from 'element-plus';
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
import * as XLSX from 'xlsx';
 | 
			
		||||
 | 
			
		||||
interface TableItem {
 | 
			
		||||
    id: number;
 | 
			
		||||
    name: string;
 | 
			
		||||
    sno: string;
 | 
			
		||||
    class: string;
 | 
			
		||||
    age: string;
 | 
			
		||||
    sex: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tableData = ref<TableItem[]>([]);
 | 
			
		||||
// 获取表格数据
 | 
			
		||||
const getData = () => {
 | 
			
		||||
    tableData.value = [
 | 
			
		||||
        {
 | 
			
		||||
            id: 1,
 | 
			
		||||
            name: '小明',
 | 
			
		||||
            sno: 'S001',
 | 
			
		||||
            class: '一班',
 | 
			
		||||
            age: '10',
 | 
			
		||||
            sex: '男',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            id: 2,
 | 
			
		||||
            name: '小红',
 | 
			
		||||
            sno: 'S002',
 | 
			
		||||
            class: '一班',
 | 
			
		||||
            age: '9',
 | 
			
		||||
            sex: '女',
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
const importList = ref<any>([]);
 | 
			
		||||
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
 | 
			
		||||
    importList.value = await analysisExcel(rawFile);
 | 
			
		||||
    return true;
 | 
			
		||||
};
 | 
			
		||||
const analysisExcel = (file: any) => {
 | 
			
		||||
    return new Promise(function (resolve, reject) {
 | 
			
		||||
        const reader = new FileReader();
 | 
			
		||||
        reader.onload = function (e: any) {
 | 
			
		||||
            const data = e.target.result;
 | 
			
		||||
            let datajson = XLSX.read(data, {
 | 
			
		||||
                type: 'binary',
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const sheetName = datajson.SheetNames[0];
 | 
			
		||||
            const result = XLSX.utils.sheet_to_json(datajson.Sheets[sheetName]);
 | 
			
		||||
            resolve(result);
 | 
			
		||||
        };
 | 
			
		||||
        reader.readAsBinaryString(file);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleMany = async () => {
 | 
			
		||||
    // 把数据传给服务器后获取最新列表,这里只是示例,不做请求
 | 
			
		||||
    const list = importList.value.map((item: any, index: number) => {
 | 
			
		||||
        return {
 | 
			
		||||
            id: index,
 | 
			
		||||
            name: item['姓名'],
 | 
			
		||||
            sno: item['学号'],
 | 
			
		||||
            class: item['班级'],
 | 
			
		||||
            age: item['年龄'],
 | 
			
		||||
            sex: item['性别'],
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
    tableData.value.push(...list);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.handle-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
.mr10 {
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										21
									
								
								frontend/src/views/demo/markdown.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/src/views/demo/markdown.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<div class="plugins-tips">
 | 
			
		||||
			md-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地址:
 | 
			
		||||
			<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
 | 
			
		||||
		</div>
 | 
			
		||||
		<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
 | 
			
		||||
		<el-button type="primary">提交</el-button>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="md">
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import MdEditor from 'md-editor-v3';
 | 
			
		||||
import 'md-editor-v3/lib/style.css';
 | 
			
		||||
 | 
			
		||||
const text = ref('Hello Editor!');
 | 
			
		||||
const onUploadImg = (files: any) => {
 | 
			
		||||
	console.log(files);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										191
									
								
								frontend/src/views/demo/table.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								frontend/src/views/demo/table.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,191 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class="container">
 | 
			
		||||
			<div class="handle-box">
 | 
			
		||||
				<el-select v-model="query.address" placeholder="地址" class="handle-select mr10">
 | 
			
		||||
					<el-option key="1" label="广东省" value="广东省"></el-option>
 | 
			
		||||
					<el-option key="2" label="湖南省" value="湖南省"></el-option>
 | 
			
		||||
				</el-select>
 | 
			
		||||
				<el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
 | 
			
		||||
				<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
 | 
			
		||||
				<el-button type="primary" :icon="Plus">新增</el-button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
 | 
			
		||||
				<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="name" label="用户名"></el-table-column>
 | 
			
		||||
				<el-table-column label="账户余额">
 | 
			
		||||
					<template #default="scope">¥{{ scope.row.money }}</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
				<el-table-column label="头像(查看大图)" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-image
 | 
			
		||||
							class="table-td-thumb"
 | 
			
		||||
							:src="scope.row.thumb"
 | 
			
		||||
							:z-index="10"
 | 
			
		||||
							:preview-src-list="[scope.row.thumb]"
 | 
			
		||||
							preview-teleported
 | 
			
		||||
						>
 | 
			
		||||
						</el-image>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
				<el-table-column prop="address" label="地址"></el-table-column>
 | 
			
		||||
				<el-table-column label="状态" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-tag
 | 
			
		||||
							:type="scope.row.state === '成功' ? 'success' : scope.row.state === '失败' ? 'danger' : ''"
 | 
			
		||||
						>
 | 
			
		||||
							{{ scope.row.state }}
 | 
			
		||||
						</el-tag>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
 | 
			
		||||
				<el-table-column prop="date" label="注册时间"></el-table-column>
 | 
			
		||||
				<el-table-column label="操作" width="220" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)" v-permiss="'default'">
 | 
			
		||||
							编辑
 | 
			
		||||
						</el-button>
 | 
			
		||||
						<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index)" v-permiss="'default'">
 | 
			
		||||
							删除
 | 
			
		||||
						</el-button>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
			</el-table>
 | 
			
		||||
			<div class="pagination">
 | 
			
		||||
				<el-pagination
 | 
			
		||||
					background
 | 
			
		||||
					layout="total, prev, pager, next"
 | 
			
		||||
					:current-page="query.pageIndex"
 | 
			
		||||
					:page-size="query.pageSize"
 | 
			
		||||
					:total="pageTotal"
 | 
			
		||||
					@current-change="handlePageChange"
 | 
			
		||||
				></el-pagination>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- 编辑弹出框 -->
 | 
			
		||||
		<el-dialog title="编辑" v-model="editVisible" width="30%">
 | 
			
		||||
			<el-form label-width="70px">
 | 
			
		||||
				<el-form-item label="用户名">
 | 
			
		||||
					<el-input v-model="form.name"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="地址">
 | 
			
		||||
					<el-input v-model="form.address"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
			</el-form>
 | 
			
		||||
			<template #footer>
 | 
			
		||||
				<span class="dialog-footer">
 | 
			
		||||
					<el-button @click="editVisible = false">取 消</el-button>
 | 
			
		||||
					<el-button type="primary" @click="saveEdit">确 定</el-button>
 | 
			
		||||
				</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="basetable">
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
 | 
			
		||||
import { fetchData } from '../../api/index';
 | 
			
		||||
 | 
			
		||||
interface TableItem {
 | 
			
		||||
	id: number;
 | 
			
		||||
	name: string;
 | 
			
		||||
	money: string;
 | 
			
		||||
	state: string;
 | 
			
		||||
	date: string;
 | 
			
		||||
	address: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const query = reactive({
 | 
			
		||||
	address: '',
 | 
			
		||||
	name: '',
 | 
			
		||||
	pageIndex: 1,
 | 
			
		||||
	pageSize: 10
 | 
			
		||||
});
 | 
			
		||||
const tableData = ref<TableItem[]>([]);
 | 
			
		||||
const pageTotal = ref(0);
 | 
			
		||||
// 获取表格数据
 | 
			
		||||
const getData = () => {
 | 
			
		||||
	fetchData().then(res => {
 | 
			
		||||
		tableData.value = res.data.list;
 | 
			
		||||
		pageTotal.value = res.data.pageTotal || 50;
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
// 查询操作
 | 
			
		||||
const handleSearch = () => {
 | 
			
		||||
	query.pageIndex = 1;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
// 分页导航
 | 
			
		||||
const handlePageChange = (val: number) => {
 | 
			
		||||
	query.pageIndex = val;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 删除操作
 | 
			
		||||
const handleDelete = (index: number) => {
 | 
			
		||||
	// 二次确认删除
 | 
			
		||||
	ElMessageBox.confirm('确定要删除吗?', '提示', {
 | 
			
		||||
		type: 'warning'
 | 
			
		||||
	})
 | 
			
		||||
		.then(() => {
 | 
			
		||||
			ElMessage.success('删除成功');
 | 
			
		||||
			tableData.value.splice(index, 1);
 | 
			
		||||
		})
 | 
			
		||||
		.catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 表格编辑时弹窗和保存
 | 
			
		||||
const editVisible = ref(false);
 | 
			
		||||
let form = reactive({
 | 
			
		||||
	name: '',
 | 
			
		||||
	address: ''
 | 
			
		||||
});
 | 
			
		||||
let idx: number = -1;
 | 
			
		||||
const handleEdit = (index: number, row: any) => {
 | 
			
		||||
	idx = index;
 | 
			
		||||
	form.name = row.name;
 | 
			
		||||
	form.address = row.address;
 | 
			
		||||
	editVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
const saveEdit = () => {
 | 
			
		||||
	editVisible.value = false;
 | 
			
		||||
	ElMessage.success(`修改第 ${idx + 1} 行成功`);
 | 
			
		||||
	tableData.value[idx].name = form.name;
 | 
			
		||||
	tableData.value[idx].address = form.address;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.handle-box {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-select {
 | 
			
		||||
	width: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-input {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
}
 | 
			
		||||
.table {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
.red {
 | 
			
		||||
	color: #F56C6C;
 | 
			
		||||
}
 | 
			
		||||
.mr10 {
 | 
			
		||||
	margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
.table-td-thumb {
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	width: 40px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										116
									
								
								frontend/src/views/demo/tabs.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								frontend/src/views/demo/tabs.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<el-tabs v-model="message">
 | 
			
		||||
			<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
 | 
			
		||||
				<el-table :data="state.unread" :show-header="false" style="width: 100%">
 | 
			
		||||
					<el-table-column>
 | 
			
		||||
						<template #default="scope">
 | 
			
		||||
							<span class="message-title">{{ scope.row.title }}</span>
 | 
			
		||||
						</template>
 | 
			
		||||
					</el-table-column>
 | 
			
		||||
					<el-table-column prop="date" width="180"></el-table-column>
 | 
			
		||||
					<el-table-column width="120">
 | 
			
		||||
						<template #default="scope">
 | 
			
		||||
							<el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
 | 
			
		||||
						</template>
 | 
			
		||||
					</el-table-column>
 | 
			
		||||
				</el-table>
 | 
			
		||||
				<div class="handle-row">
 | 
			
		||||
					<el-button type="primary">全部标为已读</el-button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</el-tab-pane>
 | 
			
		||||
			<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
 | 
			
		||||
				<template v-if="message === 'second'">
 | 
			
		||||
					<el-table :data="state.read" :show-header="false" style="width: 100%">
 | 
			
		||||
						<el-table-column>
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<span class="message-title">{{ scope.row.title }}</span>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
						<el-table-column prop="date" width="150"></el-table-column>
 | 
			
		||||
						<el-table-column width="120">
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<el-button type="danger" @click="handleDel(scope.$index)">删除</el-button>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
					</el-table>
 | 
			
		||||
					<div class="handle-row">
 | 
			
		||||
						<el-button type="danger">删除全部</el-button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</template>
 | 
			
		||||
			</el-tab-pane>
 | 
			
		||||
			<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
 | 
			
		||||
				<template v-if="message === 'third'">
 | 
			
		||||
					<el-table :data="state.recycle" :show-header="false" style="width: 100%">
 | 
			
		||||
						<el-table-column>
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<span class="message-title">{{ scope.row.title }}</span>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
						<el-table-column prop="date" width="150"></el-table-column>
 | 
			
		||||
						<el-table-column width="120">
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<el-button @click="handleRestore(scope.$index)">还原</el-button>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
					</el-table>
 | 
			
		||||
					<div class="handle-row">
 | 
			
		||||
						<el-button type="danger">清空回收站</el-button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</template>
 | 
			
		||||
			</el-tab-pane>
 | 
			
		||||
		</el-tabs>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="tabs">
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
 | 
			
		||||
const message = ref('first');
 | 
			
		||||
const state = reactive({
 | 
			
		||||
	unread: [
 | 
			
		||||
		{
 | 
			
		||||
			date: '2018-04-19 20:00:00',
 | 
			
		||||
			title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			date: '2018-04-19 21:00:00',
 | 
			
		||||
			title: '今晚12点整发大红包,先到先得'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	read: [
 | 
			
		||||
		{
 | 
			
		||||
			date: '2018-04-19 20:00:00',
 | 
			
		||||
			title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	recycle: [
 | 
			
		||||
		{
 | 
			
		||||
			date: '2018-04-19 20:00:00',
 | 
			
		||||
			title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleRead = (index: number) => {
 | 
			
		||||
	const item = state.unread.splice(index, 1);
 | 
			
		||||
	state.read = item.concat(state.read);
 | 
			
		||||
};
 | 
			
		||||
const handleDel = (index: number) => {
 | 
			
		||||
	const item = state.read.splice(index, 1);
 | 
			
		||||
	state.recycle = item.concat(state.recycle);
 | 
			
		||||
};
 | 
			
		||||
const handleRestore = (index: number) => {
 | 
			
		||||
	const item = state.recycle.splice(index, 1);
 | 
			
		||||
	state.read = item.concat(state.read);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.message-title {
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.handle-row {
 | 
			
		||||
	margin-top: 30px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										174
									
								
								frontend/src/views/demo/user.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								frontend/src/views/demo/user.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
		<el-row :gutter="20">
 | 
			
		||||
			<el-col :span="12">
 | 
			
		||||
				<el-card shadow="hover">
 | 
			
		||||
					<template #header>
 | 
			
		||||
						<div class="clearfix">
 | 
			
		||||
							<span>基础信息</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
					<div class="info">
 | 
			
		||||
						<div class="info-image" @click="showDialog">
 | 
			
		||||
							<el-avatar :size="100" :src="avatarImg" />
 | 
			
		||||
							<span class="info-edit">
 | 
			
		||||
								<i class="el-icon-lx-camerafill"></i>
 | 
			
		||||
							</span>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="info-name">{{ name }}</div>
 | 
			
		||||
						<div class="info-desc">不可能!我的代码怎么可能会有bug!</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
			<el-col :span="12">
 | 
			
		||||
				<el-card shadow="hover">
 | 
			
		||||
					<template #header>
 | 
			
		||||
						<div class="clearfix">
 | 
			
		||||
							<span>账户编辑</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
					<el-form label-width="90px">
 | 
			
		||||
						<el-form-item label="用户名:"> {{ name }} </el-form-item>
 | 
			
		||||
						<el-form-item label="旧密码:">
 | 
			
		||||
							<el-input type="password" v-model="form.old"></el-input>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
						<el-form-item label="新密码:">
 | 
			
		||||
							<el-input type="password" v-model="form.new"></el-input>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
						<el-form-item label="个人简介:">
 | 
			
		||||
							<el-input v-model="form.desc"></el-input>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
						<el-form-item>
 | 
			
		||||
							<el-button type="primary" @click="onSubmit">保存</el-button>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
					</el-form>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
		</el-row>
 | 
			
		||||
		<el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
 | 
			
		||||
			<vue-cropper
 | 
			
		||||
				ref="cropper"
 | 
			
		||||
				:src="imgSrc"
 | 
			
		||||
				:ready="cropImage"
 | 
			
		||||
				:zoom="cropImage"
 | 
			
		||||
				:cropmove="cropImage"
 | 
			
		||||
				style="width: 100%; height: 400px"
 | 
			
		||||
			></vue-cropper>
 | 
			
		||||
 | 
			
		||||
			<template #footer>
 | 
			
		||||
				<span class="dialog-footer">
 | 
			
		||||
					<el-button class="crop-demo-btn" type="primary"
 | 
			
		||||
						>选择图片
 | 
			
		||||
						<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
 | 
			
		||||
					</el-button>
 | 
			
		||||
					<el-button type="primary" @click="saveAvatar">上传并保存</el-button>
 | 
			
		||||
				</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="user">
 | 
			
		||||
import { reactive, ref } from 'vue';
 | 
			
		||||
import VueCropper from 'vue-cropperjs';
 | 
			
		||||
import 'cropperjs/dist/cropper.css';
 | 
			
		||||
import avatar from '../../assets/img/img.jpg';
 | 
			
		||||
 | 
			
		||||
const name = localStorage.getItem('ms_username');
 | 
			
		||||
const form = reactive({
 | 
			
		||||
	old: '',
 | 
			
		||||
	new: '',
 | 
			
		||||
	desc: '不可能!我的代码怎么可能会有bug!'
 | 
			
		||||
});
 | 
			
		||||
const onSubmit = () => {};
 | 
			
		||||
 | 
			
		||||
const avatarImg = ref(avatar);
 | 
			
		||||
const imgSrc = ref('');
 | 
			
		||||
const cropImg = ref('');
 | 
			
		||||
const dialogVisible = ref(false);
 | 
			
		||||
const cropper: any = ref();
 | 
			
		||||
 | 
			
		||||
const showDialog = () => {
 | 
			
		||||
	dialogVisible.value = true;
 | 
			
		||||
	imgSrc.value = avatarImg.value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setImage = (e: any) => {
 | 
			
		||||
	const file = e.target.files[0];
 | 
			
		||||
	if (!file.type.includes('image/')) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const reader = new FileReader();
 | 
			
		||||
	reader.onload = (event: any) => {
 | 
			
		||||
		dialogVisible.value = true;
 | 
			
		||||
		imgSrc.value = event.target.result;
 | 
			
		||||
		cropper.value && cropper.value.replace(event.target.result);
 | 
			
		||||
	};
 | 
			
		||||
	reader.readAsDataURL(file);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cropImage = () => {
 | 
			
		||||
	cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveAvatar = () => {
 | 
			
		||||
	avatarImg.value = cropImg.value;
 | 
			
		||||
	dialogVisible.value = false;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.info {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	padding: 35px 0;
 | 
			
		||||
}
 | 
			
		||||
.info-image {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	width: 100px;
 | 
			
		||||
	height: 100px;
 | 
			
		||||
	background: #f8f8f8;
 | 
			
		||||
	border: 1px solid #eee;
 | 
			
		||||
	border-radius: 50px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.info-edit {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	background: rgba(0, 0, 0, 0.5);
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transition: opacity 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
.info-edit i {
 | 
			
		||||
	color: #eee;
 | 
			
		||||
	font-size: 25px;
 | 
			
		||||
}
 | 
			
		||||
.info-image:hover .info-edit {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
.info-name {
 | 
			
		||||
	margin: 15px 0 10px;
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
	font-weight: 500;
 | 
			
		||||
	color: #262626;
 | 
			
		||||
}
 | 
			
		||||
.crop-demo-btn {
 | 
			
		||||
	position: relative;
 | 
			
		||||
}
 | 
			
		||||
.crop-input {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	width: 100px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										389
									
								
								frontend/src/views/equipment-setting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										389
									
								
								frontend/src/views/equipment-setting.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,389 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class="container">
 | 
			
		||||
			<!-- 筛选 -->
 | 
			
		||||
			<div class="handle-box">
 | 
			
		||||
				<el-select v-model="query.params.deviceType" placeholder="设备类型" class="handle-select mr10"
 | 
			
		||||
					style="width: 160px;">
 | 
			
		||||
					<el-option :key="''" label="全部设备类型" :value="''"></el-option>
 | 
			
		||||
					<el-option v-for="opt in deviceTypeOption" :key="opt.id" :label="opt.typeName"
 | 
			
		||||
						:value="opt.id"></el-option>
 | 
			
		||||
				</el-select>
 | 
			
		||||
				<!-- <el-input v-model="query.params.name" placeholder="设备名" class="handle-input mr10"></el-input>
 | 
			
		||||
				<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button> -->
 | 
			
		||||
				<el-button type="primary" :icon="Plus" @click="handleNew">新增设备</el-button>
 | 
			
		||||
				<el-button type="primary" :icon="Plus" @click="exportExcel">导出设备列表</el-button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<!-- 表格 -->
 | 
			
		||||
			<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
 | 
			
		||||
				<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="name" label="设备名称" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="type" label="设备类型" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="deviceName" label="设备ID" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="location" label="设备位置" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="contractor" label="承建商" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="manufacturer" label="生产商" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column label="状态" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-tag :type="scope.row.state === '在线' ? 'success' : (scope.row.state === '离线' ? 'danger' : '')">
 | 
			
		||||
							{{ scope.row.state }}
 | 
			
		||||
						</el-tag>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
				<el-table-column label="操作" width="220" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)"
 | 
			
		||||
							v-permiss="'equipment-setting-manage'">
 | 
			
		||||
							编辑
 | 
			
		||||
						</el-button>
 | 
			
		||||
						<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index, scope.row)"
 | 
			
		||||
							v-permiss="'equipment-setting-manage'">
 | 
			
		||||
							删除
 | 
			
		||||
						</el-button>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
			</el-table>
 | 
			
		||||
			<!-- 分页 -->
 | 
			
		||||
			<div class="pagination">
 | 
			
		||||
				<el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
 | 
			
		||||
					:page-size="query.pageSize" :total="pageTotal" @current-change="handlePageChange"></el-pagination>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- 编辑弹出框 -->
 | 
			
		||||
		<el-dialog :title="formId > 0 ? '编辑' : '新增'" v-model="editVisible" style="width: 30%; min-width: 280px;">
 | 
			
		||||
			<el-form ref="editForm" label-width="80px" :rules="rules" :model="form">
 | 
			
		||||
				<el-form-item label="设备名称" prop="name">
 | 
			
		||||
					<el-input class="popup-item" v-model="form.name"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="设备类型" prop="typeId">
 | 
			
		||||
					<el-select class="popup-item" v-model="form.typeId" placeholder="设备类型">
 | 
			
		||||
						<el-option :key="''" label="请选择设备类型" :value="''"></el-option>
 | 
			
		||||
						<el-option v-for="opt in deviceTypeOption" :key="opt.id" :label="opt.typeName"
 | 
			
		||||
							:value="opt.id"></el-option>
 | 
			
		||||
					</el-select>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="设备ID" prop="deviceName">
 | 
			
		||||
					<el-input class="popup-item" v-model="form.deviceName"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="设备位置">
 | 
			
		||||
					<el-input class="popup-item" v-model="form.location"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="承建商">
 | 
			
		||||
					<el-input class="popup-item" v-model="form.contractor"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="生产商">
 | 
			
		||||
					<el-input class="popup-item" v-model="form.manufacturer"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
			</el-form>
 | 
			
		||||
			<template #footer>
 | 
			
		||||
				<span class="dialog-footer">
 | 
			
		||||
					<el-button @click="editVisible = false">取 消</el-button>
 | 
			
		||||
					<el-button type="primary" @click="saveEdit(editForm)">确 定</el-button>
 | 
			
		||||
				</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, reactive, watch } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import type { FormInstance, FormRules } from 'element-plus';
 | 
			
		||||
import { ElLoading } from 'element-plus';
 | 
			
		||||
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
import * as XLSX from 'xlsx';
 | 
			
		||||
 | 
			
		||||
// 筛选条件
 | 
			
		||||
const query = reactive({
 | 
			
		||||
	params: {
 | 
			
		||||
		deviceType: '',
 | 
			
		||||
		name: '',
 | 
			
		||||
	},
 | 
			
		||||
	pageIndex: 1,
 | 
			
		||||
	pageSize: 10
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 下拉框数据
 | 
			
		||||
const deviceTypeOption = ref<[{ id: any, typeName: any }]>()
 | 
			
		||||
 | 
			
		||||
// 表格数据
 | 
			
		||||
const tableData: any = ref([]);
 | 
			
		||||
const pageTotal = ref(0);
 | 
			
		||||
const deviceTypeDict: any = ref({});
 | 
			
		||||
// 获取表格&下拉框数据
 | 
			
		||||
const getData = async () => {
 | 
			
		||||
	const loading = ElLoading.service({
 | 
			
		||||
		lock: true,
 | 
			
		||||
		text: '请稍候',
 | 
			
		||||
		background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
	});
 | 
			
		||||
	await send_request('v1/device/list', "GET", {
 | 
			
		||||
		...query.params,
 | 
			
		||||
		pageIndex: query.pageIndex,
 | 
			
		||||
		pageSize: query.pageSize,
 | 
			
		||||
		page: query.pageIndex,
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
 | 
			
		||||
		let deviceList = data.list;
 | 
			
		||||
		let deviceTypeList = data.optionDeviceType;
 | 
			
		||||
 | 
			
		||||
		//生成"设备类型"数据字典
 | 
			
		||||
		console.log("deviceList/deviceTypeList:", deviceList, deviceTypeList)
 | 
			
		||||
		deviceTypeList.forEach((item: any) => deviceTypeDict.value[item.id] = item.typeName)
 | 
			
		||||
 | 
			
		||||
		// 渲染表格
 | 
			
		||||
		tableData.value = deviceList.list.map((i: any) => {
 | 
			
		||||
			i.state = "在线"
 | 
			
		||||
			i.type = deviceTypeDict.value[i.typeId]
 | 
			
		||||
			return i
 | 
			
		||||
		});
 | 
			
		||||
		// console.log("tableData", tableData);
 | 
			
		||||
		pageTotal.value = deviceList.total;
 | 
			
		||||
		// 渲染下拉框
 | 
			
		||||
		deviceTypeOption.value = deviceTypeList;
 | 
			
		||||
	});
 | 
			
		||||
	loading.close();
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
// 查询操作
 | 
			
		||||
const handleSearch = () => {
 | 
			
		||||
	query.pageIndex = 1;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 筛选条件变化自动搜索
 | 
			
		||||
watch(() => query.params, function (newVal, oldVal, ...args) {
 | 
			
		||||
	console.log("watch query.params newVal", JSON.stringify(newVal))
 | 
			
		||||
	console.log("watch query.params oldVal", JSON.stringify(oldVal))
 | 
			
		||||
	console.log("watch query.params args", args)
 | 
			
		||||
	handleSearch();
 | 
			
		||||
}, { deep: true });
 | 
			
		||||
 | 
			
		||||
// 分页导航
 | 
			
		||||
const handlePageChange = (val: number) => {
 | 
			
		||||
	query.pageIndex = val;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 删除操作
 | 
			
		||||
const handleDelete = (index: number, row: any) => {
 | 
			
		||||
	// 二次确认删除
 | 
			
		||||
	ElMessageBox.confirm('确定要删除吗?', '提示', {
 | 
			
		||||
		type: 'warning'
 | 
			
		||||
	})
 | 
			
		||||
		.then(() => send_request('v1/device/delete', "POST", {
 | 
			
		||||
			deviceId: row.id
 | 
			
		||||
		}, (data: any) => {
 | 
			
		||||
			// console.log(data);
 | 
			
		||||
			ElMessage.success('删除成功');
 | 
			
		||||
			tableData.value.splice(index, 1);
 | 
			
		||||
		}))
 | 
			
		||||
		.catch(() => {
 | 
			
		||||
			ElMessage.success('删除失败');
 | 
			
		||||
		});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 添加/修改记录时表单验证
 | 
			
		||||
const editForm = ref<FormInstance>();
 | 
			
		||||
const rules: FormRules = {
 | 
			
		||||
	name: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请输入设备名称',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			min: 2,
 | 
			
		||||
			message: '设备名称过短',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			max: 52,
 | 
			
		||||
			message: '设备名称过长',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	typeId: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请选择设备类型',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	deviceName: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请输入设备ID',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			min: 2,
 | 
			
		||||
			message: '设备ID过短',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			max: 52,
 | 
			
		||||
			message: '设备ID过长',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
};
 | 
			
		||||
// 表格编辑时弹窗和保存
 | 
			
		||||
const editVisible = ref(false);
 | 
			
		||||
let form = reactive({
 | 
			
		||||
	name: '',
 | 
			
		||||
	typeId: '',
 | 
			
		||||
	deviceName: '',
 | 
			
		||||
	location: '',
 | 
			
		||||
	contractor: '',
 | 
			
		||||
	manufacturer: '',
 | 
			
		||||
});
 | 
			
		||||
let idx: number = -1;
 | 
			
		||||
let formId: number = -1;
 | 
			
		||||
const handleEdit = (index: number, row: any) => {
 | 
			
		||||
	idx = index;
 | 
			
		||||
	formId = row.id;
 | 
			
		||||
	form.name = row.name;
 | 
			
		||||
	form.typeId = row.typeId;
 | 
			
		||||
	form.deviceName = row.deviceName;
 | 
			
		||||
	form.location = row.location;
 | 
			
		||||
	form.contractor = row.contractor;
 | 
			
		||||
	form.manufacturer = row.manufacturer;
 | 
			
		||||
	editVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
// 新增弹窗
 | 
			
		||||
const handleNew = () => {
 | 
			
		||||
	formId = -1;
 | 
			
		||||
	for (let formKeys of Object.keys(form)) {
 | 
			
		||||
		form[formKeys] = ''
 | 
			
		||||
	}
 | 
			
		||||
	editVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveEdit = async (formEl: FormInstance | undefined) => {
 | 
			
		||||
	if (!formEl) return;
 | 
			
		||||
	// console.log("formEl", formEl);
 | 
			
		||||
	formEl.validate(async (valid: boolean, invalidFields: any) => {
 | 
			
		||||
		if (!valid) {
 | 
			
		||||
			// console.log("invalidFields", invalidFields);
 | 
			
		||||
			// 对表单中的每一个不合法输入框进行遍历
 | 
			
		||||
			Object.values(invalidFields).forEach((input: any) => {
 | 
			
		||||
				// 对该不合法输入框的提示信息进行遍历
 | 
			
		||||
				input.forEach((element: any) => {
 | 
			
		||||
					ElMessage.error({ message: element.message, grouping: true });
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (formId > 0) {
 | 
			
		||||
			// 更新表格中数据
 | 
			
		||||
			tableData.value[idx].name = form.name;
 | 
			
		||||
			tableData.value[idx].typeId = form.typeId;
 | 
			
		||||
			tableData.value[idx].deviceName = form.deviceName;
 | 
			
		||||
			tableData.value[idx].location = form.location;
 | 
			
		||||
			tableData.value[idx].contractor = form.contractor;
 | 
			
		||||
			tableData.value[idx].manufacturer = form.manufacturer;
 | 
			
		||||
			// 修改记录
 | 
			
		||||
			await send_request('v1/device/edit', "POST", {
 | 
			
		||||
				id: formId,
 | 
			
		||||
				contractor: form.contractor,
 | 
			
		||||
				deviceName: form.deviceName,
 | 
			
		||||
				name: form.name,
 | 
			
		||||
				location: form.location,
 | 
			
		||||
				manufacturer: form.manufacturer,
 | 
			
		||||
				typeId: form.typeId
 | 
			
		||||
			}, (data: any) => {
 | 
			
		||||
				// console.log(data)
 | 
			
		||||
				// 关闭弹窗
 | 
			
		||||
				editVisible.value = false;
 | 
			
		||||
				ElMessage.success(`修改成功`);
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			// 新增记录
 | 
			
		||||
			await send_request('v1/device/add', "POST", {
 | 
			
		||||
				contractor: form.contractor,
 | 
			
		||||
				deviceName: form.deviceName,
 | 
			
		||||
				name: form.name,
 | 
			
		||||
				location: form.location,
 | 
			
		||||
				manufacturer: form.manufacturer,
 | 
			
		||||
				typeId: form.typeId
 | 
			
		||||
			}, (data: any) => {
 | 
			
		||||
				// console.log(data)
 | 
			
		||||
				// 关闭弹窗
 | 
			
		||||
				editVisible.value = false;
 | 
			
		||||
				ElMessage.success(`添加成功`);
 | 
			
		||||
				query.pageIndex = Math.ceil((pageTotal.value + 1) / query.pageSize);
 | 
			
		||||
				getData();
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 导出设备到Excel列表
 | 
			
		||||
const exportExcel = async () => {
 | 
			
		||||
	const loading = ElLoading.service({
 | 
			
		||||
		lock: true,
 | 
			
		||||
		text: '请稍候',
 | 
			
		||||
		background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	let list = [['序号', '设备名称', '设备类型', '设备ID', '设备位置', '承建商', '生产商']];
 | 
			
		||||
	await send_request('v1/device/listAll', "GET", {
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
		data.list.map((item: any, i: number) => {
 | 
			
		||||
			const arr: any[] = [i + 1];
 | 
			
		||||
			// console.log("-----------------", arr);
 | 
			
		||||
			arr.push(...[item.name, deviceTypeDict.value[item.typeId], item.deviceName, item.location, item.manufacturer, item.contractor]);
 | 
			
		||||
			list.push(arr);
 | 
			
		||||
		});
 | 
			
		||||
		let WorkSheet = XLSX.utils.aoa_to_sheet(list);
 | 
			
		||||
		let new_workbook = XLSX.utils.book_new();
 | 
			
		||||
		XLSX.utils.book_append_sheet(new_workbook, WorkSheet, '第一页');
 | 
			
		||||
		XLSX.writeFile(new_workbook, `表格.xlsx`);
 | 
			
		||||
	});
 | 
			
		||||
	loading.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.handle-box {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-select {
 | 
			
		||||
	width: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-input {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.red {
 | 
			
		||||
	color: #F56C6C;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mr10 {
 | 
			
		||||
	margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-td-thumb {
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	width: 40px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.popup-item {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										54
									
								
								frontend/src/views/error-page/403.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								frontend/src/views/error-page/403.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="error-page">
 | 
			
		||||
		<div class="error-code">4<span>0</span>3</div>
 | 
			
		||||
		<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
 | 
			
		||||
		<div class="error-handle">
 | 
			
		||||
			<router-link to="/">
 | 
			
		||||
				<el-button type="primary" size="large">返回首页</el-button>
 | 
			
		||||
			</router-link>
 | 
			
		||||
			<el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="403">
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const goBack = () => {
 | 
			
		||||
	router.go(-2);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.error-page {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	background: #f3f3f3;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
.error-code {
 | 
			
		||||
	line-height: 1;
 | 
			
		||||
	font-size: 250px;
 | 
			
		||||
	font-weight: bolder;
 | 
			
		||||
	color: #f02d2d;
 | 
			
		||||
}
 | 
			
		||||
.error-code span {
 | 
			
		||||
	color: #00a854;
 | 
			
		||||
}
 | 
			
		||||
.error-desc {
 | 
			
		||||
	font-size: 30px;
 | 
			
		||||
	color: #777;
 | 
			
		||||
}
 | 
			
		||||
.error-handle {
 | 
			
		||||
	margin-top: 30px;
 | 
			
		||||
	padding-bottom: 200px;
 | 
			
		||||
}
 | 
			
		||||
.error-btn {
 | 
			
		||||
	margin-left: 100px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										54
									
								
								frontend/src/views/error-page/404.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								frontend/src/views/error-page/404.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="error-page">
 | 
			
		||||
		<div class="error-code">4<span>0</span>4</div>
 | 
			
		||||
		<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
 | 
			
		||||
		<div class="error-handle">
 | 
			
		||||
			<router-link to="/">
 | 
			
		||||
				<el-button type="primary" size="large">返回首页</el-button>
 | 
			
		||||
			</router-link>
 | 
			
		||||
			<el-button class="error-btn" type="primary" size="large" @click="goBack">返回上一页</el-button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="404">
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const goBack = () => {
 | 
			
		||||
	router.go(-1);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.error-page {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	background: #f3f3f3;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
.error-code {
 | 
			
		||||
	line-height: 1;
 | 
			
		||||
	font-size: 250px;
 | 
			
		||||
	font-weight: bolder;
 | 
			
		||||
	color: #2d8cf0;
 | 
			
		||||
}
 | 
			
		||||
.error-code span {
 | 
			
		||||
	color: #00a854;
 | 
			
		||||
}
 | 
			
		||||
.error-desc {
 | 
			
		||||
	font-size: 30px;
 | 
			
		||||
	color: #777;
 | 
			
		||||
}
 | 
			
		||||
.error-handle {
 | 
			
		||||
	margin-top: 30px;
 | 
			
		||||
	padding-bottom: 200px;
 | 
			
		||||
}
 | 
			
		||||
.error-btn {
 | 
			
		||||
	margin-left: 100px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										26
									
								
								frontend/src/views/home.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								frontend/src/views/home.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<v-header />
 | 
			
		||||
	<v-sidebar />
 | 
			
		||||
	<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
 | 
			
		||||
		<v-tags></v-tags>
 | 
			
		||||
		<div class="content">
 | 
			
		||||
			<router-view v-slot="{ Component }">
 | 
			
		||||
				<transition name="move" mode="out-in">
 | 
			
		||||
					<keep-alive :include="tags.nameList">
 | 
			
		||||
						<component :is="Component" style="height: 100%;"></component>
 | 
			
		||||
					</keep-alive>
 | 
			
		||||
				</transition>
 | 
			
		||||
			</router-view>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useSidebarStore } from '../store/sidebar';
 | 
			
		||||
import { useTagsStore } from '../store/tags';
 | 
			
		||||
import vHeader from '../components/header.vue';
 | 
			
		||||
import vSidebar from '../components/sidebar.vue';
 | 
			
		||||
import vTags from '../components/tags.vue';
 | 
			
		||||
 | 
			
		||||
const sidebar = useSidebarStore();
 | 
			
		||||
const tags = useTagsStore();
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										222
									
								
								frontend/src/views/login.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								frontend/src/views/login.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="login-wrap">
 | 
			
		||||
		<div class="login-container">
 | 
			
		||||
			<div class="ms-login">
 | 
			
		||||
				<div class="ms-title">{{ settings.siteFullTitle }}</div>
 | 
			
		||||
				<el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
 | 
			
		||||
					<el-form-item prop="username">
 | 
			
		||||
						<el-input v-model="param.username" placeholder="用户名">
 | 
			
		||||
							<template #prepend>
 | 
			
		||||
								<el-button :icon="User"></el-button>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-input>
 | 
			
		||||
					</el-form-item>
 | 
			
		||||
					<el-form-item prop="password">
 | 
			
		||||
						<el-input type="password" placeholder="密码" v-model="param.password"
 | 
			
		||||
							@keyup.enter="submitForm(login)">
 | 
			
		||||
							<template #prepend>
 | 
			
		||||
								<el-button :icon="Lock"></el-button>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-input>
 | 
			
		||||
					</el-form-item>
 | 
			
		||||
					<div class="login-btn">
 | 
			
		||||
						<el-button type="primary" @click="submitForm(login)">
 | 
			
		||||
							<!-- <el-icon><UserFilled /></el-icon>-->
 | 
			
		||||
							登 录 <el-icon>
 | 
			
		||||
								<Right />
 | 
			
		||||
							</el-icon>
 | 
			
		||||
						</el-button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</el-form>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="company-info" v-if="settings.companyName">
 | 
			
		||||
			{{ settings.companyName }}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
import { useTagsStore } from '../store/tags';
 | 
			
		||||
import { usePermissStore } from '../store/permiss';
 | 
			
		||||
import { useRouter } from 'vue-router';
 | 
			
		||||
import { ElMessage, ElLoading } from 'element-plus';
 | 
			
		||||
import type { FormInstance, FormRules } from 'element-plus';
 | 
			
		||||
import { Lock, User } from '@element-plus/icons-vue';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
import settings from '../utils/settings';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface LoginInfo {
 | 
			
		||||
	username: string;
 | 
			
		||||
	password: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface UserInfo {
 | 
			
		||||
	username: string;
 | 
			
		||||
	id: string;
 | 
			
		||||
	roleId: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface PrivilegeInfo {
 | 
			
		||||
	"id": Number,
 | 
			
		||||
	"roleId": Number,
 | 
			
		||||
	"privilegeName": string,
 | 
			
		||||
	"module": string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface RoleInfo {
 | 
			
		||||
	id: Number,
 | 
			
		||||
	roleName: string,
 | 
			
		||||
	comment: any,
 | 
			
		||||
	privileges: Array<PrivilegeInfo>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const param = reactive<LoginInfo>({
 | 
			
		||||
	username: 'admin',
 | 
			
		||||
	password: '123123'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const rules: FormRules = {
 | 
			
		||||
	username: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请输入用户名',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	password: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请输入密码',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	]
 | 
			
		||||
};
 | 
			
		||||
const permiss: any = usePermissStore();
 | 
			
		||||
const login = ref<FormInstance>();
 | 
			
		||||
const submitForm = (formEl: FormInstance | undefined) => {
 | 
			
		||||
	if (!formEl) return;
 | 
			
		||||
	formEl.validate(async (valid: boolean, invalidFields: any) => {
 | 
			
		||||
		if (!valid) {
 | 
			
		||||
			// ElMessage.error('请填写用户名或密码');
 | 
			
		||||
 | 
			
		||||
			console.log("invalidFields", invalidFields);
 | 
			
		||||
			// 对表单中的每一个不合法输入框进行遍历
 | 
			
		||||
			Object.values(invalidFields).forEach((input: any) => {
 | 
			
		||||
				// console.log("input", input)
 | 
			
		||||
				// 对该不合法输入框的提示信息进行遍历
 | 
			
		||||
				input.forEach((element: any) => {
 | 
			
		||||
					// console.log("element", element)
 | 
			
		||||
					ElMessage.error({ message: element.message, grouping: true });
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const loading = ElLoading.service({
 | 
			
		||||
			lock: true,
 | 
			
		||||
			text: '请稍候',
 | 
			
		||||
			background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		await send_request('v1/user/login', "POST", {
 | 
			
		||||
			userName: param.username,
 | 
			
		||||
			passWord: param.password
 | 
			
		||||
		}, async (data: UserInfo) => {
 | 
			
		||||
			// 判断用户是否登录成功
 | 
			
		||||
			if (!data) {
 | 
			
		||||
				ElMessage.error("用户名或密码错误");
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			ElMessage.success('登录成功');
 | 
			
		||||
			localStorage.setItem('ms_username', data.username);
 | 
			
		||||
			localStorage.setItem('ms_user_id', data.id);
 | 
			
		||||
			localStorage.setItem('ms_role_id', data.roleId);
 | 
			
		||||
 | 
			
		||||
			let defaultList = {};
 | 
			
		||||
			await send_request('v1/role/list', "GET", {}, (roleList: Array<RoleInfo>) => {
 | 
			
		||||
				for (let role of roleList) {
 | 
			
		||||
					defaultList[role.id.toString()] = role.privileges.map((i: any) => i.module)
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			permiss.defaultList = defaultList;
 | 
			
		||||
			permiss.key = defaultList[data.roleId];
 | 
			
		||||
			if (typeof (permiss.key) === "undefined") return;
 | 
			
		||||
			localStorage.setItem('ms_keys', JSON.stringify(permiss.key));
 | 
			
		||||
			localStorage.setItem('ms_default_list', JSON.stringify(defaultList));
 | 
			
		||||
			let targetRoute: any = router.currentRoute?.value?.query?.redirectTo
 | 
			
		||||
			if (targetRoute && !targetRoute.includes('/login')) {
 | 
			
		||||
				router.push(targetRoute);
 | 
			
		||||
			} else {
 | 
			
		||||
				router.push('/');
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		loading.close();
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const tags = useTagsStore();
 | 
			
		||||
tags.clearTags();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.login-wrap {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-container {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	display: grid;
 | 
			
		||||
	place-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ms-title {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	padding: 18px 24px;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	font-size: 20px;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
	border-bottom: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ms-login {
 | 
			
		||||
	width: min(380px, 95vw);
 | 
			
		||||
	padding: 5px 10px;
 | 
			
		||||
	border-radius: 5px;
 | 
			
		||||
	background: rgba(255, 255, 255, 0.3);
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ms-content {
 | 
			
		||||
	padding: 30px 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-btn {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-btn button {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 36px;
 | 
			
		||||
	margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.company-info {
 | 
			
		||||
	color: #7589b6;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	right: 0;
 | 
			
		||||
	bottom: 10px;
 | 
			
		||||
	font-size: 13px;
 | 
			
		||||
	letter-spacing: 1px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										182
									
								
								frontend/src/views/monitor-data-view.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								frontend/src/views/monitor-data-view.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class="container">
 | 
			
		||||
			<div class="handle-box">
 | 
			
		||||
				<el-select v-model="query.address" placeholder="地址" class="handle-select mr10">
 | 
			
		||||
					<el-option key="1" label="广东省" value="广东省"></el-option>
 | 
			
		||||
					<el-option key="2" label="湖南省" value="湖南省"></el-option>
 | 
			
		||||
				</el-select>
 | 
			
		||||
				<el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
 | 
			
		||||
				<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
 | 
			
		||||
				<el-button type="primary" :icon="Plus">新增</el-button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
 | 
			
		||||
				<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="name" label="用户名"></el-table-column>
 | 
			
		||||
				<el-table-column label="账户余额">
 | 
			
		||||
					<template #default="scope">¥{{ scope.row.money }}</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
				<el-table-column label="头像(查看大图)" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-image class="table-td-thumb" :src="scope.row.thumb" :z-index="10"
 | 
			
		||||
							:preview-src-list="[scope.row.thumb]" preview-teleported>
 | 
			
		||||
						</el-image>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
				<el-table-column prop="address" label="地址"></el-table-column>
 | 
			
		||||
				<el-table-column label="状态" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-tag :type="scope.row.state === '成功' ? 'success' : scope.row.state === '失败' ? 'danger' : ''">
 | 
			
		||||
							{{ scope.row.state }}
 | 
			
		||||
						</el-tag>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
 | 
			
		||||
				<el-table-column prop="date" label="注册时间"></el-table-column>
 | 
			
		||||
				<el-table-column label="操作" width="220" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)" v-permiss="15">
 | 
			
		||||
							编辑
 | 
			
		||||
						</el-button>
 | 
			
		||||
						<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index)" v-permiss="16">
 | 
			
		||||
							删除
 | 
			
		||||
						</el-button>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
			</el-table>
 | 
			
		||||
			<div class="pagination">
 | 
			
		||||
				<el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
 | 
			
		||||
					:page-size="query.pageSize" :total="pageTotal" @current-change="handlePageChange"></el-pagination>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- 编辑弹出框 -->
 | 
			
		||||
		<el-dialog title="编辑" v-model="editVisible" width="30%">
 | 
			
		||||
			<el-form label-width="70px">
 | 
			
		||||
				<el-form-item label="用户名">
 | 
			
		||||
					<el-input v-model="form.name"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="地址">
 | 
			
		||||
					<el-input v-model="form.address"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
			</el-form>
 | 
			
		||||
			<template #footer>
 | 
			
		||||
				<span class="dialog-footer">
 | 
			
		||||
					<el-button @click="editVisible = false">取 消</el-button>
 | 
			
		||||
					<el-button type="primary" @click="saveEdit">确 定</el-button>
 | 
			
		||||
				</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
 | 
			
		||||
import { fetchData } from '../api/index';
 | 
			
		||||
 | 
			
		||||
interface TableItem {
 | 
			
		||||
	id: number;
 | 
			
		||||
	name: string;
 | 
			
		||||
	money: string;
 | 
			
		||||
	state: string;
 | 
			
		||||
	date: string;
 | 
			
		||||
	address: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const query = reactive({
 | 
			
		||||
	address: '',
 | 
			
		||||
	name: '',
 | 
			
		||||
	pageIndex: 1,
 | 
			
		||||
	pageSize: 10
 | 
			
		||||
});
 | 
			
		||||
const tableData = ref<TableItem[]>([]);
 | 
			
		||||
const pageTotal = ref(0);
 | 
			
		||||
// 获取表格数据
 | 
			
		||||
const getData = () => {
 | 
			
		||||
	fetchData().then(res => {
 | 
			
		||||
		tableData.value = res.data.list;
 | 
			
		||||
		pageTotal.value = res.data.pageTotal || 50;
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
// 查询操作
 | 
			
		||||
const handleSearch = () => {
 | 
			
		||||
	query.pageIndex = 1;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
// 分页导航
 | 
			
		||||
const handlePageChange = (val: number) => {
 | 
			
		||||
	query.pageIndex = val;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 删除操作
 | 
			
		||||
const handleDelete = (index: number) => {
 | 
			
		||||
	// 二次确认删除
 | 
			
		||||
	ElMessageBox.confirm('确定要删除吗?', '提示', {
 | 
			
		||||
		type: 'warning'
 | 
			
		||||
	})
 | 
			
		||||
		.then(() => {
 | 
			
		||||
			ElMessage.success('删除成功');
 | 
			
		||||
			tableData.value.splice(index, 1);
 | 
			
		||||
		})
 | 
			
		||||
		.catch(() => { });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 表格编辑时弹窗和保存
 | 
			
		||||
const editVisible = ref(false);
 | 
			
		||||
let form = reactive({
 | 
			
		||||
	name: '',
 | 
			
		||||
	address: ''
 | 
			
		||||
});
 | 
			
		||||
let idx: number = -1;
 | 
			
		||||
const handleEdit = (index: number, row: any) => {
 | 
			
		||||
	idx = index;
 | 
			
		||||
	form.name = row.name;
 | 
			
		||||
	form.address = row.address;
 | 
			
		||||
	editVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
const saveEdit = () => {
 | 
			
		||||
	editVisible.value = false;
 | 
			
		||||
	ElMessage.success(`修改第 ${idx + 1} 行成功`);
 | 
			
		||||
	tableData.value[idx].name = form.name;
 | 
			
		||||
	tableData.value[idx].address = form.address;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.handle-box {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-select {
 | 
			
		||||
	width: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-input {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.red {
 | 
			
		||||
	color: #F56C6C;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mr10 {
 | 
			
		||||
	margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-td-thumb {
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	width: 40px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										286
									
								
								frontend/src/views/privilege-role-setting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								frontend/src/views/privilege-role-setting.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<div class="plugins-tips">角色及其对应的权限</div>
 | 
			
		||||
		<!-- <div>
 | 
			
		||||
			<span class="label">角色:</span>
 | 
			
		||||
			<el-button type="primary" :icon="Search" @click="handleNew">增加</el-button>
 | 
			
		||||
			<el-button type="primary" :icon="Search" @click="handleNew">修改</el-button>
 | 
			
		||||
			<el-button type="primary" :icon="Search" @click="handleNew">删除</el-button>
 | 
			
		||||
		</div> -->
 | 
			
		||||
 | 
			
		||||
		<div style="display: grid; grid-template-columns: 1fr 3fr;">
 | 
			
		||||
			<div style="margin-top: 40px;">
 | 
			
		||||
 | 
			
		||||
				<div class="mgb20">
 | 
			
		||||
					<span class="label">角色:</span>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div>
 | 
			
		||||
					<el-radio-group v-model="selectRoleId" style="display: block;">
 | 
			
		||||
						<el-radio :label="role.id" size="large" v-for="role in roleList" style="display: block;"
 | 
			
		||||
							@click="() => { }">
 | 
			
		||||
							{{ role.roleName }}
 | 
			
		||||
						</el-radio>
 | 
			
		||||
					</el-radio-group>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<div class="mgb20">
 | 
			
		||||
					<span class="label">角色对应权限:</span>
 | 
			
		||||
					<!-- <el-select v-model="role" @change="() => { }/*handleChange*/">
 | 
			
		||||
						<el-option label="超级管理员" value="admin"></el-option>
 | 
			
		||||
						<el-option label="普通用户" value="user"></el-option>
 | 
			
		||||
					</el-select> -->
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="mgb20 tree-wrapper">
 | 
			
		||||
					<el-tree ref="tree" :data="data" node-key="id" default-expand-all show-checkbox
 | 
			
		||||
						:default-checked-keys="checkedKeys" />
 | 
			
		||||
				</div>
 | 
			
		||||
				<!-- <el-button type="primary" @click="onSubmit">保存权限</el-button> -->
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- 编辑弹出框 -->
 | 
			
		||||
		<el-dialog :title="formId > 0 ? '编辑' : '新增'" v-model="editVisible" style="width: 30%; min-width: 280px;">
 | 
			
		||||
			<el-form ref="editForm" label-width="80px" :rules="rules" :model="form">
 | 
			
		||||
				<el-form-item label="角色名称" prop="roleName">
 | 
			
		||||
					<el-input class="popup-item" v-model="form.roleName"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="备注">
 | 
			
		||||
					<el-input class="popup-item" v-model="form.comment"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
			</el-form>
 | 
			
		||||
			<template #footer>
 | 
			
		||||
				<span class="dialog-footer">
 | 
			
		||||
					<el-button @click="editVisible = false">取 消</el-button>
 | 
			
		||||
					<el-button type="primary" @click="addRole(editForm)">确 定</el-button>
 | 
			
		||||
				</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, reactive, watch } from 'vue';
 | 
			
		||||
import { ElTree } from 'element-plus';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import type { FormInstance, FormRules } from 'element-plus';
 | 
			
		||||
import { ElLoading } from 'element-plus';
 | 
			
		||||
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
 | 
			
		||||
const editForm = ref<FormInstance>();
 | 
			
		||||
console.log("editForm:", editForm)
 | 
			
		||||
 | 
			
		||||
const rules: FormRules = {
 | 
			
		||||
	deviceName: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请输入设备名称',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			min: 2,
 | 
			
		||||
			message: '设备名称过短',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			max: 52,
 | 
			
		||||
			message: '设备名称过长',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	type: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请选择设备类型',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	deviceId: [
 | 
			
		||||
		{
 | 
			
		||||
			required: true,
 | 
			
		||||
			message: '请输入设备ID',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			min: 2,
 | 
			
		||||
			message: '设备ID过短',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			max: 52,
 | 
			
		||||
			message: '设备ID过长',
 | 
			
		||||
			trigger: 'blur'
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let formId: number = -1;
 | 
			
		||||
const editVisible = ref(false);
 | 
			
		||||
let form = reactive({
 | 
			
		||||
	roleName: '',
 | 
			
		||||
	comment: '',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const role = ref<string>('admin');
 | 
			
		||||
 | 
			
		||||
interface Tree {
 | 
			
		||||
	id: string;
 | 
			
		||||
	label: string;
 | 
			
		||||
	children?: Tree[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const data: Tree[] = [
 | 
			
		||||
	{
 | 
			
		||||
		id: 'dashboard',
 | 
			
		||||
		label: '系统首页',
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: '3dmodel',
 | 
			
		||||
		label: '智慧矿山'
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: 'equipment',
 | 
			
		||||
		label: '设备信息',
 | 
			
		||||
		children: [
 | 
			
		||||
			{
 | 
			
		||||
				id: 'equipment-setting-manage',
 | 
			
		||||
				label: '设备管理'
 | 
			
		||||
			},
 | 
			
		||||
		]
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: 'monitor-data',
 | 
			
		||||
		label: '监测数据',
 | 
			
		||||
		children: [
 | 
			
		||||
			{
 | 
			
		||||
				id: 'monitor-data-view',
 | 
			
		||||
				label: '查看数据'
 | 
			
		||||
			},
 | 
			
		||||
		]
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		id: 'warning',
 | 
			
		||||
		label: '预警信息',
 | 
			
		||||
		children: [
 | 
			
		||||
			{
 | 
			
		||||
				id: 'warning-view',
 | 
			
		||||
				label: '总览(预警信息)'
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				id: 'warning-setting',
 | 
			
		||||
				label: '预警设置'
 | 
			
		||||
			},
 | 
			
		||||
		]
 | 
			
		||||
	},
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 获取当前权限
 | 
			
		||||
const checkedKeys = ref<string[]>([]);
 | 
			
		||||
// const getPremission = () => {
 | 
			
		||||
// 	// 请求接口返回权限
 | 
			
		||||
// 	checkedKeys.value = permiss.defaultList[role.value];
 | 
			
		||||
// };
 | 
			
		||||
// getPremission();
 | 
			
		||||
 | 
			
		||||
// 保存权限
 | 
			
		||||
const tree = ref<InstanceType<typeof ElTree>>();
 | 
			
		||||
const onSubmit = () => {
 | 
			
		||||
	// 获取选中的权限
 | 
			
		||||
	console.log(tree.value!.getCheckedKeys(false));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const roleList = ref<any[]>();
 | 
			
		||||
const selectRoleId = ref(-1);
 | 
			
		||||
 | 
			
		||||
// 获取表格&下拉框数据
 | 
			
		||||
const getData = async () => {
 | 
			
		||||
	// const loading = ElLoading.service({
 | 
			
		||||
	// 	lock: true,
 | 
			
		||||
	// 	text: '请稍候',
 | 
			
		||||
	// 	background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
	// });
 | 
			
		||||
	await send_request('v1/role/list', "GET", {
 | 
			
		||||
		// ...query.params,
 | 
			
		||||
		// pageIndex: query.pageIndex,
 | 
			
		||||
		// pageSize: query.pageSize,
 | 
			
		||||
		// page: query.pageIndex,
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
		// let deviceTypeOpt = data.optionDeviceType;
 | 
			
		||||
		console.log("role/list", data)
 | 
			
		||||
		roleList.value = data;
 | 
			
		||||
		selectRoleId.value = data && data.length > 0 ? data[0].id : -1;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		// 渲染表格
 | 
			
		||||
		// tableData.value = deviceList.list.map((i: any) => {
 | 
			
		||||
		// 	i.state = "在线"
 | 
			
		||||
		// 	return i
 | 
			
		||||
		// });
 | 
			
		||||
		// pageTotal.value = deviceList.total;
 | 
			
		||||
		// // 渲染下拉框
 | 
			
		||||
		// deviceTypeOption.value = deviceTypeOpt;
 | 
			
		||||
	});
 | 
			
		||||
	// loading.close();
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
// const handleChange = (val: string[]) => {
 | 
			
		||||
// 	tree.value!.setCheckedKeys(permiss.defaultList[role.value]);
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
// 新增弹窗
 | 
			
		||||
const handleNew = () => {
 | 
			
		||||
	formId = -1;
 | 
			
		||||
	for (let formKeys of Object.keys(form)) {
 | 
			
		||||
		form[formKeys] = ''
 | 
			
		||||
	}
 | 
			
		||||
	editVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 添加角色
 | 
			
		||||
const addRole = async (formEl: FormInstance | undefined) => {
 | 
			
		||||
	if (!formEl) return;
 | 
			
		||||
	console.log("addRole:formEl", formEl);
 | 
			
		||||
 | 
			
		||||
	// formEl.validate(async (valid: boolean, invalidFields: any) => {
 | 
			
		||||
	// 	if (!valid) {
 | 
			
		||||
	// 		console.log("invalidFields", invalidFields);
 | 
			
		||||
 | 
			
		||||
	// 		// 对表单中的每一个不合法输入框进行遍历
 | 
			
		||||
	// 		Object.values(invalidFields).forEach((input: any) => {
 | 
			
		||||
 | 
			
		||||
	// 			// 对该不合法输入框的提示信息进行遍历
 | 
			
		||||
	// 			input.forEach((element: any) => {
 | 
			
		||||
	// 				ElMessage.error({
 | 
			
		||||
	// 					message: element.message, grouping: true
 | 
			
		||||
	// 				});
 | 
			
		||||
	// 			});
 | 
			
		||||
	// 		});
 | 
			
		||||
	// 		return;
 | 
			
		||||
	// 	}
 | 
			
		||||
	// 新增记录
 | 
			
		||||
	await send_request('v1/role/add', "POST", {
 | 
			
		||||
		roleName: form.roleName,
 | 
			
		||||
		comment: form.comment,
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
		console.log(data);
 | 
			
		||||
		// editVisible.value = false;
 | 
			
		||||
		// ElMessage.success("添加成功");
 | 
			
		||||
	});
 | 
			
		||||
	// }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.tree-wrapper {
 | 
			
		||||
	max-width: 500px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.label {
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										221
									
								
								frontend/src/views/privilege-user-setting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								frontend/src/views/privilege-user-setting.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,221 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
		<div class="container">
 | 
			
		||||
			<!-- 筛选 -->
 | 
			
		||||
			<div class="handle-box">
 | 
			
		||||
				<!-- <el-select v-model="query.address" placeholder="角色类型" class="handle-select mr10">
 | 
			
		||||
					<el-option key="1" label="管理员" value="管理员"></el-option>
 | 
			
		||||
					<el-option key="2" label="普通用户" value="普通用户"></el-option>
 | 
			
		||||
				</el-select> -->
 | 
			
		||||
				<!-- <el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
 | 
			
		||||
				<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button> -->
 | 
			
		||||
				<el-button type="primary" :icon="Plus" @click="handleNew">新增用户</el-button>
 | 
			
		||||
			</div>
 | 
			
		||||
			<!-- 表格 -->
 | 
			
		||||
			<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
 | 
			
		||||
				<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="username" label="用户名" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="roleName" label="角色" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="telephone" label="电话" align="center"></el-table-column>
 | 
			
		||||
				<el-table-column prop="roleId" label="" align="center" v-if="false"></el-table-column>
 | 
			
		||||
				<el-table-column label="操作" width="220" align="center">
 | 
			
		||||
					<template #default="scope">
 | 
			
		||||
						<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)"
 | 
			
		||||
							v-permiss="'user-setting'">
 | 
			
		||||
							编辑
 | 
			
		||||
						</el-button>
 | 
			
		||||
						<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index, scope.row)"
 | 
			
		||||
							v-permiss="'user-setting'">
 | 
			
		||||
							删除
 | 
			
		||||
						</el-button>
 | 
			
		||||
					</template>
 | 
			
		||||
				</el-table-column>
 | 
			
		||||
			</el-table>
 | 
			
		||||
			<!-- 分页 -->
 | 
			
		||||
			<div class="pagination">
 | 
			
		||||
				<el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
 | 
			
		||||
					:page-size="query.pageSize" :total="pageTotal" @current-change="handlePageChange"></el-pagination>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- 编辑弹出框 -->
 | 
			
		||||
		<el-dialog title="编辑" v-model="editVisible" width="30%">
 | 
			
		||||
			<el-form label-width="70px">
 | 
			
		||||
				<el-form-item label="用户名">
 | 
			
		||||
					<el-input v-model="form.username"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="角色">
 | 
			
		||||
					<el-select v-model="form.roleName" placeholder="角色类型" class="handle-select mr10">
 | 
			
		||||
						<el-option key="1" label="管理员" value="管理员"></el-option>
 | 
			
		||||
						<el-option key="2" label="普通用户" value="普通用户"></el-option>
 | 
			
		||||
					</el-select>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
				<el-form-item label="电话">
 | 
			
		||||
					<el-input v-model="form.telephone"></el-input>
 | 
			
		||||
				</el-form-item>
 | 
			
		||||
			</el-form>
 | 
			
		||||
			<template #footer>
 | 
			
		||||
				<span class="dialog-footer">
 | 
			
		||||
					<el-button @click="editVisible = false">取 消</el-button>
 | 
			
		||||
					<el-button type="primary" @click="saveEdit">确 定</el-button>
 | 
			
		||||
				</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { ElLoading } from 'element-plus';
 | 
			
		||||
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
 | 
			
		||||
const query = reactive({
 | 
			
		||||
	id: 0,
 | 
			
		||||
	address: '',
 | 
			
		||||
	name: '',
 | 
			
		||||
	roleId: 0,
 | 
			
		||||
	pageIndex: 1,
 | 
			
		||||
	pageSize: 5
 | 
			
		||||
});
 | 
			
		||||
const tableData: any = ref([]);
 | 
			
		||||
const pageTotal = ref(0);
 | 
			
		||||
// 获取表格数据
 | 
			
		||||
const getData = async () => {
 | 
			
		||||
 | 
			
		||||
	const loading = ElLoading.service({
 | 
			
		||||
		lock: true,
 | 
			
		||||
		text: '请稍候',
 | 
			
		||||
		background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	await send_request('v1/user/list', "GET", {
 | 
			
		||||
		page: query.pageIndex,
 | 
			
		||||
		pageSize: query.pageSize
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
		console.log(data)
 | 
			
		||||
		tableData.value = data.list;
 | 
			
		||||
		pageTotal.value = data.total;
 | 
			
		||||
	});
 | 
			
		||||
	loading.close();
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
 | 
			
		||||
// 查询操作
 | 
			
		||||
const handleSearch = () => {
 | 
			
		||||
	query.pageIndex = 1;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
// 分页导航
 | 
			
		||||
const handlePageChange = (val: number) => {
 | 
			
		||||
	query.pageIndex = val;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 删除操作
 | 
			
		||||
const handleDelete = (index: number, row: any) => {
 | 
			
		||||
	idx = index;
 | 
			
		||||
	// 二次确认删除
 | 
			
		||||
 | 
			
		||||
	ElMessageBox.confirm('确定要删除吗?', '提示', {
 | 
			
		||||
		type: 'warning'
 | 
			
		||||
	})
 | 
			
		||||
		.then(async () => {
 | 
			
		||||
			await send_request('v1/user/delete', "POST", {
 | 
			
		||||
				userId: row.id
 | 
			
		||||
			}, (data: any) => {
 | 
			
		||||
				// console.log("delete index", index);
 | 
			
		||||
				// console.log(data);
 | 
			
		||||
				if (data) {
 | 
			
		||||
					ElMessage.success('删除成功');
 | 
			
		||||
					tableData.value.splice(idx, 1);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
		.catch(() => { });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 表格编辑时弹窗和保存
 | 
			
		||||
const editVisible = ref(false);
 | 
			
		||||
let form = reactive({
 | 
			
		||||
	id: 0,
 | 
			
		||||
	username: '',
 | 
			
		||||
	roleId: 0,
 | 
			
		||||
	roleName: '',
 | 
			
		||||
	telephone: ''
 | 
			
		||||
});
 | 
			
		||||
let idx: number = -1;
 | 
			
		||||
// 点击进入编辑框
 | 
			
		||||
const handleEdit = (index: number, row: any) => {
 | 
			
		||||
	idx = index;
 | 
			
		||||
	form.id = row.id;
 | 
			
		||||
	form.username = row.username;
 | 
			
		||||
	form.roleName = row.roleName;
 | 
			
		||||
	form.roleId = row.roleId;
 | 
			
		||||
	form.telephone = row.telephone;
 | 
			
		||||
	editVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
// 编辑保存到数据库
 | 
			
		||||
const saveEdit = async () => {
 | 
			
		||||
	editVisible.value = false;
 | 
			
		||||
	await send_request('v1/user/edit', "POST", {
 | 
			
		||||
		id: form.id,
 | 
			
		||||
		username: form.username,
 | 
			
		||||
		roleId: form.roleId,
 | 
			
		||||
		roleName: form.roleName,
 | 
			
		||||
		telephone: form.telephone
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
		if (data) {
 | 
			
		||||
			ElMessage.success('修改成功');
 | 
			
		||||
			tableData.value[idx].username = form.username;
 | 
			
		||||
			tableData.value[idx].roleName = form.roleName;
 | 
			
		||||
			tableData.value[idx].telephone = form.telephone;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleNew = () => {
 | 
			
		||||
	editVisible.value = true;
 | 
			
		||||
	ElMessage.success(`修改第 ${idx + 1} 行成功`);
 | 
			
		||||
	tableData.value[idx].username = form.username;
 | 
			
		||||
	tableData.value[idx].roleName = form.roleName;
 | 
			
		||||
	tableData.value[idx].telephone = form.telephone;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.handle-box {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-select {
 | 
			
		||||
	width: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-input {
 | 
			
		||||
	width: 300px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.red {
 | 
			
		||||
	color: #F56C6C;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mr10 {
 | 
			
		||||
	margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-td-thumb {
 | 
			
		||||
	display: block;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	width: 40px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										230
									
								
								frontend/src/views/user.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								frontend/src/views/user.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
		<el-row :gutter="20">
 | 
			
		||||
			<el-col :span="12">
 | 
			
		||||
				<el-card shadow="hover">
 | 
			
		||||
					<template #header>
 | 
			
		||||
						<div class="clearfix">
 | 
			
		||||
							<span>基础信息</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
					<div class="info">
 | 
			
		||||
						<div class="info-image" @click="showDialog">
 | 
			
		||||
							<el-avatar :size="100" :src="avatarImg" />
 | 
			
		||||
							<span class="info-edit">
 | 
			
		||||
								<i class="el-icon-lx-camerafill"></i>
 | 
			
		||||
							</span>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="info-name">{{ name }}</div>
 | 
			
		||||
						<!-- <div class="info-desc">不可能!我的代码怎么可能会有bug!</div> -->
 | 
			
		||||
					</div>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
			<el-col :span="12">
 | 
			
		||||
				<el-card shadow="hover">
 | 
			
		||||
					<template #header>
 | 
			
		||||
						<div class="clearfix">
 | 
			
		||||
							<span>账户编辑</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
					<el-form label-width="90px">
 | 
			
		||||
						<el-form-item label="用户名:"> {{ name }} </el-form-item>
 | 
			
		||||
						<el-form-item label="旧密码:">
 | 
			
		||||
							<el-input type="password" v-model="form.old"></el-input>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
						<el-form-item label="新密码:">
 | 
			
		||||
							<el-input type="password" v-model="form.new"></el-input>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
						<el-form-item label="确认密码:">
 | 
			
		||||
							<el-input type="password" v-model="form.new1"></el-input>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
						<!-- <el-form-item label="个人简介:">
 | 
			
		||||
							<el-input v-model="form.desc"></el-input>
 | 
			
		||||
						</el-form-item> -->
 | 
			
		||||
						<el-form-item>
 | 
			
		||||
							<el-button type="primary" @click="onSubmit">保存</el-button>
 | 
			
		||||
						</el-form-item>
 | 
			
		||||
					</el-form>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
		</el-row>
 | 
			
		||||
		<el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
 | 
			
		||||
			<vue-cropper ref="cropper" :src="imgSrc" :ready="cropImage" :zoom="cropImage" :cropmove="cropImage"
 | 
			
		||||
				style="width: 100%; height: 400px"></vue-cropper>
 | 
			
		||||
 | 
			
		||||
			<template #footer>
 | 
			
		||||
				<span class="dialog-footer">
 | 
			
		||||
					<el-button class="crop-demo-btn" type="primary">选择图片
 | 
			
		||||
						<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
 | 
			
		||||
					</el-button>
 | 
			
		||||
					<el-button type="primary" @click="saveAvatar">上传并保存</el-button>
 | 
			
		||||
				</span>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-dialog>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="user">
 | 
			
		||||
import { reactive, ref } from 'vue';
 | 
			
		||||
import VueCropper from 'vue-cropperjs';
 | 
			
		||||
import 'cropperjs/dist/cropper.css';
 | 
			
		||||
import avatar from '../assets/img/img.jpg';
 | 
			
		||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
 | 
			
		||||
const name = localStorage.getItem('ms_username');
 | 
			
		||||
const user_id = localStorage.getItem('ms_user_id');
 | 
			
		||||
const form = reactive({
 | 
			
		||||
	user_id: user_id,
 | 
			
		||||
	old: '',
 | 
			
		||||
	new: '',
 | 
			
		||||
	new1: '',
 | 
			
		||||
	desc: ''
 | 
			
		||||
});
 | 
			
		||||
const onSubmit = async () => {
 | 
			
		||||
	if (form.old == '' || form.new == '' || form.new1 == '') {
 | 
			
		||||
		// 弹窗
 | 
			
		||||
		ElMessageBox.confirm('输入为空,请检查', '提示', {
 | 
			
		||||
			type: 'warning'
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	} else if (form.new != form.new1) {
 | 
			
		||||
		// 弹窗
 | 
			
		||||
		ElMessageBox.confirm('新密码2次输入的不相同', '提示', {
 | 
			
		||||
			type: 'warning'
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	} else if (form.new == form.old) {
 | 
			
		||||
		// 弹窗
 | 
			
		||||
		ElMessageBox.confirm('新、旧密码相同', '提示', {
 | 
			
		||||
			type: 'warning'
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ElMessageBox.confirm('确认要修改密码吗?', '提示', {
 | 
			
		||||
		type: 'warning'
 | 
			
		||||
	})
 | 
			
		||||
		.then(async () => {
 | 
			
		||||
			console.log("send_request v1/user/alterPSW")
 | 
			
		||||
 | 
			
		||||
			const loading = ElLoading.service({
 | 
			
		||||
				lock: true,
 | 
			
		||||
				text: '请稍候',
 | 
			
		||||
				background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
			});
 | 
			
		||||
			await send_request('v1/user/alterPSW', "POST", {
 | 
			
		||||
				"userId": form.user_id,
 | 
			
		||||
				"oldPSW": form.old,
 | 
			
		||||
				"newPSW": form.new
 | 
			
		||||
			}, (data: any) => {
 | 
			
		||||
				console.log(data);
 | 
			
		||||
				ElMessage.success('删除成功');
 | 
			
		||||
				// tableData.value.splice(index, 1);
 | 
			
		||||
			})
 | 
			
		||||
			loading.close();
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
		.catch(() => {
 | 
			
		||||
			ElMessage.success('删除失败');
 | 
			
		||||
		});
 | 
			
		||||
	return;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const avatarImg = ref(avatar);
 | 
			
		||||
const imgSrc = ref('');
 | 
			
		||||
const cropImg = ref('');
 | 
			
		||||
const dialogVisible = ref(false);
 | 
			
		||||
const cropper: any = ref();
 | 
			
		||||
 | 
			
		||||
const showDialog = () => {
 | 
			
		||||
	dialogVisible.value = true;
 | 
			
		||||
	imgSrc.value = avatarImg.value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setImage = (e: any) => {
 | 
			
		||||
	const file = e.target.files[0];
 | 
			
		||||
	if (!file.type.includes('image/')) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const reader = new FileReader();
 | 
			
		||||
	reader.onload = (event: any) => {
 | 
			
		||||
		dialogVisible.value = true;
 | 
			
		||||
		imgSrc.value = event.target.result;
 | 
			
		||||
		cropper.value && cropper.value.replace(event.target.result);
 | 
			
		||||
	};
 | 
			
		||||
	reader.readAsDataURL(file);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cropImage = () => {
 | 
			
		||||
	cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveAvatar = () => {
 | 
			
		||||
	avatarImg.value = cropImg.value;
 | 
			
		||||
	dialogVisible.value = false;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.info {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	padding: 35px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.info-image {
 | 
			
		||||
	position: relative;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
	width: 100px;
 | 
			
		||||
	height: 100px;
 | 
			
		||||
	background: #f8f8f8;
 | 
			
		||||
	border: 1px solid #eee;
 | 
			
		||||
	border-radius: 50px;
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.info-edit {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	background: rgba(0, 0, 0, 0.5);
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	transition: opacity 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.info-edit i {
 | 
			
		||||
	color: #eee;
 | 
			
		||||
	font-size: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.info-image:hover .info-edit {
 | 
			
		||||
	opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.info-name {
 | 
			
		||||
	margin: 15px 0 10px;
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
	font-weight: 500;
 | 
			
		||||
	color: #262626;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.crop-demo-btn {
 | 
			
		||||
	position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.crop-input {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	width: 100px;
 | 
			
		||||
	height: 40px;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	opacity: 0;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										302
									
								
								frontend/src/views/warning-setting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								frontend/src/views/warning-setting.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,302 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
 | 
			
		||||
        <!-- 调试用 -->
 | 
			
		||||
        <div v-if="false" style="background-color: lightgrey;">
 | 
			
		||||
            <p>treeSelectItem: {{ treeSelectItem }}</p>
 | 
			
		||||
            <p>monitorCodeList: {{ monitorCodeList }}</p>
 | 
			
		||||
            <p>selectIndex: {{ selectIndex }}</p>
 | 
			
		||||
            <p>form: {{ form }}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div style="display: grid; grid-template-columns: 250px 2fr;">
 | 
			
		||||
            <!-- 左侧 -->
 | 
			
		||||
            <div>
 | 
			
		||||
                <!-- 树状结构 -->
 | 
			
		||||
                <el-input v-model="treeFilterText" placeholder="输入关键字以搜索..." style="margin-bottom: 10px;" />
 | 
			
		||||
                <el-tree ref="treeRef" :data="treeData" :props="defaultProps" :filter-node-method="treeFilterNode"
 | 
			
		||||
                    @node-click="handleNodeClick" default-expand-all highlight-current :expand-on-click-node="false" />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- 右侧 -->
 | 
			
		||||
            <div v-if="treeSelectItem">
 | 
			
		||||
 | 
			
		||||
                <div style="text-align: center;">
 | 
			
		||||
                    <el-radio-group v-model="selectIndex" size="large" style="margin: 10px 0;">
 | 
			
		||||
                        <el-radio-button v-for="monitorCode in monitorCodeList" :label="monitorCode" />
 | 
			
		||||
                    </el-radio-group>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <el-divider border-style="dotted">
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <!-- <el-icon style="vertical-align: text-top;"><star-filled /></el-icon> -->
 | 
			
		||||
                        您正在配置{{ treeSelectItem.displayMsg }}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </el-divider>
 | 
			
		||||
 | 
			
		||||
                <!-- form表单 -->
 | 
			
		||||
                <el-form :model="form" label-width="120px">
 | 
			
		||||
 | 
			
		||||
                    <template v-for="i in form">
 | 
			
		||||
                        <el-form-item :label="i._title">
 | 
			
		||||
                            <el-switch v-model="i.Enable" />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <!-- v-if="i.Enable" -->
 | 
			
		||||
                        <el-form-item label="预警范围">
 | 
			
		||||
                            <el-col :span="10">
 | 
			
		||||
                                <el-input-number v-model="i.Lower" :disabled="!i.Enable" style="width: 100%" />
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                            <el-col :span="2" style="text-align: center;">
 | 
			
		||||
                                <span class="text-gray-500">-</span>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                            <el-col :span="10">
 | 
			
		||||
                                <el-input-number v-model="i.Upper" :disabled="!i.Enable" style="width: 100%" />
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                            <el-col :span="2" style="text-align: center;">
 | 
			
		||||
                                <el-button v-if="i.Lower || i.Upper" type="danger" :icon="Delete" circle
 | 
			
		||||
                                    :disabled="!i.Enable" @click="i.Enable = false; i.Lower = null; i.Upper = null;" />
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <el-form-item>
 | 
			
		||||
                        <el-button type="primary" @click="onSubmit">保存</el-button>
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
                </el-form>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else style="padding-top: 11vh;">
 | 
			
		||||
                <el-empty description="请在左侧选择设备" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, watch, computed, reactive } from 'vue'
 | 
			
		||||
import { ElTree, ElLoading, ElMessage } from 'element-plus'
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
import { Delete } from '@element-plus/icons-vue'
 | 
			
		||||
 | 
			
		||||
// 树状结构
 | 
			
		||||
interface Tree {
 | 
			
		||||
    id: number
 | 
			
		||||
    label: string
 | 
			
		||||
    children?: Tree[]
 | 
			
		||||
    detail?: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 树 上方搜索框中的字
 | 
			
		||||
const treeFilterText = ref('')
 | 
			
		||||
// 树 数据
 | 
			
		||||
const treeData: any = ref([]);
 | 
			
		||||
// 树 选择的结点
 | 
			
		||||
const treeSelectItem: any = ref(null);
 | 
			
		||||
// 树
 | 
			
		||||
const treeRef = ref<InstanceType<typeof ElTree>>()
 | 
			
		||||
 | 
			
		||||
// 单选 所有选项
 | 
			
		||||
const allMonitorCodeMap: any = ref({});
 | 
			
		||||
// 单选 当前显示的选项
 | 
			
		||||
const monitorCodeList: any = computed(() => {
 | 
			
		||||
    if (treeSelectItem.value) {
 | 
			
		||||
        console.log("monitorCodeList computed", allMonitorCodeMap.value, treeSelectItem.value.typeId)
 | 
			
		||||
        let newRadioGroupList = allMonitorCodeMap.value[`${treeSelectItem.value.typeId}`]
 | 
			
		||||
        if (newRadioGroupList.length > 0 && newRadioGroupList.indexOf(selectIndex.value) == -1) {
 | 
			
		||||
            // 如果 newRadioGroupList 中不为空,且当前选择项的 monitorCode 不在 newRadioGroupList 中才更新
 | 
			
		||||
            selectIndex.value = newRadioGroupList[0]
 | 
			
		||||
        }
 | 
			
		||||
        return newRadioGroupList
 | 
			
		||||
    } else {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
// 单选 选择项
 | 
			
		||||
const selectIndex: any = ref('');
 | 
			
		||||
 | 
			
		||||
// 树 搜索框文字改变事件
 | 
			
		||||
watch(treeFilterText, (val) => {
 | 
			
		||||
    treeRef.value!.filter(val)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 树 输入搜索文字过滤节点事件
 | 
			
		||||
const treeFilterNode = (value: string, data: Tree) => {
 | 
			
		||||
    if (!value) return true
 | 
			
		||||
    return data.label.includes(value) // || data.id === -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleNodeClick = (data: Tree) => {
 | 
			
		||||
    console.log("handleNodeClick", data)
 | 
			
		||||
    treeSelectItem.value = data.detail ? data.detail : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getTreeData = async () => {
 | 
			
		||||
    const loading = ElLoading.service({
 | 
			
		||||
        lock: true,
 | 
			
		||||
        text: '请稍候',
 | 
			
		||||
        background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
    });
 | 
			
		||||
    await send_request('v1/device/listAll', "GET", {
 | 
			
		||||
    }, (data: any) => {
 | 
			
		||||
        console.log("device/listAll", data)
 | 
			
		||||
        let deviceTypeList = data.deviceType;
 | 
			
		||||
        let deviceList = data.list;
 | 
			
		||||
 | 
			
		||||
        let transData = []
 | 
			
		||||
        for (let deviceType of deviceTypeList) {
 | 
			
		||||
            transData.push({
 | 
			
		||||
                id: deviceType.id,
 | 
			
		||||
                label: deviceType.typeName,
 | 
			
		||||
                children: [
 | 
			
		||||
                    {
 | 
			
		||||
                        id: `${deviceType.id}_0`,
 | 
			
		||||
                        label: "(默认)",
 | 
			
		||||
                        detail: {
 | 
			
		||||
                            deviceId: -1,
 | 
			
		||||
                            typeId: deviceType.id,
 | 
			
		||||
                            deviceName: `(默认)`,
 | 
			
		||||
                            displayMsg: ` ${deviceType.typeName}设备 默认设置`,
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                    ...deviceList.filter((device: any) => device.typeId === deviceType.id)
 | 
			
		||||
                        .map((device: any) => {
 | 
			
		||||
                            return {
 | 
			
		||||
                                id: `${deviceType.id}_${device.id}`,
 | 
			
		||||
                                label: device.name,
 | 
			
		||||
                                detail: {
 | 
			
		||||
                                    deviceId: device.id,
 | 
			
		||||
                                    typeId: device.typeId,
 | 
			
		||||
                                    deviceName: device.name,
 | 
			
		||||
                                    displayMsg: ` ${deviceType.typeName}设备:${device.name}`,
 | 
			
		||||
                                },
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                ],
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        treeData.value = transData;
 | 
			
		||||
    });
 | 
			
		||||
    await send_request('v1/alert-model/getDeviceTypeToMonitorCodeMap', "GET", {
 | 
			
		||||
    }, (data: any) => {
 | 
			
		||||
        console.log("alert-model/getDeviceTypeToMonitorCodeMap", data)
 | 
			
		||||
        allMonitorCodeMap.value = data;
 | 
			
		||||
    });
 | 
			
		||||
    loading.close();
 | 
			
		||||
};
 | 
			
		||||
getTreeData();
 | 
			
		||||
 | 
			
		||||
// Tree渲染所使用属性
 | 
			
		||||
const defaultProps = {
 | 
			
		||||
    children: 'children',
 | 
			
		||||
    label: 'label',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch([treeSelectItem, selectIndex], ([foo, bar], [prevFoo, prevBar]) => {
 | 
			
		||||
    if (!treeSelectItem.value) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    console.log("所选模型发生改变,需要重新获取AlertModel")
 | 
			
		||||
    getAlertModel()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const getAlertModel = async () => {
 | 
			
		||||
    const loading = ElLoading.service({
 | 
			
		||||
        lock: true,
 | 
			
		||||
        text: '请稍候',
 | 
			
		||||
        background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // isDefault -> true 默认模型; -> false 指定设备的模型
 | 
			
		||||
    const isDefault = treeSelectItem.value.deviceId < 0;
 | 
			
		||||
    const postParams = {
 | 
			
		||||
        modelType: isDefault ? "default" : "specific",
 | 
			
		||||
        deviceId: isDefault ? undefined : treeSelectItem.value.deviceId,
 | 
			
		||||
        deviceTypeId: isDefault ? treeSelectItem.value.typeId : undefined,
 | 
			
		||||
        monitorCode: selectIndex.value,
 | 
			
		||||
    };
 | 
			
		||||
    console.log("getAlertModel -> postParams", postParams);
 | 
			
		||||
    await send_request('v1/alert-model/getAlertModel', "POST", postParams, (data: any) => {
 | 
			
		||||
        console.log("alert-model/getAlertModel", data)
 | 
			
		||||
        form.y.Enable = !!data.yellowIsAlert;
 | 
			
		||||
        form.y.Lower = data.yellowLowerBound;
 | 
			
		||||
        form.y.Upper = data.yellowUpperBound;
 | 
			
		||||
        form.o.Enable = !!data.orangeIsAlert;
 | 
			
		||||
        form.o.Lower = data.orangeLowerBound;
 | 
			
		||||
        form.o.Upper = data.orangeUpperBound;
 | 
			
		||||
        form.r.Enable = !!data.redIsAlert;
 | 
			
		||||
        form.r.Lower = data.redLowerBound;
 | 
			
		||||
        form.r.Upper = data.redUpperBound;
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
    loading.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Form 表单
 | 
			
		||||
const form = reactive({
 | 
			
		||||
    y: {
 | 
			
		||||
        _title: "黄色预警",
 | 
			
		||||
        Enable: false,
 | 
			
		||||
        Lower: null,
 | 
			
		||||
        Upper: null,
 | 
			
		||||
    },
 | 
			
		||||
    o: {
 | 
			
		||||
        _title: "橙色预警",
 | 
			
		||||
        Enable: false,
 | 
			
		||||
        Lower: null,
 | 
			
		||||
        Upper: null,
 | 
			
		||||
    },
 | 
			
		||||
    r: {
 | 
			
		||||
        _title: "红色预警",
 | 
			
		||||
        Enable: false,
 | 
			
		||||
        Lower: null,
 | 
			
		||||
        Upper: null,
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const onSubmit = async () => {
 | 
			
		||||
    console.log('submit!')
 | 
			
		||||
    const loading = ElLoading.service({
 | 
			
		||||
        lock: true,
 | 
			
		||||
        text: '请稍候',
 | 
			
		||||
        background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // isDefault -> true 默认模型; -> false 指定设备的模型
 | 
			
		||||
    const isDefault = treeSelectItem.value.deviceId < 0;
 | 
			
		||||
    const postParams = {
 | 
			
		||||
        modelType: isDefault ? "default" : "specific",
 | 
			
		||||
        deviceId: isDefault ? undefined : treeSelectItem.value.deviceId,
 | 
			
		||||
        deviceTypeId: isDefault ? treeSelectItem.value.typeId : undefined,
 | 
			
		||||
        monitorCode: selectIndex.value,
 | 
			
		||||
        yLower: form.y.Lower,
 | 
			
		||||
        yUpper: form.y.Upper,
 | 
			
		||||
        yEnable: form.y.Enable,
 | 
			
		||||
        oLower: form.o.Lower,
 | 
			
		||||
        oUpper: form.o.Upper,
 | 
			
		||||
        oEnable: form.o.Enable,
 | 
			
		||||
        rLower: form.r.Lower,
 | 
			
		||||
        rUpper: form.r.Upper,
 | 
			
		||||
        rEnable: form.r.Enable,
 | 
			
		||||
    };
 | 
			
		||||
    console.log("setAlertModel -> postParams", postParams);
 | 
			
		||||
    await send_request('v1/alert-model/setAlertModel', "POST", postParams, (data: any) => {
 | 
			
		||||
        console.log("v1/alert-model/setAlertModel", data)
 | 
			
		||||
        if (data) {
 | 
			
		||||
            ElMessage.success('保存成功');
 | 
			
		||||
        } else {
 | 
			
		||||
            ElMessage.error('保存失败');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    loading.close();
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.el-tree-node__content {
 | 
			
		||||
    height: 36px !important;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										396
									
								
								frontend/src/views/warning-view.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								frontend/src/views/warning-view.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,396 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="container">
 | 
			
		||||
		<el-row :gutter="20">
 | 
			
		||||
			<el-col :span="18">
 | 
			
		||||
				<el-row :gutter="20" class="mgb20">
 | 
			
		||||
					<el-col :span="6">
 | 
			
		||||
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
			
		||||
							<div class="grid-content grid-con-0">
 | 
			
		||||
								<el-icon class="grid-con-icon">
 | 
			
		||||
									<Odometer />
 | 
			
		||||
								</el-icon>
 | 
			
		||||
								<div class="grid-cont-right">
 | 
			
		||||
									<div class="grid-num">{{ levelCount?.today_total }}</div>
 | 
			
		||||
									<div>今日累计预警</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</el-card>
 | 
			
		||||
					</el-col>
 | 
			
		||||
					<el-col :span="6">
 | 
			
		||||
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
			
		||||
							<div class="grid-content grid-con-1">
 | 
			
		||||
								<el-icon class="grid-con-icon">
 | 
			
		||||
									<User />
 | 
			
		||||
								</el-icon>
 | 
			
		||||
								<div class="grid-cont-right">
 | 
			
		||||
									<div class="grid-num">{{ levelCount?.today_y }}</div>
 | 
			
		||||
									<div>黄色预警</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</el-card>
 | 
			
		||||
					</el-col>
 | 
			
		||||
					<el-col :span="6">
 | 
			
		||||
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
			
		||||
							<div class="grid-content grid-con-2">
 | 
			
		||||
								<el-icon class="grid-con-icon">
 | 
			
		||||
									<ChatDotRound />
 | 
			
		||||
								</el-icon>
 | 
			
		||||
								<div class="grid-cont-right">
 | 
			
		||||
									<div class="grid-num">{{ levelCount?.today_o }}</div>
 | 
			
		||||
									<div>橙色预警</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</el-card>
 | 
			
		||||
					</el-col>
 | 
			
		||||
					<el-col :span="6">
 | 
			
		||||
						<el-card shadow="hover" :body-style="{ padding: '0px' }">
 | 
			
		||||
							<div class="grid-content grid-con-3">
 | 
			
		||||
								<el-icon class="grid-con-icon">
 | 
			
		||||
									<Goods />
 | 
			
		||||
								</el-icon>
 | 
			
		||||
								<div class="grid-cont-right">
 | 
			
		||||
									<div class="grid-num">{{ levelCount?.today_r }}</div>
 | 
			
		||||
									<div>红色预警</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</el-card>
 | 
			
		||||
					</el-col>
 | 
			
		||||
				</el-row>
 | 
			
		||||
				<el-card shadow="hover" style="min-height: 403px">
 | 
			
		||||
					<template #header>
 | 
			
		||||
						<div class="clearfix">
 | 
			
		||||
							<span>预警列表</span>
 | 
			
		||||
							<el-button style="float: right; padding: 3px 0" text>查看全部</el-button>
 | 
			
		||||
						</div>
 | 
			
		||||
					</template>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
					<!-- 表格 -->
 | 
			
		||||
					<el-table :data="tableData" border class="table" ref="multipleTable"
 | 
			
		||||
						header-cell-class-name="table-header">
 | 
			
		||||
						<el-table-column prop="id" label="ID" width="55" align="center" v-if="false"></el-table-column>
 | 
			
		||||
						<el-table-column prop="createTime" label="报警时间" align="center"></el-table-column>
 | 
			
		||||
						<el-table-column prop="deviceName" label="报警设备" align="center"></el-table-column>
 | 
			
		||||
						<el-table-column prop="alertLevel" label="报警等级" align="center"></el-table-column>
 | 
			
		||||
						<el-table-column prop="monitorCode" label="监测项" align="center"></el-table-column>
 | 
			
		||||
						<el-table-column prop="monitorValue" label="监测值" align="center"></el-table-column>
 | 
			
		||||
						<el-table-column prop="threshold" label="阈值" align="center"></el-table-column>
 | 
			
		||||
						<el-table-column prop="deviceId" label="" align="center" v-if="false"></el-table-column>
 | 
			
		||||
						<!-- <el-table-column label="操作" width="220" align="center"> -->
 | 
			
		||||
						<!-- <template #default="scope">
 | 
			
		||||
								<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)"
 | 
			
		||||
									v-permiss="'user-setting'">
 | 
			
		||||
									编辑
 | 
			
		||||
								</el-button>
 | 
			
		||||
								<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index, scope.row)"
 | 
			
		||||
									v-permiss="'user-setting'">
 | 
			
		||||
									删除
 | 
			
		||||
								</el-button>
 | 
			
		||||
							</template> -->
 | 
			
		||||
						<!-- </el-table-column> -->
 | 
			
		||||
					</el-table>
 | 
			
		||||
					<!-- 分页 -->
 | 
			
		||||
					<div class="pagination">
 | 
			
		||||
						<el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
 | 
			
		||||
							:page-size="query.pageSize" :total="pageTotal"
 | 
			
		||||
							@current-change="handlePageChange"></el-pagination>
 | 
			
		||||
					</div>
 | 
			
		||||
					<!-- <el-table :show-header="false" :data="todoList" style="width: 100%">
 | 
			
		||||
						<el-table-column width="40">
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<el-checkbox v-model="scope.row.status"></el-checkbox>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
						<el-table-column>
 | 
			
		||||
							<template #default="scope">
 | 
			
		||||
								<div
 | 
			
		||||
									class="todo-item"
 | 
			
		||||
									:class="{
 | 
			
		||||
										'todo-item-del': scope.row.status
 | 
			
		||||
									}"
 | 
			
		||||
								>
 | 
			
		||||
									{{ scope.row.title }}
 | 
			
		||||
								</div>
 | 
			
		||||
							</template>
 | 
			
		||||
						</el-table-column>
 | 
			
		||||
					</el-table> -->
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
			<el-col :span="6">
 | 
			
		||||
				<el-card shadow="hover" class="mgb20">
 | 
			
		||||
					<p>
 | 
			
		||||
						监测预警总览
 | 
			
		||||
					</p>
 | 
			
		||||
					<div class="alert-image-container">
 | 
			
		||||
						<img class="alert-image" :class="isAlerting ? 'alert-ing' : []"
 | 
			
		||||
							:src="isAlerting ? '/assets/image/svg/alert_warning.svg' : '/assets/image/svg/alert_default.svg'"
 | 
			
		||||
							@click="isAlerting = !isAlerting">
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<!-- 表格 -->
 | 
			
		||||
					<el-table :data="alertLogCountTableData" style="width: 100%">
 | 
			
		||||
						<el-table-column prop="timeRange" label="" />
 | 
			
		||||
						<el-table-column prop="yellow" label="黄色预警" />
 | 
			
		||||
						<el-table-column prop="orange" label="橙色预警" />
 | 
			
		||||
						<el-table-column prop="red" label="红色预警" />
 | 
			
		||||
						<el-table-column prop="total" label="总计" />
 | 
			
		||||
					</el-table>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</el-col>
 | 
			
		||||
		</el-row>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="dashboard">
 | 
			
		||||
import { ref, reactive } from 'vue';
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
 | 
			
		||||
const name = localStorage.getItem('ms_username');
 | 
			
		||||
const role: string = name === 'admin' ? '超级管理员' : '普通用户';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// 筛选条件
 | 
			
		||||
const query = reactive({
 | 
			
		||||
	params: {
 | 
			
		||||
		deviceType: '',
 | 
			
		||||
		name: '',
 | 
			
		||||
	},
 | 
			
		||||
	pageIndex: 1,
 | 
			
		||||
	pageSize: 10
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 展示的铃铛是否处于报警状态
 | 
			
		||||
const isAlerting = ref(false);
 | 
			
		||||
 | 
			
		||||
const alertLevel = {
 | 
			
		||||
	"1": "黄色预警",
 | 
			
		||||
	"2": "橙色预警",
 | 
			
		||||
	"3": "红色预警",
 | 
			
		||||
}
 | 
			
		||||
const tableData: any = ref([]);
 | 
			
		||||
const pageTotal = ref(0);
 | 
			
		||||
const levelCount = ref({
 | 
			
		||||
	'today_y': '',
 | 
			
		||||
	'today_o': '',
 | 
			
		||||
	'today_r': '',
 | 
			
		||||
	'today_total': '',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 统计表格的数据
 | 
			
		||||
const alertLogCountTableData = ref([]);
 | 
			
		||||
 | 
			
		||||
const getData = async () => {
 | 
			
		||||
	// const loading = ElLoading.service({
 | 
			
		||||
	// 	lock: true,
 | 
			
		||||
	// 	text: '请稍候',
 | 
			
		||||
	// 	background: 'rgba(0, 0, 0, 0.7)',
 | 
			
		||||
	// });
 | 
			
		||||
	await send_request('v1/alert/list', "GET", {
 | 
			
		||||
		...query.params,
 | 
			
		||||
		pageIndex: query.pageIndex,
 | 
			
		||||
		pageSize: query.pageSize,
 | 
			
		||||
		page: query.pageIndex,
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
		let alertList = data;
 | 
			
		||||
 | 
			
		||||
		//生成"设备类型"数据字典
 | 
			
		||||
		//console.log("deviceList/deviceTypeList:", deviceList, deviceTypeList)
 | 
			
		||||
		// deviceTypeList.forEach((item: any) => deviceTypeDict.value[item.id] = item.type)
 | 
			
		||||
 | 
			
		||||
		// 渲染表格
 | 
			
		||||
		tableData.value = alertList.list.map((i: any) => {
 | 
			
		||||
			i.state = "在线"
 | 
			
		||||
			i.alertLevel = alertLevel[i.alertLevel]
 | 
			
		||||
			i.createTime = new Date(new Date(i.createTime).getTime() + 8 * 3600 * 1000).toISOString().substring(0, 19).replace('T', ' ')
 | 
			
		||||
			return i
 | 
			
		||||
		});
 | 
			
		||||
		pageTotal.value = alertList.total;
 | 
			
		||||
		// // 渲染下拉框
 | 
			
		||||
		// deviceTypeOption.value = deviceTypeList;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// 各种预警的数量
 | 
			
		||||
	await send_request('v1/chart-data/levelCount', "GET", {
 | 
			
		||||
	}, (data: any) => {
 | 
			
		||||
		console.log("v1/chart-data/levelCount:", data);
 | 
			
		||||
 | 
			
		||||
		let timeRangeDict = {
 | 
			
		||||
			"day": "今日",
 | 
			
		||||
			"week": "本周",
 | 
			
		||||
			"month": "本月",
 | 
			
		||||
			"year": "今年",
 | 
			
		||||
			"total": "累计",
 | 
			
		||||
		}
 | 
			
		||||
		alertLogCountTableData.value = data.map((row: any) => {
 | 
			
		||||
			row.total = row.yellow + row.orange + row.red;
 | 
			
		||||
			if (row.timeRange == "day") {
 | 
			
		||||
				levelCount.value.today_y = row.yellow;
 | 
			
		||||
				levelCount.value.today_o = row.orange;
 | 
			
		||||
				levelCount.value.today_r = row.red;
 | 
			
		||||
				levelCount.value.today_total = row.total;
 | 
			
		||||
				console.log(levelCount.value)
 | 
			
		||||
			}
 | 
			
		||||
			row.timeRange = timeRangeDict[row.timeRange]
 | 
			
		||||
			return row
 | 
			
		||||
		})
 | 
			
		||||
		console.log("alertLogCountTableData.value", alertLogCountTableData.value);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// loading.close();
 | 
			
		||||
};
 | 
			
		||||
getData();
 | 
			
		||||
// 分页导航
 | 
			
		||||
const handlePageChange = (val: number) => {
 | 
			
		||||
	query.pageIndex = val;
 | 
			
		||||
	getData();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
/* 警铃抖动样式 */
 | 
			
		||||
@keyframes shaking {
 | 
			
		||||
	0% {
 | 
			
		||||
		transform: rotate(0deg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	35% {
 | 
			
		||||
		transform: rotate(20deg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	65% {
 | 
			
		||||
		transform: rotate(-20deg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	100% {
 | 
			
		||||
		transform: rotate(0deg);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.alert-image-container {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.alert-image {
 | 
			
		||||
	width: 60%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.alert-ing {
 | 
			
		||||
	animation-duration: 1s;
 | 
			
		||||
	animation-name: shaking;
 | 
			
		||||
	animation-direction: normal;
 | 
			
		||||
	animation-iteration-count: infinite;
 | 
			
		||||
	animation-timing-function: cubic-bezier(0.76, 0.44, 0.33, 0.75);
 | 
			
		||||
	animation-delay: 0.8s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ############ */
 | 
			
		||||
 | 
			
		||||
.el-row {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-content {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	--grid-content-height: min(150px, 6vw);
 | 
			
		||||
	height: var(--grid-content-height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-cont-right {
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
	color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-num {
 | 
			
		||||
	font-size: min(70px, 4vw);
 | 
			
		||||
	font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-icon {
 | 
			
		||||
	font-size: min(50px, 2.8vw);
 | 
			
		||||
	width: min(150px, 5vw);
 | 
			
		||||
	height: var(--grid-content-height);
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	line-height: 100px;
 | 
			
		||||
	color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-0 .grid-con-icon {
 | 
			
		||||
	background: rgb(43 157 255);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-0 .grid-num {
 | 
			
		||||
	color: rgb(43 157 255);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-1 .grid-con-icon {
 | 
			
		||||
	background: rgb(255 223 43);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-1 .grid-num {
 | 
			
		||||
	color: rgb(255 223 43);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-2 .grid-con-icon {
 | 
			
		||||
	background: rgb(255, 135, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-2 .grid-num {
 | 
			
		||||
	color: rgb(255, 135, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-3 .grid-con-icon {
 | 
			
		||||
	background: rgb(242, 94, 67);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-con-3 .grid-num {
 | 
			
		||||
	color: rgb(242, 94, 67);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	align-items: center;
 | 
			
		||||
	padding-bottom: 20px;
 | 
			
		||||
	border-bottom: 2px solid #ccc;
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-cont {
 | 
			
		||||
	padding-left: 50px;
 | 
			
		||||
	flex: 1;
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
	color: #999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-cont div:first-child {
 | 
			
		||||
	font-size: 30px;
 | 
			
		||||
	color: #222;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-list {
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
	color: #999;
 | 
			
		||||
	line-height: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-info-list span {
 | 
			
		||||
	margin-left: 70px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mgb20 {
 | 
			
		||||
	margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.todo-item {
 | 
			
		||||
	font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.todo-item-del {
 | 
			
		||||
	text-decoration: line-through;
 | 
			
		||||
	color: #999;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										10
									
								
								frontend/src/vite-env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/src/vite-env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
/// <reference types="vite/client" />
 | 
			
		||||
 | 
			
		||||
declare module '*.vue' {
 | 
			
		||||
  import type { DefineComponent } from 'vue'
 | 
			
		||||
  const component: DefineComponent<{}, {}, any>
 | 
			
		||||
  export default component
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare module 'vue-schart';
 | 
			
		||||
declare module 'vue-cropperjs';
 | 
			
		||||
							
								
								
									
										32
									
								
								frontend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "ESNext",
 | 
			
		||||
    "useDefineForClassFields": true,
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "jsx": "preserve",
 | 
			
		||||
    "sourceMap": true,
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "isolatedModules": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "suppressImplicitAnyIndexErrors": true,
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "ESNext",
 | 
			
		||||
      "DOM"
 | 
			
		||||
    ],
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "allowJs": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "src/**/*.ts",
 | 
			
		||||
    "src/**/*.d.ts",
 | 
			
		||||
    "src/**/*.vue",
 | 
			
		||||
    "src/**/*.js"
 | 
			
		||||
  ],
 | 
			
		||||
  "references": [
 | 
			
		||||
    {
 | 
			
		||||
      "path": "./tsconfig.node.json"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								frontend/tsconfig.node.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/tsconfig.node.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "composite": true,
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "allowSyntheticDefaultImports": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["vite.config.ts"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								frontend/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								frontend/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import { defineConfig } from 'vite';
 | 
			
		||||
import vue from '@vitejs/plugin-vue';
 | 
			
		||||
import VueSetupExtend from 'vite-plugin-vue-setup-extend';
 | 
			
		||||
import AutoImport from 'unplugin-auto-import/vite';
 | 
			
		||||
import Components from 'unplugin-vue-components/vite';
 | 
			
		||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
 | 
			
		||||
import cesium from 'vite-plugin-cesium';
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
	base: './',
 | 
			
		||||
	server: {
 | 
			
		||||
		host: '0.0.0.0', // 允许局域网访问
 | 
			
		||||
	},
 | 
			
		||||
	plugins: [
 | 
			
		||||
		vue(),
 | 
			
		||||
		VueSetupExtend(),
 | 
			
		||||
		AutoImport({
 | 
			
		||||
			resolvers: [ElementPlusResolver()]
 | 
			
		||||
		}),
 | 
			
		||||
		Components({
 | 
			
		||||
			resolvers: [ElementPlusResolver()]
 | 
			
		||||
		}),
 | 
			
		||||
		cesium()
 | 
			
		||||
	],
 | 
			
		||||
	optimizeDeps: {
 | 
			
		||||
		include: ['schart.js']
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user