Pārlūkot izejas kodu

feat(view/training): 优化培训报名页面UI并增加培训详情展示

- 重构报名页面布局,将培训信息与报名信息分离展示
- 新增培训信息卡片,展示培训类型、形式、地点等详细信息
- 添加培训说明弹窗功能,支持富文本内容展示
- 优化页面样式,采用卡片式设计提升视觉体验
- 隐藏原有表单中重复的培训信息字段
张旭伟 5 dienas atpakaļ
vecāks
revīzija
5c907155c4
1 mainītis faili ar 302 papildinājumiem un 35 dzēšanām
  1. 302 35
      src/view/training/enroll.vue

+ 302 - 35
src/view/training/enroll.vue

@@ -8,19 +8,44 @@
 -->
 <template>
   <div class="app-container">
-    <van-form ref="formRef" @submit="onSubmit" class="mt10" required="auto">
-      <van-cell-group>
-        <van-field v-model="state.form.trainingNoticeName" label="培训名称" placeholder="培训名称" readonly :rules="[{ required: true }]" />
-        <van-field v-model="state.form.startTime" label="培训时间" placeholder="培训时间" readonly :rules="[{ required: true, message: '请选择培训时间' }]">
-          <template #input>{{ state.form.startTime }}~{{ state.form.endTime }}</template>
-        </van-field>
-        <van-field v-model="state.form.name" label="报名人" placeholder="报名人" :rules="[{ required: true, message: '请填写报名人' }]" />
-        <van-field v-model="state.form.telephone" label="联系电话" placeholder="联系电话" :rules="[{ required: true, message: '请填写联系电话' }]" />
-        <van-field v-model="state.form.type" label="人员类型" placeholder="人员类型" readonly>
-          <template #input>{{ getDictLabel(userTypeList, state.form.type) }}</template>
+    <div class="card" v-if="noticeInfo.id">
+      <h4>培训信息</h4>
+      <van-cell-group :border="false">
+        <van-field :model-value="noticeInfo.title" label="培训名称" readonly />
+        <van-field :model-value="getDictLabel(typeList, noticeInfo.type)" label="培训类型" readonly />
+        <van-field :model-value="getDictLabel(methodList, noticeInfo.method)" label="培训形式" readonly />
+        <van-field :model-value="noticeInfo.relationExamPaperName" label="考试" readonly />
+        <van-field :model-value="noticeInfo.relationCertName" label="证书" readonly />
+        <van-field :model-value="noticeInfo.location" label="培训地点" readonly />
+        <van-field label="培训时间" readonly>
+          <template #input>
+            <div class="time-column">
+              <span>{{ noticeInfo.startTime }}</span>
+             
+              <span>{{ noticeInfo.endTime }}</span>
+            </div>
+          </template>
         </van-field>
-        <van-field v-model="state.form.projectGroup" label="课题组" placeholder="课题组" readonly />
+        <van-cell v-if="noticeInfo.desc" title="培训说明" is-link @click="showDesc = true" value="点击查看详情" />
       </van-cell-group>
+    </div>
+
+    <van-form ref="formRef" @submit="onSubmit" class="mt10" required="auto">
+      <div class="card">
+        <h4>报名信息</h4>
+        <van-cell-group :border="false">
+          <van-field v-model="state.form.trainingNoticeName" label="培训名称" placeholder="培训名称" readonly :rules="[{ required: true }]" v-if="false" />
+          <van-field v-model="state.form.startTime" label="培训时间" placeholder="培训时间" readonly :rules="[{ required: true, message: '请选择培训时间' }]" v-if="false">
+            <template #input>{{ state.form.startTime }}~{{ state.form.endTime }}</template>
+          </van-field>
+          <van-field v-model="state.form.name" label="报名人" placeholder="报名人" :rules="[{ required: true, message: '请填写报名人' }]" />
+          <van-field v-model="state.form.telephone" label="联系电话" placeholder="联系电话" :rules="[{ required: true, message: '请填写联系电话' }]" />
+          <van-field v-model="state.form.type" label="人员类型" placeholder="人员类型" readonly>
+            <template #input>{{ getDictLabel(userTypeList, state.form.type) }}</template>
+          </van-field>
+          <van-field v-model="state.form.projectGroup" label="课题组" placeholder="课题组" readonly />
+        </van-cell-group>
+      </div>
       <div style="margin: 16px">
         <van-button round block type="primary" native-type="submit"> 提交 </van-button>
       </div>
