فهرست منبع

数据总览页面样式调整,权限调整(年份、取消报到)

hzj18279462576@163.com 1 ماه پیش
والد
کامیت
bbb7ad6ff9

+ 2 - 2
src/utils/request.js

@@ -82,8 +82,8 @@ export const request = (
   });
 };
 
-const api = "/welcome/welcome_api"; // 代理地址
-// const api = "/welcome/api"; // 代理地址
+// const api = "/welcome/welcome_api"; // 代理地址
+const api = "/welcome/api"; // 代理地址
 // 请求封装
 export const https = {
   get(url, flag, params = {}) {

+ 1 - 1
src/views/bed/bed.vue

@@ -134,7 +134,7 @@
               <el-option label="否" value="1" />
             </el-select>
           </div>
-          <div class="condition">
+          <div class="condition" v-if="store.BtnRole('bedManagementSetting18')">
             <span>年份 :</span>
             <el-select
               clearable

+ 1 - 1
src/views/caller/caller.vue

@@ -55,7 +55,7 @@
               value-format="YYYY-MM-DD"
             />
           </div>
-          <div class="condition">
+          <div class="condition" v-if="store.BtnRole('visitorManagementSetting6')">
             <span>年份 :</span>
             <el-select
               clearable

+ 282 - 0
src/views/dataOverview/Echart1.vue

@@ -0,0 +1,282 @@
+<template>
+  <div class="content-box1">
+    <div class="chart-header">
+      <span class="col-rate">报到率</span>
+      <span class="col-gap"></span>
+      <span class="col-checked">已报到人数</span>
+      <span class="col-slash">/</span>
+      <span class="col-total">录取总数</span>
+    </div>
+    <div ref="chartRef" class="chart-box"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch, onMounted, onUnmounted, nextTick } from "vue";
+import * as echarts from "echarts";
+import { https } from "@/utils/request"; // 绝对路径
+
+import { ElMessage, ElMessageBox } from "element-plus";
+// 各学院现场报到统计 =================================
+const chartRef = ref(null);
+let chartInstance = null;
+
+const props = defineProps({
+  yearId: {
+    type: [String, Number],
+    default: "",
+  },
+});
+
+// 源数据和截图完全对齐
+const tableData = ref();
+const initChart = async (yearId) => {
+  // chartInstance = echarts.init(chartRef.value);
+  if (!chartInstance) {
+    chartInstance = echarts.init(chartRef.value);
+  }
+
+  let params = {
+    // collegeId: searchInput.college,
+    yearId: yearId ? yearId : "",
+  };
+  let res = await https.get(
+    "/welcome/api/welcomeStudent/studentRegister",
+    "params",
+    params,
+  );
+  if (res.code == 200) {
+    console.log(res, "各学院现场报到统计11111111111111111");
+    console.log(!res.data || res.data.length <= 1);
+
+    tableData.value = [];
+    // 接口数据只有 1 条(汇总/占位)或为空,显示暂无数据
+    if (!res.data || res.data.length <= 1) {
+      tableData.value = [
+        { college: "交通运输学院", rate: 0, checked: 0, total: 0 },
+        { college: "人工智能学院", rate: 0, checked: 0, total: 0 },
+        { college: "智能制造学院", rate: 0, checked: 0, total: 0 },
+        { college: "商学院", rate: 0, checked: 0, total: 0 },
+        { college: "交通与土木工程学院", rate: 0, checked: 0, total: 0 },
+        { college: "艺术与体育", rate: 0, checked: 0, total: 0 },
+        { college: "文法学院", rate: 0, checked: 0, total: 0 },
+        { college: "数智五金产业学院", rate: 0, checked: 0, total: 0 },
+        // { college: "报到总数", rate: 0, checked: 0, total: 0 },
+      ];
+    } else {
+      // 接口有数据后再转格式
+      tableData.value = res.data
+        .filter((item) => item.name !== "报到总数")
+        .map((item) => ({
+          college: item.name,
+          rate: Number(item.rate),
+          checked: item.count,
+          total: item.totalCount,
+        }));
+    }
+    const yNames = tableData.value.map((item) => item.college);
+    const barPercent = tableData.value.map((item) => item.rate);
+    const option = {
+      tooltip: {
+        trigger: "axis",
+        axisPointer: { type: "shadow" },
+        formatter: (params) => {
+          const idx = params[0].dataIndex;
+          const row = tableData.value[idx];
+          return `${row.college}<br/>报到率:${row.rate}%<br/>已报到:${row.checked}<br/>录取总数:${row.total}`;
+        },
+      },
+      grid: {
+        left: 20,
+        right: 230,
+        top: 0,
+        bottom: 10,
+        containLabel: true,
+      },
+      xAxis: {
+        type: "value",
+        max: 100,
+        axisLine: { show: false },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: { show: false },
+      },
+      yAxis: {
+        type: "category",
+        inverse: false,
+        data: yNames,
+        axisLine: { show: false },
+        axisTick: { show: false },
+        axisLabel: { fontSize: 14, color: "#333", margin: 12 },
+      },
+      series: [
+        {
+          // 灰色 100% 背景条,label 挂在它上面保证所有行对齐
+          name: "总数",
+          type: "bar",
+          data: yNames.map(() => 100),
+          barWidth: 12,
+          itemStyle: { color: "#E5E5E5", borderRadius: [0, 4, 4, 0] },
+          silent: true,
+          animation: false,
+          label: {
+            show: true,
+            position: "right",
+            distance: 15,
+            formatter: (params) => {
+              const row = tableData.value[params.dataIndex];
+              return `{rate|${row.rate}%}{gap| }{numBlue|${row.checked}}{slash| / }{numBlack|${row.total}}`;
+            },
+            rich: {
+              rate: {
+                color: "#C82423",
+                fontSize: 14,
+                fontWeight: "bold",
+                width: 60,
+                align: "right",
+              },
+              gap: { width: 16 },
+              numBlue: {
+                color: "#1967E7",
+                fontSize: 14,
+                fontWeight: "bold",
+                width: 40,
+                align: "right",
+              },
+              slash: {
+                color: "#111",
+                fontSize: 14,
+                width: 20,
+                align: "center",
+              },
+              numBlack: {
+                color: "#111111",
+                fontSize: 14,
+                fontWeight: "bold",
+                width: 50,
+                align: "right",
+              },
+            },
+          },
+        },
+        {
+          // 蓝色报到率进度条
+          name: "报到率",
+          type: "bar",
+          data: barPercent,
+          barWidth: 12,
+          barGap: "-100%",
+          itemStyle: { color: "#1967E7", borderRadius: [0, 4, 4, 0] },
+          label: { show: false },
+        },
+      ],
+    };
+    chartInstance.setOption(option);
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+const handleResize = () => {
+  chartInstance && chartInstance.resize();
+};
+// 监听 yearId 变化,实时刷新
+watch(
+  () => props.yearId,
+  (newVal) => {
+    if (newVal) {
+      initChart(newVal);
+    } else {
+      if (chartInstance) initChart();
+    }
+  },
+  { immediate: true }, // 组件初始化时立即执行一次
+);
+onMounted(() => {
+  nextTick(() => {
+    initChart(props.yearId);
+  });
+  window.addEventListener("resize", handleResize);
+});
+
+onUnmounted(() => {
+  if (chartInstance) {
+    chartInstance.dispose();
+    chartInstance = null;
+  }
+  window.removeEventListener("resize", handleResize);
+});
+</script>
+
+<style scoped lang="scss">
+.content-box1 {
+  width: calc(100% - 40px);
+  // height: calc(100% - 105px);
+  margin: 20px auto 0;
+  background-color: #ffffff;
+  color: #000;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+
+  .chart-box {
+    position: relative;
+    width: 100%;
+    height: 360px;
+  }
+
+  .chart-header {
+    height: 30px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    // margin-right: 20px;
+    font-size: 13px;
+    color: #666;
+    font-weight: bold;
+    z-index: 10;
+    user-select: none;
+    margin-right: 18px;
+
+    span {
+      display: inline-block;
+    }
+    .col-rate {
+      width: 60px;
+      text-align: right;
+    }
+    .col-gap {
+      width: 16px;
+    }
+    .col-checked {
+      width: 66px;
+      text-align: right;
+    }
+    .col-slash {
+      width: 20px;
+      text-align: center;
+    }
+    .col-total {
+      width: 59px;
+      text-align: right;
+    }
+  }
+
+  .no-data {
+    position: absolute;
+    inset: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #999;
+    font-size: 14px;
+    background: #fff;
+    pointer-events: none;
+  }
+}
+</style>

+ 252 - 0
src/views/dataOverview/Echart2.vue

@@ -0,0 +1,252 @@
+<template>
+  <div class="content-box1">
+    <div
+      class="total"
+      style="width: 88%; display: flex; justify-content: space-between;margin-left: 20px;"
+    >
+      <span>
+        <span
+          >本科现场报到率 :
+          <span style="font-size: 20px; margin-right: 20px"
+            >{{ levelRate[0] }}%</span
+          ></span
+        >
+        <span
+          >专升本现场报到率 :
+          <span style="font-size: 20px; margin-right: 20px"
+            >{{ levelRate[1] }}%</span
+          ></span
+        >
+        <span
+          >专科现场报到率 :
+          <span style="font-size: 20px">{{ levelRate[2] }}%</span></span
+        >
+      </span>
+    </div>
+    <div ref="levelRegisterRef" class="chart-wrap"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref,reactive,watch, onMounted, onUnmounted, nextTick } from "vue";
+import * as echarts from "echarts";
+import { https } from "@/utils/request"; // 绝对路径
+
+import { ElMessage, ElMessageBox } from "element-plus";
+const levelRate = ref([]);
+
+const levelRegisterRef = ref(null);
+let levelRegisterEcharts = null;
+
+const props = defineProps({
+  yearId: {
+    type: [String, Number],
+    default: ''
+  }
+})
+
+// 监听 yearId 变化,实时刷新
+watch(
+  () => props.yearId,
+  (newVal) => {
+    if (newVal) {
+      levelRegister(newVal)
+    }else{
+      if(levelRegisterEcharts)
+      levelRegister()
+    }
+  },
+  { immediate: true } // 组件初始化时立即执行一次
+)
+
+// 新生报到层次统计
+const levelRegister = async (yearId) => {
+  levelRegisterEcharts = echarts.init(levelRegisterRef.value);
+  let params = {
+    // collegeId: searchInput.college,
+    yearId: yearId,
+  };
+  let res = await https.get(
+    "/welcome/api/welcomeStudent/levelRegister",
+    "params",
+    params,
+  );
+  console.log(res, "新生报到层次统计");
+  if (res.code == 200) {
+    const data = {
+      name: [],
+      count: [],
+      totalCount: [],
+    };
+    levelRate.value = [];
+    // let arr = res.data.slice(0, -1).reverse();
+    let sortedData = res.data.slice(0, -1);
+
+    // 定义自定义排序顺序
+    const customOrder = ["本科", "专升本", "专科"];
+
+    // 对数据进行排序
+    const arr = sortedData.sort((a, b) => {
+      return customOrder.indexOf(a.name) - customOrder.indexOf(b.name);
+    });
+    console.log(sortedData, "整合数据");
+
+    let manTotal = 0;
+    arr.forEach((i) => {
+      manTotal += i.count;
+      data.name.push(i.name);
+      data.count.push(i.count);
+      data.totalCount.push(i.totalCount);
+      levelRate.value.push(i.rate);
+    });
+    console.log(data, levelRate.value, "新生报到整合数据");
+    const option = {
+      // title: {
+      //   text: `本科现场报到率${levelRate.value[0]}%、专升本现场报到率、专科现场报到率`,
+      // },
+      legend: {
+        right: "5%",
+        padding: [
+          15, // 上
+          10, // 右
+          15, // 下
+          10, // 左
+        ],
+      },
+      tooltip: {
+        trigger: "axis",
+        axisPointer: {
+          type: "shadow",
+        },
+      },
+      grid: {
+        left: "5%",
+        right: "5%",
+        top: "10%",
+        bottom: "5%",
+        containLabel: true,
+      },
+      xAxis: {
+        name: "人",
+        type: "value",
+        boundaryGap: [0, 0.01],
+      },
+      yAxis: {
+        type: "category",
+        data: data.name,
+        axisLine: {
+          show: false,
+        },
+        axisTick: {
+          show: false,
+        },
+      },
+      series: [
+        {
+          name: "总人数",
+          type: "bar",
+          data: data.totalCount,
+          label: {
+            // 关键:把 label 打开
+            show: true,
+            position: "center", // 'inside' 在柱子内部,'top' 在柱子顶端
+            formatter: "{c}", // {c} 就是当前柱子的数值
+            position: "right", // 关键:把数值放在柱子右边
+          },
+        },
+        {
+          name: "已报到人数",
+          type: "bar",
+          data: data.count,
+          label: {
+            // 关键:把 label 打开
+            show: true,
+            position: "center", // 'inside' 在柱子内部,'top' 在柱子顶端
+            formatter: "{c}", // {c} 就是当前柱子的数值
+            position: "right", // 关键:把数值放在柱子右边
+          },
+        },
+      ],
+    };
+   
+    levelRegisterEcharts.setOption(option);
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+const handleResize = () => {
+  if (levelRegisterRef.value) levelRegisterEcharts.resize();
+};
+
+onMounted(() => {
+  nextTick(() => {
+    levelRegister();
+  });
+  window.addEventListener("resize", handleResize);
+});
+
+onUnmounted(() => {
+  if (levelRegisterRef.value) levelRegisterEcharts.dispose();
+  window.removeEventListener("resize", handleResize);
+});
+</script>
+
+<style scoped lang="scss">
+.content-box1 {
+  width: calc(100% - 40px);
+  // height: calc(100% - 105px);
+  margin: 20px auto 0;
+  background-color: #ffffff;
+  color: #000;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+
+  .chart-wrap {
+    position: relative;
+    width: 100%;
+    height: 360px;
+  }
+
+  .chart-inner {
+    width: 100%;
+    height: 100%;
+  }
+
+  .chart-header {
+    position: absolute;
+    top: 10px;
+    right: 20px;
+    display: flex;
+    align-items: center;
+    font-size: 13px;
+    color: #666;
+    font-weight: bold;
+    z-index: 10;
+    user-select: none;
+    pointer-events: none;
+
+    span {
+      display: inline-block;
+    }
+    .h-checked {
+      width: 50px;
+      text-align: right;
+    }
+    .h-slash {
+      width: 20px;
+      text-align: center;
+    }
+    .h-total {
+      width: 60px;
+      text-align: left;
+    }
+  }
+}
+</style>

+ 386 - 0
src/views/dataOverview/Echart3 copy.vue

@@ -0,0 +1,386 @@
+<template>
+  <div class="content-box1">
+    <div class="chart-wrap">
+      <!-- 右上角表头(HTML 绝对定位,确保压在 canvas 上方) -->
+      <div class="chart-header">
+        <span class="h-checked">已入住数</span>
+        <span class="h-slash">/</span>
+        <span class="h-total">床位开放总数</span>
+      </div>
+
+      <!-- ECharts 挂载点 -->
+      <div ref="stayChartRef" class="chart-inner"></div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch, onUnmounted, nextTick } from "vue";
+import * as echarts from "echarts";
+
+import { https } from "@/utils/request"; // 绝对路径
+import { ElMessage, ElMessageBox } from "element-plus";
+
+const stayChartRef = ref(null);
+let stayChartInstance = null;
+
+const props = defineProps({
+  yearId: {
+    type: [String, Number],
+    default: "",
+  },
+});
+
+// 监听 yearId 变化,实时刷新
+watch(
+  () => props.yearId,
+  (newVal) => {
+    if (newVal) {
+      stayInitChart(newVal);
+    }else{
+      if(stayChartInstance)
+      stayInitChart()
+    }
+  },
+  { immediate: true }, // 组件初始化时立即执行一次
+);
+// ============================================================
+// 数据:8 个学院,每个学院男 / 女两行(无空行占位)
+// 实际项目里换成接口返回,再按「男 / 女」拆两行即可
+//   res.data.forEach(item => {
+//     stayData.value.push({ college: item.collegeName, gender: "男", checked: item.manCount, total: item.manTotal });
+//     stayData.value.push({ college: item.collegeName, gender: "女", checked: item.girlCount, total: item.girlTotal });
+//   });
+// ============================================================
+const stayData = ref([
+  { college: "交通运输学院", gender: "男", checked: 0, total: 0 },
+  { college: "交通运输学院", gender: "女", checked: 0, total: 0 },
+  { college: "人工智能学院", gender: "男", checked: 0, total: 0 },
+  { college: "人工智能学院", gender: "女", checked: 0, total: 0 },
+  { college: "智能制造学院", gender: "男", checked: 0, total: 0 },
+  { college: "智能制造学院", gender: "女", checked: 0, total: 0 },
+  { college: "商学院", gender: "男", checked: 0, total: 0 },
+  { college: "商学院", gender: "女", checked: 0, total: 0 },
+  { college: "交通与土木工程学院", gender: "男", checked: 0, total: 0 },
+  { college: "交通与土木工程学院", gender: "女", checked: 0, total: 0 },
+  { college: "艺术与体育", gender: "男", checked: 0, total: 0 },
+  { college: "艺术与体育", gender: "女", checked: 0, total: 0 },
+  { college: "文法学院", gender: "男", checked: 0, total: 0 },
+  { college: "文法学院", gender: "女", checked: 0, total: 0 },
+  { college: "数智五金产业学院", gender: "男", checked: 0, total: 0 },
+  { college: "数智五金产业学院", gender: "女", checked: 0, total: 0 },
+]);
+
+// Y 轴标签:学院名只在「男」行显示,女行用 spacer 占位对齐
+const yData = stayData.value.map((item) => `${item.college}|${item.gender}`);
+
+// 用 ECharts graphic 在学院之间精确画虚线分隔
+// 因为 markLine 的浮点 yAxis 在 category 轴上不一定生效
+const drawSeparators = () => {
+  if (!stayChartInstance) return;
+  const lines = [];
+  for (let i = 1; i < stayData.value.length; i += 2) {
+    if (i + 1 >= stayData.value.length) continue;
+    const y1 = stayChartInstance.convertToPixel({ yAxisIndex: 0 }, i);
+    const y2 = stayChartInstance.convertToPixel({ yAxisIndex: 0 }, i + 1);
+    const x1 = stayChartInstance.convertToPixel({ xAxisIndex: 0 }, 0);
+    const x2 = stayChartInstance.convertToPixel({ xAxisIndex: 0 }, 2200);
+    if (y1 && y2 && x1 && x2) {
+      lines.push({
+        type: "line",
+        shape: { x1, y1: (y1 + y2) / 2, x2, y2: (y1 + y2) / 2 },
+        style: { stroke: "#ddd", lineWidth: 1, lineDash: [4, 4] },
+        z: 100,
+        silent: true,
+      });
+    }
+  }
+  stayChartInstance.setOption({ graphic: lines }, false);
+};
+
+const stayInitChart = async (yearId) => {
+  stayChartInstance = echarts.init(stayChartRef.value);
+  let params = {
+    // collegeId: searchInput.college,
+    yearId: yearId,
+  };
+  let res = await https.get(
+    "/welcome/api/welcomeStudent/collegeStudentStay",
+    "params",
+    params,
+  );
+  console.log(res, "各学院住宿情况统计3333333333333333333333333333");
+  if (res.code == 200) {
+    const rawList = res.data;
+    const tempArr = [];
+    const sortOrder = [
+      "交通运输学院",
+      "人工智能学院",
+      "智能制造学院",
+      "商学院",
+      "交通与土木工程学院",
+      "艺术与体育",
+      "文法学院",
+      "数智五金产业学院",
+    ];
+    rawList.forEach((item) => {
+      let name = item.collegeName;
+      if (name === "艺术与体育学院") name = "艺术与体育";
+      tempArr.push({
+        college: name,
+        gender: "男",
+        checked: item.maleCheck,
+        total: item.maleTotal,
+        
+      });
+      tempArr.push({
+        college: name,
+        gender: "女",
+        checked: item.femaleCheck,
+        total: item.femaleTotal,
+      });
+    });
+    tempArr.sort((a) => sortOrder.indexOf(a.college));
+    stayData.value = tempArr;
+
+    const option = {
+      tooltip: {
+        trigger: "axis",
+        axisPointer: { type: "shadow" },
+        formatter: (params) => {
+          const row = stayData.value[params[0].dataIndex];
+          return `${row.college} - ${row.gender}<br/>已入住:${row.checked}<br/>总数:${row.total}`;
+        },
+      },
+      grid: {
+        left: 10,
+        right: 160,
+        top: 40,
+        bottom: 10,
+        containLabel: true,
+      },
+      xAxis: {
+        type: "value",
+        max: 2200,
+        axisLine: { show: false },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: { show: false },
+      },
+      yAxis: {
+        type: "category",
+        inverse: true, // 从上到下排列,学院名在最上面(男)行显示
+        data: yData,
+        axisLine: { show: false },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: {
+          margin: 12,
+          formatter: (value) => {
+            const [college, gender] = value.split("|");
+            if (gender === "男") {
+              return `{college|${college}}{gap|}{genderMale|男}`;
+            }
+            return `{spacer|}{gap|}{genderFemale|女}`;
+          },
+          rich: {
+            college: {
+              color: "#333",
+              fontSize: 14,
+              fontWeight: "bold",
+              width: 140,
+            },
+            spacer: { width: 140 },
+            gap: { width: 10 },
+            genderMale: { color: "#2F7FFF", fontSize: 12, width: 20 },
+            genderFemale: { color: "#FF6B6B", fontSize: 12, width: 20 },
+          },
+        },
+      },
+      series: [
+        {
+          // 灰色总床位背景条
+          name: "总床位",
+          type: "bar",
+          data: stayData.value.map((item) => item.total),
+          barWidth: 8,
+          itemStyle: { color: "#E8E8E8", borderRadius: [0, 4, 4, 0] },
+          silent: true,
+          animation: false,
+          label: {
+            show: true,
+            position: "right",
+            distance: 15,
+            formatter: (params) => {
+              const row = stayData.value[params.dataIndex];
+              const checkedKey =
+                row.gender === "男" ? "checkedMale" : "checkedFemale";
+              return `{${checkedKey}|${row.checked}}{slash| / }{total|${row.total}}`;
+            },
+            rich: {
+              checkedMale: {
+                color: "#2F7FFF",
+                fontSize: 13,
+                fontWeight: "bold",
+                width: 50,
+                align: "right",
+              },
+              checkedFemale: {
+                color: "#FF6B6B",
+                fontSize: 13,
+                fontWeight: "bold",
+                width: 50,
+                align: "right",
+              },
+              slash: {
+                color: "#333",
+                fontSize: 13,
+                width: 20,
+                align: "center",
+              },
+              total: {
+                color: "#333",
+                fontSize: 13,
+                fontWeight: "bold",
+                width: 60,
+                align: "left",
+              },
+            },
+          },
+        },
+        {
+          // 已入住进度条:男蓝女橙
+          name: "已入住",
+          type: "bar",
+          data: stayData.value.map((item) => item.checked),
+          barWidth: 8,
+          barGap: "-100%",
+          itemStyle: {
+            color: (params) =>
+              stayData.value[params.dataIndex].gender === "男"
+                ? "#2F7FFF"
+                : "#FF6B6B",
+            borderRadius: [0, 4, 4, 0],
+          },
+          label: { show: false },
+        },
+      ],
+    };
+
+    stayChartInstance.setOption(option);
+
+    // 渲染完成后,用 graphic 精确绘制学院间分隔线
+    // 注意:finished 事件处于 ECharts 主流程,里面不能同步 setOption,用 setTimeout 解耦
+    stayChartInstance.on("finished", function drawOnce() {
+      setTimeout(drawSeparators, 0);
+      stayChartInstance.off("finished", drawOnce);
+    });
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+const handleResize = () => {
+  if (stayChartRef.value) {
+    stayChartInstance.resize();
+    // resize 后重新绘制分隔线
+    stayChartInstance.on("finished", function drawOnce() {
+      setTimeout(drawSeparators, 0);
+      stayChartInstance.off("finished", drawOnce);
+    });
+  }
+};
+
+onMounted(() => {
+  nextTick(() => {
+    stayInitChart();
+  });
+  window.addEventListener("resize", handleResize);
+});
+
+onUnmounted(() => {
+  if (stayChartRef.value) stayChartInstance.dispose();
+  window.removeEventListener("resize", handleResize);
+});
+</script>
+
+<style scoped lang="scss">
+.content-box1 {
+  width: calc(100% - 40px);
+  height: calc(100% - 105px);
+  margin: 20px auto 0;
+  background-color: #ffffff;
+  color: #000;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+
+  .system_title {
+    height: 45px;
+    border-radius: 0 12px 0 0;
+    background: rgb(243, 249, 255);
+    margin-bottom: 15px;
+    display: flex;
+    align-items: center;
+
+    h4 {
+      font-size: 15px;
+      padding: 0 5px 0 20px;
+      margin: 0;
+    }
+  }
+
+  .chart-wrap {
+    position: relative;
+    width: 100%;
+    height: 480px;
+  }
+
+  .chart-inner {
+    width: 100%;
+    height: 100%;
+  }
+
+  .chart-header {
+    position: absolute;
+    top: 10px;
+    right: 20px;
+    width: 180px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    white-space: nowrap;
+    font-size: 13px;
+    color: #666;
+    font-weight: bold;
+    z-index: 10;
+    user-select: none;
+    pointer-events: none;
+
+    span {
+      display: inline-block;
+      flex-shrink: 0;
+    }
+
+    .h-checked {
+      width: 70px;
+      text-align: right;
+    }
+
+    .h-slash {
+      width: 20px;
+      text-align: center;
+    }
+
+    .h-total {
+      width: 90px;
+      text-align: left;
+    }
+  }
+}
+</style>

+ 357 - 0
src/views/dataOverview/Echart3.vue

@@ -0,0 +1,357 @@
+<template>
+  <div class="content-box1">
+    <div class="chart-wrap">
+      <!-- 右上角表头 -->
+      <div class="chart-header">
+        <span class="h-rate">入住率</span>
+        <span class="h-checked">已入住数</span>
+        <span class="h-slash">/</span>
+        <span class="h-total">床位开放总数</span>
+      </div>
+      <!-- ECharts 挂载点 -->
+      <div ref="stayChartRef" class="chart-inner"></div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch, onUnmounted, nextTick, computed } from "vue";
+import * as echarts from "echarts";
+import { https } from "@/utils/request";
+import { ElMessage } from "element-plus";
+
+const stayChartRef = ref(null);
+let stayChartInstance = null;
+
+const props = defineProps({
+  yearId: {
+    type: [String, Number],
+    default: "",
+  },
+});
+
+const stayData = ref([]);
+
+// 安全计算Y轴标签,空数据不会报错
+const yData = computed(() => {
+  if (!Array.isArray(stayData.value) || stayData.value.length === 0) return [];
+  return stayData.value.map((item) => `${item.college}|${item.gender}`);
+});
+
+// 学院分隔虚线
+const drawSeparators = () => {
+  if (!stayChartInstance || !stayData.value.length) return;
+  const lines = [];
+  for (let i = 1; i < stayData.value.length; i += 2) {
+    if (i + 1 >= stayData.value.length) continue;
+    const y1 = stayChartInstance.convertToPixel({ yAxisIndex: 0 }, i);
+    const y2 = stayChartInstance.convertToPixel({ yAxisIndex: 0 }, i + 1);
+    const x1 = stayChartInstance.convertToPixel({ xAxisIndex: 0 }, 0);
+    const x2 = stayChartInstance.convertToPixel({ xAxisIndex: 0 }, 100);
+    if (y1 && y2 && x1 && x2) {
+      lines.push({
+        type: "line",
+        shape: { x1, y1: (y1 + y2) / 2, x2, y2: (y1 + y2) / 2 },
+        style: { stroke: "#ddd", lineWidth: 1, lineDash: [4, 4] },
+        z: 100,
+        silent: true,
+      });
+    }
+  }
+  stayChartInstance.setOption({ graphic: lines }, false);
+};
+
+// 图表初始化函数,增加DOM判空兜底
+const stayInitChart = async (yearId) => {
+  // 核心修复:先判断DOM是否存在,不存在直接终止,防止invalid dom
+  if (!stayChartRef.value) return;
+
+  // 实例存在则销毁重建,避免重复初始化报错
+  if (stayChartInstance) {
+    stayChartInstance.dispose();
+    stayChartInstance = null;
+  }
+  stayChartInstance = echarts.init(stayChartRef.value);
+
+  let params = { yearId };
+  let res = await https.get("/welcome/api/welcomeStudent/collegeStudentStay", "params", params);
+  console.log(res, "各学院住宿情况统计");
+
+  if (res.code == 200) {
+    const rawList = res.data;
+    // 空数据兜底模板
+    const emptyTemplate = [
+      { college: "交通运输学院", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "交通运输学院", gender: "女", checked: 0, total: 0, rate: 0 },
+      { college: "人工智能学院", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "人工智能学院", gender: "女", checked: 0, total: 0, rate: 0 },
+      { college: "智能制造学院", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "智能制造学院", gender: "女", checked: 0, total: 0, rate: 0 },
+      { college: "商学院", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "商学院", gender: "女", checked: 0, total: 0, rate: 0 },
+      { college: "交通与土木工程学院", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "交通与土木工程学院", gender: "女", checked: 0, total: 0, rate: 0 },
+      { college: "艺术与体育", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "艺术与体育", gender: "女", checked: 0, total: 0, rate: 0 },
+      { college: "文法学院", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "文法学院", gender: "女", checked: 0, total: 0, rate: 0 },
+      { college: "数智五金产业学院", gender: "男", checked: 0, total: 0, rate: 0 },
+      { college: "数智五金产业学院", gender: "女", checked: 0, total: 0, rate: 0 },
+    ];
+
+    if (!rawList || rawList.length === 0) {
+      stayData.value = emptyTemplate;
+    } else {
+      const tempArr = [];
+      const sortOrder = [
+        "交通运输学院",
+        "人工智能学院",
+        "智能制造学院",
+        "商学院",
+        "交通与土木工程学院",
+        "艺术与体育",
+        "文法学院",
+        "数智五金产业学院",
+      ];
+      rawList.forEach((item) => {
+        let name = item.collegeName === "艺术与体育学院" ? "艺术与体育" : item.collegeName;
+        tempArr.push({
+          college: name,
+          gender: "男",
+          checked: item.maleCheck,
+          total: item.maleTotal,
+          rate: item.maleRate,
+        });
+        tempArr.push({
+          college: name,
+          gender: "女",
+          checked: item.femaleCheck,
+          total: item.femaleTotal,
+          rate: item.femaleRate,
+        });
+      });
+      tempArr.sort((a) => sortOrder.indexOf(a.college));
+      stayData.value = tempArr;
+    }
+
+    const rateList = stayData.value.map((item) => item.rate);
+    const bgGrayList = stayData.value.map(() => 100);
+
+    const option = {
+      tooltip: {
+        trigger: "axis",
+        axisPointer: { type: "shadow" },
+        formatter: (params) => {
+          const row = stayData.value[params[0].dataIndex];
+          return `${row.college} - ${row.gender}<br/>入住率:${row.rate}%<br/>已入住:${row.checked}<br/>床位总数:${row.total}`;
+        },
+      },
+      grid: {
+        left: 10,
+        right: 230, // 加宽右侧预留空间,文字不会挤压
+        top: 40,
+        bottom: 10,
+        containLabel: true,
+      },
+      xAxis: {
+        type: "value",
+        max: 100,
+        axisLine: { show: false },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: { show: false },
+      },
+      yAxis: {
+        type: "category",
+        inverse: true,
+        data: yData.value,
+        axisLine: { show: false },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: {
+          margin: 12,
+          formatter: (value) => {
+            const [college, gender] = value.split("|");
+            return gender === "男"
+              ? `{college|${college}}{gap|}{genderMale|男}`
+              : `{spacer|}{gap|}{genderFemale|女}`;
+          },
+          rich: {
+            college: { color: "#333", fontSize: 14,  width: 140 },
+            spacer: { width: 140 },
+            gap: { width: 10 },
+            genderMale: { color: "#2F7FFF", fontSize: 12, width: 20 },
+            genderFemale: { color: "#FF6B6B", fontSize: 12, width: 20 },
+          },
+        },
+      },
+      series: [
+        {
+          // 灰色 100% 背景条 —— label 挂在这里,不管数据有没有都在右侧
+          name: "总床位基底",
+          type: "bar",
+          data: bgGrayList,
+          barWidth: 8,
+          itemStyle: { color: "#E8E8E8", borderRadius: [0, 4, 4, 0] },
+          silent: true,
+          animation: false,
+          label: {
+            show: true,
+            position: "right",
+            distance: 20,
+            formatter: (params) => {
+              const row = stayData.value[params.dataIndex];
+              const colorKey = row.gender === "男" ? "blueNum" : "redNum";
+              return `{rateTxt|${row.rate}%}  {${colorKey}|${row.checked}}{slash| / }{totalTxt|${row.total}}`;
+            },
+            rich: {
+              rateTxt: {
+                color: "#d93025",
+                fontSize: 13,
+                fontWeight: "bold",
+                width: 40,
+              },
+              blueNum: {
+                color: "#2F7FFF",
+                fontSize: 13,
+                fontWeight: "bold",
+                width: 60,
+                align: "right",
+              },
+              redNum: {
+                color: "#FF6B6B",
+                fontSize: 13,
+                fontWeight: "bold",
+                width: 60,
+                align: "right",
+              },
+              slash: {
+                color: "#333",
+                fontSize: 13,
+                width: 15,
+                align: "center",
+              },
+              totalTxt: {
+                color: "#333",
+                fontSize: 13,
+                fontWeight: "bold",
+                width: 80,
+              },
+            },
+          },
+        },
+        {
+          // 已入住占比:男蓝女橙
+          name: "已入住占比",
+          type: "bar",
+          data: rateList,
+          barWidth: 8,
+          barGap: "-100%",
+          itemStyle: {
+            color: (params) => stayData.value[params.dataIndex].gender === "男" ? "#2F7FFF" : "#FF6B6B",
+            borderRadius: [0, 4, 4, 0],
+          },
+          label: { show: false },
+        },
+      ],
+    };
+    stayChartInstance.setOption(option);
+    stayChartInstance.on("finished", function drawOnce() {
+      setTimeout(drawSeparators, 0);
+      stayChartInstance.off("finished", drawOnce);
+    });
+  } else {
+    ElMessage({ type: "error", showClose: true, message: res.message, center: true });
+  }
+};
+
+const handleResize = () => {
+  if (stayChartRef.value && stayChartInstance) {
+    stayChartInstance.resize();
+    stayChartInstance.on("finished", function drawOnce() {
+      setTimeout(drawSeparators, 0);
+      stayChartInstance.off("finished", drawOnce);
+    });
+  }
+};
+
+// 监听年份,去掉immediate,挂载完成后再执行
+watch(
+  () => props.yearId,
+  (newVal) => {
+    nextTick(() => stayInitChart(newVal));
+  }
+);
+
+onMounted(() => {
+  // 仅DOM挂载完成后执行初始化,彻底解决DOM不存在问题
+  nextTick(() => stayInitChart(props.yearId));
+  window.addEventListener("resize", handleResize);
+});
+
+onUnmounted(() => {
+  if (stayChartInstance) stayChartInstance.dispose();
+  window.removeEventListener("resize", handleResize);
+});
+</script>
+
+<style scoped lang="scss">
+.content-box1 {
+  width: calc(100% - 40px);
+  height: calc(100% - 105px);
+  margin: 20px auto 0;
+  background-color: #ffffff;
+  color: #000;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+
+  .system_title {
+    height: 45px;
+    border-radius: 0 12px 0 0;
+    background: rgb(243, 249, 255);
+    margin-bottom: 15px;
+    display: flex;
+    align-items: center;
+    h4 {
+      font-size: 15px;
+      padding: 0 5px 0 20px;
+      margin: 0;
+    }
+  }
+
+  .chart-wrap {
+    position: relative;
+    width: 100%;
+    height: 480px;
+  }
+  .chart-inner {
+    width: 100%;
+    height: 100%;
+  }
+  .chart-header {
+    position: absolute;
+    top: 10px;
+    right: 20px;
+    width: 260px; /* 和grid.right保持一致,表头对齐下方文字 */
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    white-space: nowrap;
+    font-size: 13px;
+    color: #666;
+    font-weight: bold;
+    z-index: 10;
+    user-select: none;
+    pointer-events: none;
+    span {
+      display: inline-block;
+      flex-shrink: 0;
+    }
+    .h-rate { width: 70px; text-align: right; }
+    .h-checked { width: 70px; text-align: right; }
+    .h-slash { width: 25px; text-align: center; }
+    .h-total { width: 60px; text-align: left; }
+  }
+}
+</style>

+ 187 - 0
src/views/dataOverview/Echart4.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="content-box1">
+    <div class="gender">
+      <div id="gender" ref="genderRef"></div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref,reactive,watch, onMounted, onUnmounted, nextTick } from "vue";
+import * as echarts from "echarts";
+import { https } from "@/utils/request"; // 绝对路径
+
+import { ElMessage, ElMessageBox } from "element-plus";
+const genderRef = ref(null);
+let genderChartInstance = null;
+
+const props = defineProps({
+  yearId: {
+    type: [String, Number],
+    default: ''
+  }
+})
+// 监听 yearId 变化,实时刷新
+watch(
+  () => props.yearId,
+  (newVal) => {
+    if (newVal) {
+      gender(newVal)
+    }else{
+      if(genderChartInstance)
+      gender()
+    }
+  },
+  { immediate: true } // 组件初始化时立即执行一次
+)
+
+const gender = async (yearId) => {
+  genderChartInstance = echarts.init(genderRef.value);
+  let params = {
+    // accountId: sessionStorage.getItem("accountId"),
+    // collegeId: searchInput.college,
+    yearId:yearId,
+  };
+  let res = await https.get(
+    "/welcome/api/welcomeStudent/studentSexRatio",
+    "params",
+    params,
+  );
+  console.log(res, "学生性别比例");
+  if (res.code == 200) {
+    let data = [];
+    res.data.slice(0, -1).forEach((i) => {
+      if (!i.girlCount) {
+        i.girlCount = 0;
+      } else if (!i.manCount) {
+        i.manCount = 0;
+      }
+      if (i) {
+        data.push(i);
+      }
+    });
+    console.log(data);
+
+    const option = {
+      legend: {
+        right: "5%",
+        padding: [
+          10, // 上
+          10, // 右
+          5, // 下
+          10, // 左
+        ],
+      },
+      tooltip: {},
+      grid: {
+        left: "5%",
+        right: "5%",
+        top: "18%",
+        bottom: "5%",
+        containLabel: true,
+      },
+      dataset: {
+        dimensions: ["collegeName", "manCount", "girlCount"],
+        source: data,
+      },
+      xAxis: {
+        type: "category",
+        axisLine: {
+          show: false,
+        },
+        axisTick: {
+          show: false,
+        },
+        axisLabel: {
+          interval: 0, // 0 = 一个都不省略
+          formatter: (value) => {
+            return value.length > 5
+              ? value.slice(0, 5) + "\n" + value.slice(5)
+              : value;
+          },
+          lineHeight: 16,
+        },
+      },
+      yAxis: {
+        name: "人",
+        // nameLocation:""
+      },
+      series: [
+        {
+          type: "bar",
+          name: "男",
+          barMinWidth: 20,
+          barMaxWidth: 30,
+          label: {
+            show: true,
+            position: "top",
+          },
+          itemStyle: {
+            color: "rgba(0, 97, 255, 1)",
+          },
+        },
+        {
+          type: "bar",
+          name: "女",
+          barMinWidth: 20,
+          barMaxWidth: 30,
+          label: {
+            show: true,
+            position: "top",
+          },
+          itemStyle: {
+            color: "rgba(0, 186, 173, 1)",
+          },
+        },
+      ],
+    };
+    genderChartInstance.setOption(option);
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+const handleResize = () => {
+  if (genderRef.value) genderChartInstance.resize();
+};
+
+onMounted(() => {
+  nextTick(() => {
+    gender();
+  });
+  window.addEventListener("resize", handleResize);
+});
+
+onUnmounted(() => {
+  if (genderRef.value) genderChartInstance.dispose();
+  window.removeEventListener("resize", handleResize);
+});
+</script>
+
+<style scoped lang="scss">
+.content-box1 {
+  width: calc(100% - 40px);
+  height: 480px;
+  margin: 20px auto 0;
+  background-color: #ffffff;
+  color: #000;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+
+  .gender {
+    width: calc(100%);
+    height: 480px;
+    
+    #gender {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+</style>

+ 773 - 0
src/views/dataOverview/dataOverview copy 2.vue

@@ -0,0 +1,773 @@
+<template>
+  <div class="content-box">
+    <div class="left">
+      <div class="cameratxt">
+        数据总览
+        <!-- <el-select
+          @change="collegeChange"
+          v-model="searchInput.college"
+          placeholder="请选择院系"
+          clearable
+          style="width: 250px; margin-left: 20px"
+        >
+          <el-option
+            v-for="i in collegeData"
+            :key="i.id"
+            :label="i.name"
+            :value="i.id"
+          />
+        </el-select> -->
+        <el-select
+          @change="yearChange"
+          clearable
+          v-model="searchInput.yearId"
+          placeholder="请选择年份"
+          style="width: 250px; margin-left: 20px"
+          v-if="store.BtnRole('dataOverview2')"
+        >
+          <el-option
+            v-for="i in yearData"
+            :key="i.id"
+            :label="i.name"
+            :value="i.id"
+          />
+        </el-select>
+      </div>
+    </div>
+
+    <div class="dashboard-wrap">
+      <!-- 循环渲染卡片 -->
+      <div class="stat-card" v-for="(item, idx) in cardList" :key="idx">
+        <!-- 左侧圆形图标 -->
+        <div class="icon-circle" :style="{ background: item.iconBg }">
+          <span class="icon">{{ item.iconText }}</span>
+        </div>
+        <!-- 右侧内容区 -->
+        <div class="card-content">
+          <div class="title">{{ item.title }}</div>
+          <div class="main-num">{{ item.mainValue }}</div>
+          <!-- 底部分两种:纯文字 / 进度条区域 -->
+          <template v-if="item.bottomType === 'text'">
+            <div class="bottom-text">
+              <span class="blue-text">{{ item.bottomTxt }}</span>
+              <span class="red-text">{{ item.bottomNum }}</span>
+            </div>
+          </template>
+          <template v-else-if="item.bottomType === 'progress'">
+            <div
+              class="progress-group"
+              v-for="p in item.progressList"
+              :key="p.label"
+            >
+              <template v-if="idx != 3">
+                <div class="progress-label">{{ p.label }}</div>
+                <div class="progress-bar-track">
+                  <div  
+                    class="progress-bar-fill"
+                    :style="{ width: p.percent, background: p.barColor }"
+                  ></div>
+                </div>
+                <span class="progress-rate">{{ p.percent }}</span>
+              </template>
+              <template v-else>
+                <div class="progress-bar-track progress-bar-track-baodao">
+                  <div
+                    class="progress-bar-fill"
+                    :style="{ width: p.percent, background: p.barColor }"
+                  ></div>
+                </div>
+                <span class="progress-rate progress-rate-baodao">{{
+                  p.percent
+                }}</span>
+              </template>
+            </div>
+          </template>
+        </div>
+      </div>
+    </div>
+
+    <!-- 学院新生报到 -->
+    <div class="echarts">
+      <div class="echarts_register">
+        <div class="system_title">
+          <h4>各学院现场报到统计</h4>
+        </div>
+        <Echart1 :yearId="searchInput.yearId"></Echart1>
+      </div>
+      <div class="echarts_traffic">
+        <div class="system_title">
+          <h4>新生报到层次统计</h4>
+        </div>
+        <Echart2 :yearId="searchInput.yearId"></Echart2>
+      </div>
+    </div>
+
+    <div class="echarts">
+      <div class="echarts_traffic">
+        <div class="system_title">
+          <h4>各学院住宿情况统计</h4>
+        </div>
+        <Echart3 :yearId="searchInput.yearId"></Echart3>
+      </div>
+      <div class="echarts_register">
+        <!-- 性别比例 -->
+        <div class="system_title">
+          <h4>学院新生性别统计</h4>
+        </div>
+        <Echart4 :yearId="searchInput.yearId"></Echart4>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import {
+  ref,
+  watch,
+  reactive,
+  nextTick,
+  onMounted,
+  onBeforeMount,
+  onBeforeUnmount,
+  onUnmounted,
+} from "vue";
+import { useRouter } from "vue-router";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { dayjs } from "element-plus";
+import { useTransition } from "@vueuse/core";
+import lodash from "lodash";
+import * as echarts from "echarts";
+import { https } from "@/utils/request"; // 绝对路径
+
+import { storeToRefs } from "pinia";
+import { useCounterStore } from "@/stores/index";
+import { right } from "@popperjs/core";
+
+import Echart1 from "./Echart1.vue";
+import Echart2 from "./Echart2.vue";
+import Echart3 from "./Echart3.vue";
+import Echart4 from "./Echart4.vue";
+
+const api = ref("");
+const router = useRouter();
+const store = useCounterStore();
+
+const tableData = reactive({
+  list: [],
+});
+const currentPage = ref(1); // 当前页
+const pageSize = ref(10);
+const total = ref(0); // 当前总数
+
+// 为避免解构时失去响应性
+const { name, age, isCollapse, realAge, collegeRole } = storeToRefs(store);
+
+const cardList = ref([]);
+
+const searchInput = reactive({
+  college: "",
+  yearId: "",
+});
+
+// 学院
+const collegeData = ref([]);
+const collegeList = async () => {
+  // collegeData.value = collegeRole.value;
+  let res = await https.get("/welcome/api/welcomeOrg/getColleges", "params");
+  console.log(res, "学院数据");
+  if (res.code == 200) {
+    collegeData.value = res.data;
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+const yearData = ref([]);
+const yearList = async () => {
+  let res = await https.get("/welcome/api/systemSetting/yearDatas", "params");
+  console.log(res, "年份数据");
+  if (res.code == 200) {
+    yearData.value = res.data;
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+// 处置情况
+const studentOverview = async () => {
+  let params = {
+    // accountId: sessionStorage.getItem("accountId"),
+    collegeId: searchInput.college,
+    yearId: searchInput.yearId,
+  };
+  let res = await https.get(
+    "/welcome/api/welcomeStudent/studentOverview",
+    "params",
+    params,
+  );
+  console.log(res, "顶部数据栏");
+  if (res.code == 200) {
+    
+    cardList.value=[
+      {
+        title: "总录取人数",
+        iconText: "👥",
+        iconBg: "#eef4ff",
+        mainValue: res.data.enrollmentTotal,
+        bottomType: "text",
+        bottomTxt: res.data.yearStr,
+        textColorClass: "blue-text",
+      },
+      {
+        title: "现场报到总人数/现场报到率",
+        iconText: "🗹",
+        iconBg: "#90caf9",
+        mainValue: `${res.data.registrationCount} / ${res.data.registrationRate}%`,
+        bottomType: "text",
+        bottomTxt: "剩余人数 ",
+        bottomNum: res.data.notRegistrationCount,
+        textColorClass: "red-text",
+      },
+      {
+        title: "实时已缴费人数/缴费率",
+        iconText: "¥",
+        iconBg: "#e8f8ef",
+        mainValue: `${res.data.totalPayCount} / ${res.data.totalPayRate}%`,
+        bottomType: "progress",
+        progressList: [
+          { label: "全部缴费", percent: `${res.data.payRate}%`, barColor: "#1967e3" },
+          { label: "部分缴费", percent: `${res.data.portionPayRate}%`, barColor: "#ff982b" },
+        ],
+      },
+      {
+        title: "已缴费但未现场报到人数/占比",
+        iconText: "📊",
+        iconBg: "#eef4ff",
+        mainValue: `${res.data.payNotRegistrationCount} / ${res.data.payNotRegistrationRate}%`,
+        bottomType: "progress",
+        progressList: [{ label: "", percent: `${res.data.payNotRegistrationRate}%`, barColor: "#1967e3" }],
+      },
+      {
+        title: "当天预约车辆情况统计",
+        iconText: "🕒",
+        iconBg: "#eef4ff",
+        mainValue: res.data.mvisitorTotal,
+        bottomType: "text",
+        bottomTxt: "当天剩余车位",
+        bottomNum: res.data.mcarTotal,
+        textColorClass: "red-text",
+      },
+    ];
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+const collegeChange = () => {
+  studentOverview();
+};
+const yearChange = () => {
+  studentOverview();
+};
+
+// 学生住宿情况
+const studentStay = async () => {
+  let params = {
+    // accountId: sessionStorage.getItem("accountId"),
+    collegeId: searchInput.college,
+    yearId: searchInput.yearId,
+  };
+  let res = await https.get(
+    "/welcome/api/welcomeStudent/studentStay",
+    "params",
+    params,
+  );
+  console.log(res, "学生住宿情况");
+  if (res.code == 200) {
+    tableData.list = res.data;
+  } else {
+    ElMessage({
+      type: "error",
+      showClose: true,
+      message: res.message,
+      center: true,
+    });
+  }
+};
+
+onMounted(() => {
+  studentOverview();
+  studentStay();
+  collegeList();
+  yearList();
+});
+
+onBeforeMount(() => {});
+onUnmounted(() => {});
+onBeforeUnmount(() => {});
+</script>
+
+<style scoped lang="scss">
+.content-box {
+  // min-width: calc(100% - 40px);
+  width: calc(100% - 40px);
+  height: calc(100% - 105px);
+  margin: 20px auto;
+  background-color: #ffffff;
+  color: #000;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+  .left {
+    height: 60px;
+    margin: 0 30px;
+    border-bottom: 1px solid #ccc;
+    color: #000;
+    font-size: 18px;
+    font-weight: 600;
+
+    .cameratxt {
+      margin-right: 15px;
+      height: 60px;
+      line-height: 60px;
+    }
+  }
+  .system_title {
+    // width: 100%;
+    height: 45px;
+    opacity: 1;
+    border-radius: 0px 12px 0px 0px;
+    background: rgb(243, 249, 255);
+    margin: 15px 20px;
+    display: flex;
+    align-items: center;
+    // justify-content: center;
+    h4 {
+      font-size: 15px;
+      padding: 0 5px 0 20px;
+    }
+    .wain {
+      font-size: 13px;
+      color: rgba(212, 48, 48, 1);
+    }
+  }
+  // 处置情况
+  .dispose {
+    margin: 0 30px;
+    display: flex;
+    justify-content: space-between;
+    list-style: none;
+    padding: 0;
+    li {
+      // max-width: 385px;
+      min-width: 345px;
+      width: 24%;
+      // flex: 1;
+      // margin: 0 10px;
+      height: 133px;
+      border: 1px solid rgba(143, 143, 143, 1);
+      border-radius: 0.98px;
+      background: rgba(250, 252, 255, 1);
+      display: flex;
+      align-items: center;
+      // justify-content: space-around;
+      .el-icon {
+        margin: 0 30px;
+      }
+      .dispose_content {
+        p {
+          margin: 5px 0 10px 0;
+        }
+      }
+      .yuyue {
+        span {
+          cursor: pointer;
+        }
+        .active {
+          color: rgb(248, 130, 11);
+        }
+      }
+    }
+  }
+
+  .dashboard-wrap {
+    display: flex;
+    gap: 10px;
+    padding: 10px 20px;
+    flex-wrap: wrap;
+    // background: #ffffff;
+    .stat-card {
+      background: #fff;
+      border-radius: 14px;
+      padding: 22px 18px;
+      // min-width: 260px;
+      // flex: 1;
+      display: flex;
+      gap: 14px;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+      .icon-circle {
+        width: 58px;
+        height: 58px;
+        border-radius: 50%;
+        flex-shrink: 0;
+        display: grid;
+        place-items: center;
+        font-size: 24px;
+      }
+      .card-content {
+        flex: 1;
+        padding: 0 10px;
+        display: flex;
+        flex-direction: column;
+        // justify-content: space-between;
+        .title {
+          font-size: 16px;
+          color: #333;
+          margin-bottom: 8px;
+          line-height: 1.35;
+        }
+        .main-num {
+          font-size: 32px;
+          font-weight: 600;
+          color: #111;
+          // margin-bottom: 10px;
+        }
+        /* 底部普通文字样式 */
+        .bottom-text {
+          font-size: 14px;
+          margin-top: 20px;
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+        }
+        .blue-text {
+          color: #1967e3;
+        }
+        .red-text {
+          color: #d93025;
+          font-size: 18px;
+          font-weight: 600;
+        }
+        /* 进度条样式 */
+        .progress-group {
+          margin: 6px 0;
+          display: flex;
+          align-items: center;
+          gap: 8px;
+        }
+        .progress-label {
+          width: 50px;
+          font-size: 12px;
+          color: #333;
+          flex-shrink: 0;
+        }
+        .progress-bar-track {
+          // flex: 1;
+          width: 150px;
+          height: 10px;
+          background: #e9ecef;
+          border-radius: 999px;
+          position: relative;
+          overflow: visible;
+        }
+        .progress-bar-track-baodao {
+          height: 15px;
+        }
+        .progress-bar-fill {
+          height: 100%;
+          border-radius: 999px;
+        }
+        .progress-rate {
+          // position: absolute;
+          right: -38px;
+          top: -3px;
+          font-size: 14px;
+          color: #d93025;
+        }
+        .progress-rate-baodao {
+          top: 0px !important;
+        }
+      }
+    }
+  }
+
+  // 学院新生报到情况
+  .echarts {
+    display: flex;
+
+    // justify-content: space-around;
+    .echarts_register {
+      width: 50%;
+      position: relative;
+      #echarts_register {
+        width: calc(100% - 20px);
+        height: 300px;
+        margin: 0 auto;
+      }
+      .total {
+        display: inline-block;
+        position: relative;
+        top: 0;
+        left: 50px;
+        z-index: 999;
+      }
+      .chart-box {
+        width: calc(100% - 20px);
+        height: 300px;
+        position: relative;
+      }
+      .chart-header {
+        height: 30px;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        margin-right: 20px;
+        font-size: 13px;
+        color: #666;
+        font-weight: bold;
+        z-index: 10;
+        user-select: none;
+
+        span {
+          display: inline-block;
+        }
+        .col-rate {
+          width: 60px;
+          text-align: right;
+        }
+        .col-gap {
+          width: 16px;
+        }
+        .col-checked {
+          width: 66px;
+          text-align: right;
+        }
+        .col-slash {
+          width: 20px;
+          text-align: center;
+        }
+        .col-total {
+          width: 59px;
+          text-align: right;
+        }
+      }
+    }
+
+    .echarts_traffic {
+      width: 50%;
+
+      #echarts_traffic {
+        width: calc(100% - 0px);
+        height: 300px;
+        margin: 0 auto;
+      }
+      .total {
+        display: inline-block;
+        position: relative;
+        top: 0;
+        left: 50px;
+        z-index: 999;
+      }
+      #echarts_levelRegister {
+        width: calc(100% - 0px);
+        height: 300px;
+        margin: 0 auto;
+      }
+      .chart-wrap {
+        position: relative;
+        // height: 300px;
+      }
+      .chart-inner {
+        width: 100%;
+        height: 330px;
+      }
+      .chart-header {
+        position: absolute;
+        top: 0px;
+        right: 20px;
+        display: flex;
+        gap: 10px;
+        font-size: 13px;
+        color: #666;
+        font-weight: bold;
+        z-index: 10;
+      }
+    }
+  }
+
+  .gender {
+    width: calc(100% - 60px);
+    height: 300px;
+    margin: 0 auto 10px;
+    #gender {
+      width: 100%;
+      height: 300px;
+    }
+  }
+
+  .footer {
+    width: 100%;
+    margin-bottom: 25px;
+    .el-table--fit {
+      width: calc(100% - 60px);
+      margin: 0 auto;
+      :deep(.el-table__header-wrapper) {
+        background-color: #000;
+        font-size: 15px;
+        color: #000;
+        .cell {
+          color: #000;
+        }
+      }
+      :deep(.el-table__row) {
+        height: 50px;
+        font-size: 15px;
+        color: #000;
+      }
+      :deep(.el-table__row td) {
+        padding: 0;
+        border: 0;
+      }
+
+      .el-button--primary {
+        margin-left: 5px;
+      }
+      :deep(.el-table__body .even) {
+        background-color: #fff;
+      }
+      :deep(.el-table__body .odd) {
+        background-color: rgba(240, 243, 247, 1);
+      }
+      :deep(.options) {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        .edit {
+          margin: 0 15px 0 0;
+          color: rgba(0, 186, 173, 1);
+          cursor: pointer;
+        }
+        .delete {
+          color: rgba(212, 48, 48, 1);
+          cursor: pointer;
+        }
+      }
+    }
+
+    .pageSize {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin: 0 30px;
+      height: 60px;
+
+      span {
+        color: #000;
+      }
+
+      .el-pagination {
+        // width: 1600px;
+        :deep(.el-pagination__total) {
+          color: #000;
+        }
+
+        :deep(.el-pagination__goto) {
+          color: #000;
+        }
+
+        :deep(.el-pagination__classifier) {
+          color: #000;
+        }
+
+        :deep(.el-input__wrapper) {
+          border: 1px solid rgba(0, 0, 0, 1);
+          border-radius: 5px;
+          box-shadow: none;
+        }
+
+        :deep(.el-pager li) {
+          margin: 0 5px;
+          border: 1px solid rgba(0, 0, 0, 1);
+          border-radius: 5px;
+          background-color: transparent;
+        }
+
+        :deep(.el-pager li.is-active) {
+          // background-color: rgba(0, 97, 255, 0.8);
+          border: 1px solid rgba(0, 97, 255, 1);
+          color: rgba(0, 97, 255, 1);
+        }
+
+        :deep(.btn-prev) {
+          margin-right: 5px;
+          border: 1px solid rgba(0, 0, 0, 1);
+          border-radius: 5px;
+          background-color: transparent;
+        }
+
+        :deep(.btn-next) {
+          margin-left: 5px;
+          border: 1px solid rgba(0, 0, 0, 1);
+          border-radius: 5px;
+          background-color: transparent;
+        }
+      }
+    }
+  }
+  @media (max-width: 1050px) {
+    .dispose {
+      flex-wrap: wrap;
+      li {
+        width: 98%;
+        margin: 10px 0 0;
+      }
+    }
+  }
+  @media (min-width: 1051px) and (max-width: 1399px) {
+    .dispose {
+      flex-wrap: wrap;
+      li {
+        width: 49%;
+        margin: 10px 0 0;
+      }
+    }
+  }
+  @media (min-width: 1400px) and (max-width: 1750px) {
+    // 处置情况
+    .dispose {
+      flex-wrap: wrap;
+      li {
+        width: 32%;
+        margin: 10px 0 0;
+      }
+    }
+  }
+
+  @media (max-width: 1650px) {
+    .echarts {
+      flex-wrap: wrap;
+      .echarts_register {
+        width: 98%;
+      }
+      .echarts_traffic {
+        width: 98%;
+      }
+    }
+  }
+}
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1280 - 0
src/views/dataOverview/dataOverview copy.vue


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 310 - 788
src/views/dataOverview/dataOverview.vue


+ 1 - 1
src/views/dormitory/dormitory.vue

@@ -119,7 +119,7 @@
               <el-option label="否" value="1" />
             </el-select>
           </div>
-          <div class="condition">
+          <div class="condition" v-if="store.BtnRole('dormitoryManagementSetting11')">
             <span>年份 :</span>
             <el-select
               clearable

+ 1 - 1
src/views/moneySetting/moneySetting.vue

@@ -39,7 +39,7 @@
               />
             </el-select>
           </div>
-          <div class="condition">
+          <div class="condition" v-if="store.BtnRole('dormitoryMoneySetting7')">
             <span>年份 :</span>
             <el-select
               clearable

+ 1 - 1
src/views/quarterage/quarterage.vue

@@ -38,7 +38,7 @@
               <el-option label="女" value="女" />
             </el-select>
           </div>
-          <div class="condition">
+          <div class="condition" v-if="store.BtnRole('accommodationStatisticsSetting3')">
             <span>年份 :</span>
             <el-select
               clearable

+ 16 - 3
src/views/role/role.vue

@@ -632,7 +632,10 @@ const addRolesList1 = reactive({
     {
       name: "全部",
       id: 0,
-      children: [{ name: "查看页面", id: 1 }],
+      children: [
+        { name: "查看页面", id: 1 },
+        { name: "年份查询", id: 2 },
+      ],
     },
   ],
   four: "1",
@@ -656,6 +659,8 @@ const addRolesList2 = reactive({
         { name: "删除", id: 6 },
         { name: "查看页面", id: 7 },
         { name: "现场报到", id: 8 },
+        { name: "年份查询", id: 9 },
+        { name: "取消报到权限", id: 10 },
       ],
     },
   ],
@@ -704,6 +709,7 @@ const addRolesList4 = reactive({
         { name: "编辑", id: 8 },
         { name: "删除", id: 9 },
         { name: "查看页面", id: 10 },
+        { name: "年份查询", id: 11 },
       ],
     },
   ],
@@ -737,6 +743,7 @@ const addRolesList5 = reactive({
         { name: "所属专业", id: 15 },
         { name: "所属班级", id: 16 },
         { name: "辅导员", id: 17 },
+        { name: "年份查询", id: 18 },
         // {
         //   name: "编辑",
         //   id: 14,
@@ -764,6 +771,7 @@ const addRolesList6 = reactive({
       children: [
         { name: "导出", id: 1 },
         { name: "查看页面", id: 2 },
+        { name: "年份查询", id: 3 },
       ],
     },
   ],
@@ -782,6 +790,7 @@ const addRolesList7 = reactive({
       children: [
         { name: "导出", id: 1 },
         { name: "查看页面", id: 2 },
+        { name: "年份查询", id: 3 },
       ],
     },
   ],
@@ -803,6 +812,7 @@ const addRolesList8 = reactive({
         { name: "编辑", id: 3 },
         { name: "删除", id: 4 },
         { name: "查看页面", id: 5 },
+        { name: "年份查询", id: 6 },
       ],
     },
   ],
@@ -865,6 +875,7 @@ const addRolesList12 = reactive({
         { name: "编辑", id: 4 },
         { name: "删除", id: 5 },
         { name: "查看页面", id: 6 },
+        { name: "年份查询", id: 7 },
       ],
     },
   ],
@@ -1113,7 +1124,9 @@ const updateS = async (row) => {
     if (addRolesData13.value) {
       if (row.logManagementSetting) {
         addRoles.logManagementSetting = row.logManagementSetting.split(",");
-        addRolesData13.value.setCheckedKeys(row.logManagementSetting.split(","));
+        addRolesData13.value.setCheckedKeys(
+          row.logManagementSetting.split(","),
+        );
       }
     }
 
@@ -1221,7 +1234,7 @@ const confirmEdit = async () => {
     accountManagementSetting: addRoles.accountManagementSetting.join(","),
     dormitoryMoneySetting: addRoles.dormitoryMoneySetting.join(","),
     settingManagementSetting: addRoles.settingManagementSetting.join(","),
-    logManagementSetting:addRoles.logManagementSetting.join(",")
+    logManagementSetting: addRoles.logManagementSetting.join(","),
   };
   if (addRoles.id) {
     data.id = addRoles.id;

+ 1 - 1
src/views/student/student.vue

@@ -111,7 +111,7 @@
               style="width: 180px"
             />
           </div>
-          <div class="condition">
+          <div class="condition" v-if="store.BtnRole('studentAccommodationSetting3')">
             <span>年份 :</span>
             <el-select
               clearable

+ 187 - 34
src/views/studentInfo/studentInfo.vue

@@ -14,8 +14,8 @@
               v-model="searchInput.schoolId"
               placeholder="请选择校区"
             >
-            <el-option label="墨轩湖校区" value="墨轩湖校区" />
-            <el-option label="黄家湖校区" value="黄家湖校区" />
+              <el-option label="墨轩湖校区" value="墨轩湖校区" />
+              <el-option label="黄家湖校区" value="黄家湖校区" />
             </el-select>
           </div>
           <div class="condition">
@@ -157,7 +157,10 @@
               @clear="searchBtn"
             />
           </div>
-          <div class="condition">
+          <div
+            class="condition"
+            v-if="store.BtnRole('studentManagementSetting9')"
+          >
             <span>年份 :</span>
             <el-select
               clearable
@@ -367,10 +370,22 @@
             </template>
           </el-table-column>
           <el-table-column
+            width="180"
+            align="center"
+            prop="remark"
+            label="备注"
+          >
+            <template #default="{ row }">
+              <div style="max-height: 100px;overflow-y: auto;">
+                {{ row.remark }}
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column
             align="center"
             label="操作"
             fixed="right"
-            min-width="230"
+            min-width="280"
           >
             <template #default="{ row }">
               <div class="options">
@@ -406,12 +421,19 @@
                   class="delete"
                   style="color: #c003a2; margin-left: 15px"
                   v-if="
-                    store.BtnRole('studentManagementSetting8') &&
+                    store.BtnRole('studentManagementSetting10') &&
                     row.isRegistered == 1
                   "
                   @click="registerS(row, 2)"
                   >取消报到</span
                 >
+                <span
+                  class="info"
+                  style="margin-left: 15px"
+                  v-if="row.isRegistered == 2"
+                  @click="infoRemark(row)"
+                  >备注</span
+                >
               </div>
             </template>
           </el-table-column>
@@ -1448,6 +1470,51 @@
         >
       </div>
     </el-dialog>
+
+    <el-dialog
+      class="remark"
+      v-model="remarkVisible"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      title="备注"
+      align-center
+      width="540"
+      :before-close="cancelRemark"
+      destroy-on-close
+      draggable
+    >
+      <el-form
+        ref="ruleFormRemarkRef"
+        :model="ruleFormRemark"
+        :rules="rulesRemark"
+        label-width="80px"
+        class="demo-ruleForm"
+        :size="formSize"
+        label-position="right"
+        status-icon
+      >
+        <el-form-item label="备注 :" prop="remark">
+          <el-input
+            v-model.trim="ruleFormRemark.remark"
+            placeholder="请输入备注"
+            :autosize="{ minRows: 5, maxRows: 8 }"
+            type="textarea"
+            style="width: 400px"
+          />
+        </el-form-item>
+        <el-form-item class="options">
+          <el-button @click="cancelRemark">取消</el-button>
+          <el-button
+            color="rgba(0, 97, 255, 1)"
+            class="queding"
+            type="primary"
+            @click="submitRemark(ruleFormRemarkRef)"
+          >
+            确定
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
   </div>
 </template>
 
@@ -1484,7 +1551,7 @@ const activeIndex = ref(); // 默认跳转路由
 const dialongTitle = ref("新增账号"); // 弹窗标题
 
 const searchInput = reactive({
-  schoolId:"",
+  schoolId: "",
   college: "",
   major: "",
   classstr: "",
@@ -1493,8 +1560,8 @@ const searchInput = reactive({
   isPay: "",
   isCheck: "",
   name: "",
-  batchValue:"",
-  carNumber:"",
+  batchValue: "",
+  carNumber: "",
   isRegistered: "",
 }); // 搜索按钮数据
 
@@ -1692,7 +1759,7 @@ const majorList = async (flag) => {
   let res = await https.get(
     "/welcome/api/welcomeOrg/getMajors",
     "params",
-    params
+    params,
   );
   console.log(res, "专业数据");
   if (res.code == 200) {
@@ -1717,7 +1784,7 @@ const classstrList = async (flag) => {
   let res = await https.get(
     "/welcome/api/welcomeOrg/getClasss",
     "params",
-    params
+    params,
   );
   console.log(res, "班级数据");
   if (res.code == 200) {
@@ -1797,7 +1864,7 @@ const districtData = ref([]); // 家庭住址
 const provinceList = async () => {
   let res = await https.get(
     "/welcome/api/welcomeCity/getProvinceLevel",
-    "params"
+    "params",
   );
   console.log(res, "省数据");
   if (res.code == 200) {
@@ -1826,7 +1893,7 @@ const cityList = async (flag) => {
   let res = await https.get(
     "/welcome/api/welcomeCity/getCityLevel",
     "params",
-    params
+    params,
   );
   console.log(res, "市数据");
   if (res.code == 200) {
@@ -1859,7 +1926,7 @@ const districtList = async (flag) => {
   let res = await https.get(
     "/welcome/api/welcomeCity/getDistrictLevel",
     "params",
-    params
+    params,
   );
   console.log(res, "区数据");
   if (res.code == 200) {
@@ -1932,7 +1999,7 @@ const arriveData = ref([]); // 到站时间段
 const arriveList = async () => {
   let res = await https.get(
     "/welcome/api/welcomeArriveSetting/queryArriveTimes",
-    "params"
+    "params",
   );
   console.log(res, "时间段数据");
   if (res.code == 200) {
@@ -1976,7 +2043,7 @@ const buildList = async () => {
   let res = await https.get(
     "/welcome/api/welcome-build/buildGroup",
     "params",
-    params
+    params,
   );
   console.log(res, "楼栋数据");
   if (res.code == 200) {
@@ -1998,7 +2065,7 @@ const dormitoryList = async () => {
   let res = await https.get(
     "/welcome/api/welcome-dormitory/dormitoryGroup",
     "params",
-    params
+    params,
   );
   console.log(res, "寝室号数据");
   if (res.code == 200) {
@@ -2019,7 +2086,7 @@ const bedList = async () => {
   let res = await https.get(
     "/welcome/api/welcomeBed/queryBedDatas",
     "params",
-    params
+    params,
   );
   console.log(res, "床位号数据");
   if (res.code == 200) {
@@ -2094,6 +2161,66 @@ const buildRef = ref();
 const buildFile = ref();
 const buildImportLoading = ref(false);
 
+// 备注
+const remarkVisible = ref(false);
+const ruleFormRemarkRef = ref();
+const ruleFormRemark = reactive({
+  remark: "",
+  id: "",
+});
+const rulesRemark = reactive({
+  remark: [{ required: true, message: "备注不能为空", trigger: "blur" }],
+});
+const infoRemark = (row) => {
+  ruleFormRemark.id=row.id
+  remarkVisible.value = true;
+};
+const submitRemark = lodash.debounce(async (formEl) => {
+  if (!formEl) return;
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      let data = {
+        remark: ruleFormRemark.remark,
+        id: ruleFormRemark.id,
+      };
+      console.log(data);
+
+      if (ruleFormRemark.id) {
+        data.id = ruleFormRemark.id;
+        let res = await https.post(
+          "/welcome/api/welcomeStudent/insertRemark",
+          "data",
+          data,
+        );
+        if (res.code == 200) {
+          remarkVisible.value = false;
+          getList();
+          ElMessage({
+            type: "success",
+            showClose: true,
+            message: res.message,
+            center: true,
+          });
+        } else {
+          ElMessage({
+            type: "error",
+            showClose: true,
+            message: res.message,
+            center: true,
+          });
+        }
+      }
+    } else {
+      console.log("error submit!", fields);
+      loading.value = false;
+    }
+  });
+}, 1000);
+const cancelRemark = () => {
+  remarkVisible.value = false;
+  // ruleFormRef.value.resetFields();
+};
+
 // 获取账户列表
 const getList = async () => {
   loading.value = true;
@@ -2119,7 +2246,7 @@ const getList = async () => {
   let res = await https.get(
     "/welcome/api/welcomeStudent/queryPageStudents",
     "params",
-    params
+    params,
   );
   console.log(res, "学生信息");
   if (res.code == 200) {
@@ -2154,10 +2281,10 @@ const resetBtn = lodash.debounce(async () => {
   searchInput.isPay = null;
   searchInput.isCheck = null;
   searchInput.isRegistered = null;
-  searchInput.schoolId=null
-  searchInput.batchValue=null
-  searchInput.carNumber=null
-  searchInput.yearId=null
+  searchInput.schoolId = null;
+  searchInput.batchValue = null;
+  searchInput.carNumber = null;
+  searchInput.yearId = null;
   getList();
 }, 300);
 
@@ -2299,7 +2426,7 @@ const updateS = async (row) => {
   let res = await https.get(
     "/welcome/api/welcomeStudent/queryStudentDetail",
     "params",
-    data
+    data,
   );
   if (res.code == 200) {
     console.log(res.data, "学生信息");
@@ -2400,7 +2527,7 @@ const deleteS = async (row) => {
       let res = await https.get(
         "/welcome/api/welcomeStudent/deleteStudentInfo",
         "params",
-        data
+        data,
       );
       if (res.code == 200) {
         ElMessage({
@@ -2432,7 +2559,7 @@ const registerS = async (row, flag) => {
     let res = await https.post(
       "/welcome/api/welcomeStudent/isRegistered",
       "data",
-      data
+      data,
     );
     if (res.code == 200) {
       ElMessage({
@@ -2464,7 +2591,7 @@ const registerS = async (row, flag) => {
         let res = await https.post(
           "/welcome/api/welcomeStudent/isRegistered",
           "data",
-          data
+          data,
         );
         if (res.code == 200) {
           ElMessage({
@@ -2563,7 +2690,7 @@ const infoStudent = async (row) => {
   let res = await https.get(
     "/welcome/api/welcomeStudent/queryStudentDetail",
     "params",
-    data
+    data,
   );
   if (res.code == 200) {
     console.log(res.data, "学生信息");
@@ -2670,7 +2797,7 @@ const pictureImportChange = async (file, fileLists) => {
   let res = await https.post(
     "/welcome/api/welcomeFile/uploadFile",
     "data",
-    data
+    data,
   );
   console.log(res, "导入照片");
   if (res.code == 200) {
@@ -2760,7 +2887,7 @@ const submitAdd = lodash.debounce(async (formEl) => {
         let res = await https.post(
           "/welcome/api/welcomeStudent/updateStudentInfo",
           "data",
-          data
+          data,
         );
         if (res.code == 200) {
           addDialogVisible.value = false;
@@ -2783,7 +2910,7 @@ const submitAdd = lodash.debounce(async (formEl) => {
         let res = await https.post(
           "/welcome/api/welcomeStudent/insertStudentInfo",
           "data",
-          data
+          data,
         );
         if (res.code == 200) {
           addDialogVisible.value = false;
@@ -2855,7 +2982,7 @@ const buildExportbtn = async () => {
   let res = await https.getBlob(
     "/welcome/api/welcomeStudent/welcomeStudentExport",
     "params",
-    params
+    params,
   );
   console.log(res, "学生信息导出");
   let name = `学生信息管理`;
@@ -2882,7 +3009,7 @@ const buildImportBtn = () => {
 const templateDown = async () => {
   let res = await https.get(
     "/welcome/api/welcomeStudent/downloadStudentExcel",
-    "params"
+    "params",
   );
   console.log(res, "模板下载");
   if (res.code == 200) {
@@ -2934,7 +3061,7 @@ const projectImportConfirm = async () => {
   let res = await https.post(
     "/welcome/api/welcomeStudent/importStudentExcel",
     "data",
-    data
+    data,
   );
   console.log(res, "导入项目");
   if (res.code == 200) {
@@ -2971,7 +3098,7 @@ onBeforeMount(() => {
   schoolList(); // 校区
   provinceList(); // 省
   arriveList(); // 时间段
-  yearList()
+  yearList();
 });
 onUnmounted(() => {
   // document.removeEventListener("keyup", Enters);
@@ -3321,6 +3448,31 @@ onUnmounted(() => {
       }
     }
   }
+  :deep(.remark) {
+    .el-dialog__body {
+      height: 200px;
+      overflow: auto;
+      padding: 20px 20px 10px 20px;
+      .el-form {
+        .el-form-item__content {
+          display: flex;
+          align-items: normal;
+        }
+      }
+      .options{
+        margin-top: 40px;
+      }
+      .el-input {
+        width: 240px;
+        .el-input__suffix-inner {
+          color: rgba(61, 81, 232, 1);
+        }
+      }
+      .el-select {
+        width: 240px;
+      }
+    }
+  }
 
   :deep(.infoStudent) {
     .el-dialog__body {
@@ -3504,6 +3656,7 @@ onUnmounted(() => {
       }
     }
   }
+
   // 导入
   :deep(.projectImport) {
     .el-dialog__body {

+ 11 - 11
vite.config.js

@@ -31,21 +31,21 @@ export default defineConfig({
     // 是否开启 https
     // https: false,
 
-    proxy: {
-      "/welcome/welcome_api": {
-        target: "https://chtech.ncjti.edu.cn/welcome/welcome_api", // 线上环境
-        changeOrigin: true,
-        rewrite: (path) => path.replace(/^\/welcome\/welcome_api/, ""),
-      },
-    },
-
     // proxy: {
-    //   "/welcome/api": {
-    //     target: "http://192.168.161.190:8080", // 线上环境
+    //   "/welcome/welcome_api": {
+    //     target: "https://chtech.ncjti.edu.cn/welcome/welcome_api", // 线上环境
     //     changeOrigin: true,
-    //     rewrite: (path) => path.replace(/^\/welcome\/api/, ""),
+    //     rewrite: (path) => path.replace(/^\/welcome\/welcome_api/, ""),
     //   },
     // },
+
+    proxy: {
+      "/welcome/api": {
+        target: "http://192.168.161.111:8080", // 线上环境
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/welcome\/api/, ""),
+      },
+    },
   },
   publicDir: "public",
   // base: "/",