Explorar el Código

Merge branch 'dev-wyf' into 'dev'

保卫总览前三个部分

See merge request product-group-fe/sfy-safety-group/sfy-safety!229
ai0197 hace 6 meses
padre
commit
f2ff7222bd

+ 33 - 0
src/api/security-confidentiality-overview/index.ts

@@ -0,0 +1,33 @@
+import { http } from '@/utils/http/axios';
+
+import type { PersonOverview, VehicleOverview, RegulationItem } from '@/views/security-confidentiality/overview/types';
+
+export async function getPersonOverview() {
+  return http.request<PersonOverview>({
+    url: '/overview/queryOverviewVisitorStatistics',
+    method: 'get',
+  });
+}
+
+export async function getVehicleOverview() {
+  return http.request<VehicleOverview>({
+    url: '/overview/queryVehicleEntryRecordOverview',
+    method: 'get',
+  });
+}
+
+// 查询保卫规定与通知列表总览
+export async function getSecurityOverview() {
+  return http.request<RegulationItem[]>({
+    url: '/overview/queryDefenseRuleNoticeInfoOverview',
+    method: 'get',
+  });
+}
+
+// 查询保密规定与通知列表总览
+export async function getConfidentialityOverview() {
+  return http.request<RegulationItem[]>({
+    url: '/overview/querySecrecyRuleNoticeInfoOverview',
+    method: 'get',
+  });
+}

BIN
src/assets/images/security-confidientiality/overview-more.png


BIN
src/assets/images/security-confidientiality/vehicle-flow.png


+ 170 - 0
src/views/security-confidentiality/overview/charts/OuterPersonChart.vue

@@ -0,0 +1,170 @@
+<template>
+  <div :id="props.id" class="pieChart"></div>
+</template>
+<script setup lang="ts">
+  import * as echarts from 'echarts/core';
+  import { GraphicComponent } from 'echarts/components';
+  import { PieChart } from 'echarts/charts';
+  import { CanvasRenderer } from 'echarts/renderers';
+  import { onMounted, ref, watch } from 'vue';
+  import type { PieChartData } from '../types';
+
+  const props = defineProps<{
+    data: PieChartData[];
+    id: string;
+  }>();
+
+  echarts.use([PieChart, GraphicComponent, CanvasRenderer]);
+
+  const sum = ref(props.data.reduce((acc, cur) => acc + cur.value, 0));
+  let doughnut;
+
+  onMounted(() => {
+    doughnut = echarts.init(document.getElementById(props.id as string)!);
+    if (doughnut) {
+      initDoughnut(doughnut);
+      drawDoughnut(doughnut);
+    }
+  });
+
+  const drawDoughnut = (doughnut) => {
+    const doughnutOption = {
+      series: [
+        {
+          data: props.data,
+        },
+      ],
+    };
+    doughnut.setOption(doughnutOption);
+  };
+
+  const initDoughnut = (doughnut) => {
+    const doughnutOption = {
+      graphic: [
+        {
+          type: 'group',
+          width: '60%',
+          children: [
+            {
+              type: 'text',
+              left: 'center',
+              top: '90px',
+              z: 10,
+              style: {
+                text: `${sum.value}`,
+                fill: '#1777ff',
+                fontSize: 25,
+                fontWeight: 'bold',
+              },
+            },
+            {
+              type: 'text',
+              left: 'center',
+              top: '125px',
+              z: 10,
+              style: {
+                text: '总人数',
+                fill: '#1777ff',
+                fontSize: 14,
+              },
+            },
+          ],
+        },
+
+        {
+          type: 'text',
+          left: '5%',
+          bottom: '5px',
+          z: 10,
+          style: {
+            text: '说明:常驻外部人员及临时项目报备人员除外',
+            fill: '#CCCDD0',
+            fontSize: 14,
+          },
+        },
+      ],
+      // legend: {
+      //   orient: 'vertical',
+      //   top: 'center',
+      //   right: '20%',
+      //   icon: 'circle',
+      //   formatter: (name) => {
+      //     const item = props.data.find((item) => item.name === name);
+      //     return `{a|${name}} ` + (item?.id === 1 ? `{b|${item.value}} ` : `{c|${item?.value}} `) + `{a|人}`;
+      //   },
+      //   textStyle: {
+      //     rich: {
+      //       a: {
+      //         fontSize: 14,
+      //         color: '#8B8B8B',
+      //       },
+      //       b: {
+      //         fontSize: 14,
+      //         color: '#3777FD',
+      //       },
+      //       c: {
+      //         fontSize: 14,
+      //         color: '#F1C7A3',
+      //       },
+      //     },
+      //   },
+      // },
+      series: [
+        {
+          type: 'pie',
+          radius: ['50%', '70%'],
+          center: ['30%', '50%'],
+          clockwise: true,
+          avoidLabelOverlap: false,
+          padAngle: 5,
+          label: {
+            show: false,
+          },
+          emphasis: {
+            itemStyle: {
+              borderColor: '#f3f3f3',
+              borderWidth: 5,
+            },
+          },
+          data: props.data,
+          itemStyle: {
+            normal: {
+              color: function (params) {
+                const colorList = [
+                  ['#3777FD', '#52A8FF'],
+                  ['#F1C7A3', '#F19570'],
+                ];
+                const index = params.dataIndex;
+                return new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  {
+                    offset: 0,
+                    color: colorList[index][0],
+                  },
+                  {
+                    offset: 1,
+                    color: colorList[index][1],
+                  },
+                ]);
+              },
+            },
+          },
+        },
+      ],
+    };
+    doughnut.setOption(doughnutOption);
+  };
+
+  watch(
+    () => props.data,
+    () => {
+      sum.value = props.data.reduce((acc, cur) => acc + cur.value, 0);
+      drawDoughnut(doughnut);
+    },
+  );
+</script>
+
+<style scoped lang="scss">
+  .pieChart {
+    height: 100%;
+  }
+</style>

