Explorar el Código

Merge branch 'master' of http://47.100.37.243:10191/wutt/manHourHousekeeper

QuYueTing hace 4 días
padre
commit
a2bbb03243

+ 34 - 1
fhKeeper/formulahousekeeper/course-pc/src/routes.js

@@ -11,6 +11,9 @@ import courselist from './views/coursemanagement/list';
 import lecturerList from './views/lecturerManagement/index.vue'
 import offlineTraining from './views/offlineTraining/offlineTraining.vue'
 import examCertification from './views/examCertification/examCertification.vue'
+import gcpCertification from './views/gcpCertification/gcpCertification.vue'
+import offlineRegistration from './views/offlineRegistration/registration.vue'
+import hotTopicCarousel from './views/hotTopicCarousel/hotTopicCarousel.vue'
 
 Vue.use(Router)
 
@@ -66,7 +69,7 @@ export const allRouters = [
         iconCls: 'iconfont firerock-iconkehu',
         leaf: true,
         children: [
-            { path: '/exam-certification', component: examCertification, name: '考试拿证流程介绍' }
+            { path: '/exam-certification', component: examCertification, name: '考试拿证流程介绍' },
         ]
     },
     {
@@ -79,6 +82,36 @@ export const allRouters = [
             { path: '/offline-training', component: offlineTraining, name: '线下研修班' }
         ]
     },
