dedsudiyu 1 week ago
parent
commit
fea60b9df9
24 changed files with 4634 additions and 25 deletions
  1. 2 0
      demo.md
  2. 0 1
      src/assets/styles/commonality.scss
  3. 217 0
      src/views/safetyEducationExaminationNew/components/answerKey.vue
  4. 252 0
      src/views/safetyEducationExaminationNew/components/examRecords.vue
  5. 237 0
      src/views/safetyEducationExaminationNew/components/historyOfIncorrectQuestions.vue
  6. 1 1
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/studentsTakingMakeUpExams.vue
  7. 1 3
      src/views/safetyEducationExaminationNew/examManagement/examinationResult/index.vue
  8. 1 16
      src/views/safetyEducationExaminationNew/examManagement/examinationResult/viewGrades.vue
  9. 246 0
      src/views/safetyEducationExaminationNew/mockPractice/mockPractice.vue
  10. 4 4
      src/views/safetyEducationExaminationNew/publicConfiguration/certificateTemplate/addPage.vue
  11. 426 0
      src/views/safetyEducationExaminationNew/safetyEducation/knowledgeLearning/index.vue
  12. 361 0
      src/views/safetyEducationExaminationNew/safetyEducation/learningTasks/index.vue
  13. 361 0
      src/views/safetyEducationExaminationNew/safetyTest/mockExam/index.vue
  14. 360 0
      src/views/safetyEducationExaminationNew/safetyTest/myGrades/index.vue
  15. 355 0
      src/views/safetyEducationExaminationNew/safetyTest/onlineExam/index.vue
  16. 415 0
      src/views/safetyEducationExaminationNew/safetyTest/practiceQuestions/index.vue
  17. 246 0
      src/views/safetyEducationExaminationNew/startExam/startExam.vue
  18. 244 0
      src/views/safetyEducationExaminationNew/workbench/ExamStatsCard.vue
  19. 192 0
      src/views/safetyEducationExaminationNew/workbench/ResourceCard.vue
  20. 144 0
      src/views/safetyEducationExaminationNew/workbench/SafetyPersonChart.vue
  21. 159 0
      src/views/safetyEducationExaminationNew/workbench/StudentStats.vue
  22. 184 0
      src/views/safetyEducationExaminationNew/workbench/TaskCard.vue
  23. 147 0
      src/views/safetyEducationExaminationNew/workbench/UserInfoCard.vue
  24. 79 0
      src/views/safetyEducationExaminationNew/workbench/index.vue

+ 2 - 0
demo.md

@@ -0,0 +1,2 @@
+1. 根据当前UI图片在E:\git\2026项目\safetyEducationExamination\src\views\safetyEducationExaminationNew\examManagement\testPaperManagement\addPage.vue内生成页面
+2. 所有表单内容生成在.content-box内

+ 0 - 1
src/assets/styles/commonality.scss

@@ -1315,7 +1315,6 @@
   .top-1-p {
   .top-1-p {
     flex: 1;
     flex: 1;
     font-size: 16px;
     font-size: 16px;
-    font-weight: 700;
   }
   }
 
 
   .top-2-p {
   .top-2-p {

File diff suppressed because it is too large
+ 217 - 0
src/views/safetyEducationExaminationNew/components/answerKey.vue


+ 252 - 0
src/views/safetyEducationExaminationNew/components/examRecords.vue

@@ -0,0 +1,252 @@
+<!-- 考试记录 -->
+<template>
+  <div class="page-container examRecords-addPage">
+    <div class="page-container myGradesPage" v-if="pageType === 1">
+      <div class="page-container-top-max-big-box">
+        <p class="top-1-p">考试记录</p>
+        <p class="top-2-p" @click="backPage()">返回</p>
+      </div>
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;">
+          <div class="table-top-button-box">
+            <div>
+              <p :class="shareStatus === ''?'check-table-button':''" @click="tableTypeButton('')">全部</p>
+              <p :class="shareStatus === 1?'check-table-button':''" @click="tableTypeButton(1)">通过</p>
+              <p :class="shareStatus === 2?'check-table-button':''" @click="tableTypeButton(2)">未通过</p>
+            </div>
+          </div>
+          <el-form-item label="" prop="state">
+            <el-date-picker
+              :clearable="false"
+              v-model="dateRange"
+              size="small"
+              style="width: 240px"
+              value-format="yyyy-MM-dd"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <el-table class="table-box" v-loading="loading" border :data="dataList">
+          <el-table-column label="考试时间" prop="createTime"  show-overflow-tooltip>
+            <template slot-scope="scope">
+              <span>{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}至{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="用时" prop="content" width="200" show-overflow-tooltip/>
+          <el-table-column label="得分" prop="content" width="200" show-overflow-tooltip/>
+          <el-table-column label="及格分" prop="content" width="200" show-overflow-tooltip/>
+          <el-table-column label="考试结果" prop="content" width="200" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <span :class="scope.row.content?'colorA':(scope.row.content?'colorB':(scope.row.content?'colorC':''))">
+                {{scope.row.content?'通过':(scope.row.content?'未通过':(scope.row.content?'成绩无效':''))}}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="200" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <div class="table-button-box">
+                <p class="table-button-null"></p>
+                <p class="table-button-p"
+                   @click="tableButton(1,scope.row)"
+                >试卷详情</p>
+                <p class="table-button-p"
+                   @click="tableButton(2,scope.row)"
+                >历史错题</p>
+                <p class="table-button-null"></p>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+    <historyOfIncorrectQuestions :propsData="propsData" v-if="pageType === 2"></historyOfIncorrectQuestions>
+    <el-drawer
+      size="100%"
+      custom-class="examinationArrange-addPage-drawer"
+      :append-to-body="true"
+      :title="drawerTitle"
+      ref="drawer"
+      :visible.sync="drawer"
+      direction="rtl"
+    >
+      <examDetails></examDetails>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  import historyOfIncorrectQuestions from "@/views/safetyEducationExaminationNew/components/historyOfIncorrectQuestions.vue";
+  import examDetails from '@/views/safetyEducationExaminationNew/components/examDetails.vue'
+  export default {
+    name: 'addPage',
+    components: {
+      historyOfIncorrectQuestions,
+      examDetails,
+    },
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        pageType:1,
+        newData: {},
+        shareStatus:'',
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+        },
+        //时间数据
+        dateRange: [],
+        //列表数据
+        dataList: [{}],
+        //数据数量
+        total: 0,
+        //弹窗相关
+        drawerTitle: '',
+        drawerType: '',
+        drawer: false
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.initialize()
+    },
+    methods: {
+      // 初始化
+      initialize() {
+
+      },
+      // 返回按钮
+      backPage() {
+        this.$parent.tableButton(6)
+      },
+      // 提交按钮
+      submitForm() {
+
+      },
+      tableTypeButton(val){
+        if(this.shareStatus != val){
+          this.$set(this,'shareStatus',val);
+          this.handleQuery();
+        }
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          // 试卷详情
+          this.$set(this, 'drawerTitle', '试卷详情')
+          this.$set(this, 'drawerType', 2)
+          this.$set(this, 'drawer', true)
+        } else if (type == 2) {
+          // 历史错题
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      }
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .examRecords-addPage {
+
+    .table-top-button-box{
+      display: inline-block;
+      vertical-align: top;
+      margin-right:10px;
+      div{
+        display: flex;
+        p{
+          border:1px solid #DCDFE6;
+          width:80px;
+          text-align: center;
+          height:40px;
+          line-height:40px;
+          cursor: pointer;
+          font-size:14px;
+        }
+        p:nth-child(1){
+          border-top-left-radius:4px;
+          border-bottom-left-radius:4px;
+        }
+        p:nth-child(2){
+          border-left:none;
+          border-right:none;
+        }
+        p:nth-child(3){
+          border-top-right-radius:4px;
+          border-bottom-right-radius:4px;
+        }
+        .check-table-button{
+          border:1px solid #0183fa;
+          background-color: #0183fa;
+          color:#fff;
+        }
+      }
+    }
+    .colorA{
+      color:#70B603;
+    }
+    .colorB{
+      color:#D90029;
+    }
+    .colorC{
+      color:#999999;
+    }
+  }
+</style>

+ 237 - 0
src/views/safetyEducationExaminationNew/components/historyOfIncorrectQuestions.vue

@@ -0,0 +1,237 @@
+<!-- 历史错题 -->
+<template>
+  <div class="page-container historyOfIncorrectQuestions-addPage">
+    <div class="page-container-top-max-big-box">
+      <p class="top-1-p">历史错题</p>
+      <p class="top-2-p" @click="backPage()">返回</p>
+    </div>
+    <div class="page-form-title-box">
+      <el-form :model="queryParams" class="form-box" ref="queryForm"
+               :inline="true" style="width:100%;">
+        <el-form-item label="" prop="queryParamsData1">
+          <el-cascader
+            v-model="queryParams.queryParamsData1"
+            :options="optionListA"
+            :props="{
+                emitPath:false,
+                checkStrictly: true,
+                value: 'id',
+                label: 'knowledgePointName',
+                children: 'children',
+              }"
+            style="width: 180px"
+            :show-all-levels="false"
+            filterable
+            placeholder="知识点"
+          ></el-cascader>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData2">
+          <el-select v-model="queryParams.queryParamsData2" placeholder="题型" style="width: 200px">
+            <el-option
+              v-for="dict in optionListB"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData3">
+          <el-select v-model="queryParams.queryParamsData3" placeholder="难度" style="width: 200px">
+            <el-option
+              v-for="dict in optionListC"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData4">
+          <el-input
+            maxLength="30"
+            v-model="queryParams.queryParamsData4"
+            placeholder="搜索名称"
+            style="width: 200px"
+          />
+        </el-form-item>
+        <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+        <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        <p class="page-submit-common-style-button"
+           style="float: right;"
+           @click="tableButton(1)"
+        >开始训练</p>
+      </el-form>
+    </div>
+    <div class="page-content-box">
+      <el-table class="table-box" v-loading="loading" border :data="dataList">
+        <el-table-column label="题干" prop="name"  show-overflow-tooltip/>
+        <el-table-column label="题型" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="难度" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="做对次数" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="做错次数" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="操作" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <div class="table-button-box">
+              <p class="table-button-null"></p>
+              <p class="table-button-p"
+                 @click="tableButton(2,scope.row)"
+              >查看答案</p>
+              <p class="table-button-null"></p>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination :page-sizes="[20, 30, 40, 50]"
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.page"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getList"
+      />
+    </div>
+    <el-drawer
+      size="40%"
+      custom-class="examinationArrange-addPage-drawer"
+      :append-to-body="true"
+      :title="drawerTitle"
+      ref="drawer"
+      :visible.sync="drawer"
+      direction="rtl">
+      <answerKey></answerKey>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  import { examElKnowledgePointTreeList, } from "@/api/safetyEducationExaminationNew/index";
+  import answerKey from "@/views/safetyEducationExaminationNew/components/answerKey.vue";
+  export default {
+    name: 'addPage',
+    components: {
+      answerKey,
+    },
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading: false,
+        //下拉列表数据
+        optionListA: [],
+        optionListB: [{value:'1',label:'单选'},{value:'2',label:'多选'},{value:'3',label:'判断'}],
+        optionListC: [{value:"1",label:'简单'},{value:"2",label:'一般'},{value:"3",label:'较难'},{value:"4",label:'极难'}],
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: null,
+          queryParamsData2: null,
+          queryParamsData3: null,
+          queryParamsData4: '',
+        },
+        //列表数据
+        dataList:[{}],
+        //数据数量
+        total:0,
+        //弹窗相关
+        drawerTitle: '',
+        drawerType: '',
+        drawer: false,
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.initialize()
+      this.examElKnowledgePointTreeList();
+    },
+    methods: {
+      // 初始化
+      initialize() {
+
+      },
+      // 返回按钮
+      backPage() {
+        this.$parent.tableButton(6)
+      },
+      // 提交按钮
+      submitButton() {
+
+      },
+      tableButton(type){
+        if(type == '1'){
+          //开始训练
+        }else if(type == 2){
+          //查看答案
+          this.$set(this, 'drawerTitle', '查看试题答案')
+          this.$set(this, 'drawerType', 1)
+          this.$set(this, 'drawer', true)
+        }
+      },
+      //弹窗返回
+      drawerReturnButton(){
+        this.$refs.drawer.closeDrawer();
+      },
+      //查询按钮
+      handleQuery(){
+        this.$set(this.queryParams,'page',1);
+        this.getList();
+      },
+      //重置按钮
+      resetQuery(){
+        this.$set(this,'queryParams',{
+          page:1,
+          pageSize:20,
+          queryParamsData1: null,
+          queryParamsData2: null,
+          queryParamsData3: null,
+          queryParamsData4: '',
+        });
+        this.getList();
+      },
+      //获取数据列表
+      getList(){
+        this.$set(this,'loading',true);
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this,'loading',false);
+          this.$set(this,'dataList',response.data.records);
+          this.$set(this,'total',response.data.total);
+        });
+      },
+      examElKnowledgePointTreeList(){
+        examElKnowledgePointTreeList({}).then(response => {
+          const list = response.data || [];
+          this.formatTreeData(list);
+          this.$set(this, 'optionListA', list);
+        });
+      },
+      formatTreeData(data) {
+        data.forEach(item => {
+          // 如果 children 存在,且是数组,且长度为 0(即 children: [])
+          if (Array.isArray(item.children) && item.children.length === 0) {
+            // 将 children 设为 undefined(或直接 delete item.children;)
+            item.children = undefined;
+          } else if (item.children && item.children.length > 0) {
+            // 如果还有下一级,继续递归处理
+            this.formatTreeData(item.children);
+          }
+        });
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .historyOfIncorrectQuestions-addPage {
+    .content-box {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      overflow: hidden;
+      padding: 20px;
+    }
+  }
+</style>

+ 1 - 1
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/studentsTakingMakeUpExams.vue

@@ -260,7 +260,7 @@
 
 
       },
       },
       lockExam(){
       lockExam(){
-        this.$set(this, 'drawerTitle', '试详情')
+        this.$set(this, 'drawerTitle', '试详情')
         this.$set(this, 'drawerType', 2)
         this.$set(this, 'drawerType', 2)
         this.$set(this, 'drawer', true)
         this.$set(this, 'drawer', true)
       },
       },

+ 1 - 3
src/views/safetyEducationExaminationNew/examManagement/examinationResult/index.vue

@@ -113,11 +113,9 @@
       :title="drawerTitle"
       :title="drawerTitle"
       ref="drawer"
       ref="drawer"
       :visible.sync="drawer"
       :visible.sync="drawer"
