CzRger 3 months ago
parent
commit
b982d770ff

+ 2 - 2
snowy-admin-web/.env.development

@@ -5,8 +5,8 @@ NODE_ENV = development
 VITE_TITLE = gsc
 
 # 接口地址
-VITE_API_BASEURL = http://127.0.0.1:18070
-#VITE_API_BASEURL = http://8.130.72.63:18070
+#VITE_API_BASEURL = http://127.0.0.1:18070
+VITE_API_BASEURL = http://8.130.72.63:18070
 #VITE_API_BASEURL = http://192.168.108.70:18070
 
 # 本地端口

+ 1 - 1
snowy-admin-web/package.json

@@ -31,7 +31,7 @@
 		"axios": "1.6.2",
 		"cropperjs": "1.6.1",
 		"dayjs": "1.11.10",
-		"echarts": "5.4.3",
+		"echarts": "^5.5.1",
 		"echarts-stat": "1.2.0",
 		"enquire.js": "2.1.6",
 		"event-source-polyfill": "1.0.31",

+ 2 - 2
snowy-admin-web/src/api/gsc/basic.js

@@ -18,7 +18,7 @@ export default {
 		return request('/biz/passengerinfo/detail', data, 'get')
 	},
 	// 统计
-	passengerChart(data) {
-		return request('/biz/passenger/chart', data, 'post')
+  passengerstatisticsList(data) {
+		return request('/biz/passengerstatistics/list', data, 'get')
 	},
 }

BIN
snowy-admin-web/src/views/gsc/pass-statistic/card.png


BIN
snowy-admin-web/src/views/gsc/pass-statistic/chart/bottom.png


+ 125 - 0
snowy-admin-web/src/views/gsc/pass-statistic/chart/line.vue

@@ -0,0 +1,125 @@
+<template>
+  <div class="chart-main" ref="ref_main">
+    <div class="chart-ref" ref="ref_chart"/>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {getCurrentInstance, reactive, ref, onMounted, watch, nextTick} from "vue";
+import * as echarts from 'echarts';
+
+const props = defineProps({
+  data: {},
+  color: {}
+})
+
+const {proxy} = getCurrentInstance()
+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);
+  const option = {
+    color: props.color,
+	grid: {
+      bottom: '10%',
+	  right: '20',
+	  left: '10%',
+	  top: '10%',
+	},
+    tooltip: {
+      trigger: 'axis'
+    },
+    xAxis: {
+      type: 'category',
+      data: props.data.map(v => v.name),
+      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
+      }
+    },
+    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
+	  }
+    },
+    series: [
+      {
+        type: 'line',
+        data: props.data,
+        symbol: 'circle',
+        areaStyle: {
+          color: {
+            type: 'linear',
+            x: 0,
+            y: 0,
+            x2: 0,
+            y2: 1,
+            colorStops: [{
+              offset: 0, color: props.color[0] // 0% 处的颜色
+            }, {
+              offset: 1, color: props.color[1] // 100% 处的颜色
+            }],
+            global: false // 缺省为 false
+          }
+        },
+      }
+    ]
+  }
+  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>

+ 106 - 0
snowy-admin-web/src/views/gsc/pass-statistic/chart/pie.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="chart-main" ref="ref_main">
+    <div class="chart-ref" ref="ref_chart"/>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {getCurrentInstance, reactive, ref, onMounted, watch, nextTick} from "vue";
+import * as echarts from 'echarts';
+
+const props = defineProps({
+  data: {},
+  color: {},
+  right: {},
+  type: {}
+})
+
+const {proxy} = getCurrentInstance()
+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);
+  const option = {
+    color: props.color,
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'vertical',
+      right: 'right',
+      top: 'center',
+      icon: 'circle',
+      textStyle: {
+        color: '#ffffff'
+      }
+    },
+    series: [
+      {
+        center: props.type == 2 ? ['40%', '50%'] : '50%',
+        type: 'pie',
+        radius: ['40%', '70%'],
+        avoidLabelOverlap: false,
+        padAngle: 2,
+        itemStyle: {
+          borderRadius: 0
+        },
+        label: {
+          color: 'inherit',
+          formatter: (p) => {
+            let str = ''
+            str += p.name + '\n'
+            str += p.value + '人' + '\n'
+            str += p.percent + '%'
+            return str
+            console.log(p)
+          }
+        },
+        labelLine: {
+          length: 10,
+          length2: props.type == 2 ? 30 : 80,
+        },
+        emphasis: {},
+        data: props.data
+      }
+    ]
+  }
+  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>