+    {
+        path: '/offline-registration',
+        component: Home,
+        name: '线下研修班报名',
+        iconCls: 'iconfont firerock-iconkehu',
+        leaf: true,
+        children: [
+            { path: '/offline-registration', component: offlineRegistration, name: '线下研修班报名' }
+        ]
+    },
+    {
+        path: '/hotTopic-carousel',
+        component: Home,
+        name: '轮播热点内容',
+        iconCls: 'iconfont firerock-iconkehu',
+        leaf: true,
+        children: [
+            { path: '/hotTopic-carousel', component: hotTopicCarousel, name: '轮播热点内容' }
+        ]
+    },
+    {
+        path: '/gcpc-certification',
+        component: Home,
+        name: 'GCP证书展示',
+        iconCls: 'iconfont firerock-iconkehu',
+        leaf: true,
+        children: [
+            { path: '/gcpc-certification', component: gcpCertification, name: 'GCP证书展示' }
+        ]
+    },
     {
         path: '*',
         hidden: true,

+ 233 - 0
fhKeeper/formulahousekeeper/course-pc/src/views/gcpCertification/gcpCertification.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="gcp-certification">
+    <h2 class="title">GCP证书上传</h2>
+    <div class="upload-height">
+      <div class="upload-area">
+        <!-- 上传按钮 -->
+        <div class="imageUpload">
+          <el-upload ref="pictureCardRef" action="#" list-type="picture-card" :multiple="true"
+            :show-file-list="false" :http-request="uploadGCPImg" accept="image/*">
+            <i class="el-icon-plus"></i>
+          </el-upload>
+        </div>
+        <!-- 视频或图片 -->
+        <div class="image-list">
+          <div v-for="(file, index) in fileList" :key="index" :class="`${!file.loading ? 'image-item-hover' : ''} image-item`">
+            <img :src="file.url" class="cert-image" @click="previewImage(index)">
+            <div class="image-actions" v-if="!file.loading">
+              <el-button type="danger" icon="el-icon-delete" circle @click.stop="removeImage(index)"></el-button>
+              <el-button type="primary" icon="el-icon-view" circle @click.stop="previewImage(index)"></el-button>
+            </div>
+            <div class="image-actions-loging" v-if="file.loading">
+              文件上传中...
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import Viewer from 'v-viewer'
+import 'viewerjs/dist/viewer.css'
+
+Vue.use(Viewer)
+
+import { post, checkAndAddUpload } from '../../api'
+export default {
+  name: 'GcpCertification',
+  data() {
+    return {
+      fileList: [],
+      imageList: [],
+      imageListTime: null,
+    }
+  },
+  methods: {
+    uploadGCPImg(file) {
+      this.imageList.unshift(file.file)
+      if (this.imageListTime) {
+        clearTimeout(this.imageListTime)
+      }
+      this.imageListTime = setTimeout(() => {
+        this.batchUploadGCPimg()
+      }, 500)
+    },
+    batchUploadGCPimg() {
+      for (let i = 0; i < this.imageList.length; i++) {
+        const formData = new FormData()
+        formData.append('coverImage', this.imageList[i])
+        this.fileList.unshift({
+          name: 'a',
+          loading: true,
+          url: '',
+          imgError: ''
+        })
+        this.http.uploadFile(`/gcp-show/uploadAndSave`, formData, res => {
+          this.fileList[i].url = checkAndAddUpload(res.data.url)
+          this.fileList[i].name = res.data.id
+          this.fileList[i].loading = false
+        }, err => {
+          this.fileList[i].loading = false
+          this.fileList[i].imgError = '图片上传失败'
+        })
+      }
+
+      setTimeout(() => {
+        this.imageList = []
+        this.$refs.pictureCardRef.clearFiles()
+      }, 1500)
+    },
+    removeImage(index) {
+      this.$confirm('此操作将永久删除该图片, 是否继续?', '删除图片', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        post(`/gcp-show/delete`, { id: this.fileList[index].name }).then(res => {
+          this.$message({
+            type: 'success',
+            message: '删除成功!'
+          })
+          this.fileList.splice(index, 1)
+        })
+      })
+    },
+    previewImage(index) {
+      this.$viewerApi({
+        images: this.fileList.map(f => f.url),
+        options: {
+          initialViewIndex: index,
+          toolbar: {
+            zoomIn: 1,
+            zoomOut: 1,
+            oneToOne: 1,
+            reset: 1,
+            prev: 1,
+            next: 1,
+            rotateLeft: 1,
+            rotateRight: 1,
+            flipHorizontal: 1,
+            flipVertical: 1
+          }
+        }
+      })
+    },
+    obtainCertificateList() {
+      post(`/gcp-show/list`, {}).then(res => {
+        this.fileList = res.data.map(item => {
+          return {
+            name: item.id,
+            url: checkAndAddUpload(item.url),
+            loading: false,
+            imgError: ''
+          }
+        }).reverse()
+      })
+    }
+  },
+  mounted() {
+    this.obtainCertificateList()
+  }
+}
+</script>
+
+<style scoped>
+.gcp-certification {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+.title {
+  text-align: center;
+  margin-bottom: 30px;
+  color: #333;
+}
+
+.upload-area {
+  display: flex;
+  flex-wrap: wrap;
+  margin-bottom: 30px;
+}
+
+.upload-height {
+  height: 70vh;
+  overflow-y: auto;
+}
+
+.hide-upload>>>.el-upload--picture-card {
+  display: none;
+}
+
+.image-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+  margin-bottom: 30px;
+}
+
+.imageUpload {
+  margin-right: 20px;
+}
+
+.image-item {
+  position: relative;
+  width: 148px;
+  height: 148px;
+  border-radius: 4px;
+  overflow: hidden;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  transition: all 0.3s;
+}
+
+.image-item:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
+}
+
+.image-item-hover:hover .image-actions {
+  opacity: 1;
+}
+
+.cert-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  cursor: pointer;
+}
+
+.image-actions {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(0, 0, 0, .5);
+  gap: 10px;
+  opacity: 0;
+  transition: opacity 0.3s;
+}
+
+.image-actions-loging {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(0, 0, 0, .5);
+  color: #fff;
+}
+
+.submit-area {
+  text-align: center;
+}
+</style>