+ 45 - 0
src/views/security-confidentiality/overview/charts/OuterPersonChartLegend.vue

@@ -0,0 +1,45 @@
+<template>
+  <div style="cursor: default">
+    <div class="legend-item" v-for="item in props.data" :key="item.id">
+      <div class="icon" :style="{ backgroundColor: item.color }"></div>
+      <span class="name">{{ item.name }}</span>
+      <span class="value" :style="{ color: item.color }">{{ item.value }}</span>
+      <span class="unit">人</span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import type { PieChartData } from '../types';
+
+  const props = defineProps<{
+    data: PieChartData[];
+  }>();
+</script>
+
+<style scoped>
+  .legend-item {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+  }
+  .icon {
+    width: 20px;
+    height: 20px;
+    border-radius: 50%;
+    border: 5px solid #fff;
+    box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
+    margin-right: 10px;
+  }
+  .name {
+    color: #6e6f6e;
+    margin-right: 5px;
+  }
+  .value {
+    font-weight: bold;
+  }
+  .unit {
+    color: #b5b5b5;
+    font-size: 12px;
+  }
+</style>

+ 129 - 0
src/views/security-confidentiality/overview/charts/VehicleChart.vue

@@ -0,0 +1,129 @@
+<template>
+  <div :id="props.id" class="barChart"></div>
+</template>
+<script setup lang="ts">
+  import * as echarts from 'echarts/core';
+  import { TooltipComponent, GridComponent, LegendComponent } from 'echarts/components';
+  import { BarChart } from 'echarts/charts';
+  import { CanvasRenderer } from 'echarts/renderers';
+  import { onMounted, ref, watch } from 'vue';
+  import type { BarChartData } from '../types';
+
+  const props = defineProps<{
+    data: BarChartData;
+    id: string;
+  }>();
+
+  echarts.use([TooltipComponent, GridComponent, LegendComponent, BarChart, CanvasRenderer]);
+
+  let doughnut;
+
+  onMounted(() => {
+    doughnut = echarts.init(document.getElementById(props.id as string)!);
+    if (doughnut) {
+      initDoughnut(doughnut);
+      drawDoughnut(doughnut);
+    }
+  });
+
+  const drawDoughnut = (doughnut) => {
+    const doughnutOption = {
+      xAxis: [
+        {
+          data: props.data.category,
+        },
+      ],
+      series: [
+        {
+          data: props.data.value,
+        },
+      ],
+    };
+    doughnut.setOption(doughnutOption);
+  };
+
+  const initDoughnut = (doughnut) => {
+    const doughnutOption = {
+      tooltip: {
+        show: true,
+        trigger: 'axis',
+        axisPointer: {
+          type: 'shadow',
+        },
+      },
+      grid: {
+        left: '5%',
+        right: '5%',
+        top: '20px',
+        bottom: '5%',
+        containLabel: true,
+      },
+      xAxis: [
+        {
+          type: 'category',
+          data: props.data.category,
+          axisTick: {
+            alignWithLabel: true,
+          },
+          axisLabel: {
+            color: 'rgba(0,0,0,0.5)',
+            fontFamily: 'HelveticaNeue',
+            fontSize: 12,
+          },
+        },
+      ],
+      yAxis: [
+        {
+          type: 'value',
+          splitLine: {
+            lineStyle: {
+              color: '#909399',
+              type: 'dashed',
+              width: 1,
+            },
+          },
+          axisLabel: {
+            color: 'rgba(0,0,0,0.5)',
+            fontFamily: 'HelveticaNeue',
+            fontSize: 12,
+          },
+        },
+      ],
+      series: [
+        {
+          name: '车流量',
+          type: 'bar',
+          barWidth: '30%',
+          data: props.data.value,
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#38C3FE' },
+              { offset: 1, color: '#1777FF' },
+            ]),
+          },
+          emphasis: {
+            label: {
+              show: true,
+              position: 'top',
+              color: '#38C3FE',
+            },
+          },
+        },
+      ],
+    };
+    doughnut.setOption(doughnutOption);
+  };
+
+  watch(
+    () => props.data,
+    () => {
+      drawDoughnut(doughnut);
+    },
+  );
+</script>
+
+<style scoped lang="scss">
+  .barChart {
+    height: 100%;
+  }
+</style>

