ソースを参照

feature(培训考试):添加我的考试列表、考试结果、培训详情页面

wanglj 8 ヶ月 前
コミット
e8d935c129

+ 2 - 0
components.d.ts

@@ -8,6 +8,8 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     CustomForm: typeof import('./src/components/CustomForm.vue')['default']
+    Flow: typeof import('./src/components/Flow.vue')['default']
+    FlowTable: typeof import('./src/components/FlowTable.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     VanActionBar: typeof import('vant/es')['ActionBar']

+ 8 - 0
src/api/training/index.ts

@@ -30,6 +30,10 @@ export function useTrainingApi() {
     getExam(query?: object) {
       return request.postRequest(basePath, 'MineExamination', 'Get', query)
     },
+    // 考试列表
+    getExamList(query?: object) {
+      return request.postRequest(basePath, 'MineExamination', 'List', query)
+    },
     // 查询试卷详情
     getPaper(query?: object) {
       return request.postRequest(basePath, 'MineExamination', 'GetAnswerPaper', query)
@@ -53,6 +57,10 @@ export function useTrainingApi() {
     // 单个提交答案
     submitDocAmswer(query?: object) {
       return request.postRequest(basePath, 'MineExamination', 'UploadDocAnswer', query)
+    },
+    // 考试成绩
+    getAnswerInfo(query?: object) {
+      return request.postRequest(basePath, 'MineExamination', 'GetAnswerInfo', query)
     }
   }
 }

+ 83 - 0
src/components/FlowTable.vue

@@ -0,0 +1,83 @@
+<template>
+  <van-steps direction="vertical" :active="-1">
+    <van-step>
+      <template #active-icon></template>
+      <template #inactive-icon></template>
+      <div class="flex">
+        <h4>审核方式</h4>
+        <h4>审核人</h4>
+        <h4>审核结果</h4>
+        <h4>审核时间</h4>
+      </div>
+    </van-step>
+  </van-steps>
+  <van-steps direction="vertical" :active="active">
+    <van-step v-for="row in apprList" :key="row.id">
+      <div class="flex">
+        <p>{{ getNodeModel(row.nodeType, row.nodeModel) }}</p>
+        <p>{{ row.userName }}</p>
+        <p>
+          <span v-if="row.approvalResult === 'pass'">通过</span>
+          <span v-else-if="row.approvalResult === 'rejection'">拒绝</span>
+          <span v-else-if="row.approvalResult === 'back'">退回</span>
+          <span v-else>审批中</span>
+        </p>
+        <p>{{ row.approvalDate ? formatDate(new Date(row.approvalDate), 'YYYY-mm-dd') : '' }}</p>
+      </div>
+    </van-step>
+  </van-steps>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, watch } from 'vue'
+  import { useFlowApi } from '/@/api/execution/flow'
+  import { formatDate } from '/@/utils/formatTime'
+  import to from 'await-to-js'
+  const flowApi = useFlowApi()
+  const props = defineProps({
+    id: { type: Number, default: 0 },
+    businessCode: { type: String, default: '' },
+    defCode: { type: String, default: '' }
+  })
+  const apprList = ref<any[]>([])
+  const active = computed(() => {
+    return apprList.value.length - 1
+  })
+  // 审核方式翻译
+  const getNodeModel = (type: string, model: string) => {
+    if (type === 'start') return '发起'
+    if (model === '10') {
+      return '会签'
+    } else if (model === '20') {
+      return '或签'
+    }
+  }
+  // 详情工作流列表
+  const getFlowInstance = async () => {
+    const [err, res]: ToResponse = await to(flowApi.getFlowInstance({ id: props.id, businessCode: props.businessCode, defCode: props.defCode }))
+    if (err) return
+    const arr = res?.data?.nodes || []
+    apprList.value = arr
+  }
+  watch(
+    () => props.id,
+    (val) => {
+      if (val) {
+        getFlowInstance()
+      }
+    },
+    {
+      deep: true,
+      immediate: true
+    }
+  )
+</script>
+<style lang="scss" scoped>
+  :deep(.van-step) {
+    p,
+    h4 {
+      flex: 1;
+      text-align: center;
+      color: #333;
+    }
+  }
+</style>

+ 2 - 1
src/layout/training.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-13 09:07:55
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-27 09:02:11
+ * @LastEditTime: 2025-03-28 11:49:58
  * @FilePath: \labsop-h5\src\layout\index.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -11,6 +11,7 @@
   <van-tabbar route :placeholder="true">
     <van-tabbar-item replace to="/training" icon="info-o">全部培训</van-tabbar-item>
     <van-tabbar-item replace to="/training/done" icon="passed">我的培训</van-tabbar-item>
+    <van-tabbar-item replace to="/exam/mine" icon="question-o">我的考试</van-tabbar-item>
   </van-tabbar>
 </template>
 

+ 53 - 21
src/router.ts

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-10 11:40:15
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-27 17:12:32
+ * @LastEditTime: 2025-03-28 11:33:50
  * @FilePath: \vue3-ts\src\router.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -130,8 +130,8 @@ const routes = [
       },
       {
         name: 'approvalDetail',
-        path: '/todo/approval-detail',
-        component: () => import('/@/view/todo/approval-detail.vue'),
+        path: '/todo/detail',
+        component: () => import('/@/view/todo/detail.vue'),
         meta: {
           title: '审批详情'
         }
@@ -176,22 +176,6 @@ const routes = [
           title: '修改密码'
         }
       },
-      {
-        name: 'examCover',
-        path: '/exam-cover',
-        component: () => import('/@/view/exam/cover.vue'),
-        meta: {
-          title: '开始考试'
-        }
-      },
-      {
-        name: 'exam',
-        path: '/exam',
-        component: () => import('/@/view/exam/index.vue'),
-        meta: {
-          title: '在线考试'
-        }
-      },
     ]
   },
   {
@@ -230,7 +214,15 @@ const routes = [
         meta: {
           title: '新增入室申请'
         }
-      }
+      },
+      {
+        name: 'entryDetail',
+        path: '/entry/detail',
+        component: () => import('/@/view/entry/detail.vue'),
+        meta: {
+          title: '入室详情'
+        }
+      },
     ]
   },
   {
@@ -262,8 +254,48 @@ const routes = [
           title: '培训报名'
         }
       },