+ 145 - 0
snowy-admin-web/src/views/gsc/pass-statistic/chart/pie2.vue

@@ -0,0 +1,145 @@
+<template>
+  <div class="chart-main" ref="ref_main">
+    <div class="chart-ref" ref="ref_chart"/>
+    <div class="bottom">
+      {{ props.data?.[props.index]?.name }}
+      <img src="./bottom.png"/>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {getCurrentInstance, reactive, ref, onMounted, watch, nextTick} from "vue";
+import * as echarts from 'echarts';
+
+const props = defineProps({
+  data: {},
+  color: {},
+  index: {},
+})
+
+const {proxy} = getCurrentInstance()
+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);
+  if (props.data.length > 0) {
+    const conf = {
+      startAngle: -145,
+      endAngle: -35,
+      label: {
+        show: false
+      },
+      emphasis: {disabled: true},
+    }
+    const option = {
+      series: [
+        {
+          type: 'pie',
+          radius: ['60%', '70%'],
+          data: [
+            {
+              name: props.data[Math.abs(0 - props.index)].name,
+              value: props.data[Math.abs(0 - props.index)].value,
+              itemStyle: {
+                color: {
+                  type: 'linear',
+                  x: 0,
+                  y: 0.5,
+                  x2: 0.5,
+                  y2: 0.5,
+                  colorStops: [{
+                    offset: 0, color: props.color[0] // 0% 处的颜色
+                  }, {
+                    offset: 1, color: props.color[1] // 100% 处的颜色
+                  }],
+                  global: false // 缺省为 false
+                }
+              },
+              label: {
+                show: true,
+                position: 'center',
+                formatter: '{c}%',
+                fontSize: 28,
+                color: 'rgba(0, 255, 255, 1)'
+              }
+            },
+            {
+              name: props.data[Math.abs(1 - props.index)].name,
+              value: props.data[Math.abs(1 - props.index)].value,
+              itemStyle: {
+                color: '#4A5373'
+              }
+            },
+          ],
+          ...conf
+        },
+        {
+          type: 'pie',
+          radius: ['50%', '60%'],
+          data: props.data,
+          itemStyle: {
+            color: '#364064'
+          },
+          ...conf
+        }
+      ]
+    }
+    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%;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .chart-ref {
+    width: 100%;
+    height: 100%;
+  }
+  .bottom {
+    position: absolute;
+    color: #ffffff;
+    bottom: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 20px;
+    >img {
+      position: absolute;
+      z-index: 1;
+      top: 10px;
+    }
+  }
+}
+</style>

BIN
snowy-admin-web/src/views/gsc/pass-statistic/dou.png


BIN
snowy-admin-web/src/views/gsc/pass-statistic/feiji.png


+ 323 - 3
snowy-admin-web/src/views/gsc/pass-statistic/index.vue

@@ -1,16 +1,336 @@
 <template>
