소스 검색

通关旅客统计分析

CzRger 1 주 전
부모
커밋
819999196a
3개의 변경된 파일378개의 추가작업 그리고 48개의 파일을 삭제
  1. 143 0
      src/views/pass-guest-statistic/chart/line.vue
  2. 22 3
      src/views/pass-guest-statistic/chart/pie.vue
  3. 213 45
      src/views/pass-guest-statistic/index.vue

+ 143 - 0
src/views/pass-guest-statistic/chart/line.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="chart-main" ref="ref_main">
+    <div class="chart-ref" ref="ref_chart" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref, onMounted, watch, nextTick } from 'vue'
+import * as echarts from 'echarts'
+
+const props = defineProps({
+  data: <any>{},
+  unit: {}
+})
+
+const state = reactive({
+  resizeObserver: <any>null,
+  chart: <any>null
+})
+const ref_chart = ref()
+const ref_main = ref()
+const initChart = () => {
+  echarts.dispose(ref_chart.value)
+  const chart = echarts.init(ref_chart.value)
+  let maxY = 0
+  props.data.forEach((d) => {
+    d.data.forEach((v) => {
+      if (v.value > maxY) {
+        maxY = v.value
+      }
+    })
+  })
+  const option = {
+    color: ['#5ad8a6', '#2e81ff', '#ffa254', '#8692ff', '#ffd454', '#ff5454'],
+    grid: {
+      top: 40,
+      bottom: 50,
+      left: String(maxY).length * 8 + 12,
+      right: 10
+    },
+    legend: {
+      width: '100%',
+      orient: 'horizontal',
+      right: 'center',
+      top: 'top',
+      icon: 'circle',
+      textStyle: {
+        color: '#6C7482'
+      },
+      itemWidth: 8
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    xAxis: {
+      type: 'category',
+      data: props.data?.[0]?.data?.map((v) => v.name) || [],
+      axisLine: { show: false },
+      axisTick: { show: false },
+      axisLabel: {
+        // color: 'rgba(255, 255, 255, 0.50)',
+        fontSize: 10
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: { show: false },
+      axisTick: { show: false },
+      // splitLine: {
+      //   lineStyle: {
+      //     color: 'rgba(255, 255, 255, 0.50)',
+      //     type: 'dotted'
+      //   }
+      // },
+      axisLabel: {
+        // color: 'rgba(255, 255, 255, 0.50)',
+        fontSize: 10
+      }
+    },
+    dataZoom: [
+      {
+        type: 'inside',
+        start: 0,
+        end: 100
+      },
+      {
+        type: 'slider',
+        start: 0,
+        end: 100,
+        // backgroundColor: '#158EFE',
+        height: 20,
+        bottom: 4,
+        showDetail: false
+      }
+    ],
+    series: props.data.map((v) => {
+      return {
+        name: v.name,
+        type: 'line',
+        data: v.data,
+        symbol: 'none',
+        smooth: true
+      }
+    })
+  }
+  chart.setOption(option)
+  state.resizeObserver = new ResizeObserver((entries) => {
+    for (const entry of entries) {
+      chart && chart.resize()
+    }
+  })
+  state.resizeObserver.observe(ref_main.value)
+  return chart
+}
+watch(
+  () => props.data,
+  () => {
+    state.chart = initChart()
+  }
+)
+onMounted(() => {
+  nextTick(() => {
+    state.chart = initChart()
+  })
+  return () => {
+    state.resizeObserver?.unobserve(ref_main?.value)
+    state.resizeObserver?.disconnect()
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.chart-main {
+  flex: 1;
+  width: 100%;
+  height: 100%;
+
+  .chart-ref {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 22 - 3
src/views/pass-guest-statistic/chart/pie.vue

@@ -5,14 +5,14 @@
 </template>
 
 <script setup lang="ts">
-import { getCurrentInstance, reactive, ref, onMounted, watch, nextTick } from 'vue'
+import { reactive, ref, onMounted, watch, nextTick } from 'vue'
 import * as echarts from 'echarts'
 
 const props = defineProps({
-  data: {}
+  data: {},
+  total: {}
 })
 
-const { proxy } = getCurrentInstance()
 const state = reactive({
   resizeObserver: <any>null,
   chart: <any>null
@@ -22,12 +22,31 @@ const ref_main = ref()
 const initChart = () => {
   echarts.dispose(ref_chart.value)
   const chart = echarts.init(ref_chart.value)
+  let title = ''
+  String(props.total)
+    .split('')
+    .reverse()
+    .forEach((v, i) => {
+      if (i > 0 && i % 3 === 0) {
+        title = ',' + title
+      }
+      title = v + title
+    })
   const option = {
     color: ['#2e81ff', '#ffa254', '#8692ff', '#ffd454', '#ff5454', '#5ad8a6'],
     tooltip: {
       trigger: 'item',
       formatter: '{b} {c}({d}%)'
     },
+    title: {
+      text: title,
+      top: 'center',
+      left: 'center',
+      textStyle: {
+        color: '#606266',
+        fontSize: 26
+      }
+    },
     legend: {
       width: '100%',
       orient: 'horizontal',

+ 213 - 45
src/views/pass-guest-statistic/index.vue

@@ -1,71 +1,239 @@
 <template>
-  <div class="size-full flex p-4 gap-4 flex-col overflow-y-auto">
-    <el-card class="card-1 h-[430px]">
-      <div class="flex flex-col gap-4">
-        <div class="flex items-center gap-4">
-          <span class="text-[#2E3238] text-xl font-bold">离岛旅客总量统计</span>
-          <div>
-            <el-date-picker v-model="state.time" type="daterange" value-format="YYYY-MM-DD" />
+  <div class="size-full flex p-4 gap-4 flex-col overflow-hidden">
+    <el-scrollbar>
+      <el-card class="card-1 h-[430px]">
+        <div class="flex flex-col gap-4">
+          <div class="flex items-center gap-4">
+            <span class="text-[#2E3238] text-xl font-bold">离岛旅客总量统计</span>
+            <div>
+              <el-date-picker v-model="state.top.time" type="daterange" value-format="YYYY-MM-DD" @change="initTop" />
+            </div>
+          </div>
+          <div class="flex gap-4">
+            <div class="flex-1 flex flex-col gap-4">
+              <div
+                class="h-[110px] rounded-sm relative bg-[url('@/assets/images/bg-1.png')] bg-no-repeat bg-[length:100%_100%] w-full flex items-center pl-4"
+              >
+                <div class="flex flex-col gap-2">
+                  <div class="text-base text-white">离岛旅客总量</div>
+                  <digits v-model="state.top.total" />
+                </div>
+                <img src="@/assets/images/icon-1.png" class="absolute right-4" />
+              </div>
+              <div class="grid grid-cols-4 gap-x-4 gap-y-9">
+                <template v-for="(item, index) in state.top.data" :key="index">
+                  <div class="col-span-1 text-base flex flex-col">
+                    <div class="text-[#606266]">{{ item.name }}</div>
+                    <div class="flex items-center gap-4">
+                      <img v-if="item.name.includes('机场')" src="@/assets/images/icon-3.png" />
+                      <img v-else src="@/assets/images/icon-4.png" />
+                      <div>
+                        <span class="text-[#606266] text-[28px] font-bold" style="font-family: cursive">{{
+                          item.value
+                        }}</span>
+                        <span class="text-[#909399]">/个</span>
+                      </div>
+                    </div>
+                  </div>
+                </template>
+              </div>
+            </div>
+            <div class="flex-1">
+              <pieChart :data="state.top.data" :total="state.top.total" />
+            </div>
           </div>
         </div>
-        <div class="flex gap-4">
-          <div class="flex-1 flex flex-col gap-4">
-            <div
-              class="h-[110px] rounded-sm relative bg-[url('@/assets/images/bg-1.png')] bg-no-repeat bg-[length:100%_100%] w-full flex items-center pl-4"
+      </el-card>
+      <el-card class="card-2" style="margin-top: 16px">
+        <div class="flex flex-col gap-4">
+          <div class="flex items-center">
+            <el-checkbox
+              v-model="state.bottom.checkAll"
+              :indeterminate="state.bottom.isIndeterminate"
+              @change="handleCheckAllChange"
+              class="pr-4"
             >
-              <div class="flex flex-col gap-2">
-                <div class="text-base text-white">离岛旅客总量</div>
-                <digits v-model="state.guestTotal" />
+              全选
+            </el-checkbox>
+            <el-checkbox-group v-model="state.bottom.areas" @change="handleCheckedChange">
+              <template v-for="item in areasDictCpt" :key="item.dictValue">
+                <el-checkbox :label="item.dictLabel" :value="item.dictValue" />
+              </template>
+            </el-checkbox-group>
+            <el-button type="primary" style="margin-left: auto" @click="initLines(false)">查询</el-button>
+            <el-button type="info" @click="initLines(true)">重置</el-button>
+          </div>
+          <div class="grid grid-cols-2 gap-4">
+            <div class="h-80 shadow border/20 p-4 rounded-sm bg-white flex flex-col gap-4">
+              <div class="flex items-center justify-between">
+                <span class="text-[#2E3238] text-[20px] font-bold">旅客日流量分析</span>
+                <el-date-picker
+                  v-model="state.bottom.chart1.time"
+                  type="date"
+                  value-format="YYYY-MM-DD"
+                  style="width: 140px"
+                />
+              </div>
+              <div class="flex-1">
+                <lineChart :data="state.bottom.chart1.data" />
               </div>
-              <img src="@/assets/images/icon-1.png" class="absolute right-4" />
             </div>
-            <div class="grid grid-cols-4 gap-x-4 gap-y-9">
-              <template v-for="(item, index) in state.topArr" :key="index">
-                <div class="col-span-1 text-base flex flex-col">
-                  <div class="text-[#606266]">{{ item.name }}</div>
-                  <div class="flex items-center gap-4">
-                    <img v-if="item.name.includes('机场')" src="@/assets/images/icon-3.png" />
-                    <img v-else src="@/assets/images/icon-4.png" />
-                    <div>
-                      <span class="text-[#606266] text-[28px] font-bold" style="font-family: cursive">{{
-                        item.value
-                      }}</span>
-                      <span class="text-[#909399]">/个</span>
-                    </div>
-                  </div>
+            <div class="h-80 shadow border/20 p-4 rounded-sm bg-white flex flex-col gap-4">
+              <div class="flex items-center justify-between">
+                <span class="text-[#2E3238] text-[20px] font-bold">旅客月流量分析</span>
+                <el-date-picker
+                  v-model="state.bottom.chart2.time"
+                  type="month"
+                  value-format="YYYY-MM"
+                  style="width: 120px"
+                />
+              </div>
+              <div class="flex-1">
+                <lineChart :data="state.bottom.chart2.data" />
+              </div>
+            </div>
+            <div class="h-80 shadow border/20 p-4 rounded-sm bg-white flex flex-col gap-4">
+              <div class="flex items-center justify-between">
+                <span class="text-[#2E3238] text-[20px] font-bold">查验成功率分析</span>
+                <div>
+                  <el-date-picker
+                    v-model="state.bottom.chart3.time"
+                    type="daterange"
+                    value-format="YYYY-MM-DD"
+                    style="width: 220px"
+                  />
                 </div>
-              </template>
+              </div>
+              <div class="flex-1">
+                <lineChart :data="state.bottom.chart3.data" />
+              </div>
+            </div>
+            <div class="h-80 shadow border/20 p-4 rounded-sm bg-white flex flex-col gap-4">
+              <div class="flex items-center justify-between">
+                <span class="text-[#2E3238] text-[20px] font-bold">拦截成功率分析</span>
+                <div>
+                  <el-date-picker
+                    v-model="state.bottom.chart4.time"
+                    type="daterange"
+                    value-format="YYYY-MM-DD"
+                    style="width: 220px"
+                  />
+                </div>
+              </div>
+              <div class="flex-1">
+                <lineChart :data="state.bottom.chart4.data" />
+              </div>
             </div>
-          </div>
-          <div class="flex-1">
-            <pieChart :data="state.topArr" />
           </div>
         </div>
-      </div>
-    </el-card>
-    <el-card class="card-2"> </el-card>
+      </el-card>
+    </el-scrollbar>
   </div>
 </template>
 
 <script setup lang="ts">
-import { onMounted, reactive } from 'vue'
+import { computed, onMounted, reactive } from 'vue'
 import dayjs from 'dayjs'
 import digits from './digits.vue'
 import { passengerstatisticsList } from '@/apis/lvke'
 import pieChart from './chart/pie.vue'
+import lineChart from './chart/line.vue'
 
 const state: any = reactive({
-  time: [dayjs(new Date()).format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')],
-  guestTotal: 0,
-  topArr: []
+  top: {
+    time: [dayjs(new Date()).format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')],
+    total: 0,
+    data: []
+  },
+  bottom: {
+    checkAll: false,
+    isIndeterminate: false,
+    area: [],
+    chart1: {
+      time: dayjs(new Date()).format('YYYY-MM-DD'),
+      data: []
+    },
+    chart2: {
+      time: dayjs(new Date()).format('YYYY-MM-DD'),
+      data: []
+    },
+    chart3: {
+      time: [
+        dayjs(new Date().getTime() - 60 * 60 * 24 * 7).format('YYYY-MM-DD'),
+        dayjs(new Date()).format('YYYY-MM-DD')
+      ],
+      data: []
+    },
+    chart4: {
+      time: [
+        dayjs(new Date().getTime() - 60 * 60 * 24 * 7).format('YYYY-MM-DD'),
+        dayjs(new Date()).format('YYYY-MM-DD')
+      ],
+      data: []
+    }
+  }
 })
-onMounted(() => {
+const areasDictCpt = computed(() => {
+  return [
+    { dictLabel: '青龙机场', dictValue: 1 },
+    { dictLabel: '白虎机场', dictValue: 2 },
+    { dictLabel: '朱雀机场', dictValue: 3 },
+    { dictLabel: '玄武机场', dictValue: 4 }
+  ]
+})
+const handleCheckAllChange = (val: any) => {
+  state.bottom.areas = val ? areasDictCpt.value.map((v) => v.dictValue) : []
+  state.bottom.isIndeterminate = false
+}
+const handleCheckedChange = (value: any) => {
+  const checkedCount = value.length
+  state.bottom.checkAll = checkedCount === areasDictCpt.value.length
+  state.bottom.isIndeterminate = checkedCount > 0 && checkedCount < areasDictCpt.value.length
+}
+const initTop = () => {
   passengerstatisticsList({ type: 1 }).then((res) => {
-    state.topArr = res.data || []
-  })
-  passengerstatisticsList({ type: 2 }).then((res) => {
-    console.log(res)
+    state.top.data = res.data || []
+    let total = 0
+    state.top.data.forEach((v) => {
+      total += Number(v.value)
+    })
+    state.top.total = total
   })
+}
+const initLines = (reset = true) => {
+  if (reset) {
+  }
+  state.bottom.chart1.data = areasDictCpt.value.map((v) => ({ name: v.dictLabel, data: [] }))
+  for (let i = 1; i <= 24; i++) {
+    state.bottom.chart1.data.forEach((v) => {
+      v.data.push({ name: String(i), value: Math.ceil(Math.random() * 100000) })
+    })
+  }
+
+  state.bottom.chart2.data = areasDictCpt.value.map((v) => ({ name: v.dictLabel, data: [] }))
+  for (let i = 1; i <= 31; i++) {
+    state.bottom.chart2.data.forEach((v) => {
+      v.data.push({ name: String(i), value: Math.ceil(Math.random() * 100000) })
+    })
+  }
+
+  state.bottom.chart3.data = areasDictCpt.value.map((v) => ({ name: v.dictLabel, data: [] }))
+  for (let i = 1; i <= 31; i++) {
+    state.bottom.chart3.data.forEach((v) => {
+      v.data.push({ name: `2025-10-${i < 10 ? '0' + i : i}`, value: Math.ceil(Math.random() * 100000) })
+    })
+  }
+
+  state.bottom.chart4.data = areasDictCpt.value.map((v) => ({ name: v.dictLabel, data: [] }))
+  for (let i = 1; i <= 31; i++) {
+    state.bottom.chart4.data.forEach((v) => {
+      v.data.push({ name: `2025-10-${i < 10 ? '0' + i : i}`, value: Math.ceil(Math.random() * 100000) })
+    })
+  }
+}
+onMounted(() => {
+  initTop()
+  initLines()
 })
 </script>