Pārlūkot izejas kodu

feat: 调查统计新页面

TIANYONG 3 nedēļas atpakaļ
vecāks
revīzija
f5619a18fb
26 mainītis faili ar 1337 papildinājumiem un 3 dzēšanām
  1. 16 0
      src/router/router-root.ts
  2. 2 2
      src/views/preview-protocol/privacy.tsx
  3. 316 0
      src/views/questionnaire-statistics-new/detail.tsx
  4. 78 0
      src/views/questionnaire-statistics-new/drawGraph.ts
  5. BIN
      src/views/questionnaire-statistics-new/images/arrow_icon.png
  6. BIN
      src/views/questionnaire-statistics-new/images/arrow_right.png
  7. BIN
      src/views/questionnaire-statistics-new/images/class_icon.png
  8. BIN
      src/views/questionnaire-statistics-new/images/dt_bg.png
  9. BIN
      src/views/questionnaire-statistics-new/images/position_icon.png
  10. BIN
      src/views/questionnaire-statistics-new/images/qt_bg.png
  11. BIN
      src/views/questionnaire-statistics-new/images/sc_icon1.png
  12. BIN
      src/views/questionnaire-statistics-new/images/sc_icon2.png
  13. BIN
      src/views/questionnaire-statistics-new/images/sc_icon3.png
  14. BIN
      src/views/questionnaire-statistics-new/images/sc_line.png
  15. BIN
      src/views/questionnaire-statistics-new/images/school_icon.png
  16. BIN
      src/views/questionnaire-statistics-new/images/search_btn.png
  17. BIN
      src/views/questionnaire-statistics-new/images/search_icon.png
  18. BIN
      src/views/questionnaire-statistics-new/images/sort_icon1.png
  19. BIN
      src/views/questionnaire-statistics-new/images/sort_icon2.png
  20. BIN
      src/views/questionnaire-statistics-new/images/sort_icon3.png
  21. BIN
      src/views/questionnaire-statistics-new/images/total_box_icon.png
  22. BIN
      src/views/questionnaire-statistics-new/images/xz_icon1.png
  23. BIN
      src/views/questionnaire-statistics-new/images/xz_icon2.png
  24. 559 0
      src/views/questionnaire-statistics-new/index.module.less
  25. 365 0
      src/views/questionnaire-statistics-new/index.tsx
  26. 1 1
      vite.config.ts

+ 16 - 0
src/router/router-root.ts