-  统计
+  <div class="statistic">
+    <div class="statistic-tr">
+      <div class="statistic-block">
+        <div class="title">离岛旅客总量统计
+          <a-date-picker v-model:value="state.date1"/>
+        </div>
+        <div class="content content-1">
+          <div class="left">
+            <div class="total">
+              <div class="card">{{ total1[0] }}</div>
+              <div class="card">{{ total1[1] }}</div>
+              <div class="dou"></div>
+              <div class="card">{{ total1[2] }}</div>
+              <div class="card">{{ total1[3] }}</div>
+              <div class="card">{{ total1[4] }}</div>
+              <div class="dou"></div>
+              <div class="card">{{ total1[5] }}</div>
+              <div class="card">{{ total1[6] }}</div>
+              <div class="card">{{ total1[7] }}</div>
+              <div class="unit">/离岛旅客量</div>
+            </div>
+            <pieChart :data="state.arr1" :color="state.color1"/>
+          </div>
+          <div class="line"/>
+          <div class="right">
+            <template v-for="(item, index) in state.arr1">
+              <div class="right-item">
+                <div class="value" :style="{color: state.color1[index]}">{{ item.value }}</div>
+                <div class="name">{{ item.name }}</div>
+              </div>
+            </template>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="statistic-tr">
+      <div class="statistic-block">
+        <div class="title">旅客日流量分析
+          <a-date-picker v-model:value="state.date2"/>
+        </div>
+        <div class="content">
+          <lineChart :data="state.arr2" :color="state.color2"/>
+        </div>
+      </div>
+      <div class="statistic-block">
+        <div class="title">旅客月流量分析
+          <a-date-picker v-model:value="state.date3" picker="month"/>
+        </div>
+		<div class="content">
+			<lineChart :data="state.arr3" :color="state.color3"/>
+		</div>
+      </div>
+    </div>
+    <div class="statistic-tr">
+      <div class="statistic-block">
+        <div class="title">查验通过率分析
+          <a-date-picker v-model:value="state.date4"/>
+        </div>
+		  <div class="content">
+			  <pie2Chart :data="state.arr4" :color="state.color4" :index="0"/>
+		  </div>
+      </div>
+      <div class="statistic-block">
+        <div class="title">平均查验时间分析
+          <a-date-picker v-model:value="state.date5"/>
+        </div>
+      </div>
+    </div>
+    <div class="statistic-tr">
+      <div class="statistic-block">
+        <div class="title">拦截成功率分析
+          <a-date-picker v-model:value="state.date6"/>
+        </div>
+      </div>
+      <div class="statistic-block">
+        <div class="title">风险趋势分析
+          <a-date-picker v-model:value="state.date7"/>
+        </div>
+        <div class="content">
+          <pieChart :data="state.arr7" :color="state.color7" :type="2"/>
+        </div>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup name="demo2">
 import basicApi from '@/api/gsc/basic'
+import pieChart from './chart/pie.vue'
+import lineChart from './chart/line.vue'
+import pie2Chart from './chart/pie2.vue'
 
 const {proxy} = getCurrentInstance()
-
+const state = reactive({
+  date1: '',
+  date2: '',
+  date3: '',
+  date4: '',
+  date5: '',
+  date6: '',
+  date7: '',
+  arr1: [],
+  arr2: [],
+  arr3: [],
+  arr4: [],
+  arr5: [],
+  arr6: [],
+  arr7: [],
+  color1: ['#16C843', '#00EAFF', '#FB9A04', '#00A2FF', '#5AD8A6', '#F4FF78'],
+  color2: ['rgba(1, 255, 246, 1)', 'rgba(1, 255, 246, 0)'],
+  color3: ['rgba(14, 156, 255, 1)', 'rgba(14, 156, 255, 0)'],
+  color4: ['rgba(0, 194, 255, 1)', 'rgba(255, 0, 61, 1)'],
+  color7: ['#16C843', '#00EAFF', '#FB9A04', '#00A2FF', '#9966FF', '#FC6767', '#ABB2B4'],
+})
 const initData = () => {
-  basicApi.passengerChart({type: 1})
+  basicApi.passengerstatisticsList({type: 1}).then(res => {
+    state.arr1 = res
+  })
+  basicApi.passengerstatisticsList({type: 2}).then(res => {
+    state.arr2 = res
+  })
+  basicApi.passengerstatisticsList({type: 3}).then(res => {
+    state.arr3 = res
+  })
+  basicApi.passengerstatisticsList({type: 4}).then(res => {
+    state.arr4 = res
+  })
+  basicApi.passengerstatisticsList({type: 5}).then(res => {
+    state.arr5 = res
+  })
+  basicApi.passengerstatisticsList({type: 6}).then(res => {
+    state.arr6 = res
+  })
+  basicApi.passengerstatisticsList({type: 7}).then(res => {
+    state.arr7 = res
+  })
 }
+const total1 = computed(() => {
+  let total = 0
+  state.arr1.forEach(v => {
+    total += Number(v.value)
+  })
+  const str = String(total).split('')
+  for (let i = 0; i < (9 - str.length); i++) {
+    str.unshift('0')
+  }
+  return str
+})
 onMounted(() => {
   initData()
 })
 </script>