-      direction="rtl"
-    >
+      direction="rtl">
       <viewGrades v-if="drawerType==1"></viewGrades>
       <viewGrades v-if="drawerType==1"></viewGrades>
     </el-drawer>
     </el-drawer>
-    <!--<add-page :propsData="propsData" v-if="pageType === 2"></add-page>-->
   </div>
   </div>
 </template>
 </template>
 <script>
 <script>

+ 1 - 16
src/views/safetyEducationExaminationNew/examManagement/examinationResult/viewGrades.vue

@@ -176,25 +176,10 @@
     },
     },
     methods: {
     methods: {
       lockExam(){
       lockExam(){
-        this.$set(this, 'drawerTitle', '试详情')
+        this.$set(this, 'drawerTitle', '试详情')
         this.$set(this, 'drawerType', 2)
         this.$set(this, 'drawerType', 2)
         this.$set(this, 'drawer', true)
         this.$set(this, 'drawer', true)
       },
       },
-      //操作按钮
-      tableButton(type,row){
-        let self = this;
-        if(type == 1){
-          // TODO: 查看成绩
-          this.$set(this, 'drawerTitle', '查看成绩')
-          this.$set(this, 'drawerType', 1)
-          this.$set(this, 'drawer', true)
-        }else if(type == 2){
-          // TODO: 学习情况
-          this.$set(this, 'drawerTitle', '学习情况')
-          this.$set(this, 'drawerType', 2)
-          this.$set(this, 'drawer', true)
-        }
-      },
       // 提交按钮
       // 提交按钮
       checkButton() {
       checkButton() {
         this.$parent.$parent.drawerOffButton()
         this.$parent.$parent.drawerOffButton()

+ 246 - 0
src/views/safetyEducationExaminationNew/mockPractice/mockPractice.vue

@@ -0,0 +1,246 @@
+<!-- 模拟练习 -->
+<template>
+  <div class="app-container mockPractice">
+    <div class="page-container mockPracticePage" v-if="pageType === 1">
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;"
+        >
+          <el-form-item label="" prop="queryParamsData1">
+            <el-input
+              maxLength="30"
+              v-model="queryParams.queryParamsData1"
+              placeholder="请输入"
+              style="width: 200px"
+            />
+          </el-form-item>
+          <el-form-item label="" prop="queryParamsData2">
+            <el-select v-model="queryParams.queryParamsData2" placeholder="请选择" style="width: 200px">
+              <el-option
+                v-for="dict in optionList"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="" prop="state">
+            <el-date-picker
+              :clearable="false"
+              v-model="dateRange"
+              size="small"
+              style="width: 240px"
+              value-format="yyyy-MM-dd"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+          <p class="page-submit-common-style-button"
+             style="float: right;"
+             @click="tableButton(1)"
+             v-hasPermiRouter="['demo:demo:add']"
+          >新增</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <el-table class="table-box" v-loading="loading" border :data="dataList">
+          <el-table-column label="名称" prop="name" show-overflow-tooltip/>
+          <el-table-column label="内容" prop="content" width="200" show-overflow-tooltip/>
+          <el-table-column label="状态" prop="state" width="100" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <el-switch
+                @click.native="tableButton(5,scope.row)"
+                class="switch captcha-img"
+                :active-value="true"
+                :inactive-value="false"
+                active-color="#0183FA"
+                inactive-color="#999"
+                v-model="scope.row.state"
+                active-text="启用"
+                inactive-text="停用"
+                disabled
+              ></el-switch>
+            </template>
+          </el-table-column>
+          <el-table-column label="创建时间" prop="createTime" width="200" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <span>{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="200" show-overflow-tooltip v-if="tableButtonType">
+            <template slot-scope="scope">
+              <div class="table-button-box">
+                <p class="table-button-null"></p>
+                <p class="table-button-p"
+                   @click="tableButton(2,scope.row)"
+                   v-hasPermiRouter="['demo:demo:detail']"
+                >详情</p>
+                <p class="table-button-p"
+                   @click="tableButton(3,scope.row)"
+                   v-hasPermiRouter="['demo:demo:edit']"
+                >编辑</p>
+                <p class="table-button-p"
+                   @click="tableButton(4,scope.row)"
+                   v-hasPermiRouter="['demo:demo:del']"
+                >删除</p>
+                <p class="table-button-null"></p>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+    <!--<add-page :propsData="propsData" v-if="pageType === 2"></add-page>-->
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  //import { getInfo } from "@/api/basicsModules/index";
+  //import addPage from "./addPage.vue";
+  export default {
+    name: 'index',
+    //components: {
+    //  addPage
+    //},
+    data() {
+      return {
+        tableButtonType: this.hasPermiDom(['demo:demo:detail', 'demo:demo:edit', 'demo:demo:del']),
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        //下拉列表数据
+        optionList: [{ value: true, label: '启用' }, { value: false, label: '停用' }],
+        //查询条件
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        },
+        //时间数据
+        dateRange: [],
+        //列表数据
+        dataList: [],
+        //数据数量
+        total: 0,
+        //组件传参
+        propsData: {}
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      //this.getList();
+    },
+    methods: {
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          //新增
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 2) {
+          //详情
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = true
+          this.$set(this, 'propsData', obj)
+        } else if (type == 3) {
+          //编辑
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = false
+          this.$set(this, 'propsData', obj)
+        } else if (type == 4) {
+          //删除
+          this.$confirm('是否确认删除?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            deleteFunction({ id: row.id }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 5) {
+          //启用&停用
+          let text = row.state ? '停用' : '启用'
+          this.$confirm('是否确认' + text + '?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            stateFunction({ id: row.id, state: !row.state }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      }
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .mockPractice {
+    .mockPracticePage {
+
+    }
+  }
+</style>

+ 4 - 4
src/views/safetyEducationExaminationNew/publicConfiguration/certificateTemplate/addPage.vue

@@ -1,10 +1,10 @@
 <!--新增模板-->
 <!--新增模板-->
 <template>
 <template>
   <div class="page-container certificateTemplate-addPage">
   <div class="page-container certificateTemplate-addPage">
-    <div class="page-top-title-box">
-      <p class="page-top-title-name-p">{{newData.id?'编辑证书模板':'新增证书模板'}}</p>
-      <p class="page-top-title-out-p" @click="backPage">返回</p>
-      <p class="page-top-title-add-p" @click="submitForm">提交</p>
+    <div class="page-container-top-max-big-box">
+      <p class="top-1-p">{{newData.id?'编辑证书模板':'新增证书模板'}}</p>
+      <p class="top-2-p" @click="backPage()">返回</p>
+      <p class="top-4-p" @click="submitForm()">保存</p>
     </div>
     </div>
     <div class="content-box">
     <div class="content-box">
       <div class="left-content-box">
       <div class="left-content-box">

+ 426 - 0
src/views/safetyEducationExaminationNew/safetyEducation/knowledgeLearning/index.vue

@@ -0,0 +1,426 @@
+<!-- 知识学习 -->
+<template>
+  <div class="app-container knowledgeLearning">
+    <div class="page-container knowledgeLearningPage">
+      <p class="pageTitle">知识学习</p>
+      <div class="max-big-box">
+        <div class="left-box scrollbar-box">
+          <p @click="leftCheckButton(index)"
+            v-for="(item,index) in letDataList" :key="index"
+            class="left-for-p" :class="index==leftDataIndex?'leftCheck':''">
+            {{item.name}}
+          </p>
+        </div>
+        <div class="right-box">
+          <div class="top-max-button-box">
+            <p :class="topButtonIndex == 1?'top-check-button':''" @click="topCheckButton(1)">视频资源</p>
+            <p :class="topButtonIndex == 2?'top-check-button':''" @click="topCheckButton(2)">文档文章</p>
+          </div>
+          <div class="page-form-title-box">
+            <el-form :model="queryParams" class="form-box" ref="queryForm"
+                     :inline="true" style="width:100%;">
+              <div class="table-top-button-box">
+                <div>
+                  <p :class="shareStatus === ''?'check-table-button':''" @click="tableTypeButton('')">全部</p>
+                  <p :class="shareStatus === 1?'check-table-button':''" @click="tableTypeButton(1)">未学习</p>
+                  <p :class="shareStatus === 2?'check-table-button':''" @click="tableTypeButton(2)">学习中</p>
+                  <p :class="shareStatus === 3?'check-table-button':''" @click="tableTypeButton(3)">已学完</p>
+                </div>
+              </div>
+              <el-form-item label="" prop="queryParamsData1">
+                <el-cascader
+                  v-model="queryParams.queryParamsData1"
+                  :options="optionListB"
+                  :props="{
+                emitPath:false,
+                checkStrictly: true,
+                value: 'id',
+                label: 'knowledgePointName',
+                children: 'children',
+              }"
+                  style="width: 180px"
+                  :show-all-levels="false"
+                  filterable
+                  placeholder="知识点"
+                ></el-cascader>
+              </el-form-item>
+              <el-form-item label="" prop="queryParamsData2">
+                <el-input
+                  maxLength="30"
+                  v-model="queryParams.queryParamsData2"
+                  placeholder="搜索名称"
+                  style="width: 200px"
+                />
+              </el-form-item>
+              <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+              <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+            </el-form>
+          </div>
+          <div class="page-content-box">
+            <div class="for-max-big-box scrollbar-box">
+              <div class="for-big-box" v-for="(item,index) in dataList" :key="index">
+                <img :src="item.data1" alt="未配置封面">
+                <div class="bottom-big-box">
+                  <p class="name-p">{{item.data3}}</p>
+                  <p class="time-p">{{item.data4}}分钟 丨 {{item.data5}}学时 丨 {{item.data6}}积分</p>
+                </div>
+                <p class="type-p" :class="item.data2=='1'?'type-p-1':(item.data2=='2'?'type-p-2':(item.data2=='3'?'type-p-3':''))">{{item.data2=='1'?'未学习':(item.data2=='2'?'学习中':(item.data2=='3'?'已学完':''))}}</p>
+              </div>
+            </div>
+            <pagination :page-sizes="[20, 30, 40, 50]"
+                        v-show="total>0"
+                        :total="total"
+                        :page.sync="queryParams.page"
+                        :limit.sync="queryParams.pageSize"
+                        @pagination="getList"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  //import { getInfo } from "@/api/basicsModules/index";
+  //import addPage from "./addPage.vue";
+  export default {
+    name: 'index',
+    //components: {
+    //  addPage
+    //},
+    data() {
+      return {
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        letDataList:[
+          {name:'全部知识点',id:'1'},{name:'全部知识点',id:'2'},
+          {name:'全部知识点',id:'3'},{name:'全部知识点',id:'4'},
+          {name:'全部知识点',id:'5'},{name:'全部知识点',id:'6'},
+          {name:'全部知识点',id:'7'},{name:'全部知识点',id:'8'},
+          {name:'全部知识点',id:'9'},{name:'全部知识点',id:'10'},
+          {name:'全部知识点',id:'11'},{name:'全部知识点',id:'12'},
+          {name:'全部知识点',id:'13'},{name:'全部知识点',id:'14'},
+        ],
+        leftDataIndex:0,
+        topButtonIndex:1,
+        //下拉列表数据
+        optionListB: [],
+        //查询条件
+        shareStatus:'',
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        },
+        //列表数据
+        dataList: [
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'2',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'2',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'3',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'3',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'2',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'3',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+        ],
+        //数据数量
+        total: 0,
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElKnowledgePointTreeList();
+      //this.getList();
+    },
+    methods: {
+      //左侧列表切换
+      leftCheckButton(index){
+        if(this.leftDataIndex != index){
+          this.$set(this,'leftDataIndex',index);
+        }
+      },
+      //顶部切换
+      topCheckButton(index){
+        if(this.topButtonIndex != index){
+          this.$set(this,'topButtonIndex',index);
+        }
+      },
+      //切换按钮
+      tableTypeButton(val){
+        if(this.shareStatus != val){
+          this.$set(this,'shareStatus',val);
+          this.handleQuery();
+        }
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          //新增
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 2) {
+          //详情
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = true
+          this.$set(this, 'propsData', obj)
+        } else if (type == 3) {
+          //编辑
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = false
+          this.$set(this, 'propsData', obj)
+        } else if (type == 4) {
+          //删除
+          this.$confirm('是否确认删除?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            deleteFunction({ id: row.id }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 5) {
+          //启用&停用
+          let text = row.state ? '停用' : '启用'
+          this.$confirm('是否确认' + text + '?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            stateFunction({ id: row.id, state: !row.state }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      },
+      examElKnowledgePointTreeList(){
+        examElKnowledgePointTreeList({}).then(response => {
+          const list = response.data || [];
+          this.formatTreeData(list);
+          this.$set(this, 'optionListB', list);
+        });
+      },
+      formatTreeData(data) {
+        data.forEach(item => {
+          // 如果 children 存在,且是数组,且长度为 0(即 children: [])
+          if (Array.isArray(item.children) && item.children.length === 0) {
+            // 将 children 设为 undefined(或直接 delete item.children;)
+            item.children = undefined;
+          } else if (item.children && item.children.length > 0) {
+            // 如果还有下一级,继续递归处理
+            this.formatTreeData(item.children);
+          }
+        });
+      },
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .knowledgeLearning {
+    .knowledgeLearningPage {
+      .pageTitle{
+        height:60px;
+        line-height:60px;
+        padding:0 20px;
+        border-bottom:1px solid #dedede;
+      }
+      .max-big-box{
+        display: flex;
+        overflow: hidden;
+        .left-box{
+          width:200px;
+          border-right:1px solid #dedede;
+          padding:20px 0 ;
+          .left-for-p{
+            margin:5px 0;
+            padding:0 20px;
+            text-align: center;
+            border-right:4px solid #ffffff;
+            cursor: pointer;
+            font-weight:700;
+            font-size:16px;
+            line-height:40px;
+          }
+          .left-for-p:hover{
+            background-color: #0183fa;
+            color: #ffffff;
+          }
+          .leftCheck{
+            color: #0183fa;
+            border-right:4px solid #0183fa;
+          }
+        }
+        .right-box{
+          flex:1;
+          display: flex;
+          flex-direction: column;
+          overflow: hidden;
+          .top-max-button-box{
+            display: flex;
+            margin:20px 0 0 20px;
+            p{
+              width:120px;
+              line-height:40px;
+              border-radius:4px;
+              color:#333;
+              border:1px solid #333;
+              text-align: center;
+              margin-right:20px;
+              cursor: pointer;
+            }
+            .top-check-button{
+              border:1px solid #0183fa;
+              background-color: #0183fa;
+              color:#fff;
+            }
+          }
+          .table-top-button-box{
+            display: inline-block;
+            vertical-align: top;
+            margin-right:10px;
+            div{
+              display: flex;
+              p{
+                border:1px solid #DCDFE6;
+                width:80px;
+                text-align: center;
+                height:40px;
+                line-height:40px;
+                cursor: pointer;
+                font-size:14px;
+              }
+              p:nth-child(1){
+                border-top-left-radius:4px;
+                border-bottom-left-radius:4px;
+              }
+              p:nth-child(2){
+                border-left:none;
+                border-right:none;
+              }
+              p:nth-child(3){
+                border-left:none;
+                border-right:none;
+              }
+              p:nth-child(4){
+                border-top-right-radius:4px;
+                border-bottom-right-radius:4px;
+              }
+              .check-table-button{
+                border:1px solid #0183fa;
+                background-color: #0183fa;
+                color:#fff;
+              }
+            }
+          }
+          .page-content-box{
+            padding:20px 0;
+          }
+          .for-max-big-box{
+            flex:1;
+            .for-big-box{
+              cursor: pointer;
+              display: inline-block;
+              width:320px;
+              margin:0 0 20px 20px;
+              border-radius:6px;
+              overflow: hidden;
+              box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
+              position: relative;
+              img{
+                width:320px;
+                height:180px;
+              }
+              .bottom-big-box{
+                padding:10px 0 10px 10px;
+                .name-p{
+                  line-height:30px;
+                  font-size:16px;
+                  font-weight:700;
+                }
+                .time{
+                  line-height:30px;
+                  font-size:14px;
+                }
+              }
+              .type-p{
+                position: absolute;
+                right:20px;
+                bottom:15px;
+                padding:5px 10px;
+                border-radius:4px;
+                font-size:14px;
+              }
+              .type-p-1{
+                color:#fff;
+                background-color: #999;
+              }
+              .type-p-2{
+                color:#fff;
+                background-color: #7CCD7C;
+              }
+              .type-p-3{
+                color:#fff;
+                background-color: #0183fa;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 361 - 0
src/views/safetyEducationExaminationNew/safetyEducation/learningTasks/index.vue

@@ -0,0 +1,361 @@
+<!-- 学习任务 -->
+<template>
+  <div class="app-container learningTasks">
+    <div class="page-container learningTasksPage">
+      <p class="pageTitle">学习任务</p>
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;">
+          <div class="table-top-button-box">
+            <div>
+              <p :class="shareStatus === ''?'check-table-button':''" @click="tableTypeButton('')">全部</p>
+              <p :class="shareStatus === 1?'check-table-button':''" @click="tableTypeButton(1)">未学习</p>
+              <p :class="shareStatus === 2?'check-table-button':''" @click="tableTypeButton(2)">学习中</p>
+              <p :class="shareStatus === 3?'check-table-button':''" @click="tableTypeButton(3)">已学完</p>
+            </div>
+          </div>
+          <el-form-item label="" prop="queryParamsData1">
+            <el-cascader
+              v-model="queryParams.queryParamsData1"
+              :options="optionListB"
+              :props="{
+                emitPath:false,
+                checkStrictly: true,
+                value: 'id',
+                label: 'knowledgePointName',
+                children: 'children',
+              }"
+              style="width: 180px"
+              :show-all-levels="false"
+              filterable
+              placeholder="知识点"
+            ></el-cascader>
+          </el-form-item>
+          <el-form-item label="" prop="queryParamsData2">
+            <el-input
+              maxLength="30"
+              v-model="queryParams.queryParamsData2"
+              placeholder="搜索名称"
+              style="width: 200px"
+            />
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <div class="for-max-big-box scrollbar-box">
+          <div class="for-big-box" v-for="(item,index) in dataList" :key="index">
+            <img :src="item.data1" alt="未配置封面">
+            <div class="bottom-big-box">
+              <p class="name-p">{{item.data3}}</p>
+              <p class="time-p">{{item.data4}}分钟 丨 {{item.data5}}学时 丨 {{item.data6}}积分</p>
+              <div class="text-box">
+                <p>已学 {{item.data7}} <span> / 总 {{item.data8}} 课件</span></p>
+              </div>
+            </div>
+            <p class="type-p" :class="item.data2=='1'?'type-p-1':(item.data2=='2'?'type-p-2':(item.data2=='3'?'type-p-3':''))">{{item.data2=='1'?'未学习':(item.data2=='2'?'学习中':(item.data2=='3'?'已学完':''))}}</p>
+            <el-progress
+              class="num-p" type="circle"
+              :stroke-width="4"
+              :width="60" :percentage="item.data9">
+            </el-progress>
+          </div>
+        </div>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  import { examElKnowledgePointTreeList, } from "@/api/safetyEducationExaminationNew/index";
+  export default {
+    name: 'index',
+    data() {
+      return {
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        //下拉列表数据
+        optionListB: [],
+        //查询条件
+        shareStatus:'',
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        },
+        //列表数据
+        dataList: [
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'2',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'2',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'3',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'3',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'2',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'3',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+          {data1:'https://img1.baidu.com/it/u=1480196961,2254893225&fm=253&app=138&f=JPEG?w=800&h=1010',data2:'1',data3:'课程名称',data4:'20',data5:'1',data6:'3',data7:'0',data8:'2',data9:'50',},
+        ],
+        //数据数量
+        total: 0,
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      //this.getList();
+      this.examElKnowledgePointTreeList();
+    },
+    methods: {
+      //切换按钮
+      tableTypeButton(val){
+        if(this.shareStatus != val){
+          this.$set(this,'shareStatus',val);
+          this.handleQuery();
+        }
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          //新增
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 2) {
+          //详情
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = true
+          this.$set(this, 'propsData', obj)
+        } else if (type == 3) {
+          //编辑
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = false
+          this.$set(this, 'propsData', obj)
+        } else if (type == 4) {
+          //删除
+          this.$confirm('是否确认删除?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            deleteFunction({ id: row.id }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 5) {
+          //启用&停用
+          let text = row.state ? '停用' : '启用'
+          this.$confirm('是否确认' + text + '?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            stateFunction({ id: row.id, state: !row.state }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      },
+      examElKnowledgePointTreeList(){
+        examElKnowledgePointTreeList({}).then(response => {
+          const list = response.data || [];
+          this.formatTreeData(list);
+          this.$set(this, 'optionListB', list);
+        });
+      },
+      formatTreeData(data) {
+        data.forEach(item => {
+          // 如果 children 存在,且是数组,且长度为 0(即 children: [])
+          if (Array.isArray(item.children) && item.children.length === 0) {
+            // 将 children 设为 undefined(或直接 delete item.children;)
+            item.children = undefined;
+          } else if (item.children && item.children.length > 0) {
+            // 如果还有下一级,继续递归处理
+            this.formatTreeData(item.children);
+          }
+        });
+      },
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .learningTasks {
+    .learningTasksPage {
+      .pageTitle{
+        height:60px;
+        line-height:60px;
+        padding:0 20px;
+        border-bottom:1px solid #dedede;
+      }
+      .table-top-button-box{
+        display: inline-block;
+        vertical-align: top;
+        margin-right:10px;
+        div{
+          display: flex;
+          p{
+            border:1px solid #DCDFE6;
+            width:80px;
+            text-align: center;
+            height:40px;
+            line-height:40px;
+            cursor: pointer;
+            font-size:14px;
+          }
+          p:nth-child(1){
+            border-top-left-radius:4px;
+            border-bottom-left-radius:4px;
+          }
+          p:nth-child(2){
+            border-left:none;
+            border-right:none;
+          }
+          p:nth-child(3){
+            border-left:none;
+            border-right:none;
+          }
+          p:nth-child(4){
+            border-top-right-radius:4px;
+            border-bottom-right-radius:4px;
+          }
+          .check-table-button{
+            border:1px solid #0183fa;
+            background-color: #0183fa;
+            color:#fff;
+          }
+        }
+      }
+      .page-content-box{
+        padding:20px 0;
+      }
+      .for-max-big-box{
+        flex:1;
+        .for-big-box{
+          cursor: pointer;
+          display: inline-block;
+          width:370px;
+          margin:0 0 20px 20px;
+          border-radius:6px;
+          overflow: hidden;
+          box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
+          position: relative;
+          img{
+            width:370px;
+            height:180px;
+          }
+          .bottom-big-box{
+            padding:10px 0 10px 10px;
+            .name-p{
+              line-height:30px;
+              font-size:16px;
+              font-weight:700;
+            }
+            .time{
+              line-height:30px;
+              font-size:14px;
+            }
+            .text-box{
+              display: flex;
+              p{
+                line-height:30px;
+                font-size:14px;
+                font-weight:700;
+                span{
+                  color:#666;
+                  font-weight:500;
+                }
+              }
+            }
+          }
+          .type-p{
+            position: absolute;
+            right:20px;
+            top:15px;
+            padding:5px 10px;
+            border-radius:4px;
+            font-size:14px;
+          }
+          .type-p-1{
+            color:#fff;
+            background-color: #999;
+          }
+          .type-p-2{
+            color:#fff;
+            background-color: #7CCD7C;
+          }
+          .type-p-3{
+            color:#fff;
+            background-color: #0183fa;
+          }
+          .num-p{
+            position: absolute;
+            right:20px;
+            bottom:20px;
+          }
+          .num-p ::v-deep .el-progress__text {
+            margin-left:0!important;
+            font-size:12px!important;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 361 - 0
src/views/safetyEducationExaminationNew/safetyTest/mockExam/index.vue

@@ -0,0 +1,361 @@
+<!-- 模拟考试 -->
+<template>
+  <div class="app-container mockExam">
+    <div class="page-container mockExamPage" v-if="pageType === 1">
+      <p class="pageTitle">模拟考试</p>
+      <div class="top-max-button-box scrollbar-x-box">
+        <p :class="topCheckId == item.id?'check-button':''"
+           @click="topCheckButton(item.id)"
+           v-for="(item,index) in topDataList" :key="index">{{item.label}} {{item.num}}</p>
+      </div>
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;">
+          <div class="table-top-button-box">
+            <div>
+              <p :class="shareStatus === ''?'check-table-button':''" @click="tableTypeButton('')">全部</p>
+              <p :class="shareStatus === 1?'check-table-button':''" @click="tableTypeButton(1)">未开始</p>
+              <p :class="shareStatus === 2?'check-table-button':''" @click="tableTypeButton(2)">进行中</p>
+              <p :class="shareStatus === 3?'check-table-button':''" @click="tableTypeButton(3)">已完成</p>
+            </div>
+          </div>
+          <el-form-item label="" prop="queryParamsData1">
+            <el-input
+              maxLength="30"
+              v-model="queryParams.queryParamsData1"
+              placeholder="搜索名称"
+              style="width: 200px"
+            />
+          </el-form-item>
+          <el-form-item label="" prop="state">
+            <el-date-picker
+              :clearable="false"
+              v-model="dateRange"
+              size="small"
+              style="width: 240px"
+              value-format="yyyy-MM-dd"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <div class="for-max-big-box scrollbar-box">
+          <div class="for-big-box" v-for="(item,index) in dataList" :key="index">
+            <div class="top-box">
+              <p class="title-p">{{item.data1}}</p>
+              <p class="text-p" v-if="item.data5">满足前置条件后可参加考试</p>
+            </div>
+            <div class="bottom-box">
+              <p class="text-big-p">{{item.data2}}</p>
+              <p class="text-p">{{item.data3}} 至 {{item.data4}}</p>
+              <p class="time-p">时长:{{item.data6}}分钟 丨 总分:{{item.data7}}分 丨 及格分:{{item.data8}}分 丨 可考试次数:{{item.data9}}次</p>
+            </div>
+            <p class="position-p-1" v-if="item.data5" @click="tableButton(1)">完成模拟考试练习后解锁</p>
+            <p class="position-p-2" v-if="item.data10" @click="tableButton(2)">历史错题</p>
+          </div>
+        </div>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    style="margin-right:20px;"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+    <historyOfIncorrectQuestions :propsData="propsData" v-if="pageType === 2"></historyOfIncorrectQuestions>
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  //import { getInfo } from "@/api/basicsModules/index";
+  //import addPage from "./addPage.vue";
+  import historyOfIncorrectQuestions from "@/views/safetyEducationExaminationNew/components/historyOfIncorrectQuestions.vue";
+  export default {
+    name: 'index',
+    components: {
+      historyOfIncorrectQuestions,
+    },
+    data() {
+      return {
+        tableButtonType: this.hasPermiDom(['demo:demo:detail', 'demo:demo:edit', 'demo:demo:del']),
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        //横向列表
+        topDataList:[
+          {label:'全部',id:'1',num:'10'},{label:'全部',id:'2',num:'10'},{label:'全部',id:'3',num:'10'},
+          {label:'全部',id:'4',num:'10'},{label:'全部',id:'5',num:'10'},{label:'全部',id:'6',num:'10'},
+          {label:'全部',id:'1',num:'10'},{label:'全部',id:'2',num:'10'},{label:'全部',id:'3',num:'10'},
+          {label:'全部',id:'4',num:'10'},{label:'全部',id:'5',num:'10'},{label:'全部',id:'6',num:'10'},
+        ],
+        //查询条件
+        topCheckId:'1',
+        shareStatus:'',
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+        },
+        //时间数据
+        dateRange: [],
+        //列表数据
+        dataList: [
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:true,
+            data6:'30',data7:'100',data8:'60',data9:'10',data10:true,},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:false,
+            data6:'30',data7:'100',data8:'60',data9:'10',data10:true,},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:true,
+            data6:'30',data7:'100',data8:'60',data9:'10',data10:false,},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:true,
+            data6:'30',data7:'100',data8:'60',data9:'10',data10:true,},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:true,
+            data6:'30',data7:'100',data8:'60',data9:'10',data10:false,},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:false,
+            data6:'30',data7:'100',data8:'60',data9:'10',data10:true,},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:true,
+            data6:'30',data7:'100',data8:'60',data9:'10',data10:false,},
+        ],
+        //数据数量
+        total: 0,
+        //组件传参
+        propsData: {}
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      //this.getList();
+    },
+    methods: {
+      topCheckButton(id){
+        if(this.topCheckId != id){
+          this.$set(this,'topCheckId',id);
+        }
+      },
+      //切换按钮
+      tableTypeButton(val){
+        if(this.shareStatus != val){
+          this.$set(this,'shareStatus',val);
+          this.handleQuery();
+        }
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          //模拟练习
+
+        } else if (type == 2) {
+          //历史错题
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      }
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .mockExam {
+    .mockExamPage {
+      .pageTitle{
+        height:60px;
+        line-height:60px;
+        padding:0 20px;
+        border-bottom:1px solid #dedede;
+      }
+      .page-form-title-box{
+        padding-top:8px;
+        height: 72px;
+      }
+      .top-max-button-box{
+        padding:20px 20px 8px;
+        p{
+          display: inline-block;
+          padding:0 20px;
+          text-align: center;
+          color:#666;
+          border:1px solid #666;
+          line-height:40px;
+          font-size:16px;
+          border-radius:4px;
+          min-width:80px;
+          margin-right:10px;
+          cursor: pointer;
+        }
+        .check-button{
+          background-color: #0183fa;
+          color:#fff;
+          border:1px solid #0183fa;
+        }
+      }
+      .table-top-button-box{
+        display: inline-block;
+        vertical-align: top;
+        margin-right:10px;
+        div{
+          display: flex;
+          p{
+            border:1px solid #DCDFE6;
+            width:80px;
+            text-align: center;
+            height:40px;
+            line-height:40px;
+            cursor: pointer;
+            font-size:14px;
+          }
+          p:nth-child(1){
+            border-top-left-radius:4px;
+            border-bottom-left-radius:4px;
+          }
+          p:nth-child(2){
+            border-left:none;
+            border-right:none;
+          }
+          p:nth-child(3){
+            border-left:none;
+            border-right:none;
+          }
+          p:nth-child(4){
+            border-top-right-radius:4px;
+            border-bottom-right-radius:4px;
+          }
+          .check-table-button{
+            border:1px solid #0183fa;
+            background-color: #0183fa;
+            color:#fff;
+          }
+        }
+      }
+      .page-content-box{
+        padding:10px 0;
+      }
+      .for-max-big-box{
+        flex:1;
+        .for-big-box{
+          display: inline-block;
+          width:760px;
+          margin:10px 0 10px 20px;
+          border-radius:10px;
+          overflow: hidden;
+          box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+          position: relative;
+          padding:20px;
+          .top-box{
+            height:60px;
+          }
+          .bottom-box{
+            background-color: rgba(1,131,250,0.1);
+            padding:10px 20px 20px;
+            border-radius:10px;
+            margin-top:10px;
+          }
+          .title-p{
+            color:#0183fa;
+            font-size:16px;
+            font-weight:700;
+            line-height:20px;
+          }
+          .text-p{
+            font-size:14px;
+            margin-top:10px;
+            line-height:20px;
+          }
+          .text-big-p{
+            font-size:14px;
+            margin-top:10px;
+            line-height:20px;
+          }
+          .time-p{
+            border-radius:4px;
+            font-size:14px;
+            margin-top:20px;
+            line-height:20px;
+          }
+          .position-p-1{
+            position:absolute;
+            top:30px;
+            right:30px;
+            border:1px solid #0183fa;
+            color:#0183fa;
+            background-color: rgba(1,131,250,0.2);
+            border-radius:4px;
+            font-size:14px;
+            line-height:30px;
+            width:180px;
+            text-align: center;
+            cursor: pointer;
+          }
+          .position-p-2{
+            position:absolute;
+            bottom:100px;
+            right:40px;
+            border:1px solid #0183fa;
+            color:#fff;
+            background-color: rgba(1,131,250,1);
+            border-radius:4px;
+            font-size:14px;
+            line-height:30px;
+            width:80px;
+            text-align: center;
+            cursor: pointer;
+          }
+        }
+      }
+
+    }
+  }
+</style>

+ 360 - 0
src/views/safetyEducationExaminationNew/safetyTest/myGrades/index.vue

@@ -0,0 +1,360 @@
+<!-- 我的成绩 -->
+<template>
+  <div class="app-container myGrades">
+    <div class="page-container myGradesPage" v-if="pageType === 1">
+      <p class="pageTitle">模拟考试</p>
+      <div class="top-max-button-box scrollbar-x-box">
+        <p :class="topCheckId == item.id?'check-button':''"
+           @click="topCheckButton(item.id)"
+           v-for="(item,index) in topDataList" :key="index">{{item.label}} {{item.num}}</p>
+      </div>
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;">
+          <div class="table-top-button-box">
+            <div>
+              <p :class="shareStatus === ''?'check-table-button':''" @click="tableTypeButton('')">全部</p>
+              <p :class="shareStatus === 1?'check-table-button':''" @click="tableTypeButton(1)">通过</p>
+              <p :class="shareStatus === 2?'check-table-button':''" @click="tableTypeButton(2)">未通过</p>
+            </div>
+          </div>
+          <el-form-item label="" prop="state">
+            <el-date-picker
+              :clearable="false"
+              v-model="dateRange"
+              size="small"
+              style="width: 240px"
+              value-format="yyyy-MM-dd"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <div class="for-max-big-box scrollbar-box">
+          <div class="for-big-box" v-for="(item,index) in dataList" :key="index">
+            <p class="title-p">{{item.data1}}</p>
+            <p class="text-p">{{item.data2}}</p>
+            <p class="text-p">{{item.data3}}</p>
+            <p class="color-p">时长:{{item.data4}}分钟 丨 总分:{{item.data5}}分 丨 及格分:{{item.data6}}分 丨 考试次数:{{item.data7}}/{{item.data8}}次</p>
+            <div class="color-box">
+              <p>我的用时:{{item.data9}}</p>
+              <p>最高成绩:{{item.data10}}</p>
+              <p>获得积分:{{item.data11}}</p>
+              <p>考试结果:<span :class="item.data12?'colorA':'colorB'">{{item.data12?'通过':'未通过'}}</span></p>
+            </div>
+            <p class="position-p-1" @click="tableButton(1)">考试记录</p>
+            <p class="position-p-2" v-if="item.data12" @click="tableButton(2)">历史错题</p>
+          </div>
+        </div>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    style="margin-right:20px;"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+    <examRecords :propsData="propsData" v-if="pageType === 2"></examRecords>
+    <historyOfIncorrectQuestions :propsData="propsData" v-if="pageType === 3"></historyOfIncorrectQuestions>
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  //import { getInfo } from "@/api/basicsModules/index";
+  //import addPage from "./addPage.vue";
+  import historyOfIncorrectQuestions from "@/views/safetyEducationExaminationNew/components/historyOfIncorrectQuestions.vue";
+  import examRecords from "@/views/safetyEducationExaminationNew/components/examRecords.vue";
+  export default {
+    name: 'index',
+    components: {
+      historyOfIncorrectQuestions,
+      examRecords,
+    },
+    data() {
+      return {
+        tableButtonType: this.hasPermiDom(['demo:demo:detail', 'demo:demo:edit', 'demo:demo:del']),
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        //横向列表
+        topDataList:[
+          {label:'全部',id:'1',num:'10'},{label:'全部',id:'2',num:'10'},{label:'全部',id:'3',num:'10'},
+          {label:'全部',id:'4',num:'10'},{label:'全部',id:'5',num:'10'},{label:'全部',id:'6',num:'10'},
+          {label:'全部',id:'1',num:'10'},{label:'全部',id:'2',num:'10'},{label:'全部',id:'3',num:'10'},
+          {label:'全部',id:'4',num:'10'},{label:'全部',id:'5',num:'10'},{label:'全部',id:'6',num:'10'},
+        ],
+        //查询条件
+        topCheckId:'1',
+        shareStatus:'',
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+        },
+        //时间数据
+        dateRange: [],
+        //列表数据
+        dataList: [
+          { data1:'分级分类考试', data2:'考试名称考试名称考试名称',
+            data3:'2026-09-01  9:30 ~ 2026-09-01 10:00',
+            data4:'30',data5:'100',data6:'60',data7:'6',data8:'10',
+            data9:'20分16秒',data10:'90',data11:'6',data12:true, },
+          { data1:'分级分类考试', data2:'考试名称考试名称考试名称',
+            data3:'2026-09-01  9:30 ~ 2026-09-01 10:00',
+            data4:'30',data5:'100',data6:'60',data7:'6',data8:'10',
+            data9:'20分16秒',data10:'90',data11:'6',data12:false, },
+          { data1:'分级分类考试', data2:'考试名称考试名称考试名称',
+            data3:'2026-09-01  9:30 ~ 2026-09-01 10:00',
+            data4:'30',data5:'100',data6:'60',data7:'6',data8:'10',
+            data9:'20分16秒',data10:'90',data11:'6',data12:true, },
+          { data1:'分级分类考试', data2:'考试名称考试名称考试名称',
+            data3:'2026-09-01  9:30 ~ 2026-09-01 10:00',
+            data4:'30',data5:'100',data6:'60',data7:'6',data8:'10',
+            data9:'20分16秒',data10:'90',data11:'6',data12:false, },
+          { data1:'分级分类考试', data2:'考试名称考试名称考试名称',
+            data3:'2026-09-01  9:30 ~ 2026-09-01 10:00',
+            data4:'30',data5:'100',data6:'60',data7:'6',data8:'10',
+            data9:'20分16秒',data10:'90',data11:'6',data12:true, },
+          { data1:'分级分类考试', data2:'考试名称考试名称考试名称',
+            data3:'2026-09-01  9:30 ~ 2026-09-01 10:00',
+            data4:'30',data5:'100',data6:'60',data7:'6',data8:'10',
+            data9:'20分16秒',data10:'90',data11:'6',data12:false, },
+        ],
+        //数据数量
+        total: 0,
+        //组件传参
+        propsData: {}
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      //this.getList();
+    },
+    methods: {
+      topCheckButton(id){
+        if(this.topCheckId != id){
+          this.$set(this,'topCheckId',id);
+        }
+      },
+      //切换按钮
+      tableTypeButton(val){
+        if(this.shareStatus != val){
+          this.$set(this,'shareStatus',val);
+          this.handleQuery();
+        }
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          //考试记录
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 2) {
+          //历史错题
+          this.$set(this, 'pageType', 3)
+          this.$set(this, 'propsData', {})
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      },
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .myGrades {
+    .myGradesPage {
+      .pageTitle{
+        height:60px;
+        line-height:60px;
+        padding:0 20px;
+        border-bottom:1px solid #dedede;
+      }
+      .page-form-title-box{
+        padding-top:8px;
+        height: 72px;
+      }
+      .top-max-button-box{
+        padding:20px 20px 8px;
+        p{
+          display: inline-block;
+          padding:0 20px;
+          text-align: center;
+          color:#666;
+          border:1px solid #666;
+          line-height:40px;
+          font-size:16px;
+          border-radius:4px;
+          min-width:80px;
+          margin-right:10px;
+          cursor: pointer;
+        }
+        .check-button{
+          background-color: #0183fa;
+          color:#fff;
+          border:1px solid #0183fa;
+        }
+      }
+      .table-top-button-box{
+        display: inline-block;
+        vertical-align: top;
+        margin-right:10px;
+        div{
+          display: flex;
+          p{
+            border:1px solid #DCDFE6;
+            width:80px;
+            text-align: center;
+            height:40px;
+            line-height:40px;
+            cursor: pointer;
+            font-size:14px;
+          }
+          p:nth-child(1){
+            border-top-left-radius:4px;
+            border-bottom-left-radius:4px;
+          }
+          p:nth-child(2){
+            border-left:none;
+            border-right:none;
+          }
+          p:nth-child(3){
+            border-top-right-radius:4px;
+            border-bottom-right-radius:4px;
+          }
+          .check-table-button{
+            border:1px solid #0183fa;
+            background-color: #0183fa;
+            color:#fff;
+          }
+        }
+      }
+      .page-content-box{
+        padding:10px 0;
+      }
+      .for-max-big-box{
+        flex:1;
+        .for-big-box{
+          display: inline-block;
+          width:760px;
+          margin:10px 0 10px 20px;
+          border-radius:10px;
+          overflow: hidden;
+          box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+          position: relative;
+          padding:20px;
+          .title-p{
+            color:#0183fa;
+            font-size:16px;
+            font-weight:700;
+            line-height:20px;
+          }
+          .text-p{
+            font-size:14px;
+            margin-top:10px;
+            line-height:20px;
+          }
+          .color-p{
+            padding:0 10px;
+            border-radius:4px;
+            font-size:14px;
+            margin-top:10px;
+            line-height:30px;
+            background-color: rgba(222,222,222,0.4);
+          }
+          .color-box{
+            padding:0 10px;
+            border-radius:4px;
+            margin-top:10px;
+            line-height:30px;
+            display: flex;
+            font-size:14px;
+            background-color: rgba(1,131,250,0.1);
+            p{
+              flex:1;
+            }
+            .colorA{
+              color:#00B176;
+            }
+            .colorB{
+              color:#E86452;
+            }
+          }
+          .position-p-1{
+            position:absolute;
+            top:20px;
+            right:20px;
+            border:1px solid #0183fa;
+            color:#0183fa;
+            background-color: rgba(1,131,250,0.2);
+            border-radius:4px;
+            font-size:14px;
+            line-height:30px;
+            width:80px;
+            text-align: center;
+            cursor: pointer;
+          }
+          .position-p-2{
+            position:absolute;
+            top:60px;
+            right:20px;
+            border:1px solid #F59A48;
+            color:#F59A48;
+            background-color: rgba(245,154,72,0.2);
+            border-radius:4px;
+            font-size:14px;
+            line-height:30px;
+            width:80px;
+            text-align: center;
+            cursor: pointer;
+          }
+        }
+      }
+
+    }
+  }
+</style>

+ 355 - 0
src/views/safetyEducationExaminationNew/safetyTest/onlineExam/index.vue

@@ -0,0 +1,355 @@
+<!-- 在线考试 -->
+<template>
+  <div class="app-container onlineExam">
+    <div class="page-container onlineExamPage" v-if="pageType === 1">
+      <p class="pageTitle">在线考试</p>
+      <div class="top-max-button-box scrollbar-x-box">
+        <p :class="topCheckId == item.id?'check-button':''"
+           @click="topCheckButton(item.id)"
+          v-for="(item,index) in topDataList" :key="index">{{item.label}} {{item.num}}</p>
+      </div>
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;">
+          <div class="table-top-button-box">
+            <div>
+              <p :class="shareStatus === ''?'check-table-button':''" @click="tableTypeButton('')">全部</p>
+              <p :class="shareStatus === 1?'check-table-button':''" @click="tableTypeButton(1)">未开始</p>
+              <p :class="shareStatus === 2?'check-table-button':''" @click="tableTypeButton(2)">进行中</p>
+              <p :class="shareStatus === 3?'check-table-button':''" @click="tableTypeButton(3)">已完成</p>
+            </div>
+          </div>
+          <el-form-item label="" prop="queryParamsData1">
+            <el-input
+              maxLength="30"
+              v-model="queryParams.queryParamsData1"
+              placeholder="搜索名称"
+              style="width: 200px"
+            />
+          </el-form-item>
+          <el-form-item label="" prop="state">
+            <el-date-picker
+              :clearable="false"
+              v-model="dateRange"
+              size="small"
+              style="width: 240px"
+              value-format="yyyy-MM-dd"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <div class="for-max-big-box scrollbar-box">
+          <div class="for-big-box" v-for="(item,index) in dataList" :key="index">
+            <div class="top-box">
+              <p class="title-p">{{item.data1}}</p>
+              <p class="text-p" v-if="item.data5">满足前置条件后可参加考试</p>
+            </div>
+            <div class="bottom-box">
+              <p class="text-big-p">{{item.data2}}</p>
+              <p class="text-p">{{item.data3}} 至 {{item.data4}}</p>
+              <p class="time-p">时长:{{item.data6}}分钟 丨 总分:{{item.data7}}分 丨 及格分:{{item.data8}}分 丨 可考试次数:{{item.data9}}次</p>
+            </div>
+            <p class="position-p-1"
+               v-if="item.data5" @click="tableButton(1,item)">
+              {{item.data5==1?'完成学时任务后解锁':(item.data5==2?'完成学时要求后解锁':(item.data5==3?'完成考前课程学习后解锁':(item.data5==4?'完成模拟考试练习后解锁':(item.data5==5?'开始考试':''))))}}
+            </p>
+          </div>
+        </div>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    style="margin-right:20px;"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+    <historyOfIncorrectQuestions :propsData="propsData" v-if="pageType === 2"></historyOfIncorrectQuestions>
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  //import { getInfo } from "@/api/basicsModules/index";
+  //import addPage from "./addPage.vue";
+  import historyOfIncorrectQuestions from "@/views/safetyEducationExaminationNew/components/historyOfIncorrectQuestions.vue";
+  export default {
+    name: 'index',
+    components: {
+      historyOfIncorrectQuestions,
+    },
+    data() {
+      return {
+        tableButtonType: this.hasPermiDom(['demo:demo:detail', 'demo:demo:edit', 'demo:demo:del']),
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        //横向列表
+        topDataList:[
+          {label:'全部',id:'1',num:'10'},{label:'全部',id:'2',num:'10'},{label:'全部',id:'3',num:'10'},
+          {label:'全部',id:'4',num:'10'},{label:'全部',id:'5',num:'10'},{label:'全部',id:'6',num:'10'},
+          {label:'全部',id:'1',num:'10'},{label:'全部',id:'2',num:'10'},{label:'全部',id:'3',num:'10'},
+          {label:'全部',id:'4',num:'10'},{label:'全部',id:'5',num:'10'},{label:'全部',id:'6',num:'10'},
+        ],
+        //查询条件
+        topCheckId:'1',
+        shareStatus:'',
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+        },
+        //时间数据
+        dateRange: [],
+        //列表数据
+        dataList: [
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:1,
+            data6:'30',data7:'100',data8:'60',data9:'10',},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:2,
+            data6:'30',data7:'100',data8:'60',data9:'10',},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:3,
+            data6:'30',data7:'100',data8:'60',data9:'10',},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:4,
+            data6:'30',data7:'100',data8:'60',data9:'10',},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:5,
+            data6:'30',data7:'100',data8:'60',data9:'10',},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:5,
+            data6:'30',data7:'100',data8:'60',data9:'10',},
+          { data1:'新生入学考试', data2:'模拟考试名称考试名称考试',
+            data3:'2026-09-01', data4:'2026-09-30', data5:5,
+            data6:'30',data7:'100',data8:'60',data9:'10',},
+        ],
+        //数据数量
+        total: 0,
+        //组件传参
+        propsData: {}
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      //this.getList();
+    },
+    methods: {
+      topCheckButton(id){
+        if(this.topCheckId != id){
+          this.$set(this,'topCheckId',id);
+        }
+      },
+      //切换按钮
+      tableTypeButton(val){
+        if(this.shareStatus != val){
+          this.$set(this,'shareStatus',val);
+          this.handleQuery();
+        }
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, item) {
+        let self = this
+        if (type == 1) {
+          //模拟练习
+          if(item.data5 == 1){
+
+          }else if(item.data5 == 2){
+
+          }else if(item.data5 == 3){
+
+          }else if(item.data5 == 4){
+
+          }else if(item.data5 == 5){
+
+          }
+
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      }
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .onlineExam {
+    .onlineExamPage {
+      .pageTitle{
+        height:60px;
+        line-height:60px;
+        padding:0 20px;
+        border-bottom:1px solid #dedede;
+      }
+      .page-form-title-box{
+        padding-top:8px;
+        height: 72px;
+      }
+      .top-max-button-box{
+        padding:20px 20px 8px;
+        p{
+          display: inline-block;
+          padding:0 20px;
+          text-align: center;
+          color:#666;
+          border:1px solid #666;
+          line-height:40px;
+          font-size:16px;
+          border-radius:4px;
+          min-width:80px;
+          margin-right:10px;
+          cursor: pointer;
+        }
+        .check-button{
+          background-color: #0183fa;
+          color:#fff;
+          border:1px solid #0183fa;
+        }
+      }
+      .table-top-button-box{
+        display: inline-block;
+        vertical-align: top;
+        margin-right:10px;
+        div{
+          display: flex;
+          p{
+            border:1px solid #DCDFE6;
+            width:80px;
+            text-align: center;
+            height:40px;
+            line-height:40px;
+            cursor: pointer;
+            font-size:14px;
+          }
+          p:nth-child(1){
+            border-top-left-radius:4px;
+            border-bottom-left-radius:4px;
+          }
+          p:nth-child(2){
+            border-left:none;
+            border-right:none;
+          }
+          p:nth-child(3){
+            border-left:none;
+            border-right:none;
+          }
+          p:nth-child(4){
+            border-top-right-radius:4px;
+            border-bottom-right-radius:4px;
+          }
+          .check-table-button{
+            border:1px solid #0183fa;
+            background-color: #0183fa;
+            color:#fff;
+          }
+        }
+      }
+      .page-content-box{
+        padding:10px 0;
+      }
+      .for-max-big-box{
+        flex:1;
+        .for-big-box{
+          display: inline-block;
+          width:760px;
+          margin:10px 0 10px 20px;
+          border-radius:10px;
+          overflow: hidden;
+          box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+          position: relative;
+          padding:20px;
+          .top-box{
+            height:60px;
+          }
+          .bottom-box{
+            background-color: rgba(1,131,250,0.1);
+            padding:10px 20px 20px;
+            border-radius:10px;
+            margin-top:10px;
+          }
+          .title-p{
+            color:#0183fa;
+            font-size:16px;
+            font-weight:700;
+            line-height:20px;
+          }
+          .text-p{
+            font-size:14px;
+            margin-top:10px;
+            line-height:20px;
+          }
+          .text-big-p{
+            font-size:14px;
+            margin-top:10px;
+            line-height:20px;
+          }
+          .time-p{
+            border-radius:4px;
+            font-size:14px;
+            margin-top:20px;
+            line-height:20px;
+          }
+          .position-p-1{
+            position:absolute;
+            top:30px;
+            right:30px;
+            border:1px solid #0183fa;
+            color:#0183fa;
+            background-color: rgba(1,131,250,0.2);
+            border-radius:4px;
+            font-size:14px;
+            line-height:30px;
+            width:180px;
+            text-align: center;
+            cursor: pointer;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 415 - 0
src/views/safetyEducationExaminationNew/safetyTest/practiceQuestions/index.vue

@@ -0,0 +1,415 @@
+<!-- 刷题练习 -->
+<template>
+  <div class="app-container practiceQuestions">
+    <div class="page-container practiceQuestionsPage" v-if="pageType === 1">
+      <p class="pageTitle">刷题练习</p>
+      <div class="max-big-box">
+        <div class="left-box scrollbar-box">
+          <p @click="leftCheckButton(index)"
+             v-for="(item,index) in letDataList" :key="index"
+             class="left-for-p" :class="index==leftDataIndex?'leftCheck':''">
+            {{item.name}}
+          </p>
+        </div>
+        <div class="right-box">
+          <div class="page-form-title-box">
+            <el-form :model="queryParams" class="form-box" ref="queryForm"
+                     :inline="true" style="width:100%;">
+              <div class="table-top-button-box">
+                <div>
+                  <p :class="shareStatus === ''?'check-table-button':''" @click="tableTypeButton('')">全部</p>
+                  <p :class="shareStatus === 1?'check-table-button':''" @click="tableTypeButton(1)">未开始</p>
+                  <p :class="shareStatus === 2?'check-table-button':''" @click="tableTypeButton(2)">练习中</p>
+                  <p :class="shareStatus === 3?'check-table-button':''" @click="tableTypeButton(3)">已学完</p>
+                </div>
+              </div>
+              <el-form-item label="" prop="queryParamsData1">
+                <el-cascader
+                  v-model="queryParams.queryParamsData1"
+                  :options="optionListB"
+                  :props="{
+                emitPath:false,
+                checkStrictly: true,
+                value: 'id',
+                label: 'knowledgePointName',
+                children: 'children',
+              }"
+                  style="width: 180px"
+                  :show-all-levels="false"
+                  filterable
+                  placeholder="知识点"
+                ></el-cascader>
+              </el-form-item>
+              <el-form-item label="" prop="queryParamsData2">
+                <el-input
+                  maxLength="30"
+                  v-model="queryParams.queryParamsData2"
+                  placeholder="搜索名称"
+                  style="width: 200px"
+                />
+              </el-form-item>
+              <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+              <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+            </el-form>
+          </div>
+          <div class="page-content-box">
+            <div class="for-max-big-box scrollbar-box">
+              <div class="for-big-box" v-for="(item,index) in dataList" :key="index">
+                <p class="title-p">{{item.data1}}{{item.data2?' - '+item.data2:''}}</p>
+                <div class="text-box">
+                  <div class="text-min-box">
+                    <p class="el-icon-time"></p>
+                    <p>{{item.data3}}</p>
+                  </div>
+                  <p class="text-p">练习进度:{{item.data4}} / {{item.data5}}</p>
+                  <p class="text-p">正确数:{{item.data6}} 丨 错题数:{{item.data7}} 丨 正确率:{{item.data8}}%</p>
+                </div>
+                <p class="position-time">{{item.data9}}</p>
+                <p class="position-p" v-if="item.data11" @click="tableButton(1,item)">历史错题</p>
+                <el-progress
+                  class="num-p" type="circle"
+                  :stroke-width="4"
+                  :width="60" :percentage="item.data10">
+                </el-progress>
+              </div>
+            </div>
+            <pagination :page-sizes="[20, 30, 40, 50]"
+                        style="margin-right:20px;"
+                        v-show="total>0"
+                        :total="total"
+                        :page.sync="queryParams.page"
+                        :limit.sync="queryParams.pageSize"
+                        @pagination="getList"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <historyOfIncorrectQuestions :propsData="propsData" v-if="pageType === 2"></historyOfIncorrectQuestions>
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  //import { getInfo } from "@/api/basicsModules/index";
+  //import addPage from "./addPage.vue";
+  import { examElKnowledgePointTreeList, } from "@/api/safetyEducationExaminationNew/index";
+  import historyOfIncorrectQuestions from "@/views/safetyEducationExaminationNew/components/historyOfIncorrectQuestions.vue";
+  export default {
+    name: 'index',
+    components: {
+      historyOfIncorrectQuestions,
+    },
+    data() {
+      return {
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        letDataList:[
+          {name:'全部知识点',id:'1'},{name:'全部知识点',id:'2'},
+          {name:'全部知识点',id:'3'},{name:'全部知识点',id:'4'},
+          {name:'全部知识点',id:'5'},{name:'全部知识点',id:'6'},
+          {name:'全部知识点',id:'7'},{name:'全部知识点',id:'8'},
+          {name:'全部知识点',id:'9'},{name:'全部知识点',id:'10'},
+          {name:'全部知识点',id:'11'},{name:'全部知识点',id:'12'},
+          {name:'全部知识点',id:'13'},{name:'全部知识点',id:'14'},
+        ],
+        leftDataIndex:0,
+        //下拉列表数据
+        optionListB: [],
+        //查询条件
+        shareStatus:'',
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        },
+        //列表数据
+        dataList: [
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'100',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:100,data11:true},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:true},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:false},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:true},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:true},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:false},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:false},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:true},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:false},
+          {data1:'知识点',data2:'二级知识点',data3:'2026-09-1 9:30:33',data4:'33',data5:'369',data6:'3',data7:'3',data8:'69',data9:'2026-09-1 9:30:33',data10:33,data11:false},
+        ],
+        //数据数量
+        total: 0,
+        propsData:{},
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      //this.getList();
+      this.examElKnowledgePointTreeList();
+    },
+    methods: {
+      //左侧列表切换
+      leftCheckButton(index){
+        if(this.leftDataIndex != index){
+          this.$set(this,'leftDataIndex',index);
+        }
+      },
+      //切换按钮
+      tableTypeButton(val){
+        if(this.shareStatus != val){
+          this.$set(this,'shareStatus',val);
+          this.handleQuery();
+        }
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          //历史错题
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      },
+      examElKnowledgePointTreeList(){
+        examElKnowledgePointTreeList({}).then(response => {
+          const list = response.data || [];
+          this.formatTreeData(list);
+          this.$set(this, 'optionListB', list);
+        });
+      },
+      formatTreeData(data) {
+        data.forEach(item => {
+          // 如果 children 存在,且是数组,且长度为 0(即 children: [])
+          if (Array.isArray(item.children) && item.children.length === 0) {
+            // 将 children 设为 undefined(或直接 delete item.children;)
+            item.children = undefined;
+          } else if (item.children && item.children.length > 0) {
+            // 如果还有下一级,继续递归处理
+            this.formatTreeData(item.children);
+          }
+        });
+      },
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .practiceQuestions {
+    .practiceQuestionsPage {
+      .pageTitle{
+        height:60px;
+        line-height:60px;
+        padding:0 20px;
+        border-bottom:1px solid #dedede;
+      }
+      .max-big-box{
+        display: flex;
+        overflow: hidden;
+        .left-box{
+          width:200px;
+          border-right:1px solid #dedede;
+          padding:20px 0 ;
+          .left-for-p{
+            margin:5px 0;
+            padding:0 20px;
+            text-align: center;
+            border-right:4px solid #ffffff;
+            cursor: pointer;
+            font-weight:700;
+            font-size:16px;
+            line-height:40px;
+          }
+          .left-for-p:hover{
+            background-color: #0183fa;
+            color: #ffffff;
+          }
+          .leftCheck{
+            color: #0183fa;
+            border-right:4px solid #0183fa;
+          }
+        }
+        .right-box{
+          flex:1;
+          display: flex;
+          flex-direction: column;
+          overflow: hidden;
+          .top-max-button-box{
+            display: flex;
+            margin:20px 0 0 20px;
+            p{
+              width:120px;
+              line-height:40px;
+              border-radius:4px;
+              color:#333;
+              border:1px solid #333;
+              text-align: center;
+              margin-right:20px;
+              cursor: pointer;
+            }
+            .top-check-button{
+              border:1px solid #0183fa;
+              background-color: #0183fa;
+              color:#fff;
+            }
+          }
+          .table-top-button-box{
+            display: inline-block;
+            vertical-align: top;
+            margin-right:10px;
+            div{
+              display: flex;
+              p{
+                border:1px solid #DCDFE6;
+                width:80px;
+                text-align: center;
+                height:40px;
+                line-height:40px;
+                cursor: pointer;
+                font-size:14px;
+              }
+              p:nth-child(1){
+                border-top-left-radius:4px;
+                border-bottom-left-radius:4px;
+              }
+              p:nth-child(2){
+                border-left:none;
+                border-right:none;
+              }
+              p:nth-child(3){
+                border-left:none;
+                border-right:none;
+              }
+              p:nth-child(4){
+                border-top-right-radius:4px;
+                border-bottom-right-radius:4px;
+              }
+              .check-table-button{
+                border:1px solid #0183fa;
+                background-color: #0183fa;
+                color:#fff;
+              }
+            }
+          }
+          .page-content-box{
+            padding:20px 0;
+          }
+          .for-max-big-box{
+            flex:1;
+            .for-big-box{
+              cursor: pointer;
+              display: inline-block;
+              width:660px;
+              margin:0 0 20px 20px;
+              border-radius:10px;
+              overflow: hidden;
+              box-shadow: 4px 4px 4px 4px rgba(0,0,0,0.1);
+              position: relative;
+              border:1px solid rgba(222,222,222,0.4);
+              background-color: rgba(222,222,222,0.2);
+              .title-p{
+                margin:20px;
+                font-size:16px;
+                font-weight:700;
+              }
+              .text-box{
+                margin:0 20px 20px 20px;
+                background-color: rgba(1,131,250,0.1);
+                overflow: hidden;
+                border-radius:10px;
+                padding:10px 20px;
+                .text-min-box{
+                  line-height: 40px;
+                  color:#0183fa;
+                  display: flex;
+                  p:nth-child(1){
+                    font-size:24px;
+                    line-height: 40px;
+                    margin-right:6px;
+                  }
+                  p:nth-child(2){
+                    font-size:16px;
+                    font-weight:700;
+                  }
+                }
+                .text-p{
+                  font-size:14px;
+                  color:#666;
+                  line-height:30px;
+                  font-weight:700;
+                }
+              }
+              .position-p{
+                position: absolute;
+                right:20px;
+                top:20px;
+                color:#0183fa;
+                font-weight:700;
+              }
+              .position-time{
+                position: absolute;
+                right:40px;
+                bottom:29px;
+                font-size:14px;
+                color:#666;
+                line-height:30px;
+                font-weight:700;
+              }
+              .num-p{
+                position: absolute;
+                right:40px;
+                bottom:65px;
+              }
+              .num-p ::v-deep .el-progress__text {
+                margin-left:0!important;
+                font-size:12px!important;
+              }
+              .num-p ::v-deep svg path:first-child {
+                stroke: #cccccc !important; /* 修改为红色 */
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 246 - 0
src/views/safetyEducationExaminationNew/startExam/startExam.vue

@@ -0,0 +1,246 @@
+<!-- 开始考试 -->
+<template>
+  <div class="app-container startExam">
+    <div class="page-container startExamPage" v-if="pageType === 1">
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;"
+        >
+          <el-form-item label="" prop="queryParamsData1">
+            <el-input
+              maxLength="30"
+              v-model="queryParams.queryParamsData1"
+              placeholder="请输入"
+              style="width: 200px"
+            />
+          </el-form-item>
+          <el-form-item label="" prop="queryParamsData2">
+            <el-select v-model="queryParams.queryParamsData2" placeholder="请选择" style="width: 200px">
+              <el-option
+                v-for="dict in optionList"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="" prop="state">
+            <el-date-picker
+              :clearable="false"
+              v-model="dateRange"
+              size="small"
+              style="width: 240px"
+              value-format="yyyy-MM-dd"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+          <p class="page-submit-common-style-button"
+             style="float: right;"
+             @click="tableButton(1)"
+             v-hasPermiRouter="['demo:demo:add']"
+          >新增</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <el-table class="table-box" v-loading="loading" border :data="dataList">
+          <el-table-column label="名称" prop="name" show-overflow-tooltip/>
+          <el-table-column label="内容" prop="content" width="200" show-overflow-tooltip/>
+          <el-table-column label="状态" prop="state" width="100" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <el-switch
+                @click.native="tableButton(5,scope.row)"
+                class="switch captcha-img"
+                :active-value="true"
+                :inactive-value="false"
+                active-color="#0183FA"
+                inactive-color="#999"
+                v-model="scope.row.state"
+                active-text="启用"
+                inactive-text="停用"
+                disabled
+              ></el-switch>
+            </template>
+          </el-table-column>
+          <el-table-column label="创建时间" prop="createTime" width="200" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <span>{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="200" show-overflow-tooltip v-if="tableButtonType">
+            <template slot-scope="scope">
+              <div class="table-button-box">
+                <p class="table-button-null"></p>
+                <p class="table-button-p"
+                   @click="tableButton(2,scope.row)"
+                   v-hasPermiRouter="['demo:demo:detail']"
+                >详情</p>
+                <p class="table-button-p"
+                   @click="tableButton(3,scope.row)"
+                   v-hasPermiRouter="['demo:demo:edit']"
+                >编辑</p>
+                <p class="table-button-p"
+                   @click="tableButton(4,scope.row)"
+                   v-hasPermiRouter="['demo:demo:del']"
+                >删除</p>
+                <p class="table-button-null"></p>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+    <!--<add-page :propsData="propsData" v-if="pageType === 2"></add-page>-->
+  </div>
+</template>
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  //import { getInfo } from "@/api/basicsModules/index";
+  //import addPage from "./addPage.vue";
+  export default {
+    name: 'index',
+    //components: {
+    //  addPage
+    //},
+    data() {
+      return {
+        tableButtonType: this.hasPermiDom(['demo:demo:detail', 'demo:demo:edit', 'demo:demo:del']),
+        //页面状态
+        pageType: 1,
+        //页面遮罩
+        loading: false,
+        //下拉列表数据
+        optionList: [{ value: true, label: '启用' }, { value: false, label: '停用' }],
+        //查询条件
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        },
+        //时间数据
+        dateRange: [],
+        //列表数据
+        dataList: [],
+        //数据数量
+        total: 0,
+        //组件传参
+        propsData: {}
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      //this.getList();
+    },
+    methods: {
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'dateRange', [])
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: '',
+          queryParamsData2: null
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        if (this.dateRange[0]) {
+          obj.startTime = this.dateRange[0] + 'T00:00:00'
+          obj.endTime = this.dateRange[1] + 'T23:59:59'
+        } else {
+          obj.startTime = ''
+          obj.endTime = ''
+        }
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //操作按钮
+      tableButton(type, row) {
+        let self = this
+        if (type == 1) {
+          //新增
+          this.$set(this, 'pageType', 2)
+          this.$set(this, 'propsData', {})
+        } else if (type == 2) {
+          //详情
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = true
+          this.$set(this, 'propsData', obj)
+        } else if (type == 3) {
+          //编辑
+          this.$set(this, 'pageType', 2)
+          let obj = JSON.parse(JSON.stringify(row))
+          obj.showType = false
+          this.$set(this, 'propsData', obj)
+        } else if (type == 4) {
+          //删除
+          this.$confirm('是否确认删除?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            deleteFunction({ id: row.id }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 5) {
+          //启用&停用
+          let text = row.state ? '停用' : '启用'
+          this.$confirm('是否确认' + text + '?', '警告', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(function() {
+          }).then(() => {
+            stateFunction({ id: row.id, state: !row.state }).then(response => {
+              self.msgSuccess(response.message)
+              self.getList()
+            })
+          }).catch(() => {
+          })
+        } else if (type == 6) {
+          //返回并刷新
+          this.$set(this, 'pageType', 1)
+          this.getList()
+        }
+      }
+    }
+  }
+</script>
+<style scoped lang="scss">
+  .startExam {
+    .startExamPage {
+
+    }
+  }
+</style>

+ 244 - 0
src/views/safetyEducationExaminationNew/workbench/ExamStatsCard.vue

@@ -0,0 +1,244 @@
+<!-- 安监室安全分布表 + 考试成绩统计 -->
+<template>
+  <div class="exam-stats-card">
+    <!-- 安监室安全分布表 -->
+    <div class="section-card">
+      <div class="section-header">
+        <span class="section-title">安监室安全分布表</span>
+        <div class="header-actions">
+          <el-button size="mini">年</el-button>
+          <el-button size="mini">季度</el-button>
+          <el-button size="mini">月</el-button>
+          <el-button size="mini" type="primary">下载</el-button>
+        </div>
+      </div>
+      <el-table :data="tableData.safetyTable" border size="small" stripe>
+        <el-table-column prop="name" label="姓名" min-width="80" />
+        <el-table-column prop="dept" label="部门" min-width="100" />
+        <el-table-column prop="userId" label="用户编号" min-width="100" />
+        <el-table-column prop="eduTitle" label="安全教育名称" min-width="160" />
+        <el-table-column prop="subject" label="科目" min-width="60" />
+        <el-table-column prop="status" label="状态" min-width="60">
+          <template slot-scope="scope">
+            <el-tag :type="scope.row.status === '完成' ? 'success' : 'warning'" size="mini">{{ scope.row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="person" label="负责人" min-width="80" />
+        <el-table-column prop="startDate" label="开始时间" min-width="110" />
+        <el-table-column prop="endDate" label="截止时间" min-width="110" />
+        <el-table-column prop="score" label="成绩" min-width="60" />
+        <el-table-column label="操作" min-width="160" fixed="right">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="() => {}">查看</el-button>
+            <el-button size="mini" type="success" @click="() => {}">{{ scope.row.status === '完成' ? '重考' : '考试' }}</el-button>
+            <el-button size="mini" type="warning" @click="() => {}">提醒</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 实验室安全分布表 -->
+    <div class="section-card">
+      <div class="section-header">
+        <span class="section-title">实验室安全分布表</span>
+        <div class="header-actions">
+          <el-button size="mini">年</el-button>
+          <el-button size="mini">季度</el-button>
+          <el-button size="mini">月</el-button>
+          <el-button size="mini" type="primary">下载</el-button>
+        </div>
+      </div>
+      <el-table :data="tableData.labTable" border size="small" stripe>
+        <el-table-column prop="name" label="姓名" min-width="80" />
+        <el-table-column prop="dept" label="部门" min-width="100" />
+        <el-table-column prop="userId" label="用户编号" min-width="100" />
+        <el-table-column prop="eduTitle" label="安全教育名称" min-width="160" />
+        <el-table-column prop="subject" label="科目" min-width="60" />
+        <el-table-column prop="status" label="状态" min-width="60">
+          <template slot-scope="scope">
+            <el-tag :type="scope.row.status === '完成' ? 'success' : 'warning'" size="mini">{{ scope.row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="person" label="负责人" min-width="80" />
+        <el-table-column prop="startDate" label="开始时间" min-width="110" />
+        <el-table-column prop="endDate" label="截止时间" min-width="110" />
+        <el-table-column prop="score" label="成绩" min-width="60" />
+        <el-table-column label="操作" min-width="160" fixed="right">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="() => {}">查看</el-button>
+            <el-button size="mini" type="success" @click="() => {}">{{ scope.row.status === '完成' ? '重考' : '考试' }}</el-button>
+            <el-button size="mini" type="warning" @click="() => {}">提醒</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 考试成绩统计 -->
+    <div class="section-card">
+      <div class="section-header">
+        <span class="section-title">考试成绩统计</span>
+        <div class="header-actions">
+          <el-select v-model="tableData.statsYear" size="mini" style="width:140px">
+            <el-option label="2025-2026 学年" value="2025-2026" />
+            <el-option label="2024-2025 学年" value="2024-2025" />
+          </el-select>
+          <el-button size="mini">季度</el-button>
+          <el-button size="mini">月</el-button>
+          <el-button size="mini" type="primary">下载</el-button>
+        </div>
+      </div>
+      <div class="stats-body">
+        <!-- 柱状图占位 -->
+        <div class="chart-placeholder">
+          <div class="bar-chart-mock">
+            <div v-for="(bar, i) in tableData.barData" :key="i" class="bar-col">
+              <div class="bar green" :style="{height: bar.pass + 'px'}"></div>
+              <div class="bar red" :style="{height: bar.fail + 'px'}"></div>
+              <div class="bar-label">{{ bar.label }}</div>
+            </div>
+          </div>
+        </div>
+        <!-- 饼图占位 -->
+        <div class="pie-placeholder">
+          <div class="pie-mock">
+            <svg viewBox="0 0 100 100" width="120" height="120">
+              <circle cx="50" cy="50" r="40" fill="#67c23a" />
+              <path d="M50,50 L50,10 A40,40 0 0,1 85,65 Z" fill="#f56c6c" />
+              <path d="M50,50 L85,65 A40,40 0 0,1 20,80 Z" fill="#e6a23c" />
+            </svg>
+          </div>
+          <div class="pie-legend">
+            <div><span class="dot green"></span>优秀 ≥90</div>
+            <div><span class="dot yellow"></span>良好 60-89</div>
+            <div><span class="dot red"></span>不及格 &lt;60</div>
+          </div>
+        </div>
+      </div>
+      <!-- 统计表格 -->
+      <el-table :data="tableData.statsTable" border size="small" stripe>
+        <el-table-column prop="category" label="分类" min-width="120" />
+        <el-table-column prop="name" label="名称" min-width="120" />
+        <el-table-column prop="total" label="总人数" min-width="80" />
+        <el-table-column prop="pass" label="通过人数" min-width="80" />
+        <el-table-column prop="fail" label="未通过人数" min-width="80" />
+        <el-table-column prop="rate" label="通过率" min-width="80" />
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ExamStatsCard',
+  data() {
+    const mockRow = (n) => ({
+      name: '姓名', dept: '部门单位', userId: `202500000${n}`,
+      eduTitle: '高校宝安全教育培训与考核内容', subject: '无',
+      status: n % 2 === 0 ? '完成' : '待完成',
+      person: '负责人', startDate: '2026-04-28 16:30', endDate: '2026-05-28 16:30',
+      score: n % 2 === 0 ? '90' : '--'
+    })
+    return {
+      tableData: {
+        statsYear: '2025-2026',
+        safetyTable: Array.from({length: 5}, (_, i) => mockRow(i + 1)),
+        labTable: Array.from({length: 5}, (_, i) => mockRow(i + 1)),
+        barData: [
+          { label: '总览', pass: 80, fail: 20 },
+          { label: '生物类', pass: 60, fail: 30 },
+          { label: '辐射类', pass: 70, fail: 15 },
+          { label: '化学', pass: 90, fail: 10 },
+          { label: '科目', pass: 50, fail: 40 },
+        ],
+        statsTable: [
+          { category: '安全教育', name: '安全教育名称', total: 25, pass: 20, fail: 5, rate: '80%' },
+          { category: '安全教育', name: '安全教育名称', total: 30, pass: 28, fail: 2, rate: '93%' },
+          { category: '实验室安全', name: '实验室安全名称', total: 20, pass: 15, fail: 5, rate: '75%' },
+          { category: '化学实验', name: '化学实验名称', total: 18, pass: 16, fail: 2, rate: '89%' },
+        ]
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.exam-stats-card {
+  .section-card {
+    background: #fff;
+    border-radius: 6px;
+    padding: 16px 20px;
+    margin-bottom: 16px;
+    box-shadow: 0 1px 4px rgba(0,0,0,0.08);
+
+    .section-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 14px;
+      .section-title { font-size: 15px; font-weight: bold; color: #333; }
+      .header-actions { display: flex; gap: 8px; align-items: center; }
+    }
+
+    .stats-body {
+      display: flex;
+      gap: 24px;
+      margin-bottom: 16px;
+
+      .chart-placeholder {
+        flex: 1;
+        background: #f5f7fa;
+        border-radius: 4px;
+        padding: 16px;
+        min-height: 160px;
+        display: flex;
+        align-items: flex-end;
+
+        .bar-chart-mock {
+          display: flex;
+          align-items: flex-end;
+          gap: 12px;
+          width: 100%;
+          justify-content: space-around;
+
+          .bar-col {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            gap: 2px;
+
+            .bar {
+              width: 20px;
+              &.green { background: #67c23a; }
+              &.red { background: #f56c6c; }
+            }
+
+            .bar-label { font-size: 11px; color: #666; margin-top: 4px; }
+          }
+        }
+      }
+
+      .pie-placeholder {
+        width: 200px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        gap: 12px;
+
+        .pie-legend {
+          font-size: 12px;
+          color: #666;
+          div { margin-bottom: 4px; display: flex; align-items: center; gap: 6px; }
+          .dot {
+            width: 10px; height: 10px; border-radius: 50%; display: inline-block;
+            &.green { background: #67c23a; }
+            &.yellow { background: #e6a23c; }
+            &.red { background: #f56c6c; }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 192 - 0
src/views/safetyEducationExaminationNew/workbench/ResourceCard.vue

@@ -0,0 +1,192 @@
+<!-- 实验室安全素养资源卡 -->
+<template>
+  <div class="resource-card">
+    <div class="card-title">实验室安全素养资源卡</div>
+
+    <!-- 分类 Tab -->
+    <div class="tab-bar">
+      <el-button
+        v-for="tab in resourceData.tabs"
+        :key="tab"
+        :type="resourceData.activeTab === tab ? 'primary' : ''"
+        size="mini"
+        @click="resourceData.activeTab = tab"
+      >{{ tab }}</el-button>
+    </div>
+
+    <div class="resource-body">
+      <!-- 左侧:知识学习 + 刷题练习 -->
+      <div class="left-panel">
+        <!-- 知识学习 -->
+        <div class="sub-section">
+          <div class="sub-title">知识学习 <span class="more-link">查看更多</span></div>
+          <div v-for="item in resourceData.knowledgeList" :key="item.name" class="file-item">
+            <i class="el-icon-document file-icon"></i>
+            <div class="file-info">
+              <div class="file-name">{{ item.name }}</div>
+              <div class="file-meta">{{ item.year }}|{{ item.term }}|{{ item.credit }}学分</div>
+            </div>
+            <el-button size="mini" type="primary" plain>学习</el-button>
+          </div>
+        </div>
+
+        <!-- 刷题练习 -->
+        <div class="sub-section">
+          <div class="sub-title">刷题练习 <span class="more-link">查看更多</span></div>
+          <div class="practice-item">
+            <div class="practice-name">
+              <a class="link">2026-09-01 9:30:33</a>
+            </div>
+            <div class="practice-meta">
+              <span>练习题:32/369</span>
+            </div>
+            <div class="practice-meta">
+              <span>答对率:50%</span>
+              <span style="margin-left:16px">正确数:72%</span>
+              <span style="margin-left:16px">错误数:96%</span>
+            </div>
+            <div class="practice-time">2026-09-01 11:03:25</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 右侧:视频课程学习 -->
+      <div class="right-panel">
+        <div class="sub-title">视频课程学习 <span class="more-link">查看更多</span></div>
+        <div class="video-grid">
+          <div v-for="item in resourceData.videoList" :key="item.name" class="video-item">
+            <div class="video-thumb">
+              <div class="thumb-placeholder">视频缩略图</div>
+            </div>
+            <div class="video-info">
+              <div class="video-name">{{ item.name }}</div>
+              <div class="video-meta">{{ item.year }}|{{ item.term }}|{{ item.credit }}学分</div>
+              <el-button size="mini" type="primary" plain>学习</el-button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ResourceCard',
+  data() {
+    return {
+      resourceData: {
+        activeTab: '总览',
+        tabs: ['总览', '生物类', '辐射类', '化学', '总览', '科目', '生物类', '辐射类', '化学', '总览', '科目', '辐射类'],
+        knowledgeList: [
+          { name: '课件资料名称.PDF', year: '2026年', term: '1学年', credit: '1' },
+          { name: '课件资料名称.PDF', year: '2026年', term: '1学年', credit: '1' },
+          { name: '课件资料名称.PDF', year: '2026年', term: '1学年', credit: '1' },
+          { name: '课件资料名称.PDF', year: '2026年', term: '1学年', credit: '1' },
+          { name: '课件资料名称.PDF', year: '2026年', term: '1学年', credit: '1' },
+        ],
+        videoList: [
+          { name: '视频课程名称', year: '2026年', term: '1学年', credit: '1' },
+          { name: '视频课程名称', year: '2026年', term: '1学年', credit: '1' },
+          { name: '视频课程名称', year: '2026年', term: '1学年', credit: '1' },
+          { name: '视频课程名称', year: '2026年', term: '1学年', credit: '1' },
+          { name: '视频课程名称', year: '2026年', term: '1学年', credit: '1' },
+          { name: '视频课程名称', year: '2026年', term: '1学年', credit: '1' },
+        ]
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.resource-card {
+  background: #fff;
+  border-radius: 6px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
+
+  .card-title {
+    font-size: 15px;
+    font-weight: bold;
+    color: #333;
+    margin-bottom: 12px;
+  }
+
+  .tab-bar {
+    display: flex;
+    gap: 6px;
+    flex-wrap: wrap;
+    margin-bottom: 16px;
+  }
+
+  .resource-body {
+    display: flex;
+    gap: 24px;
+
+    .left-panel { flex: 1; }
+    .right-panel { flex: 1; }
+  }
+
+  .sub-section { margin-bottom: 20px; }
+
+  .sub-title {
+    font-size: 14px;
+    font-weight: bold;
+    color: #333;
+    margin-bottom: 10px;
+    display: flex;
+    justify-content: space-between;
+    .more-link { color: #409eff; font-weight: normal; font-size: 12px; cursor: pointer; }
+  }
+
+  .file-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    padding: 8px 0;
+    border-bottom: 1px solid #f5f5f5;
+    .file-icon { font-size: 20px; color: #e6a23c; }
+    .file-info {
+      flex: 1;
+      .file-name { font-size: 13px; color: #333; }
+      .file-meta { font-size: 12px; color: #999; }
+    }
+  }
+
+  .practice-item {
+    padding: 10px;
+    border: 1px solid #eee;
+    border-radius: 4px;
+    .practice-name .link { color: #409eff; font-size: 13px; cursor: pointer; }
+    .practice-meta { font-size: 12px; color: #666; margin-top: 4px; }
+    .practice-time { font-size: 12px; color: #999; margin-top: 6px; text-align: right; }
+  }
+
+  .video-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+
+    .video-item {
+      border: 1px solid #eee;
+      border-radius: 4px;
+      overflow: hidden;
+      .video-thumb {
+        height: 80px;
+        background: #f5f7fa;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .thumb-placeholder { color: #ccc; font-size: 12px; }
+      }
+      .video-info {
+        padding: 8px;
+        .video-name { font-size: 13px; color: #333; margin-bottom: 4px; }
+        .video-meta { font-size: 12px; color: #999; margin-bottom: 6px; }
+      }
+    }
+  }
+}
+</style>

+ 144 - 0
src/views/safetyEducationExaminationNew/workbench/SafetyPersonChart.vue

@@ -0,0 +1,144 @@
+<!-- 实验室管与人员图 -->
+<template>
+  <div class="safety-person-card">
+    <div class="section-header">
+      <span class="section-title">实验室管与人员图</span>
+      <div class="header-actions">
+        <el-button size="mini" type="primary">实验室管理下载</el-button>
+        <el-button size="mini" type="success">实验室人员下载</el-button>
+      </div>
+    </div>
+
+    <!-- 柱状图占位 -->
+    <div class="chart-area">
+      <div class="bar-chart-mock">
+        <div class="y-axis">
+          <span v-for="y in chartData.yAxis" :key="y">{{ y }}</span>
+        </div>
+        <div class="bars-wrap">
+          <div v-for="(group, gi) in chartData.groups" :key="gi" class="bar-group">
+            <div class="bar orange" :style="{height: group.v1 + 'px'}" :title="group.v1"></div>
+            <div class="bar blue" :style="{height: group.v2 + 'px'}" :title="group.v2"></div>
+            <div class="group-label">{{ group.label }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 汇总表格 -->
+    <el-table :data="chartData.tableData" border size="small" stripe>
+      <el-table-column prop="category" label="分类" min-width="120" />
+      <el-table-column prop="total1" label="第1学年人数" min-width="100" />
+      <el-table-column prop="total2" label="第2学年人数" min-width="100" />
+      <el-table-column prop="total3" label="第3学年人数" min-width="100" />
+      <el-table-column prop="total4" label="第4学年人数" min-width="100" />
+      <el-table-column prop="total5" label="第5学年人数" min-width="100" />
+      <el-table-column prop="total6" label="第6学年人数" min-width="100" />
+      <el-table-column prop="sum" label="合计" min-width="80" />
+    </el-table>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SafetyPersonChart',
+  data() {
+    return {
+      chartData: {
+        yAxis: [500, 400, 300, 200, 100, 0],
+        groups: [
+          { label: '博(一年级)', v1: 60, v2: 100 },
+          { label: '博(二年级)', v1: 80, v2: 120 },
+          { label: '硕(一年级)', v1: 200, v2: 300 },
+          { label: '硕(二年级)', v1: 180, v2: 260 },
+          { label: '硕(三年级)', v1: 40, v2: 50 },
+        ],
+        tableData: [
+          { category: '化学与化工学院', total1: 10, total2: 20, total3: 30, total4: 0, total5: 0, total6: 0, sum: 60 },
+          { category: '生命科学与技术学院', total1: 15, total2: 25, total3: 20, total4: 10, total5: 0, total6: 0, sum: 70 },
+          { category: '环境科学与工程学院', total1: 8, total2: 12, total3: 15, total4: 5, total5: 0, total6: 0, sum: 40 },
+          { category: '电气/工程学院', total1: 20, total2: 30, total3: 25, total4: 15, total5: 5, total6: 0, sum: 95 },
+          { category: '物理学院', total1: 12, total2: 18, total3: 20, total4: 8, total5: 2, total6: 0, sum: 60 },
+          { category: '材料科学与工程学院', total1: 10, total2: 15, total3: 18, total4: 7, total5: 0, total6: 0, sum: 50 },
+        ]
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.safety-person-card {
+  background: #fff;
+  border-radius: 6px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 14px;
+    .section-title { font-size: 15px; font-weight: bold; color: #333; }
+    .header-actions { display: flex; gap: 8px; }
+  }
+
+  .chart-area {
+    background: #f5f7fa;
+    border-radius: 4px;
+    padding: 16px;
+    margin-bottom: 16px;
+    min-height: 200px;
+
+    .bar-chart-mock {
+      display: flex;
+      gap: 8px;
+
+      .y-axis {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        font-size: 11px;
+        color: #999;
+        padding-bottom: 20px;
+        min-width: 30px;
+        text-align: right;
+      }
+
+      .bars-wrap {
+        display: flex;
+        align-items: flex-end;
+        gap: 16px;
+        flex: 1;
+        justify-content: space-around;
+
+        .bar-group {
+          display: flex;
+          align-items: flex-end;
+          gap: 4px;
+          flex-direction: row;
+          position: relative;
+
+          .bar {
+            width: 18px;
+            border-radius: 2px 2px 0 0;
+            &.orange { background: #e6a23c; }
+            &.blue { background: #409eff; }
+          }
+
+          .group-label {
+            position: absolute;
+            bottom: -18px;
+            left: 50%;
+            transform: translateX(-50%);
+            font-size: 10px;
+            color: #666;
+            white-space: nowrap;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 159 - 0
src/views/safetyEducationExaminationNew/workbench/StudentStats.vue

@@ -0,0 +1,159 @@
+<!-- 学期统计 -->
+<template>
+  <div class="student-stats-card">
+    <div class="section-header">
+      <span class="section-title">学期统计</span>
+      <el-button size="mini" type="primary">下载统计</el-button>
+    </div>
+
+    <!-- 柱状图占位 -->
+    <div class="chart-area">
+      <div class="chart-legend">
+        <span><i class="dot green"></i>完成</span>
+        <span><i class="dot blue"></i>待完成</span>
+        <span><i class="dot yellow"></i>未开始</span>
+        <span><i class="dot gray"></i>其他</span>
+      </div>
+      <div class="bar-chart-mock">
+        <div v-for="(col, i) in statsData.chartCols" :key="i" class="chart-col">
+          <div class="stacked-bar">
+            <div class="seg green" :style="{height: col.done + 'px'}"></div>
+            <div class="seg blue" :style="{height: col.pending + 'px'}"></div>
+            <div class="seg yellow" :style="{height: col.notStart + 'px'}"></div>
+          </div>
+          <div class="col-label">{{ col.label }}</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 统计表格 -->
+    <el-table :data="statsData.tableData" border size="small" stripe>
+      <el-table-column prop="name" label="姓名" min-width="80" />
+      <el-table-column prop="userId" label="用户编号" min-width="100" />
+      <el-table-column prop="dept" label="部门" min-width="100" />
+      <el-table-column prop="eduName" label="安全教育名称" min-width="160" />
+      <el-table-column prop="term1" label="第1学期" min-width="80" />
+      <el-table-column prop="term2" label="第2学期" min-width="80" />
+      <el-table-column prop="total" label="总计" min-width="80" />
+    </el-table>
+
+    <!-- 分页 -->
+    <div class="pagination-wrap">
+      <el-pagination
+        :current-page.sync="statsData.page"
+        :page-size="10"
+        :total="statsData.total"
+        layout="prev, pager, next"
+        background
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'StudentStats',
+  data() {
+    return {
+      statsData: {
+        page: 1,
+        total: 50,
+        chartCols: [
+          { label: '生物类', done: 80, pending: 40, notStart: 20 },
+          { label: '化学类', done: 60, pending: 60, notStart: 30 },
+          { label: '辐射类', done: 100, pending: 20, notStart: 10 },
+          { label: '科目类', done: 50, pending: 50, notStart: 40 },
+          { label: '电气类', done: 70, pending: 30, notStart: 15 },
+        ],
+        tableData: [
+          { name: '姓名', userId: '20250001', dept: '部门单位', eduName: '安全教育名称', term1: 10, term2: 20, total: 30 },
+          { name: '姓名', userId: '20250002', dept: '部门单位', eduName: '安全教育名称', term1: 8, term2: 15, total: 23 },
+          { name: '姓名', userId: '20250003', dept: '部门单位', eduName: '安全教育名称', term1: 12, term2: 18, total: 30 },
+          { name: '姓名', userId: '20250004', dept: '部门单位', eduName: '安全教育名称', term1: 5, term2: 10, total: 15 },
+          { name: '姓名', userId: '20250005', dept: '部门单位', eduName: '安全教育名称', term1: 20, term2: 25, total: 45 },
+          { name: '姓名', userId: '20250006', dept: '部门单位', eduName: '安全教育名称', term1: 9, term2: 14, total: 23 },
+          { name: '姓名', userId: '20250007', dept: '部门单位', eduName: '安全教育名称', term1: 11, term2: 22, total: 33 },
+        ]
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.student-stats-card {
+  background: #fff;
+  border-radius: 6px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 14px;
+    .section-title { font-size: 15px; font-weight: bold; color: #333; }
+  }
+
+  .chart-area {
+    background: #f5f7fa;
+    border-radius: 4px;
+    padding: 16px;
+    margin-bottom: 16px;
+
+    .chart-legend {
+      display: flex;
+      gap: 16px;
+      margin-bottom: 12px;
+      font-size: 12px;
+      color: #666;
+      span { display: flex; align-items: center; gap: 4px; }
+      .dot {
+        width: 10px; height: 10px; border-radius: 2px; display: inline-block;
+        &.green { background: #67c23a; }
+        &.blue { background: #409eff; }
+        &.yellow { background: #e6a23c; }
+        &.gray { background: #c0c4cc; }
+      }
+    }
+
+    .bar-chart-mock {
+      display: flex;
+      align-items: flex-end;
+      gap: 24px;
+      justify-content: space-around;
+      min-height: 160px;
+
+      .chart-col {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 4px;
+
+        .stacked-bar {
+          display: flex;
+          flex-direction: column-reverse;
+          width: 24px;
+          gap: 1px;
+
+          .seg {
+            width: 100%;
+            &.green { background: #67c23a; }
+            &.blue { background: #409eff; }
+            &.yellow { background: #e6a23c; }
+          }
+        }
+
+        .col-label { font-size: 11px; color: #666; margin-top: 4px; }
+      }
+    }
+  }
+
+  .pagination-wrap {
+    display: flex;
+    justify-content: center;
+    margin-top: 16px;
+  }
+}
+</style>

+ 184 - 0
src/views/safetyEducationExaminationNew/workbench/TaskCard.vue

@@ -0,0 +1,184 @@
+<!-- 工作台任务卡片 -->
+<template>
+  <div class="task-card">
+    <div class="card-title">工作台任务卡片</div>
+    <div class="task-grid">
+      <!-- 手势数据统计 -->
+      <div class="task-item">
+        <div class="item-title">手势数据统计 <span class="year-tag">{{ taskData.year }}</span> <span class="status-tag">待完成</span></div>
+        <div class="item-desc">高校宝安全教育培训与考核</div>
+      </div>
+
+      <!-- 学习任务 -->
+      <div class="task-item">
+        <div class="item-title">学习任务</div>
+        <div class="item-desc">高校宝安全教育培训与考核</div>
+        <div class="item-row">
+          <el-checkbox v-model="taskData.studyTask.checked">课程任务名称</el-checkbox>
+        </div>
+        <div class="item-row sub">
+          <el-radio v-model="taskData.studyTask.radio" :label="1">完成</el-radio>
+          <el-radio v-model="taskData.studyTask.radio" :label="0">未完成</el-radio>
+        </div>
+        <div class="item-row">
+          <el-checkbox>课程任务名称</el-checkbox>
+        </div>
+        <div class="item-row sub">
+          <el-radio v-model="taskData.studyTask.radio2" :label="1">完成</el-radio>
+          <el-radio v-model="taskData.studyTask.radio2" :label="0">未完成</el-radio>
+        </div>
+      </div>
+
+      <!-- 模拟考试 -->
+      <div class="task-item">
+        <div class="item-title">模拟考试</div>
+        <div class="item-desc">高校宝安全教育考试</div>
+        <div class="progress-row">
+          <span class="big-num">200</span><span class="sep">/</span><span>3000</span>
+        </div>
+        <div class="item-desc">已完成进度 距离目标还差多少</div>
+        <div class="item-row">
+          <span class="pct">20%</span>
+          <span class="pct-label">对题率 近期对题率</span>
+        </div>
+      </div>
+
+      <!-- 培训活动 -->
+      <div class="task-item highlight">
+        <div class="item-title">培训活动</div>
+        <div class="item-desc">高校宝安全培训与安全考试考核</div>
+        <div class="act-time">2026-06-10 10:00 至 11:00</div>
+        <el-button type="primary" size="mini" class="sign-btn">签到打卡</el-button>
+      </div>
+
+      <!-- 新生入学考试 -->
+      <div class="task-item">
+        <div class="item-title">新生入学考试 <span class="item-desc">高校宝安全教育培训与考核</span></div>
+        <div class="item-row">
+          <span class="label">学习任务</span>
+          <el-tag type="success" size="mini">学习中</el-tag>
+        </div>
+        <div class="item-row">
+          <span class="label">考试状态</span>
+          <span class="value">待完成 每天完成考试后提示</span>
+        </div>
+      </div>
+
+      <!-- 分级鉴定考试 -->
+      <div class="task-item">
+        <div class="item-title">分级鉴定考试 <span class="item-desc">高校宝安全教育培训与考核</span></div>
+        <div class="item-row">
+          <span class="label">学习任务</span>
+          <span class="value">无</span>
+        </div>
+        <div class="item-row">
+          <span class="label">考试状态</span>
+          <span class="value">待完成 每天完成考试后提示</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TaskCard',
+  data() {
+    return {
+      taskData: {
+        year: '2025-2026',
+        studyTask: {
+          checked: true,
+          radio: 1,
+          radio2: 0,
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.task-card {
+  background: #fff;
+  border-radius: 6px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
+
+  .card-title {
+    font-size: 15px;
+    font-weight: bold;
+    color: #333;
+    margin-bottom: 14px;
+  }
+
+  .task-grid {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 16px;
+
+    .task-item {
+      border: 1px solid #eee;
+      border-radius: 6px;
+      padding: 12px;
+      font-size: 13px;
+
+      &.highlight {
+        background: #fff0f0;
+        border-color: #ffcccc;
+      }
+
+      .item-title {
+        font-weight: bold;
+        color: #333;
+        margin-bottom: 6px;
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        flex-wrap: wrap;
+      }
+
+      .year-tag {
+        font-size: 12px;
+        color: #409eff;
+        font-weight: normal;
+      }
+
+      .status-tag {
+        font-size: 12px;
+        color: #e6a23c;
+        font-weight: normal;
+      }
+
+      .item-desc {
+        color: #999;
+        font-size: 12px;
+        margin-bottom: 8px;
+      }
+
+      .item-row {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        margin-bottom: 6px;
+        &.sub { padding-left: 22px; }
+        .label { color: #666; min-width: 56px; }
+        .value { color: #333; }
+      }
+
+      .progress-row {
+        margin: 8px 0;
+        .big-num { font-size: 22px; font-weight: bold; color: #333; }
+        .sep { color: #999; margin: 0 2px; }
+      }
+
+      .pct { font-size: 20px; font-weight: bold; color: #409eff; }
+      .pct-label { color: #999; font-size: 12px; }
+
+      .act-time { color: #e6a23c; font-size: 13px; margin: 6px 0; }
+      .sign-btn { margin-top: 6px; }
+    }
+  }
+}
+</style>

+ 147 - 0
src/views/safetyEducationExaminationNew/workbench/UserInfoCard.vue

@@ -0,0 +1,147 @@
+<!-- 用户信息卡片 -->
+<template>
+  <div class="user-info-card">
+    <div class="card-title">用户信息卡片</div>
+    <div class="card-body">
+      <!-- 左侧:基本信息 -->
+      <div class="left-section">
+        <div class="info-row">
+          <span class="label">姓名</span>
+          <span class="value">Name</span>
+          <span class="label">学号</span>
+          <span class="value">20251001</span>
+        </div>
+        <div class="info-row">
+          <span class="label">学历</span>
+          <span class="value">硕士研究生</span>
+          <span class="label">学院</span>
+          <span class="value">学院Name</span>
+        </div>
+        <div class="info-row">
+          <span class="label">研究室</span>
+          <span class="value">智无研定实验室</span>
+        </div>
+        <div class="more-link">查看更多</div>
+      </div>
+
+      <!-- 中间:学年选择 + 课程任务 -->
+      <div class="mid-section">
+        <el-select v-model="userInfo.year" size="small" class="year-select">
+          <el-option v-for="y in userInfo.yearOptions" :key="y" :label="y" :value="y" />
+        </el-select>
+        <div class="mid-row">
+          <span class="label">安全教育</span>
+          <span class="value">无</span>
+        </div>
+        <div class="mid-row">
+          <span class="label">课程任务</span>
+          <span class="value">分级考试</span>
+        </div>
+      </div>
+
+      <!-- 右侧:当前学年任务状态 -->
+      <div class="right-section">
+        <div class="section-title">当前学年任务状态</div>
+        <div class="status-grid">
+          <div v-for="item in userInfo.taskStatus" :key="item.label" class="status-item">
+            <span class="s-label">{{ item.label }}</span>
+            <el-tag :type="item.type" size="small">{{ item.value }}</el-tag>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'UserInfoCard',
+  data() {
+    return {
+      userInfo: {
+        year: '2025-2026 学年',
+        yearOptions: ['2023-2024 学年', '2024-2025 学年', '2025-2026 学年'],
+        taskStatus: [
+          { label: '考试邀请', value: '完成', type: 'success' },
+          { label: '证件状态', value: '完成', type: 'success' },
+          { label: '课程状态', value: '完成', type: 'success' },
+          { label: '毕业状态', value: '待完成', type: 'warning' },
+        ]
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.user-info-card {
+  background: #fff;
+  border-radius: 6px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
+
+  .card-title {
+    font-size: 15px;
+    font-weight: bold;
+    color: #333;
+    margin-bottom: 14px;
+  }
+
+  .card-body {
+    display: flex;
+    gap: 32px;
+
+    .left-section {
+      min-width: 260px;
+      .info-row {
+        display: flex;
+        gap: 8px;
+        align-items: center;
+        margin-bottom: 10px;
+        .label { color: #999; font-size: 13px; min-width: 40px; }
+        .value { color: #333; font-size: 13px; }
+      }
+      .more-link {
+        color: #409eff;
+        font-size: 13px;
+        cursor: pointer;
+        margin-top: 6px;
+      }
+    }
+
+    .mid-section {
+      min-width: 200px;
+      .year-select { margin-bottom: 12px; width: 160px; }
+      .mid-row {
+        display: flex;
+        gap: 8px;
+        margin-bottom: 10px;
+        .label { color: #999; font-size: 13px; min-width: 56px; }
+        .value { color: #333; font-size: 13px; }
+      }
+    }
+
+    .right-section {
+      flex: 1;
+      .section-title {
+        font-size: 13px;
+        color: #666;
+        margin-bottom: 12px;
+        font-weight: bold;
+      }
+      .status-grid {
+        display: grid;
+        grid-template-columns: repeat(3, 1fr);
+        gap: 10px;
+        .status-item {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          .s-label { color: #666; font-size: 13px; }
+        }
+      }
+    }
+  }
+}
+</style>

+ 79 - 0
src/views/safetyEducationExaminationNew/workbench/index.vue

@@ -0,0 +1,79 @@
+<!-- 工作台 -->
+<template>
+  <div class="app-container workbench">
+    <div class="page-container workbenchPage" v-if="pageType === 1">
+      <!-- 顶部通知栏 -->
+      <div class="notice-bar">
+        <span class="notice-label">近期推荐资料(类型):<a class="link">规划官方通告</a></span>
+        <el-button size="mini" type="text" class="notice-btn">通知 ></el-button>
+        <el-button size="mini" type="text" class="notice-btn">监控 ></el-button>
+      </div>
+
+      <!-- 用户信息卡片 -->
+      <user-info-card />
+
+      <!-- 工作台任务卡片 -->
+      <task-card />
+
+      <!-- 实验室安全素养资源卡 -->
+      <resource-card />
+
+      <!-- 安监室/实验室安全分布表 + 考试成绩统计 -->
+      <exam-stats-card />
+
+      <!-- 实验室管与人员图 -->
+      <safety-person-chart />
+
+      <!-- 学期统计 -->
+      <student-stats />
+    </div>
+  </div>
+</template>
+
+<script>
+  import UserInfoCard from './UserInfoCard.vue'
+  import TaskCard from './TaskCard.vue'
+  import ResourceCard from './ResourceCard.vue'
+  import ExamStatsCard from './ExamStatsCard.vue'
+  import SafetyPersonChart from './SafetyPersonChart.vue'
+  import StudentStats from './StudentStats.vue'
+
+  export default {
+    name: 'index',
+    components: {
+      UserInfoCard,
+      TaskCard,
+      ResourceCard,
+      ExamStatsCard,
+      SafetyPersonChart,
+      StudentStats,
+    },
+    data() {
+      return {
+        pageType: 1,
+      }
+    },
+  }
+</script>
+
+<style scoped lang="scss">
+  .workbench {
+    .workbenchPage {
+      padding: 16px;
+      background: #f5f7fa;
+      min-height: 100vh;
+
+      .notice-bar {
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        gap: 12px;
+        margin-bottom: 12px;
+        font-size: 13px;
+        .notice-label { color: #666; margin-right: auto; }
+        .link { color: #409eff; cursor: pointer; }
+        .notice-btn { color: #409eff; padding: 0; }
+      }
+    }
+  }
+</style>