+      {
+        name: 'examMine',
+        path: '/exam/mine',
+        component: () => import('/@/view/exam/mine.vue'),
+        meta: {
+          title: '我的考试'
+        }
+      },
+      {
+        name: 'examResult',
+        path: '/exam/result',
+        component: () => import('/@/view/exam/result.vue'),
+        meta: {
+          title: '考试成绩'
+        }
+      },
     ]
-  }
+  },
+  {
+    name: 'trainingDetail',
+    path: '/training/detail',
+    component: () => import('/@/view/training/detail.vue'),
+    meta: {
+      title: '培训详情'
+    }
+  },
+  {
+    name: 'examCover',
+    path: '/exam/cover',
+    component: () => import('/@/view/exam/cover.vue'),
+    meta: {
+      title: '开始考试'
+    }
+  },
+  {
+    name: 'exam',
+    path: '/exam',
+    component: () => import('/@/view/exam/index.vue'),
+    meta: {
+      title: '在线考试'
+    }
+  },
 ]
 
 const router = createRouter({

+ 3 - 3
src/stores/userInfo.ts

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-17 14:46:02
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-27 16:38:39
+ * @LastEditTime: 2025-03-28 10:46:15
  * @FilePath: \labsop-h5\src\view\stores\userInfo.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -158,10 +158,10 @@ export const useUserInfo = defineStore('userInfo', {
       //   })
       // })
     },
-    scanCode() {
+    scanCode(needResult:0 | 1 = 1) {
       return new Promise((resolve, reject) => {
         wx.scanQRCode({
-          needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
+          needResult: needResult, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
           scanType: ['qrCode', 'barCode'], // 可以指定扫二维码还是一维码,默认二者都有
           success: function (res: any) {
             var result: string = res.resultStr // 当needResult 为 1 时,扫码返回的结果

+ 0 - 2
src/theme/index.scss

@@ -19,14 +19,12 @@ body,
   display: flex;
   flex-direction: column;
   .app-container {
-    height: 100%;
     flex: 1;
     padding: 0 10px;
     overflow-y: auto;
     background-color: #f7f8fa;
   }
   .entry-container {
-    height: 100%;
     flex: 1;
     overflow-y: auto;
     background-color: #f7f8fa;

+ 369 - 0
src/view/entry/detail.vue

@@ -0,0 +1,369 @@
+<!--
+ * @Author: wanglj wanglijie@dashoo.cn
+ * @Date: 2025-03-29 14:15:09
+ * @LastEditors: wanglj wanglijie@dashoo.cn
+ * @LastEditTime: 2025-03-29 14:51:56
+ * @FilePath: \labsop_h5\src\view\entry\detail.vue
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+-->
+<!--
+ * @Author: wanglj wanglijie@dashoo.cn
+ * @Date: 2025-03-24 09:17:15
+ * @LastEditors: wanglj wanglijie@dashoo.cn
+ * @LastEditTime: 2025-03-29 14:28:02
+ * @FilePath: \labsop_h5\src\view\instr\detail.vue
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+-->
+<template>
+  <div class="instr-detail">
+    <header class="flex">
+      <div class="i-right ml10">
+        <div class="h100 flex flex-top flex-column flex-between">
+          <div class="flex flex-top mb4 ml2">
+            <div class="detailTxt name">{{ `${state.form.memberName}的${state.form.platformName}入室申请` }}</div>
+          </div>
+          <footer>
+            <div class="flex flex-top mt-auto">
+              <img class="i-r-icon" src="../../assets/img/user.png" />
+              <div class="detailTxt">{{ state.form.createdName }}</div>
+            </div>
+            <div class="flex flex-top">
+              <div class="detailTxt">{{ state.form.createdTime }}</div>
+            </div>
+          </footer>
+        </div>
+      </div>
+    </header>
+    <div class="content">
+      <div class="card">
+        <h4>申请入室平台</h4>
+        <ul v-if="state.form.platformTime">
+          <li>
+            <label>申请平台</label>
+            <span>细胞</span>
+          </li>
+          <li>
+            <label>申请时长</label>
+            <span>{{ state.form.platformTime }}个月</span>
+          </li>
+          <li>
+            <label>拟培养细胞种类</label>
+            <span>{{ state.form.cellType }}</span>
+          </li>
+          <li>
+            <label>细胞房预约需求</label>
+            <span>{{ state.form.cellSourceType == '10' ? '普通' : '层流' }}</span>
+          </li>
+        </ul>
+        <ul v-else>
+          <li>
+            <label>申请平台</label>
+            <span>分子生物平台</span>
+          </li>
+          <li>
+            <label>申请时长</label>
+            <span>{{ state.form.molecularTime }}个月 </span>
+          </li>
+          <li>
+            <label>其他需求</label>
+            <span>{{ state.form.platOtherNeed }}</span>
+          </li>
+        </ul>
+      </div>
+      <div class="card">
+        <h4>申请人信息</h4>
+        <ul>
+          <li>
+            <label>申请人姓名</label>
+            <span>{{ state.form.memberName }}</span>
+          </li>
+          <li>
+            <label>人员类型</label>
+            <span>{{ getDictLabel(userTypeList, state.form.memberType) }}</span>
+          </li>
+          <li>
+            <label>申请人手机号</label>
+            <span>{{ state.form.memberPhone }}</span>
+          </li>
+          <li>
+            <label>申请人科室</label>
+            <span>{{ state.form.deptName }}</span>
+          </li>
+          <li>
+            <label>课题组</label>
+            <span>{{ state.form.pgName }}</span>
+          </li>
+        </ul>
+      </div>
+      <div class="card">
+        <h4>审批记录</h4>
+        <FlowTable :id="state.form.id" :businessCode="`${state.form.id}`" defCode="plat_platform_appoint" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import to from 'await-to-js'
+  import { useRoute, useRouter } from 'vue-router'
+  import { defineAsyncComponent, onMounted, reactive, ref } from 'vue'
+  import { useTrainingApi } from '/@/api/training'
+  import { useDictApi } from '/@/api/system/dict'
+  import { getDictLabel } from '/@/utils/other'
+  import { usePlatformAppointApi } from '/@/api/platform/appoint'
+  import { useFlowApi } from '/@/api/execution/flow'
+  const FlowTable = defineAsyncComponent(() => import('/@/components/FlowTable.vue'))
+  const platformAppointApi = usePlatformAppointApi()
+  const flowApi = useFlowApi()
+  const route = useRoute()
+  const router = useRouter()
+  const dictApi = useDictApi()
+  const apprList = ref<any[]>([])
+  const userTypeList = ref(<RowDicDataType[]>[])
+  const state = reactive({
+    detailsLoading: false,
+    form: {
+      id: 0,
+      deptId: null,
+      deptName: '',
+      isTemporary: '10',
+      memberId: 0,
+      memberName: '',
+      memberPhone: '',
+      memberType: '',
+      mentorObj: null,
+      pgId: null,
+      pgName: '',
+      mentorId: null,
+      mentorName: '',
+      mentorDeptName: '',
+      mentorPhone: '',
+      platformId: null,
+      platformName: '',
+      platformTime: null,
+      platformType: '',
+      platformList: [] as any[],
+      isCellChecked: '20',
+      cellType: '',
+      cellSourceType: '',
+      isMolecularChecked: '20',
+      molecularTime: null,
+      platOtherNeed: '',
+      createdName: '',
+      createdTime: ''
+    },
+    queryParams: {
+      pageNum: 1,
+      pageSize: 10,
+      appointStatus: []
+    }
+  })
+  const getDicts = () => {
+    Promise.all([dictApi.getDictDataByType('sys_user_type')]).then(([type]) => {
+      userTypeList.value = type?.data?.values || []
+    })
+  }
+  //详情
+  const getDetail = async (id: number) => {
+    state.detailsLoading = true
+    const [err, res]: ToResponse = await to(platformAppointApi.getDetail({ id }))
+    state.detailsLoading = false
+    if (err) return
+    if (res?.code === 200) {
+      state.form = res.data
+    }
+  }
+  onMounted(() => {
+    const id = route.query.id ? +route.query.id : 0
+    getDicts()
+    getDetail(id)
+  })
+</script>
+
+<style lang="scss" scoped>
+  .instr-detail {
+    flex: 1;
+    overflow-y: auto;
+    background-color: #f7f8fa;
+    .my-swipe {
+      background-color: #fff;
+      height: 30px !important;
+      line-height: 30px !important;
+      :deep(.flex) {
+        height: 30px;
+        overflow: hidden;
+        padding: 0 12px;
+        span {
+          display: inline-block;
+          height: 30px;
+          line-height: 30px;
+        }
+        span:first-child {
+          flex: 1;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+    > header {
+      height: 40px;
+      background-color: #fff;
+      padding: 12px;
+    }
+    .inst-info {
+      display: flex;
+    }
+    .i-right {
+      flex: 1;
+      font-size: 14px;
+      height: 40px;
+      .i-r-icon {
+        width: 15px;
+        height: 15px;
+        margin-right: 10px;
+      }
+      footer {
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+      }
+    }
+    .detailTxt {
+      font-size: 12px;
+      color: #333333;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      &.name {
+        font-weight: bold;
+        font-size: 16px;
+      }
+    }
+    .content {
+      padding: 10px;
+    }
+    .card {
+      border-radius: 4px;
+      background-color: #fff;
+      padding: 10px;
+      box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
+      & + .card {
+        margin-top: 10px;
+      }
+      h4 {
+        height: 18px;
+        line-height: 18px;
+        display: flex;
+        margin-bottom: 10px;
+        span {
+          font-weight: normal;
+          margin-left: auto;
+        }
+        &::before {
+          display: inline-block;
+          content: '';
+          width: 3px;
+          height: 18px;
+          background-color: #1c9bfd;
+          margin-right: 4px;
+          vertical-align: middle;
+        }
+      }
+      > ul {
+        li {
+          display: flex;
+          align-items: center;
+          padding: 6px 0;
+          label {
+            width: 100px;
+            min-width: 80px;
+            color: #969799;
+          }
+          span {
+            word-break: break-all;
+          }
+        }
+      }
+      .text {
+        white-space: pre-wrap;
+      }
+    }
+    .van-list {
+      padding: 10px;
+      border-radius: 4px;
+      flex: 1;
+      .van-cell {
+        background-color: #fff;
+        + .van-cell {
+          margin-top: 10px;
+        }
+        header,
+        footer {
+          color: #333;
+        }
+        .title {
+          flex: 1;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          text-align: left;
+        }
+        .inst-title {
+          color: #333;
+          text-align: left;
+          flex: 1;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          margin-top: 4px;
+          span:first-child {
+            display: inline-block;
+            width: 80px;
+            min-width: 80px;
+            color: rgb(120, 120, 120);
+          }
+        }
+        .time {
+          color: #f69a4d;
+        }
+      }
+    }
+  }
+  .btns {
+    flex: 1;
+    display: flex;
+    li {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 0 8px;
+      font-size: 12px;
+      i {
+        margin-bottom: 4px;
+      }
+    }
+  }
+  :deep(.follow .van-icon) {
+    color: #fdc33e;
+  }
+  .need-to-know {
+    height: calc(100% - 20px);
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    padding: 10px 20px;
+    white-space: pre-wrap;
+    p {
+      flex: 1;
+      overflow-y: auto;
+    }
+    footer {
+      flex: 0 0 45px;
+      margin-top: 4px;
+      border-top: 1px solid #f7f8fa;
+    }
+  }
+</style>

+ 7 - 6
src/view/entry/index.vue

@@ -2,16 +2,16 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-11 18:02:10
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-26 18:13:14
+ * @LastEditTime: 2025-03-29 14:16:50
  * @FilePath: \vant-demo-master\vant\vue3-ts\src\view\login\index.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
 <template>
   <div class="entry-container">
     <van-tabs v-model:active="state.queryParams.approveStatus" @change="changeType">
-      <van-tab title="审批中" name="10,20"></van-tab>
+      <van-tab title="审批中" name="20"></van-tab>
       <van-tab title="已通过" name="30"></van-tab>
-      <van-tab title="全部申请" name="0"></van-tab>
+      <van-tab title="全部申请" name=""></van-tab>
     </van-tabs>
     <div class="list-container">
       <van-list v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad">
@@ -67,10 +67,10 @@
   const platformAppointApi = usePlatformAppointApi()
   const router = useRouter()
   const route = useRoute()
-  const offset = ref({ x: -80, y: 400 })
+  const offset = ref({ x: -80, y: 600 })
   const state = reactive({
     queryParams: {
-      approveStatus: '10,20',
+      approveStatus: '20',
       pageNum: 1,
       pageSize: 10
     },
@@ -80,6 +80,7 @@
   })
   const changeType = (name: string) => {
     state.queryParams.pageNum = 1
+    state.list = []
     onLoad()
   }
   const onLoad = async () => {
@@ -97,7 +98,7 @@
   }
   const toDetail = (id: number) => {
     router.push({
-      path: '/todo/approval-detail',
+      path: '/entry/detail',
       query: {
         id
       }

+ 15 - 5
src/view/exam/cover.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-11 18:02:10
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-27 17:02:21
+ * @LastEditTime: 2025-03-28 11:42:45
  * @FilePath: \vant-demo-master\vant\vue3-ts\src\view\login\index.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -17,8 +17,12 @@
       <van-cell title="答题时间" :value="`${state.form.duration}分钟`" />
       <van-cell title="考试次数" :value="`${state.form.examineCount}/${state.form.totalCount}`" />
     </van-cell-group>
-    <van-button type="primary" class="mt10" @click="startExam">开始考试</van-button>
   </div>
+  <van-action-bar placeholder>
+    <van-action-bar-icon icon="wap-home-o" text="首页" @click="onRouterPush('/home')" />
+    <van-action-bar-icon icon="question-o" text="我的考试" @click="onRouterPush('/exam/mine')" />
+    <van-action-bar-button type="primary" text="开始考试" @click="startExam" />
+  </van-action-bar>
 </template>
 
 <script name="home" lang="ts" setup>
@@ -59,7 +63,7 @@
     router.go(-1)
   }
   const initExam = async () => {
-    const [err, res]: ToResponse = await to(trainingApi.getMineExamResultInfo({ id: route.query.state }))
+    const [err, res]: ToResponse = await to(trainingApi.getMineExamResultInfo({ id: +route.query.state }))
     if (err) return
     state.form = res?.data
   }
@@ -94,9 +98,15 @@
     if (err) return
     Local.set('token', res?.data.token)
   }
+  const onRouterPush = (val: string, params?: any) => {
+    router.push({
+      path: val,
+      query: { ...params }
+    })
+  }
   onMounted(async () => {
-    await storesUseUserInfo.setOpenId(route.query.code as string)
-    await openIdLogin()
+    // await storesUseUserInfo.setOpenId(route.query.code as string)
+    // await openIdLogin()
     initExam()
   })
 </script>

+ 32 - 9
src/view/exam/index.vue

@@ -55,11 +55,27 @@
       </van-checkbox-group>
       <!-- 填空 -->
       <van-cell-group v-else-if="currentNode.quType == 4">
-        <van-field v-for="(item, index) in currentNode.quContent" :key="item.id" v-model="item.content" :label="setFillNo(item.name)" label-width="20"></van-field>
+        <van-field
+          v-for="(item, index) in currentNode.quContent"
+          :key="item.id"
+          v-model="item.content"
+          :label="setFillNo(item.name)"
+          label-width="20"
+        ></van-field>
       </van-cell-group>
       <!-- 问答 -->
       <van-cell-group v-else-if="currentNode.quType == 5">
-        <van-field v-model="currentNode.answer" rows="2" autosize label="答" type="textarea" maxlength="300" placeholder="请输入" show-word-limit label-width="20" />
+        <van-field
+          v-model="currentNode.answer"
+          rows="2"
+          autosize
+          label="答"
+          type="textarea"
+          maxlength="300"
+          placeholder="请输入"
+          show-word-limit
+          label-width="20"
+        />
       </van-cell-group>
     </div>
     <div class="btns">
@@ -71,11 +87,14 @@
           <van-button :disabled="curQuNo == sortQuList.length - 1" class="w100" @click="handleNext">下一题</van-button>
         </van-col>
       </van-row>
-      <van-row gutter="10" class="mt10">
+      <!-- <van-row gutter="10" class="mt10">
         <van-button type="primary" class="w100" @click="handleSubmitAllAnswer(false)">交卷</van-button>
-      </van-row>
+      </van-row> -->
     </div>
   </div>
+  <van-action-bar placeholder>
+    <van-action-bar-button type="primary" text="交卷" @click="handleSubmitAllAnswer(false)" />
+  </van-action-bar>
 </template>
 
 <script name="home" lang="ts" setup>
@@ -201,13 +220,14 @@
   }
   // 倒计时
   const countdown = () => {
-    const end = Date.parse(new Date(examEndTime.value).toLocaleString())
-    const now = Date.parse(new Date().toLocaleString())
+    const end = new Date(examEndTime.value).getTime()
+    const now = new Date().getTime()
     const msec = end - now - 60
     if (msec <= 0) {
       return
     }
-    ;(min.value = Math.floor((msec / 1000 / 60) << 0)), (sec.value = Math.floor((msec / 1000) % 60))
+    min.value = Math.floor((msec / 1000 / 60) << 0)
+    sec.value = Math.floor((msec / 1000) % 60)
     if (min.value >= 0 && sec.value >= 0) {
       //倒计时结束关闭考试
       if (min.value == 0 && sec.value == 0) {
@@ -297,7 +317,10 @@
       type: 'success'
     })
     router.push({
-      path: '/home'
+      path: '/exam/result',
+      query: {
+        id: answerId
+      }
     })
   }
   onMounted(() => {
@@ -338,7 +361,7 @@
       }
     }
     .btns {
-      margin-bottom: 20px;
+      margin-bottom: 10px;
     }
     .selection {
       display: flex;

+ 123 - 0
src/view/exam/mine.vue

@@ -0,0 +1,123 @@
+<!--
+ * @Author: wanglj wanglijie@dashoo.cn
+ * @Date: 2025-03-17 13:36:58
+ * @LastEditors: wanglj wanglijie@dashoo.cn
+ * @LastEditTime: 2025-03-28 14:47:27
+ * @FilePath: \labsop-h5\src\view\training\index.vue
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+-->
+<template>
+  <div class="app-container">
+    <van-list v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad" class="mt10">
+      <van-cell v-for="(item, index) in state.list" :key="index" :center="true">
+        <template #title>
+          <div class="list" @click="onDetail(item)">
+            <header class="flex justify-between">
+              <van-text-ellipsis class="title" :content="item.name" />
+              <span>次数:{{ item.examineCount }}/{{ item.totalCount }}</span>
+            </header>
+            <footer>
+              {{ `${formatDate(new Date(item.startTime), 'YYYY-mm-dd HH:MM')}~${formatDate(new Date(item.endTime), 'YYYY-mm-dd HH:MM')}` }}
+            </footer>
+          </div>
+        </template>
+      </van-cell>
+    </van-list>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import to from 'await-to-js'
+  import { onMounted, reactive } from 'vue'
+  import { useRouter } from 'vue-router'
+  import { useTrainingApi } from '/@/api/training'
+  import { formatDate } from '/@/utils/formatTime'
+  import { useUserInfo } from '/@/stores/userInfo'
+  import { showNotify } from 'vant'
+  const props = defineProps({
+    isAll: {
+      type: String,
+      default: '10'
+    }
+  })
+  const trainingApi = useTrainingApi()
+  const router = useRouter()
+  const state = reactive({
+    queryParams: {
+      pageNum: 1,
+      pageSize: 10,
+      isAll: false
+    },
+    list: [] as any[],
+    loading: false,
+    finished: false
+  })
+  const onLoad = async () => {
+    if (state.loading) return
+    state.loading = true
+    const params = JSON.parse(JSON.stringify(state.queryParams))
+    params.userId = useUserInfo().userInfos.id
+    const [err, res]: ToResponse = await to(trainingApi.getExamList(params))
+    if (err) {
+      state.loading = false
+      return
+    }
+    const list = res?.data?.list || []
+    for (const item of list) {
+      state.list.push(item)
+    }
+    if (list.length < state.queryParams.pageSize) {
+      state.finished = true
+    }
+    state.queryParams.pageNum++
+    state.loading = false
+  }
+  const onDetail = (row: any) => {
+    if(!row.lastExam) return
+    router.push({
+      path: '/exam/result',
+      query: {
+        id: row.lastExam.answerId
+      }
+    })
+  }
+  onMounted(() => {
+    onLoad()
+  })
+</script>
+
+<style lang="scss" scoped>
+  .app-container {
+    .van-cell {
+      background-color: #fff;
+      margin-top: 10px;
+      header {
+        color: #333;
+        font-size: 16px;
+      }
+      footer {
+        color: #969799;
+        margin-top: 4px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        text-align: right;
+      }
+      .title {
+        font-weight: bold;
+      }
+      .inst-title {
+        color: #333;
+        text-align: left;
+        flex: 1;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        margin-top: 4px;
+      }
+      .time {
+        color: #f69a4d;
+      }
+    }
+  }
+</style>

+ 140 - 0
src/view/exam/result.vue

@@ -0,0 +1,140 @@
+<!--
+ * @Author: wanglj wanglijie@dashoo.cn
+ * @Date: 2025-03-17 13:36:58
+ * @LastEditors: wanglj wanglijie@dashoo.cn
+ * @LastEditTime: 2025-03-28 13:46:42
+ * @FilePath: \labsop-h5\src\view\training\index.vue
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+-->
+<template>
+  <div class="app-container">
+    <h4>考试结果</h4>
+    <van-cell-group>
+      <van-cell title="考生姓名" :value="state.form.fullName"></van-cell>
+      <van-cell title="考试时间" :value="state.form.entryTime"></van-cell>
+      <van-cell title="试卷总分" :value="state.form.epTotalScore"></van-cell>
+      <van-cell title="得分" :value="state.form.exScore"></van-cell>
+      <van-cell title="结果" :value="state.form.exScore >= state.form.passingScore ? '及格' : '不及格'"></van-cell>
+      <van-cell title="用时" :value="`${useMin}分钟`"></van-cell>
+    </van-cell-group>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import to from 'await-to-js'
+  import { computed, onMounted, reactive } from 'vue'
+  import { useRouter, useRoute } from 'vue-router'
+  import { useTrainingApi } from '/@/api/training'
+  import { formatDate } from '/@/utils/formatTime'
+  import { useUserInfo } from '/@/stores/userInfo'
+  import { showNotify } from 'vant'
+  const props = defineProps({
+    isAll: {
+      type: String,
+      default: '10'
+    }
+  })
+  const trainingApi = useTrainingApi()
+  const route = useRoute()
+  const router = useRouter()
+  const state = reactive({
+    answerId: 0,
+    queryParams: {
+      pageNum: 1,
+      pageSize: 10,
+      isAll: false
+    },
+    form: {
+      fullName: '',
+      entryTime: '',
+      epTotalScore: 0,
+      exScore: 0,
+      passingScore: 0,
+      handTime: ''
+    },
+    loading: false,
+    finished: false
+  })
+  const useMin = computed(() => {
+    let startDate = new Date(state.form.entryTime).getTime()
+    let endDate = new Date(state.form.handTime).getTime()
+    let min = Math.floor(((endDate - startDate) / 1000 / 60) << 0)
+    return min
+  })
+  const initData = async () => {
+    const [err, res]: ToResponse = await to(trainingApi.getAnswerInfo({ answerId: state.answerId }))
+    if (err) return
+    if (res?.data) {
+      state.form = res.data
+    }
+  }
+  const dateRemoveSec = (date: any) => {
+    if (date && typeof date == 'string') {
+      let dateArr = []
+      dateArr = date.split(':')
+      return dateArr[0] + ':' + dateArr[1]
+    } else {
+      return '/'
+    }
+  }
+  onMounted(() => {
+    const id = route.query.id ? Number(route.query.id) : 0
+    state.answerId = id
+    initData()
+  })
+</script>
+
+<style lang="scss" scoped>
+  .app-container {
+    .van-cell {
+      background-color: #fff;
+      margin-top: 10px;
+      header {
+        color: #333;
+        font-size: 16px;
+      }
+      footer {
+        color: #969799;
+        margin-top: 4px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        text-align: right;
+      }
+      .title {
+        font-weight: bold;
+      }
+      .inst-title {
+        color: #333;
+        text-align: left;
+        flex: 1;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        margin-top: 4px;
+      }
+      .time {
+        color: #f69a4d;
+      }
+    }
+    h4 {
+      height: 18px;
+      line-height: 18px;
+      display: flex;
+      margin: 10px 0;
+      span {
+        font-weight: normal;
+        margin-left: auto;
+      }
+      &::before {
+        display: inline-block;
+        content: '';
+        width: 3px;
+        height: 18px;
+        background-color: #1c9bfd;
+        margin-right: 4px;
+        vertical-align: middle;
+      }
+    }
+  }
+</style>

+ 2 - 2
src/view/home/index.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-11 18:02:10
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-26 17:50:22
+ * @LastEditTime: 2025-03-28 10:55:46
  * @FilePath: \vant-demo-master\vant\vue3-ts\src\view\login\index.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -43,7 +43,7 @@
           <img src="../../assets/img/培训考试.png" alt="" />
           <p>培训考试</p>
         </li>
-        <li @click="onRouterPush('/exam-cover')" v-auth="'h5-home-animal'">
+        <li v-auth="'h5-home-animal'">
           <img src="../../assets/img/动物笼位.png" alt="" />
           <p>动物笼位</p>
         </li>

+ 3 - 3
src/view/instr/appoint.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-24 09:17:15
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-26 16:39:21
+ * @LastEditTime: 2025-03-28 11:44:38
  * @FilePath: \labsop_h5\src\view\instr\detail.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -92,7 +92,7 @@
       <CustomForm ref="customFormRef" :formData="state.form.createForm"></CustomForm>
     </van-form>
   </div>
-  <van-action-bar>
+  <van-action-bar placeholder>
     <van-action-bar-button class="w100" type="primary" text="提交" @click="onClickButton" />
   </van-action-bar>
   <!-- 选择服务 -->
@@ -367,7 +367,7 @@
 
 <style lang="scss" scoped>
   .container {
-    height: calc(100% - 70px);
+    flex: 1;
     padding: 10px;
     background-color: #f9f9f9;
     overflow-y: auto;

+ 1 - 1
src/view/instr/appointList/soonGeton/index.vue

@@ -177,7 +177,7 @@
         } else if (row.controlMode == '30') {
           // 蓝牙
           // this.$refs.bluetoothRef.initBlue('open', row)
-          await useUserInfo().scanCode()
+          await useUserInfo().scanCode(0)
         }
       },
       /**

+ 6 - 4
src/view/instr/calendar.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-24 16:28:47
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-25 14:51:00
+ * @LastEditTime: 2025-03-28 11:47:37
  * @FilePath: \labsop_h5\src\view\instr\components\appoint-dialog.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -29,7 +29,7 @@
       </template>
     </div>
   </div>
-  <van-action-bar>
+  <van-action-bar placeholder>
     <van-action-bar-button class="w100" type="primary" text="提交" @click="onClickButton" />
   </van-action-bar>
 </template>
@@ -127,7 +127,7 @@
       }
       // 禁用不可预约时间段和已预约时间段
       for(const item of state.currentWeekAppointList) {
-        if(obj.startStamp >= item.startStamp && obj.endStamp <= item.endStamp) {
+        if(obj.endStamp <= item.endStamp) {
           obj.disabled = true
           continue
         }
@@ -245,7 +245,8 @@
 </script>
 <style lang="scss" scoped>
   .calendar-container {
-    height: calc(100% - 50px);
+    flex: 1;
+    overflow: hidden;
     display: flex;
     flex-direction: column;
     header {
@@ -261,6 +262,7 @@
     .main {
       flex: 1;
       overflow-y: auto;
+      overflow-x: hidden;
       padding: 10px 6px;
       display: flex;
       flex-wrap: wrap;

+ 3 - 3
src/view/instr/detail.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-24 09:17:15
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-26 15:28:13
+ * @LastEditTime: 2025-03-28 11:39:21
  * @FilePath: \labsop_h5\src\view\instr\detail.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -150,7 +150,7 @@
     </van-list>
     <van-back-top target=".instr-detail" bottom="10vh" />
   </div>
-  <van-action-bar>
+  <van-action-bar placeholder>
     <van-action-bar-icon icon="wap-home-o" text="首页" @click="onRouterPush('/home')" />
     <van-action-bar-icon icon="calendar-o" text="周视图" />
     <van-action-bar-icon :icon="state.instDetail.following ? 'star' : 'star-o'" :class="{ follow: state.instDetail.following }" :text="state.instDetail.following ? '取消收藏' : '收藏'" @click="handleFollowInst" />
@@ -360,7 +360,7 @@
 
 <style lang="scss" scoped>
   .instr-detail {
-    height: calc(100% - 50px);
+    flex: 1;
     overflow-y: auto;
     background-color: #f7f8fa;
     .my-swipe {

+ 4 - 25
src/view/todo/approval-detail.vue → src/view/todo/detail.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-11 18:02:10
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-19 13:46:04
+ * @LastEditTime: 2025-03-29 16:15:47
  * @FilePath: \vant-demo-master\vant\vue3-ts\src\view\login\index.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -27,39 +27,18 @@
       <van-cell title="文件" :value="state.form.defName" />
     </van-cell-group>
     <h4>审批记录</h4>
-    <van-cell value-class="flow-cell">
-      <template #value>
-        <h4>审核方式</h4>
-        <h4>审核人</h4>
-        <h4>审核结果</h4>
-        <h4>审核时间</h4>
-      </template>
-    </van-cell>
-    <van-steps direction="vertical" :active="0">
-      <van-step v-for="row in apprList" :key="row.id">
-        <div class="flex">
-          <p>{{ getNodeModel(row.nodeType, row.nodeModel) }}</p>
-          <p>{{ row.userName }}</p>
-          <p>
-            <span v-if="row.approvalResult === 'pass'">通过</span>
-            <span v-else-if="row.approvalResult === 'rejection'">拒绝</span>
-            <span v-else-if="row.approvalResult === 'back'">退回</span>
-            <span v-else>审批中</span>
-          </p>
-          <p>{{ row.approvalDate ? formatDate(new Date(row.approvalDate), 'YYYY-mm-dd') : '' }}</p>
-        </div>
-      </van-step>
-    </van-steps>
+    <FlowTable :id="state.form.id" :businessCode="state.form.businessCode" :defCode="state.form.defCode" />
   </div>
 </template>
 
 <script name="home" lang="ts" setup>
   import to from 'await-to-js'
   import { formatDate } from '/@/utils/formatTime'
-  import { onMounted, reactive, ref } from 'vue'
+  import { defineAsyncComponent, onMounted, reactive, ref } from 'vue'
   import { useRouter, useRoute } from 'vue-router'
   import { useExecutionApi } from '/@/api/execution'
   import { useFlowApi } from '/@/api/execution/flow'
+  const FlowTable = defineAsyncComponent(() => import('/@/components/FlowTable.vue'))
   const executionApi = useExecutionApi()
   const flowApi = useFlowApi()
   const router = useRouter()

+ 1 - 1
src/view/todo/index.vue

@@ -76,7 +76,7 @@
   }
   const toDetail = (id: number) => {
     router.push({
-      path: '/todo/approval-detail',
+      path: '/todo/detail',
       query: {
         id
       }

+ 344 - 0
src/view/training/detail.vue

@@ -0,0 +1,344 @@
+<!--
+ * @Author: wanglj wanglijie@dashoo.cn
+ * @Date: 2025-03-24 09:17:15
+ * @LastEditors: wanglj wanglijie@dashoo.cn
+ * @LastEditTime: 2025-03-29 14:52:24
+ * @FilePath: \labsop_h5\src\view\instr\detail.vue
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+-->
+<template>
+  <div class="instr-detail">
+    <header class="flex">
+      <div class="i-right ml10">
+        <div class="h100 flex flex-top flex-column flex-between">
+          <div class="flex flex-top mb4 ml2">
+            <div class="detailTxt name">{{ state.form.title }}</div>
+          </div>
+          <footer>
+            <div class="flex flex-top mt-auto">
+              <img class="i-r-icon" src="../../assets/img/user.png" />
+              <div class="detailTxt">{{ state.form.createdName }}</div>
+            </div>
+            <div class="flex flex-top">
+              <div class="detailTxt">{{ state.form.createdTime }}</div>
+            </div>
+          </footer>
+        </div>
+      </div>
+    </header>
+    <div class="content">
+      <div class="card">
+        <h4>基本信息</h4>
+        <ul>
+          <li>
+            <label>培训类型</label>
+            <span>{{ getDictLabel(typeList, state.form.type) }}</span>
+          </li>
+          <li>
+            <label>培训形式</label>
+            <span>{{ getDictLabel(methodList, state.form.method) }}</span>
+          </li>
+          <li>
+            <label>培训地点</label>
+            <span>{{ state.form.location }}</span>
+          </li>
+          <li>
+            <label>开始时间</label>
+            <span>{{ state.form.startTime }}</span>
+          </li>
+          <li>
+            <label>结束时间</label>
+            <span>{{ state.form.endTime }}</span>
+          </li>
+        </ul>
+      </div>
+      <div class="card">
+        <h4>关联考试</h4>
+        <ul>
+          <li>
+            <label>考试名称</label>
+            <span>{{ state.form.relationExam }}</span>
+          </li>
+          <li>
+            <label>考试成绩</label>
+            <span>{{ `${state.form.totalScore}(${state.form.totalScore >= state.form.passScore ? '及格' : '不及格'})` }}</span>
+          </li>
+          <li>
+            <label>试卷总分</label>
+            <span>{{ state.form.totalScore }}</span>
+          </li>
+          <li>
+            <label>及格分数</label>
+            <span>{{ state.form.passScore }}</span>
+          </li>
+          <li>
+            <label>考试次数</label>
+            <span>{{ state.form.examTimes }}</span>
+          </li>
+          <li>
+            <label>关联证书</label>
+            <span>{{ state.form.relationCertName }}</span>
+          </li>
+        </ul>
+      </div>
+      <div class="card">
+        <h4>培训说明</h4>
+        <div class="text" v-html="state.form.desc"></div>
+      </div>
+    </div>
+  </div>
+  <van-action-bar placeholder>
+    <van-action-bar-icon icon="wap-home-o" text="首页" @click="onRouterPush('/home')" />
+    <van-action-bar-icon icon="passed" text="我的培训" @click="onRouterPush('/training/done')" />
+    <van-action-bar-button type="primary" text="前往考试" @click="onRouterPush('/exam/cover', { state: state.form.relationExamId })" />
+  </van-action-bar>
+</template>
+
+<script lang="ts" setup>
+  import to from 'await-to-js'
+  import { useRoute, useRouter } from 'vue-router'
+  import { onMounted, reactive, ref } from 'vue'
+  import { useTrainingApi } from '/@/api/training'
+  import { useDictApi } from '/@/api/system/dict'
+import { getDictLabel } from '/@/utils/other'
+  const route = useRoute()
+  const router = useRouter()
+  const trainingApi = useTrainingApi()
+  const dictApi = useDictApi()
+  const state = reactive({
+    detailsLoading: false,
+    instStatus: {
+      10: '正常',
+      20: '故障',
+      30: '报废'
+    },
+    form: {} as any,
+    queryParams: {
+      pageNum: 1,
+      pageSize: 10,
+      instId: 0,
+      appointStatus: []
+    },
+    list: [] as any[]
+  })
+  const typeList = ref(<RowDicDataType[]>[])
+  const methodList = ref(<RowDicDataType[]>[])
+  const getDicts = () => {
+    Promise.all([
+      dictApi.getDictDataByType('te_training_notice_type'),
+      dictApi.getDictDataByType('te_training_notice_method'), 
+    ]).then(([type, method]) => {
+      typeList.value = type?.data?.values || []
+      methodList.value = method?.data?.values || []
+    })
+  }
+  //详情
+  const getDetail = async (id: number) => {
+    state.detailsLoading = true
+    const [err, res]: ToResponse = await to(trainingApi.getEntity({ id }))
+    state.detailsLoading = false
+    if (err) return
+    if (res?.code === 200) {
+      state.form = res.data
+    }
+  }
+  const onRouterPush = (val: string, params?: any) => {
+    router.push({
+      path: val,
+      query: { ...params }
+    })
+  }
+  onMounted(() => {
+    const id = route.query.id ? +route.query.id : 0
+    getDicts()
+    getDetail(id)
+  })
+</script>
+
+<style lang="scss" scoped>
+  .instr-detail {
+    flex: 1;
+    overflow-y: auto;
+    background-color: #f7f8fa;
+    .my-swipe {
+      background-color: #fff;
+      height: 30px !important;
+      line-height: 30px !important;
+      :deep(.flex) {
+        height: 30px;
+        overflow: hidden;
+        padding: 0 12px;
+        span {
+          display: inline-block;
+          height: 30px;
+          line-height: 30px;
+        }
+        span:first-child {
+          flex: 1;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+    > header {
+      height: 40px;
+      background-color: #fff;
+      padding: 12px;
+    }
+    .inst-info {
+      display: flex;
+    }
+    .i-right {
+      flex: 1;
+      font-size: 14px;
+      height: 40px;
+      .i-r-icon {
+        width: 15px;
+        height: 15px;
+        margin-right: 10px;
+      }
+      footer {
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+      }
+    }
+    .detailTxt {
+      font-size: 12px;
+      color: #333333;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      &.name {
+        font-weight: bold;
+        font-size: 16px;
+      }
+    }
+    .content {
+      padding: 10px;
+    }
+    .card {
+      border-radius: 4px;
+      background-color: #fff;
+      padding: 10px;
+      box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
+      & + .card {
+        margin-top: 10px;
+      }
+      h4 {
+        height: 18px;
+        line-height: 18px;
+        display: flex;
+        margin-bottom: 10px;
+        span {
+          font-weight: normal;
+          margin-left: auto;
+        }
+        &::before {
+          display: inline-block;
+          content: '';
+          width: 3px;
+          height: 18px;
+          background-color: #1c9bfd;
+          margin-right: 4px;
+          vertical-align: middle;
+        }
+      }
+      > ul {
+        li {
+          display: flex;
+          align-items: center;
+          padding: 6px 0;
+          label {
+            width: 80px;
+            min-width: 80px;
+            color: #969799;
+          }
+          span {
+            word-break: break-all;
+          }
+        }
+      }
+      .text {
+        white-space: pre-wrap;
+      }
+    }
+    .van-list {
+      padding: 10px;
+      border-radius: 4px;
+      flex: 1;
+      .van-cell {
+        background-color: #fff;
+        + .van-cell {
+          margin-top: 10px;
+        }
+        header,
+        footer {
+          color: #333;
+        }
+        .title {
+          flex: 1;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          text-align: left;
+        }
+        .inst-title {
+          color: #333;
+          text-align: left;
+          flex: 1;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          margin-top: 4px;
+          span:first-child {
+            display: inline-block;
+            width: 80px;
+            min-width: 80px;
+            color: rgb(120, 120, 120);
+          }
+        }
+        .time {
+          color: #f69a4d;
+        }
+      }
+    }
+  }
+  .btns {
+    flex: 1;
+    display: flex;
+    li {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 0 8px;
+      font-size: 12px;
+      i {
+        margin-bottom: 4px;
+      }
+    }
+  }
+  :deep(.follow .van-icon) {
+    color: #fdc33e;
+  }
+  .need-to-know {
+    height: calc(100% - 20px);
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    padding: 10px 20px;
+    white-space: pre-wrap;
+    p {
+      flex: 1;
+      overflow-y: auto;
+    }
+    footer {
+      flex: 0 0 45px;
+      margin-top: 4px;
+      border-top: 1px solid #f7f8fa;
+    }
+  }
+</style>

+ 2 - 2
src/view/training/enroll.vue

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-18 20:02:42
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-19 18:39:48
+ * @LastEditTime: 2025-03-28 10:53:32
  * @FilePath: \labsop_h5\src\view\training\enroll.vue
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 -->
@@ -112,7 +112,7 @@
       type: 'success',
       message: '报名成功'
     })
-    router.push('/home')
+    router.push('/training/done')
   }
   onMounted(() => {
     storesUseUserInfo.setOpenId(route.query.code as string)

+ 15 - 6
src/view/training/index.vue

@@ -35,7 +35,7 @@
   import { useTrainingApi } from '/@/api/training'
   import { formatDate } from '/@/utils/formatTime'
   import { useUserInfo } from '/@/stores/userInfo'
-import { showNotify } from 'vant'
+  import { showNotify } from 'vant'
   const props = defineProps({
     isAll: {
       type: String,
@@ -76,11 +76,20 @@ import { showNotify } from 'vant'
     state.loading = false
   }
   const onEnroll = (row: any) => {
-    if(row.applyStatus === '20') {
-      showNotify({
-        type: 'warning',
-        message: '您已报名该培训,请勿重复报名'
-      })
+    if (row.applyStatus === '20') {
+      if (props.isAll === '10') {
+        showNotify({
+          type: 'warning',
+          message: '您已报名该培训,请勿重复报名'
+        })
+      } else {
+        router.push({
+          path: '/training/detail',
+          query: {
+            id: row.id
+          }
+        })
+      }
       return
     }
     router.push({

+ 6 - 6
vite.config.ts

@@ -2,7 +2,7 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-03-10 11:40:15
  * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-20 13:58:08
+ * @LastEditTime: 2025-03-29 09:21:26
  * @FilePath: \vue3-ts\vite.config.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -33,11 +33,11 @@ export default defineConfig({
     https: false,
     allowedHosts: ['4ik677er1300.vicp.fun'],
     proxy: {
-      // '/wechat': {
-      //   target: 'http://gz-zyyfy-wx.labsop.cn',
-      //   ws: true,
-      //   changeOrigin: true
-      // },
+      '/wechat': {
+        target: 'http://gz-zyyfy-wx.labsop.cn',
+        ws: true,
+        changeOrigin: true
+      },
     },
   },
 });