任一存 преди 1 година
родител
ревизия
37f7d412e0
променени са 6 файла, в които са добавени 589 реда и са изтрити 4 реда
  1. 2 0
      .vscode/settings.json
  2. 1 0
      package.json
  3. 208 2
      src/pages/A9board/index.module.scss
  4. 350 2
      src/pages/A9board/index.tsx
  5. 8 0
      src/store/action/A9board.ts
  6. 20 0
      yarn.lock

+ 2 - 0
.vscode/settings.json

@@ -0,0 +1,2 @@
+{
+}

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "braft-editor": "^2.3.9",
     "braft-utils": "^3.0.12",
     "dayjs": "^1.11.7",
+    "echarts": "^5.5.0",
     "js-base64": "^3.7.3",
     "js-export-excel": "^1.1.4",
     "react": "^18.2.0",

+ 208 - 2
src/pages/A9board/index.module.scss

@@ -1,7 +1,213 @@
+.ChartTitle {
+  :global{
+    flex: 0 0 auto;
+    display: flex;
+    align-items: center;
+    .decorator{
+      width: 8px;
+      height: 22px;
+      background-color: #b5b5b5;
+      border-radius: 3px;
+      margin-right: 10px;
+    }
+    h3.chart-title{
+      font-size: 18px;
+      font-weight: bold;
+    }
+  }
+}
+
+.DataBar{
+  :global{
+    position: relative;
+    width: 100%;
+    height: 40px;
+    .bar-left{
+      position: absolute;
+      left: 0;
+      height: 100%;
+      background: #ececec;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-left: 10px;
+      padding-right: 20px;
+      overflow: hidden;
+      white-space: pre;
+      text-overflow: ellipsis;
+    }
+    .bar-right{
+      position: absolute;
+      right: 0;
+      height: 100%;
+      background: #ececec;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-left: 20px;
+      padding-right: 10px;
+    }
+    .name{
+      overflow: hidden;
+      white-space: pre;
+      text-overflow: ellipsis;
+    }
+  }
+}
+
+.DataList{
+  :global {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-evenly;
+    li{
+      width: 100%;
+      display: flex;
+      justify-content: center;
+      gap: 5%;
+      .key{
+        flex: 0 0 auto;
+        width: 55%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: pre;
+      }
+      .value{
+        flex: 0 0 auto;
+        width: 40%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: pre;
+      }
+    }
+  }
+}
+
+.BinChart{
+  :global{
+    display: flex;
+    flex-direction: column;
+    justify-content: space-evenly;
+    height: 100%;
+    .bin-track{
+      height: 14px;
+      border-radius: 7px;
+      background-color: #f0f0f0;
+      overflow: hidden;
+      .bin{
+        height: 100%;
+        border-radius: inherit;
+        background-color: #59759f;
+      }
+    }
+  }
+}
+
 .A9board{
   :global{
-    .aa{
-      color: red;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 10px;
+    p.update-time{
+      flex: 0 0 auto;
+    }
+    .chart-card {
+      background-color: #fff;
+      border-radius: 15px;
+      padding: 25px;
+      display: flex;
+      flex-direction: column;
+      gap: 25px;
+    }
+    .chart-area{
+      $design-page-width: 1256px;
+      $design-page-height: 680px;
+      flex: 1 0 1px;
+      width: 100%;
+      display: flex;
+      justify-content: space-between;
+      .column-1{
+        flex: 0 0 auto;
+        width: calc(660px / $design-page-width * 100%);
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        .total-visit{
+          flex: 0 0 auto;
+          height: calc(499px / $design-page-height * 100%);
+          .chart-1{
+            flex: 1 1 auto;
+            display: flex;
+            max-height: calc(100% - 22px - 100px);
+            .pie-chart{
+              width: 60%;
+              display: flex;
+              justify-content: center;
+            }
+            .data-list{
+              width: 40%;
+            }
+          }
+          .chart-2{
+            flex: 0 0 auto;
+          }
+        }
+        .total-visitor{
+          flex: 0 0 auto;
+          height: calc(169px / $design-page-height * 100%);
+          display: flex;
+          flex-direction: column;
+          .chart-area{
+            flex: 1 0 auto;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+          }
+        }
+      }
+      .column-2{
+        flex: 0 0 auto;
+        width: calc(587px / $design-page-width * 100%);
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        .hot-remain{
+          flex: 0 0 auto;
+          height: calc(377px / $design-page-height * 100%);
+          .chart-1{
+            flex: 1 0 auto;
+            display: flex;
+            justify-content: space-between;
+            .data-list{
+              flex: 0 0 atuo;
+              width: 40%;
+            }
+            .bin-chart{
+              flex: 0 0 atuo;
+              width: 55%;
+            }
+          }
+        }
+        .love-coin{
+          flex: 0 0 auto;
+          height: calc(292px / $design-page-height * 100%);
+          .chart-1{
+            flex: 1 0 auto;
+            display: flex;
+            .pie-chart{
+              width: 60%;
+              display: flex;
+              justify-content: center;
+            }
+            .data-list{
+              width: 40%;
+            }
+          }
+        }
+      }
     }
   }
 }

+ 350 - 2
src/pages/A9board/index.tsx

@@ -1,10 +1,358 @@
-import React from "react";
+import React, {useEffect, useRef, useState} from "react";
 import styles from "./index.module.scss";
+import * as echarts from "echarts";
+import { A9_APIgetData } from "@/store/action/A9board";
+
+function ChartTitle({text} : {text: string}) {
+  return (
+    <div className={styles.ChartTitle}>
+      <div className="decorator"></div>
+      <h3 className="chart-title">{text}</h3>
+    </div>
+  )
+}
+
+function DataBar({keyName1, value1, keyName2, value2}: {
+  keyName1: string,
+  value1: number,
+  keyName2: string,
+  value2: number,
+}) {
+  if ((value1 + value2) === 0) {
+    return null
+  }
+
+  const v1Rate = value1 / (value1 + value2)
+  const v2Rate = value2 / (value1 + value2)
+  const bar1Width = `calc(${v1Rate * 100}% + 10px - 4px)`
+  const bar2Width = `calc(${v2Rate * 100}% + 10px - 4px)`
+
+  return (
+    <div className={styles.DataBar}>
+      <div className="bar-left" style={{
+          width: bar1Width,
+          clipPath: `polygon(0 0, calc(100% - (10px - 4px) - 10px) 0, 100% 100%, 0 100%)`,
+        }}
+      >
+        <div className="name" title={keyName1}>{keyName1}</div>
+        <div className="value">{(v1Rate * 100).toFixed(2) }%</div>
+      </div>
+      <div className="bar-right" style={{
+          width: bar2Width,
+          clipPath: `polygon( 0 0, 100% 0, 100% 100%, calc(10px - 4px + 10px) 100%)`,
+        }}
+      >
+        <div className="value">{(v2Rate * 100).toFixed()}%</div>
+        <div className="name" title={keyName2}>{keyName2}</div>
+      </div>
+    </div>
+  )
+}
+
+function DataList({className, list, reverseOrder}: {
+  className?: string,
+  list: {
+    key: string,
+    value: number
+  }[],
+  reverseOrder?: boolean,
+}) {
+  return (
+    <ul className={`${styles.DataList} ${className}`}>
+      {list.map((item: {
+        key: string,
+        value: number,
+      }) => {
+        if (reverseOrder) {
+          return (
+            <li key={item.key}>
+              <div
+                className="value"
+                style={{
+                  textAlign: 'right',
+                }}
+              >
+                {item.value}
+              </div>
+              <div className="key">{item.key}</div>
+            </li>
+          )
+        } else {
+          return (
+            <li key={item.key}>
+              <div className="key"
+                style={{
+                  textAlign: 'right',
+                }}
+              >
+                {item.key}
+              </div>
+              <div className="value">{item.value}</div>
+            </li>
+          )
+        }
+      })}
+    </ul>
+  )
+}
+
+function BinChart({className, list}: {
+  className?: string,
+  list: {
+    key: string,
+    value: number
+  }[],
+}) {
+  let maxValue = 0
+  for (const item of list) {
+    if (item.value > maxValue) {
+      maxValue = item.value
+    }
+  }
+  let trackValue = 0
+  if (maxValue === 0) {
+    trackValue = 1
+  } else {
+    trackValue = maxValue * 1.2
+  }
+
+  return (
+    <div className={`${styles.BinChart} ${className}`}>
+      {list.map((item: {
+        key: string,
+        value: number,
+      }) => {
+        return (
+          <div className="bin-track" key={item.key}>
+            <div className="bin" style={{
+              width: `${item.value / trackValue * 100}%`
+            }}></div>
+          </div>
+        )
+      })}
+    </div>
+  )
+}
+
 function A9board() {
+  const chartRefTotalVisit = useRef(null);
+  const chartRefLoveCoin = useRef(null);
+
+  const [pageVisitData, setPageVisitData] = useState([])
+  const [totalVisitData, setTotalVisitData] = useState(0)
+  const [browserVisitData, setBrowserVisitData] = useState({
+    keyName1: '小程序访客',
+    value1: 0,
+    keyName2: '网页端访客',
+    value2: 0,
+  })
+  const [totalVisitorData, setTotalVisitorData] = useState({
+    keyName1: '注册访客',
+    value1: 0,
+    keyName2: '非注册访客',
+    value2: 0,
+  })
+  const [totaltotalVisitorData, setTotaltotalVisitorData] = useState(0)
+  const [hotRelicData, setHotRelicData] = useState<{
+    key: string,
+    value: number,
+  }[]>([])
+  const [loveCoinData, setLoveCoinData] = useState<{
+    key: string,
+    value: number,
+  }[]>([])
+  const [totalLoveCoinData, setTotalLoveCoinData] = useState(0)
+
+  useEffect(() => {
+    A9_APIgetData().then((res) => {
+      console.log('sdfsdf', res);
+
+      let totalVisitNum = 0
+      setPageVisitData(res.data['总访问量'].map((item: {
+        groupKey: string,
+        pcsPc: number,
+        pcsWx: number,
+      }) => {
+        totalVisitNum += (item.pcsPc + item.pcsWx)
+        return {
+          key: item.groupKey,
+          value: item.pcsPc + item.pcsWx,
+        }
+      }))
+      setTotalVisitData(totalVisitNum)
+
+
+      let wxVisitNum = 0
+      let pcVisitNum = 0
+      for (const iterator of res.data['总访问量']) {
+        wxVisitNum += iterator.pcsWx
+        pcVisitNum += iterator.pcsPc
+      }
+      setBrowserVisitData({
+        keyName1: '小程序访客',
+        value1: wxVisitNum,
+        keyName2: '网页端访客',
+        value2: pcVisitNum,
+      })
+
+      setTotalVisitorData({
+        keyName1: '注册访客',
+        value1: res.data['总访客数'].pcsWx, // 后端字段命名如此,并非错误
+        keyName2: '非注册访客',
+        value2: res.data['总访客数'].pcsPc, // 后端字段命名如此,并非错误
+      })
+      setTotaltotalVisitorData(res.data['总访客数'].pcsWx + res.data['总访客数'].pcsPc)
+
+      const hotRelicDataTemp: {
+        key: string,
+        value: number,
+      }[] = []
+      for (const key in res.data['热门遗存']) {
+        if (Object.prototype.hasOwnProperty.call(res.data['热门遗存'], key)) {
+          const element = res.data['热门遗存'][key];
+          hotRelicDataTemp.push({
+            key,
+            value: element,
+          })
+        }
+      }
+      setHotRelicData(hotRelicDataTemp)
+
+      const loveCoinDataTemp: {
+        key: string,
+        value: number,
+      }[] = []
+      let totalLoveCoinDataTemp = 0
+      for (const key in res.data['爱心币奖励']) {
+        if (Object.prototype.hasOwnProperty.call(res.data['爱心币奖励'], key)) {
+          const element = res.data['爱心币奖励'][key];
+          loveCoinDataTemp.push({
+            key,
+            value: element,
+          })
+          totalLoveCoinDataTemp += element
+        }
+      }
+      setLoveCoinData(loveCoinDataTemp)
+      setTotalLoveCoinData(totalLoveCoinDataTemp)
+    })
+  }, [])
+
+  useEffect(() => {
+      const chartTotalVisitOption = {
+        tooltip: {
+          trigger: 'item'
+        },
+        series: [
+          {
+            name: 'Access From',
+            type: 'pie',
+            radius: '60%',
+            data: pageVisitData.map((item: {
+              key: string,
+              value: number,
+            }) => {
+              return {
+                name: item.key,
+                value: item.value
+              }
+            }),
+            emphasis: {
+              itemStyle: {
+                shadowBlur: 10,
+                shadowOffsetX: 0,
+                shadowColor: 'rgba(0, 0, 0, 0.5)'
+              }
+            }
+          }
+        ]
+      }
+  
+      echarts.init(chartRefTotalVisit.current).setOption(chartTotalVisitOption);
+  }, [pageVisitData])
+  
+  useEffect(() => {
+    const chartLoveCoinOption = {
+      tooltip: {
+        trigger: 'item'
+      },
+      series: [
+        {
+          name: 'Access From',
+          type: 'pie',
+          radius: '60%',
+          data: loveCoinData.map(item => {
+            return {
+              name: item.key,
+              value: item.value,
+            }
+          }),
+          emphasis: {
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)'
+            }
+          }
+        }
+      ]
+    }
+  
+    echarts.init(chartRefLoveCoin.current).setOption(chartLoveCoinOption);
+  }, [loveCoinData])
+
   return (
     <div className={styles.A9board}>
       <div className="pageTitle">数据看板</div>
-      <h1>等待开发</h1>
+      <p className="update-time">统计数据更新于 2024-04-06 6:00</p>
+      <div className="chart-area">
+        <div className="column-1">
+          <div className="total-visit chart-card">
+            <ChartTitle text={`总访问量:${totalVisitData}`}></ChartTitle>
+            <div className="chart-1">
+              <div className="pie-chart" ref={chartRefTotalVisit}></div>
+              <DataList
+                className="data-list"
+                list={pageVisitData}
+              ></DataList>
+            </div>
+            <div className="chart-2">
+              <DataBar keyName1={browserVisitData.keyName1} value1={browserVisitData.value1} keyName2={browserVisitData.keyName2} value2={browserVisitData.value2}></DataBar>
+            </div>
+          </div>
+          <div className="total-visitor chart-card">
+            <ChartTitle text={`总访客数:${totaltotalVisitorData}`}></ChartTitle>
+            <div className="chart-area">
+              <DataBar keyName1={'注册访客'} value1={1} keyName2={'非注册访客'} value2={2}></DataBar>
+            </div>
+          </div>
+        </div>
+        <div className="column-2">
+          <div className="hot-remain chart-card">
+            <ChartTitle text={'热门遗存'}></ChartTitle>
+            <div className="chart-1">
+              <DataList
+                className="data-list"
+                list={hotRelicData}
+                reverseOrder={true}
+              ></DataList>
+              <div className="bin-chart">
+                <BinChart list={hotRelicData}></BinChart>
+              </div>
+            </div>
+          </div>
+          <div className="love-coin chart-card">
+            <ChartTitle text={`爱心币奖励:${totalLoveCoinData}`}></ChartTitle>
+            <div className="chart-1">
+              <div className="pie-chart" ref={chartRefLoveCoin}></div>
+              <DataList
+                className="data-list"
+                list={loveCoinData}
+              ></DataList>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
   );
 }