@@ -29,6 +54,15 @@
   <van-popup v-model:show="showPicker" position="bottom">
     <van-date-picker v-model="state.form.trainingDate" @confirm="onConfirm" @cancel="showPicker = false" />
   </van-popup>
+
+  <!-- 培训说明弹窗 -->
+  <van-popup v-model:show="showDesc" round :closeable="true" position="bottom" :style="{ height: '90vh' }">
+    <div class="need-to-know">
+        <h4 class="mt8 mb8">培训详情说明</h4>
+        <p v-html="noticeInfo.desc"></p>
+    </div>
+  </van-popup>
+ 
 </template>
 
 <script name="home" lang="ts" setup>
@@ -51,8 +85,12 @@
   const route = useRoute()
   const formRef = ref()
   const showPicker = ref(false)
+  const showDesc = ref(false)
   const dictApi = useDictApi()
   const userTypeList = ref(<RowDicDataType[]>[])
+  const typeList = ref(<RowDicDataType[]>[])
+  const methodList = ref(<RowDicDataType[]>[])
+  const noticeInfo = ref<any>({})
   const state = reactive({
     form: {
       trainingNoticeId: 0,
@@ -71,8 +109,14 @@
     }
   })
   const getDicts = () => {
-    Promise.all([dictApi.getDictDataByType('sys_user_type')]).then(([type]) => {
-      userTypeList.value = type.data.values || []
+    Promise.all([
+      dictApi.getDictDataByType('sys_user_type'),
+      dictApi.getDictDataByType('te_training_notice_type'),
+      dictApi.getDictDataByType('te_training_notice_method')
+    ]).then(([userType, type, method]) => {
+      userTypeList.value = userType.data.values || []
+      typeList.value = type.data.values || []
+      methodList.value = method.data.values || []
     })
   }
   const initForm = async () => {
@@ -89,6 +133,7 @@
     if(state.form.trainingNoticeId) {
       const [err, res]: ToResponse = await to(trainingApi.getEntity({ id: state.form.trainingNoticeId }))
       if(err) return
+      noticeInfo.value = res?.data || {}
       state.form.trainingNoticeName = res?.data?.title
       state.form.startTime = res?.data?.startTime
       state.form.endTime = res?.data?.endTime
@@ -127,31 +172,253 @@
 
 <style lang="scss" scoped>
   .app-container {
-    header {
-      padding: 14px;
-      background-color: #f9ffff;
-      border-radius: 4px;
-      margin-top: 10px;
-    }
-    > h4 {
-      margin: 10px 0;
-      height: 20px;
-      line-height: 20px;
-      &::before {
-        display: inline-block;
-        content: '';
-        background-color: #3c78e3;
-        width: 4px;
-        height: 20px;
-        margin-right: 4px;
-        vertical-align: top;
+    background-color: #f7f8fa;
+    min-height: 60vh;
+    padding-bottom: 40px;
+    overflow-y: auto;
+    display: block;
+    -webkit-overflow-scrolling: touch;
+    .card {
+      margin: 10px;
+      padding: 15px;
+      background-color: #fff;
+      border-radius: 8px;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+
+      h4 {
+        margin: 0 0 15px 0;
+        font-size: 16px;
+        color: #323233;
+        display: flex;
+        align-items: center;
+
+        &::before {
+          content: '';
+          width: 4px;
+          height: 16px;
+          background-color: #1989fa;
+          margin-right: 8px;
+          border-radius: 2px;
+        }
+      }
+
+      .time-column {
+        display: flex;
+        flex-direction: column;
+        width: 100%;
+        .to-text {
+          font-size: 12px;
+          color: #969799;
+          margin: 2px 0;
+          text-align: left;
+        }
+        span {
+          text-align: left;
+          line-height: 1.4;
+        }
+      }
+
+      .info-list {
+        li {
+          display: flex;
+          margin-bottom: 10px;
+          font-size: 14px;
+          line-height: 1.4;
+
+          label {
+            width: 80px;
+            color: #969799;
+            flex-shrink: 0;
+          }
+
+          span {
+            color: #323233;
+            word-break: break-all;
+          }
+
+          &.time-item {
+            flex-direction: column;
+            align-items: flex-start;
+            
+            label {
+              margin-bottom: 6px;
+            }
+            
+            .time-content {
+              display: flex;
+              flex-direction: column;
+              color: #323233;
+              font-weight: 500;
+              
+              .to-text {
+                font-size: 12px;
+                color: #969799;
+                margin: 2px 0;
+                position: relative;
+                padding-left: 10px;
+                
+                &::before {
+                  content: '';
+                  position: absolute;
+                  left: 0;
+                  top: 50%;
+                  width: 4px;
+                  height: 1px;
+                  background-color: #ebedf0;
+                }
+              }
+            }
+          }
+
+          &.desc-entry {
+            align-items: center;
+            padding: 14px 0;
+            border-top: 1px dashed #ebedf0;
+            margin-top: 8px;
+            
+            .link-text {
+              color: #1989fa;
+              display: flex;
+              align-items: center;
+              font-size: 14px;
+              background-color: #f0f7ff;
+              padding: 4px 12px;
+              border-radius: 16px;
+              font-weight: 500;
+              
+              .van-icon {
+                margin-left: 2px;
+              }
+            }
+          }
+        }
       }
     }
-    :deep(.flow-cell) {
-      margin-left: 22px;
+
+    .desc-popup-container {
       display: flex;
-      justify-content: space-between;
-      color: #333;
+      flex-direction: column;
+      height: 100%;
+      background-color: #fff;
+      
+      .popup-header {
+        flex-shrink: 0;
+        height: 56px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-bottom: 1px solid #f2f3f5;
+        background-color: #fff;
+        position: sticky;
+        top: 0;
+        z-index: 10;
+
+        .header-title {
+          font-size: 17px;
+          font-weight: 600;
+          color: #323233;
+        }
+      }
+
+      .popup-body {
+        flex: 1;
+        overflow-y: auto;
+        padding: 20px 16px 40px;
+        -webkit-overflow-scrolling: touch;
+      }
+
+      .rich-text-content {
+        font-size: 15px;
+        color: #444;
+        line-height: 1.8;
+        letter-spacing: 0.5px;
+
+        :deep(img) {
+          max-width: 100%;
+          height: auto !important;
+          display: block;
+          margin: 16px auto;
+          border-radius: 8px;
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+        }
+
+        :deep(p) {
+          margin-bottom: 12px;
+          text-align: justify;
+        }
+
+        :deep(h1), :deep(h2), :deep(h3), :deep(h4) {
+          color: #1a1a1a;
+          margin: 24px 0 12px;
+          font-weight: 600;
+          display: flex;
+          align-items: center;
+
+          &::before {
+            content: '';
+            width: 4px;
+            height: 18px;
+            background: linear-gradient(to bottom, #1989fa, #66b1ff);
+            margin-right: 10px;
+            border-radius: 2px;
+          }
+        }
+
+        :deep(h1) { font-size: 20px; }
+        :deep(h2) { font-size: 18px; }
+        :deep(h3) { font-size: 17px; }
+
+        :deep(ul), :deep(ol) {
+          padding-left: 20px;
+          margin-bottom: 12px;
+          li {
+            margin-bottom: 6px;
+            list-style-type: disc;
+          }
+        }
+        
+        :deep(ol) li {
+          list-style-type: decimal;
+        }
+
+        :deep(blockquote) {
+          margin: 12px 0;
+          padding: 12px 16px;
+          background-color: #f7f8fa;
+          border-left: 4px solid #c8c9cc;
+          color: #646566;
+          font-style: italic;
+        }
+
+        :deep(strong) {
+          color: #1a1a1a;
+          font-weight: 600;
+        }
+      }
+    }
+
+    .mt10 {
+      margin-top: 10px;
     }
   }
+  
+.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>