+
+<style lang="less" scoped>
+.statistic {
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+
+  .statistic-tr {
+    background: #14204A;
+    border-radius: 8px;
+    padding: 24px;
+    display: flex;
+    gap: 24px;
+
+    .statistic-block {
+      flex: 1;
+      box-shadow: inset 0px 0px 100px 0px rgba(0, 228, 255, 0.12);
+      border-radius: 0px 0px 0px 0px;
+      border: 2px solid rgba(0, 174, 255, 0.33);
+
+      .title {
+        height: 46px;
+        line-height: 46px;
+        padding: 0 16px;
+        background: linear-gradient(90deg, rgba(31, 180, 255, 0.12) 0%, rgba(31, 180, 255, 0) 100%);
+        color: #ffffff;
+        font-size: 20px;
+        font-weight: bold;
+        display: flex;
+        align-items: center;
+
+        :deep(.ant-picker) {
+          margin-left: auto;
+          height: 30px;
+          background: linear-gradient(180deg, rgba(31, 180, 255, 0.32) 0%, rgba(31, 180, 255, 0) 100%);
+          border-radius: 0;
+          border-top: 1px solid #1FC6FF;
+
+          .ant-picker-input {
+            > input {
+              color: #1DC8FF;
+
+              &::placeholder {
+                color: #1DC8FF;
+              }
+            }
+
+            .ant-picker-suffix {
+              > span {
+                color: #1DC8FF;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+.content {
+  height: 300px;
+  width: 100%;
+
+  &.content-1 {
+    height: 290px;
+    padding-top: 24px;
+    display: flex;
+    gap: 40px;
+
+    .left {
+      width: 595px;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+
+      .total {
+        height: 79px;
+        width: 100%;
+        background: rgba(0, 174, 255, 0.05);
+        border-radius: 0px 0px 0px 0px;
+        border: 2px solid rgba(0, 174, 255, 0.33);
+        display: flex;
+        align-items: center;
+        padding-left: 10px;
+
+        .card {
+          background-image: url("./card.png");
+          background-size: 42px 56px;
+          width: 43px;
+          height: 56px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          font-family: Bebas;
+          font-weight: 400;
+          font-size: 38px;
+          color: #FFFFFF;
+        }
+
+        .dou {
+          background-image: url("./dou.png");
+          width: 12px;
+          height: 16px;
+          margin-top: 40px;
+        }
+
+        .unit {
+          font-weight: 500;
+          font-size: 18px;
+          color: #68C3FF;
+        }
+
+        > * {
+          margin-left: 8px;
+        }
+      }
+    }
+
+    .line {
+      width: 1px;
+      height: calc(100% - 16px);
+      background-color: rgba(18, 104, 155, 0.4);
+    }
+
+    .right {
+      flex: 1;
+      height: 100%;
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      grid-template-rows: repeat(auto-fill, 113px);
+      gap: 24px 16px;
+      padding-right: 16px;
+
+      .right-item {
+        box-shadow: inset 0px 0px 5px 2px #0CBDAF;
+        display: flex;
+        flex-direction: column;
+        padding-left: 13px;
+        background-color: rgba(31, 180, 255, 0.12);
+        font-family: "PingFang SC";
+        position: relative;
+
+        &:after {
+          z-index: 1;
+          content: '';
+          position: absolute;
+          width: 94px;
+          height: 75px;
+          background-image: url('./feiji.png');
+          top: 18px;
+          right: 22px;
+        }
+
+        .value {
+          z-index: 2;
+          font-weight: 500;
+          font-size: 40px;
+          line-height: 60px;
+          margin-top: 13px;
+        }
+
+        .name {
+          z-index: 2;
+          font-weight: 400;
+          font-size: 16px;
+          color: rgba(255, 255, 255, 0.6);
+        }
+
+        &:nth-child(n+4) {
+          &:after {
+            width: 84px;
+            height: 84px;
+            background-image: url('./port.png');
+          }
+        }
+      }
+    }
+  }
+}
+</style>

BIN
snowy-admin-web/src/views/gsc/pass-statistic/port.png