+ 8 - 0
src/store/action/A9board.ts

@@ -0,0 +1,8 @@
+import http from "@/utils/http";
+
+/**
+ * 获取统计数据
+ */
+export const A9_APIgetData = () => {
+  return http.get("cms/visit/report");
+};

+ 20 - 0
yarn.lock

@@ -4500,6 +4500,14 @@ eastasianwidth@^0.2.0:
   resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
   integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
 
+echarts@^5.5.0:
+  version "5.5.0"
+  resolved "https://registry.npmmirror.com/echarts/-/echarts-5.5.0.tgz#c13945a7f3acdd67c134d8a9ac67e917830113ac"
+  integrity sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==
+  dependencies:
+    tslib "2.3.0"
+    zrender "5.5.0"
+
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -10285,6 +10293,11 @@ tsconfig-paths@^3.15.0:
     minimist "^1.2.6"
     strip-bom "^3.0.0"
 
+tslib@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
+  integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
+
 tslib@^1.8.1:
   version "1.14.1"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@@ -11131,3 +11144,10 @@ yocto-queue@^0.1.0:
   version "0.1.0"
   resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
   integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+zrender@5.5.0:
+  version "5.5.0"
+  resolved "https://registry.npmmirror.com/zrender/-/zrender-5.5.0.tgz#54d0d6c4eda81a96d9f60a9cd74dc48ea026bc1e"
+  integrity sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==
+  dependencies:
+    tslib "2.3.0"