+ 27 - 2
src/views/security-confidentiality/overview/components/OuterPerson.vue

@@ -4,11 +4,29 @@
       <span class="line"></span>
       <span class="title">外部人员管理</span>
     </div>
-    <div class="chart-container"></div>
+    <OuterPersonChart
+      v-if="pieData.length > 0"
+      class="chart-container"
+      id="OuterPersonChart"
+      :data="pieData"
+    ></OuterPersonChart>
+    <OuterPersonChartLegend v-if="pieData.length > 0" class="chart-legend" :data="pieData"></OuterPersonChartLegend>
   </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import OuterPersonChart from '../charts/OuterPersonChart.vue';
+  import OuterPersonChartLegend from '../charts/OuterPersonChartLegend.vue';
+  import { getPersonOverviewChartData } from '../hooks';
+  import type { PieChartData } from '../types';
+
+  const pieData = ref<PieChartData[]>([]);
+
+  onMounted(async () => {
+    pieData.value = await getPersonOverviewChartData();
+  });
+</script>
 
 <style scoped lang="scss">
   .container-title {
@@ -35,10 +53,17 @@
     width: 100%;
     height: 100%;
     padding-top: 14px;
+    position: relative;
   }
 
   .chart-container {
     width: 100%;
     height: calc(100% - 24px);
   }
+
+  .chart-legend {
+    position: absolute;
+    top: 120px;
+    left: 55%;
+  }
 </style>

+ 126 - 1
src/views/security-confidentiality/overview/components/RegulationAndNotice.vue

@@ -4,10 +4,96 @@
       <span class="line"></span>
       <span class="title">管理规定与通知</span>
     </div>
