2025年04月01日18:07:16

This commit is contained in:
luojiayi 2025-04-01 18:07:18 +08:00
parent 6cedf36ce4
commit e46ea4b376
23 changed files with 336 additions and 634 deletions

33
components.d.ts vendored
View File

@ -8,24 +8,14 @@ export {}
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
BatchImp: typeof import('./src/components/batch-imp.vue')['default'] 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'] Countup: typeof import('./src/components/countup.vue')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
ElCard: typeof import('element-plus/es')['ElCard'] 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'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol'] 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'] 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'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider'] ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
@ -37,43 +27,21 @@ declare module '@vue/runtime-core' {
ElImage: typeof import('element-plus/es')['ElImage'] ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover'] 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'] ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] 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'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] 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'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] 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'] 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'] ElUpload: typeof import('element-plus/es')['ElUpload']
ElWatermark: typeof import('element-plus/es')['ElWatermark']
Header: typeof import('./src/components/header.vue')['default'] Header: typeof import('./src/components/header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
@ -82,7 +50,6 @@ declare module '@vue/runtime-core' {
TableDetail: typeof import('./src/components/table-detail.vue')['default'] TableDetail: typeof import('./src/components/table-detail.vue')['default']
TableEdit: typeof import('./src/components/table-edit.vue')['default'] TableEdit: typeof import('./src/components/table-edit.vue')['default']
TableSearch: typeof import('./src/components/table-search.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'] UploadImg: typeof import('./src/components/upload-img.vue')['default']
} }
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="header"> <div class="app-wrapper-header">
<!-- 折叠按钮 --> <!-- 折叠按钮 -->
<div class="header-left"> <div class="header-left">
<div class="web-title"> <div class="web-title">
@ -7,7 +7,7 @@
{{ item }}{{ index != tab.list.length - 1 ? " / " : "" }} {{ item }}{{ index != tab.list.length - 1 ? " / " : "" }}
</div> </div>
</div> </div>
<div class="web-time">2025/03/26 10:05</div> <div class="web-time">{{ format(comm.time) }}</div>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="header-user-con"> <div class="header-user-con">
@ -51,17 +51,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from "vue"; import { onMounted } from "vue";
import { useSidebarStore } from "../store/sidebar"; import { useSidebarStore } from "../store/sidebar";
import { useCommonStore } from "../store/common";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import imgurl from "../assets/img/img.jpg"; import imgurl from "../assets/img/img.jpg";
import { useTabsStore } from "@/store/tabs"; import { useTabsStore } from "@/store/tabs";
import { routes } from "@/router/index"; import { routes } from "@/router/index";
import { RouteRecordRaw } from "vue-router"; import { RouteRecordRaw } from "vue-router";
import { format } from "@/utils";
const tab = useTabsStore(); const tab = useTabsStore();
const username: string | null = localStorage.getItem("vuems_name"); const username: string | null = localStorage.getItem("vuems_name");
const message: number = 2; const message: number = 2;
const sidebar = useSidebarStore(); const sidebar = useSidebarStore();
const comm = useCommonStore();
// onMounted(() => { // onMounted(() => {
// if (document.body.clientWidth < 1500) { // if (document.body.clientWidth < 1500) {
@ -85,7 +88,7 @@ const toPage = (index: number) => {
if (!item.component) return; if (!item.component) return;
router.push(item.path); router.push(item.path);
}; };
const getPath = (list: RouteRecordRaw[], name: string) => { const getPath = (list: any[], name: string) => {
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
const item = list[i]; const item = list[i];
if (item.meta.tabs[item.meta.tabs.length - 1] == name) { if (item.meta.tabs[item.meta.tabs.length - 1] == name) {
@ -106,7 +109,7 @@ const setFullScreen = () => {
}; };
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.header { .app-wrapper-header {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@ -104,7 +104,7 @@ const collapseChage = () => {
position: fixed; position: fixed;
top: 0px; top: 0px;
z-index: 1001; z-index: 1001;
overflow: visible; overflow: hidden;
width: 210px; width: 210px;
.sidebar-head { .sidebar-head {

View File

@ -83,6 +83,7 @@
import { toRefs, PropType, ref } from "vue"; import { toRefs, PropType, ref } from "vue";
import { Delete, Edit, View, Refresh } from "@element-plus/icons-vue"; import { Delete, Edit, View, Refresh } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus"; import { ElMessageBox } from "element-plus";
import { TableItem } from "@/types/table";
const props = defineProps({ const props = defineProps({
// //
@ -154,7 +155,7 @@ columns.value.forEach((item) => {
} }
}); });
const tableRowClassName = ({ row, rowIndex }: { row: User; rowIndex: number }) => { const tableRowClassName = ({ row, rowIndex }: { row: TableItem; rowIndex: number }) => {
if (rowIndex % 2 == 1) { if (rowIndex % 2 == 1) {
return "warning-row"; return "warning-row";
} }

View File

@ -16,7 +16,7 @@
></el-input> ></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-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-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-option v-for="opt in item.opts" :label="opt.label" :value="opt.value" :disabled="opt.disabled"></el-option>
</el-select> </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-date-picker v-else-if="item.type === 'date'" type="date" v-model="form[item.prop]" :value-format="item.format"></el-date-picker>
<el-input <el-input

View File

@ -34,6 +34,17 @@ console.log(sidebar);
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
// @media screen and (max-width: 800px) {
// .main-container {
// margin-left: 0 !important;
// }
// .sidebar-container {
// width: 0 !important;
// }
// .app-wrapper-header {
// width: 100% !important;
// }
// }
.app-wrapper { .app-wrapper {
position: relative; position: relative;
width: 100%; width: 100%;

View File

@ -1,5 +1,6 @@
import { createRouter, createWebHashHistory, RouteRecordRaw, } from 'vue-router'; import { createRouter, createWebHashHistory, RouteRecordRaw, } from 'vue-router';
import { usePermissStore } from '../store/permiss'; import { usePermissStore } from '../store/permiss';
import { useCommonStore } from '../store/common';
import { useTabsStore } from "@/store/tabs"; import { useTabsStore } from "@/store/tabs";
import Layout from '@/layout/index.vue'; import Layout from '@/layout/index.vue';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
@ -117,8 +118,10 @@ router.beforeEach((to, from, next) => {
NProgress.start(); NProgress.start();
const permiss = usePermissStore(); const permiss = usePermissStore();
const comm = useCommonStore();
const tab = useTabsStore(); const tab = useTabsStore();
tab.setTabsItem(to.meta.tabs); tab.setTabsItem(to.meta.tabs);
comm.setTime()
if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) { if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) {
// 如果没有权限则进入403 // 如果没有权限则进入403

15
src/store/common.ts Normal file
View File

@ -0,0 +1,15 @@
import { defineStore } from 'pinia';
export const useCommonStore = defineStore('common', {
state: () => {
return {
time: new Date()
};
},
getters: {},
actions: {
setTime() {
this.time = new Date();
}
}
});

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,72 @@
import dayjs from "dayjs";
export const setProperty = (prop: string, val: any, dom = document.documentElement) => { export const setProperty = (prop: string, val: any, dom = document.documentElement) => {
dom.style.setProperty(prop, val); dom.style.setProperty(prop, val);
}; };
export const mix = (color1: string, color2: string, weight: number = 0.5): string => { export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
let color = '#'; let color = '#';
for (let i = 0; i <= 2; i++) { for (let i = 0; i <= 2; i++) {
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16); 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 c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16);
const c = Math.round(c1 * weight + c2 * (1 - weight)); const c = Math.round(c1 * weight + c2 * (1 - weight));
color += c.toString(16).padStart(2, '0'); color += c.toString(16).padStart(2, '0');
} }
return color; return color;
}; };
/**
*
* @param {*} time
* @param {*} fmStr YYYY-MM-DD HH:mm:ss
* @returns
*/
export function format(time: Date | string, fmStr: string = "YYYY-MM-DD HH:mm:ss") {
if (!time) return "";
return dayjs(typeof time == 'string' ? new Date(time) : time).format(fmStr);
}
/**
*
* @param fn
* @param time
* @returns
*/
export const debounce = (fn: Function, time: number = 300) => {
let timer;
return function (...argu) {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn(...argu);
clearTimeout(timer);
timer = null;
}, time);
};
}
/**
*
* @param fn
* @param time
* @returns
*/
export const throttle = (fn: Function, time: number = 300) => {
let flag = true
return function (...argu) {
if (!flag) {
return
}
flag = false
let timer = setTimeout(() => {
fn(...argu)
flag = true
clearTimeout(timer)
timer = null
}, time)
}
}

View File

@ -45,9 +45,11 @@ import DeviceRecord from "./deviceRecord.vue";
import { MapCustom } from "@/utils/mapCustom"; import { MapCustom } from "@/utils/mapCustom";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { fetchData } from "@/api/index"; import { fetchData } from "@/api/index";
import { onMounted, ref, reactive } from "vue"; import { onMounted, onDeactivated, ref, reactive } from "vue";
import { TableItem } from "@/types/table"; import { TableItem } from "@/types/table";
import { debounce } from "@/utils";
const chartRef = ref(null); const chartRef = ref(null);
let myChart = null;
const ringOptions = { const ringOptions = {
xAxis: { xAxis: {
@ -85,14 +87,21 @@ const changePage = (val: number) => {
getData(); getData();
}; };
const handleResize = debounce(() => {
myChart.resize();
}, 200);
onMounted(() => { onMounted(() => {
new MapCustom({ dom: "mapcontainer" }); new MapCustom({ dom: "mapcontainer" });
if (chartRef.value) { if (chartRef.value) {
const myChart = echarts.init(chartRef.value); myChart = echarts.init(chartRef.value);
myChart.setOption(ringOptions); myChart.setOption(ringOptions);
window.addEventListener("resize", handleResize);
} }
}); });
onDeactivated(() => {
window.removeEventListener("resize", handleResize);
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.monitoringMap { .monitoringMap {

View File

@ -31,6 +31,7 @@ const value1 = ref("");
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.card { .card {
height: 380px;
height: 100%; height: 100%;
background: #ffffff; background: #ffffff;
padding: 16px 10px; padding: 16px 10px;
@ -40,6 +41,7 @@ const value1 = ref("");
flex-direction: column; flex-direction: column;
border-radius: 5px; border-radius: 5px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12); box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
margin-bottom: 20px;
.card-head { .card-head {
color: #061451; color: #061451;
font-size: 20px; font-size: 20px;

View File

@ -59,6 +59,7 @@ let columns = [
flex-direction: column; flex-direction: column;
border-radius: 5px; border-radius: 5px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12); box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
margin-bottom: 20px;
.card-head { .card-head {
color: #061451; color: #061451;
font-size: 20px; font-size: 20px;

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="container scrollbar"> <div class="container scrollbar">
<MonitoringTop /> <MonitoringTop />
<el-row :gutter="20" style="margin-top: 20px; height: 380px"> <el-row :gutter="20">
<el-col :span="16"> <el-col class="elcol" :span="16" :xs="24" :sm="24" :md="24" :lg="16" :xl="16">
<DeviceHistory> <DeviceHistory>
<template #chart> <template #chart>
<div ref="chartRef" style="flex: 1"></div> <div ref="chartRef" style="flex: 1"></div>
</template> </template>
</DeviceHistory> </DeviceHistory>
</el-col> </el-col>
<el-col :span="8"> <el-col class="elcol" :span="8" :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<OnLineStatistics> <OnLineStatistics>
<template #chart> <template #chart>
<div ref="chartRef1" style="flex: 1"></div> <div ref="chartRef1" style="flex: 1"></div>
@ -17,11 +17,11 @@
</OnLineStatistics> </OnLineStatistics>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20" style="margin-top: 20px; height: 380px"> <el-row :gutter="20">
<el-col :span="16"> <el-col class="elcol" :span="16" :xs="24" :sm="24" :md="24" :lg="16" :xl="16">
<EmergencyList :tableData="tableData" :page="page" :getData="getData" :changePage="changePage"></EmergencyList> <EmergencyList :tableData="tableData" :page="page" :getData="getData" :changePage="changePage"></EmergencyList>
</el-col> </el-col>
<el-col :span="8"> <el-col class="elcol" :span="8" :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<OnLineStatistics> <OnLineStatistics>
<template #chart> <template #chart>
<div ref="chartRef2" style="width: 100%; height: 100%"></div> <div ref="chartRef2" style="width: 100%; height: 100%"></div>
@ -40,12 +40,17 @@ import * as echarts from "echarts";
import { MapCustom } from "@/utils/mapCustom"; import { MapCustom } from "@/utils/mapCustom";
import { fetchData } from "@/api/index"; import { fetchData } from "@/api/index";
import { TableItem } from "@/types/table"; import { TableItem } from "@/types/table";
import { onMounted, ref, reactive } from "vue"; import { onMounted, ref, reactive, onDeactivated } from "vue";
import { debounce } from "@/utils";
const chartRef = ref(null); const chartRef = ref(null);
const chartRef1 = ref(null); const chartRef1 = ref(null);
const chartRef2 = ref(null); const chartRef2 = ref(null);
let myChart = null;
let myChart1 = null;
let myChart2 = null;
const option = { const option = {
xAxis: { xAxis: {
type: "category", type: "category",
@ -102,18 +107,34 @@ const changePage = (val: number) => {
getData(); getData();
}; };
onMounted(() => { const handleResize = debounce(() => {
if (chartRef.value) { myChart.resize();
new MapCustom({ dom: "mapcontainer" }); myChart1.resize();
myChart2.resize();
}, 200);
const myChart = echarts.init(chartRef.value); onMounted(() => {
new MapCustom({ dom: "mapcontainer" });
if (chartRef.value) {
myChart = echarts.init(chartRef.value);
myChart.setOption(option); myChart.setOption(option);
const myChart1 = echarts.init(chartRef1.value); myChart1 = echarts.init(chartRef1.value);
myChart1.setOption(option1); myChart1.setOption(option1);
const myChart2 = echarts.init(chartRef2.value); myChart2 = echarts.init(chartRef2.value);
myChart2.setOption(option1); myChart2.setOption(option1);
window.addEventListener("resize", handleResize);
} }
}); });
onDeactivated(() => {
window.removeEventListener("resize", handleResize);
});
</script> </script>
<style lang="less" scoped>
.elcol {
height: 380px;
margin-bottom: 20px;
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<el-row :gutter="20" class="monitoring-top"> <el-row :gutter="20" class="monitoring-top">
<el-col :span="6" v-for="item in funcList"> <el-col :span="24" :xs="24" :sm="12" :md="12" :lg="6" :xl="6" v-for="item in funcList">
<div class="item"> <div class="item">
<div class="item-left"> <div class="item-left">
<div class="item-left-head"> <div class="item-left-head">
@ -37,6 +37,7 @@ let funcList = [
justify-content: space-between; justify-content: space-between;
.item { .item {
height: 160px; height: 160px;
margin-bottom: 20px;
padding: 25px 33px 16px 36px; padding: 25px 33px 16px 36px;
box-sizing: border-box; box-sizing: border-box;
background: #ffffff; background: #ffffff;

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="deviceStatistics card"> <div class="deviceStatistics card">
<div class="card-head"> <div class="card-head">
<div class="title">在线设备统计</div> <div class="title">告警占比</div>
</div> </div>
<slot name="chart"></slot> <slot name="chart"></slot>
</div> </div>
@ -17,6 +17,7 @@
display: flex; display: flex;
border-radius: 5px; border-radius: 5px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12); box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
margin-bottom: 20px;
.card-head { .card-head {
position: absolute; position: absolute;
@ -26,6 +27,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
transform: translateX(-10px); transform: translateX(-10px);
margin-bottom: 20px;
.title { .title {
&::before { &::before {
display: inline-block; display: inline-block;

View File

@ -32,6 +32,9 @@
<el-dialog title="重置密码" v-model="pwdVisible" width="700px" destroy-on-close :close-on-click-modal="false" @close="closeDialog"> <el-dialog title="重置密码" v-model="pwdVisible" width="700px" destroy-on-close :close-on-click-modal="false" @close="closeDialog">
<TableEdit :form-data="rowData" :options="pwdOptions" :edit="true" :update="updateData" @cancel="handelCancel" /> <TableEdit :form-data="rowData" :options="pwdOptions" :edit="true" :update="updateData" @cancel="handelCancel" />
</el-dialog> </el-dialog>
<el-dialog title="编辑类型" v-model="typeVisible" width="800px" destroy-on-close :close-on-click-modal="false" @close="closeDialog">
<UserType />
</el-dialog>
<BatchImp ref="batchImpRef" /> <BatchImp ref="batchImpRef" />
</div> </div>
</template> </template>
@ -44,14 +47,26 @@ import TableCustom from "@/components/table-custom.vue";
import TableSearch from "@/components/table-search.vue"; import TableSearch from "@/components/table-search.vue";
import TableEdit from "@/components/table-edit.vue"; import TableEdit from "@/components/table-edit.vue";
import BatchImp from "@/components/batch-imp.vue"; import BatchImp from "@/components/batch-imp.vue";
import UserType from "./userType.vue";
import { TableItem } from "@/types/table"; import { TableItem } from "@/types/table";
import { FormOption, FormOptionList } from "@/types/form-option"; import { FormOption, FormOptionList } from "@/types/form-option";
const visible = ref(false);
const typeVisible = ref(false);
const pwdVisible = ref(false);
const isEdit = ref(false);
const rowData = ref({});
// //
const query = reactive({ const query = reactive({
name: "", name: "",
}); });
const batchImpRef = ref(); const batchImpRef = ref();
const page = reactive({
index: 1,
size: 10,
total: 200,
});
const tableData = ref<TableItem[]>([]);
const searchOpt = ref<FormOptionList[]>([ const searchOpt = ref<FormOptionList[]>([
{ type: "input", label: "用户名:", prop: "name" }, { type: "input", label: "用户名:", prop: "name" },
@ -91,12 +106,44 @@ let columns = ref([
{ prop: "state", label: "状态" }, { prop: "state", label: "状态" },
{ prop: "operator", label: "操作", width: 250 }, { prop: "operator", label: "操作", width: 250 },
]); ]);
const page = reactive({
index: 1, // /
size: 10, let options = ref<FormOption>({
total: 200, labelWidth: "100px",
span: 24,
list: [
{ placeholder: "请输入用户名称", type: "input", label: "用户名称", prop: "name1", required: true },
{ placeholder: "请输入电话号码", type: "input", label: "电话号码", prop: "money2", required: true },
{ placeholder: "请输入警员号", type: "input", label: "警员号", prop: "money3", required: true },
{
placeholder: "请选择类型",
type: "select",
label: "类型",
opts: [
{ label: "管理员", value: "1", disabled: true },
{ label: "警察", value: "2" },
{ label: "辅警", value: "3" },
{ label: "协警", value: "4" },
],
prop: "money4",
required: true,
},
{ placeholder: "请输入密码", type: "password", label: "密码", prop: "mone5y", required: true },
{ type: "switch", label: "状态", prop: "mone5y", required: true },
{ placeholder: "请输入确认密码", type: "password", label: "确认密码", prop: "mon6ey" },
{ placeholder: "请输入备注", type: "textarea", label: "备注", prop: "mon8ey" },
],
});
// /
let pwdOptions = ref<FormOption>({
labelWidth: "100px",
span: 24,
list: [
{ type: "password", label: "原始密码", prop: "name1", required: true },
{ type: "password", label: "新密码", prop: "money2", required: true },
{ type: "password", label: "确认密码", prop: "money3", required: true },
],
}); });
const tableData = ref<TableItem[]>([]);
const handleImp = () => { const handleImp = () => {
batchImpRef.value.dialogVisible = true; batchImpRef.value.dialogVisible = true;
@ -116,35 +163,6 @@ const changePage = (val: number) => {
getData(); 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" },
],
});
// /
let pwdOptions = ref<FormOption>({
labelWidth: "100px",
span: 24,
list: [
{ type: "password", label: "原始密码", prop: "name1", required: true },
{ type: "password", label: "新密码", prop: "money2", required: true },
{ type: "password", label: "确认密码", prop: "money3", required: true },
],
});
const visible = ref(false);
const pwdVisible = ref(false);
const isEdit = ref(false);
const rowData = ref({});
// //
const handelDel = (row: TableItem) => { const handelDel = (row: TableItem) => {
ElMessageBox.confirm("确定要删除吗?", "提示", { ElMessageBox.confirm("确定要删除吗?", "提示", {

View File

@ -0,0 +1,122 @@
<template>
<div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="name" label="" width="150" align="center">
<template #default="{ row }">
<el-checkbox value="Value 1" size="large" v-if="row.flag" />
<el-input v-model="row.name" v-else />
</template>
</el-table-column>
<el-table-column prop="date" label="web管理权限" width="150" align="center">
<template #default="{ row }">
<el-checkbox size="large" v-model="row.check" disabled>
<template #default>
<div class="box box1" v-if="!row.check">
<el-icon><CloseBold style="color: red" /></el-icon>
</div>
<div class="box box2" v-else>
<el-icon><Select style="color: #fff" /></el-icon>
</div>
</template>
</el-checkbox>
</template>
</el-table-column>
<el-table-column label="APP权限" align="center">
<el-table-column prop="name" label="手铐管理权限" align="center">
<template #default="{ row }">
<el-checkbox value="Value 1" size="large" disabled>
<template #default>
<div class="box box1" v-if="!row.check">
<el-icon><CloseBold style="color: red" /></el-icon>
</div>
<div class="box box2" v-else>
<el-icon><Select style="color: #fff" /></el-icon>
</div>
</template>
</el-checkbox>
</template>
</el-table-column>
<el-table-column prop="name" label="告警管理权限" align="center">
<template #default="{ row }">
<el-checkbox value="Value 1" size="large" disabled>
<template #default>
<div class="box box1" v-if="!row.check">
<el-icon><CloseBold style="color: red" /></el-icon>
</div>
<div class="box box2" v-else>
<el-icon><Select style="color: #fff" /></el-icon>
</div>
</template>
</el-checkbox>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="date" label="操作" width="200" align="center">
<template #default="scope">
<el-button link type="primary" size="small">锁定</el-button>
<el-button link type="primary" size="small">修改</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="mt-4" style="width: 100%" @click="addType"> 添加类型 </el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const tableData = ref([
{
name: "管理员",
flag: false,
check: false,
},
{
name: "警察",
flag: false,
check: false,
},
{
name: "辅警",
flag: false,
check: false,
},
{
name: "协警",
flag: false,
check: true,
},
]);
const addType = () => {
tableData.value = [
...tableData.value,
{
name: "",
flag: false,
check: false,
},
];
};
</script>
<style scoped lang="less">
.box {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #02174f;
}
.box1 {
background-color: #fff;
}
.box2 {
background-color: #02174f;
}
/deep/ .el-checkbox__input {
display: none;
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@
"strict": false, "strict": false,
"jsx": "preserve", "jsx": "preserve",
"sourceMap": true, "sourceMap": true,
"allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,