@@ -359,6 +359,22 @@ export default [
     }
   },    
   {
+    path: '/questionnaire-statistics-new',
+    name: 'questionnaire-statistics-new',
+    component: () => import('@/views/questionnaire-statistics-new/index'),
+    meta: {
+      title: '数字化转型问卷统计'
+    }
+  },  
+  {
+    path: '/statistics-detail-new',
+    name: 'statistics-detail-new',
+    component: () => import('@/views/questionnaire-statistics-new/detail'),
+    meta: {
+      title: '数字化转型问卷统计'
+    }
+  },   
+  {
     path: '/:pathMatch(.*)*',
     component: () => import('@/views/404'),
     meta: {

+ 2 - 2
src/views/preview-protocol/privacy.tsx

@@ -32,7 +32,7 @@ export default defineComponent({
           {this.name}
           功能息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。
           <br />
-          您登使用或缴费使用我们的付费功能,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。
+          您登使用或缴费使用我们的付费功能,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。
           <br />
           <br />
           <h2>我们如何收集信息</h2>
@@ -480,7 +480,7 @@ export default defineComponent({
           <br />
           <strong>发布日期:2023-02-28</strong>
           <br />
-          <strong>最新更新日期:2021-05-06</strong>
+          <strong>最新更新日期:2023-02-28</strong>
         </div>
       </>
     );

+ 316 - 0
src/views/questionnaire-statistics-new/detail.tsx

@@ -0,0 +1,316 @@
+import { defineComponent, onMounted, reactive, ref, nextTick } from 'vue';
+import styles from './index.module.less';
+import { List, Popup, DatePicker, Popover, Picker } from 'vant';
+import request from '@/helpers/request';
+import { useRoute, useRouter } from 'vue-router';
+import OEmpty from '@/components/m-empty';
+import MWxTip from '@/components/m-wx-tip';
+import OSearch from '@/components/m-search';
+import positionIcon from './images/position_icon.png';
+import scIcon1 from './images/sc_icon1.png';
+import scIcon2 from './images/sc_icon2.png';
+import scIcon3 from './images/sc_icon3.png';
+import schoolIcon from './images/school_icon.png';
+import gradeIcon from './images/class_icon.png';
+import { number } from 'echarts';
+import { drawCircle } from './drawGraph'
+
+
+export default defineComponent({
+  name: 'statistics-detail',
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+    const tabName = ref('all');
+    const forms = reactive({
+      schoolName: '',
+      id: route.query.id,
+      yearStatus: false,
+      schoolId: null,
+      
+    });
+
+    const refreshing = ref(false);
+    const loading = ref(true);
+
+
+    const state = reactive({
+      saveLoading: false,
+      image: null as any,
+      shareLoading: false,
+      schoolInfo: {} as any,
+      classList: [] as any,
+      currentSort: 1, // 当前选中的排序方式
+      currentAsc: false, // 是否生序,默认是降序
+      gradeColumns: [],
+      gradeStatus: false,
+      gradePopShow: false,
+      currentGrade: "",
+      currentClass: "",
+      gradeOptionIndex: [] as any,
+      gradeContent: null as any,
+    });
+
+    // 获取学校信息
+    const queryInfo = async () => {
+      try {
+        const res = await request.post(
+          '/edu-app/open/meetingQuestionSetting/schoolStat',
+          {
+            data: {
+              schoolAreaId: forms.id
+            }
+          }
+        );
+        state.schoolInfo = res.data || {}
+        // 构建年级、班级及联选择
+        const columns: any = []
+        res.data.classData.unshift({
+          currentClassList: [],
+          currentGrade: '全部年级'
+        })
+        res.data.classData.forEach((grade: any, index: number) => {
+          let columnItem: any = {}
+          columnItem.text = grade.currentGrade
+          columnItem.value = 'grade' + index
+          columnItem.children = []
+          grade.currentClassList.unshift('全部班级')
+          if (grade.currentClassList?.length) {
+            grade.currentClassList.forEach((classObj: any, cIdx: number) => {
+              columnItem.children.push({
+                text: classObj,
+                value: 'class' + cIdx
+              })
+            })
+          }
+          columns.push(columnItem)
+        })
+        state.gradeColumns = columns
+        // console.log('555',res, state.gradeColumns)        
+      } catch (error) {
+        
+      }
+
+    }
+
+    const queryList = async () => {
+      try {
+        const res = await request.post(
+          '/edu-app/open/meetingQuestionSetting/schoolClass',
+          {
+            data: {
+              schoolAreaId: forms.id,
+              currentGrade: state.currentGrade,
+              currentClass: state.currentClass,
+              sortType: state.currentSort,
+              asc: state.currentAsc
+            }
+          }
+        );
+        state.classList = res.data || []
+        // console.log('222',res)        
+      } catch (error) {
+        
+      }
+
+    }
+
+    const filterList = (num: number) => {
+      if (state.currentSort !== num) {
+        state.currentAsc = false
+      } else {
+        state.currentAsc = !state.currentAsc
+      }
+      state.currentSort = num;
+      
+      queryList()
+    }
+
+    const formatNumberWithComma = (num: number | string) => {
+      // 将数字转换为字符串,去掉小数点后面的部分
+      let [integer, decimal] = num.toString().split('.');
+      
+      // 使用正则表达式添加千分位分隔符
+      integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+      
+      // 如果有小数部分,则保留小数部分
+      return decimal ? `${integer}.${decimal}` : integer;
+    }
+
+    onMounted(async () => {
+      await queryInfo()
+      queryList()
+      nextTick(() => {
+        let percentage = 0;
+        const interval = setInterval(() => {
+          if (percentage <= state.schoolInfo.supportStudentRate) {
+            drawCircle('circle1', 1, percentage)
+          }
+          if (percentage <= state.schoolInfo.participationStudentRate) {
+            drawCircle('circle2', 2, percentage)
+          }
+          percentage += 1; // 每次增加1%
+          if (percentage > Math.max(state.schoolInfo.supportStudentRate, state.schoolInfo.participationStudentRate)) {
+            clearInterval(interval); // 停止定时器
+          }
+        }, 25); // 每25ms更新一次
+      });
+    });
+
+    return () => (
+      <div class={styles.detailBody}>
+        <div class={styles.dbTitle}>
+          <div class={styles.dtName}>
+            <img src={schoolIcon} />
+            <p>{state.schoolInfo.schoolName}</p>
+          </div>
+          <div class={styles.dtDesc}>共<span> {state.schoolInfo.classNum} </span>个班级,<span>{state.schoolInfo.studentNum}</span>人参与调查</div>
+        </div>
+
+        {/* <div class={styles.dbStatic}>
+          <div class={styles.dsItem}>
+            <div><span>{formatNumberWithComma(state.schoolInfo.supportStudentNum || 0)}</span><i>人</i></div>
+            <p>支持学校开展</p>
+            <i class={[styles.dsIcon, styles.dsIcon1]}></i>
+          </div>
+          <div class={styles.dsItem}>
+            <div><span>{state.schoolInfo.supportStudentRate}%</span></div>
+            <p>支持率</p>
+            <i class={[styles.dsIcon, styles.dsIcon2]}></i>
+          </div>          
+        </div> */}
+
+        {/** 圆环统计 */}
+        <div class={styles.sRing}>
+          <div class={[styles.srItem,styles.srItemOne]}>
+            <div class={styles.siLeft}>
+              <canvas id="circle1" width="70" height="70"></canvas>
+              <p>支持率</p>
+            </div>
+            <div class={styles.siRight}>
+              <div><span class={styles.sBlue}>{formatNumberWithComma(state.schoolInfo.supportStudentNum || 0)}</span><i>人</i></div>
+              <p>支持开展</p>
+            </div>
+          </div>
+          <div class={styles.srItem}>
+            <div class={styles.siLeft}>
+              <canvas id="circle2" width="70" height="70"></canvas>
+              <p>参加率</p>
+            </div>
+            <div class={styles.siRight}>
+              <div><span class={styles.sGreen}>{formatNumberWithComma(state.schoolInfo.participationStudentNum||0)}</span><i>人</i></div>
+              <p>报名参加</p>
+            </div>
+          </div>
+        </div>      
+
+        {/** 选择年级班级 */}
+        <div class={[styles.gradeColumn, state.gradeStatus && styles.openVal]} onClick={() => state.gradeStatus = true}>
+          <span class={state.gradeContent && styles.gcText}>{state.gradeContent ? state.gradeContent : '请选择年级班级'}</span>
+          <i></i>
+        </div>
+
+        {/** 排序栏 */}
+        <ul class={styles.sortColumn}>
+            <li class={state.currentSort === 1 && styles.sortActive} onClick={() => filterList(1)}>
+              <span>参与调查人数</span>
+              <i class={[(state.currentSort === 1 && !state.currentAsc) && styles.actDown, (state.currentSort === 1 && state.currentAsc) && styles.actUp]}></i>
+            </li>
+            <li class={state.currentSort === 2 && styles.sortActive} onClick={() => filterList(2)}>
+              <span>支持人数</span>
+              <i class={[(state.currentSort === 2 && !state.currentAsc) && styles.actDown, (state.currentSort === 2 && state.currentAsc) && styles.actUp]}></i>
+            </li>
+            <li class={state.currentSort === 3 && styles.sortActive} onClick={() => filterList(3)}>
+              <span>支持率</span>
+              <i class={[(state.currentSort === 3 && !state.currentAsc) && styles.actDown, (state.currentSort === 3 && state.currentAsc) && styles.actUp]}></i>
+            </li>      
+            <li class={state.currentSort === 4 && styles.sortActive} onClick={() => filterList(4)}>
+              <span>参加人数</span>
+              <i class={[(state.currentSort === 4 && !state.currentAsc) && styles.actDown, (state.currentSort === 4 && state.currentAsc) && styles.actUp]}></i>
+            </li>   
+            <li class={state.currentSort === 5 && styles.sortActive} onClick={() => filterList(5)}>
+              <span>参加率</span>
+              <i class={[(state.currentSort === 5 && !state.currentAsc) && styles.actDown, (state.currentSort === 5 && state.currentAsc) && styles.actUp]}></i>
+            </li>                                             
+        </ul>
+        {/** 班级列表 */}
+        <div class={styles.scList}>
+          {
+            state.classList.map((item: any) => (
+              <div class={styles.sItem}>
+              <div class={[styles.itemTile, styles.itemTileDetail]}>
+                <img src={gradeIcon} />
+                <p>{item.currentGrade||''}{item.currentClass || ''}</p>
+                <div class={styles.itRight}><span class={styles.sRed}>{formatNumberWithComma(item.studentNum || 0)}</span> 人参与调查</div>
+              </div>
+              <ul class={styles.itemContent}>
+                <li>
+                  <div class={styles.icTop}>
+                    <span class={styles.sBlue}>{formatNumberWithComma(item.supportStudentNum || 0)}</span><i>人</i>
+                  </div>
+                 <p>支持开展</p>
+                </li>      
+                <li>
+                  <div class={styles.icTop}>
+                    <span class={styles.sBlue}>{Number(item.supportStudentRate || 0).toFixed(2)}%</span>
+                  </div>
+                 <p>支持率</p>
+                </li>  
+
+                <li>
+                  <div class={styles.icTop}>
+                    <span class={styles.sGreen}>{formatNumberWithComma(item.participationStudentNum || 0)}</span><i>人</i>
+                  </div>
+                 <p>报名参加</p>
+                </li>
+
+                <li>
+                  <div class={styles.icTop}>
+                    <span class={styles.sGreen}>{Number(item.participationStudentRate || 0).toFixed(2)}%</span>
+                  </div>
+                 <p>参加率</p>
+                </li>                                
+              </ul>
+            </div>
+            ))
+          }
+        </div>
+
+        {/* 班级 */}
+        <Popup
+          v-model:show={state.gradeStatus}
+          position="bottom"
+          round
+          safeAreaInsetBottom
+          lazyRender={false}
+          class={'popupBottomSearch'}
+          onOpen={() => {
+            state.gradePopShow = true;
+          }}
+          onClosed={() => {
+            state.gradePopShow = false;
+          }}>
+          {state.gradePopShow && (
+            <Picker
+              showToolbar
+              v-model={state.gradeOptionIndex}
+              columns={state.gradeColumns}
+              onCancel={() => (state.gradeStatus = false)}
+              onConfirm={(val: any) => {
+                console.log('选择1111',val)
+                state.currentGrade = val.selectedOptions[0].text === '全部年级' ? '' : val.selectedOptions[0].text
+                state.currentClass = val.selectedOptions[1].text === '全部班级' ? '' : val.selectedOptions[1].text
+                state.gradeOptionIndex = [val.selectedOptions[0].value, val.selectedOptions[1].value]
+                state.gradeContent = state.currentGrade + state.currentClass
+                state.gradeStatus = false;
+                queryList()
+              }}
+            />
+          )}
+        </Popup> 
+
+        {/* <MWxTip /> */}
+      </div>
+    );
+  }
+});

+ 78 - 0
src/views/questionnaire-statistics-new/drawGraph.ts

@@ -0,0 +1,78 @@
+export const drawCircle = (domId: string, type: number, rateNum: number) => {
+    const colorObj = type === 1 ?{
+        start: '#279FFE',
+        end: '#43C8FE',
+        bg: '#D9EEFF',
+    } : {
+        start: '#24BD90',
+        end: '#7CE3C5',
+        bg: '#E8F6F2',
+    }
+    // 获取canvas元素和上下文
+    const canvas: any = document.getElementById(domId);
+    const ctx = canvas.getContext('2d');
+
+    // 设置百分比(0-100)
+    const percentage = rateNum || 0;
+
+    // 圆环的设置
+    const radius = 30; // 圆环半径
+    const lineWidth = 8; // 圆环的宽度
+
+    // 圆心坐标
+    const centerX = canvas.width / 2;
+    const centerY = canvas.height / 2;
+
+    // 创建渐变色
+    function createGradient() {
+        const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
+        gradient.addColorStop(0, colorObj.start); // 渐变开始颜色
+        gradient.addColorStop(1, colorObj.end); // 渐变结束颜色
+        return gradient;
+    }    
+
+    // 绘制背景圆环
+    function drawBackgroundCircle() {
+      ctx.beginPath();
+      ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
+      ctx.lineWidth = lineWidth;
+      ctx.strokeStyle = colorObj.bg; // 背景圆环的颜色
+      ctx.lineJoin = 'round'; // 设置圆角效果
+      ctx.stroke();
+    }
+
+    // 绘制进度圆环
+    function drawProgressCircle() {
+      const startAngle = -0.5 * Math.PI; // 起始角度,从顶部开始
+      const endAngle = (percentage / 100) * 2 * Math.PI - 0.5 * Math.PI; // 结束角度,按照百分比计算
+
+      ctx.beginPath();
+      ctx.arc(centerX, centerY, radius, startAngle, endAngle);
+      ctx.lineWidth = lineWidth;
+      ctx.strokeStyle = createGradient(); // 进度圆环使用渐变色
+      ctx.lineJoin = 'round'; // 设置圆角效果
+      ctx.stroke();
+    }
+
+    // 绘制百分比文本
+    function drawText() {
+      ctx.font = '18px DIN';
+      ctx.fillStyle = '#259CFE';
+      ctx.textAlign = 'center';
+      ctx.textBaseline = 'middle';
+      ctx.fillText(percentage + '%', centerX, centerY-6); // 绘制百分比文本
+    }
+    // 清除文本区域
+    function clearTextArea() {
+        const textWidth = ctx.measureText(percentage + '%').width; // 获取文本宽度
+        const padding = 10; // 给文本区域留一些边距
+
+        // 清除文本区域的矩形区域
+        ctx.clearRect(centerX - textWidth / 2 - padding, centerY - 24 - padding, textWidth + padding * 2, 48);
+    }
+    clearTextArea();
+    drawBackgroundCircle(); // 绘制背景圆环
+    drawProgressCircle();   // 绘制进度圆环
+    drawText(); 
+   
+  }

BIN
src/views/questionnaire-statistics-new/images/arrow_icon.png


BIN
src/views/questionnaire-statistics-new/images/arrow_right.png


BIN
src/views/questionnaire-statistics-new/images/class_icon.png


BIN
src/views/questionnaire-statistics-new/images/dt_bg.png


BIN
src/views/questionnaire-statistics-new/images/position_icon.png


BIN
src/views/questionnaire-statistics-new/images/qt_bg.png


BIN
src/views/questionnaire-statistics-new/images/sc_icon1.png


BIN
src/views/questionnaire-statistics-new/images/sc_icon2.png


BIN
src/views/questionnaire-statistics-new/images/sc_icon3.png


BIN
src/views/questionnaire-statistics-new/images/sc_line.png


BIN
src/views/questionnaire-statistics-new/images/school_icon.png


BIN
src/views/questionnaire-statistics-new/images/search_btn.png


BIN
src/views/questionnaire-statistics-new/images/search_icon.png


BIN
src/views/questionnaire-statistics-new/images/sort_icon1.png


BIN
src/views/questionnaire-statistics-new/images/sort_icon2.png


BIN
src/views/questionnaire-statistics-new/images/sort_icon3.png


BIN
src/views/questionnaire-statistics-new/images/total_box_icon.png


BIN
src/views/questionnaire-statistics-new/images/xz_icon1.png


BIN
src/views/questionnaire-statistics-new/images/xz_icon2.png


+ 559 - 0
src/views/questionnaire-statistics-new/index.module.less

@@ -0,0 +1,559 @@
+.statisBody {
+  min-height: 100vh;
+  background-size: contain;
+  padding: 12px;
+  overflow: hidden;
+  position: relative;
+  background: url('./images/qt_bg.png') no-repeat top center #F6F8F9;
+  background-size: contain;
+}
+
+.spColumn {
+  display: flex;
+  align-items: center;
+  background: rgba(255,255,255,0.6);
+  border-radius: 18px;
+  border: 1px solid #FFFFFF;
+  height: 33px;
+  padding: 0 12px;
+  p {
+    flex: 1;
+    font-size: 15px;
+    color: #333333;
+    font-weight: 600;
+    padding-right: 8px;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    word-break: break-all;
+    white-space: nowrap;
+  }
+  img {
+    width: 16px;
+    height: auto;
+    object-fit: contain;
+    margin-right: 4px;
+  }
+  >i {
+    background: url('./images/arrow_icon.png') no-repeat center;
+    background-size: contain;
+    width: 12px;
+    height: 12px;
+    transition: all 0.5s;
+  }
+}
+
+.scContent {
+  background: url('./images/total_box_icon.png') no-repeat center;
+  background-size: contain;
+  width: 100%;
+  height: 145px;
+  margin: 8px 0 12px;
+  max-width: 400px;
+  position: relative;
+  left: 50%;
+  transform: translateX(-50%);
+  .scTop {
+    margin: 19px 0 0 24px;
+    display: inline-block;
+    align-items: flex-end;
+    font-size: 14px;
+    color: #333333;
+    position: relative;
+    z-index: 2;
+    > div {
+      display: inline-block;
+      font-weight: 500;
+    }
+    > span {
+      color: #FE3F25;
+      font-size: 28px;
+      font-weight: 500;
+      position: relative;
+      z-index: 2;
+      margin: 0 1px 0 4px;
+      top: 2px;
+      font-family: DIN;
+    }
+    > i {
+      font-style: normal;
+      color: #777777;
+    }
+    &::before {
+      content: "";
+      position: absolute;
+      z-index: 1;
+      left: -1px;
+      bottom: 1px;
+      width: 106%;
+      height: 8px;
+      background: linear-gradient( 90deg, rgba(37,156,254,0.3) 0%, rgba(91,236,255,0.3) 100%);
+    }
+  }
+  .scBottom {
+    margin-top: 24px;
+    display: flex;
+    align-items: center;
+    position: relative;
+    li {
+      position: relative;
+      flex: 1;
+      .sNum {
+        font-size: 20px;
+        font-weight: 500;
+        display: flex;
+        justify-content: center;
+        align-items: flex-end;
+        i {
+          font-weight: normal;
+          font-style: normal;
+          color: #777777;
+          font-size: 12px;
+          position: relative;
+          top: -1px;
+        }
+      }
+      .sDesc {
+        margin-top: 6px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 13px;
+        color: #777777;
+        img {
+          width: 16px;
+          height: 16px;
+          margin-right: 2px;
+        }
+      }  
+      &:nth-child(1)::before,&:nth-child(2)::before {
+        content: "";
+        position: absolute;
+        right: 0;
+        top: 0;
+        height: 43px;
+        width: 1px;
+        background: url('./images/sc_line.png') no-repeat center;
+        background-size: contain;
+      }    
+      &:nth-child(1)::before {
+        right: 4px;
+      }
+      &:nth-child(2)::before {
+        right: -8px;
+        z-index: 99;
+      }
+    }    
+  }
+}
+
+.searechInfo {
+  background: #fff;
+  display: flex;
+  align-items: center;
+  background: rgba(255,255,255,0.6);
+  border-radius: 19px;
+  border: 1px solid #FFFFFF;
+  padding: 3px 3px 3px 12px;
+  .searchIcon {
+    width: 16px;
+    height: 16px;
+    margin-right: 4px;
+  }
+  .searchBtn {
+    width: 56px;
+    height: 28px;
+    margin-left: 4px;
+  }
+  :global {
+    .van-cell {
+      padding: 0;
+      background: transparent;
+      border: none;
+    }
+    .van-cell:after {
+      display: none;
+    }
+    input::placeholder {
+      color: rgba(0,0,0,0.4);
+    }
+  }
+}
+
+.sortColumn {
+  display: flex;
+  align-items: center;
+  flex-wrap: nowrap;
+  overflow-x: scroll;
+  margin-top: 12px;
+  &::-webkit-scrollbar {
+    display: none;
+  }
+  // position: sticky;
+  // top: 0;
+  >li {
+    display: flex;
+    align-items: center;
+    height: 28px;
+    box-sizing: border-box;
+    background: #fff;
+    border: 1px solid #fff;
+    border-radius: 14px;
+    font-size: 12px;
+    color: #333333;
+    padding: 0 8px;
+    margin-right: 10px;
+    >span {
+      word-break: keep-all;
+    }
+    >i {
+      background: url('./images/sort_icon1.png') no-repeat center;
+      background-size: contain;
+      width: 6px;
+      height: 10px;
+      margin-left: 4px;
+      &.actUp {
+        background: url('./images/sort_icon2.png') no-repeat center;
+        background-size: contain;
+      }
+      &.actDown {
+        background: url('./images/sort_icon3.png') no-repeat center;
+        background-size: contain;
+      }
+    }
+  }
+  .sortActive {
+    background: rgba(28,172,241,0.06);
+    border: 1px solid #2FAFF1;
+    font-size: 12px;
+    color: #1CACF1;
+    font-weight: 600;
+  }
+}
+
+.scList {
+  .sItem {
+    margin-top: 12px;
+    background: #fff;
+    border-radius: 10px;
+    padding: 0 10px;
+    .itemTile {
+      display: flex;
+      align-items: center;
+      padding: 12px 0 8px;
+      img {
+        width: 16px;
+        height: 16px;
+      }
+      p {
+        flex: 1;
+        font-size: 14px;
+        color: #333;
+        font-weight: 500;
+        padding: 0 8px 0 3px;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        word-break: break-all;
+        white-space: nowrap;
+      }
+      i {
+        width: 12px;
+        height: 12px;
+        background: url('./images/arrow_right.png') no-repeat center;
+        background-size: contain;
+      }
+    }
+    .itemTileDetail {
+      padding: 12px 0 10px;
+      border-bottom: 1px dashed #f0f0f0;
+      .itRight {
+        font-size: 12px;
+        color: #333333;
+      }
+    }
+    .itemDesc {
+      border-bottom: 1px dashed #f0f0f0;
+      padding-bottom: 12px;
+      font-size: 12px;
+      color: #333333;
+      span {
+        font-weight: 500;
+      }
+    }
+    .itemContent {
+      display: flex;
+      align-items: center;
+      >li {
+        flex: 1;
+        padding: 12px 0;
+        .icTop {
+          text-align: center;
+          font-size: 18px;
+          font-weight: 500;
+          display: flex;
+          align-items: flex-end;
+          justify-content: center;
+          i {
+            font-style: normal;
+            font-size: 12px;
+            color: #777777;
+            position: relative;
+            top: -1px;
+          }
+        }
+        >p {
+          margin-top: 4px;
+          font-size: 12px;
+          color: #777777;
+          text-align: center;
+        }
+      }
+    }
+  }
+}
+
+
+
+.sRed {
+  font-family: DIN;
+  color: #FE3F25;
+}
+.sBlue {
+  font-family: DIN;
+  color: #259CFE;
+}
+.sGreen {
+  font-family: DIN;
+  color: #24BD90;
+}
+.sOrange {
+  font-family: DIN;
+  color: #FE6F25;
+}
+
+// 详情页
+.detailBody {
+  min-height: 100vh;
+  background: url('./images/dt_bg.png') no-repeat top center #F6F8F9;
+  background-size: contain;
+  padding: 20px 12px;
+  position: relative;
+}
+
+.dbTitle {
+  margin-bottom: 12px;
+  .dtName {
+    display: flex;
+    align-items: center;
+    margin-bottom: 6px;
+    img {
+      width: 16px;
+      height: 16px;
+      margin-right: 3px;
+    }
+    p {
+      font-size: 15px;
+      color: #333333;
+      font-weight: 500;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      word-break: break-all;
+      white-space: nowrap;      
+    }
+  }
+  .dtDesc {
+    font-size: 12px;
+    color: #333333;
+    span {
+      font-weight: 500;
+      color: #FE2525;
+    }
+  }
+}
+
+.dbStatic {
+  display: flex;
+  align-items: center;
+  .dsItem {
+    flex: 1;
+    background: linear-gradient( 157deg, rgba(255,255,255,0.45) 0%, #FFFFFF 100%);
+    border: 2px solid rgba(179, 224, 236, 0.66);
+    border-radius: 10px;
+    position: relative;
+    overflow: hidden;
+    padding: 14px 0 16px 16px;
+    >div {
+      display: flex;
+      align-items: flex-end;
+      font-size: 12px;
+      color: #777777;
+      margin-bottom: 5px;
+      span {
+        font-size: 22px;
+        font-weight: 500;
+        color: #259CFE;
+        font-family: DIN;
+      }
+      i {
+        position: relative;
+        top: -2px;
+        font-style: normal;
+      }
+    }
+    >p {
+      font-size: 13px;
+      color: #777777;
+    }
+    .dsIcon {
+      position: absolute;
+      right: 0;
+      bottom: 0;
+    }
+    .dsIcon1 {
+      background: url('./images/xz_icon2.png') no-repeat center;
+      background-size: contain;
+      width: 44px;
+      height: 48px;
+    }
+    .dsIcon2 {
+      background: url('./images/xz_icon1.png') no-repeat center;
+      background-size: contain;
+      width: 41px;
+      height: 40px;
+    }
+    &:nth-child(1) {
+      margin-right: 8px;
+    }
+  }
+}
+
+.gradeColumn {
+  margin: 12px 0;
+  background: rgba(255,255,255,0.6);
+  border-radius: 19px;
+  border: 1px solid #FFFFFF;
+  height: 34px;
+  display: flex;
+  align-items: center;
+  padding: 0 12px;
+  span {
+    flex: 1;
+    font-size: 14px;
+    color: rgba(0,0,0,0.4);
+  }
+  .gcText {
+    color: #333;
+  }
+  i {
+    background: url('./images/arrow_icon.png') no-repeat center;
+    background-size: contain;
+    width: 12px;
+    height: 12px;
+    transition: all 0.5s;
+  }
+}
+.openVal {
+  i {
+    transform: rotate(180deg);
+  }
+}
+
+.sTotal {
+  background: #fff;
+  border-radius: 10px;
+  display: flex;
+  height: 68px;
+  padding: 8px 0 12px;
+  margin: 12px 0 10px;
+  .stOne {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: space-between;
+    .soTitle {
+      display: flex;
+      align-items: flex-end;
+      font-size: 12px;
+      color: #777777;
+      margin-bottom: 6px;
+      >span {
+        font-size: 24px;
+        font-weight: 500;
+      }
+      >i {
+        font-style: normal;
+        position: relative;
+        top: -3px;
+      }
+    }
+    .soDesc {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #777777;
+      line-height: 10px;
+      img {
+        font-size: 16px;
+        height: 16px;
+        margin-right: 2px;
+      }
+    }
+  }
+}
+
+.sRing {
+  display: flex;
+  margin-bottom: 12px;
+  .srItem {
+    flex: 1;
+    background: #FFFFFF;
+    border-radius: 10px;
+    display: flex;
+    padding: 14px;
+    .siRight {
+      height: 60px;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      padding: 10px 0;
+      margin-left: 6px;
+      >div {
+        font-size: 12px;
+        color: #777777;
+        span {
+          font-size: 20px;
+          font-weight: 500;
+        }
+        i {
+          font-style: normal;
+        }
+      }
+      >p {
+        font-size: 12px;
+        color: #777777;
+        margin-top: 3px;
+      }
+    }
+    .siLeft {
+      width: 70px;
+      height: 70px;
+      position: relative;
+      >p {
+        position: absolute;
+        left: 50%;
+        bottom: 18px;
+        transform: translateX(-50%);
+        text-align: center;
+      }
+    }
+  }
+  .srItemOne {
+    margin-right: 8px;
+  }
+}
+
+#circle1, #circle2 {
+  width: 60px;
+  height: 60px;
+  margin-right: 8px;
+}

+ 365 - 0
src/views/questionnaire-statistics-new/index.tsx

@@ -0,0 +1,365 @@
+import { defineComponent, onMounted, reactive, ref, nextTick } from 'vue';
+import styles from './index.module.less';
+import { List, Popup, DatePicker, Popover, Field, Picker, } from 'vant';
+import request from '@/helpers/request';
+import { useRoute, useRouter } from 'vue-router';
+import OEmpty from '@/components/m-empty';
+import MWxTip from '@/components/m-wx-tip';
+import OSearch from '@/components/m-search';
+import positionIcon from './images/position_icon.png';
+import scIcon1 from './images/sc_icon1.png';
+import scIcon2 from './images/sc_icon2.png';
+import scIcon3 from './images/sc_icon3.png';
+import schoolIcon from './images/school_icon.png';
+import searchIcon from './images/search_icon.png';
+import searchBtn from './images/search_btn.png';
+import totalBoxBg from './images/total_box_icon.png';
+import { drawCircle } from './drawGraph'
+
+export default defineComponent({
+  name: 'questionnaire-statistics-new',
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+    const tabName = ref('all');
+    const forms = reactive({
+      schoolName: '',
+      id: route.query.id,
+      // id: '1687275949971763202',
+      yearStatus: false,
+      schoolId: null,
+      classList: [] as any,
+      page: 1,
+      rows: 20,
+      isClick: false,
+      tenantId: route.query.id,
+      areaList: [] as any,
+      areaColumns: [] as any,
+      areaStatus: false,
+      areaPopupShow: false,
+      areaOptionIndex: [] as any,
+      currentArea: null as any, // 当前的区域
+      currentAreaInfo: null as any,
+      schoolList: [] as any,
+      totalInfo: {} as any,
+      sortType: 'DESC' as any, // 排序方式,ASC(升序)/DESC(降序)
+      sortField: 'totalNum', // totalNum: 总人数,supportNum:支持人数,supportRate:支持率
+      areaIdx: 0 as any,
+    });
+
+    const refreshing = ref(false);
+    const loading = ref(true);
+    const finished = ref(false);
+    const showContact = ref(false);
+    const list = ref([]);
+
+    const queryArea = async () => {
+      try {
+        const { data } = await request.get(
+          `/edu-app/open/tenantInfo/getArea?tenantId=${forms.tenantId}`, {
+            hideLoading: false,
+          }
+        );
+        forms.areaList = data || []
+        data.forEach((item: any, index: number) => {
+          const {provinceName='',cityName='',regionName=''} = item
+          forms.areaColumns.push({
+            text: provinceName + ' ' + cityName + ' ' + (regionName ? regionName : ''),
+            value: index
+          })
+        })
+        forms.currentArea = forms.areaColumns.length ? forms.areaColumns[forms.areaIdx].text : ''
+        forms.currentAreaInfo = forms.areaList.length ? forms.areaList[forms.areaIdx] : null;
+      } catch (error) {
+        
+      }
+      await queryInfo();
+      await getList();
+    }
+
+    const queryInfo = async () => {
+      try {
+        const { provinceCode='',cityCode='',regionCode='' } = forms.currentAreaInfo
+        const res = await request.post(
+          '/edu-app/open/schoolMeetingQuestion/areaSummarySum',
+          {
+            data: {
+              tenantId: forms.tenantId,
+              schoolName: forms.schoolName,
+              provinceCode,
+              cityCode,
+              districtCode: regionCode,
+            }
+          }
+        );
+        forms.totalInfo = res.data|| {}
+      } catch (error) {
+        
+      }
+    }
+
+    const getList = async () => {
+      try {
+        const { provinceCode='',cityCode='',regionCode='' } = forms.currentAreaInfo
+        const res = await request.post(
+          '/edu-app/open/schoolMeetingQuestion/areaSummary',
+          {
+            data: {
+              tenantId: forms.tenantId,
+              schoolName: forms.schoolName,
+              provinceCode,
+              cityCode,
+              districtCode: regionCode,
+              sortType: forms.sortType,
+              sortField: forms.sortField,
+            }
+          }
+        );
+
+        forms.schoolList = res?.data || []
+      } catch {
+        // 
+      } finally {
+        //
+      }
+      
+    };
+
+    const state = reactive({
+      saveLoading: false,
+      image: null as any,
+      shareLoading: false
+    });
+
+    const skipDetail = (id: any) => {
+      sessionStorage.setItem('areaIdx', forms.areaIdx)
+      router.push({
+        path: '/statistics-detail-new',
+        query: {
+          id,
+        }
+      });
+    }
+
+    const filterList = (val: string) => {
+      if (forms.sortField !== val) {
+        forms.sortType = 'DESC'
+      } else {
+        forms.sortType = forms.sortType === 'DESC' ? 'ASC' : 'DESC'
+      }
+      forms.sortField = val
+      getList()
+    }
+
+    const formatNumberWithComma = (num: number | string) => {
+      // 将数字转换为字符串,去掉小数点后面的部分
+      let [integer, decimal] = num.toString().split('.');
+      
+      // 使用正则表达式添加千分位分隔符
+      integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+      
+      // 如果有小数部分,则保留小数部分
+      return decimal ? `${integer}.${decimal}` : integer;
+    }
+
+
+    onMounted(async () => {
+      // console.log('刷新页面')
+      forms.areaIdx = sessionStorage.getItem('areaIdx') || 0;
+      await queryArea();
+      nextTick(() => {
+        let percentage = 0;
+        const interval = setInterval(() => {
+          if (percentage <= forms.totalInfo.supportRate) {
+            drawCircle('circle1', 1, percentage)
+          }
+          if (percentage <= forms.totalInfo.participationRate) {
+            drawCircle('circle2', 2, percentage)
+          }
+          percentage += 1; // 每次增加1%
+          if (percentage > Math.max(forms.totalInfo.supportRate, forms.totalInfo.participationRate)) {
+            clearInterval(interval); // 停止定时器
+          }
+        }, 25); // 每25ms更新一次
+      });
+    });
+
+    return () => (
+      <div class={[styles.statisBody]}>
+        {
+        forms.areaColumns.length > 1 && 
+        <div class={[styles.spColumn, forms.areaStatus && styles.openVal]} onClick={() => {
+          forms.areaOptionIndex = [Number(forms.areaIdx)]
+          forms.areaStatus = true
+        }}>
+          <img src={positionIcon} />
+          <p>{forms.currentArea}</p>
+          <i></i>
+        </div>
+        }
+
+        {/** 参与学校统计 */}
+        <div class={styles.sTotal}>
+          <div class={styles.stOne}>
+            <div class={styles.soTitle}><span class={styles.sOrange}>{formatNumberWithComma(forms.totalInfo.schoolNum||0)}</span><i>所</i></div>
+            <p class={styles.soDesc}><img src={schoolIcon} />参与学校</p>
+          </div>
+          <div class={styles.stOne}>
+            <div class={styles.soTitle}><span class={styles.sRed}>{formatNumberWithComma(forms.totalInfo.totalNum||0)}</span><i>人</i></div>
+            <p class={styles.soDesc}><img src={scIcon1} />参与调查</p>
+          </div> 
+        </div>
+
+        {/** 圆环统计 */}
+        <div class={styles.sRing}>
+          <div class={[styles.srItem,styles.srItemOne]}>
+            <div class={styles.siLeft}>
+              <canvas id="circle1" width="70" height="70"></canvas>
+              <p>支持率</p>
+            </div>
+            <div class={styles.siRight}>
+              <div><span class={styles.sBlue}>{formatNumberWithComma(forms.totalInfo.supportNum||0)}</span><i>人</i></div>
+              <p>支持开展</p>
+            </div>
+          </div>
+          <div class={styles.srItem}>
+            <div class={styles.siLeft}>
+              <canvas id="circle2" width="70" height="70"></canvas>
+              <p>参加率</p>
+            </div>
+            <div class={styles.siRight}>
+              <div><span class={styles.sGreen}>{formatNumberWithComma(forms.totalInfo.participationNum||0)}</span><i>人</i></div>
+              <p>报名参加</p>
+            </div>
+          </div>
+        </div>
+
+        {/** 搜索栏 */}
+        <div class={styles.searechInfo}>
+            <img src={searchIcon} class={styles.searchIcon} />
+             <Field
+                clearable={true}
+                inputAlign="left"
+                placeholder="请输入学校名称"
+                autocomplete="off"
+                center
+                maxlength={30}
+                v-model={forms.schoolName}
+                onUpdate:modelValue={(val: any) => {
+                  // 输入框内容变化时触发
+                  // console.log('搜索内容变化',val)
+                  forms.schoolName = val
+                  getList()
+                }}>
+              </Field>   
+              <img src={searchBtn} class={styles.searchBtn} onClick={getList} />         
+        </div>
+
+        {/** 排序栏 */}
+        <ul class={styles.sortColumn}>
+            <li class={forms.sortField === 'totalNum' && styles.sortActive} onClick={() => filterList('totalNum')}>
+              <span>参与调查人数</span>
+              <i class={[(forms.sortField === 'totalNum' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'totalNum' && forms.sortType === 'ASC') && styles.actUp]}></i>
+            </li>
+            <li class={forms.sortField === 'supportNum' && styles.sortActive} onClick={() => filterList('supportNum')}>
+              <span>支持人数</span>
+              <i class={[(forms.sortField === 'supportNum' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'supportNum' && forms.sortType === 'ASC') && styles.actUp]}></i>
+            </li>
+            <li class={forms.sortField === 'supportRate' && styles.sortActive} onClick={() => filterList('supportRate')}>
+              <span>支持率</span>
+              <i class={[(forms.sortField === 'supportRate' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'supportRate' && forms.sortType === 'ASC') && styles.actUp]}></i>
+            </li>        
+            <li class={forms.sortField === 'participationNum' && styles.sortActive} onClick={() => filterList('participationNum')}>
+              <span>参加人数</span>
+              <i class={[(forms.sortField === 'participationNum' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'participationNum' && forms.sortType === 'ASC') && styles.actUp]}></i>
+            </li>       
+            <li class={forms.sortField === 'participationRate' && styles.sortActive} onClick={() => filterList('participationRate')}>
+              <span>参加率</span>
+              <i class={[(forms.sortField === 'participationRate' && forms.sortType === 'DESC') && styles.actDown, (forms.sortField === 'participationRate' && forms.sortType === 'ASC') && styles.actUp]}></i>
+            </li>                                    
+        </ul>
+        {/** 学校列表 */}
+        {
+          forms.schoolList.length ?
+          <div class={styles.scList}>
+            {forms.schoolList.map((item: any) => (
+              <div class={styles.sItem} onClick={() => skipDetail(item.schoolAreaId)}>
+                <div class={styles.itemTile}>
+                  <img src={schoolIcon} />
+                  <p>{item.schoolName}</p>
+                  <i></i>
+                </div>
+                <div class={[styles.itemDesc]}><span class={styles.sRed}>{formatNumberWithComma(item.totalNum || 0)}</span> 人参与调查</div>
+                <ul class={styles.itemContent}>
+                  <li>
+                    <div class={styles.icTop}>
+                      <span class={styles.sRed}>{formatNumberWithComma(item.supportNum || 0)}</span><i>人</i>
+                    </div>
+                    <p>支持开展</p>
+                  </li>
+                  <li>
+                    <div class={styles.icTop}>
+                      <span class={styles.sBlue}>{Number(item.supportRate || 0).toFixed(2)}%</span>
+                    </div>
+                    <p>支持率</p>
+                  </li>
+                  <li>
+                    <div class={styles.icTop}>
+                      <span class={styles.sGreen}>{formatNumberWithComma(item.participationNum || 0)}</span><i>人</i>
+                    </div>
+                    <p>报名参加</p>
+                  </li>     
+                  <li>
+                    <div class={styles.icTop}>
+                      <span class={styles.sGreen}>{Number(item.participationRate || 0).toFixed(2)}%</span>
+                    </div>
+                    <p>参加率</p>
+                  </li>                                               
+                </ul>
+              </div>
+            ))}
+          </div> : 
+          <OEmpty description="暂无内容" class={styles.emptyC} />
+        }
+        {/* 区域 */}
+        <Popup
+          v-model:show={forms.areaStatus}
+          position="bottom"
+          round
+          safeAreaInsetBottom
+          lazyRender={false}
+          class={'popupBottomSearch'}
+          onOpen={() => {
+            forms.areaPopupShow = true;
+          }}
+          onClosed={() => {
+            forms.areaPopupShow = false;
+          }}>
+          {forms.areaPopupShow && (
+            <Picker
+              showToolbar
+              v-model={forms.areaOptionIndex}
+              columns={forms.areaColumns}
+              onCancel={() => (forms.areaStatus = false)}
+              onConfirm={(val: any) => {
+                // forms.gradeAndClassIndex = [val.selectedOptions[0].value, val.selectedOptions[1].value]
+                // forms.currentArea = val.selectedOptions[0].text;
+                // forms.currentClass = val.selectedOptions[1].text;
+                forms.currentArea = val.selectedOptions[0].text
+                forms.areaOptionIndex = [val.selectedOptions[0].value]
+                forms.areaIdx = val.selectedOptions[0].value
+                forms.areaStatus = false;
+                forms.currentAreaInfo = forms.areaList[val.selectedOptions[0].value]
+                queryInfo()
+                getList()
+                console.log('选择1111',val,forms.areaOptionIndex)
+              }}
+            />
+          )}
+        </Popup>
+
+        {/* <MWxTip /> */}
+      </div>
+    );
+  }
+});

+ 1 - 1
vite.config.ts

@@ -14,7 +14,7 @@ function resolve(dir: string) {
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
 // const proxyUrl = 'https://test.lexiaoya.cn/';
-const proxyUrl = 'https://test.kt.colexiu.com/';
+const proxyUrl = 'https://dev.kt.colexiu.com/';
 // const proxyUrl = 'http://192.168.3.143:7093/';
 // const proxyUrl = 'https://dev.kt.colexiu.com/';
 export default defineConfig({