+    <div class="tab-content">
+      <el-tabs v-model="activeName">
+        <el-tab-pane label="保卫" name="security">
+          <div class="item-content">
+            <div class="regulation-item" v-for="item in securityRegulationData" :key="item.id">
+              <div class="round" @click="handleItemRedirect(item)"> </div>
+              <div class="item-name" @click="handleItemRedirect(item)">{{ item.name }}</div>
+            </div>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="保密" name="confidentiality">
+          <div class="item-content">
+            <div class="regulation-item" v-for="item in confidentialityRegulationData" :key="item.id">
+              <div class="round" @click="handleItemRedirect(item)"> </div>
+              <div class="item-name" @click="handleItemRedirect(item)">{{ item.name }}</div>
+            </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+    <div class="more">
+      <span style="padding-right: 5px; cursor: pointer" @click="handleMoreRedirect">更多</span>
+      <img
+        style="cursor: pointer"
+        @click="handleMoreRedirect"
+        src="@/assets/images/security-confidientiality/overview-more.png"
+        alt=""
+      />
+    </div>
   </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { onUnmounted, ref, watch } from 'vue';
+  import { useRouter } from 'vue-router';
+  import type { RegulationItem } from '../types';
+  import { getSecurityOverview, getConfidentialityOverview } from '@/api/security-confidentiality-overview';
+
+  const router = useRouter();
+  const activeName = ref(sessionStorage.getItem('security-confidentiality-overview-active') || 'security');
+
+  const securityRegulationData = ref<RegulationItem[]>([]);
+  const confidentialityRegulationData = ref<RegulationItem[]>([]);
+
+  async function getSecurityRegulationData() {
+    securityRegulationData.value = await getSecurityOverview();
+  }
+
+  async function getConfidentialityRegulationData() {
+    confidentialityRegulationData.value = await getConfidentialityOverview();
+  }
+
+  onUnmounted(() => {
+    sessionStorage.setItem('security-confidentiality-overview-active', activeName.value);
+  });
+
+  watch(
+    () => activeName.value,
+    (val) => {
+      if (val === 'security') {
+        getSecurityRegulationData();
+      } else if (val === 'confidentiality') {
+        getConfidentialityRegulationData();
+      }
+    },
+    {
+      immediate: true,
+    },
+  );
+
+  function handleItemRedirect(item: RegulationItem) {
+    sessionStorage.setItem(`${activeName.value}-regulation-active`, 'regulation');
+    if (item.managementType === 1) {
+      router.push({
+        path: `/security-confidentiality/${activeName.value}-regulation-notice`,
+      });
+    } else if (item.managementType === 2) {
+      router.push({
+        path: `/security-confidentiality/${activeName.value}-regulation-notice-item`,
+        query: { id: item.id, operate: 'notice-view' },
+      });
+    }
+  }
+  function handleMoreRedirect() {
+    sessionStorage.setItem(`${activeName.value}-regulation-active`, 'regulation');
+    router.push({
+      path: `/security-confidentiality/${activeName.value}-regulation-notice`,
+    });
+  }
+</script>
 
 <style scoped lang="scss">
   .container-title {
@@ -35,4 +121,43 @@
     height: 100%;
     padding-top: 14px;
   }