+ 225 - 0
fhKeeper/formulahousekeeper/course-pc/src/views/hotTopicCarousel/hotTopicCarousel.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="gcp-certification">
+    <h2 class="title">轮播图热点内容</h2>
+    <div class="upload-height">
+      <div class="upload-area">
+        <!-- 上传按钮 -->
+        <div class="imageUpload">
+          <el-upload ref="pictureCardRef" action="#" list-type="picture-card" :multiple="true" :show-file-list="false"
+            :http-request="uploadGCPImg" accept="video/mp4,video/avi,video/x-msvideo">
+            <i class="el-icon-plus"></i>
+          </el-upload>
+        </div>
+        <!-- 视频或图片 -->
+        <div class="image-list">
+          <div v-for="(file, index) in fileList" :key="index"
+            :class="`${!file.loading ? 'image-item-hover' : ''} image-item`">
+            <img :src="require('../../assets/image/yunketang.png')" class="cert-image" @click="previewImage(index)">
+            <div class="image-actions" v-if="!file.loading">
+              <el-button type="danger" icon="el-icon-delete" circle @click.stop="removeImage(index)"></el-button>
+              <el-button type="primary" icon="el-icon-view" circle @click.stop="previewingVideo(index)"></el-button>
+            </div>
+            <div class="image-actions-loging" v-if="file.loading">
+              文件上传中...
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 预览视频 -->
+    <el-dialog title="预览视频" append-to-body :visible.sync="previewingVideoVisable" width="900px" top="6.5vh" :before-close="handleClose">
+      <div class="previewingVideo">
+        <video :src="previewVideoSrc" :poster="require('../../assets/image/yunketang.png')" controls></video>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { post, checkAndAddUpload } from '../../api'
+export default {
+  name: 'GcpCertification',
+  data() {
+    return {
+      fileList: [],
+      imageList: [],
+      imageListTime: null,
+      previewingVideoVisable: false,
+      previewVideoSrc: ''
+    }
+  },
+  methods: {
+    previewingVideo(index) {
+      const row = this.fileList[index]
+      this.previewVideoSrc = row.url
+      this.previewingVideoVisable = true
+    },
+    uploadGCPImg(file) {
+      this.imageList.unshift(file.file)
+      if (this.imageListTime) {
+        clearTimeout(this.imageListTime)
+      }
+      this.imageListTime = setTimeout(() => {
+        this.batchUploadGCPimg()
+      }, 500)
+    },
+    batchUploadGCPimg() {
+      for (let i = 0; i < this.imageList.length; i++) {
+        const formData = new FormData()
+        formData.append('coverImage', this.imageList[i])
+        this.fileList.unshift({
+          name: 'a',
+          loading: true,
+          url: '',
+          imgError: ''
+        })
+        this.http.uploadFile(`/course-carousel/uploadAndSave`, formData, res => {
+          this.fileList[i].url = checkAndAddUpload(res.data.courseUrl)
+          this.fileList[i].name = res.data.id
+          this.fileList[i].loading = false
+        }, err => {
+          this.fileList.splice(i, 1)
+          this.$message({
+            type: 'error',
+            message: '视频上传失败'
+          })
+        })
+      }
+
+      setTimeout(() => {
+        this.imageList = []
+        this.$refs.pictureCardRef.clearFiles()
+      }, 1500)
+    },
+    removeImage(index) {
+      this.$confirm('此操作将永久删除该视频, 是否继续?', '删除视频', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        post(`/course-carousel/delete`, { id: this.fileList[index].name }).then(res => {
+          this.$message({
+            type: 'success',
+            message: '删除成功!'
+          })
+          this.fileList.splice(index, 1)
+        })
+      })
+    },
+    obtainCertificateList() {
+      post(`/course-carousel/list`, {}).then(res => {
+        this.fileList = res.data.map(item => {
+          return {
+            name: item.id,
+            url: checkAndAddUpload(item.courseUrl),
+            loading: false,
+            imgError: ''
+          }
+        }).reverse()
+      })
+    }
+  },
+  mounted() {
+    this.obtainCertificateList()
+  }
+}
+</script>
+
+<style scoped>
+.gcp-certification {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+.imageUpload {
+  margin-right: 20px;
+}
+
+.title {
+  text-align: center;
+  margin-bottom: 30px;
+  color: #333;
+}
+
+.upload-area {
+  display: flex;
+  flex-wrap: wrap;
+  margin-bottom: 30px;
+}
+
+.upload-height {
+  height: 70vh;
+  overflow-y: auto;
+}
+
+.hide-upload>>>.el-upload--picture-card {
+  display: none;
+}
+
+.image-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+  margin-bottom: 30px;
+}
+
+.image-item {
+  position: relative;
+  width: 148px;
+  height: 148px;
+  border-radius: 4px;
+  overflow: hidden;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  transition: all 0.3s;
+}
+
+.image-item:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
+}
+
+.image-item-hover:hover .image-actions {
+  opacity: 1;
+}
+
+.cert-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  cursor: pointer;
+}
+
+.image-actions {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(0, 0, 0, .5);
+  gap: 10px;
+  opacity: 0;
+  transition: opacity 0.3s;
+}
+
+.image-actions-loging {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(0, 0, 0, .5);
+  color: #fff;
+}
+
+.submit-area {
+  text-align: center;
+}
+</style>

