Ver código fonte

Merge remote-tracking branch 'origin/master'

sunshihao 6 dias atrás
pai
commit
3f9a882324

+ 1 - 1
xinkeaboard-admin/src/pages/statistics/bigscreen/components/DistributorEnquireLocation.js

@@ -16,7 +16,7 @@ const DistributorEnquire = ({ data, loading, dispatch }) => {
     <Spin spinning={loading}>
       <div className={styles.common}>
         <div className={styles.common_header}>
-          <PanelNav title="供应链企业地区分布" />
+          <PanelNav title="分销商地区分布" />
         </div>
         <div className={styles.common_content}>
           <EnquirePieChart data={parsedata(data)} />

+ 2 - 0
xinkeaboard-admin/src/pages/statistics/bigscreen/components/HeadContent.js

@@ -9,6 +9,8 @@ const HeaderContent = ({ updateTime }) => {
       <div className={styles.headerContentTitle}>
         南京江北新区跨境出海服务平台
       </div>
+      <div className={styles.leftMoveLine}></div>
+      <div className={styles.rightMoveLine}></div>
       <div className={styles.updateTime}>
         <span>更新时间: </span>
         <span>{updateTime}</span>

+ 2 - 2
xinkeaboard-admin/src/pages/statistics/bigscreen/components/OverseasEnquireLocation.js

@@ -14,13 +14,13 @@ const OverSeasEnquire = ({ data, loading, currentSite }) => {
     }));
   };
 
