Parcourir la source

reactor:随手拍大屏;

zhangchong il y a 1 mois
Parent
commit
fda8e15552

+ 1 - 1
package.json

@@ -10,7 +10,7 @@
     "preview": "vite preview --port 4173"
   },
   "dependencies": {
-    "@dataview/datav-vue3": "^0.0.0-test.1672506674342",
+    "@kjgl77/datav-vue3": "^1.7.4",
     "@microsoft/signalr": "^8.0.0",
     "axios": "^1.5.1",
     "countup.js": "^2.8.0",

+ 1 - 1
src/views/home/center-map.vue

@@ -12,7 +12,7 @@
 <script setup lang="ts">
 import { ref, onMounted, watch, nextTick } from "vue";
 import { registerMap, getMap } from "echarts/core";
-import { optionHandle } from "./center.map";
+import { optionHandle } from "@/views/home/center.map";
 import { loopShowTooltip } from "@/utils/tooltip-auto-show";
 import axios from "axios";
 import { areaDetail } from "api/home";

+ 196 - 0
src/views/snapshot/center-bottom.vue

@@ -0,0 +1,196 @@
+<template>
+  <div class="center_bottom">
+    <div class="center_bottom-title flex">
+      <div class="flex items-center">
+        <img src="@/assets/img/home/title_arrow.png" alt="" />
+        <span>行业类型统计</span>
+      </div>
+    </div>
+    <div class="center_bottom-content" v-if="active === '0'">
+      <div class="table-header">
+        <div class="table-header-item">超期状态</div>
+        <div class="table-header-item">来源</div>
+        <div class="table-header-item">标题</div>
+        <div class="table-header-item">受理类型</div>
+        <div class="table-header-item">热点类型</div>
+        <div class="table-header-item">区域</div>
+        <div class="table-header-item">接办部门</div>
+      </div>
+      <div class="scroll" v-loading="loading">
+        <vue3-seamless-scroll
+          :list="list"
+          hover
+          :singleHeight="100"
+          v-if="list.length"
+        >
+          <div class="item" v-for="(item, index) in list" :key="index">
+            <span>
+              <span class="exceedSoon" v-if="item.expiredStatus === 1">{{
+                item.expiredStatusText
+              }}</span>
+              <span class="exceed" v-if="item.expiredStatus === 2">{{
+                item.expiredStatusText
+              }}</span>
+              <span class="normal" v-if="item.expiredStatus === 0">{{
+                item.expiredStatusText
+              }}</span>
+            </span>
+            <TextTooltip :content="item.sourceChannel"></TextTooltip>
+            <TextTooltip :content="item.title"></TextTooltip>
+            <TextTooltip :content="item.acceptType"></TextTooltip>
+            <TextTooltip :content="item.hotspotName"></TextTooltip>
+            <!--            <el-tooltip placement="top">
+                          <template #content> {{ item.hotspotName }}</template>
+                          <span>{{ item.hotspotName }}</span>
+                        </el-tooltip>-->
+            <TextTooltip :content="item.county"></TextTooltip>
+            <TextTooltip :content="item.actualHandleOrgName"></TextTooltip>
+          </div>
+        </vue3-seamless-scroll>
+        <empty v-else />
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, onMounted, onUnmounted, defineAsyncComponent } from "vue";
+import { Vue3SeamlessScroll } from "vue3-seamless-scroll";
+import { orderView, secondaryHandling } from "api/home";
+import signalR from "@/utils/signalR";
+import { useThemeConfig } from "@/stores/themeConfig";
+import { storeToRefs } from "pinia";
+import { formatDate } from "@/utils/formatTime";
+
+const TextTooltip = defineAsyncComponent(
+  () => import("@/components/TextTooltip/index.vue")
+);
+const Empty = defineAsyncComponent(
+  () => import("@/components/Empty/index.vue")
+);
+
+const active = ref("0");
+const list = ref([]);
+const loading = ref(false);
+const getData = async () => {
+  loading.value = true;
+  try {
+    const { result } = await orderView();
+    list.value = result;
+    loading.value = false;
+  } catch (e) {
+    console.log(e);
+    loading.value = false;
+  }
+};
+onMounted(() => {
+  // 接收消息
+  signalR.SR.on("orderHandlingDetail", (res: any) => {
+    list.value = res;
+  });
+  getData();
+});
+onUnmounted(() => {
+  // 取消接收消息
+  signalR.SR.off("orderHandlingDetail");
+  // 取消接收消息
+  signalR.SR.off("OrderSecondaryHandlingDetail");
+});
+</script>
+
+<style scoped lang="scss">
+.center_bottom {
+  padding: 10px 20px;
+
+  :deep(.custom-text-content) {
+    flex: 1;
+    text-align: center;
+    color: #cfd2d2;
+  }
+
+  &-title {
+    color: #fff;
+    justify-content: space-between;
+    align-items: center;
+    span {
+      cursor: pointer;
+      text-align: center;
+      font-size: 18px;
+      &.active {
+        color: #fff;
+        font-size: 20px;
+        font-weight: bold;
+        position: relative;
+      }
+    }
+  }
+
+  &-content {
+    margin-top: 10px;
+
+    .table-header {
+      display: flex;
+      justify-content: space-between;
+      background: linear-gradient(
+        to right,
+        rgba(27, 51, 55, 0.8),
+        rgba(45, 83, 91, 0.8)
+      );
+
+      width: 100%;
+      height: 40px;
+      line-height: 40px;
+
+      .table-header-item {
+        text-align: center;
+        color: #b4d7ec;
+        flex: 1;
+      }
+    }
+
+    .scroll {
+      height: 200px;
+      overflow: hidden;
+      margin-top: 10px;
+
+      .item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        border-bottom: 1px solid #225073;
+        padding: 6px 0;
+
+        span {
+          display: inline-block;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          text-align: center;
+          color: #cfd2d2;
+          flex: 1;
+        }
+
+        .exceedSoon {
+          border: 1px solid #eca455;
+          border-radius: 3px;
+          color: #eca455;
+          padding: 2px 5px;
+        }
+
+        .exceed {
+          border: 1px solid #d70024;
+          border-radius: 3px;
+          color: #d70024;
+          padding: 2px 5px;
+        }
+
+        .normal {
+          border: 1px solid #69bbf6;
+          border-radius: 3px;
+          color: #69bbf6;
+          padding: 2px 5px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 147 - 0
src/views/snapshot/center-top.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="center-top">
+    <div class="center-top-title flex">
+      <div class="flex items-center">
+        <img src="@/assets/img/home/title_arrow.png" alt="" />
+        <span>区域上报统计</span>
+      </div>
+    </div>
+    <v-chart
+      class="chart"
+      :option="option"
+      ref="centerMapRef"
+      :loading="loading"
+      :loading-options="loadingOptions"
+    />
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, onMounted, watch, nextTick } from "vue";
+import { registerMap, getMap } from "echarts/core";
+import { optionHandle } from "@/views/home/center.map";
+import { loopShowTooltip } from "@/utils/tooltip-auto-show";
+import axios from "axios";
+import { areaDetail } from "api/home";
+import dayjs from "dayjs";
+import { loadingOptions } from "@/utils/constants";
+import { useThemeConfig } from "@/stores/themeConfig";
+import { storeToRefs } from "pinia";
+
+const storesThemeConfig = useThemeConfig();
+const { themeConfig } = storeToRefs(storesThemeConfig);
+const props = defineProps({
+  dateArray: {
+    type: Array,
+    default: () => [],
+  },
+});
+const date = ref([]);
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    date.value = val;
+  },
+  { immediate: true }
+);
+
+watch(
+  () => props.dateArray,
+  () => {
+    getData();
+  }
+);
+const loading = ref(false);
+const option = ref({});
+const code = ref(themeConfig.value.cityCode); //100000 代表中国 其他地市是行政编码
+// const code = ref("510500");
+const dataSetHandle = async (regionCode: string, list: object[]) => {
+  code.value = regionCode;
+  const geoJson: any = await getGeoJson(regionCode);
+  let cityCenter: any = {};
+  //获取当前地图每块行政区中心点
+  geoJson.features.forEach((element: any) => {
+    cityCenter[element.properties.name] =
+      element.properties.centroid || element.properties.center;
+  });
+  option.value = optionHandle(regionCode, list);
+};
+
+const getGeoJson = (regionCode: string) => {
+  return new Promise<boolean>(async (resolve) => {
+    let mapJson = getMap(regionCode);
+    if (mapJson) {
+      mapJson = mapJson.geoJSON;
+      resolve(mapJson);
+    } else {
+      mapJson = await axios
+        .get(`./map-geojson/${regionCode}.json`)
+        .then((data) => data.data);
+      code.value = regionCode;
+      registerMap(regionCode, {
+        geoJSON: mapJson as any,
+        specialAreas: {},
+      });
+      resolve(mapJson);
+    }
+  });
+};
+// 获取数据设置到地图中
+const getData = async () => {
+  loading.value = true;
+  try {
+    const { result } = await areaDetail({
+      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
+      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
+    });
+    const regionData = result.map((item: any) => {
+      return {
+        name: item.areaName,
+        ...item,
+      };
+    });
+    await dataSetHandle(code.value, regionData);
+    loading.value = false;
+  } catch (e) {
+    console.log(e);
+    loading.value = false;
+  }
+};
+const tooltipTimer = ref<any>(null);
+const centerMapRef = ref<any>(null);
+const tooltipMap = (myChart: any, option: any) => {
+  tooltipTimer.value && tooltipTimer.value.clearLoop(); // this.tooltipTimer 在data里定义
+  tooltipTimer.value = 0;
+  // 调用轮播的方法
+  // myChart为Echarts容器实例
+  tooltipTimer.value = loopShowTooltip(myChart, option, {
+    interval: 5000, // 轮播间隔时间
+    loopSeries: false, // 是否开启轮播循环
+    seriesIndex: 0, // 开始轮播的系列索引
+    // shunXu: "daoXu", // 显示顺序
+    // loopSeries: boolean类型,默认为false。true表示循环所有series的tooltip;false则显示指定seriesIndex的tooltip。
+    // seriesIndex: 默认为0,指定某个系列(option中的series索引)循环显示tooltip,当loopSeries为true时,从seriesIndex系列开始执行。
+  });
+};
+onMounted(async () => {
+  await getData();
+  await nextTick(() => {
+    tooltipMap(centerMapRef.value!.chart, option.value);
+  });
+});
+</script>
+
+<style scoped lang="scss">
+.center-top {
+  padding: 10px 20px;
+  &-title {
+    color: #fff;
+    justify-content: space-between;
+    align-items: center;
+    span {
+      cursor: pointer;
+      text-align: center;
+      font-size: 18px;
+    }
+  }
+}
+</style>

+ 46 - 27
src/views/snapshot/container.vue

@@ -1,34 +1,48 @@
 <template>
   <div class="container-box">
     <div class="container-left">
-      <LeftTop class="container-left-top" :dateArray="dateArray" />
-      <LeftCenter class="container-left-center" :dateArray="dateArray" />
-      <LeftBottom class="container-left-bottom" :dateArray="dateArray" />
+      <dv-border-box12 class="container-left-top">
+        <LeftTop />
+      </dv-border-box12>
+      <dv-border-box12 class="container-left-center">
+        <LeftCenter :dateArray="dateArray" />
+      </dv-border-box12>
+      <dv-border-box12 class="container-left-bottom">
+        <LeftBottom :dateArray="dateArray" />
+      </dv-border-box12>
     </div>
     <div class="container-center">
-      <CenterTop class="container-center-top" :dateArray="dateArray" />
-      <CenterMap class="container-center-map" :dateArray="dateArray" />
-      <CenterFloat class="container-center-float" :dateArray="dateArray" />
-      <CenterBottom class="container-center-bottom" :dateArray="dateArray" />
+      <dv-border-box12 class="container-center-top">
+        <CenterTop :dateArray="dateArray" />
+      </dv-border-box12>
+      <dv-border-box12 class="container-center-bottom">
+        <CenterBottom :dateArray="dateArray" />
+      </dv-border-box12>
     </div>
     <div class="container-right">
-      <RightTop class="container-right-top" :dateArray="dateArray" />
-      <RightCenter class="container-right-center" :dateArray="dateArray" />
-      <RightBottom class="container-right-bottom" :dateArray="dateArray" />
+      <dv-border-box12 class="container-right-top">
+        <RightTop />
+      </dv-border-box12>
+      <dv-border-box12 class="container-right-center">
+        <RightCenter />
+      </dv-border-box12>
+      <dv-border-box12 class="container-right-bottom">
+        <RightBottom />
+      </dv-border-box12>
     </div>
   </div>
 </template>
 <script setup lang="ts">
 import LeftTop from "@/views/snapshot/left-top.vue";
-import LeftCenter from "@/views/home/left-center.vue";
-import LeftBottom from "@/views/home/left-bottom.vue";
-import CenterTop from "@/views/home/center-top.vue";
-import CenterMap from "@/views/home/center-map.vue";
-import CenterFloat from "@/views/home/center-float.vue";
-import CenterBottom from "@/views/home/center-bottom.vue";
-import RightTop from "@/views/home/right-top.vue";
-import RightCenter from "@/views/home/right-center.vue";
-import RightBottom from "@/views/home/right-bottom.vue";
+import LeftCenter from "@/views/snapshot/left-center.vue";
+import LeftBottom from "@/views/snapshot/left-bottom.vue";
+import CenterTop from "@/views/snapshot/center-top.vue";
+import CenterBottom from "@/views/snapshot/center-bottom.vue";
+import RightTop from "@/views/snapshot/right-top.vue";
+import RightCenter from "@/views/snapshot/right-center.vue";
+import RightBottom from "@/views/snapshot/right-bottom.vue";
+
+import { BorderBox12 as DvBorderBox12 } from "@kjgl77/datav-vue3";
 
 const props = defineProps({
   dateArray: {
@@ -53,17 +67,17 @@ const props = defineProps({
     width: 540px;
     box-sizing: border-box;
     flex-shrink: 0;
-
+    margin: 0 10px;
     .container-left-top {
-      height: 350px;
+      height: 150px;
     }
 
     .container-left-center {
-      height: 305px;
+      height: 320px;
     }
 
     .container-left-bottom {
-      height: 290px;
+      height: 440px;
     }
   }
 
@@ -80,8 +94,10 @@ const props = defineProps({
     display: flex;
     flex-direction: column;
     overflow: hidden;
-    .center_center-bottom {
-      height: 315px;
+    .container-center-bottom {
+      height: 400px;
+      overflow: hidden;
+      margin-top: 30px;
     }
   }
 
@@ -96,9 +112,12 @@ const props = defineProps({
     flex-shrink: 0;
 
     .container-right-top,
-    .container-right-center,
+    .container-right-center {
+      height: 230px;
+      overflow: hidden;
+    }
     .container-right-bottom {
-      height: 300px;
+      height: 500px;
       overflow: hidden;
     }
   }

+ 2 - 18
src/views/snapshot/header.vue

@@ -1,18 +1,5 @@
 <template>
   <div class="d-flex jc-center title_wrap">
-    <div class="time-picker">
-      <!--      <el-date-picker
-        v-model="timeValue"
-        type="daterange"
-        unlink-panels
-        range-separator="至"
-        start-placeholder="开始时间"
-        end-placeholder="结束时间"
-        :shortcuts="shortcuts"
-        :clearable="false"
-        @change="changeDate"
-      />-->
-    </div>
     <div class="d-flex jc-center">
       <div class="title">
         <span class="title-text">{{ title }}</span>
@@ -26,7 +13,6 @@
 <script setup lang="ts">
 import { onMounted, ref } from "vue";
 import dayjs from "dayjs";
-import { shortcuts } from "@/utils/constants";
 import { useNow, useTitle } from "@vueuse/core";
 import { formatDate } from "@/utils/formatTime";
 import { useThemeConfig } from "@/stores/themeConfig";
@@ -43,9 +29,7 @@ const timeValue = ref<any>([
   dayjs().toDate(),
 ]); //默认近一个月
 emit("changeDate", timeValue.value);
-const changeDate = (val: any) => {
-  emit("changeDate", val);
-};
+
 onMounted(() => {
   title.value = `${themeConfig.value.cityName}随手拍`;
   useTitle(title.value);
@@ -63,7 +47,7 @@ onMounted(() => {
     position: absolute;
     left: 30px;
     top: 40px;
-    font-size: 18px;
+    font-size: 14px;
     z-index: 9;
   }
 

+ 369 - 0
src/views/snapshot/left-bottom.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="left_bottom">
+    <div class="left_bottom-title flex">
+      <div class="flex items-center">
+        <img src="@/assets/img/home/title_arrow.png" alt="" />
+        热点类型统计
+      </div>
+    </div>
+    <div class="left_bottom-content">
+      <template v-if="list.length">
+        <v-chart
+          class="chart"
+          :option="option"
+          :loading="loading"
+          :loading-options="loadingOptions"
+        />
+      </template>
+      <empty v-else />
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { defineAsyncComponent, onMounted, ref, watch } from "vue";
+import { ArrowDown } from "@element-plus/icons-vue";
+import { getArea, hotSpot } from "@/api/home";
+import dayjs from "dayjs";
+import { loadingOptions } from "@/utils/constants";
+import { graphic } from "echarts/core";
+import { arraySortByKey } from "@/utils/tools";
+import { useThemeConfig } from "@/stores/themeConfig";
+import { storeToRefs } from "pinia";
+
+const Empty = defineAsyncComponent(
+  () => import("@/components/Empty/index.vue")
+);
+const props = defineProps({
+  dateArray: {
+    type: Array,
+    default: () => [],
+  },
+});
+const date = ref([]);
+const loading = ref(false);
+const option = ref<any>({});
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    date.value = val;
+  },
+  { immediate: true }
+);
+
+watch(
+  () => props.dateArray,
+  () => {
+    getData();
+  }
+);
+const storesThemeConfig = useThemeConfig();
+const { themeConfig } = storeToRefs(storesThemeConfig);
+const areaCode = ref(themeConfig.value.cityCode);
+const areaText = ref(themeConfig.value.cityName);
+const list = ref<any>([]);
+const getData = async () => {
+  loading.value = true;
+  try {
+    const { result } = await hotSpot({
+      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
+      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
+      AreaCode: areaCode.value,
+    });
+    list.value = result;
+    /*
+        list.value.push({
+          hotspotId: "1925",
+          hotspotName: "不按规定的内容和方式明码标价",
+          sumCount: 20,
+          hotspotSpliceName: "市场管理-物价-价格违法违规行为-不执行法定的价格干预措施、紧急措施-不按规定的内容和方式明码标价"
+        })
+    */
+
+    const category = arraySortByKey(result, "sumCount");
+    const charts = {
+      // 按顺序排列从大到小
+      cityList: category.map((item: any) => item.hotspotName),
+      cityData: category.map((item: any) => item.sumCount),
+    };
+    const top10CityList = charts.cityList;
+
+    const top10CityData = charts.cityData;
+    const color = ["#ff9500", "#02d8f9", "#027fff"];
+    const color1 = ["#ffb349", "#70e9fc", "#4aa4ff"];
+
+    let lineY = [];
+    let lineT = [];
+    for (let i = 0; i < charts.cityList.length; i++) {
+      let x = i;
+      if (x > 1) {
+        x = 2;
+      }
+      const data = {
+        name: charts.cityList[i],
+        color: color[x],
+        value: top10CityData[i],
+        barGap: "-100%",
+        itemStyle: {
+          show: true,
+          color: new graphic.LinearGradient(
+            0,
+            0,
+            1,
+            0,
+            [
+              {
+                offset: 0,
+                color: color[x],
+              },
+              {
+                offset: 1,
+                color: color1[x],
+              },
+            ],
+            false
+          ),
+          borderRadius: 10,
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 15,
+            shadowColor: "rgba(0, 0, 0, 0.1)",
+          },
+        },
+      };
+      const data1 = {
+        value: top10CityData[0],
+        itemStyle: {
+          color: "#001235",
+          borderRadius: 10,
+        },
+      };
+      lineY.push(data);
+      lineT.push(data1);
+    }
+
+    option.value = {
+      backgroundColor: "rgba(0, 0, 0, 0)",
+      title: {
+        show: false,
+      },
+      tooltip: {
+        trigger: "axis",
+        axisPointer: {
+          type: "shadow",
+        },
+        formatter: function (params: any) {
+          let tar;
+          if (params[0].seriesIndex === 0) {
+            tar = params[0];
+          } else {
+            tar = params[1];
+          }
+          return tar.marker + tar.name + "<br/>" + " 当前数量: " + tar.value;
+        },
+
+        enterable: true, //滚动条
+        confine: true,
+        extraCssText: "max-width:90%;max-height:83%;overflow:auto;",
+        //改变提示框的位置 不超出屏幕显示
+        position: function (point, params, dom, rect, size) {
+          //其中point为当前鼠标的位置,
+          //size中有两个属性:viewSize和contentSize,分别为外层div和tooltip提示框的大小
+          // 鼠标坐标和提示框位置的参考坐标系是:以外层div的左上角那一点为原点,x轴向右,y轴向下
+          // 提示框位置
+          let x = 0; // x坐标位置
+          let y = 0; // y坐标位置
+          // 当前鼠标位置
+          const pointX = point[0];
+          const pointY = point[1];
+          // 提示框大小
+          const boxWidth = size.contentSize[0];
+          const boxHeight = size.contentSize[1];
+          // boxWidth > pointX 说明鼠标左边放不下提示框
+          if (boxWidth > pointX) {
+            x = 5;
+          } else {
+            // 左边放的下
+            x = pointX - boxWidth;
+          }
+          // boxHeight > pointY 说明鼠标上边放不下提示框
+          if (boxHeight > pointY) {
+            y = 5;
+          } else {
+            // 上边放得下
+            y = pointY - boxHeight;
+          }
+          return [x, y];
+        },
+      },
+      grid: {
+        borderWidth: 0,
+        top: "5%",
+        left: "5%",
+        right: "15%",
+        bottom: "10%",
+      },
+      color: color,
+      yAxis: [
+        {
+          type: "category",
+          inverse: true,
+          axisTick: {
+            show: false,
+          },
+          axisLine: {
+            show: false,
+          },
+          axisLabel: {
+            show: false,
+            inside: false,
+          },
+          data: top10CityList,
+        },
+        {
+          type: "category",
+          inverse: true,
+          axisLine: {
+            show: false,
+          },
+          axisTick: {
+            show: false,
+          },
+          axisLabel: {
+            show: true,
+            inside: false,
+            verticalAlign: "middle",
+            lineHeight: "40",
+            color: "#fff",
+            fontSize: "14",
+            fontFamily: "PingFangSC-Regular",
+            formatter: function (val) {
+              return `${val}`;
+            },
+          },
+          splitArea: {
+            show: false,
+          },
+          splitLine: {
+            show: false,
+          },
+          data: top10CityData,
+        },
+      ],
+      xAxis: {
+        type: "value",
+        axisTick: {
+          show: false,
+        },
+        axisLine: {
+          show: false,
+        },
+        splitLine: {
+          show: false,
+        },
+        axisLabel: {
+          show: false,
+        },
+      },
+      series: [
+        {
+          type: "bar",
+          zLevel: 2,
+          barWidth: "10px",
+          data: lineY,
+          label: {
+            color: "#b3ccf8",
+            show: true,
+            position: [0, "-18px"],
+            fontSize: 16,
+            width: 40,
+            formatter: function (a) {
+              let num = "";
+              let str = "";
+              if (a.dataIndex + 1 < 10) {
+                num = "0" + (a.dataIndex + 1);
+              } else {
+                num = a.dataIndex + 1;
+              }
+              let names = "";
+              if (a.name.length > 25) {
+                names = a.name.slice(0, 25) + "...";
+              } else {
+                names = a.name;
+              }
+              if (a.dataIndex === 0) {
+                str = `{color1|${num}} {color4|${names}}`;
+              } else if (a.dataIndex === 1) {
+                str = `{color2|${num}} {color4|${names}}`;
+              } else {
+                str = `{color3|${num}} {color4|${names}}`;
+              }
+              return str;
+            },
+            rich: {
+              color1: {
+                color: "#ff9500",
+              },
+              color2: {
+                color: "#02d8f9",
+              },
+              color3: {
+                color: "#027fff",
+              },
+              color4: {
+                color: "#e5eaff",
+              },
+            },
+          },
+        },
+      ],
+    };
+    loading.value = false;
+  } catch (e) {
+    console.log(e);
+    loading.value = false;
+  }
+};
+// 选择省市区
+const handleCommand = (command: any) => {
+  areaCode.value = command.id;
+  areaText.value = command.areaName;
+  getData();
+};
+const areaList = ref<EmptyArrayType>([]);
+const getAreaData = async () => {
+  try {
+    const { result } = await getArea();
+    areaList.value = result ?? [];
+  } catch (e) {
+    console.log(e);
+  }
+};
+onMounted(() => {
+  getData();
+  getAreaData();
+});
+</script>
+<style scoped lang="scss">
+.left_bottom {
+  padding: 10px 20px;
+
+  &-title {
+    font-size: 20px;
+    color: #fff;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  &-content {
+    height: 410px;
+  }
+}
+
+:deep(.link-button) {
+  cursor: pointer;
+  color: #7dbdec;
+  display: flex;
+  align-items: center;
+}
+</style>

+ 246 - 0
src/views/snapshot/left-center.vue

@@ -0,0 +1,246 @@
+<template>
+  <div class="left-center">
+    <div class="left-center-title flex items-center">
+      <img src="@/assets/img/home/title_arrow.png" alt="" />
+      受理类型办件分析
+    </div>
+    <div class="left-center-content">
+      <template v-if="xData.length">
+        <v-chart
+          class="chart"
+          :option="option"
+          :loading="loading"
+          :loading-options="loadingOptions"
+        />
+      </template>
+      <empty v-else />
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, onMounted, watch, defineAsyncComponent } from "vue";
+import { acceptType } from "@/api/home";
+import dayjs from "dayjs";
+import { loadingOptions } from "@/utils/constants";
+
+const Empty = defineAsyncComponent(
+  () => import("@/components/Empty/index.vue")
+);
+const props = defineProps({
+  dateArray: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+const date = ref([]);
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    date.value = val;
+  },
+  { immediate: true }
+);
+
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    getData();
+  }
+);
+
+const option = ref({});
+const xData = ref([]);
+const loading = ref(true);
+const getData = async () => {
+  loading.value = true;
+  try {
+    const { result } = await acceptType({
+      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
+      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
+    });
+    xData.value = result.map((item: any) => item.acceptType);
+    const totalData = result.reduce((pre: any, cur: any) => {
+      pre.push(cur.sumCount);
+      return pre;
+    }, []);
+    const handlingCount = result.reduce((pre: any, cur: any) => {
+      pre.push(cur.handlingCount);
+      return pre;
+    }, []);
+    const filedCount = result.reduce((pre: any, cur: any) => {
+      pre.push(cur.filedCount);
+      return pre;
+    }, []);
+    const overTimeCount = result.reduce((pre: any, cur: any) => {
+      pre.push(cur.overTimeCount);
+      return pre;
+    }, []);
+    // mock 数据
+    let dataArr = {
+      xData: xData.value,
+      result: [
+        { name: "总数", data: totalData },
+        { name: "已办", data: handlingCount },
+        { name: "在办", data: filedCount },
+        { name: "超期", data: overTimeCount },
+      ],
+      series: [],
+    };
+    const diamondData = dataArr.result.reduce((pre, cur, index) => {
+      pre[index] = cur.data.map(
+        (el, id) => el + (pre[index - 1] ? pre[index - 1][id] : 0)
+      );
+      return pre;
+    }, []);
+    const color = [
+      [
+        { offset: 0, color: "#efff37" },
+        { offset: 1, color: "#d5e700" },
+      ],
+      [
+        { offset: 0, color: "#32ffee" },
+        { offset: 1, color: "#00e8d5" },
+      ],
+      [
+        { offset: 0, color: "#46c9ff" },
+        { offset: 1, color: "#00b4ff" },
+      ],
+      [
+        { offset: 0, color: "#54a0ff" },
+        { offset: 1, color: "#1f83ff" },
+      ],
+    ];
+
+    dataArr.series = dataArr.result.reduce((p, c, i, array) => {
+      p.push(
+        {
+          z: i + 1,
+          stack: true,
+          type: "bar",
+          name: c.name,
+          barWidth: 15,
+          data: c.data,
+          itemStyle: {
+            color: {
+              type: "linear",
+              x: 0,
+              x2: 0,
+              y: 0,
+              y2: 1,
+              colorStops: color[i],
+            },
+          },
+        },
+        {
+          z: i + 10,
+          name: c.name,
+          type: "pictorialBar",
+          symbolPosition: "end",
+          symbol: "circle",
+          symbolOffset: [0, "-50%"],
+          symbolSize: [15, 12.5],
+          data: diamondData[i],
+          itemStyle: {
+            color: color[i + 1] ? color[i + 1][0].color : null,
+          },
+          tooltip: { show: false },
+        }
+      );
+
+      return p;
+    }, []);
+
+    // 最上边顶
+    dataArr.series.push({
+      name: dataArr.result[dataArr.result.length - 1].name,
+      z: 20,
+      type: "pictorialBar",
+      symbolPosition: "end",
+      data: diamondData[diamondData.length - 1],
+      symbol: "circle",
+      symbolOffset: ["0%", "-50%"],
+      symbolSize: [15, 12.5],
+      itemStyle: {
+        color: color[color.length - 1][0].color,
+      },
+      tooltip: { show: false },
+    });
+
+    // 最下边底
+    dataArr.series.push({
+      name: dataArr.result[0].name,
+      z: 30,
+      type: "pictorialBar",
+      symbolPosition: "start",
+      data: diamondData[0],
+      symbol: "circle",
+      symbolOffset: ["0%", "50%"],
+      symbolSize: [15, 12.5],
+      itemStyle: {
+        color: color[0][0].color,
+      },
+      tooltip: { show: false },
+    });
+    setOption(dataArr);
+    loading.value = false;
+  } catch (e) {
+    console.log(e);
+    loading.value = false;
+  }
+};
+const setOption = (data: any) => {
+  option.value = {
+    tooltip: {
+      trigger: "axis",
+      backgroundColor: "rgba(0,0,0,0.7)",
+      borderColor: "#000",
+      borderWidth: 1,
+      textStyle: { color: "#fff" },
+    },
+    xAxis: {
+      axisTick: { show: true },
+      axisLine: { lineStyle: { color: "rgba(255,255,255, .2)" } },
+      axisLabel: { fontSize: 12, color: "#fff" },
+      data: data.xData,
+    },
+    yAxis: [
+      {
+        name: "单位:件",
+        nameTextStyle: { color: "#fff", fontSize: 14 },
+        nameGap: 25,
+        splitLine: { lineStyle: { color: "rgba(255,255,255, .05)" } },
+        axisLine: { show: false },
+        axisLabel: { fontSize: 12, color: "#fff" },
+      },
+    ],
+    grid: { top: "20%", left: "10%", right: "3%", bottom: "10%" },
+    legend: {
+      data: data.result.map((item) => item.name),
+      textStyle: { fontSize: 12, color: "#fff" },
+      itemWidth: 15,
+      itemHeight: 10,
+      top: "10%",
+      right: "20",
+    },
+    series: data.series,
+  };
+};
+onMounted(() => {
+  getData();
+});
+</script>
+<style scoped lang="scss">
+.left-center {
+  padding: 10px 20px;
+
+  &-title {
+    font-size: 20px;
+    color: #fff;
+  }
+
+  .left-center-content {
+    height: 260px;
+  }
+}
+</style>

+ 42 - 436
src/views/snapshot/left-top.vue

@@ -1,462 +1,68 @@
 <template>
-  <div class="left-top">
-    <BorderBox1 class="container"> Content </BorderBox1>
-    <div class="flex w-full tab">
-      <span :class="{ active: active === '0' }" @click="active = '0'"
-        >工单统计</span
-      >
-      <span
-        :class="{ active: active === '2' }"
-        @click="active = '2'"
-        v-if="['ZiGong', 'LuZhou'].includes(themeConfig.appScope)"
-        >二次办理统计</span
-      >
-      <span :class="{ active: active === '1' }" @click="active = '1'"
-        >知识库统计</span
-      >
+  <div class="w-full h-full flex p-12 left-top-box">
+    <div class="flex-1">
+      <CountFlop :val="100" />
+      <p>运行总数据</p>
     </div>
-    <div
-      v-if="active === '0'"
-      class="flex flex-wrap left-top-content-order"
-      v-loading="loading"
-    >
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/done_order.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">办结工单</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.order.completionCount" :duration="2" />
-            </b>
-            件
-          </div>
-          <div class="left-top-content-item-text-rate">
-            办结率:{{ state.order.completionRate }}%
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/wait_order.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">待受理工单</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.order.haveToAcceptCount" :duration="2" />
-            </b>
-            件
-          </div>
-          <div class="left-top-content-item-text-rate">
-            待受理率:{{ state.order.haveToAcceptRate }}%
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/ex_order.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">超期工单</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.order.overTimeCount" :duration="2" />
-            </b>
-            件
-          </div>
-          <div class="left-top-content-item-text-rate">
-            超期率:{{ state.order.overTimeRate }}%
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/delay_order.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">延期工单</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.order.delayCount" :duration="2" />
-            </b>
-            件
-          </div>
-          <div class="left-top-content-item-text-rate">
-            延期率:{{ state.order.delayRate }}%
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/my_order.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">满意工单</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.order.satisfiedCount" :duration="2" />
-            </b>
-            件
-          </div>
-          <div class="left-top-content-item-text-rate">
-            总满意率:{{ state.order.satisfiedRate }}%
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/provice_order.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">省工单</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.order.provinceOrderCount" :duration="2" />
-            </b>
-            件
-          </div>
-          <div class="left-top-content-item-text-rate">
-            已办结:{{ state.order.provinceOrderCompletionCount }}件
-          </div>
-        </div>
-      </div>
+    <div class="flex-1 num-2">
+      <CountFlop :val="100" />
+      <p>今年上报量</p>
     </div>
-    <div
-      v-if="active === '1'"
-      class="flex flex-wrap left-top-content-knowledge"
-      v-loading="loading"
-    >
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/knowledge_add.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">本月新增知识数量</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.knowledge.todayAddCount" :duration="2" />
-            </b>
-            条
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/knowledge_update.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">本月更新知识数</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp
-                :endVal="state.knowledge.thisMonthModifyCount"
-                :duration="2"
-              />
-            </b>
-            条
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/knowledge_read.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">今日知识阅读量</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.knowledge.todayReadCount" :duration="2" />
-            </b>
-            条
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/knowledge_total.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">知识总数</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp :endVal="state.knowledge.knowledgeCount" :duration="2" />
-            </b>
-            条
-          </div>
-        </div>
-      </div>
-    </div>
-    <div
-      v-if="active === '2'"
-      class="flex flex-wrap left-top-content-knowledge"
-      v-loading="loading"
-    >
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/done_order.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">二次办理工单</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp
-                :endVal="state.secondHandling.orderCount"
-                :duration="2"
-              />
-            </b>
-            件
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/knowledge_update.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">二次办理超期</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp
-                :endVal="state.secondHandling.orderOverdueCount"
-                :duration="2"
-              />
-            </b>
-            件
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/knowledge_read.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">
-            二次办理即将超期工单
-          </div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              <CountUp
-                :endVal="state.secondHandling.orderSoonOverdueCount"
-                :duration="2"
-              />
-            </b>
-            件
-          </div>
-        </div>
-      </div>
-      <div class="flex left-top-content-item">
-        <img src="@/assets/img/home/knowledge_total.png" alt="" />
-        <div class="left-top-content-item-text">
-          <div class="left-top-content-item-text-title">二次办理满意度</div>
-          <div class="left-top-content-item-text-num">
-            <b>
-              {{ state.secondHandling.satisfactionRate }}
-            </b>
-          </div>
-        </div>
-      </div>
+    <div class="flex-1 num-3">
+      <CountFlop :val="100" />
+      <p>近一月上报量</p>
     </div>
   </div>
 </template>
 <script setup lang="ts">
 import { defineAsyncComponent, onMounted, reactive, ref, watch } from "vue";
-import { knowledge, workOrder, secondHandling } from "@/api/home";
-import dayjs from "dayjs";
 import { useThemeConfig } from "@/stores/themeConfig";
 import { storeToRefs } from "pinia";
-
-import { BorderBox1 } from "@dataview/datav-vue3";
-
-const CountUp = defineAsyncComponent(
-  () => import("@/components/Count-up/index.vue")
+const CountFlop = defineAsyncComponent(
+  () => import("@/components/Count-flop/index.vue")
 );
 
 const storesThemeConfig = useThemeConfig();
 const { themeConfig } = storeToRefs(storesThemeConfig);
 
-const active = ref("0");
-const props = defineProps({
-  dateArray: {
-    type: Array,
-    default: () => [],
-  },
-});
-const loading = ref(false);
-const date = ref([]);
-watch(
-  () => props.dateArray,
-  (val: any) => {
-    date.value = val;
-  },
-  { immediate: true }
-);
-
-watch(
-  () => props.dateArray,
-  (val: any) => {
-    getWorkOrder();
-    if (["ZiGong"].includes(themeConfig.value.appScope)) {
-      getSecondHandling();
-    }
-  }
-);
-const state = reactive({
-  order: {
-    completionCount: 0,
-    completionRate: 0,
-    haveToAcceptCount: 0,
-    haveToAcceptRate: 0,
-    overTimeCount: 0,
-    overTimeRate: 0,
-    delayCount: 0,
-    delayRate: 0,
-    satisfiedCount: 0,
-    satisfiedRate: 0,
-    provinceOrderCount: 0,
-    provinceOrderCompletionCount: 0,
-  },
-  knowledge: {
-    todayAddCount: 0,
-    thisMonthModifyCount: 0,
-    todayReadCount: 0,
-    knowledgeCount: 0,
-  },
-  secondHandling: {
-    orderCount: 0,
-    orderOverdueCount: 0,
-    orderSoonOverdueCount: 0,
-    satisfactionRate: "0%",
-  },
-});
-// 获取工单统计
-const getWorkOrder = async () => {
-  loading.value = true;
-  try {
-    const { result } = await workOrder({
-      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
-      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
-    });
-    state.order = result;
-    loading.value = false;
-  } catch (e) {
-    console.log(e);
-    loading.value = false;
-  }
-};
-// 获取知识库统计
-const getKnowledge = async () => {
-  loading.value = true;
-  try {
-    const { result } = await knowledge();
-    state.knowledge = result;
-    loading.value = false;
-  } catch (e) {
-    console.log(e);
-    loading.value = false;
-  }
-};
-// 获取二次办理统计
-const getSecondHandling = async () => {
-  loading.value = true;
-  try {
-    const { result } = await secondHandling({
-      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
-      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
-    });
-    state.secondHandling = result;
-    loading.value = false;
-  } catch (e) {
-    console.log(e);
-    loading.value = false;
-  }
-};
-onMounted(() => {
-  getWorkOrder();
-  getKnowledge();
-  if (["ZiGong"].includes(themeConfig.value.appScope)) {
-    getSecondHandling();
-  }
-});
+onMounted(() => {});
 </script>
 <style scoped lang="scss">
-.left-top {
-  padding: 0 30px;
-  .container {
-    width: 500px;
-    height: 200px;
+.left-top-box {
+  &:nth-child(1) {
+    color: #24fffb;
   }
-
-  .tab {
-    span {
-      cursor: pointer;
-      flex: 1;
-      height: 50px;
-      text-align: center;
-      line-height: 50px;
-      font-size: 16px;
-
-      margin-right: 20px;
-      background: url("@/assets/img/home/tab_bg.png") no-repeat;
-      background-size: 100% 100%;
-
-      &:last-child {
-        margin-right: 0;
-      }
-
-      &.active {
-        color: #fff;
-        font-size: 18px;
-        font-weight: bold;
-        position: relative;
-
-        &:after {
-          content: "";
-          display: block;
-          width: 50%;
-          height: 30px;
-          background: linear-gradient(
-            to right,
-            rgba(255, 255, 255, 0.2),
-            rgba(255, 255, 255, 0.1)
-          );
-          border-radius: 20px;
-          position: absolute;
-          top: 20%;
-          left: 25%;
-        }
-      }
-    }
+  &:nth-child(2) {
+    color: #17a7fc;
   }
-
-  &-content {
-    &-order {
-      justify-content: space-between;
-      margin-top: 20px;
-    }
-
-    &-knowledge {
-      justify-content: space-between;
-      margin-top: 40px;
+  &:nth-child(3) {
+    color: #efb53b;
+  }
+  .count-flop {
+    height: 42px;
+    line-height: 38px;
+    font-size: 38px;
+    color: #fff;
+    text-align: center;
+
+    :deep(.count-flop-box) {
+      line-height: inherit;
+      border: 1px solid #1b918f;
+      background: #196665;
     }
-
-    &-item {
-      width: 50%;
-      margin-bottom: 15px;
-
-      &:last-child {
-        margin-right: 0;
-        margin-bottom: 0;
-      }
-
-      img {
-        width: 50px;
-        height: 50px;
-      }
-
-      &-text {
-        margin-left: 10px;
-
-        &-title {
-          font-size: 16px;
-          color: #aaafaf;
-          white-space: nowrap;
-        }
-
-        &-num {
-          color: #aaafaf;
-
-          b {
-            font-size: 30px;
-            color: #fff;
-          }
-        }
-
-        &-rate {
-          font-size: 12px;
-          color: #fff;
-        }
-      }
+  }
+  .num-2 {
+    :deep(.count-flop-box) {
+      border: 1px solid #166690;
+      background: rgba(16, 75, 100, 0.8);
     }
   }
 
-  .count-up-wrap {
-    display: inline-block;
+  .num-3 {
+    :deep(.count-flop-box) {
+      border: 1px solid #fdd442;
+      background: rgba(56, 65, 49, 0.4);
+    }
   }
 }
 </style>

+ 273 - 0
src/views/snapshot/right-bottom.vue

@@ -0,0 +1,273 @@
+<template>
+  <div class="left_bottom">
+    <div class="left_bottom-title flex">
+      <div class="flex items-center">
+        <img src="@/assets/img/home/title_arrow.png" alt="" />
+        占比分析
+      </div>
+      <div class="left_bottom-title-tabs">
+        <span
+          :class="{ active: activeIndex === 0 }"
+          class="mr-5 left_bottom-title-tabs-item"
+          @click="change(0)"
+          >来源方式</span
+        >
+        <span
+          :class="{ active: activeIndex === 1 }"
+          class="left_bottom-title-tabs-item"
+          @click="change(1)"
+          >受理类型</span
+        >
+      </div>
+    </div>
+    <div class="left_bottom-content">
+      <template v-if="dataList.length">
+        <v-chart
+          class="chart"
+          :option="option"
+          :loading="loading"
+          :loading-options="loadingOptions"
+        />
+      </template>
+      <empty v-else />
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { ref, onMounted, watch, computed, defineAsyncComponent } from "vue";
+import { proportionAnalysis } from "api/home";
+import dayjs from "dayjs";
+import { loadingOptions } from "@/utils/constants";
+
+const Empty = defineAsyncComponent(
+  () => import("@/components/Empty/index.vue")
+);
+const props = defineProps({
+  dateArray: {
+    type: Array,
+    default: () => [],
+  },
+});
+const date = ref([]);
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    date.value = val;
+  },
+  { immediate: true }
+);
+
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    getData();
+  }
+);
+
+const activeIndex = ref(0);
+const option = ref<any>({});
+const activeText = computed(() => {
+  return activeIndex.value === 0 ? "来源占比" : "类型占比";
+});
+const change = (index: number) => {
+  activeIndex.value = index;
+  getData();
+};
+const dataList = ref([]);
+const loading = ref(false);
+const getData = async () => {
+  loading.value = true;
+  try {
+    const { result } = await proportionAnalysis({
+      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
+      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
+      IsSource: activeIndex.value === 0,
+    });
+    const legEndTime = result
+      .map((item: any) => item.name)
+      .filter((item: any) => item);
+    dataList.value = result
+      .map((item: any) => {
+        return {
+          name: item.name ?? "",
+          value: item.hasCount,
+          ...item,
+        };
+      })
+      .filter((item: any) => item.name);
+    setOption(legEndTime, dataList.value);
+    loading.value = false;
+  } catch (e) {
+    loading.value = false;
+    console.log(e);
+  }
+};
+const setOption = (legEndTime: any, data: any) => {
+  option.value = {
+    tooltip: {
+      trigger: "item",
+    },
+    legend: {
+      type: "scroll",
+      pageIconColor: "#fff",
+      pageIconInactiveColor: "#333",
+      height: "180",
+      icon: "circle",
+      top: "center",
+      right: "0",
+      orient: "vertical",
+      itemGap: 20,
+      data: legEndTime,
+      textStyle: {
+        color: "#fff",
+        fontSize: 14,
+      },
+      formatter: (name) => {
+        const item = dataList.value.find((item) => item.name == name);
+        const len = name.length;
+        let str = "";
+        if (len > 5) {
+          name = name.slice(0, 8) + "...";
+        } else {
+          name = name + str;
+        }
+        return `${name}  ${item.hasRate}% (${item.hasCount}件)`; //返回出图例所显示的内容是名称+百分比
+      },
+    },
+    graphic: [
+      {
+        type: "text",
+        z: 100,
+        left: "23%",
+        top: "40%",
+        style: {
+          fill: "#fff",
+          text: "100%",
+          font: "28px Microsoft YaHei",
+        },
+      },
+      {
+        type: "text",
+        z: 100,
+        left: "24%",
+        top: "56%",
+        style: {
+          fill: "#fff",
+          text: activeText.value,
+          font: "16px Microsoft YaHei",
+        },
+      },
+    ],
+    series: [
+      {
+        name: "",
+        type: "gauge",
+        splitNumber: 10,
+        radius: "65%",
+        center: ["30%", "50%"],
+        startAngle: 0,
+        endAngle: 360,
+        axisLine: {
+          show: false,
+        },
+        axisTick: {
+          show: true,
+          lineStyle: {
+            width: 4,
+          },
+          length: 8,
+          splitNumber: 3,
+        },
+        splitLine: {
+          show: false,
+        },
+        axisLabel: {
+          show: false,
+        },
+        detail: {
+          show: false,
+        },
+      },
+      {
+        name: "数量", //内部圆
+        type: "pie",
+        animation: false,
+        clockwise: false,
+        radius: "38%",
+        center: ["30%", "50%"],
+        data: [10],
+        itemStyle: {
+          color: "#082141",
+        },
+        label: {
+          show: false,
+        },
+        emphasis: {
+          show: false,
+          textStyle: {
+            fontSize: "14",
+          },
+        },
+        labelLine: {
+          show: false,
+        },
+      },
+      {
+        name: "",
+        type: "pie",
+        radius: ["55%", "80%"],
+        center: ["30%", "50%"],
+        avoidLabelOverlap: false,
+        itemStyle: {
+          borderRadius: 0,
+          borderColor: "#050D0E",
+          borderWidth: 10,
+        },
+        label: {
+          show: false,
+          formatter: "{b}:{d}%", // 用来换行
+        },
+        labelLine: {
+          show: false,
+        },
+        data: data,
+      },
+    ],
+  };
+};
+onMounted(() => {
+  getData();
+});
+</script>
+<style scoped lang="scss">
+.left_bottom {
+  padding: 10px 20px;
+
+  &-title {
+    font-size: 20px;
+    color: #fff;
+    justify-content: space-between;
+    align-items: center;
+
+    &-tabs {
+      font-size: 14px;
+      color: #7dbdec;
+
+      &-item {
+        padding: 5px 10px;
+        cursor: pointer;
+      }
+    }
+
+    .active {
+      color: #b4d7ec;
+      border: 1px solid #b4d7ec;
+      border-radius: 5px;
+    }
+  }
+
+  &-content {
+    height: calc(100% - 40px);
+  }
+}
+</style>

+ 88 - 0
src/views/snapshot/right-center.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="right_center flex flex-col w-full h-full">
+    <div class="right_center-title flex">
+      <div class="flex items-center">
+        <img src="@/assets/img/home/title_arrow.png" alt="" />
+        今日随手拍奖励统计
+      </div>
+    </div>
+    <div class="flex flex-col items-center justify-between h-full flex-1 p-2">
+      <div class="flex items-center">
+        奖励总金额:<CountUp :endVal="100" :duration="2" />
+      </div>
+      <div class="flex items-center">
+        市民已发总额:<CountUp :endVal="100" :duration="2" />
+      </div>
+      <div class="flex items-center">
+        网格员已发总额:<CountUp :endVal="100" :duration="2" />
+      </div>
+      <div class="flex items-center">
+        市民待发总额:<CountUp :endVal="100" :duration="2" />
+      </div>
+      <div class="flex items-center">
+        网格员持发总额:<CountUp :endVal="100" :duration="2" />
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { onMounted, ref, watch, defineAsyncComponent } from "vue";
+import { departmentSatisfaction } from "api/home";
+import dayjs from "dayjs";
+
+const CountUp = defineAsyncComponent(
+  () => import("@/components/Count-up/index.vue")
+);
+
+const props = defineProps({
+  dateArray: {
+    type: Array,
+    default: () => [],
+  },
+});
+const date = ref([]);
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    date.value = val;
+  },
+  { immediate: true }
+);
+
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    getData();
+  }
+);
+const list = ref<any>([]);
+const loading = ref(false);
+const getData = async () => {
+  loading.value = true;
+  try {
+    const { result } = await departmentSatisfaction({
+      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
+      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
+    });
+    list.value = result;
+    loading.value = false;
+  } catch (e) {
+    console.log(e);
+    loading.value = false;
+  }
+};
+onMounted(() => {
+  getData();
+});
+</script>
+<style scoped lang="scss">
+.right_center {
+  padding: 10px 20px;
+  &-title {
+    font-size: 20px;
+    color: #fff;
+    justify-content: space-between;
+    align-items: center;
+  }
+}
+</style>

+ 160 - 0
src/views/snapshot/right-top.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="right_top flex flex-col w-full h-full">
+    <div class="right_top-title flex">
+      <div class="flex items-center">
+        <img src="@/assets/img/home/title_arrow.png" alt="" />
+        今日办件统计
+      </div>
+    </div>
+    <div class="flex flex-col items-center justify-between h-full flex-1 p-4">
+      <div class="flex items-center">
+        随手拍来件数: <CountUp :endVal="100" :duration="2" />
+      </div>
+      <div class="flex items-center">
+        受理范围内总件数:<CountUp :endVal="100" :duration="2" />
+      </div>
+      <div class="flex items-center">
+        非受理范围内总件数:<CountUp :endVal="100" :duration="2" />
+      </div>
+      <div class="flex items-center">
+        随手拍转12345件数:<CountUp :endVal="100" :duration="2" />
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { onMounted, ref, watch, defineAsyncComponent } from "vue";
+import { highFrequency } from "@/api/home";
+import dayjs from "dayjs";
+
+const CountUp = defineAsyncComponent(
+  () => import("@/components/Count-up/index.vue")
+);
+
+const props = defineProps({
+  dateArray: {
+    type: Array,
+    default: () => [],
+  },
+});
+const date = ref([]);
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    date.value = val;
+  },
+  { immediate: true }
+);
+
+watch(
+  () => props.dateArray,
+  (val: any) => {
+    getData();
+  }
+);
+
+const list = ref<any>([]);
+const loading = ref(false);
+const getData = async () => {
+  loading.value = true;
+  try {
+    const { result } = await highFrequency({
+      StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
+      EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
+    });
+    list.value = result;
+    // list.value = [...result,...result,...result,...result,...result];
+    loading.value = false;
+  } catch (e) {
+    console.log(e);
+    loading.value = false;
+  }
+};
+onMounted(() => {
+  getData();
+});
+</script>
+<style scoped lang="scss">
+.right_top {
+  padding: 10px 20px;
+  font-size: 15px;
+
+  &-title {
+    font-size: 20px;
+    color: #fff;
+    justify-content: space-between;
+    align-items: center;
+    line-height: initial;
+  }
+
+  .scroll {
+    height: 280px;
+    overflow: hidden;
+    margin-top: 10px;
+
+    &-item {
+      width: 100%;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      color: #d2d4d5;
+      height: 30px;
+      line-height: 25px;
+      margin-bottom: 10px;
+      border-left: transparent;
+
+      &-empty {
+        display: inline-block;
+        width: 5px;
+        color: transparent;
+      }
+
+      &-area {
+        display: inline-block;
+        background-color: #69bbf6;
+        padding: 0 6px;
+        color: #333;
+        border-radius: 4px;
+        white-space: nowrap;
+        width: 75px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        text-align: center;
+      }
+
+      &-title {
+        width: 40%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        margin-left: 10px;
+        line-height: initial;
+      }
+
+      &-hotspot {
+        width: 30%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        flex: 1;
+      }
+    }
+  }
+}
+
+.count-up-wrap {
+  display: inline-block;
+}
+</style>
+<style lang="scss">
+.scroll-item-title {
+  width: 45%;
+  margin-left: 10px;
+  line-height: initial;
+}
+
+.scroll-item-hotspot {
+  width: 30%;
+  flex: 1;
+}
+</style>

+ 45 - 19
yarn.lock

@@ -49,15 +49,6 @@
   resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31"
   integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
 
-"@dataview/datav-vue3@^0.0.0-test.1672506674342":
-  version "0.0.0-test.1672506674342"
-  resolved "https://registry.yarnpkg.com/@dataview/datav-vue3/-/datav-vue3-0.0.0-test.1672506674342.tgz#244105d18936a82ee139ab64a8b3bdd8b276f62d"
-  integrity sha512-d0oT/msAi592CTvWmQl0umkLpHgMwtTN2+peyo0L2GHNG7b4cKeO9meEF5o28DgFzRwOLeNQW73vKCF4JC+ihw==
-  dependencies:
-    "@jiaminghi/color" "^0.1.1"
-    classnames "^2.3.2"
-    lodash-es "^4.17.21"
-
 "@element-plus/icons-vue@^2.3.1":
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz#1f635ad5fdd5c85ed936481525570e82b5a8307a"
@@ -205,10 +196,40 @@
     wrap-ansi "^8.1.0"
     wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
 
-"@jiaminghi/color@^0.1.1":
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/@jiaminghi/color/-/color-0.1.1.tgz#bddf9012dfce6982d3829a983ffeb91ee9dda7b6"
-  integrity sha512-M09+Sb5HGqVim0zo+nG5gU1v+6gXT8ptr0BZR6dMGt83XmCJgnZtO8s7llTW4hLFFFM5co6geZvTekqLpSPAAQ==
+"@jiaminghi/bezier-curve@*":
+  version "0.0.9"
+  resolved "https://registry.yarnpkg.com/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz#5196aca93c8b061a612b4c3eabcedf9490cef6ee"
+  integrity sha512-u9xJPOEl6Dri2E9FfmJoGxYQY7vYJkURNX04Vj64tdi535tPrpkuf9Sm0lNr3QTKdHQh0DdNRsaa62FLQNQEEw==
+  dependencies:
+    "@babel/runtime" "^7.5.5"
+
+"@jiaminghi/c-render@^0.4.3":
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/@jiaminghi/c-render/-/c-render-0.4.3.tgz#982ebd8f71b443bb9507834227834973ebd9b6d8"
+  integrity sha512-FJfzj5hGj7MLqqqI2D7vEzHKbQ1Ynnn7PJKgzsjXaZpJzTqs2Yw5OSeZnm6l7Qj7jyPAP53lFvEQNH4o4j6s+Q==
+  dependencies:
+    "@babel/runtime" "^7.5.5"
+    "@jiaminghi/bezier-curve" "*"
+    "@jiaminghi/color" "*"
+    "@jiaminghi/transition" "*"
+
+"@jiaminghi/charts@^0.2.18":
+  version "0.2.18"
+  resolved "https://registry.yarnpkg.com/@jiaminghi/charts/-/charts-0.2.18.tgz#63ded95200789fc1a1fd04b7fd9e56f58d22d90f"
+  integrity sha512-K+HXaOOeWG9OOY1VG6M4mBreeeIAPhb9X+khG651AbnwEwL6G2UtcAQ8GWCq6GzhczcLwwhIhuaHqRygwHC0sA==
+  dependencies:
+    "@babel/runtime" "^7.5.5"
+    "@jiaminghi/c-render" "^0.4.3"
+
+"@jiaminghi/color@*", "@jiaminghi/color@^1.1.3":
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/@jiaminghi/color/-/color-1.1.3.tgz#a2336750d1266155ffe80375c58c26fdec495611"
+  integrity sha512-ZY3hdorgODk4OSTbxyXBPxAxHPIVf9rPlKJyK1C1db46a50J0reFKpAvfZG8zMG3lvM60IR7Qawgcu4ZDO3+Hg==
+
+"@jiaminghi/transition@*":
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/@jiaminghi/transition/-/transition-1.1.11.tgz#576d8af092434b34201eba5eaecc79dd33c8ad8c"
+  integrity sha512-owBggipoHMikDHHDW5Gc7RZYlVuvxHADiU4bxfjBVkHDAmmck+fCkm46n2JzC3j33hWvP9nSCAeh37t6stgWeg==
   dependencies:
     "@babel/runtime" "^7.5.5"
 
@@ -252,6 +273,16 @@
     "@jridgewell/resolve-uri" "^3.1.0"
     "@jridgewell/sourcemap-codec" "^1.4.14"
 
+"@kjgl77/datav-vue3@^1.7.4":
+  version "1.7.4"
+  resolved "https://registry.yarnpkg.com/@kjgl77/datav-vue3/-/datav-vue3-1.7.4.tgz#329a39e5ecec996b24a417384b2cbb83f21dc496"
+  integrity sha512-zYVTVKkklUxwtiNKS1qPBilm4rTW+WItfp0zVpaRAI8wgXkLSPbDR9xPq2+UcU/Jft7/DVdMfBp709E2ResuPQ==
+  dependencies:
+    "@jiaminghi/c-render" "^0.4.3"
+    "@jiaminghi/charts" "^0.2.18"
+    "@jiaminghi/color" "^1.1.3"
+    "@vueuse/core" "^10.11.1"
+
 "@microsoft/signalr@^8.0.0":
   version "8.0.7"
   resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-8.0.7.tgz#94419ddbf9418753e493f4ae4c13990316ec2ea5"
@@ -563,7 +594,7 @@
   resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.4.0.tgz#f01e2f6089b5098136fb084a0dd0cdd4533b72b0"
   integrity sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==
 
-"@vueuse/core@^10.5.0":
+"@vueuse/core@^10.11.1", "@vueuse/core@^10.5.0":
   version "10.11.1"
   resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6"
   integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==
@@ -852,11 +883,6 @@ chokidar@^4.0.0:
   dependencies:
     readdirp "^4.0.1"
 
-classnames@^2.3.2:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
-  integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
-
 claygl@^1.2.1:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/claygl/-/claygl-1.3.0.tgz#7a6e2903210519ac358848f5d78070ed211685f3"