2025年03月31日18:24:36
23
.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?
|
5
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Generated by 'unplugin-auto-import'
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
|
||||||
|
}
|
88
components.d.ts
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// 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 {
|
||||||
|
BatchImp: typeof import('./src/components/batch-imp.vue')['default']
|
||||||
|
copy: typeof import('./src/components/countup copy.vue')['default']
|
||||||
|
Countup: typeof import('./src/components/countup.vue')['default']
|
||||||
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||||
|
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||||
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
|
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||||
|
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||||
|
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||||
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
|
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||||
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||||
|
ElCountdown: typeof import('element-plus/es')['ElCountdown']
|
||||||
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
|
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||||
|
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||||
|
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']
|
||||||
|
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']
|
||||||
|
ElLink: typeof import('element-plus/es')['ElLink']
|
||||||
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
|
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
||||||
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
|
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']
|
||||||
|
ElRate: typeof import('element-plus/es')['ElRate']
|
||||||
|
ElResult: typeof import('element-plus/es')['ElResult']
|
||||||
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
|
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||||
|
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||||
|
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||||
|
ElStep: typeof import('element-plus/es')['ElStep']
|
||||||
|
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||||
|
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']
|
||||||
|
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||||
|
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||||
|
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
|
||||||
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
|
ElTour: typeof import('element-plus/es')['ElTour']
|
||||||
|
ElTourStep: typeof import('element-plus/es')['ElTourStep']
|
||||||
|
ElTransfer: typeof import('element-plus/es')['ElTransfer']
|
||||||
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
|
ElWatermark: typeof import('element-plus/es')['ElWatermark']
|
||||||
|
Header: typeof import('./src/components/header.vue')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
Sidebar: typeof import('./src/components/sidebar.vue')['default']
|
||||||
|
TableCustom: typeof import('./src/components/table-custom.vue')['default']
|
||||||
|
TableDetail: typeof import('./src/components/table-detail.vue')['default']
|
||||||
|
TableEdit: typeof import('./src/components/table-edit.vue')['default']
|
||||||
|
TableSearch: typeof import('./src/components/table-search.vue')['default']
|
||||||
|
Tabs: typeof import('./src/components/tabs.vue')['default']
|
||||||
|
UploadImg: typeof import('./src/components/upload-img.vue')['default']
|
||||||
|
}
|
||||||
|
}
|
28
index.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!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="//at.alicdn.com/t/c/font_830376_92o68tc95je.css">
|
||||||
|
<script>window._AMapSecurityConfig = {
|
||||||
|
securityJsCode: '83572bd6398cb4594c611f93f89b506a'
|
||||||
|
}</script>
|
||||||
|
<script
|
||||||
|
src="https://webapi.amap.com/maps?v=1.4.15&key=e1e6dde852b57c61bacdcf1af21a3d9a&plugin=AMap.MouseTool&plugin=AMap.PolygonEditor&plugin=AMap.CircleEditor&plugin=AMap.MoveAnimation&plugin=AMap.PlaceSearch&plugin=AMap.AutoComplete&plugin=AMap.MoveAnimation"></script>
|
||||||
|
<script src=https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js></script>
|
||||||
|
</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>
|
47
package.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "vue-manage-system",
|
||||||
|
"version": "5.5.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"serve": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "*",
|
||||||
|
"@wangeditor/editor": "^5.1.23",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
|
"axios": "^1.6.3",
|
||||||
|
"countup.js": "^2.8.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
|
"echarts-wordcloud": "^2.1.0",
|
||||||
|
"element-plus": "^2.6.3",
|
||||||
|
"less": "^3.13.1",
|
||||||
|
"less-loader": "^5.0.0",
|
||||||
|
"md-editor-v3": "^2.11.2",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"vue": "^3.4.5",
|
||||||
|
"vue-cropper": "1.1.1",
|
||||||
|
"vue-echarts": "^6.6.9",
|
||||||
|
"vue-router": "^4.2.5",
|
||||||
|
"vue-schart": "^2.0.0",
|
||||||
|
"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-vue-setup-extend": "^0.4.0",
|
||||||
|
"vue-tsc": "^0.38.4"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
46
public/mock/role.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "管理员",
|
||||||
|
"key": "admin",
|
||||||
|
"status": true,
|
||||||
|
"permiss": [
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"11",
|
||||||
|
"12",
|
||||||
|
"13",
|
||||||
|
"2",
|
||||||
|
"21",
|
||||||
|
"22",
|
||||||
|
"23",
|
||||||
|
"24",
|
||||||
|
"3",
|
||||||
|
"31",
|
||||||
|
"32",
|
||||||
|
"33",
|
||||||
|
"331",
|
||||||
|
"332",
|
||||||
|
"4",
|
||||||
|
"41",
|
||||||
|
"42",
|
||||||
|
"5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "普通用户",
|
||||||
|
"key": "user",
|
||||||
|
"status": true,
|
||||||
|
"permiss": [
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"11",
|
||||||
|
"12",
|
||||||
|
"13"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pageTotal": 2
|
||||||
|
}
|
41
public/mock/table.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "张三",
|
||||||
|
"money": 123,
|
||||||
|
"address": "广东省东莞市长安镇",
|
||||||
|
"state": true,
|
||||||
|
"date": "2019-11-1",
|
||||||
|
"thumb": "https://lin-xin.gitee.io/images/post/wms.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "李四",
|
||||||
|
"money": 456,
|
||||||
|
"address": "广东省广州市白云区",
|
||||||
|
"state": true,
|
||||||
|
"date": "2019-10-11",
|
||||||
|
"thumb": "https://lin-xin.gitee.io/images/post/node3.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "王五",
|
||||||
|
"money": 789,
|
||||||
|
"address": "湖南省长沙市",
|
||||||
|
"state": false,
|
||||||
|
"date": "2019-11-11",
|
||||||
|
"thumb": "https://lin-xin.gitee.io/images/post/parcel.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "赵六",
|
||||||
|
"money": 1011,
|
||||||
|
"address": "福建省厦门市鼓浪屿",
|
||||||
|
"state": true,
|
||||||
|
"date": "2019-10-20",
|
||||||
|
"thumb": "https://lin-xin.gitee.io/images/post/notice.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pageTotal": 4
|
||||||
|
}
|
23
public/mock/user.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "张三",
|
||||||
|
"password": "123",
|
||||||
|
"email": "123@qq.com",
|
||||||
|
"phone": "12345678944",
|
||||||
|
"date": "2024-01-01",
|
||||||
|
"role": "管理员"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "李四",
|
||||||
|
"password": "123",
|
||||||
|
"email": "1234@qq.com",
|
||||||
|
"phone": "12345678945",
|
||||||
|
"date": "2024-01-01",
|
||||||
|
"role": "普通用户"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pageTotal": 2
|
||||||
|
}
|
BIN
public/template.xlsx
Normal file
BIN
screenshots/wms1.png
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
screenshots/wms3.png
Normal file
After Width: | Height: | Size: 425 KiB |
17
src/App.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<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';
|
||||||
|
import { useThemeStore } from './store/theme';
|
||||||
|
|
||||||
|
const theme = useThemeStore();
|
||||||
|
theme.initTheme();
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
@import './assets/css/main.css';
|
||||||
|
</style>
|
22
src/api/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import request from '../utils/request';
|
||||||
|
|
||||||
|
export const fetchData = () => {
|
||||||
|
return request({
|
||||||
|
url: './mock/table.json',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchUserData = () => {
|
||||||
|
return request({
|
||||||
|
url: './mock/user.json',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchRoleData = () => {
|
||||||
|
return request({
|
||||||
|
url: './mock/role.json',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
4
src/assets/css/icon.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[class*=" el-icon-lx"],
|
||||||
|
[class^=el-icon-lx] {
|
||||||
|
font-family: lx-iconfont !important;
|
||||||
|
}
|
101
src/assets/css/main.css
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
outline: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 22px 17px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table th {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-tips {
|
||||||
|
padding: 20px 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: #eef1f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-tips a {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button+.el-tooltip {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgb20 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgb10 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr10 {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-enter-active,
|
||||||
|
.move-leave-active {
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-enter-from,
|
||||||
|
.move-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--header-bg-color: #242f42;
|
||||||
|
--header-text-color: #fff;
|
||||||
|
--active-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.noScrollbar {
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
BIN
src/assets/img/battery.png
Normal file
After Width: | Height: | Size: 526 B |
BIN
src/assets/img/blood.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/img/handcuffs.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
src/assets/img/heart.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/assets/img/img.jpg
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/img/login-bg.jpg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
src/assets/img/loginimg.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/img/logo.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
1
src/assets/img/logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144.08 128.61"><title>资源 82</title><path d="M72.23 128.61c-7.1-.23-11.51-3.72-14.76-9.36C48 102.87 38.43 86.59 29.1 70.16a36 36 0 0 1-4.47-11.35A14.61 14.61 0 0 1 34 42.51c7.49-2.71 15.71-.21 19.67 6.43 7.52 12.56 14.77 25.27 22.12 37.92 3 5.17 5.89 10.43 9 15.51 5 8 3.45 18-4.22 23.31-2.3 1.62-5.52 1.99-8.34 2.93z" fill="#2ef2e9"/><path d="M72.66.33c6-.57 10.39 2.6 13.51 8C95.61 24.69 105 41.1 114.52 57.4c3.9 6.65-.28 17.13-6.39 20.44-8.93 4.83-17.88 1.28-21.86-5.62C76.82 55.86 67.14 39.62 58.11 23 52.06 12 59.61.24 72.66.33z" fill="#fa6663"/><path d="M144.08 15.83c-.58 8.62-6.73 15.57-15.51 15.66-9.31.09-16.87-7-16.95-15.62S119 0 127.87 0c9.13.09 16.22 7 16.21 15.83z" fill="#fbb355"/><path d="M16.24 31.5C7 31.33-.19 24.42 0 15.8.19 7.5 7.19-.06 14.64 0c10.53.08 18.27 6.73 17.61 15.9-.64 8.96-6.25 15.28-16.01 15.6z" fill="#8a56c2"/></svg>
|
After Width: | Height: | Size: 918 B |
BIN
src/assets/img/newly.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/img/onLine.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/assets/img/report.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/img/temperature.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
src/assets/img/ucenter-bg.jpg
Normal file
After Width: | Height: | Size: 37 KiB |
62
src/components/batch-imp.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog title="批量创建" v-model="dialogVisible" width="500px" destroy-on-close :close-on-click-modal="false">
|
||||||
|
<el-upload
|
||||||
|
:on-success="handleSuccess"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
class="upload-demo"
|
||||||
|
drag
|
||||||
|
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||||
|
<div class="el-upload__text"><em>点击</em>或将文件拖拽到这里上传</div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="tip-text">
|
||||||
|
<div class="el-upload__tip">只能上传excel文件,且不超过500KB</div>
|
||||||
|
<div class="down el-upload__tip">下载导入模板</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="dialogVisible = false"> 确定 </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
defineExpose({ dialogVisible });
|
||||||
|
const handleSuccess = (rawFile: any) => {
|
||||||
|
console.log(rawFile);
|
||||||
|
};
|
||||||
|
const beforeUpload = (rawFile: any) => {
|
||||||
|
if (rawFile.size / 1024 > 500) {
|
||||||
|
ElMessage.error("文件大小不能超过500KB");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (rawFile.name.indexOf(".xlsx") === -1 && rawFile.name.indexOf(".xls") === -1) {
|
||||||
|
ElMessage.error("只能上传excel文件");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tip-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.down {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
</style>
|
39
src/components/countup.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<span ref="countRef"></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { CountUp } from 'countup.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
end: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const countRef = ref<any>(null);
|
||||||
|
let countUp: any;
|
||||||
|
onMounted(() => {
|
||||||
|
countUp = new CountUp(countRef.value, props.end, props.options);
|
||||||
|
if (countUp.error) {
|
||||||
|
console.error(countUp.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
countUp.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.end, (newVal) => {
|
||||||
|
if (countUp) {
|
||||||
|
countUp.update(newVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
235
src/components/header.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<!-- 折叠按钮 -->
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="web-title">
|
||||||
|
<div class="t" v-for="(item, index) in tab.list" :key="`tab_${index}`" @click="toPage(index)">
|
||||||
|
{{ item }}{{ index != tab.list.length - 1 ? " / " : "" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="web-time">2025/03/26 10:05</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="header-user-con">
|
||||||
|
<!-- <div class="btn-icon" @click="router.push('/theme')">
|
||||||
|
<el-tooltip effect="dark" content="设置主题" placement="bottom">
|
||||||
|
<i class="el-icon-lx-skin"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="btn-icon" @click="router.push('/ucenter')">
|
||||||
|
<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>
|
||||||
|
<div class="btn-icon" @click="setFullScreen">
|
||||||
|
<el-tooltip effect="dark" content="全屏" placement="bottom">
|
||||||
|
<i class="el-icon-lx-full"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</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>
|
||||||
|
<el-dropdown-item 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 { useRouter } from "vue-router";
|
||||||
|
import imgurl from "../assets/img/img.jpg";
|
||||||
|
import { useTabsStore } from "@/store/tabs";
|
||||||
|
import { routes } from "@/router/index";
|
||||||
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
const tab = useTabsStore();
|
||||||
|
|
||||||
|
const username: string | null = localStorage.getItem("vuems_name");
|
||||||
|
const message: number = 2;
|
||||||
|
|
||||||
|
const sidebar = useSidebarStore();
|
||||||
|
|
||||||
|
// onMounted(() => {
|
||||||
|
// if (document.body.clientWidth < 1500) {
|
||||||
|
// sidebar.handleCollapse();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// 用户名下拉菜单选择事件
|
||||||
|
const router = useRouter();
|
||||||
|
const handleCommand = (command: string) => {
|
||||||
|
if (command == "loginout") {
|
||||||
|
localStorage.removeItem("vuems_name");
|
||||||
|
router.push("/login");
|
||||||
|
} else if (command == "user") {
|
||||||
|
router.push("/ucenter");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const toPage = (index: number) => {
|
||||||
|
if (index == tab.list.length - 1) return;
|
||||||
|
let item = getPath(routes[0].children, tab.list[index]);
|
||||||
|
if (!item.component) return;
|
||||||
|
router.push(item.path);
|
||||||
|
};
|
||||||
|
const getPath = (list: RouteRecordRaw[], name: string) => {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const item = list[i];
|
||||||
|
if (item.meta.tabs[item.meta.tabs.length - 1] == name) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
if (item.children && item.children.length) {
|
||||||
|
return getPath(item.children, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFullScreen = () => {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else {
|
||||||
|
document.body.requestFullscreen.call(document.body);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 70px;
|
||||||
|
color: var(--header-text-color);
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: calc(100% - 210px);
|
||||||
|
z-index: 998;
|
||||||
|
/* background-color: var(--header-bg-color); */
|
||||||
|
/* border-bottom: 1px solid #ddd; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 20px;
|
||||||
|
height: 100%;
|
||||||
|
.web-title {
|
||||||
|
color: #838ca9;
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
margin-right: 18px;
|
||||||
|
}
|
||||||
|
.t {
|
||||||
|
margin-right: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:last-child {
|
||||||
|
color: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.web-time {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-left: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-btn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.8;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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-icon {
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--header-text-color);
|
||||||
|
margin: 0 5px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-bell-badge {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 0px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f56c6c;
|
||||||
|
color: var(--header-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avator {
|
||||||
|
margin: 0 10px 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dropdown-link {
|
||||||
|
color: var(--header-text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #061451;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dropdown-menu__item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
225
src/components/sidebar.vue
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sidebar-container">
|
||||||
|
<el-menu
|
||||||
|
class="sidebar-el-menu"
|
||||||
|
:default-active="onRoutes"
|
||||||
|
:collapse="sidebar.collapse"
|
||||||
|
:background-color="sidebar.bgColor"
|
||||||
|
:text-color="sidebar.textColor"
|
||||||
|
>
|
||||||
|
<div class="sidebar-head">
|
||||||
|
<div class="sidebar-img">
|
||||||
|
<img src="../assets/img/logo.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-text" v-if="!sidebar.collapse">
|
||||||
|
<div class="sidebar-text-ch">智能手铐管理系统</div>
|
||||||
|
<div class="sidebar-text-en">ZHINENGSHOUKAOGUANLIXITONG</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-for="(item, index) in menuList">
|
||||||
|
<template v-if="item.children">
|
||||||
|
<el-sub-menu :index="item.name" :key="index">
|
||||||
|
<template #title>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="item.meta.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ item.meta.tabs[0] }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-for="(subItem, subIde) in item.children">
|
||||||
|
<el-sub-menu v-if="subItem.meta.children" :index="subItem.name" :key="`subIde_${subIde}`">
|
||||||
|
<template #title>{{ subItem.meta.tabs[0] }}</template>
|
||||||
|
<el-menu-item v-for="(threeItem, i) in subItem.children" :key="`subIde_${subIde}_${i}`" :index="threeItem.name" @click="menuClick(threeItem)">
|
||||||
|
{{ threeItem.meta.tabs[0] }}
|
||||||
|
</el-menu-item>
|
||||||
|
</el-sub-menu>
|
||||||
|
<el-menu-item v-else :index="subItem.name" @click="menuClick(subItem)">
|
||||||
|
<div class="yuan"></div>
|
||||||
|
{{ subItem.meta.tabs[1] }}
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</el-sub-menu>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-menu-item :index="item.name" :key="index" @click="menuClick(item)">
|
||||||
|
<el-icon>
|
||||||
|
<component :is="item.meta.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
<template #title>{{ item.meta.tabs[0] }}</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, useRouter, RouteRecordRaw } from "vue-router";
|
||||||
|
import { routes } from "@/router/index";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const onRoutes = computed(() => {
|
||||||
|
if (String(route.name).indexOf("/") != -1) {
|
||||||
|
return String(route.name).split("/")[0];
|
||||||
|
}
|
||||||
|
return route.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuClick = (item: RouteRecordRaw) => {
|
||||||
|
router.push(item.path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMenuList = (list: RouteRecordRaw[]) => {
|
||||||
|
let array = [];
|
||||||
|
list.forEach((item) => {
|
||||||
|
if (item.meta.hideMenu == true) {
|
||||||
|
} else {
|
||||||
|
array.push(item);
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
item.children = getMenuList(item.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return array;
|
||||||
|
};
|
||||||
|
|
||||||
|
let menuList = getMenuList(routes[0].children);
|
||||||
|
|
||||||
|
const sidebar = useSidebarStore();
|
||||||
|
// 侧边栏折叠
|
||||||
|
const collapseChage = () => {
|
||||||
|
sidebar.handleCollapse();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.sidebar-container {
|
||||||
|
bottom: 0px;
|
||||||
|
font-size: 0px;
|
||||||
|
height: 100%;
|
||||||
|
left: 0px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 1001;
|
||||||
|
overflow: visible;
|
||||||
|
width: 210px;
|
||||||
|
|
||||||
|
.sidebar-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
.sidebar-img {
|
||||||
|
width: 41px;
|
||||||
|
height: 41px;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-text {
|
||||||
|
color: #061550;
|
||||||
|
margin-left: 15px;
|
||||||
|
|
||||||
|
.sidebar-text-ch {
|
||||||
|
font-size: 18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.sidebar-text-en {
|
||||||
|
font-size: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.collapse-btn {
|
||||||
|
height: 40px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .sidebar::-webkit-scrollbar {
|
||||||
|
// width: 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.sidebar-el-menu:not(.el-menu--collapse) {
|
||||||
|
width: 210px;
|
||||||
|
height: 100%;
|
||||||
|
padding-top: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
.is-active {
|
||||||
|
color: #061451;
|
||||||
|
background: #061451;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.el-menu-item {
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 198px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&:hover {
|
||||||
|
background: #061451;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-sub-menu {
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 198px;
|
||||||
|
margin: 0 auto;
|
||||||
|
// color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
&.is-active {
|
||||||
|
background-color: transparent;
|
||||||
|
/deep/ .el-sub-menu__title {
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #061451;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-sub-menu__title {
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 228px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.el-menu {
|
||||||
|
.el-menu-item {
|
||||||
|
color: #787878;
|
||||||
|
&:hover {
|
||||||
|
background: #e6e7ed;
|
||||||
|
}
|
||||||
|
.yuan {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: #787878;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 21px;
|
||||||
|
}
|
||||||
|
&.is-active {
|
||||||
|
color: #061451;
|
||||||
|
background: #e6e7ed;
|
||||||
|
.yuan {
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-el-menu {
|
||||||
|
flex: 1;
|
||||||
|
// border: none;
|
||||||
|
// min-height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
217
src/components/table-custom.vue
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="table-toolbar" v-if="hasToolbar">
|
||||||
|
<div class="table-toolbar-left">
|
||||||
|
<slot name="toolbarBtn"></slot>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="table-toolbar-right flex-center">
|
||||||
|
<template v-if="multipleSelection.length > 0">
|
||||||
|
<el-tooltip effect="dark" content="删除选中" placement="top">
|
||||||
|
<el-icon class="columns-setting-icon" @click="delSelection(multipleSelection)">
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
</template>
|
||||||
|
<el-tooltip effect="dark" content="刷新" placement="top">
|
||||||
|
<el-icon class="columns-setting-icon" @click="refresh">
|
||||||
|
<Refresh />
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-tooltip effect="dark" content="列设置" placement="top">
|
||||||
|
<el-dropdown :hide-on-click="false" size="small" trigger="click">
|
||||||
|
<el-icon class="columns-setting-icon">
|
||||||
|
<Setting />
|
||||||
|
</el-icon>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="c in columns">
|
||||||
|
<el-checkbox v-model="c.visible" :label="c.label" />
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</el-tooltip>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
class="mgb20"
|
||||||
|
:row-class-name="tableRowClassName"
|
||||||
|
:style="{ width: '100%' }"
|
||||||
|
:data="tableData"
|
||||||
|
:row-key="rowKey"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
table-layout="auto"
|
||||||
|
>
|
||||||
|
<template v-for="item in columns" :key="item.prop">
|
||||||
|
<el-table-column v-if="item.visible" :prop="item.prop" :label="item.label" :width="item.width" :type="item.type" :align="item.align || 'center'">
|
||||||
|
<template #default="{ row, column, $index }" v-if="item.type === 'index'">
|
||||||
|
{{ getIndex($index) }}
|
||||||
|
</template>
|
||||||
|
<template #default="{ row, column, $index }" v-if="!item.type">
|
||||||
|
<slot :name="item.prop" :rows="row" :index="$index">
|
||||||
|
<template v-if="item.prop == 'operator'">
|
||||||
|
<el-button type="primary" size="small" :icon="Edit" @click="editFunc(row)"> 编辑 </el-button>
|
||||||
|
<el-button type="danger" size="small" :icon="Delete" @click="handleDelete(row)"> 删除 </el-button>
|
||||||
|
</template>
|
||||||
|
<span v-else-if="item.formatter">
|
||||||
|
{{ item.formatter(row[item.prop]) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ row[item.prop] }}
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-if="hasPagination"
|
||||||
|
:current-page="currentPage"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:pager-count="5"
|
||||||
|
:background="true"
|
||||||
|
:layout="layout"
|
||||||
|
:total="total"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { toRefs, PropType, ref } from "vue";
|
||||||
|
import { Delete, Edit, View, Refresh } from "@element-plus/icons-vue";
|
||||||
|
import { ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 表格相关
|
||||||
|
tableData: {
|
||||||
|
type: Array,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array as PropType<any[]>,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
rowKey: {
|
||||||
|
type: String,
|
||||||
|
default: "id",
|
||||||
|
},
|
||||||
|
hasToolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 分页相关
|
||||||
|
hasPagination: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
currentPage: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
|
||||||
|
layout: {
|
||||||
|
type: String,
|
||||||
|
default: "total, prev, pager, next",
|
||||||
|
},
|
||||||
|
delFunc: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
editFunc: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
delSelection: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
refresh: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
changePage: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let { tableData, columns, rowKey, hasToolbar, hasPagination, total, currentPage, pageSize, layout } = toRefs(props);
|
||||||
|
|
||||||
|
columns.value.forEach((item) => {
|
||||||
|
if (item.visible === undefined) {
|
||||||
|
item.visible = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableRowClassName = ({ row, rowIndex }: { row: User; rowIndex: number }) => {
|
||||||
|
if (rowIndex % 2 == 1) {
|
||||||
|
return "warning-row";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当选择项发生变化时会触发该事件
|
||||||
|
const multipleSelection = ref([]);
|
||||||
|
const handleSelectionChange = (selection: any[]) => {
|
||||||
|
multipleSelection.value = selection;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前页码变化的事件
|
||||||
|
const handleCurrentChange = (val: number) => {
|
||||||
|
props.changePage(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
ElMessageBox.confirm("确定要删除吗?", "提示", {
|
||||||
|
type: "warning",
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
props.delFunc(row);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIndex = (index: number) => {
|
||||||
|
return index + 1 + (currentPage.value - 1) * pageSize.value;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #dedede;
|
||||||
|
padding-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.columns-setting-icon {
|
||||||
|
display: block;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #676767;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.table-header .cell {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table .warning-row {
|
||||||
|
--el-table-tr-bg-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
.el-table .success-row {
|
||||||
|
/* --el-table-tr-bg-color: var(--el-color-success-light-9); */
|
||||||
|
}
|
||||||
|
</style>
|
21
src/components/table-detail.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<el-descriptions :title="title" :column="column" border>
|
||||||
|
<el-descriptions-item v-for="item in list" :span="item.span">
|
||||||
|
<template #label> {{ item.label }} </template>
|
||||||
|
<slot :name="item.prop" :rows="row">
|
||||||
|
{{ item.value || row[item.prop] }}
|
||||||
|
</slot>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { row, title, column = 2, list } = props.data;
|
||||||
|
|
||||||
|
</script>
|
117
src/components/table-edit.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" :label-width="options.labelWidth">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="options.span" v-for="item in options.list">
|
||||||
|
<el-form-item :label="item.label" :prop="item.prop">
|
||||||
|
<!-- 文本框、数字框、下拉框、日期框、开关、上传 -->
|
||||||
|
<el-input v-if="item.type === 'input'" v-model="form[item.prop]" :disabled="item.disabled" :placeholder="item.placeholder" clearable></el-input>
|
||||||
|
<el-input
|
||||||
|
v-if="item.type === 'textarea'"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
v-model="form[item.prop]"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
clearable
|
||||||
|
></el-input>
|
||||||
|
<el-input-number v-else-if="item.type === 'number'" v-model="form[item.prop]" :disabled="item.disabled" controls-position="right"></el-input-number>
|
||||||
|
<el-select v-else-if="item.type === 'select'" v-model="form[item.prop]" :disabled="item.disabled" :placeholder="item.placeholder" clearable>
|
||||||
|
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="form[item.prop]" :value-format="item.format"></el-date-picker>
|
||||||
|
<el-switch
|
||||||
|
v-else-if="item.type === 'switch'"
|
||||||
|
v-model="form[item.prop]"
|
||||||
|
:active-value="item.activeValue"
|
||||||
|
:inactive-value="item.inactiveValue"
|
||||||
|
:active-text="item.activeText"
|
||||||
|
:inactive-text="item.inactiveText"
|
||||||
|
></el-switch>
|
||||||
|
<el-upload v-else-if="item.type === 'upload'" class="avatar-uploader" action="#" :show-file-list="false" :on-success="handleAvatarSuccess">
|
||||||
|
<img v-if="form[item.prop]" :src="form[item.prop]" class="avatar" />
|
||||||
|
<el-icon v-else class="avatar-uploader-icon">
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
</el-upload>
|
||||||
|
<slot :name="item.prop" v-else> </slot>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="saveEdit(formRef)">保 存</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { FormOption } from "@/types/form-option";
|
||||||
|
import { FormInstance, FormRules, UploadProps } from "element-plus";
|
||||||
|
import { PropType, ref } from "vue";
|
||||||
|
|
||||||
|
const { options, formData, edit, update } = defineProps({
|
||||||
|
options: {
|
||||||
|
type: Object as PropType<FormOption>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
formData: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = ref({ ...(edit ? formData : {}) });
|
||||||
|
|
||||||
|
const rules: FormRules = options.list
|
||||||
|
.map((item) => {
|
||||||
|
if (item.required) {
|
||||||
|
return { [item.prop]: [{ required: true, message: `${item.label}不能为空`, trigger: "blur" }] };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
})
|
||||||
|
.reduce((acc, cur) => ({ ...acc, ...cur }), {});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const saveEdit = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate((valid) => {
|
||||||
|
if (!valid) return false;
|
||||||
|
update(form.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAvatarSuccess: UploadProps["onSuccess"] = (response, uploadFile) => {
|
||||||
|
form.value.thumb = URL.createObjectURL(uploadFile.raw!);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.avatar-uploader .el-upload {
|
||||||
|
border: 1px dashed var(--el-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: var(--el-transition-duration-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-uploader .el-upload:hover {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon.avatar-uploader-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8c939d;
|
||||||
|
width: 178px;
|
||||||
|
height: 178px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
81
src/components/table-search.vue
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-container">
|
||||||
|
<el-form ref="searchRef" :model="query" :inline="true">
|
||||||
|
<el-form-item :label="item.label" :prop="item.prop" v-for="item in options" :key="item.prop">
|
||||||
|
<!-- 文本框、下拉框、日期框 -->
|
||||||
|
<el-input
|
||||||
|
v-if="item.type === 'input'"
|
||||||
|
v-model="query[item.prop]"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:placeholder="item.placeholder || '请输入'"
|
||||||
|
clearable
|
||||||
|
></el-input>
|
||||||
|
<el-select
|
||||||
|
v-else-if="item.type === 'select'"
|
||||||
|
v-model="query[item.prop]"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
:placeholder="item.placeholder || '请选择'"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option v-for="(opt, index) in item.opts" :label="opt.label" :value="opt.value" :key="index"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="query[item.prop]" :value-format="item.format"></el-date-picker>
|
||||||
|
<el-date-picker
|
||||||
|
style="width: 220px"
|
||||||
|
v-else-if="item.type === 'daterange'"
|
||||||
|
v-model="query[item.prop]"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="-"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="search">搜索</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetForm(searchRef)">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { FormInstance } from "element-plus";
|
||||||
|
import { Search, Refresh } from "@element-plus/icons-vue";
|
||||||
|
import { PropType, ref } from "vue";
|
||||||
|
import { FormOptionList } from "@/types/form-option";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
query: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array as PropType<Array<FormOptionList>>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchRef = ref<FormInstance>();
|
||||||
|
const resetForm = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.resetFields();
|
||||||
|
props.search();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-container {
|
||||||
|
padding: 20px 30px 0;
|
||||||
|
background-color: #fff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
width: 168px;
|
||||||
|
}
|
||||||
|
</style>
|
69
src/components/upload-img.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<el-upload
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||||
|
list-type="picture-card"
|
||||||
|
:on-preview="handlePictureCardPreview"
|
||||||
|
:on-remove="handleRemove"
|
||||||
|
:style="`--width: ${size}px; --height: ${size}px`"
|
||||||
|
class="upload"
|
||||||
|
>
|
||||||
|
<el-icon><Plus /></el-icon>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogVisible">
|
||||||
|
<img w-full :src="dialogImageUrl" alt="Preview Image" />
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { Plus } from "@element-plus/icons-vue";
|
||||||
|
import type { UploadProps, UploadUserFile } from "element-plus";
|
||||||
|
const { size } = defineProps({
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileList = ref<UploadUserFile[]>([
|
||||||
|
{
|
||||||
|
name: "food.jpeg",
|
||||||
|
url: "https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "food.jpeg",
|
||||||
|
url: "https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dialogImageUrl = ref("");
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
|
||||||
|
const handleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {
|
||||||
|
console.log(uploadFile, uploadFiles);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePictureCardPreview: UploadProps["onPreview"] = (uploadFile) => {
|
||||||
|
dialogImageUrl.value = uploadFile.url!;
|
||||||
|
dialogVisible.value = true;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.upload {
|
||||||
|
/deep/ .el-upload {
|
||||||
|
width: var(--width);
|
||||||
|
height: var(--height);
|
||||||
|
}
|
||||||
|
/deep/ .el-upload-list__item {
|
||||||
|
width: var(--width);
|
||||||
|
height: var(--height);
|
||||||
|
}
|
||||||
|
/deep/ .el-icon--close-tip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
53
src/layout/index.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-wrapper">
|
||||||
|
<v-sidebar />
|
||||||
|
<v-header />
|
||||||
|
<div class="main-container">
|
||||||
|
<div class="app-main" :class="{ 'content-collapse': sidebar.collapse }">
|
||||||
|
<el-scrollbar>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="move" mode="out-in">
|
||||||
|
<keep-alive>
|
||||||
|
<component :is="Component"></component>
|
||||||
|
</keep-alive>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useSidebarStore } from "@/store/sidebar";
|
||||||
|
import vHeader from "@/components/header.vue";
|
||||||
|
import vSidebar from "@/components/sidebar.vue";
|
||||||
|
|
||||||
|
const sidebar = useSidebarStore();
|
||||||
|
console.log(sidebar);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.app-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
.main-container {
|
||||||
|
height: 100vh;
|
||||||
|
margin-left: 210px;
|
||||||
|
min-height: 100%;
|
||||||
|
position: relative;
|
||||||
|
transition: 0.3s;
|
||||||
|
background-color: #f2f4fa;
|
||||||
|
}
|
||||||
|
.app-main {
|
||||||
|
height: 100vh;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 70px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
29
src/main.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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';
|
||||||
|
import { ElNotification } from 'element-plus'
|
||||||
|
|
||||||
|
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 (binding.value && !permiss.key.includes(String(binding.value))) {
|
||||||
|
el['hidden'] = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.mount('#app');
|
135
src/router/index.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { createRouter, createWebHashHistory, RouteRecordRaw, } from 'vue-router';
|
||||||
|
import { usePermissStore } from '../store/permiss';
|
||||||
|
import { useTabsStore } from "@/store/tabs";
|
||||||
|
import Layout from '@/layout/index.vue';
|
||||||
|
import NProgress from 'nprogress';
|
||||||
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
|
export const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/statisticalCenter',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/statisticalCenter',
|
||||||
|
name: 'statisticalCenter',
|
||||||
|
component: () => import('@/views/statisticalCenter/index.vue'),
|
||||||
|
meta: { tabs: ['统计中心'], icon: 'el-icon-set-up' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/monitoringCenter',
|
||||||
|
name: 'monitoringCenter',
|
||||||
|
component: () => import('@/views/monitoringCenter/index.vue'),
|
||||||
|
meta: { tabs: ['监控中心'], icon: 'el-icon-set-up' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/alarmCenter',
|
||||||
|
name: 'alarmCenter',
|
||||||
|
component: () => import('@/views/alarmCenter/index.vue'),
|
||||||
|
meta: { tabs: ['告警中心'], icon: 'el-icon-set-up' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/incidentDispose',
|
||||||
|
name: 'alarmCenter/incidentDispose',
|
||||||
|
component: () => import('@/views/incidentDispose/index.vue'),
|
||||||
|
meta: { tabs: ['告警中心', '处理事件'], icon: 'el-icon-set-up', hideMenu: true, }
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '/synthesizeManage',
|
||||||
|
name: 'synthesizeManage',
|
||||||
|
meta: { tabs: ['综合管理中心'], icon: 'el-icon-school' },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/synthesizeManage/deviceManage',
|
||||||
|
name: 'deviceManage',
|
||||||
|
component: () => import('@/views/synthesizeManage/deviceManage/index.vue'),
|
||||||
|
meta: { tabs: ['综合管理中心', '设备管理'], icon: 'el-icon-set-up' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/synthesizeManage/deviceInfo',
|
||||||
|
name: 'deviceManage/deviceInfo',
|
||||||
|
component: () => import('@/views/synthesizeManage/deviceInfo/index.vue'),
|
||||||
|
meta: { tabs: ['综合管理中心', '设备管理', '详细信息'], hideMenu: true, }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/synthesizeManage/setting',
|
||||||
|
name: 'deviceManage/setting',
|
||||||
|
component: () => import('@/views/synthesizeManage/setting/index.vue'),
|
||||||
|
meta: { tabs: ['综合管理中心', '设备管理', '专项设置'], hideMenu: true, }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/synthesizeManage/mapLocation',
|
||||||
|
name: 'deviceManage/mapLocation',
|
||||||
|
component: () => import('@/views/synthesizeManage/mapLocation/index.vue'),
|
||||||
|
meta: { tabs: ['综合管理中心', '设备管理', '地图位置'], hideMenu: true, }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/synthesizeManage/userManage',
|
||||||
|
name: 'userManage',
|
||||||
|
component: () => import('@/views/synthesizeManage/userManage/index.vue'),
|
||||||
|
meta: { tabs: ['综合管理中心', '人员管理'], icon: 'el-icon-set-up' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/synthesizeManage/areaManage',
|
||||||
|
name: 'areaManage',
|
||||||
|
component: () => import('@/views/synthesizeManage/areaManage/index.vue'),
|
||||||
|
meta: { tabs: ['综合管理中心', '辖区管理'], icon: 'el-icon-set-up' }
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
meta: {
|
||||||
|
title: '登录',
|
||||||
|
noAuth: true,
|
||||||
|
},
|
||||||
|
component: () => import('@/views/login.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/403',
|
||||||
|
meta: {
|
||||||
|
title: '没有权限',
|
||||||
|
noAuth: true,
|
||||||
|
},
|
||||||
|
component: () => import('@/views/403.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
meta: {
|
||||||
|
title: '找不到页面',
|
||||||
|
noAuth: true,
|
||||||
|
},
|
||||||
|
component: () => import('@/views/404.vue'),
|
||||||
|
},
|
||||||
|
{ path: '/:path(.*)', redirect: '/404' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
|
||||||
|
NProgress.start();
|
||||||
|
const permiss = usePermissStore();
|
||||||
|
const tab = useTabsStore();
|
||||||
|
tab.setTabsItem(to.meta.tabs);
|
||||||
|
|
||||||
|
if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) {
|
||||||
|
// 如果没有权限,则进入403
|
||||||
|
next('/403');
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
|
NProgress.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
60
src/store/permiss.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
interface ObjectList {
|
||||||
|
[key: string]: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePermissStore = defineStore('permiss', {
|
||||||
|
state: () => {
|
||||||
|
const defaultList: ObjectList = {
|
||||||
|
admin: [
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'11',
|
||||||
|
'12',
|
||||||
|
'13',
|
||||||
|
'2',
|
||||||
|
'21',
|
||||||
|
'22',
|
||||||
|
'23',
|
||||||
|
'24',
|
||||||
|
'25',
|
||||||
|
'26',
|
||||||
|
'27',
|
||||||
|
'28',
|
||||||
|
'29',
|
||||||
|
'291',
|
||||||
|
'292',
|
||||||
|
'3',
|
||||||
|
'31',
|
||||||
|
'32',
|
||||||
|
'33',
|
||||||
|
'34',
|
||||||
|
'4',
|
||||||
|
'41',
|
||||||
|
'42',
|
||||||
|
'5',
|
||||||
|
'7',
|
||||||
|
'6',
|
||||||
|
'61',
|
||||||
|
'62',
|
||||||
|
'63',
|
||||||
|
'64',
|
||||||
|
'65',
|
||||||
|
'66',
|
||||||
|
],
|
||||||
|
user: ['0', '1', '11', '12', '13'],
|
||||||
|
};
|
||||||
|
const username = localStorage.getItem('vuems_name');
|
||||||
|
console.log(username);
|
||||||
|
return {
|
||||||
|
key: (username == 'admin' ? defaultList.admin : defaultList.user) as string[],
|
||||||
|
defaultList,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
handleSet(val: string[]) {
|
||||||
|
this.key = val;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
25
src/store/sidebar.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useSidebarStore = defineStore('sidebar', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
collapse: false,
|
||||||
|
bgColor: localStorage.getItem('sidebar-bg-color') || '#ffffff',
|
||||||
|
textColor: localStorage.getItem('sidebar-text-color') || '#061451'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
handleCollapse() {
|
||||||
|
this.collapse = !this.collapse;
|
||||||
|
},
|
||||||
|
setBgColor(color: string) {
|
||||||
|
this.bgColor = color;
|
||||||
|
localStorage.setItem('sidebar-bg-color', color);
|
||||||
|
},
|
||||||
|
setTextColor(color: string) {
|
||||||
|
this.textColor = color;
|
||||||
|
localStorage.setItem('sidebar-text-color', color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
20
src/store/tabs.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
|
|
||||||
|
export const useTabsStore = defineStore('tabs', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
list: <string[]>[]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
setTabsItem(data: any) {
|
||||||
|
|
||||||
|
this.list = data
|
||||||
|
},
|
||||||
|
clearTabs() {
|
||||||
|
this.list = [];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
60
src/store/theme.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { mix, setProperty } from '@/utils';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useThemeStore = defineStore('theme', {
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
primary: '#061451',
|
||||||
|
success: '',
|
||||||
|
warning: '',
|
||||||
|
danger: '',
|
||||||
|
info: '',
|
||||||
|
headerBgColor: '#242f42',
|
||||||
|
headerTextColor: '#fff',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
initTheme() {
|
||||||
|
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
|
||||||
|
const color = localStorage.getItem(`theme-${type}`) || '';
|
||||||
|
if (color) {
|
||||||
|
this.setPropertyColor(color, type); // 设置主题色
|
||||||
|
} else {
|
||||||
|
this.setPropertyColor('#061451', 'primary'); // 设置主题色
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const headerBgColor = localStorage.getItem('header-bg-color');
|
||||||
|
headerBgColor && this.setHeaderBgColor(headerBgColor);
|
||||||
|
const headerTextColor = localStorage.getItem('header-text-color');
|
||||||
|
headerTextColor && this.setHeaderTextColor(headerTextColor);
|
||||||
|
},
|
||||||
|
resetTheme() {
|
||||||
|
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
|
||||||
|
this.setPropertyColor('', type); // 重置主题色
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setPropertyColor(color: string, type: string = 'primary') {
|
||||||
|
this[type] = color;
|
||||||
|
setProperty(`--el-color-${type}`, color);
|
||||||
|
localStorage.setItem(`theme-${type}`, color);
|
||||||
|
this.setThemeLight(type);
|
||||||
|
},
|
||||||
|
setThemeLight(type: string = 'primary') {
|
||||||
|
[3, 5, 7, 8, 9].forEach((v) => {
|
||||||
|
setProperty(`--el-color-${type}-light-${v}`, mix('#ffffff', this[type], v / 10));
|
||||||
|
});
|
||||||
|
setProperty(`--el-color-${type}-dark-2`, mix('#ffffff', this[type], 0.2));
|
||||||
|
},
|
||||||
|
setHeaderBgColor(color: string) {
|
||||||
|
this.headerBgColor = color;
|
||||||
|
setProperty('--header-bg-color', color);
|
||||||
|
localStorage.setItem(`header-bg-color`, color);
|
||||||
|
},
|
||||||
|
setHeaderTextColor(color: string) {
|
||||||
|
this.headerTextColor = color;
|
||||||
|
setProperty('--header-text-color', color);
|
||||||
|
localStorage.setItem(`header-text-color`, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
21
src/types/form-option.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export interface FormOption {
|
||||||
|
list: FormOptionList[];
|
||||||
|
labelWidth?: number | string;
|
||||||
|
span?: number;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormOptionList {
|
||||||
|
prop: string;
|
||||||
|
label: string;
|
||||||
|
type: string;
|
||||||
|
placeholder?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
opts?: any[];
|
||||||
|
format?: string;
|
||||||
|
activeValue?: any;
|
||||||
|
inactiveValue?: any;
|
||||||
|
activeText?: string;
|
||||||
|
inactiveText?: string;
|
||||||
|
required?: boolean;
|
||||||
|
}
|
9
src/types/menu.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface Menus {
|
||||||
|
id: string;
|
||||||
|
pid?: string;
|
||||||
|
icon?: string;
|
||||||
|
index: string;
|
||||||
|
title: string;
|
||||||
|
permiss?: string;
|
||||||
|
children?: Menus[];
|
||||||
|
}
|
8
src/types/role.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
export interface Role {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
status: boolean;
|
||||||
|
permiss: string[]
|
||||||
|
}
|
9
src/types/table.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface TableItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
thumb: string;
|
||||||
|
money: number;
|
||||||
|
state: string;
|
||||||
|
date: string;
|
||||||
|
address: string;
|
||||||
|
}
|
16
src/types/user.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
role: string;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Register {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
email: string;
|
||||||
|
}
|
3
src/utils/china.ts
Normal file
14
src/utils/index.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const setProperty = (prop: string, val: any, dom = document.documentElement) => {
|
||||||
|
dom.style.setProperty(prop, val);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
|
||||||
|
let color = '#';
|
||||||
|
for (let i = 0; i <= 2; i++) {
|
||||||
|
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16);
|
||||||
|
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16);
|
||||||
|
const c = Math.round(c1 * weight + c2 * (1 - weight));
|
||||||
|
color += c.toString(16).padStart(2, '0');
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
};
|
38
src/utils/mapCustom.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export class MapCustom {
|
||||||
|
drawResolve = null;
|
||||||
|
constructor(data) {
|
||||||
|
this.map = new AMap.Map(data.dom, {
|
||||||
|
version: "1.4.15",
|
||||||
|
zoom: 13,
|
||||||
|
center: [116.397428, 39.90923], //初始化地图中心点
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
draw() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.drawResolve = resolve;
|
||||||
|
this.map.clearMap();
|
||||||
|
let list = [];
|
||||||
|
let mouseTool = new AMap.MouseTool(this.map);
|
||||||
|
// mouseTool.rectangle();
|
||||||
|
mouseTool.polygon({
|
||||||
|
center: ["116.368074", "39.927925"],
|
||||||
|
fillColor: "#00aeff57",
|
||||||
|
strokeColor: "#00AEFF", //描边颜色
|
||||||
|
strokeWeight: 2,
|
||||||
|
strokeStyle: "solid",
|
||||||
|
});
|
||||||
|
|
||||||
|
mouseTool.on("draw", (event) => {
|
||||||
|
event.obj.getPath().forEach((e, i) => {
|
||||||
|
list.push({ lat: e.lat, lng: e.lng });
|
||||||
|
});
|
||||||
|
mouseTool.close(false);
|
||||||
|
this.drawResolve(list);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
clearMap() {
|
||||||
|
this.map.clearMap();
|
||||||
|
}
|
||||||
|
}
|
31
src/utils/request.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import axios, { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
const service: AxiosInstance = axios.create({
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config: InternalAxiosRequestConfig) => {
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error: AxiosError) => {
|
||||||
|
console.log(error);
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
service.interceptors.response.use(
|
||||||
|
(response: AxiosResponse) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
Promise.reject();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error: AxiosError) => {
|
||||||
|
console.log(error);
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default service;
|
67
src/views/403.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error-page">
|
||||||
|
<div class="error-box">
|
||||||
|
<div class="error-code">403</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" size="large" @click="goBack">返回上一页</el-button>
|
||||||
|
</div>
|
||||||
|
</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: 100vh;
|
||||||
|
background: #eef0fc;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-box {
|
||||||
|
width: 400px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 80px 50px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code {
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 100px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-desc {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #777;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-handle {
|
||||||
|
margin-top: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-btn {
|
||||||
|
margin-left: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
67
src/views/404.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error-page">
|
||||||
|
<div class="error-box">
|
||||||
|
<div class="error-code">404</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" size="large" @click="goBack">返回上一页</el-button>
|
||||||
|
</div>
|
||||||
|
</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: 100vh;
|
||||||
|
background: #eef0fc;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-box {
|
||||||
|
width: 400px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 80px 50px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code {
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 100px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-desc {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #777;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-handle {
|
||||||
|
margin-top: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-btn {
|
||||||
|
margin-left: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
96
src/views/alarmCenter/index.vue
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||||
|
<div class="table-container">
|
||||||
|
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :refresh="getData" :currentPage="page.index" :changePage="changePage">
|
||||||
|
<template #money="{ rows }"> ¥{{ rows.money }} </template>
|
||||||
|
<template #thumb="{ rows }">
|
||||||
|
<el-image class="table-td-thumb" :src="rows.thumb" :z-index="10" :preview-src-list="[rows.thumb]" preview-teleported> </el-image>
|
||||||
|
</template>
|
||||||
|
<template #state="{ rows }">
|
||||||
|
<el-tag :type="rows.state ? 'success' : 'danger'">
|
||||||
|
{{ rows.state ? "正常" : "异常" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #operator>
|
||||||
|
<el-button type="primary" size="small" link @click="router.push('/incidentDispose')"> 处理事件 </el-button>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="basetable">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { fetchData } from "@/api/index";
|
||||||
|
import TableCustom from "@/components/table-custom.vue";
|
||||||
|
import TableDetail from "@/components/table-detail.vue";
|
||||||
|
import TableSearch from "@/components/table-search.vue";
|
||||||
|
import { TableItem } from "@/types/table";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { FormOption, FormOptionList } from "@/types/form-option";
|
||||||
|
const router = useRouter();
|
||||||
|
// 查询相关
|
||||||
|
const query = reactive({
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
const searchOpt = ref<FormOptionList[]>([
|
||||||
|
{ type: "select", label: "来源手铐:", prop: "name1", opts: [{ label: "866503071660886", value: "866503071660886" }] },
|
||||||
|
{ type: "select", label: "事件类型:", prop: "name2", opts: [{ label: "866503071660886", value: "866503071660886" }] },
|
||||||
|
{ type: "select", label: "事件级别:", prop: "name3", opts: [{ label: "866503071660886", value: "866503071660886" }] },
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
label: "处理状态:",
|
||||||
|
prop: "name4",
|
||||||
|
opts: [
|
||||||
|
{ label: "已处理", value: "1" },
|
||||||
|
{ label: "未处理", value: "0" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "daterange", label: "创建时间:", prop: "name5" },
|
||||||
|
]);
|
||||||
|
const handleSearch = () => {
|
||||||
|
changePage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "手铐IMEI号" },
|
||||||
|
{ prop: "name", label: "关联人员" },
|
||||||
|
{ prop: "name", label: "事件类型" },
|
||||||
|
{ prop: "money", label: "事件级别" },
|
||||||
|
{ prop: "thumb", label: "时间" },
|
||||||
|
{ prop: "state", label: "处理状态" },
|
||||||
|
{ prop: "operator", label: "操作" },
|
||||||
|
]);
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 200,
|
||||||
|
});
|
||||||
|
const tableData = ref<TableItem[]>([]);
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchData();
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-td-thumb {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
273
src/views/incidentDispose/index.vue
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="7">
|
||||||
|
<div class="incidentList">
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tabs-item" :class="`${index == 0 ? 'active' : ''}`" v-for="(item, index) in tabs" :key="index">{{ item.label }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
<div class="list-item active" v-for="item in 5" :key="item">
|
||||||
|
<div class="img">
|
||||||
|
<img :src="handcuffs" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-name">设备序号:10</div>
|
||||||
|
<div class="content-time">2025/03/26 18:33:32</div>
|
||||||
|
</div>
|
||||||
|
<el-tag type="warning" size="large">未处理</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="10">
|
||||||
|
<div class="map-content">
|
||||||
|
<div class="map" id="mapcontainer"></div>
|
||||||
|
<div class="chart">
|
||||||
|
<div ref="chartRef" style="width: 100%; height: 100%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="7">
|
||||||
|
<div class="info">
|
||||||
|
<div class="info-text">设备序号:05</div>
|
||||||
|
<div class="info-text">设备SN号:860116079430636</div>
|
||||||
|
<div class="info-text">告警时间:2025/03/26 18:33:32</div>
|
||||||
|
<div class="info-text">告警类型:体表温度过低</div>
|
||||||
|
<div class="info-text">绑定关联人名称:管理员</div>
|
||||||
|
<div class="info-text">绑定关联人警号:欧尼蒋</div>
|
||||||
|
<div class="info-text">现在状态:禁用</div>
|
||||||
|
<div class="info-text">紧急电话:10000000000</div>
|
||||||
|
<div class="info-text">隶属辖区:87</div>
|
||||||
|
<div class="info-box">
|
||||||
|
<div class="info-box-title">生理记录</div>
|
||||||
|
<div class="info-box-contetn">
|
||||||
|
<div class="item">
|
||||||
|
<div class="label">血氧:</div>
|
||||||
|
<div class="value">97%</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="label">体表温度:</div>
|
||||||
|
<div class="value">36.4℃</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="label">心率:</div>
|
||||||
|
<div class="value">86 次/分</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-form">
|
||||||
|
<el-form :model="formInline" label-position="top">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="处理人:">
|
||||||
|
<el-input v-model="formInline.user" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="警号:">
|
||||||
|
<el-input v-model="formInline.user" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="处理记录:">
|
||||||
|
<el-input v-model="formInline.user" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理图片:">
|
||||||
|
<Upload />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary">保存</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="incidentDispose">
|
||||||
|
import handcuffs from "@/assets/img/handcuffs.png";
|
||||||
|
import { MapCustom } from "@/utils/mapCustom";
|
||||||
|
import { onMounted, ref, reactive } from "vue";
|
||||||
|
import Upload from "@/components/upload-img.vue";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
const chartRef = ref(null);
|
||||||
|
|
||||||
|
let tabs = [
|
||||||
|
{ label: "全部", value: "0" },
|
||||||
|
{ label: "未处理", value: "1" },
|
||||||
|
{ label: "已处理", value: "2" },
|
||||||
|
];
|
||||||
|
const ringOptions = {
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: "10%",
|
||||||
|
right: "4%",
|
||||||
|
bottom: "10%",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||||
|
type: "line",
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const formInline = reactive({
|
||||||
|
user: "",
|
||||||
|
region: "",
|
||||||
|
date: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (chartRef.value) {
|
||||||
|
const myChart = echarts.init(chartRef.value);
|
||||||
|
myChart.setOption(ringOptions);
|
||||||
|
}
|
||||||
|
new MapCustom({ dom: "mapcontainer" });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.container {
|
||||||
|
.incidentList {
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 54px;
|
||||||
|
background: #ffffff;
|
||||||
|
.tabs-item {
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
color: #787878;
|
||||||
|
font-size: 20px;
|
||||||
|
&.active {
|
||||||
|
color: #061451;
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
width: 20px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #061451;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -13px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
.list-item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100px;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 14px;
|
||||||
|
&.active {
|
||||||
|
border-left: 2px solid #061451;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 10px;
|
||||||
|
.content-name {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.content-time {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
.map {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.chart {
|
||||||
|
height: 391px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
background: #ffffff;
|
||||||
|
height: 100%;
|
||||||
|
padding: 17px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.info-text {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.info-box {
|
||||||
|
padding: 20px 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #fafafa;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
|
||||||
|
.info-box-title {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 18px;
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.info-box-contetn {
|
||||||
|
padding: 0 11px;
|
||||||
|
.item {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 18px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.info-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// /deep/ .el-upload {
|
||||||
|
// width: 100px;
|
||||||
|
// height: 100px;
|
||||||
|
// }
|
||||||
|
</style>
|
91
src/views/login.vue
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="img"></div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-title">欢迎来到,智能手铐管理系统</div>
|
||||||
|
<el-form :model="form" label-position="top" label-width="auto" style="max-width: 600px">
|
||||||
|
<el-form-item label="账号">
|
||||||
|
<el-input v-model="form.acc" class="input" placeholder="请输入账号" size="large" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码">
|
||||||
|
<el-input v-model="form.pwd" type="password" class="input" placeholder="请输入密码" size="large" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="onSubmit" class="btn" size="large">登 录</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<div class="hint">
|
||||||
|
如遇到账号问题,请联系管理员<br />
|
||||||
|
管理员联系方式:13812345678 邮箱:123456@qq.com
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// do not use same name with ref
|
||||||
|
const form = reactive({
|
||||||
|
acc: "",
|
||||||
|
pwd: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
// console.log("submit!");
|
||||||
|
router.push("statisticalCenter");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
@media screen and (max-width: 1350px) {
|
||||||
|
.img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.img {
|
||||||
|
width: 844px;
|
||||||
|
height: 100%;
|
||||||
|
background: url("../assets/img/loginimg.png") no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 570px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.content-title {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 80px;
|
||||||
|
}
|
||||||
|
.input {
|
||||||
|
width: 476px;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 476px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hint {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.el-input__suffix-inner {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
45
src/views/monitoringCenter/deviceHistory.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div class="deviceStatistics card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="title">当前设备历史数据</div>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type="primary">心率</el-button>
|
||||||
|
<el-button>血氧</el-button>
|
||||||
|
<el-button>体表温度</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
<slot name="chart"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup></script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.card {
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 16px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.title {
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
content: "";
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
134
src/views/monitoringCenter/deviceInfo.vue
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<div class="device">
|
||||||
|
<div class="device-head">
|
||||||
|
<div class="title">设备列表</div>
|
||||||
|
<el-select class="select" size="large">
|
||||||
|
<el-option label="全部" value="0" />
|
||||||
|
<el-option label="常规模式" value="1" />
|
||||||
|
<el-option label="户外押送" value="2" />
|
||||||
|
<el-option label="审讯模式" value="3" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="device-list noScrollbar">
|
||||||
|
<el-popover class="box-item" title="Title" content="Top Left prompts info" placement="bottom" v-for="item in 4" :key="item">
|
||||||
|
<template #reference>
|
||||||
|
<div class="item">
|
||||||
|
<div class="item-img">
|
||||||
|
<img src="@/assets/img/handcuffs.png" alt="" srcset="" />
|
||||||
|
</div>
|
||||||
|
<div class="item-content">
|
||||||
|
<div class="item-content-name">手铐-002</div>
|
||||||
|
<div class="item-content-num">860116079430636</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-right">
|
||||||
|
<div class="battery">
|
||||||
|
<div class="battery-icon">
|
||||||
|
<img src="@/assets/img/battery.png" alt="" srcset="" />
|
||||||
|
</div>
|
||||||
|
<div class="battery-num">98%</div>
|
||||||
|
</div>
|
||||||
|
<div class="user">admin</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup></script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.device {
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.device-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-top: 14px;
|
||||||
|
.title {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
content: "";
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.select {
|
||||||
|
width: 120px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.device-list {
|
||||||
|
overflow: auto;
|
||||||
|
.item {
|
||||||
|
height: 89px;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #dedede;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.item-img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
margin-left: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-content {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 15px;
|
||||||
|
.item-content-name {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.item-content-num {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-right {
|
||||||
|
margin-right: 20px;
|
||||||
|
.battery {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.battery-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 5px;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.battery-num {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.user {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
84
src/views/monitoringCenter/deviceRecord.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div class="deviceStatistics card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="title">当前设备告警记录</div>
|
||||||
|
<div class="search">
|
||||||
|
<el-select class="select" style="width: 120px; margin-right: 20px">
|
||||||
|
<el-option label="户外押送" value="1" />
|
||||||
|
<el-option label="审讯模式" value="2" />
|
||||||
|
</el-select>
|
||||||
|
<el-select class="select" style="width: 120px">
|
||||||
|
<el-option label="户外押送" value="1" />
|
||||||
|
<el-option label="审讯模式" value="2" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :refresh="getData" :currentPage="page.index" :changePage="changePage">
|
||||||
|
<template #state="{ rows }">
|
||||||
|
<el-tag :type="rows.state ? 'success' : 'danger'">
|
||||||
|
{{ rows.state ? "正常" : "异常" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
tableData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
getData: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
changePage: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = [
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "事件类型" },
|
||||||
|
{ prop: "name", label: "触发时间" },
|
||||||
|
{ prop: "state", label: "处理状态" },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.card {
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 16px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.title {
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
content: "";
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
103
src/views/monitoringCenter/index.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6"><DeviceInfo /></el-col>
|
||||||
|
<el-col :span="18">
|
||||||
|
<MonitoringTop />
|
||||||
|
<div v-if="false">
|
||||||
|
<div class="monitoringMap" id="mapcontainer"></div>
|
||||||
|
<el-row :gutter="20" style="margin-top: 20px">
|
||||||
|
<el-col :span="12">
|
||||||
|
<DeviceHistory>
|
||||||
|
<template #chart>
|
||||||
|
<div ref="chartRef" style="width: 100%; height: 100%"></div>
|
||||||
|
</template>
|
||||||
|
</DeviceHistory>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<DeviceRecord :tableData="tableData" :page="page" :getData="getData" :changePage="changePage" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-row :gutter="20" style="margin-top: 20px">
|
||||||
|
<el-col :span="24" style="height: 400px">
|
||||||
|
<DeviceHistory>
|
||||||
|
<template #chart>
|
||||||
|
<div ref="chartRef" style="width: 100%; height: 100%"></div>
|
||||||
|
</template>
|
||||||
|
</DeviceHistory>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="margin-top: 20px">
|
||||||
|
<DeviceRecord :tableData="tableData" :page="page" :getData="getData" :changePage="changePage" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import DeviceInfo from "./deviceInfo.vue";
|
||||||
|
import MonitoringTop from "./monitoringTop.vue";
|
||||||
|
import DeviceHistory from "./deviceHistory.vue";
|
||||||
|
import DeviceRecord from "./deviceRecord.vue";
|
||||||
|
import { MapCustom } from "@/utils/mapCustom";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
import { fetchData } from "@/api/index";
|
||||||
|
import { onMounted, ref, reactive } from "vue";
|
||||||
|
import { TableItem } from "@/types/table";
|
||||||
|
const chartRef = ref(null);
|
||||||
|
|
||||||
|
const ringOptions = {
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||||
|
type: "line",
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableData = ref<TableItem[]>([]);
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchData();
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
new MapCustom({ dom: "mapcontainer" });
|
||||||
|
|
||||||
|
if (chartRef.value) {
|
||||||
|
const myChart = echarts.init(chartRef.value);
|
||||||
|
myChart.setOption(ringOptions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.monitoringMap {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
82
src/views/monitoringCenter/monitoringTop.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<el-row :gutter="20" class="monitoring-top">
|
||||||
|
<el-col :span="8" v-for="item in funcList">
|
||||||
|
<div class="item">
|
||||||
|
<div class="item-left">
|
||||||
|
<div class="item-left-head">
|
||||||
|
<div class="title">{{ item.title }}</div>
|
||||||
|
<div class="en">{{ item.en }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-left-bottom">
|
||||||
|
<div class="num" :style="{ color: item.color }">{{ item.num }}</div>
|
||||||
|
<div class="unit" :style="{ color: item.color }">{{ item.unit }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-img">
|
||||||
|
<img :src="item.icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import heart from "@/assets/img/heart.png";
|
||||||
|
import temperature from "@/assets/img/temperature.png";
|
||||||
|
import blood from "@/assets/img/blood.png";
|
||||||
|
let funcList = [
|
||||||
|
{ title: "当前心率", en: "DANGQIANXINLV", icon: heart, unit: "次/分", num: 86, color: "#FF0303" },
|
||||||
|
{ title: "当前血氧", en: "DANGQIANXUEYANG", icon: blood, unit: "%", num: 99, color: "#8B51FD" },
|
||||||
|
{ title: "当前体表温度", en: "DANGQIANTIBIAOWENDU", icon: temperature, unit: "次/分", num: 86, color: "#FF6905" },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.monitoring-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.item {
|
||||||
|
height: 160px;
|
||||||
|
padding: 25px 33px 16px 36px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.item-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
.item-left-head {
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #061550;
|
||||||
|
}
|
||||||
|
.en {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-left-bottom {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
.num {
|
||||||
|
color: #ff0303;
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
.unit {
|
||||||
|
color: #ff0303;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
257
src/views/pages/icon.vue
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<el-tabs type="border-card">
|
||||||
|
<el-tab-pane label="自定义图标">
|
||||||
|
<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>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="Element图标">
|
||||||
|
<el-link type="primary" href="https://element-plus.org/zh-CN/component/icon.html#icon-collection"
|
||||||
|
target="_blank">前往官方文档查看</el-link>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</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',
|
||||||
|
'apple-line',
|
||||||
|
'baidu-fill',
|
||||||
|
'amazon-fill',
|
||||||
|
'netease-cloud-music-fill',
|
||||||
|
'qq-line',
|
||||||
|
'wechat-fill',
|
||||||
|
'alipay-fill',
|
||||||
|
'android-fill',
|
||||||
|
'android-line',
|
||||||
|
'whatsapp-line',
|
||||||
|
'whatsapp-fill',
|
||||||
|
'bilibili-fill',
|
||||||
|
'chrome-fill',
|
||||||
|
'dingding-fill',
|
||||||
|
'dingding-line',
|
||||||
|
'apple-fill',
|
||||||
|
'github-fill',
|
||||||
|
'qq-fill',
|
||||||
|
'wechat-pay-fill',
|
||||||
|
'windows-line',
|
||||||
|
'windows-fill',
|
||||||
|
'youtube-line',
|
||||||
|
'youtube-fill',
|
||||||
|
'wechat-pay-line',
|
||||||
|
'zhihu-line'
|
||||||
|
];
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 700px;
|
||||||
|
}
|
||||||
|
</style>
|
100
src/views/pages/reset-pwd.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-bg">
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="reset-title">重置密码</div>
|
||||||
|
<p class="reset-text">输入你的邮箱,我们将发送重置密码邮件</p>
|
||||||
|
<el-form :model="param" :rules="rules" ref="register" size="large">
|
||||||
|
<el-form-item prop="email">
|
||||||
|
<el-input v-model="param.email" placeholder="邮箱">
|
||||||
|
<template #prepend>
|
||||||
|
<el-icon>
|
||||||
|
<Message />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)">发送邮件</el-button>
|
||||||
|
<p class="login-text"><el-link type="primary" @click="$router.push('/login')">返回登录</el-link></p>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||||
|
|
||||||
|
const param = ref({
|
||||||
|
email: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules: FormRules = {
|
||||||
|
email: [
|
||||||
|
{ required: true, message: "请输入邮箱", trigger: "blur" },
|
||||||
|
{
|
||||||
|
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
||||||
|
message: "请输入正确的邮箱格式",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const register = ref<FormInstance>();
|
||||||
|
const submitForm = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate((valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
ElMessage.success("邮件已发送,请注意查收");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-bg {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 22px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-text {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #787878;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
width: 450px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 40px 50px 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
203
src/views/pages/theme.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card class="mgb20" shadow="hover">
|
||||||
|
<template #header>
|
||||||
|
<div class="content-title">系统主题</div>
|
||||||
|
</template>
|
||||||
|
<div class="theme-list mgb20">
|
||||||
|
<div class="theme-item" @click="setSystemTheme(item)" v-for="item in system" :style="{ backgroundColor: item.color, color: '#fff' }">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-center">
|
||||||
|
<el-button @click="resetSystemTheme">重置主题</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="mgb20" shadow="hover">
|
||||||
|
<template #header>
|
||||||
|
<div class="content-title">Element-Plus主题</div>
|
||||||
|
</template>
|
||||||
|
<div class="theme-list mgb20">
|
||||||
|
<div class="theme-item" v-for="theme in themes">
|
||||||
|
<el-button :type="theme.name">{{ theme.name }}</el-button>
|
||||||
|
<div class="theme-color">{{ theme.color }}</div>
|
||||||
|
<el-color-picker v-model="color[theme.name]" @change="changeColor(theme.name)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-center">
|
||||||
|
<el-button @click="resetTheme">重置主题</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-row :gutter="50">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card class="mgb20" shadow="hover">
|
||||||
|
<template #header>
|
||||||
|
<div class="content-title">头部主题</div>
|
||||||
|
</template>
|
||||||
|
<div class="theme-list mgb20">
|
||||||
|
<div class="theme-item">
|
||||||
|
<el-button :color="color.headerBgColor">背景颜色</el-button>
|
||||||
|
<div class="theme-color">{{ color.headerBgColor }}</div>
|
||||||
|
<el-color-picker v-model="color.headerBgColor" @change="themeStore.setHeaderBgColor(color.headerBgColor)" />
|
||||||
|
</div>
|
||||||
|
<div class="theme-item">
|
||||||
|
<el-button :color="color.headerTextColor">文字颜色</el-button>
|
||||||
|
<div class="theme-color">{{ color.headerTextColor }}</div>
|
||||||
|
<el-color-picker v-model="color.headerTextColor" @change="themeStore.setHeaderTextColor(color.headerTextColor)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-center">
|
||||||
|
<el-button @click="resetHeader">重置主题</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card class="mgb20" shadow="hover">
|
||||||
|
<template #header>
|
||||||
|
<div class="content-title">菜单主题</div>
|
||||||
|
</template>
|
||||||
|
<div class="theme-list mgb20">
|
||||||
|
<div class="theme-item">
|
||||||
|
<el-button :color="sidebar.bgColor">背景颜色</el-button>
|
||||||
|
<div class="theme-color">{{ sidebar.bgColor }}</div>
|
||||||
|
<el-color-picker v-model="sidebarColor.bgColor" @change="sidebar.setBgColor(sidebarColor.bgColor)" />
|
||||||
|
</div>
|
||||||
|
<div class="theme-item">
|
||||||
|
<el-button :color="sidebar.textColor">文字颜色</el-button>
|
||||||
|
<div class="theme-color">{{ sidebar.textColor }}</div>
|
||||||
|
<el-color-picker v-model="sidebarColor.textColor" @change="sidebar.setTextColor(sidebarColor.textColor)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-center">
|
||||||
|
<el-button @click="resetSidebar">重置主题</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useSidebarStore } from "@/store/sidebar";
|
||||||
|
import { useThemeStore } from "@/store/theme";
|
||||||
|
import { reactive, onMounted } from "vue";
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const sidebar = useSidebarStore();
|
||||||
|
|
||||||
|
console.log(themeStore, "themeStore");
|
||||||
|
|
||||||
|
const color = reactive({
|
||||||
|
primary: localStorage.getItem("theme-primary") || "#409eff",
|
||||||
|
success: localStorage.getItem("theme-success") || "#67c23a",
|
||||||
|
warning: localStorage.getItem("theme-warning") || "#e6a23c",
|
||||||
|
danger: localStorage.getItem("theme-danger") || "#f56c6c",
|
||||||
|
info: localStorage.getItem("theme-info") || "#909399",
|
||||||
|
headerBgColor: themeStore.headerBgColor,
|
||||||
|
headerTextColor: themeStore.headerTextColor,
|
||||||
|
});
|
||||||
|
const sidebarColor = reactive({
|
||||||
|
bgColor: sidebar.bgColor,
|
||||||
|
textColor: sidebar.textColor,
|
||||||
|
});
|
||||||
|
const themes = [
|
||||||
|
{
|
||||||
|
name: "primary",
|
||||||
|
color: themeStore.primary || color.primary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
color: themeStore.success || color.success,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "warning",
|
||||||
|
color: themeStore.warning || color.warning,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "danger",
|
||||||
|
color: themeStore.danger || color.danger,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "info",
|
||||||
|
color: themeStore.info || color.info,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const changeColor = (name: string) => {
|
||||||
|
themeStore.setPropertyColor("#061451", name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetTheme = () => {
|
||||||
|
themeStore.resetTheme();
|
||||||
|
};
|
||||||
|
const resetHeader = () => {
|
||||||
|
localStorage.removeItem("header-bg-color");
|
||||||
|
localStorage.removeItem("header-text-color");
|
||||||
|
location.reload();
|
||||||
|
};
|
||||||
|
const resetSidebar = () => {
|
||||||
|
localStorage.removeItem("sidebar-bg-color");
|
||||||
|
localStorage.removeItem("sidebar-text-color");
|
||||||
|
location.reload();
|
||||||
|
};
|
||||||
|
const system = [
|
||||||
|
{
|
||||||
|
name: "默认",
|
||||||
|
color: "#242f42",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "健康",
|
||||||
|
color: "#1ABC9C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "优雅",
|
||||||
|
color: "#722ed1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "热情",
|
||||||
|
color: "#f44336",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "宁静",
|
||||||
|
color: "#00bcd4",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const setSystemTheme = (data: any) => {
|
||||||
|
if (data.name === "默认") {
|
||||||
|
resetSystemTheme();
|
||||||
|
} else {
|
||||||
|
console.log(data);
|
||||||
|
themeStore.setHeaderBgColor(data.color);
|
||||||
|
themeStore.setHeaderTextColor("#fff");
|
||||||
|
sidebar.setBgColor("#fff");
|
||||||
|
sidebar.setTextColor("#5b6e88");
|
||||||
|
themeStore.setPropertyColor(data.color, "primary");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const resetSystemTheme = () => {
|
||||||
|
resetTheme();
|
||||||
|
resetHeader();
|
||||||
|
resetSidebar();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.theme-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-item {
|
||||||
|
margin-right: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-color {
|
||||||
|
color: #787878;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
69
src/views/statisticalCenter/deviceHistory.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="deviceStatistics card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="title">内容数据</div>
|
||||||
|
<div class="condition">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type="primary">心率</el-button>
|
||||||
|
<el-button>血氧</el-button>
|
||||||
|
<el-button>体表温度</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<div class="condition-time">
|
||||||
|
<el-date-picker
|
||||||
|
style="width: 350px"
|
||||||
|
v-model="value1"
|
||||||
|
type="datetimerange"
|
||||||
|
start-placeholder="Start date"
|
||||||
|
end-placeholder="End date"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
date-format="YYYY/MM/DD ddd"
|
||||||
|
time-format="A hh:mm:ss"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot name="chart"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
const value1 = ref("");
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.card {
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 16px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.card-head {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.title {
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
content: "";
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.condition {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 20px;
|
||||||
|
.condition-time {
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
89
src/views/statisticalCenter/emergencyList.vue
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<div class="deviceStatistics card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="title">内容数据</div>
|
||||||
|
<div class="condition">
|
||||||
|
<el-input style="width: 240px" placeholder="搜索设备SN号" :suffix-icon="Search" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :refresh="getData" :currentPage="page.index" :changePage="changePage">
|
||||||
|
<template #location="{ rows }">
|
||||||
|
<el-button type="success" link :icon="View"> 查看 </el-button>
|
||||||
|
</template>
|
||||||
|
<template #state="{ rows }">
|
||||||
|
<el-tag :type="rows.state ? 'success' : 'danger'">
|
||||||
|
{{ rows.state ? "正常" : "异常" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { Search, View } from "@element-plus/icons-vue";
|
||||||
|
defineProps({
|
||||||
|
tableData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
getData: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
changePage: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 表格相关
|
||||||
|
let columns = [
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "IMEI号" },
|
||||||
|
{ prop: "time", label: "时间" },
|
||||||
|
{ prop: "location", label: "地理位置" },
|
||||||
|
{ prop: "state", label: "处理状态" },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.card {
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 16px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.card-head {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.title {
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
content: "";
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.condition {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 20px;
|
||||||
|
.condition-time {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
119
src/views/statisticalCenter/index.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<MonitoringTop />
|
||||||
|
<el-row :gutter="20" style="margin-top: 20px; height: 380px">
|
||||||
|
<el-col :span="16">
|
||||||
|
<DeviceHistory>
|
||||||
|
<template #chart>
|
||||||
|
<div ref="chartRef" style="flex: 1"></div>
|
||||||
|
</template>
|
||||||
|
</DeviceHistory>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<OnLineStatistics>
|
||||||
|
<template #chart>
|
||||||
|
<div ref="chartRef1" style="flex: 1"></div>
|
||||||
|
</template>
|
||||||
|
</OnLineStatistics>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20" style="margin-top: 20px; height: 380px">
|
||||||
|
<el-col :span="16">
|
||||||
|
<EmergencyList :tableData="tableData" :page="page" :getData="getData" :changePage="changePage"></EmergencyList>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<OnLineStatistics>
|
||||||
|
<template #chart>
|
||||||
|
<div ref="chartRef2" style="width: 100%; height: 100%"></div>
|
||||||
|
</template>
|
||||||
|
</OnLineStatistics>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import MonitoringTop from "./monitoringTop.vue";
|
||||||
|
import DeviceHistory from "./deviceHistory.vue";
|
||||||
|
import OnLineStatistics from "./onLineStatistics.vue";
|
||||||
|
import EmergencyList from "./emergencyList.vue";
|
||||||
|
import * as echarts from "echarts";
|
||||||
|
import { MapCustom } from "@/utils/mapCustom";
|
||||||
|
import { fetchData } from "@/api/index";
|
||||||
|
import { TableItem } from "@/types/table";
|
||||||
|
import { onMounted, ref, reactive } from "vue";
|
||||||
|
|
||||||
|
const chartRef = ref(null);
|
||||||
|
const chartRef1 = ref(null);
|
||||||
|
const chartRef2 = ref(null);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||||
|
type: "line",
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let option1 = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: "item",
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
right: "left",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "Access From",
|
||||||
|
type: "pie",
|
||||||
|
radius: ["20%", "50%"],
|
||||||
|
data: [
|
||||||
|
{ value: 1048, name: "常规模式" },
|
||||||
|
{ value: 735, name: "户外押送" },
|
||||||
|
{ value: 580, name: "审讯模式" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableData = ref<TableItem[]>([]);
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchData();
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (chartRef.value) {
|
||||||
|
new MapCustom({ dom: "mapcontainer" });
|
||||||
|
|
||||||
|
const myChart = echarts.init(chartRef.value);
|
||||||
|
myChart.setOption(option);
|
||||||
|
|
||||||
|
const myChart1 = echarts.init(chartRef1.value);
|
||||||
|
myChart1.setOption(option1);
|
||||||
|
|
||||||
|
const myChart2 = echarts.init(chartRef2.value);
|
||||||
|
myChart2.setOption(option1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
84
src/views/statisticalCenter/monitoringTop.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<el-row :gutter="20" class="monitoring-top">
|
||||||
|
<el-col :span="6" v-for="item in funcList">
|
||||||
|
<div class="item">
|
||||||
|
<div class="item-left">
|
||||||
|
<div class="item-left-head">
|
||||||
|
<div class="title">{{ item.title }}</div>
|
||||||
|
<div class="en">{{ item.en }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-left-bottom">
|
||||||
|
<div class="num" :style="{ color: item.color }">{{ item.num }}</div>
|
||||||
|
<div class="unit">个</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-img">
|
||||||
|
<img :src="item.icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import handcuffs from "@/assets/img/handcuffs.png";
|
||||||
|
import onLine from "@/assets/img/onLine.png";
|
||||||
|
import report from "@/assets/img/report.png";
|
||||||
|
import newly from "@/assets/img/newly.png";
|
||||||
|
let funcList = [
|
||||||
|
{ title: "手铐总数", en: "SHOUKAOZONGSHU", icon: handcuffs, num: 326, color: "##71DDF9" },
|
||||||
|
{ title: "在线数量", en: "SHOUKAOZONGSHU", icon: onLine, num: 326, color: "##71DDF9" },
|
||||||
|
{ title: "告警数量", en: "SHOUKAOZONGSHU", icon: report, num: 99, color: "#8B51FD" },
|
||||||
|
{ title: "较昨日新增", en: "SHOUKAOZONGSHU", icon: newly, num: 86, color: "#FF6905" },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.monitoring-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.item {
|
||||||
|
height: 160px;
|
||||||
|
padding: 25px 33px 16px 36px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.item-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
.item-left-head {
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #061550;
|
||||||
|
}
|
||||||
|
.en {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-left-bottom {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
.num {
|
||||||
|
color: #ff0303;
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
.unit {
|
||||||
|
color: #ff0303;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-img {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
43
src/views/statisticalCenter/onLineStatistics.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div class="deviceStatistics card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="title">在线设备统计</div>
|
||||||
|
</div>
|
||||||
|
<slot name="chart"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup></script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.card {
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 16px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
position: absolute;
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.title {
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
content: "";
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
187
src/views/synthesizeManage/areaManage/addFence.vue
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :title="true ? '编辑' : '新增'" v-model="visible" width="1000px" destroy-on-close :close-on-click-modal="false" @close="closeDialog">
|
||||||
|
<div class="dialog-container">
|
||||||
|
<el-form :rules="rules" ref="ruleFormRef" :model="ruleForm" label-width="auto" class="demo-ruleForm">
|
||||||
|
<el-form-item label="辖区名称" prop="name">
|
||||||
|
<el-input v-model="ruleForm.name" type="text" autocomplete="off" placeholder="请输入辖区名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="围栏" prop="location">
|
||||||
|
<div id="mapcontainer">
|
||||||
|
<div class="shape">
|
||||||
|
<el-popover placement="top-start" width="200" trigger="hover">
|
||||||
|
<template #default>
|
||||||
|
<div style="color: red">清除地图上绘制的围栏</div>
|
||||||
|
</template>
|
||||||
|
<template #reference>
|
||||||
|
<div @click="clearMap">
|
||||||
|
<svg
|
||||||
|
t="1715926362651"
|
||||||
|
class="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="5520"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M229.12 475.52l687.296 183.872-1.408 1.472-1.024 12.864c-2.56 34.048-6.4 62.976-17.92 139.52l-6.528 43.648c-3.84 26.048-6.528 46.976-8.768 66.304-4.224 36.544-23.488 65.28-54.656 81.6-15.424 8.128-32 12.672-45.824 13.44l-4.032 0.192-90.432-24.32a18.88 18.88 0 0 1-12.16-26.688l35.392-71.552-20.352 26.24a515.328 515.328 0 0 1-47.36 50.56 19.008 19.008 0 0 1-18.112 4.672l-75.456-20.16a18.88 18.88 0 0 1-12.16-26.624l35.456-71.552-20.352 26.24a510.336 510.336 0 0 1-47.36 50.56 18.944 18.944 0 0 1-18.112 4.672l-88.448-23.68a18.944 18.944 0 0 1-12.096-26.56l35.392-71.552-20.352 26.24a515.84 515.84 0 0 1-47.36 50.56 18.944 18.944 0 0 1-18.112 4.672l-62.528-16.704a18.944 18.944 0 0 1-12.096-26.688l35.392-71.488-20.352 26.24c-8.32 10.688-20.48 24-36.48 39.936a44.736 44.736 0 0 1-43.136 11.52l-78.08-20.864-23.104-8.704c-29.632-25.536-44.608-61.44-39.36-95.552 5.76-37.12 32.32-67.648 82.112-88.704 34.368-14.656 67.584-74.176 89.664-157.44l3.84-14.464-1.024-1.792z m-42.88-76.16a60.224 60.224 0 0 1 5.056-6.4l123.776-135.424c9.92-10.752 24.832-19.392 41.856-23.936 16.64-4.416 33.984-4.672 48.64-0.768l123.584 33.024 4.096-2.368 55.36-205.824c8.704-32.448 43.968-51.456 78.72-42.24l83.2 22.272c34.752 9.28 55.808 43.392 47.104 75.84l-55.36 205.824 2.368 4.096 118.528 31.68c14.592 3.84 29.568 12.8 41.728 24.96 12.416 12.352 21.12 27.2 24.192 41.472l39.36 179.072a58.688 58.688 0 0 1 1.28 9.856c-0.704 6.592-5.312 10.304-13.696 11.2L194.944 418.048c-10.432-2.944-13.376-9.216-8.768-18.752z"
|
||||||
|
fill="#5B9CF8"
|
||||||
|
p-id="5521"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
<el-popover placement="top-start" width="200" trigger="hover">
|
||||||
|
<template #default>
|
||||||
|
<div style="color: red">1.点击图标, 进入绘制状态<br />2.在地图上多次移动并点击<br />3.双击地图结束, 完成围栏绘制</div>
|
||||||
|
</template>
|
||||||
|
<template #reference>
|
||||||
|
<div @click="draw">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 34.295 30">
|
||||||
|
<g id="组_608" data-name="组 608" transform="translate(-1304 -128.546)">
|
||||||
|
<path
|
||||||
|
id="路径_489"
|
||||||
|
data-name="路径 489"
|
||||||
|
d="M9578.717-2601.595l-7.533-10.334,10.141-10.334,14.391,8.982-5.986,9.658Z"
|
||||||
|
transform="translate(-8262.102 2755.512)"
|
||||||
|
fill="#0091ff"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
id="路径_490"
|
||||||
|
data-name="路径 490"
|
||||||
|
d="M9580.671-2596.235l-9.486-13.014,12.771-13.014,18.123,11.312-7.54,12.162Z"
|
||||||
|
transform="translate(-8265.283 2752.833)"
|
||||||
|
fill="none"
|
||||||
|
stroke="#0091ff"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-dasharray="2 2"
|
||||||
|
/>
|
||||||
|
<circle id="椭圆_31" data-name="椭圆 31" cx="1.5" cy="1.5" r="1.5" transform="translate(1317 128.546)" fill="#0091ff" />
|
||||||
|
<circle id="椭圆_36" data-name="椭圆 36" cx="1.5" cy="1.5" r="1.5" transform="translate(1314 155.546)" fill="#0091ff" />
|
||||||
|
<circle id="椭圆_80" data-name="椭圆 80" cx="1.5" cy="1.5" r="1.5" transform="translate(1328 152.546)" fill="#0091ff" />
|
||||||
|
<circle id="椭圆_81" data-name="椭圆 81" cx="1.5" cy="1.5" r="1.5" transform="translate(1335.295 141.046)" fill="#0091ff" />
|
||||||
|
<circle id="椭圆_35" data-name="椭圆 35" cx="1.5" cy="1.5" r="1.5" transform="translate(1304 142.546)" fill="#0091ff" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="handleBtn()">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleBtn(ruleFormRef)">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts" name="addFence">
|
||||||
|
import { ref, reactive, onMounted, nextTick } from "vue";
|
||||||
|
import { MapCustom } from "@/utils/mapCustom";
|
||||||
|
import type { FormRules, FormInstance } from "element-plus";
|
||||||
|
|
||||||
|
const visible = ref(true);
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
const ruleForm = reactive({
|
||||||
|
name: "",
|
||||||
|
location: "",
|
||||||
|
});
|
||||||
|
let mapInfo: any = null;
|
||||||
|
|
||||||
|
const emit = defineEmits(["closeDialog"]);
|
||||||
|
|
||||||
|
const validatePass = (_: any, _1: any, callback: any) => {
|
||||||
|
if (ruleForm.location == "") {
|
||||||
|
callback(new Error("请绘制围栏"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rules = reactive<FormRules<typeof ruleForm>>({
|
||||||
|
name: [{ required: true, message: "请输入辖区名称" }],
|
||||||
|
location: [
|
||||||
|
{ required: true, message: "请绘制围栏" },
|
||||||
|
{ validator: validatePass, trigger: "blur" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
mapInfo = new MapCustom({ dom: "mapcontainer" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const clearMap = () => {
|
||||||
|
mapInfo.clearMap();
|
||||||
|
ruleForm.location = "";
|
||||||
|
};
|
||||||
|
const draw = () => {
|
||||||
|
mapInfo.draw().then((res) => {
|
||||||
|
console.log(res, 1111);
|
||||||
|
ruleForm.location = JSON.stringify(res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const closeDialog = () => {
|
||||||
|
emit("closeDialog");
|
||||||
|
};
|
||||||
|
const handleBtn = (formEl?: FormInstance | undefined) => {
|
||||||
|
if (formEl) {
|
||||||
|
formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
console.log("submit!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
emit("closeDialog");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#mapcontainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
position: relative;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
.shape {
|
||||||
|
width: 100px;
|
||||||
|
height: 42px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 2;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.el-tooltip__trigger {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.el-tooltip__trigger:active {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏掉高德logo */
|
||||||
|
:deep(.amap-logo) {
|
||||||
|
display: none;
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
:deep(.amap-copyright) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
116
src/views/synthesizeManage/areaManage/index.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||||
|
<div class="table-container">
|
||||||
|
<TableCustom
|
||||||
|
:columns="columns"
|
||||||
|
:tableData="tableData"
|
||||||
|
:total="page.total"
|
||||||
|
:delFunc="handleDelete"
|
||||||
|
:editFunc="handleEdit"
|
||||||
|
:refresh="getData"
|
||||||
|
:currentPage="page.index"
|
||||||
|
:changePage="changePage"
|
||||||
|
>
|
||||||
|
<template #toolbarBtn>
|
||||||
|
<el-button type="primary" @click="handleAdd">新增</el-button>
|
||||||
|
<el-button>导出</el-button>
|
||||||
|
<el-button type="danger">删除</el-button>
|
||||||
|
</template>
|
||||||
|
<template #money="{ rows }"> ¥{{ rows.money }} </template>
|
||||||
|
<template #thumb="{ rows }">
|
||||||
|
<el-image class="table-td-thumb" :src="rows.thumb" :z-index="10" :preview-src-list="[rows.thumb]" preview-teleported> </el-image>
|
||||||
|
</template>
|
||||||
|
<template #state="{ rows }">
|
||||||
|
<el-tag :type="rows.state ? 'success' : 'danger'">
|
||||||
|
{{ rows.state ? "正常" : "异常" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #operator>
|
||||||
|
<el-button link type="primary" size="small"> 修改 </el-button>
|
||||||
|
<el-button link type="danger" size="small"> 删除 </el-button>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</div>
|
||||||
|
<AddFence ref="addRef" v-if="visible" @closeDialog="visible = false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="basetable">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { CirclePlusFilled } from "@element-plus/icons-vue";
|
||||||
|
import { fetchData } from "@/api/index";
|
||||||
|
import TableCustom from "@/components/table-custom.vue";
|
||||||
|
import TableSearch from "@/components/table-search.vue";
|
||||||
|
import AddFence from "./addFence.vue";
|
||||||
|
import { TableItem } from "@/types/table";
|
||||||
|
import { FormOptionList } from "@/types/form-option";
|
||||||
|
|
||||||
|
// 查询相关
|
||||||
|
const query = reactive({
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
const searchOpt = ref<FormOptionList[]>([
|
||||||
|
{ type: "input", label: "辖区名称:", prop: "name1" },
|
||||||
|
{ type: "daterange", label: "创建时间:", prop: "name2" },
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
label: "状态:",
|
||||||
|
prop: "name3",
|
||||||
|
opts: [
|
||||||
|
{ label: "启用", value: "1" },
|
||||||
|
{ label: "禁用", value: "0" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ type: "selection" },
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "辖区编号" },
|
||||||
|
{ prop: "money", label: "辖区名称" },
|
||||||
|
{ prop: "thumb", label: "创建角色" },
|
||||||
|
{ prop: "time", label: "创建时间" },
|
||||||
|
{ prop: "state", label: "账户状态" },
|
||||||
|
{ prop: "operator", label: "操作", width: 200 },
|
||||||
|
]);
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 200,
|
||||||
|
});
|
||||||
|
const tableData = ref<TableItem[]>([]);
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchData();
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
const handleSearch = () => {
|
||||||
|
changePage(1);
|
||||||
|
};
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRef = ref();
|
||||||
|
const visible = ref(false);
|
||||||
|
const rowData = ref({});
|
||||||
|
const handleEdit = (row: TableItem) => {
|
||||||
|
rowData.value = { ...row };
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除相关
|
||||||
|
const handleDelete = (row: TableItem) => {
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
};
|
||||||
|
// 删除相关
|
||||||
|
const handleAdd = () => {
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
148
src/views/synthesizeManage/deviceInfo/index.vue
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-card class="baseInfo">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">基本信息</div>
|
||||||
|
</template>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="item"><span>手铐序号:</span>手铐-10</div>
|
||||||
|
<div class="item"><span>绑定管理员:</span>欧尼蒋</div>
|
||||||
|
<div class="item"><span>现在状态:</span>禁用</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="item"><span>IMEI号:</span>860116079430636</div>
|
||||||
|
<div class="item"><span>关联人信息:</span>警察123456</div>
|
||||||
|
<div class="item"><span>当前电量:</span>87%</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="item"><span>首次绑定时间:</span>2025/03/26 18:33:32</div>
|
||||||
|
<div class="item"><span>隶属组织:</span>xxx公安局</div>
|
||||||
|
<div class="item"><span>联系电话:</span>10000000000</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="item"><span>固件版本:</span>V 3.14</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="baseInfo">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">使用记录</div>
|
||||||
|
</template>
|
||||||
|
<TableCustom :columns="record" :tableData="tableData" :total="page.total" :currentPage="page.index" :changePage="changePage">
|
||||||
|
<template #operator>
|
||||||
|
<el-button link type="primary" size="small"> 处理事件 </el-button>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</el-card>
|
||||||
|
<el-card class="baseInfo">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">当前设备告警记录</div>
|
||||||
|
</template>
|
||||||
|
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :currentPage="page.index" :changePage="changePage">
|
||||||
|
<template #operator>
|
||||||
|
<el-button link type="primary" size="small"> 处理事件 </el-button>
|
||||||
|
</template>
|
||||||
|
<template #thumb>
|
||||||
|
<el-button type="success" link> 低 </el-button>
|
||||||
|
</template>
|
||||||
|
<template #state>
|
||||||
|
<el-tag type="success"> 已处理 </el-tag>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="basetable">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import { fetchData } from "@/api/index";
|
||||||
|
import TableCustom from "@/components/table-custom.vue";
|
||||||
|
import { TableItem } from "@/types/table";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let record = ref([
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "关联人" },
|
||||||
|
{ prop: "money", label: "类型" },
|
||||||
|
{ prop: "thumb", label: "警号" },
|
||||||
|
{ prop: "state", label: "佩戴者" },
|
||||||
|
{ prop: "state", label: "使用模式" },
|
||||||
|
{ prop: "state", label: "开始使用时间" },
|
||||||
|
{ prop: "state", label: "结束使用时间" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "关联人" },
|
||||||
|
{ prop: "money", label: "事件类型" },
|
||||||
|
{ prop: "thumb", label: "事件等级" },
|
||||||
|
{ prop: "time", label: "触发时间" },
|
||||||
|
{ prop: "state", label: "处理状态" },
|
||||||
|
{ prop: "operator", label: "操作" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 200,
|
||||||
|
});
|
||||||
|
const tableData = ref<TableItem[]>([]);
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchData();
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.table-td-thumb {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.baseInfo {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
/deep/ .el-card__header {
|
||||||
|
position: relative;
|
||||||
|
.card-header {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 26px;
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: #818aa6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
152
src/views/synthesizeManage/deviceManage/index.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||||
|
<div class="table-container">
|
||||||
|
<TableCustom
|
||||||
|
:columns="columns"
|
||||||
|
:tableData="tableData"
|
||||||
|
:total="page.total"
|
||||||
|
:delFunc="handleDelete"
|
||||||
|
:editFunc="handleEdit"
|
||||||
|
:refresh="getData"
|
||||||
|
:currentPage="page.index"
|
||||||
|
:changePage="changePage"
|
||||||
|
>
|
||||||
|
<template #toolbarBtn>
|
||||||
|
<el-button type="primary" @click="handleAdd">新增</el-button>
|
||||||
|
<el-button type="warning" @click="visible = true">手铐关联</el-button>
|
||||||
|
<el-button>地图位置</el-button>
|
||||||
|
<el-button>导出</el-button>
|
||||||
|
<el-button type="danger">删除</el-button>
|
||||||
|
</template>
|
||||||
|
<template #money="{ rows }"> ¥{{ rows.money }} </template>
|
||||||
|
<template #thumb="{ rows }">
|
||||||
|
<el-image class="table-td-thumb" :src="rows.thumb" :z-index="10" :preview-src-list="[rows.thumb]" preview-teleported> </el-image>
|
||||||
|
</template>
|
||||||
|
<template #state="{ rows }">
|
||||||
|
<el-tag :type="rows.state ? 'success' : 'danger'">
|
||||||
|
{{ rows.state ? "正常" : "异常" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #operator>
|
||||||
|
<el-button link type="primary" size="small" @click="router.push('deviceInfo')"> 详细信息 </el-button>
|
||||||
|
<el-button link type="primary" size="small" @click="router.push('mapLocation')"> 地图位置 </el-button>
|
||||||
|
<el-button link type="primary" size="small" @click="router.push('setting')"> 专项配置 </el-button>
|
||||||
|
<el-button link type="primary" size="small" @click="handleEdit"> 修改 </el-button>
|
||||||
|
<el-button link type="danger" size="small"> 删除 </el-button>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</div>
|
||||||
|
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close :close-on-click-modal="false" @close="closeDialog">
|
||||||
|
<TableEdit :form-data="rowData" :options="TableEditOptions" :edit="isEdit" :update="updateData" />
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="basetable">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { fetchData } from "@/api/index";
|
||||||
|
import TableCustom from "@/components/table-custom.vue";
|
||||||
|
import TableSearch from "@/components/table-search.vue";
|
||||||
|
import TableEdit from "@/components/table-edit.vue";
|
||||||
|
import { TableItem } from "@/types/table";
|
||||||
|
import { FormOption, FormOptionList } from "@/types/form-option";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
// 新增/编辑弹窗相关
|
||||||
|
let TableEditOptions = ref<FormOption>({
|
||||||
|
labelWidth: "100px",
|
||||||
|
span: 24,
|
||||||
|
list: [],
|
||||||
|
});
|
||||||
|
const addOp = [{ type: "input", label: "唯一编码", prop: "name", required: true }];
|
||||||
|
const editOp = [
|
||||||
|
{ type: "select", label: "选择手铐", prop: "name", required: true },
|
||||||
|
{ type: "input", label: "唯一编码", prop: "name1" },
|
||||||
|
{ type: "select", label: "辖区绑定", prop: "name2" },
|
||||||
|
{ type: "input", label: "紧急电话", prop: "name3" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 查询相关
|
||||||
|
const query = reactive({
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
const searchOpt = ref<FormOptionList[]>([
|
||||||
|
{ type: "input", label: "手铐SN:", prop: "name" },
|
||||||
|
{ type: "input", label: "警察账号:", prop: "name1" },
|
||||||
|
]);
|
||||||
|
const handleSearch = () => {
|
||||||
|
changePage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ type: "selection" },
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "手铐SN" },
|
||||||
|
{ prop: "money", label: "绑定警察名称" },
|
||||||
|
{ prop: "thumb", label: "绑定警察账户" },
|
||||||
|
{ prop: "state", label: "当前状态" },
|
||||||
|
{ prop: "state", label: "登录时间" },
|
||||||
|
{ prop: "state", label: "最近使用时间" },
|
||||||
|
{ prop: "state", label: "关联辖区编号" },
|
||||||
|
{ prop: "operator", label: "操作", width: 400 },
|
||||||
|
]);
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 200,
|
||||||
|
});
|
||||||
|
const tableData = ref<TableItem[]>([]);
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchData();
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const rowData = ref({});
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
TableEditOptions.value.list = addOp;
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (row: TableItem) => {
|
||||||
|
// rowData.value = { ...row };
|
||||||
|
TableEditOptions.value.list = editOp;
|
||||||
|
isEdit.value = true;
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateData = () => {
|
||||||
|
closeDialog();
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
visible.value = false;
|
||||||
|
isEdit.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除相关
|
||||||
|
const handleDelete = (row: TableItem) => {
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-td-thumb {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
158
src/views/synthesizeManage/mapLocation/index.vue
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div class="incidentList">
|
||||||
|
<div class="head">
|
||||||
|
<div class="head-text">选择设备(SN列表)</div>
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
<div class="list-item" :class="`${index == 0 ? 'active' : ''}`" v-for="(item, index) in 5" :key="item">
|
||||||
|
<div class="img">
|
||||||
|
<img :src="handcuffs" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-name">设备序号:10</div>
|
||||||
|
<div class="content-time">2025/03/26 18:33:32</div>
|
||||||
|
</div>
|
||||||
|
<el-icon v-if="index == 0"><View /></el-icon>
|
||||||
|
<el-icon v-else><Hide /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="18" class="container-right">
|
||||||
|
<div class="editTool">
|
||||||
|
<span>时间区间:</span>
|
||||||
|
|
||||||
|
<el-radio-group v-model="sectionType">
|
||||||
|
<el-radio-button :value="1">小时</el-radio-button>
|
||||||
|
<el-radio-button :value="2">天</el-radio-button>
|
||||||
|
<el-radio-button :value="3">周</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
|
||||||
|
<div class="editTool-time">
|
||||||
|
<el-date-picker
|
||||||
|
v-if="sectionType != 3"
|
||||||
|
style="width: 300px"
|
||||||
|
type="datetimerange"
|
||||||
|
range-separator="-"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
/>
|
||||||
|
<el-date-picker v-else type="week" value-format="YYYY-MM-DD/ww" placeholder="请选择周" @change="weekChange" />
|
||||||
|
</div>
|
||||||
|
<span>位置样式:</span>
|
||||||
|
<el-radio-group v-model="styleType">
|
||||||
|
<el-radio-button :value="1">点</el-radio-button>
|
||||||
|
<el-radio-button :value="2">线</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<div id="mapcontainer" style="flex: 1"></div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import handcuffs from "@/assets/img/handcuffs.png";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
import { MapCustom } from "@/utils/mapCustom";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
let tabs = [
|
||||||
|
{ label: "全部", value: "0" },
|
||||||
|
{ label: "未处理", value: "1" },
|
||||||
|
{ label: "已处理", value: "2" },
|
||||||
|
];
|
||||||
|
let sectionType = ref(1);
|
||||||
|
let styleType = ref(1);
|
||||||
|
|
||||||
|
//处理展示数据
|
||||||
|
const weekChange = (value: any) => {};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
new MapCustom({ dom: "mapcontainer" });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.incidentList {
|
||||||
|
.head {
|
||||||
|
background-color: #fff;
|
||||||
|
height: 70px;
|
||||||
|
line-height: 70px;
|
||||||
|
|
||||||
|
.head-text {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&::before {
|
||||||
|
margin-right: 18px;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
.list-item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100px;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #dedede;
|
||||||
|
&.active {
|
||||||
|
background-color: #e6e7ed;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 10px;
|
||||||
|
.content-name {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.content-time {
|
||||||
|
color: #787878;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.editTool {
|
||||||
|
min-width: 761px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 0 20px;
|
||||||
|
height: 70px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editTool-time {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
145
src/views/synthesizeManage/setting/index.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<el-card class="baseSetting">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-header-text">专项设置</div>
|
||||||
|
<el-button type="primary">执行</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form :inline="true" :model="ruleForm" class="demo-form-inline">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="IMEI号">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入IMEI号" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="佩戴者编号:">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入佩戴者编号" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="模式选择">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入IMEI号" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-divider />
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="姓名 1:">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入姓名" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="紧急电话 1:">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入紧急电话" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="姓名 2:">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入姓名" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="紧急电话 2:">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入紧急电话" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="姓名 3:">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入姓名" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="紧急电话 3:">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入紧急电话" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-divider />
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="最小心率(次/分):">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入最小心率" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="最大心率(次/分):">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入最大心率" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="最小血氧(%):">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入最小血氧" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="最大血氧(%):">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入最大血氧" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="最小体表温度(℃):">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入最小体表温度" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="最大体表温度(℃):">
|
||||||
|
<el-input v-model="ruleForm.user" placeholder="请输入最大体表温度" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive } from "vue";
|
||||||
|
const ruleForm = reactive({
|
||||||
|
user: "",
|
||||||
|
region: "",
|
||||||
|
date: "",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.baseSetting {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
/deep/ .el-card__header {
|
||||||
|
position: relative;
|
||||||
|
.card-header {
|
||||||
|
color: #061451;
|
||||||
|
font-size: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 2px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #061451;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
169
src/views/synthesizeManage/userManage/index.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||||
|
<div class="table-container">
|
||||||
|
<TableCustom
|
||||||
|
:columns="columns"
|
||||||
|
:tableData="tableData"
|
||||||
|
:total="page.total"
|
||||||
|
:delFunc="handleDelete"
|
||||||
|
:editFunc="handleEdit"
|
||||||
|
:refresh="getData"
|
||||||
|
:currentPage="page.index"
|
||||||
|
:changePage="changePage"
|
||||||
|
>
|
||||||
|
<template #toolbarBtn>
|
||||||
|
<el-button type="primary" @click="visible = true">类型编辑</el-button>
|
||||||
|
<el-button type="primary" @click="visible = true">新增用户</el-button>
|
||||||
|
<el-button type="warning" @click="handleImp">批量创建用户</el-button>
|
||||||
|
<el-button @click="visible = true">导出</el-button>
|
||||||
|
<el-button type="danger" @click="visible = true">删除</el-button>
|
||||||
|
</template>
|
||||||
|
<template #money="{ rows }"> ¥{{ rows.money }} </template>
|
||||||
|
<template #thumb="{ rows }">
|
||||||
|
<el-image class="table-td-thumb" :src="rows.thumb" :z-index="10" :preview-src-list="[rows.thumb]" preview-teleported> </el-image>
|
||||||
|
</template>
|
||||||
|
<template #state="{ rows }">
|
||||||
|
<el-tag :type="rows.state ? 'success' : 'danger'">
|
||||||
|
{{ rows.state ? "正常" : "异常" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #operator>
|
||||||
|
<el-button link type="primary" size="small"> 重置密码 </el-button>
|
||||||
|
<el-button link type="primary" size="small"> 修改 </el-button>
|
||||||
|
<el-button link type="danger" size="small"> 删除 </el-button>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</div>
|
||||||
|
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close :close-on-click-modal="false" @close="closeDialog">
|
||||||
|
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" />
|
||||||
|
</el-dialog>
|
||||||
|
<BatchImp ref="batchImpRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="basetable">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { fetchData } from "@/api/index";
|
||||||
|
import TableCustom from "@/components/table-custom.vue";
|
||||||
|
import TableSearch from "@/components/table-search.vue";
|
||||||
|
import TableEdit from "@/components/table-edit.vue";
|
||||||
|
import BatchImp from "@/components/batch-imp.vue";
|
||||||
|
import { TableItem } from "@/types/table";
|
||||||
|
import { FormOption, FormOptionList } from "@/types/form-option";
|
||||||
|
|
||||||
|
// 查询相关
|
||||||
|
const query = reactive({
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
const searchOpt = ref<FormOptionList[]>([
|
||||||
|
{ type: "input", label: "用户名:", prop: "name" },
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
label: "用户类型:",
|
||||||
|
prop: "name1",
|
||||||
|
opts: [
|
||||||
|
{ label: "管理员", value: "1" },
|
||||||
|
{ label: "警察", value: "2" },
|
||||||
|
{ label: "辅警", value: "3" },
|
||||||
|
{ label: "协警", value: "4" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: "daterange", label: "创建时间:", prop: "name2" },
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
label: "状态:",
|
||||||
|
prop: "name3",
|
||||||
|
opts: [
|
||||||
|
{ label: "启用", value: "1" },
|
||||||
|
{ label: "禁用", value: "0" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const batchImpRef = ref();
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ type: "selection" },
|
||||||
|
{ type: "index", label: "序号", width: 55, align: "center" },
|
||||||
|
{ prop: "name", label: "用户名称" },
|
||||||
|
{ prop: "money", label: "警员号" },
|
||||||
|
{ prop: "thumb", label: "手机号" },
|
||||||
|
{ prop: "state", label: "密码" },
|
||||||
|
{ prop: "state", label: "类型" },
|
||||||
|
{ prop: "state", label: "创建时间" },
|
||||||
|
{ prop: "state", label: "状态" },
|
||||||
|
{ prop: "operator", label: "操作", width: 250 },
|
||||||
|
]);
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 200,
|
||||||
|
});
|
||||||
|
const tableData = ref<TableItem[]>([]);
|
||||||
|
|
||||||
|
const handleImp = () => {
|
||||||
|
batchImpRef.value.dialogVisible = true;
|
||||||
|
};
|
||||||
|
const handleSearch = () => {
|
||||||
|
changePage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchData();
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增/编辑弹窗相关
|
||||||
|
let options = ref<FormOption>({
|
||||||
|
labelWidth: "100px",
|
||||||
|
span: 24,
|
||||||
|
list: [
|
||||||
|
{ type: "input", label: "用户名称", prop: "name1", required: true },
|
||||||
|
{ type: "input", label: "电话号码", prop: "money2", required: true },
|
||||||
|
{ type: "input", label: "警员号", prop: "money3", required: true },
|
||||||
|
{ type: "select", label: "类型", prop: "money4", required: true },
|
||||||
|
{ type: "input", label: "密码", prop: "mone5y", required: true },
|
||||||
|
{ type: "input", label: "确认密码", prop: "mon6ey" },
|
||||||
|
{ type: "textarea", label: "备注", prop: "mon8ey" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const visible = ref(false);
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const rowData = ref({});
|
||||||
|
const handleEdit = (row: TableItem) => {
|
||||||
|
rowData.value = { ...row };
|
||||||
|
isEdit.value = true;
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
const updateData = () => {
|
||||||
|
closeDialog();
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
visible.value = false;
|
||||||
|
isEdit.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除相关
|
||||||
|
const handleDelete = (row: TableItem) => {
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-td-thumb {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
144
src/views/system/menu.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="container">
|
||||||
|
<TableCustom :columns="columns" :tableData="menuData" row-key="index" :has-pagination="false"
|
||||||
|
:viewFunc="handleView" :delFunc="handleDelete" :editFunc="handleEdit">
|
||||||
|
<template #toolbarBtn>
|
||||||
|
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||||
|
</template>
|
||||||
|
<template #icon="{ rows }">
|
||||||
|
<el-icon>
|
||||||
|
<component :is="rows.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
|
||||||
|
:close-on-click-modal="false" @close="closeDialog">
|
||||||
|
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData">
|
||||||
|
<template #parent>
|
||||||
|
<el-cascader v-model="rowData.pid" :options="cascaderOptions" :props="{ checkStrictly: true }"
|
||||||
|
clearable />
|
||||||
|
</template>
|
||||||
|
</TableEdit>
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
|
||||||
|
<TableDetail :data="viewData">
|
||||||
|
<template #icon="{ rows }">
|
||||||
|
<el-icon>
|
||||||
|
<component :is="rows.icon"></component>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</TableDetail>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="system-menu">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { CirclePlusFilled } from '@element-plus/icons-vue';
|
||||||
|
import { Menus } from '@/types/menu';
|
||||||
|
import TableCustom from '@/components/table-custom.vue';
|
||||||
|
import TableDetail from '@/components/table-detail.vue';
|
||||||
|
import { FormOption } from '@/types/form-option';
|
||||||
|
import { menuData } from '@/components/menu';
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ prop: 'title', label: '菜单名称', align: 'left' },
|
||||||
|
{ prop: 'icon', label: '图标' },
|
||||||
|
{ prop: 'index', label: '路由路径' },
|
||||||
|
{ prop: 'permiss', label: '权限标识' },
|
||||||
|
{ prop: 'operator', label: '操作', width: 250 },
|
||||||
|
])
|
||||||
|
|
||||||
|
const getOptions = (data: any) => {
|
||||||
|
return data.map(item => {
|
||||||
|
const a: any = {
|
||||||
|
label: item.title,
|
||||||
|
value: item.id,
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
a.children = getOptions(item.children)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const cascaderOptions = ref(getOptions(menuData));
|
||||||
|
|
||||||
|
|
||||||
|
// 新增/编辑弹窗相关
|
||||||
|
let options = ref<FormOption>({
|
||||||
|
labelWidth: '100px',
|
||||||
|
span: 12,
|
||||||
|
list: [
|
||||||
|
{ type: 'input', label: '菜单名称', prop: 'title', required: true },
|
||||||
|
{ type: 'input', label: '路由路径', prop: 'index', required: true },
|
||||||
|
{ type: 'input', label: '图标', prop: 'icon' },
|
||||||
|
{ type: 'input', label: '权限标识', prop: 'permiss' },
|
||||||
|
{ type: 'parent', label: '父菜单', prop: 'parent' },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const visible = ref(false);
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const rowData = ref<any>({});
|
||||||
|
const handleEdit = (row: Menus) => {
|
||||||
|
rowData.value = { ...row };
|
||||||
|
isEdit.value = true;
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
const updateData = () => {
|
||||||
|
closeDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
visible.value = false;
|
||||||
|
isEdit.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看详情弹窗相关
|
||||||
|
const visible1 = ref(false);
|
||||||
|
const viewData = ref({
|
||||||
|
row: {},
|
||||||
|
list: []
|
||||||
|
});
|
||||||
|
const handleView = (row: Menus) => {
|
||||||
|
viewData.value.row = { ...row }
|
||||||
|
viewData.value.list = [
|
||||||
|
{
|
||||||
|
prop: 'id',
|
||||||
|
label: '菜单ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'pid',
|
||||||
|
label: '父菜单ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'title',
|
||||||
|
label: '菜单名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'index',
|
||||||
|
label: '路由路径',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'permiss',
|
||||||
|
label: '权限标识',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'icon',
|
||||||
|
label: '图标',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
visible1.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除相关
|
||||||
|
const handleDelete = (row: Menus) => {
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
76
src/views/system/role-permission.vue
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-tree
|
||||||
|
class="mgb10"
|
||||||
|
ref="tree"
|
||||||
|
:data="data"
|
||||||
|
node-key="id"
|
||||||
|
default-expand-all
|
||||||
|
show-checkbox
|
||||||
|
:default-checked-keys="checkedKeys"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" @click="onSubmit">保存权限</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { ElTree } from 'element-plus';
|
||||||
|
import { menuData } from '@/components/menu';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
permissOptions: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuObj = ref({});
|
||||||
|
// const data = menuData.map((item) => {
|
||||||
|
// if (item.children) {
|
||||||
|
// menuObj.value[item.id] = item.children.map((sub) => sub.id);
|
||||||
|
// }
|
||||||
|
// return {
|
||||||
|
// id: item.id,
|
||||||
|
// label: item.title,
|
||||||
|
// children: item.children?.map((child) => {
|
||||||
|
// return {
|
||||||
|
// id: child.id,
|
||||||
|
// label: child.title,
|
||||||
|
// };
|
||||||
|
// }),
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
|
||||||
|
const getTreeData = (data) => {
|
||||||
|
return data.map((item) => {
|
||||||
|
const obj: any = {
|
||||||
|
id: item.id,
|
||||||
|
label: item.title,
|
||||||
|
};
|
||||||
|
if (item.children) {
|
||||||
|
menuObj.value[item.id] = item.children.map((sub) => sub.id);
|
||||||
|
obj.children = getTreeData(item.children);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const data = getTreeData(menuData);
|
||||||
|
const checkData = (data: string[]) => {
|
||||||
|
return data.filter((item) => {
|
||||||
|
return !menuObj.value[item] || data.toString().includes(menuObj.value[item].toString());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 获取当前权限
|
||||||
|
const checkedKeys = ref<string[]>(checkData(props.permissOptions.permiss));
|
||||||
|
|
||||||
|
// 保存权限
|
||||||
|
const tree = ref<InstanceType<typeof ElTree>>();
|
||||||
|
const onSubmit = () => {
|
||||||
|
// 获取选中的权限
|
||||||
|
const keys = [...tree.value!.getCheckedKeys(false), ...tree.value!.getHalfCheckedKeys()] as number[];
|
||||||
|
console.log(keys);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
162
src/views/system/role.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView"
|
||||||
|
:delFunc="handleDelete" :page-change="changePage" :editFunc="handleEdit">
|
||||||
|
<template #toolbarBtn>
|
||||||
|
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||||
|
</template>
|
||||||
|
<template #status="{ rows }">
|
||||||
|
<el-tag type="success" v-if="rows.status">启用</el-tag>
|
||||||
|
<el-tag type="danger" v-else>禁用</el-tag>
|
||||||
|
</template>
|
||||||
|
<template #permissions="{ rows }">
|
||||||
|
<el-button type="primary" size="small" plain @click="handlePermission(rows)">管理</el-button>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
</div>
|
||||||
|
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
|
||||||
|
:close-on-click-modal="false" @close="closeDialog">
|
||||||
|
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" />
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
|
||||||
|
<TableDetail :data="viewData">
|
||||||
|
<template #status="{ rows }">
|
||||||
|
<el-tag type="success" v-if="rows.status">启用</el-tag>
|
||||||
|
<el-tag type="danger" v-else>禁用</el-tag>
|
||||||
|
</template>
|
||||||
|
</TableDetail>
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog title="权限管理" v-model="visible2" width="500px" destroy-on-close>
|
||||||
|
<RolePermission :permiss-options="permissOptions" />
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="system-role">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { Role } from '@/types/role';
|
||||||
|
import { fetchRoleData } from '@/api';
|
||||||
|
import TableCustom from '@/components/table-custom.vue';
|
||||||
|
import TableDetail from '@/components/table-detail.vue';
|
||||||
|
import RolePermission from './role-permission.vue'
|
||||||
|
import { CirclePlusFilled } from '@element-plus/icons-vue';
|
||||||
|
import { FormOption, FormOptionList } from '@/types/form-option';
|
||||||
|
|
||||||
|
// 查询相关
|
||||||
|
const query = reactive({
|
||||||
|
name: '',
|
||||||
|
});
|
||||||
|
const searchOpt = ref<FormOptionList[]>([
|
||||||
|
{ type: 'input', label: '角色名称:', prop: 'name' }
|
||||||
|
])
|
||||||
|
const handleSearch = () => {
|
||||||
|
changePage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ type: 'index', label: '序号', width: 55, align: 'center' },
|
||||||
|
{ prop: 'name', label: '角色名称' },
|
||||||
|
{ prop: 'key', label: '角色标识' },
|
||||||
|
{ prop: 'status', label: '状态' },
|
||||||
|
{ prop: 'permissions', label: '权限管理' },
|
||||||
|
{ prop: 'operator', label: '操作', width: 250 },
|
||||||
|
])
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0,
|
||||||
|
})
|
||||||
|
const tableData = ref<Role[]>([]);
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchRoleData()
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
page.total = res.data.pageTotal;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增/编辑弹窗相关
|
||||||
|
const options = ref<FormOption>({
|
||||||
|
labelWidth: '100px',
|
||||||
|
span: 24,
|
||||||
|
list: [
|
||||||
|
{ type: 'input', label: '角色名称', prop: 'name', required: true },
|
||||||
|
{ type: 'input', label: '角色标识', prop: 'key', required: true },
|
||||||
|
{ type: 'switch', label: '状态', prop: 'status', required: false, activeText: '启用', inactiveText: '禁用' },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const visible = ref(false);
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const rowData = ref({});
|
||||||
|
const handleEdit = (row: Role) => {
|
||||||
|
rowData.value = { ...row };
|
||||||
|
isEdit.value = true;
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
const updateData = () => {
|
||||||
|
closeDialog();
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
const closeDialog = () => {
|
||||||
|
visible.value = false;
|
||||||
|
isEdit.value = false;
|
||||||
|
rowData.value = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看详情弹窗相关
|
||||||
|
const visible1 = ref(false);
|
||||||
|
const viewData = ref({
|
||||||
|
row: {},
|
||||||
|
list: [],
|
||||||
|
column: 1
|
||||||
|
});
|
||||||
|
const handleView = (row: Role) => {
|
||||||
|
viewData.value.row = { ...row }
|
||||||
|
viewData.value.list = [
|
||||||
|
{
|
||||||
|
prop: 'id',
|
||||||
|
label: '角色ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '角色名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'key',
|
||||||
|
label: '角色标识',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '角色状态',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
visible1.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除相关
|
||||||
|
const handleDelete = (row: Role) => {
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 权限管理弹窗相关
|
||||||
|
const visible2 = ref(false);
|
||||||
|
const permissOptions = ref({})
|
||||||
|
const handlePermission = (row: Role) => {
|
||||||
|
visible2.value = true;
|
||||||
|
permissOptions.value = {
|
||||||
|
id: row.id,
|
||||||
|
permiss: row.permiss
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
148
src/views/system/user.vue
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<TableSearch :query="query" :options="searchOpt" :search="handleSearch" />
|
||||||
|
<div class="container">
|
||||||
|
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView"
|
||||||
|
:delFunc="handleDelete" :page-change="changePage" :editFunc="handleEdit">
|
||||||
|
<template #toolbarBtn>
|
||||||
|
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
|
||||||
|
</template>
|
||||||
|
</TableCustom>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
|
||||||
|
:close-on-click-modal="false" @close="closeDialog">
|
||||||
|
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" />
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
|
||||||
|
<TableDetail :data="viewData"></TableDetail>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="system-user">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { CirclePlusFilled } from '@element-plus/icons-vue';
|
||||||
|
import { User } from '@/types/user';
|
||||||
|
import { fetchUserData } from '@/api';
|
||||||
|
import TableCustom from '@/components/table-custom.vue';
|
||||||
|
import TableDetail from '@/components/table-detail.vue';
|
||||||
|
import TableSearch from '@/components/table-search.vue';
|
||||||
|
import { FormOption, FormOptionList } from '@/types/form-option';
|
||||||
|
|
||||||
|
// 查询相关
|
||||||
|
const query = reactive({
|
||||||
|
name: '',
|
||||||
|
});
|
||||||
|
const searchOpt = ref<FormOptionList[]>([
|
||||||
|
{ type: 'input', label: '用户名:', prop: 'name' }
|
||||||
|
])
|
||||||
|
const handleSearch = () => {
|
||||||
|
changePage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
let columns = ref([
|
||||||
|
{ type: 'index', label: '序号', width: 55, align: 'center' },
|
||||||
|
{ prop: 'name', label: '用户名' },
|
||||||
|
{ prop: 'phone', label: '手机号' },
|
||||||
|
{ prop: 'role', label: '角色' },
|
||||||
|
{ prop: 'operator', label: '操作', width: 250 },
|
||||||
|
])
|
||||||
|
const page = reactive({
|
||||||
|
index: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0,
|
||||||
|
})
|
||||||
|
const tableData = ref<User[]>([]);
|
||||||
|
const getData = async () => {
|
||||||
|
const res = await fetchUserData()
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
page.total = res.data.pageTotal;
|
||||||
|
};
|
||||||
|
getData();
|
||||||
|
|
||||||
|
const changePage = (val: number) => {
|
||||||
|
page.index = val;
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增/编辑弹窗相关
|
||||||
|
let options = ref<FormOption>({
|
||||||
|
labelWidth: '100px',
|
||||||
|
span: 12,
|
||||||
|
list: [
|
||||||
|
{ type: 'input', label: '用户名', prop: 'name', required: true },
|
||||||
|
{ type: 'input', label: '手机号', prop: 'phone', required: true },
|
||||||
|
{ type: 'input', label: '密码', prop: 'password', required: true },
|
||||||
|
{ type: 'input', label: '邮箱', prop: 'email', required: true },
|
||||||
|
{ type: 'input', label: '角色', prop: 'role', required: true },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const visible = ref(false);
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const rowData = ref({});
|
||||||
|
const handleEdit = (row: User) => {
|
||||||
|
rowData.value = { ...row };
|
||||||
|
isEdit.value = true;
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
const updateData = () => {
|
||||||
|
closeDialog();
|
||||||
|
getData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
visible.value = false;
|
||||||
|
isEdit.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看详情弹窗相关
|
||||||
|
const visible1 = ref(false);
|
||||||
|
const viewData = ref({
|
||||||
|
row: {},
|
||||||
|
list: []
|
||||||
|
});
|
||||||
|
const handleView = (row: User) => {
|
||||||
|
viewData.value.row = { ...row }
|
||||||
|
viewData.value.list = [
|
||||||
|
{
|
||||||
|
prop: 'id',
|
||||||
|
label: '用户ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: '用户名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'password',
|
||||||
|
label: '密码',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'email',
|
||||||
|
label: '邮箱',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'phone',
|
||||||
|
label: '电话',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'role',
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'date',
|
||||||
|
label: '注册日期',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
visible1.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除相关
|
||||||
|
const handleDelete = (row: User) => {
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
10
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 'nprogress'
|
22
tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": false,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
9
tsconfig.node.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
34
vite.config.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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';
|
||||||
|
export default defineConfig({
|
||||||
|
base: './',
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
VueSetupExtend(),
|
||||||
|
AutoImport({
|
||||||
|
resolvers: [ElementPlusResolver()]
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [ElementPlusResolver()]
|
||||||
|
})
|
||||||
|
],
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['schart.js']
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': '/src',
|
||||||
|
'~': '/src/assets'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "true",
|
||||||
|
},
|
||||||
|
});
|