+ 14 - 4
fhKeeper/formulahousekeeper/course-pc/src/views/lecturerManagement/index.vue

@@ -221,13 +221,18 @@ export default {
                 cancelButtonText: '取消',
                 type: 'warning'
             }).then(() => {
-                this.http.post('/course-teacher/delete', { id: row.id }, res => {
-                    if (res.data.code === 'ok') {
+                this.http.post('/course-teacher/deleteBatch', { ids: row.id }, res => {
+                    if (res.code === 'ok') {
                         this.$message({
                             type: 'success',
                             message: '删除成功!'
                         });
                         this.getList();
+                    } else {
+                        this.$message({
+                            type: 'warning',
+                            message: res.msg
+                        });
                     }
                 });
             })
@@ -245,14 +250,19 @@ export default {
                 cancelButtonText: '取消',
                 type: 'warning'
             }).then(() => {
-                this.http.post('/course-teacher/batchDelete', { ids: this.selectedIds }, res => {
-                    if (res.data.code === 'ok') {
+                this.http.post('/course-teacher/deleteBatch', { ids: this.selectedIds.join(',') }, res => {
+                    if (res.code === 'ok') {
                         this.$message({
                             type: 'success',
                             message: '批量删除成功!'
                         });
                         this.getList();
                         this.selectedIds = [];
+                    } else {
+                        this.$message({
+                            type: 'warning',
+                            message: res.msg
+                        });
                     }
                 })
             })

+ 569 - 0
fhKeeper/formulahousekeeper/course-pc/src/views/offlineRegistration/registration.vue

@@ -0,0 +1,569 @@
+<template>
+  <section>
+    <!-- 工具条 -->
+    <el-col :span="24" class="toolbar" style="padding-bottom: 0px;">
+      <el-tabs v-model="activeTab" @tab-click="handleClick">
+        <el-tab-pane label="预报名信息" name="pre-registration"></el-tab-pane>
+        <el-tab-pane label="报名课程" name="course-registration">
+          <div class="registerForCourses">
+            <el-form :inline="true" @submit.native.prevent>
+              <el-form-item label="姓名">
+                <el-input v-model="listQuery.name" placeholder="请输入姓名" clearable @change="handleFilter"
+                  size="small"></el-input>
+              </el-form-item>
+              <el-form-item label="手机号">
+                <el-input v-model="listQuery.phone" placeholder="请输入手机号" clearable @change="handleFilter"
+                  size="small"></el-input>
+              </el-form-item>
+            </el-form>
+
+            <div>
+              <el-button type="primary" size="small" @click="showAddDialog">添加报名</el-button>
+              <el-button type="primary" size="small" @click="batchManage">线下课程管理</el-button>
+            </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </el-col>
+
+    <!-- 预报名列表 -->
+    <transition name="fade" mode="out-in">
+      <el-table v-if="activeTab === 'pre-registration'" :data="preRegistrationList" highlight-current-row
+        v-loading="listLoading" :height="tableHeight" style="width: 100%;">
+        <el-table-column label="序号" prop="id" width="80" align="center"></el-table-column>
+        <el-table-column label="姓名" prop="name" align="center"></el-table-column>
+        <el-table-column label="手机号" prop="phone" align="center"></el-table-column>
+        <el-table-column label="操作" width="180" align="center" fixed="right">
+          <template slot-scope="scope">
+            <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </transition>
+
+    <!-- 报名课程列表 -->
+    <transition name="fade" mode="out-in">
+      <div>
+        <el-table v-if="activeTab === 'course-registration'" :data="courseRegistrationList" highlight-current-row
+          v-loading="listLoading" :height="tableHeight - 58" style="width: 100%;">
+          <el-table-column label="序号" prop="id" width="80" align="center"></el-table-column>
+          <el-table-column label="姓名" prop="name" width="140" align="center"></el-table-column>
+          <el-table-column label="手机号" prop="phone" width="200" align="center"></el-table-column>
+          <el-table-column label="报名课程" prop="course" align="center"></el-table-column>
+          <el-table-column label="操作" width="260" align="center" fixed="right">
+            <template slot-scope="scope">
+              <el-button size="small" type="primary" @click="editCourseRegistration(scope.row)">编辑</el-button>
+              <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+              <el-button size="small" type="primary" @click="openExam(scope.row)">开通考试</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </transition>
+
+    <!-- 添加报名弹窗 -->
+    <el-dialog title="线下研修班报名" :visible.sync="dialogVisible" width="30%">
+      <el-form :model="registrationForm" label-width="80px">
+        <el-form-item label="姓名">
+          <el-input v-model.trim="registrationForm.name" placeholder="请输入姓名"></el-input>
+        </el-form-item>
+        <el-form-item label="手机">
+          <el-input v-model.trim="registrationForm.phone" placeholder="请输入姓名"></el-input>
+        </el-form-item>
+        <el-form-item label="报名课程">
+          <el-select v-model="registrationForm.course" multiple placeholder="请选择课程" style="width: 100%" clearable>
+            <el-option v-for="item in categoryList" :key="item.value" :label="item.label" :value="item.value">
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitRegistration">确定</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 分页 -->
+    <el-col :span="24" class="toolbar">
+      <el-pagination :current-page="pageIndex" :page-size="pageSize" :total="total"
+        layout="total, prev, pager, next, sizes" @current-change="handleCurrentChange" @size-change="handleSizeChange"
+        style="float:right;"></el-pagination>
+    </el-col>
+
+    <!-- 课程管理 -->
+    <el-dialog :visible.sync="categoryManageVisible" title="线下课程管理" width="600px" :before-close="handleClose">
+      <el-table :data="categoryList" style="width: 100%" max-height="400">
+        <el-table-column prop="label" label="课程名称" width="180"></el-table-column>
+        <el-table-column label="封面" width="180">
+          <template slot-scope="scope">
+            <img v-if="scope.row.coverImage" :src="scope.row.coverImage" class="category-cover-image" />
+            <span v-else>无封面</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="180">
+          <template slot-scope="scope">
+            <el-button size="mini" type="primary" @click="setCategoryCover(scope.$index, scope.row)">设置封面</el-button>
+            <el-button size="mini" type="danger" @click="deleteCategory(scope.$index, scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div style="margin-top: 20px;">
+        <el-form :inline="true" :model="newCategory" class="demo-form-inline">
+          <el-form-item label="课程名称">
+            <el-input v-model="newCategory.label" placeholder="请输入课程名称"></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="addCategory">添加</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <span slot="footer" class="dialog-dialog">
+        <el-button @click="categoryManageVisible = false">关 闭</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 设置课程封面 -->
+    <el-dialog :visible.sync="coverDialogVisible" title="设置课程封面" width="500px" :before-close="handleClose">
+      <div class="cover-upload-container">
+        <el-upload class="cover-uploader" action="#" :show-file-list="false" :on-change="handleCoverChange"
+          :auto-upload="false" :before-upload="beforeCoverUpload">
+          <img v-if="coverImageUrl" :src="coverImageUrl" class="cover-image" />
+          <i v-else class="el-icon-plus cover-uploader-icon"></i>
+        </el-upload>
+        <div class="cover-tip">请上传课程封面图片,建议尺寸 16:9</div>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="coverDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="saveCategoryCover">确 定</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 开通考试 -->
+    <el-dialog title="开通考试" :visible.sync="openExamDialogVisible" width="600px" top="6.5vh" :before-close="handleClose">
+      <div style="width: 100%;">
+        <el-select v-model="examRow.openExamVal" multiple placeholder="请选择" style="width: 100%;">
+          <el-option v-for="item in (examRow.offlineSignList || [])" :key="item.id" :label="item.courseOfflineName"
+            :value="item.id">
+          </el-option>
+        </el-select>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="openExamDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="openExamCli()">确 定</el-button>
+      </span>
+    </el-dialog>
+  </section>
+</template>
+
+<script>
+import { post, checkAndAddUpload } from '../../api'
+export default {
+  name: 'OfflineRegistration',
+  data() {
+    return {
+      activeTab: 'pre-registration',
+      listQuery: {
+        name: '',
+        phone: ''
+      },
+      pageIndex: 1,
+      pageSize: 20,
+      total: 0,
+      listLoading: false,
+      tableHeight: 0,
+      preRegistrationList: [],
+      courseRegistrationList: [],
+      dialogVisible: false,
+      registrationForm: {
+        name: '',
+        phone: '',
+        course: ''
+      },
+      courseOptions: [
+        { value: 'Vue高级课程', label: 'Vue高级课程' },
+        { value: 'React入门', label: 'React入门' },
+        { value: 'JavaScript基础', label: 'JavaScript基础' },
+        { value: 'Node.js实战', label: 'Node.js实战' }
+      ],
+      // 课程管理相关
+      categoryManageVisible: false,
+      categoryList: [],
+      newCategory: {
+        label: '',
+        value: '',
+        coverImage: ''
+      },
+      // 课程封面设置
+      coverDialogVisible: false,
+      currentCategoryIndex: -1,
+      coverImageUrl: '',
+      coverImageFile: null,
+
+      // 开通考试
+      openExamDialogVisible: false,
+      examRow: {},
+    }
+  },
+  created() {
+    let height = window.innerHeight
+    this.tableHeight = height - 195
+    const that = this
+    window.onresize = function temp() {
+      that.tableHeight = window.innerHeight - 195
+      console.log(that.tableHeight, '<==== that.tableHeight')
+    }
+  },
+  methods: {
+    handleClick() {
+      this.getList()
+    },
+    getList() {
+      this.listLoading = true
+      if (this.activeTab === 'pre-registration') {
+        // 预报名
+        post(`/user-offline-sign-up/signPageList`, {
+          page: this.pageIndex,
+          size: this.pageSize,
+        }).then((res) => {
+          const { records = [], total = 0 } = res.data
+          this.preRegistrationList = records
+          this.total = total
+        }).finally(() => {
+          this.listLoading = false
+        })
+      }
+
+      // 报名课程
+      if (this.activeTab === 'course-registration') {
+        post(`/course-offline-sign/userSignPageList`, {
+          page: this.pageIndex,
+          size: this.pageSize,
+          ...this.listQuery
+        }).then((res) => {
+          const { records = [], total = 0 } = res.data
+          this.courseRegistrationList = records.map((item) => {
+            return {
+              ...item,
+              course: item.offlineSignList.map(ua => ua.courseOfflineName).join(','),
+              courseIds: item.offlineSignList.map(ua => ua.id).join(','),
+              courseIdList: item.offlineSignList.map(ua => ua.id)
+            }
+          })
+          this.total = total
+        }).finally(() => {
+          this.listLoading = false
+        })
+      }
+    },
+    handleFilter() {
+      this.pageIndex = 1
+      this.getList()
+    },
+    handleCurrentChange(val) {
+      this.pageIndex = val
+      this.getList()
+    },
+    handleSizeChange(val) {
+      this.pageIndex = 1
+      this.pageSize = val
+      this.getList()
+    },
+    editCourseRegistration(row) {
+      this.dialogVisible = true
+      this.registrationForm = {
+        id: row.id,
+        name: row.name,
+        phone: row.phone,
+        course: row.courseIdList
+      }
+    },
+    handleDelete(row) {
+      const url = this.activeTab == 'pre-registration' ? '/user-offline-sign-up/deleteSignUp' : '/course-offline-sign/deleteUserSign'
+      const text = `确认删除【${row.name}】吗?`
+      const prompt = this.activeTab == 'pre-registration' ? '删除预报名信息人员' : '删除报名课程人员'
+      this.$confirm(text, prompt, {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        post(url, { id: row.id }).then(() => {
+          this.$message({
+            type: 'success',
+            message: '删除成功!'
+          });
+          this.getList()
+        })
+      });
+    },
+    showAddDialog() {
+      this.registrationForm = {
+        name: '',
+        phone: '',
+        course: []
+      }
+      this.dialogVisible = true
+    },
+    submitRegistration() {
+      // 这里添加提交逻辑
+      console.log(this.registrationForm, '<==== registrationForm')
+      this.$message({
+        type: 'success',
+        message: '报名成功!'
+      });
+      this.dialogVisible = false;
+    },
+    // 课程管理
+    batchManage(flag = true) {
+      if (flag) {
+        this.categoryManageVisible = true;
+      }
+      // 加载课程数据
+      this.http.post('/course-offline/list', {}, res => {
+        if (res.code == "ok") {
+          // 将后端返回的数据转换为前端需要的格式
+          this.categoryList = res.data.map(item => ({
+            label: item.courseName,
+            value: item.id,
+            coverImage: checkAndAddUpload(item.coverImage || '')
+          }));
+
+          // 同步更新下拉选项
+          this.courseOptions = [...this.categoryList];
+        } else {
+          this.$message({
+            message: res.msg || '获取课程列表失败',
+            type: 'error'
+          });
+        }
+      }, error => {
+        this.$message({
+          message: error || '获取课程列表失败',
+          type: 'error'
+        });
+      });
+    },
+    // 设置课程封面
+    setCategoryCover(index, row) {
+      this.currentCategoryIndex = index;
+      this.coverImageUrl = row.coverImage || '';
+      this.coverDialogVisible = true;
+    },
+    // 处理封面图片变更
+    handleCoverChange(file) {
+      this.coverImageFile = file.raw;
+      if (this.coverImageFile) {
+        this.coverImageUrl = URL.createObjectURL(this.coverImageFile);
+
+        // 获取当前课程的ID
+        if (this.currentCategoryIndex >= 0) {
+          const categoryId = this.categoryList[this.currentCategoryIndex].value;
+
+          // 立即上传封面图片
+          this.listLoading = true;
+
+          // 创建FormData对象用于上传文件
+          const formData = new FormData();
+          formData.append('id', categoryId);
+          formData.append('coverImage', this.coverImageFile);
+
+          // 调用上传图片的API
+          this.http.uploadFile('/course-offline/uploadCover', formData, res => {
+            this.listLoading = false;
+            if (res.code == "ok") {
+              // 上传成功后,获取返回的图片URL
+              const imageUrl = res.data && res.data.url ? res.data.url : this.coverImageUrl;
+
+              // 更新本地数据
+              this.categoryList[this.currentCategoryIndex].coverImage = imageUrl;
+
+              // 同步更新到课程选项中
+              const optionIndex = this.courseOptions.findIndex(item => item.value === categoryId);
+              if (optionIndex !== -1) {
+                this.courseOptions[optionIndex].coverImage = imageUrl;
+              }
+
+              this.$message({
+                type: 'success',
+                message: '封面图片上传成功!'
+              });
+            } else {
+              this.$message({
+                message: res.msg || '上传封面图片失败',
+                type: 'error'
+              });
+            }
+          }, error => {
+            this.listLoading = false;
+            this.$message({
+              message: error || '上传封面图片失败',
+              type: 'error'
+            });
+          });
+        }
+      }
+    },
+    // 处理封面图片上传前的验证
+    beforeCoverUpload(file) {
+      const isImage = file.type.indexOf('image/') === 0;
+      const isLt2M = file.size / 1024 / 1024 < 2;
+
+      if (!isImage) {
+        this.$message.error('上传封面图片只能是图片格式!');
+      }
+      if (!isLt2M) {
+        this.$message.error('上传封面图片大小不能超过 2MB!');
+      }
+
+      return isImage && isLt2M;
+    },
+    // 保存课程封面
+    saveCategoryCover() {
+      if (!this.coverImageUrl) {
+        this.$message.warning('请先上传封面图片!');
+        return;
+      }
+
+      // 关闭对话框
+      this.coverDialogVisible = false;
+      this.$message({
+        type: 'success',
+        message: '设置封面成功!'
+      });
+    },
+    // 添加课程
+    addCategory() {
+      if (!this.newCategory.label || !this.newCategory.label.trim()) {
+        this.$message({
+          type: 'warning',
+          message: '请输入课程名称!'
+        });
+        return;
+      }
+
+      // 调用后端API保存课程课程
+      this.http.post('/course-offline/saveOrUpdate', {
+        courseName: this.newCategory.label
+      }, res => {
+        if (res.code == "ok") {
+          // 生成唯一ID作为value,实际项目中应该使用后端返回的ID
+          const categoryId = res.data && res.data.id ? res.data.id : 'category_' + Date.now();
+          this.newCategory.value = categoryId;
+
+          // 添加到课程列表
+          this.categoryList.push({ ...this.newCategory });
+          this.courseOptions.push({ ...this.newCategory });
+
+          // 清空输入
+          this.newCategory.label = '';
+          this.newCategory.value = '';
+
+          this.$message({
+            type: 'success',
+            message: '添加课程成功!'
+          });
+        } else {
+          this.$message({
+            message: res.msg || '添加课程失败',
+            type: 'error'
+          });
+        }
+      }, error => {
+        this.$message({
+          message: error || '添加课程失败',
+          type: 'error'
+        });
+      });
+    },
+    // 删除课程
+    deleteCategory(index, row) {
+      this.$confirm('确认删除该线下课程?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        // 调用后端API删除课程课程
+        this.http.post('/course-offline/delete', {
+          id: row.value
+        }, res => {
+          if (res.code == "ok") {
+            // 从课程列表中删除
+            this.categoryList.splice(index, 1);
+
+            // 从下拉选项中删除
+            const optionIndex = this.courseOptions.findIndex(item => item.value === row.value);
+            if (optionIndex !== -1) {
+              this.courseOptions.splice(optionIndex, 1);
+            }
+
+            this.$message({
+              type: 'success',
+              message: '删除课程成功!'
+            });
+          } else {
+            this.$message({
+              message: res.msg || '删除课程失败',
+              type: 'error'
+            });
+          }
+        }, error => {
+          this.$message({
+            message: error || '删除课程失败',
+            type: 'error'
+          });
+        });
+      });
+    },
+    // 打开开通考试弹窗
+    openExam(row) {
+      const obj = { ...row, openExamVal: [] }
+      this.$set(this, 'examRow', obj)
+      this.openExamDialogVisible = true;
+    },
+    // 开通考试
+    openExamCli() {
+      const { id, openExamVal = [] } = this.examRow; 
+      console.log(openExamVal, '<==== openExamVal')
+      post(`/course-offline-sign/openCourseExam`, {
+        userSignId: id,
+        courseOfflineId: openExamVal.join(',')
+      }).then(() => {
+        this.$message({
+          type: 'success',
+          message: '开通考试成功'
+        })
+        this.openExamDialogVisible = false;
+        this.getList()
+      })
+    },
+    handleClose(done) {
+      done()
+    }
+  },
+  mounted() {
+    this.getList()
+    this.batchManage(false)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.registerForCourses {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.toolbar {
+  padding-bottom: 10px;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity .3s ease;
+}
+
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>