+  .tab-content {
+    height: calc(100% - 50px);
+    padding: 0 10px;
+  }
+  .regulation-item {
+    padding: 7px 0;
+    display: flex;
+    align-items: center;
+  }
+  .round {
+    width: 6px;
+    height: 6px;
+    background: #1777ff;
+    border-radius: 50%;
+    margin-right: 6px;
+    cursor: pointer;
+  }
+  .item-name {
+    max-width: calc(100% - 12px);
+    color: rgba($color: #000000, $alpha: 0.85);
+    font-size: 12px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    cursor: pointer;
+  }
+  .more {
+    text-align: end;
+    color: #007bff;
+    font-size: 12px;
+    padding: 0 10px;
+  }
+
+  :deep(.el-tabs__header) {
+    margin: 0;
+  }
+  :deep(.el-tabs__item) {
+    font-size: 14px !important;
+  }
 </style>

+ 51 - 1
src/views/security-confidentiality/overview/components/VehicleManagement.vue

@@ -4,10 +4,29 @@
       <span class="line"></span>
       <span class="title">车辆进入管理</span>
     </div>
+    <div class="vehicle-flow">
+      <img class="vehicle-flow-icon" src="@/assets/images/security-confidientiality/vehicle-flow.png" alt="" />
+      <span class="vehicle-flow-label">今日车流量</span>
+      <span class="vehicle-flow-value">{{ lastDayFlow }}</span>
+    </div>
+    <VehicleChart v-if="barData" class="chart-container" :data="barData" id="VehicleChart" />
   </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import VehicleChart from '../charts/VehicleChart.vue';
+  import { getVehicleOverviewChartData, getLastDayFlow } from '../hooks';
+  import type { BarChartData } from '../types';
+
+  const barData = ref<BarChartData>();
+  const lastDayFlow = ref<number>(0);
+
+  onMounted(async () => {
+    barData.value = await getVehicleOverviewChartData();
+    lastDayFlow.value = await getLastDayFlow();
+  });
+</script>
 
 <style scoped lang="scss">
   .container-title {
@@ -35,4 +54,35 @@
     height: 100%;
     padding-top: 14px;
   }
+
+  .vehicle-flow {
+    margin-top: 6px;
+    height: 26px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 10px;
+  }
+
+  .vehicle-flow-icon {
+    width: 20px;
+    height: 20px;
+  }
+
+  .vehicle-flow-label {
+    font-weight: 400;
+    font-size: 16px;
+    color: #000000;
+  }
+
+  .vehicle-flow-value {
+    font-weight: 500;
+    font-size: 20px;
+    color: #1777ff;
+  }
+
+  .chart-container {
+    width: 100%;
+    height: calc(100% - 56px);
+  }
 </style>

+ 36 - 0
src/views/security-confidentiality/overview/hooks.ts

@@ -0,0 +1,36 @@
+import { getPersonOverview, getVehicleOverview } from '@/api/security-confidentiality-overview';
+import type { PersonOverview, PieChartData, VehicleOverview, BarChartData } from './types';
+import dayjs from 'dayjs';
+import { getVehicleRecordList } from '@/api/security-confidentiality-vehicle';
+
+export async function getPersonOverviewChartData() {
+  const personOverview: PersonOverview = await getPersonOverview();
+  return [
+    { name: '非外籍访客', value: personOverview.nonForeignVisitorCount, id: 1, color: '#3777FD' },
+    { name: '外籍访客', value: personOverview.foreignVisitorCount, id: 2, color: '#F1C7A3' },
+  ] as PieChartData[];
+}
+
+export async function getVehicleOverviewChartData() {
+  const vehicleOverview: VehicleOverview = await getVehicleOverview();
+  return {
+    category: vehicleOverview.vehicleEntryRecordList.map((item) => item.date),
+    value: vehicleOverview.vehicleEntryRecordList.map((item) => item.vehicleCount),
+  } as BarChartData;
+}
+
+export async function getLastDayFlow() {
+  const searchTime = [
+    dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+    dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss'),
+  ];
+  const list = await getVehicleRecordList({
+    pageNumber: 1,
+    pageSize: 1,
+    queryParam: {
+      startTime: searchTime[0],
+      endTime: searchTime[1],
+    },
+  });
+  return list.totalRow;
+}

+ 29 - 0
src/views/security-confidentiality/overview/types.ts

@@ -0,0 +1,29 @@
+export interface PieChartData {
+  name: string;
+  value: number;
+  id: number;
+  color: string;
+}
+
+export interface BarChartData {
+  category: string[];
+  value: number[];
+}
+
+export interface PersonOverview {
+  nonForeignVisitorCount: number;
+  foreignVisitorCount: number;
+}
+
+export interface VehicleOverview {
+  vehicleEntryRecordList: {
+    date: string;
+    vehicleCount: number;
+  }[];
+}
+
+export interface RegulationItem {
+  id: number;
+  managementType: number;
+  name: string;
+}