-  const title = currentSite === "1" ? "海外" : "分销商";
+  // const title = currentSite === "1" ? "海外" : "分销商";
 
   return (
     <Spin spinning={loading}>
       <div className={styles.common}>
         <div className={styles.common_header}>
-          <PanelNav title={title + "询盘地区分布"} />
+          <PanelNav title='询盘地区分布' />
         </div>
         <div className={styles.common_content}>
           <EnquirePieChart data={parsedata(data)} />

+ 9 - 3
xinkeaboard-admin/src/pages/statistics/bigscreen/components/PanelBlock.js

@@ -13,7 +13,8 @@ class PanelBlock extends React.Component {
       justifyContent,
       marginBottom,
       marginRight,
-      styleObj
+      styleObj,
+      isRotateFade,
     } = this.props;
 
     const style = {
@@ -24,11 +25,16 @@ class PanelBlock extends React.Component {
       justifyContent: justifyContent ?? "center",
       marginBottom: marginBottom ?? "0",
       marginRight: marginRight ?? "0",
-      ...styleObj
+      ...styleObj,
     };
 
     return (
-      <div className={`${styles.panel} ${styles.block}`} style={style}>
+      <div
+        className={`${styles.panel} ${
+          isRotateFade ? styles.blockRotateFade : styles.block
+        }`}
+        style={style}
+      >
         {title && <PanelNav title={title} />}
         {children}
       </div>

+ 98 - 51
xinkeaboard-admin/src/pages/statistics/bigscreen/components/WorldMap.js

@@ -3,24 +3,24 @@ import * as echarts from "echarts";
 import worldJson from "../world.json";
 import topIcon from "../../../../assets/bigscreen/map-bar-head.svg";
 
-
 class WorldMap2D extends React.Component {
   constructor(props) {
     super(props);
     this.chartRef = React.createRef();
     this.chartInstance = null;
+    this.pulseTimer = null;
   }
 
   componentDidMount() {
-    echarts.registerMap("world", worldJson); // 注册地图
+    echarts.registerMap("world", worldJson);
     this.initChart();
     window.addEventListener("resize", this.resizeChart);
+    this.startPulseAnimation();
   }
 
   componentWillUnmount() {
-    if (this.chartInstance) {
-      this.chartInstance.dispose();
-    }
+    if (this.chartInstance) this.chartInstance.dispose();
+    if (this.pulseTimer) clearInterval(this.pulseTimer);
     window.removeEventListener("resize", this.resizeChart);
   }
 
@@ -29,71 +29,72 @@ class WorldMap2D extends React.Component {
   }
 
   resizeChart = () => {
-    if (this.chartInstance) {
-      this.chartInstance.resize();
-    }
+    if (this.chartInstance) this.chartInstance.resize();
   };
 
   initChart() {
     if (!this.chartRef.current) return;
     this.chartInstance = echarts.init(this.chartRef.current);
-    // 顶部图标数据,计算 y 偏移(柱子高度的一半)
-    const topIconData = this.props.data.map((d) => {
-      return {
-        name: d.name,
-        value: [d.value[0], d.value[1], d.value[2]], // 位置
-        symbolOffset: [0, -d.value[2] / 2 * 50 ], // Y轴偏移(柱子高度一半 + 额外10像素)
-      };
-    });
-    const option = {
+
+    // 顶部图标数据
+    this.topIconData = this.props.data.map((d) => ({
+      name: d.name,
+      value: [d.value[0], d.value[1], d.value[2]],
+      symbolOffset: [0, (-d.value[2] / 2) * 50],
+    }));
+
+    // 底部光圈初始化
+    this.pulseData = this.props.data.map((d) => ({
+      name: d.name,
+      value: [d.value[0], d.value[1], 0],
+    }));
+
+    // 初始图表
+    const option = this.getChartOption();
+    this.chartInstance.setOption(option);
+  }
+
+  // 获取图表配置
+  getChartOption(currentPulseIndex = -1) {
+    // 当前显示光圈的柱子
+    const pulseSeriesData =
+      currentPulseIndex >= 0 ? [this.pulseData[currentPulseIndex]] : [];
+
+    return {
       tooltip: {
-        formatter: (params) => {
-          return `${params.name} <br/>${params.value[2]}`;
-        },
-        textStyle: {
-          fontSize: 30,
-          fontWeight: "bold",
-        },
+        // formatter: (params) => `${params.name} <br/>${params.value[2]}`,
+        textStyle: { fontSize: 30, fontWeight: "bold" },
       },
       geo: {
         map: "world",
-        roam: true, // 支持缩放拖拽
+        roam: true,
         zoom: 1.1,
         itemStyle: {
           areaColor: "rgba(126, 206, 244, 0.1)",
           borderColor: "#2EA7E0",
           borderWidth: 2,
         },
-        label: {
-          show: false, // ⚡ 确保不显示文字
-          color: "#fff", // 如果 show: true 时设置文字颜色
-          fontSize: 12
-        },
+        label: { show: false },
         emphasis: {
           itemStyle: {
-            areaColor: "rgba(41, 241, 250, 0.6)", // 悬浮时高亮色
+            areaColor: "rgba(41, 241, 250, 0.6)",
             borderWidth: 1,
-            borderColor: 'rgba(41, 241, 250, 1)',
-            shadowColor: "rgba(41, 241, 250, 1)",  // 阴影颜色
-            // shadowColor: '#fff',
-            shadowBlur: 2,  
-            shadowOffsetX: 10,               // X 偏移
-            shadowOffsetY: -10,     
+            borderColor: "rgba(41, 241, 250, 1)",
+            shadowColor: "rgba(41, 241, 250, 1)",
+            shadowBlur: 2,
+            shadowOffsetX: 10,
+            shadowOffsetY: -10,
           },
-          label: {
-            show: false // 悬浮时也不显示文字
-          }
+          label: { show: false },
         },
       },
       series: [
+        // 主柱子
         {
           type: "scatter",
           coordinateSystem: "geo",
-          symbol: "rect", // 用矩形柱代替点
-          symbolSize: (val) => {
-            const height = val[2] * 50;
-            return [28, height];
-          },
+          symbol: "rect",
+          symbolSize: (val) => [28, val[2] * 50],
           itemStyle: {
             color: {
               type: "linear",
@@ -110,20 +111,66 @@ class WorldMap2D extends React.Component {
             shadowBlur: 10,
             borderRadius: [8, 8, 0, 0],
           },
-          encode: { tooltip: 2 }, // 提示框显示 value[2]
-          data: this.props.data, // [lng, lat, value]
+          encode: { tooltip: 2 },
+          data: this.props.data,
         },
+
+        // 顶部图标
         {
           type: "scatter",
           coordinateSystem: "geo",
-          symbol: `image://${topIcon}`, // 图片 URL
-          symbolSize: [33, 33], // 图片大小
-          data: topIconData,
+          symbol: `image://${topIcon}`,
+          symbolSize: [33, 33],
+          data: this.topIconData,
+        },
+
+        // 底部光圈
+        {
+          type: "effectScatter",
+          coordinateSystem: "geo",
+          rippleEffect: {
+            period: 5, // 波纹扩散周期
+            scale: 800, // 扩散倍数
+            brushType: "stroke", // 只描边
+          },
+          symbol: "circle",
+          symbolSize: 1, // 小中心点
+          itemStyle: {
+            color: "rgba(76,216,255,0.8)", // 中心点颜色
+            shadowBlur: 0, // 去掉阴影
+            shadowColor: "transparent",
+          },
+          data: pulseSeriesData,
         },
       ],
     };
+  }
 
-    this.chartInstance.setOption(option);
+  // 启动依次扩散动画
+  startPulseAnimation() {
+    const { data } = this.props;
+    clearInterval(this.pulseTimer);
+    this.currentIndex = 0;
+
+    this.pulseTimer = setInterval(() => {
+      if (!this.chartInstance) return;
+
+      const pulseData = [data[this.currentIndex]]; // 当前光圈数据
+
+      // 更新 effectScatter
+      this.chartInstance.setOption({
+        series: [{}, {}, { data: pulseData }],
+      });
+
+      // 自动显示 tooltip
+      this.chartInstance.dispatchAction({
+        type: "showTip",
+        seriesIndex: 0, // 主柱子 series
+        dataIndex: this.currentIndex,
+      });
+
+      this.currentIndex = (this.currentIndex + 1) % data.length;
+    }, 2000);
   }
 
   render() {

+ 23 - 4
xinkeaboard-admin/src/pages/statistics/bigscreen/components/addMemberTrend.js

@@ -1,17 +1,27 @@
 import React from "react";
-import { Spin } from 'antd';
+import { Spin } from "antd";
 import { connect } from "dva";
 import PanelNav from "./PanelNav";
 import BarChart from "./BarChart";
+import RadioButtonGroup from "./RadioButtonGroup";
 import styles from "../styles/common.less";
 
-const AddMemberTrend = ({ data, loading, dispatch }) => {
+const AddMemberTrend = ({ options, current, data, loading, dispatch }) => {
+  const onChange = (val) => {
+    dispatch({
+      type: "bigscreen/setMemberAddTrendData",
+      payload: { current: val },
+    });
+    dispatch({
+      type: "bigscreen/load_add_member_trend",
+    });
+  };
   const parseData = (barData) => {
     const xAxisdata = [];
     const seriesData = [];
 
-    barData.forEach((item) => {
-      xAxisdata.push(item.month);
+    (barData ?? []).forEach((item) => {
+      xAxisdata.push(item.day);
       seriesData.push(item.newMemberNum);
     });
 
@@ -26,6 +36,13 @@ const AddMemberTrend = ({ data, loading, dispatch }) => {
       <div className={styles.common}>
         <div className={styles.common_header}>
           <PanelNav title="新增会员趋势" />
+          <div className={styles.common_header_conditions}>
+            <RadioButtonGroup
+              options={options}
+              label={current}
+              onChange={onChange}
+            />
+          </div>
         </div>
         <div className={styles.common_content}>
           <BarChart {...parseData(data)} />
@@ -38,4 +55,6 @@ const AddMemberTrend = ({ data, loading, dispatch }) => {
 export default connect(({ bigscreen }) => ({
   data: bigscreen.memberAddTrendData.data,
   loading: bigscreen.memberAddTrendData.loading,
+  options: bigscreen.memberAddTrendData.options,
+  current: bigscreen.memberAddTrendData.current,
 }))(AddMemberTrend);

+ 8 - 6
xinkeaboard-admin/src/pages/statistics/bigscreen/index.js

@@ -98,6 +98,7 @@ class BigScreen extends React.Component {
         document.mozFullScreenElement ||
         document.msFullscreenElement
       ),
+      renderKey: Date.now() // 用时间戳保证唯一
     });
   };
 
@@ -143,7 +144,7 @@ class BigScreen extends React.Component {
   };
 
   render() {
-    const { isFullScreen } = this.state;
+    const { isFullScreen, renderKey } = this.state;
 
     return (
       <div className={styles.screenWrapper}>
@@ -158,9 +159,9 @@ class BigScreen extends React.Component {
         <div className={styles.screenContainer}>
           {/* 顶部 */}
           <div className={styles.headPanel}>
-            <HeaderContent />
+            <HeaderContent key={renderKey}/>
           </div>
-          <div className={styles.contentPanel}>
+          <div className={styles.contentPanel} key={renderKey}>
             {/* 左侧 */}
             <div className={styles.contentPanelLeft}>
               <PanelBlock
@@ -223,8 +224,7 @@ class BigScreen extends React.Component {
               <PanelBlock justifyContent="flex-start" height="496px">
                 <OverView />
               </PanelBlock>
-              <PanelBlock>
-              </PanelBlock>
+              <PanelBlock />
               {/* <PanelBlock flexDirection="row" height="608px">
                 <PanelBlock
                   width="822px"
@@ -280,7 +280,9 @@ class BigScreen extends React.Component {
             </div>
 
             <div className={styles.mapContainer}>
-              <MapContainer />
+              <PanelBlock isRotateFade={true}>
+                <MapContainer />
+              </PanelBlock>
             </div>
           </div>
         </div>

+ 36 - 0
xinkeaboard-admin/src/pages/statistics/bigscreen/styles/header_content.less

@@ -20,6 +20,24 @@
     color: #fff;
   }
 
+  .leftMoveLine {
+    position: absolute;
+    top: 126px;
+    width: 200px; /* 线条长度 */
+    height: 4px; /* 线条粗细 */
+    background: #06f7ff;
+    animation: leftMoveLine 2s linear infinite alternate;
+  }
+
+  .rightMoveLine {
+    position: absolute;
+    top: 126px;
+    width: 200px; /* 线条长度 */
+    height: 4px; /* 线条粗细 */
+    background: #06f7ff;
+    animation: rightMoveLine 2s linear infinite alternate;
+  }
+
   .updateTime {
     position: absolute;
     left: 50%;
@@ -44,3 +62,21 @@
     top: 20px;
   }
 }
+
+@keyframes leftMoveLine {
+  0% {
+    left: 0;
+  }
+  100% {
+    left: 820px;
+  } /* 移动到目标 x 位置 */
+}
+
+@keyframes rightMoveLine {
+  0% {
+    right: 0;
+  }
+  100% {
+    right: 820px;
+  } /* 移动到目标 x 位置 */
+}

+ 61 - 3
xinkeaboard-admin/src/pages/statistics/bigscreen/styles/panel_block.less

@@ -6,9 +6,49 @@
   padding: 10px;
 }
 
+.blockRotateFade {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  box-sizing: border-box;
+  background-repeat: no-repeat;
+  background-position: center;
+  background-size: contain;
+  z-index: 2000;
+
+  /* 初始状态缩小+旋转 */
+  transform: scale(0) rotate(-90deg);
+  opacity: 0;
+
+  /* 出场动画 */
+  animation: blockRotateFade 3s ease-out forwards; /* forwards 保持最后状态 */
+}
+
+@keyframes blockRotateFade {
+  0% {
+    transform: scale(0) rotate(-90deg);
+    opacity: 0;
+  }
+  60% {
+    transform: scale(1.15) rotate(15deg);
+    opacity: 1;
+  }
+  80% {
+    transform: scale(0.95) rotate(-5deg);
+    opacity: 1; /* 确保透明度不回退 */
+  }
+  100% {
+    transform: scale(1) rotate(0deg);
+    opacity: 1;
+  }
+}
+
+
 .block {
+  position: relative;
   display: flex;
-  flex-direction: column;
   justify-content: center;
   align-items: center;
   height: 100%;
@@ -17,6 +57,24 @@
   background-position: center;
   background-size: contain;
   z-index: 2000;
-  // margin-bottom: 10px;
-  // border: 4px dashed #fff;
+
+  /* 初始状态缩小 */
+  transform: scale(0);
+  opacity: 0;
+
+  /* 出场动画 */
+  animation: blockWave 1s ease-out forwards;
 }
+
+@keyframes blockWave {
+  0% { transform: scale(0); opacity: 0; }
+  // 60% { transform: scale(1.15); opacity: 1; }
+  80% { transform: scale(0.95); }
+  100% { transform: scale(1); opacity: 1; }
+}
+
+@keyframes waveExpand {
+  0% { width: 0%; }
+  100% { width: 100%; }
+}
+

+ 54 - 3
xinkeaboard-admin/src/pages/statistics/models/bigscreen.js

@@ -86,6 +86,48 @@ export default {
     memberAddTrendData: {
       loading: false,
       data: [],
+      options: [
+        {
+          label: "昨日",
+          value: {
+            startTime:
+              moment()
+                .subtract(1, "days")
+                .format("YYYY-MM-DD") + " 00:00:00",
+            endTime:
+              moment()
+                .subtract(1, "days")
+                .format("YYYY-MM-DD") + " 23:59:59:999",
+          },
+        },
+        {
+          label: "近7日",
+          value: {
+            startTime:
+              moment()
+                .subtract(7, "days")
+                .format("YYYY-MM-DD") + " 00:00:00",
+            endTime:
+              moment()
+                .subtract(1, "days")
+                .format("YYYY-MM-DD") + " 23:59:59:999",
+          },
+        },
+        {
+          label: "近30日",
+          value: {
+            startTime:
+              moment()
+                .subtract(30, "days")
+                .format("YYYY-MM-DD") + " 00:00:00",
+            endTime:
+              moment()
+                .subtract(1, "days")
+                .format("YYYY-MM-DD") + " 23:59:59:999",
+          },
+        },
+      ],
+      current: "昨日",
     },
     productAddTrendData: {
       loading: false,
@@ -296,7 +338,7 @@ export default {
       });
     },
 
-    // 供应链企业地区分布
+    // 分销商地区分布
     *load_distributor_enquire_location({ payload }, { call, put, select }) {
       yield put({
         type: "setDistributorEnquireLocation",
@@ -315,7 +357,16 @@ export default {
     },
 
     // 新增会员趋势
-    *load_add_member_trend({ payload }, { put, call }) {
+    *load_add_member_trend({ payload }, { put, call, select}) {
+      const current = yield select(
+        (state) => state.bigscreen.memberAddTrendData.current
+      );
+      const options = yield select(
+        (state) => state.bigscreen.memberAddTrendData.options
+      );
+      const condition = options.filter((item) => item.label == current)[0]
+        .value;
+      payload = { ...payload, ...condition };
       yield put({
         type: "setMemberAddTrendData",
         payload: { loading: true },
@@ -469,7 +520,7 @@ export default {
         put({ type: "load_overseas_enquire_location" }),
         put({ type: "load_distributor_enquire_location" }),
         put({ type: "load_add_member_trend" }),
-        put({ type: "load_add_product_trend" }),
+        // put({ type: "load_add_product_trend" }),
         put({ type: "load_search_rank" }),
         put({ type: "load_store_traffic" }),
         put({ type: "load_world_map" }),