Explorar o código

合并联盟分支的会议,修改了云药专家的部分

lisy hai 5 meses
pai
achega
7011eb3265

+ 1 - 0
package.json

@@ -48,6 +48,7 @@
     "screenfull": "6.0.2",
     "vue": "3.5.13",
     "vue-cropper": "1.1.1",
+    "vue-draggable-plus": "^0.6.0",
     "vue-i18n": "10.0.5",
     "vue-json-pretty": "2.4.0",
     "vue-qr": "^4.0.9",

+ 33 - 1
src/api/training/index.ts

@@ -1,4 +1,4 @@
-import request, { download, downloadFile } from '@/utils/request';
+import request, { downloadFile } from '@/utils/request';
 import { AxiosPromise } from 'axios';
 // 查询培训列表
 export const trainingList = (query?: any): AxiosPromise => {
@@ -75,3 +75,35 @@ export const trainingMembers = (params: any): AxiosPromise => {
         params
     });
 };
+// 按搜索结果导出参会人员
+export const exportTrainingMembers = (params: any): AxiosPromise => {
+    return downloadFile({
+        url: `/dgtmedicine/trainingSignup/download`,
+        method: 'get',
+        params,
+        responseType: 'blob'
+    });
+};
+// 报名审批统计
+export const signupCount = (id): AxiosPromise => {
+    return request({
+        url: `/dgtmedicine/trainingSignup/signupCount?trainingId=${id}`,
+        method: 'get'
+    });
+};
+// 报名审核
+export const signupApproval = (data: any): AxiosPromise => {
+    return request({
+        url: '/dgtmedicine/trainingSignup/signupApproval',
+        method: 'post',
+        data
+    });
+};
+// 开关会议临时状态
+export const offOrNoTemp = (params: any): AxiosPromise => {
+    return request({
+        url: `/dgtmedicine/training/offOrNoTemp`,
+        method: 'get',
+        params
+    });
+};

BIN=BIN
src/assets/images/shanchu.png


+ 10 - 0
src/assets/styles/index.scss

@@ -209,3 +209,13 @@ aside {
     margin-bottom: 10px;
   }
 }
+.custom-message-box .el-message-box__btns {
+  display: flex;
+  flex-direction: row-reverse !important;
+  justify-content: flex-start;
+}
+/* 调整按钮间距(可选) */
+.custom-message-box .el-message-box__btns button {
+  margin-left: 0;
+  margin-right: 10px;
+}

+ 178 - 168
src/components/FileUpload/index.vue

@@ -52,22 +52,22 @@ import { fileExt } from '@/utils/ruoyi';
 import { ImageViewer } from '@/views/models';
 
 const props = defineProps({
-  modelValue: [String, Object, Array],
-  // 数量限制
-  limit: propTypes.number.def(1),
-  // 大小限制(MB)
-  fileSize: propTypes.number.def(5),
-  // 文件类型, 例如['png', 'jpg', 'jpeg', 'bmp']
-  fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']),
-  // 是否显示提示
-  isShowTip: propTypes.bool.def(true),
-  tipText: propTypes.string.def(''),
-  span: propTypes.number.def(24),
-  multiple: propTypes.bool.def(true),
-  white: propTypes.bool.def(false),
-  // 上传数据格式
-  format: propTypes.string.def('id'),
-  btnText: propTypes.string.def('上传文件')
+    modelValue: [String, Object, Array],
+    // 数量限制
+    limit: propTypes.number.def(1),
+    // 大小限制(MB)
+    fileSize: propTypes.number.def(5),
+    // 文件类型, 例如['png', 'jpg', 'jpeg', 'bmp']
+    fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf', 'xlsx', 'docx', 'pptx', 'DOCX', 'DOC', 'PPTX', 'PPT']),
+    // 是否显示提示
+    isShowTip: propTypes.bool.def(true),
+    tipText: propTypes.string.def(''),
+    span: propTypes.number.def(24),
+    multiple: propTypes.bool.def(true),
+    white: propTypes.bool.def(false),
+    // 上传数据格式
+    format: propTypes.string.def('id'),
+    btnText: propTypes.string.def('上传文件')
 });
 const loading = ref(false);
 const lookIndex = ref(0);
@@ -86,208 +86,218 @@ const showTip = computed(() => props.isShowTip && (props.fileType || props.fileS
 
 const fileUploadRef = ref<ElUploadInstance>();
 const progressList = ref<any>([]);
+const formatValue = ref(props.format);
 watch(
-  () => props.modelValue,
-  async (val: any) => {
-      if (val) {
-          let temp = 1;
-          // 首先将值转为数组
-          let list = [];
-           if (props.format === 'array' && Array.isArray(val)) {
-              list = val;
-           } else if (props.format === 'object') {
-              list = [{
-                    name: val.fileName,
-                    url: val.url,
-                    fileSize: val.fileSize,
-                    fileType: val.fileType,
-                    ossId: val.ossId
-              }];
-          } else {
-              const res = await listByIds(val as string);
-              list = res.data.map((oss) => {
-                  const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
-                  return data;
-              });
-          }
-          // 然后将数组转为对象数组
-          fileList.value = list.map((item) => {
-              item = { name: item.fileName ? item.fileName : item.name, url: item.url, fileSize: item.fileSize, fileType: item.fileType };
-              item.uid = item.uid || new Date().getTime() + temp++;
-              return item;
-          });
-      } else {
-          fileList.value = [];
-          return [];
-      }
-  },
-  { deep: true, immediate: true }
+    () => props.modelValue,
+    async (val: any) => {
+        if (val) {
+            let temp = 1;
+            // 首先将值转为数组
+            let list = [];
+            if (props.format === 'array' || Array.isArray(val)) {
+                formatValue.value = 'array';
+                list = val;
+            } else if (props.format === 'object' || typeof val === 'object') {
+                formatValue.value = 'object';
+                list = [
+                    {
+                        name: val.fileName,
+                        url: val.url,
+                        fileSize: val.fileSize,
+                        fileType: val.fileType,
+                        ossId: val.ossId
+                    }
+                ];
+            } else {
+                // 是否http开头
+                formatValue.value = 'id';
+                const res = await listByIds(val as string);
+                list = res.data.map((oss) => {
+                    const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
+                    return data;
+                });
+            }
+            // 然后将数组转为对象数组
+            fileList.value = list.map((item) => {
+                item = { name: item.fileName ? item.fileName : item.name, url: item.url, fileSize: item.fileSize, fileType: item.fileType };
+                item.uid = item.uid || new Date().getTime() + temp++;
+                return item;
+            });
+        } else {
+            fileList.value = [];
+            return [];
+        }
+    },
+    { deep: true, immediate: true }
 );
 
 // 上传前校检格式和大小
 const handleBeforeUpload = (file: any) => {
-  // 校检文件类型
-  if (props.fileType.length) {
-      loading.value = true;
-      const fileName = file.name.split('.');
-      const fileExt = fileName[fileName.length - 1];
-      const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
-      if (!isTypeOk) {
-          proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`);
-          loading.value = false;
-          return false;
-      }
-  }
-  // 校检文件大小
-  if (props.fileSize) {
-      const isLt = file.size / 1024 / 1024 < props.fileSize;
-      if (!isLt) {
-          loading.value = false;
-          proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
-          return false;
-      }
-  }
-  number.value++;
-  return true;
+    // 校检文件类型
+    if (props.fileType.length) {
+        loading.value = true;
+        const fileName = file.name.split('.');
+        const fileExt = fileName[fileName.length - 1];
+        const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
+        if (!isTypeOk) {
+            proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`);
+            loading.value = false;
+            return false;
+        }
+    }
+    // 校检文件大小
+    if (props.fileSize) {
+        const isLt = file.size / 1024 / 1024 < props.fileSize;
+        if (!isLt) {
+            loading.value = false;
+            proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
+            return false;
+        }
+    }
+    number.value++;
+    return true;
 };
 
 // 文件个数超出
 const handleExceed = () => {
-  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
+    proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
 };
 
 // 上传失败
 const handleUploadError = () => {
-  loading.value = false;
-  proxy?.$modal.msgError('上传文件失败');
+    loading.value = false;
+    proxy?.$modal.msgError('上传文件失败');
 };
 
 // 上传成功回调
 const handleUploadSuccess = (res: any, file: UploadFile) => {
-  loading.value = false;
-  if (res.code === 200) {
-      uploadList.value.push({
-          name: res.data.fileName,
-          url: res.data.url,
-          ossId: res.data.ossId,
-          fileSize: file?.raw.size,
-          fileType: file?.raw.type
-      });
-      uploadedSuccessfully();
-  } else {
-      number.value--;
-      proxy?.$modal.msgError(res.msg);
-      fileUploadRef.value?.handleRemove(file);
-      uploadedSuccessfully();
-  }
+    loading.value = false;
+    if (res.code === 200) {
+        uploadList.value.push({
+            name: res.data.fileName,
+            url: res.data.url,
+            ossId: res.data.ossId,
+            fileSize: file?.raw.size,
+            fileType: file?.raw.type
+        });
+        uploadedSuccessfully();
+    } else {
+        number.value--;
+        proxy?.$modal.msgError(res.msg);
+        fileUploadRef.value?.handleRemove(file);
+        uploadedSuccessfully();
+    }
 };
 
 // 上传结束处理
 const uploadedSuccessfully = () => {
-  if (number.value > 0 && uploadList.value.length === number.value) {
-      fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
-      uploadList.value = [];
-      number.value = 0;
-      const valuef = fileList.value.map(({ name, url, fileType, fileSize, ossId }) => ({ fileName: name, url, fileSize, fileType, ossId }));
-      if (props.format === 'id') {
-         emit('change', listToString(valuef));
-         emit('update:modelValue', listToString(valuef));
-      } else if (props.format === 'object') {
-          emit('change', valuef[0])
-          emit('update:modelValue', valuef[0]);
-      } else {
-          emit('change', valuef)
-          emit('update:modelValue', valuef);
-      }
-  }
+    if (number.value > 0 && uploadList.value.length === number.value) {
+        fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
+        uploadList.value = [];
+        number.value = 0;
+        const valuef = fileList.value.map(({ name, url, fileType, fileSize, ossId }) => ({ fileName: name, url, fileSize, fileType, ossId }));
+        if (formatValue.value === 'id') {
+            emit('change', listToString(valuef));
+            emit('update:modelValue', listToString(valuef));
+        } else if (formatValue.value === 'object') {
+            emit('change', valuef[0]);
+            emit('update:modelValue', valuef[0]);
+        } else {
+            emit('change', valuef);
+            emit('update:modelValue', valuef);
+        }
+    }
 };
 const handleOnProgress = (event: any, file: any, list: any) => {
-  const progress = Math.round((event.loaded / event.total) * 100);
-  fileList.value = [...list];
+    const progress = Math.round((event.loaded / event.total) * 100);
+    fileList.value = [...list];
 };
 // 删除文件
 const handleDelete = (index: number) => {
-  fileUploadRef.value?.abort(fileList.value[index]);
-  fileList.value.splice(index, 1);
-  const valuef = fileList.value.map(({ name, url, fileType, fileSize }) => ({ fileName: name, url, fileSize, fileType }));
-  if (props.format === 'object') {
-      emit('change', null)
-      emit('update:modelValue', null);
-  } else {
-      emit('change', valuef)
-      emit('update:modelValue', valuef);
-  }
-  loading.value = false;
+    fileUploadRef.value?.abort(fileList.value[index]);
+    fileList.value.splice(index, 1);
+    const valuef = fileList.value.map(({ name, url, fileType, fileSize }) => ({ fileName: name, url, fileSize, fileType }));
+    if (formatValue.value === 'id') {
+        emit('change', listToString(valuef));
+        emit('update:modelValue', listToString(valuef));
+    } else if (formatValue.value === 'object') {
+        emit('change', null);
+        emit('update:modelValue', null);
+    } else {
+        emit('change', valuef);
+        emit('update:modelValue', valuef);
+    }
+    loading.value = false;
 };
 // 获取文件名称
 const getFileName = (name: string) => {
-  // 如果是url那么取最后的名字 如果不是直接返回
-  if (name.lastIndexOf('/') > -1) {
-      return name.slice(name.lastIndexOf('/') + 1);
-  } else {
-      return name;
-  }
+    // 如果是url那么取最后的名字 如果不是直接返回
+    if (name.lastIndexOf('/') > -1) {
+        return name.slice(name.lastIndexOf('/') + 1);
+    } else {
+        return name;
+    }
 };
 
 // 对象转成指定字符串分隔
 const listToString = (list: any[], separator?: string) => {
-  let strs = '';
-  separator = separator || ',';
-  list.forEach((item) => {
-      if (item.ossId) {
-          strs += item.ossId + separator;
-      }
-  });
-  return strs != '' ? strs.substring(0, strs.length - 1) : '';
+    let strs = '';
+    separator = separator || ',';
+    list.forEach((item) => {
+        if (item.ossId) {
+            strs += item.ossId + separator;
+        }
+    });
+    return strs != '' ? strs.substring(0, strs.length - 1) : '';
 };
 </script>
 
 <style scoped lang="scss">
 .btn-file {
-  width: 90px;
-  height: 40px;
-  line-height: 40px;
-  border-radius: 8px;
-  color: #666;
-  text-align: center;
-  background-color: #f7f7f7;
+    width: 90px;
+    height: 40px;
+    line-height: 40px;
+    border-radius: 8px;
+    color: #666;
+    text-align: center;
+    background-color: #f7f7f7;
 }
 .upload-list-img {
-  background-color: #f7f7f7;
-  border-radius: 8px;
+    background-color: #f7f7f7;
+    border-radius: 8px;
 
-  &.mt10 {
-      margin-top: 10px;
-  }
+    &.mt10 {
+        margin-top: 10px;
+    }
 }
 .right-item {
-  padding: 10px 16px;
-  cursor: pointer;
-  &:hover {
-      color: var(--el-color-primary);
-  }
+    padding: 10px 16px;
+    cursor: pointer;
+    &:hover {
+        color: var(--el-color-primary);
+    }
 }
 .delete-item {
-  width: 40px;
-  cursor: pointer;
+    width: 40px;
+    cursor: pointer;
 }
 .upload-file-list.white {
-  .btn-file {
-      background-color: #fff;
-  }
-  .upload-list-img {
-      background-color: #fff;
-  }
+    .btn-file {
+        background-color: #fff;
+    }
+    .upload-list-img {
+        background-color: #fff;
+    }
 }
 .right-wrap {
-  padding: 6px 10px;
-  box-sizing: border-box;
+    padding: 6px 10px;
+    box-sizing: border-box;
 }
 .progress {
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  height: 1px;
-  background-color: var(--el-color-primary);
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    height: 1px;
+    background-color: var(--el-color-primary);
 }
 </style>

+ 36 - 28
src/components/ImageUpload/index.vue

@@ -1,28 +1,32 @@
 <template>
-    <div class="component-upload-image">
-        <el-upload ref="imageUpload" multiple :action="uploadImgUrl" list-type="picture-card" :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" :limit="limit" :on-error="handleUploadError" :on-exceed="handleExceed" :before-remove="handleDelete" :show-file-list="true" :headers="headers" :file-list="fileList" :on-preview="handlePictureCardPreview" :class="{ hide: fileList.length >= limit }">
-            <el-icon class="avatar-uploader-icon">
-                <plus />
-            </el-icon>
-        </el-upload>
-        <!-- 上传提示 -->
-        <div v-if="showTip" class="el-upload__tip">
-            请上传
-            <template v-if="fileSize">
-                大小不超过
-                <b style="color: #f56c6c">{{ fileSize }}MB</b>
-            </template>
-            <template v-if="fileType">
-                格式为
-                <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
-            </template>
-            的文件
-        </div>
-
-        <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
-            <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
-        </el-dialog>
+  <div class="component-upload-image">
+    <el-upload ref="imageUpload" multiple :action="uploadImgUrl" list-type="picture-card"
+      :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" :limit="limit" :on-error="handleUploadError"
+      :on-exceed="handleExceed" :before-remove="handleDelete" :show-file-list="true" :headers="headers"
+      :file-list="fileList" :on-preview="handlePictureCardPreview" :class="{ hide: fileList.length >= limit }">
+      <el-icon class="avatar-uploader-icon">
+        <plus />
+        <div v-if="isShowPrompt" class="f-s-14 d-flex w-90 c-primary" style="position: absolute;top:80%">点击上传图片</div>
+      </el-icon>
+    </el-upload>
+    <!-- 上传提示 -->
+    <div v-if="showTip" class="el-upload__tip">
+      请上传
+      <template v-if="fileSize">
+        大小不超过
+        <b style="color: #f56c6c">{{ fileSize }}MB</b>
+      </template>
+      <template v-if="fileType">
+        格式为
+        <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
+      </template>
+      的文件
     </div>
+
+    <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
+      <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
+    </el-dialog>
+  </div>
 </template>
 
 <script setup lang="ts">
@@ -50,6 +54,10 @@ const props = defineProps({
     type: Boolean,
     default: true
   },
+  isShowPrompt: {
+    type: Boolean,
+    default: false
+  },
   // 是否支持压缩,默认否
   compressSupport: {
     type: Boolean,
@@ -92,11 +100,11 @@ watch(
           list = val as OssVO[];
         }
       } else if (val.startsWith('http')) {
-        if (val.split(',').length > 0){
+        if (val.split(',').length > 0) {
           val.split(',').forEach((item) => {
-          list.push({ url: item });
-        });
-        }else {
+            list.push({ url: item });
+          });
+        } else {
           list = [{
             url: val
           }];
@@ -196,7 +204,7 @@ const handleDelete = (file: UploadFile): boolean => {
 
   if (props.isString) {
     var findex = fileList.value.map((f) => f.url).indexOf(file.url);
-  }else {
+  } else {
     var findex = fileList.value.map((f) => f.name).indexOf(file.name);
   }
   if (findex > -1 && uploadList.value.length === number.value) {

+ 5 - 1
src/views/authority/authority-input/index.vue

@@ -142,7 +142,11 @@
                                     <FileUpload v-model="form.attachment" format="array" :limit="10" :fileSize="100"></FileUpload>
                                 </el-form-item>
                             </el-col>
-
+                            <el-col :span="12">
+                                <el-form-item label="图片" prop="images">
+                                    <imageUpload v-model="form.images" :limit="50" isString :isShowTip="false"></imageUpload>
+                                </el-form-item>
+                            </el-col>
                             <el-col :span="24">
                                 <el-form-item label="简介" prop="brief">
                                     <div class="flex1">

+ 1 - 1
src/views/authority/exportdetail/index.vue

@@ -2,7 +2,7 @@
     <div class="p-3">
         <div class="bg-fff flex1 ov-hd d-flex flex-cln">
             <div class="d-flex a-c pd-16">
-                <div class="f-s-20 c-333 f-w-7 mr-10">订单详情</div>
+                <div class="f-s-20 c-333 f-w-7 mr-10">专家信息详情</div>
                 <el-button @click="router.go(-1)" type="primary" text>
                     <el-icon>
                         <Back />

+ 26 - 17
src/views/authority/models/ExpertInfo.vue

@@ -1,7 +1,7 @@
 <template>
     <div class="pl-30 pt-30 pb-30">
         <div class="info-out">
-            <img src="https://fileserver.yujin.shuziyunyao.com/oss-file/smart-trace/2025/06/19/69b2b57f30bf4174983632ec7cf962de.png" alt="" class="av-img" />
+            <img :src="props.info?.avatar" alt="" class="av-img" />
             <div class="info">
                 <div>{{ props.info?.name }}</div>
                 <div>
@@ -15,30 +15,39 @@
         <div class="flex-container">
             <el-descriptions :column="4">
                 <el-descriptions-item label="性别:">{{ selectDictLabel(sys_sex_type, props.info?.sex) || '-' }}</el-descriptions-item>
-                <el-descriptions-item label="民族:">{{ props.info?.nation }}</el-descriptions-item>
-                <el-descriptions-item label="身份证号:">{{ props.info?.idCard }}</el-descriptions-item>
-                <el-descriptions-item label="政治面貌:">{{ props.info?.politicalStatus }}</el-descriptions-item>
+                <el-descriptions-item label="民族:">{{ props.info?.nation || '-'}}</el-descriptions-item>
+                <el-descriptions-item label="身份证号:">{{ props.info?.idCard || '-'}}</el-descriptions-item>
+                <el-descriptions-item label="政治面貌:">{{ props.info?.politicalStatus|| '-' }}</el-descriptions-item>
                 <el-descriptions-item label="最高学历:">{{ selectDictLabel(dm_educational_type,props.info?.educationalBg )|| '-' }}</el-descriptions-item>
-                <el-descriptions-item label="所学专业:">{{ props.info?.major }}</el-descriptions-item>
-                <el-descriptions-item label="从事专业:">{{ props.info?.job }}</el-descriptions-item>
-                <el-descriptions-item label="通讯地址:">{{ props.info?.address }}</el-descriptions-item>
-                <el-descriptions-item label="联系电话:">{{ props.info?.phone }}</el-descriptions-item>
-                <el-descriptions-item label="传真电话:">{{ props.info?.faxPhone }}</el-descriptions-item>
-                <el-descriptions-item label="邮箱:">{{ props.info?.email }}</el-descriptions-item>
-                <el-descriptions-item label="微信号:">{{ props.info?.wxNum }}</el-descriptions-item>
+                <el-descriptions-item label="所学专业:">{{ props.info?.major|| '-' }}</el-descriptions-item>
+                <el-descriptions-item label="从事专业:">{{ props.info?.job|| '-' }}</el-descriptions-item>
+                <el-descriptions-item label="通讯地址:">{{ props.info?.address|| '-' }}</el-descriptions-item>
+                <el-descriptions-item label="联系电话:">{{ props.info?.phone || '-'}}</el-descriptions-item>
+                <el-descriptions-item label="传真电话:">{{ props.info?.faxPhone || '-'}}</el-descriptions-item>
+                <el-descriptions-item label="邮箱:">{{ props.info?.email || '-'}}</el-descriptions-item>
+                <el-descriptions-item label="微信号:">{{ props.info?.wxNum || '-'}}</el-descriptions-item>
                 <el-descriptions-item label="岗位状态:">{{ selectDictLabel(dm_position_status,props.info?.status) || '-' }}</el-descriptions-item>
-                <el-descriptions-item label="创建人:">{{ props.info?.createByName }}</el-descriptions-item>
-                <el-descriptions-item label="创建时间:">{{ props.info?.createTime }}</el-descriptions-item>
+                <el-descriptions-item label="创建人:">{{ props.info?.createByName|| '-' }}</el-descriptions-item>
+                <el-descriptions-item label="创建时间:">{{ props.info?.createTime || '-'}}</el-descriptions-item>
             </el-descriptions>
         </div>
         <div class="jianjie">
+            <div class="f-s-14 c-#303133 mb-10">简介:</div>
             <div v-html="props.info?.brief"></div>
         </div>
 
         <div class="pt-30">
-            <div v-if="props.info?.attachments" class="flex1 pt-20">
+            <div v-if="props.info?.attachment" class="flex1 pt-20">
                 <div class="c-333 mb-10">附件:</div>
-                <FileLook v-model="props.info.attachments" :span="6"></FileLook>
+                <FileLook v-model="props.info.attachment" :span="6"></FileLook>
+            </div>
+            <div v-if="info?.images" class="flex1 pt-20">
+                <div class="c-333 mb-10">图片:</div>
+                <div class="d-flex flex-w">
+                    <template v-for="(item, index) in info?.images.split(',')" :key="index">
+                        <ImagePreview class="mr-20" :src="item" width="120px" height="120px"></ImagePreview>
+                    </template>
+                </div>
             </div>
         </div>
     </div>
@@ -73,7 +82,7 @@ const formatBytes = (bytes, decimals = 2)=> {
     height: 150px;
     width: 150px;
 }
-.info-out{
+.info-out {
     display: flex;
     margin-bottom: 30px;
 }
@@ -81,7 +90,7 @@ const formatBytes = (bytes, decimals = 2)=> {
     display: flex;
     flex-direction: column;
     justify-content: space-between;
-
+    margin-left: 20px;
 }
 .info span{
     border-style: solid;

+ 446 - 63
src/views/training/meeting-add/index.vue

@@ -11,7 +11,7 @@
                 </el-button>
             </div>
             <div class="flex1 over-auto">
-                <el-form ref="formRef" label-width="auto" label-position="top" :model="form" :rules="rules">
+                <el-form ref="formRef" label-width="auto" label-position="top" :model="form" :rules="rules" :scroll-into-view-options="scrollOptions" scroll-to-error>
                     <div class="pd-16 border-bottom ov-hd">
                         <div class="info-title mb-10">会议信息</div>
                         <el-row :gutter="20">
@@ -21,21 +21,26 @@
                                 </el-form-item>
                             </el-col>
                             <el-col :span="6">
-                                <el-form-item label="培训时间" prop="trainingTime">
-                                    <el-date-picker v-model="form.trainingTime" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" date-format="YYYY-MM-DD" time-format="HH:mm:ss" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
+                                <el-form-item label="会议时间" prop="trainingTime">
+                                    <el-date-picker v-model="form.trainingTime" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" date-format="YYYY-MM-DD" time-format="HH:mm" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
                                 </el-form-item>
                             </el-col>
                             <el-col :span="6">
-                                <el-form-item label="培训方式" prop="joinType">
+                                <el-form-item label="报名时间" prop="signupTime">
+                                    <el-date-picker v-model="form.signupsTime" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" date-format="YYYY-MM-DD" time-format="HH:mm" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="6">
+                                <el-form-item label="会议方式" prop="joinType">
                                     <!-- 单选框 -->
                                     <el-radio-group v-model="form.joinType">
-                                        <el-radio v-for="item in dm_training_join_type" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
+                                        <el-radio v-for="item in lm_training_join_type" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
                                     </el-radio-group>
                                 </el-form-item>
                             </el-col>
                             <el-col v-if="form.joinType === '0'" :span="6">
-                                <el-form-item label="培训地点" prop="trainingLocation">
-                                    <el-input v-model="form.trainingLocation" placeholder="请输入培训地点" clearable />
+                                <el-form-item label="会议地点" prop="trainingLocation">
+                                    <el-input v-model="form.trainingLocation" placeholder="请输入会议地点" clearable />
                                 </el-form-item>
                             </el-col>
                             <el-col :span="6">
@@ -48,11 +53,121 @@
                                     <el-input v-model="form.tel" maxlength="20" placeholder="请输入联系电话" clearable />
                                 </el-form-item>
                             </el-col>
+                            <el-col :span="12">
+                                <el-form-item label="可报名人员类型" prop="conditions.typeCheck">
+                                    <el-checkbox-group v-model="checkedVipLevels" @change="handleCheckedChange">
+                                        <el-checkbox v-for="city in form.conditions.typeCheck" :key="city" :label="city" :value="city">
+                                            {{ selectDictLabels(lm_check_join_type, city.vipLevel, ',') }}
+                                        </el-checkbox>
+                                    </el-checkbox-group>
+                                    <div @click="handleCheckAllChange(true)" v-if="!checkAll" class="pl-10 c-s-p">
+                                        <u>全选</u>
+                                    </div>
+                                    <div @click="handleCheckAllChange(false)" v-else class="pl-10 c-s-p"><u>取消全选</u></div>
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="6">
+                                <el-form-item label="报名人数" prop="conditions.totalCheck">
+                                    <div class="d-flex a-c">
+                                        <el-radio-group v-model="form.conditions.totalCheck" style="flex-wrap: nowrap">
+                                            <el-radio label="0">不限制</el-radio>
+                                            <el-radio label="1">限制</el-radio>
+                                        </el-radio-group>
+                                    </div>
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="12" v-if="form.conditions.totalCheck == '1'">
+                                <el-form-item prop="restrictiveConditions">
+                                    <template #label>
+                                        <span>限制条件</span>
+                                        <span class="c-999 f-s-12 f-w-4">(不限制的条件可不填)</span>
+                                    </template>
+                                    <div class="d-flex flex-cln pl-10">
+                                        <div class="d-flex">
+                                            <div class="c-#606266 f-w-6" style="">报名总人数限制:</div>
+                                            <el-input class="flex1 pl-5" v-model="form.conditions.total" maxlength="20" placeholder="请输入报名人数" clearable :disabled="form.conditions.totalCheck !== '1'" style="max-width: 200px;" />
+                                        </div>
+                                        <div class="c-#606266 f-w-6" style="">条件限制:</div>
+                                        <div class="d-flex">
+                                            <div class="d-flex flex-cln">
+                                                <el-checkbox v-model="form.conditions.levelTotalCheck" label="按单位类型限制" size="large" true-value="1" false-value="0" />
+                                                <div v-if="form.conditions.levelTotalCheck == '1' && form.conditions.cpyTotalCheck == '0'" class="pl-10 pr-10 pt-5 pb-5 border">
+                                                    <template v-for="(item, index) in form.conditions.levelCheck" :key="index">
+                                                        <div class="d-flex a-c" v-if="checkedVipLevels.some(items => items.vipLevel === item.vipLevel)">
+                                                            <el-checkbox v-model="item.check" true-value="1" false-value="0" :label="'所有' + selectDictLabels(lm_check_join_type, item.vipLevel, ',') + '参会人数≤'" size="large" />
+                                                            <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
+                                                        </div>
+                                                    </template>
+                                                    <el-empty :image-size="20" description="请先选择报名人员类型" v-if="checkedVipLevels.length == 0" />
+                                                </div>
+                                            </div>
+                                            <div class="d-flex flex-cln ml-20">
+                                                <el-checkbox v-model="form.conditions.cpyTotalCheck" label="按每家单位人数限制" size="large" true-value="1" false-value="0" />
+                                                <div v-if="form.conditions.cpyTotalCheck == '1' && form.conditions.levelTotalCheck == '0'" class="pl-10 pr-10 pt-5 pb-5 border">
+                                                    <template v-for="(item, index) in form.conditions.cpyCheck" :key="index">
+                                                        <div class="d-flex a-c" v-if="checkedVipLevels.some(items => items.vipLevel === item.vipLevel)">
+                                                            <el-checkbox v-model="item.check" :label="selectDictLabels(lm_check_join_type, item.vipLevel, ',') + '限制每家单位人数≤'" size="large" true-value="1" false-value="0" />
+                                                            <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
+                                                        </div>
+                                                    </template>
+                                                    <el-empty :image-size="20" description="请先选择报名人员类型" v-if="checkedVipLevels.length == 0" />
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="d-flex border" v-if="form.conditions.levelTotalCheck == '1' && form.conditions.cpyTotalCheck == '1'">
+                                            <div class="pl-10 pr-10 pt-5 pb-5 ">
+                                                <template v-for="(item, index) in form.conditions.levelCheck" :key="index">
+                                                    <div class="d-flex a-c" v-if="checkedVipLevels.some(items => items.vipLevel === item.vipLevel)">
+                                                        <el-checkbox v-model="item.check" :label="'所有' + selectDictLabels(lm_check_join_type, item.vipLevel, ',') + '参会人数≤'" size="large" true-value="1" false-value="0" />
+                                                        <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
+                                                    </div>
+                                                </template>
+                                            </div>
+                                            <div class="pl-10 pr-10 pt-5 pb-5">
+                                                <template v-for="(item, index) in form.conditions.cpyCheck" :key="index">
+                                                    <div class="d-flex a-c" v-if="checkedVipLevels.some(items => items.vipLevel === item.vipLevel)">
+                                                        <el-checkbox v-model="item.check" :label="selectDictLabels(lm_check_join_type, item.vipLevel, ',') + '限制每家单位人数≤'" size="large" true-value="1" false-value="0" />
+                                                        <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
+                                                    </div>
+                                                </template>
+                                            </div>
+                                            <div class="flex1 ml--10" v-if="checkedVipLevels.length == 0"><el-empty :image-size="20" description="请先选择报名人员类型" /></div>
+                                        </div>
+                                    </div>
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="6">
+                                <el-form-item label="是否电子手签" prop="eleSignature">
+                                    <div class="d-flex a-c">
+                                        <el-radio-group v-model="form.eleSignature" style="flex-wrap: nowrap">
+                                            <el-radio label="1">是</el-radio>
+                                            <el-radio label="0">否</el-radio>
+                                        </el-radio-group>
+                                    </div>
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="12">
+                                <div class="d-flex" style="align-items: flex-end;">
+                                    <el-form-item label="发放积分" prop="pointsFlag" class="">
+                                        <el-radio-group v-model="form.pointsFlag" style="flex-wrap: nowrap">
+                                            <el-radio label="1">是</el-radio>
+                                            <el-radio label="0">否</el-radio>
+                                        </el-radio-group>
+                                    </el-form-item>
+                                    <el-form-item label="" prop="points" v-if="form.pointsFlag == '1'" class="flex1 pl-10">
+                                        <div class="d-flex f-s-14">
+                                            <div>每成功参会(签到成功)1人发放</div>
+                                            <el-input v-model="form.points" style="width: 60px" />
+                                            <div>个单位积分。</div>
+                                        </div>
+                                    </el-form-item>
+                                </div>
+                            </el-col>
                         </el-row>
                         <el-row :gutter="20">
                             <el-col :span="12">
-                                <el-form-item label="培训详情" prop="description">
-                                    <el-input v-model="form.description" :rows="4" type="textarea" placeholder="请输入培训详情" />
+                                <el-form-item label="会议详情" prop="description">
+                                    <el-input v-model="form.description" :rows="4" type="textarea" placeholder="请输入会议详情" />
                                 </el-form-item>
                             </el-col>
                             <el-col :span="12">
@@ -60,108 +175,363 @@
                                     <el-input v-model="form.notice" :rows="4" type="textarea" placeholder="请输入与会须知" />
                                 </el-form-item>
                             </el-col>
+                            <el-col :span="12">
+                                <el-form-item label="会议微信群聊二维码" prop="wechatQrCode">
+                                    <ImageUpload v-model="form.wechatQrCode" :fileSize="20"></ImageUpload>
+                                </el-form-item>
+                            </el-col>
                             <el-col :span="12">
                                 <el-form-item label="封面图" prop="coverImg">
-                                    <ImageUpload v-model="form.coverImg" :limit="1"></ImageUpload>
+                                    <ImageUpload v-model="form.coverImg" :limit="1" :fileSize="20"></ImageUpload>
                                 </el-form-item>
                             </el-col>
                             <el-col :span="12">
                                 <el-form-item label="会议图" prop="trainingImg">
-                                    <ImageUpload v-model="form.trainingImg" :fileSize="40"></ImageUpload>
+                                    <ImageUpload v-model="form.trainingImg" :fileSize="20"></ImageUpload>
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="12">
+                                <el-form-item label="会议备注" prop="remark">
+                                    <el-input v-model="form.remark" :rows="4" type="textarea" placeholder="请输入会议备注" />
                                 </el-form-item>
                             </el-col>
                             <el-col :span="12">
                                 <el-form-item label="相关文件" prop="attachments">
-                                    <FileUpload v-model="form.attachments" format="array" :limit="20" :fileSize="100"></FileUpload>
+                                    <template #label>
+                                        <span>相关文件</span>
+                                        <span class="c-999 f-s-12 f-w-4">(此模块报名审核通过后才可查看)</span>
+                                    </template>
+                                    <div class="d-flex flex-cln mt-10 f-s-12 c-#606266">
+                                        <FileUpload v-model="form.attachments" format="array" :limit="20" :fileSize="100"></FileUpload>
+                                    </div>
                                 </el-form-item>
                             </el-col>
                         </el-row>
                     </div>
-                    <div class="pd-16 border-bottom">
-                        <div class="info-title mb-10">证书信息</div>
-                        <div class="d-flex j-start a-c">
-                            <el-form-item label="是否颁发证书" prop="certFlag">
-                                <el-radio-group v-model="form.certFlag">
-                                    <el-radio v-for="item in yes_no" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
-                                </el-radio-group>
-                            </el-form-item>
-                            <div v-if="+form.certFlag" class="ml-20">
-                                <el-button @click="addCertInfo">新增证书</el-button>
+                    <div class="pd-16 border-bottom d-flex">
+                        <div class="w-50% ov-at h-1000">
+                            <div class="info-title mb-10">证书信息</div>
+                            <div class="d-flex j-start a-c">
+                                <el-form-item label="是否颁发证书" prop="certFlag">
+                                    <el-radio-group v-model="form.certFlag">
+                                        <el-radio v-for="item in yes_no" :key="item.value" :label="item.value">
+                                            {{ item.label }}
+                                        </el-radio>
+                                    </el-radio-group>
+                                </el-form-item>
+                                <div v-if="+form.certFlag" class="ml-20">
+                                    <el-button @click="addCertInfo" type="primary">新增证书</el-button>
+                                </div>
                             </div>
-                        </div>
-                        <template v-if="form.certFlag">
-                            <template v-for="(item, index) in form.certificateInfo" :key="index">
-                                <el-row :gutter="20" class="bg-#f4f4f4 pd-16 mb-10">
-                                    <el-col :span="6">
-                                        <el-form-item label="证书名称" :prop="`certificateInfo.${index}.certType`" :rules="[{ required: true, message: '请选择证书名称', trigger: 'change' }]">
-                                            <el-select v-model="item.certType" placeholder="证书名称" clearable>
-                                                <el-option v-for="item in dm_training_cert" :key="item.value" :label="item.label" :value="item.value" />
-                                            </el-select>
-                                        </el-form-item>
-                                    </el-col>
-                                    <el-col :span="6">
-                                        <el-form-item :prop="`certificateInfo.${index}.certImg`" :rules="[{ required: true, message: '请上传证书图片', trigger: 'change' }]">
-                                            <template #label>
-                                                <span>证书模板图片</span>
-                                                <el-button @click="goEditor()" type="primary" text>去编辑模板图片</el-button>
-                                            </template>
-                                            <ImageUpload v-model="item.certImg" :fileSize="40" :limit="1"></ImageUpload>
-                                        </el-form-item>
-                                    </el-col>
-                                    <el-col :span="6">
-                                        <el-form-item>
-                                            <el-button type="danger" @click="deleteItem(index)">删除</el-button>
-                                        </el-form-item>
-                                    </el-col>
-                                </el-row>
+                            <template v-if="form.certFlag">
+                                <template v-for="(item, index) in form.certificateInfo" :key="index">
+                                    <el-row :gutter="20" class="bg-#f4f4f4 pd-16 mb-10">
+                                        <el-col :span="10">
+                                            <el-form-item label="证书名称" :prop="`certificateInfo.${index}.certType`" :rules="[{ required: true, message: '请选择证书名称', trigger: 'change' }]">
+                                                <el-select v-model="item.certType" placeholder="证书名称" clearable>
+                                                    <el-option v-for="item in lm_training_cert" :key="item.value" :label="item.label" :value="item.value" />
+                                                </el-select>
+                                            </el-form-item>
+                                        </el-col>
+                                        <el-col :span="10">
+                                            <el-form-item :prop="`certificateInfo.${index}.certImg`" :rules="[{ required: true, message: '请上传证书图片', trigger: 'change' }]">
+                                                <template #label>
+                                                    <span>证书模板图片</span>
+                                                    <el-button @click="goEditor()" type="primary" text>去编辑模板图片</el-button>
+                                                </template>
+                                                <ImageUpload v-model="item.certImg" :fileSize="40" :limit="1"></ImageUpload>
+                                            </el-form-item>
+                                        </el-col>
+                                        <el-col :span="4">
+                                            <el-form-item>
+                                                <el-button type="danger" @click="deleteItem(index)">删除</el-button>
+                                            </el-form-item>
+                                        </el-col>
+                                    </el-row>
+                                </template>
                             </template>
-                        </template>
+                        </div>
+
+                        <div class="w-50% d-flex flex-cln j-c a-c pl-20">
+                            <el-button type="primary" class="w-100%" plain style="height: 70px; margin-bottom: 20px;" @click="showSignIn = true">点击去编辑报名信息></el-button>
+                            <div class="w-400 h-700 border over-auto">
+                                <div class="pd-10 border bg-#fafafa">
+                                    <div class="pt-10  f-s-20 f-w-6 d-flex j-c a-c flex-cln ">报名信息</div>
+                                    <div class="f-s-12 f-w-4 d-flex j-start c-red">此页面为意向人报名时所见页面:</div>
+                                </div>
+                                <template v-for="(item, index) in fixedField" :key="index">
+                                    <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
+                                        <span class="c-#606266 f-s-16 f-w-6">{{ item.label }}</span>
+                                        <span class="f-s-14">请输入</span>
+                                    </div>
+                                </template>
+                                <template v-for="(field, index) in fields" :key="index">
+                                    <div class="meeting-custom-wrapper" @click.stop="showSignIn = true">
+                                        <meetingCustomPreview :field="field" style="pointer-events: none" v-if="field" />
+                                    </div>
+                                </template>
+                                <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
+                                    <span class="c-#606266 f-s-16 f-w-6">备注</span>
+                                    <span class="f-s-14">请输入</span>
+                                </div>
+                            </div>
+                        </div>
                     </div>
                 </el-form>
+                <MeetingEditors v-if="showSignIn" v-model:show="showSignIn" v-model:info="fields" />
             </div>
             <div class="d-flex a-c j-c pd-16">
-                <el-button @click="router.go(-1)">取消</el-button>
+                <el-button @click="Cancel">取消</el-button>
                 <el-button @click="save" type="primary">提交</el-button>
             </div>
         </div>
     </div>
 </template>
-
 <script setup name="meeting-add" lang="ts">
-import { ref, reactive, onMounted } from 'vue';
+import { trainingAdd, trainingDetail, trainingUpdate } from '@/api/training';
 import { debounce } from 'lodash';
+import { onMounted, reactive, ref } from 'vue';
 import { useRouter } from 'vue-router';
-import { trainingAdd, trainingDetail, trainingUpdate } from '@/api/training';
+import { FieldDefinition } from '../models/type';
+// 需要添加以下导入
+import meetingCustomPreview from '../models/meeting-custom-preview.vue';
+import MeetingEditors from '../models/meeting-editors.vue';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { dm_training_join_type, yes_no, dm_training_cert } = toRefs<any>(proxy?.useDict('dm_training_join_type', 'yes_no', 'dm_training_cert'));
+const { lm_training_join_type, yes_no, lm_training_cert, lm_check_join_type } = toRefs<any>(proxy?.useDict('lm_training_join_type', 'yes_no', 'lm_training_cert', 'lm_check_join_type'));
+const fields = ref<FieldDefinition[]>([])
+const showSignIn = ref(false);
+const fixedField = ref<FieldDefinition[]>([{
+    name: `ent-${generateSecureRandomString()}`,
+    label: '企业名称', type: '1',
+    required: '1', readonly: '0',
+}, {
+    name: `name-${generateSecureRandomString()}`,
+    label: '姓名', type: '1', readonly: '0',
+    required: '1'
+}, {
+    name: `pos-${generateSecureRandomString()}`,
+    label: '职务', type: '1', readonly: '0',
+    required: '1'
+}, {
+    name: `Con-${generateSecureRandomString()}`,
+    label: '联系方式', type: '1', readonly: '0',
+    required: '1'
+}])
+const scrollOptions = {
+  block: 'center',
+  behavior: 'smooth'
+};
+function generateSecureRandomString(length = 8) {
+    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+    const randomValues = new Uint32Array(length);
+    window.crypto.getRandomValues(randomValues);
+    let result = '';
+    randomValues.forEach((value) => {
+        result += chars[value % chars.length];
+    });
+    return result;
+}
+const Cancel = () => {
+    router.go(-1)
+}
 const router = useRouter();
 const route = useRoute();
+const levelTypeCheck = computed(() =>
+    String(Number(checkedVipLevels.value.length > 0))
+);
 const form = ref<any>({
-    id: undefined
+    id: undefined,
+    conditions: {
+        levelTotalCheck: '0',
+        cpyTotalCheck: '0',
+        levelTypeCheck: levelTypeCheck,
+        typeCheck: [
+            {
+                vipLevel: "0",
+                check: "0"
+            },
+            {
+                vipLevel: "1",
+                check: "0"
+            },
+            {
+                vipLevel: "3",
+                check: "0"
+            },
+            {
+                vipLevel: "5",
+                check: "0"
+            },
+            {
+                vipLevel: "6",
+                check: "0"
+            },
+            {
+                vipLevel: "7",
+                check: "0"
+            },
+            {
+                vipLevel: "9",
+                check: "0"
+            },
+            {
+                vipLevel: "10",
+                check: "0"
+            }
+        ],
+        levelCheck: [{
+            vipLevel: '0',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '1',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '3',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '5',
+            check: "0",
+            total: ''
+        },
+        {
+            vipLevel: "6",
+            check: "0",
+            total: ''
+        },
+        {
+            vipLevel: "7",
+            check: "0",
+            total: ''
+        },
+        {
+            vipLevel: "9",
+            check: "0",
+        },
+        {
+            vipLevel: "10",
+            total: ''
+        }],
+        cpyCheck: [{
+            vipLevel: '0',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '1',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '3',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '5',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '6',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '7',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '9',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '10',
+            check: "0",
+            total: ''
+        }]
+    }
 });
+
+const checkAll = ref(false)
+const checkedVipLevels = ref([])
+
+// 选项变化时的处理
+const handleCheckedChange = (selectedValues) => {
+
+    const selectedLevelMap = new Map(selectedValues.map(item => [item.vipLevel, true]));
+
+    form.value.conditions.typeCheck.forEach(item => {
+        item.check = selectedLevelMap.has(item.vipLevel) ? '1' : '0';
+    });
+}
+
+// 全选/取消全选
+const handleCheckAllChange = (val: boolean) => {
+    checkedVipLevels.value = val
+        ? form.value.conditions.typeCheck.map(item => item)
+        : []
+    handleCheckedChange(checkedVipLevels.value)
+    checkAll.value = val
+}
 const rules = reactive({
     // 自动生成全部
     trainingName: [{ required: true, message: '请输入会议名称', trigger: 'blur' }],
-    trainingTime: [{ required: true, message: '请选择培训时间', trigger: 'blur' }],
-    joinType: [{ required: true, message: '请选择培训方式', trigger: 'change' }],
-    trainingLocation: [{ required: true, message: '请输入培训地点', trigger: 'blur' }],
+    trainingTime: [{ required: true, message: '请选择会议时间', trigger: 'blur' }],
+    joinType: [{ required: true, message: '请选择会议方式', trigger: 'change' }],
+    trainingLocation: [{ required: true, message: '请输入会议地点', trigger: 'blur' }],
+    'conditions.totalCheck': [{ required: true, message: '请选择是否限制报名人数', trigger: 'change' }],
+   'conditions.typeCheck': [
+    {
+            validator: (rule, value, callback) => {
+                setTimeout(() => {
+                    const isChecked = value.some(item => item.check == "1");
+                    if (!isChecked) {
+                        callback(new Error('请至少选择一种可报名单位类型'));
+                    } else {
+                        callback();
+                    }
+                }, 300);
+            },
+            trigger: 'change' // 触发校验的时机
+        }
+  ],
     certFlag: [{ required: true, message: '请选择是否颁发证书', trigger: 'change' }],
     certificateInfo: [{ required: true, message: '请选择证书名称', trigger: 'change' }],
     description: [{ required: true, message: '请输入培训详情', trigger: 'blur' }],
-    coverImg: [{ required: true, message: '请上传封面图', trigger: 'change' }],
-    trainingImg: [{ required: true, message: '请上传会议图', trigger: 'change' }],
+    eleSignature: [{ required: true, message: '请选择是否电子手签', trigger: 'blur' }],
+    pointsFlag: [{ required: true, message: '请选择是否发放积分', trigger: 'blur' }],
+    // coverImg: [{ required: true, message: '请上传封面图', trigger: 'change' }],
+    // trainingImg: [{ required: true, message: '请上传会议图', trigger: 'change' }],
     contactName: [{ required: true, message: '请输入会议联系人', trigger: 'blur' }],
     tel: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
 });
 const formRef = ref();
-
 const save = debounce(async () => {
     await formRef.value.validate();
+    form.value.conditions.typeCheck.forEach(typeItem => {
+        if (typeItem.check === "0") {
+            // Update cpyCheck
+            const cpyItem = form.value.conditions.cpyCheck.find(item => item.vipLevel === typeItem.vipLevel);
+            if (cpyItem) {
+                cpyItem.check = "0";
+            }
+
+            // Update levelCheck
+            const levelItem = form.value.conditions.levelCheck.find(item => item.vipLevel === typeItem.vipLevel);
+            if (levelItem) {
+                levelItem.check = "0";
+            }
+        }
+    });
     const params = {
         ...form.value,
         trainingStart: form.value.trainingTime ? form.value.trainingTime[0] : undefined,
         trainingEnd: form.value.trainingTime ? form.value.trainingTime[1] : undefined,
-        certificateInfo: +form.value.certFlag ? form.value.certificateInfo : undefined
+        signupStart: form.value.signupsTime ? form.value.signupsTime[0] : undefined,
+        signupEnd: form.value.signupsTime ? form.value.signupsTime[1] : undefined,
+        certificateInfo: +form.value.certFlag ? form.value.certificateInfo : undefined,
+        questions: fields.value.map((item, index) => ({
+            ...item,
+            sort: index + 1  // 从 1 开始
+        }))
     };
     const res = form.value.id ? await trainingUpdate(params) : await trainingAdd(params);
     if (res && res.code === 200) {
@@ -171,6 +541,7 @@ const save = debounce(async () => {
 const goEditor = () => {
     window.open('https://lm.yujin.shuziyunyao.com/poster#/editor', '_blank');
 }
+
 const addCertInfo = () => {
     if (!form.value.certificateInfo) {
         form.value.certificateInfo = [];
@@ -190,11 +561,23 @@ const getMeetingDetail = async () => {
         if (!res || res.code !== 200) return;
         form.value = {
             ...res.data,
-            trainingTime: res.data.trainingStart && res.data.trainingEnd ? [res.data.trainingStart, res.data.trainingEnd] : undefined
+            trainingTime: res.data.trainingStart && res.data.trainingEnd ? [res.data.trainingStart, res.data.trainingEnd] : undefined,
+            signupsTime: res.data.signupStart && res.data.signupEnd ? [res.data.signupStart, res.data.signupEnd] : undefined
         };
+        fields.value = res.data.questions
+        form.value.conditions.typeCheck.forEach((i) => {
+            if (i.check == '1') {
+                checkedVipLevels.value.push(i)
+            }
+        })
     }
 };
 onMounted(() => {
     getMeetingDetail();
 });
 </script>
+<style scoped>
+.border {
+    border: 1px solid #dcdfe6;
+}
+</style>

+ 43 - 111
src/views/training/meeting-detail/index.vue

@@ -1,8 +1,8 @@
 <template>
     <div class="p-3">
         <div class="bg-fff flex1 ov-hd d-flex flex-cln">
-            <div class="d-flex a-c pd-16 border-bottom">
-                <div class="f-s-20 c-333 f-w-7 mr-10">会议详情</div>
+            <div class="d-flex a-c pd-16">
+                <div class="info-title f-s-20 c-333 f-w-7">会议详情</div>
                 <el-button @click="router.go(-1)" type="primary" text>
                     <el-icon>
                         <Back />
@@ -10,126 +10,36 @@
                     返回上一级
                 </el-button>
             </div>
-            <div class="flex1 over-auto">
-                <div class="pd-16 ov-hd">
-                    <el-descriptions :column="4">
-                        <el-descriptions-item label="会议名称:">{{ form?.trainingName || '-' }}</el-descriptions-item>
-                        <el-descriptions-item label="培训时间:">{{ form?.trainingStart }}~{{ form?.trainingEnd }}</el-descriptions-item>
-                        <el-descriptions-item label="培训方式:">{{ selectDictLabel(dm_training_join_type, form?.joinType) || '-' }}</el-descriptions-item>
-                        <el-descriptions-item label="培训地点:">{{ form?.trainingLocation || '-' }}</el-descriptions-item>
-                        <el-descriptions-item label="联系人:">{{ form?.contactName || '-' }}</el-descriptions-item>
-                        <el-descriptions-item label="联系电话:">{{ form?.tel || '-' }}</el-descriptions-item>
-                        <el-descriptions-item label="创建人:">{{ form?.createByName || '-' }}</el-descriptions-item>
-                        <el-descriptions-item label="创建时间:">{{ form?.createTime || '-' }}</el-descriptions-item>
-                    </el-descriptions>
-                    <div class="d-flex f-s-14 c-666 mb-10">
-                        <div class="flex1">
-                            <div class="c-333 mb-10">培训详情:</div>
-                            <div>{{ form?.description || '-' }}</div>
-                        </div>
-                        <div class="flex1">
-                            <div class="c-333 mb-10">与会须知:</div>
-                            <div>{{ form?.notice || '-' }}</div>
-                        </div>
-                        <div v-if="form?.attachments" class="flex1">
-                            <div class="c-333 mb-10">相关文件:</div>
-                            <FileLook v-model="form.attachments" :span="12"></FileLook>
-                        </div>
-                    </div>
-                    <div class="d-flex f-s-14 c-666">
-                        <div class="flex1">
-                            <div class="c-333 mb-10">封面图:</div>
-                            <div>
-                                <ImagePreview :src="form?.coverImgUrl" :width="100"></ImagePreview>
-                            </div>
-                        </div>
-                        <div v-if="form?.attachments" class="flex1">
-                            <div class="c-333 mb-10">会议图:</div>
-                            <div class="d-flex flex-wrap">
-                                <template v-for="(item, index) in form?.trainingImg" :key="index">
-                                    <div class="mr-10 mb-10">
-                                        <ImagePreview :src="item" :width="100"></ImagePreview>
-                                    </div>
-                                </template>
-                            </div>
-                        </div>
-                    </div>
-                    <div class="f-s-14">
-                        <div class="c-333 mb-10">是否颁发证书: {{ +form?.certFlag ? '是' : '否' }}</div>
-                        <vxe-table v-if="+form?.certFlag" border :data="form?.certificateInfo" min-height="0">
-                            <!-- 序号 -->
-                            <vxe-column type="seq" width="60" title="序号" align="center" />
-                            <vxe-column title="证书名称" min-width="100">
-                                <template #default="{ row }">
-                                    <DictTag :options="dm_training_cert" :value="row?.certType"></DictTag>
-                                </template>
-                            </vxe-column>
-                            <vxe-column title="证书模板">
-                                <template #default="{ row }">
-                                    <ImagePreview :src="row?.certImgUrl" :width="100"></ImagePreview>
-                                </template>
-                            </vxe-column>
-                        </vxe-table>
-                    </div>
-                    <el-divider />
-                    <div class="d-flex mb-16">
-                        <div class="info-title ">
-                            <span>参会人员信息</span>
-                            <span class="f-s-14 c-666">(报名:{{ form?.joinCount }}人 | 签到:{{ form?.signCount }}人 | 领取证书:{{ form?.certCount }}人)</span>
-                        </div>
-                    </div>
-                    <div>
-                        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
-                            <el-form-item label="姓名:" prop="name">
-                                <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable style="width: 180px" />
-                            </el-form-item>
-                            <el-form-item label="企业名称:" prop="company">
-                                <el-input v-model="queryParams.company" placeholder="请输入企业名称" clearable style="width: 180px" />
-                            </el-form-item>
-                            <el-form-item label="参会状态:" prop="signupStatus">
-                                <el-select v-model="queryParams.signupStatus" placeholder="请选择参会状态" clearable style="width: 180px">
-                                    <el-option v-for="item in dm_training_signup_status_list" :key="item.value" :label="item.label" :value="item.value" />
-                                </el-select>
-                            </el-form-item>
-                            <el-form-item>
-                                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-                                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-                            </el-form-item>
-                        </el-form>
-                    </div>
-                    <vxe-table :loading="loading" border :data="list" min-height="0">
-                        <!-- 序号 -->
-                        <vxe-column type="seq" width="60" title="序号" align="center" />
-                        <vxe-column title="企业名称" field="company" min-width="100" :formatter="colNoData" />
-                        <vxe-column title="姓名" field="name" min-width="100" :formatter="colNoData" />
-                        <vxe-column title="职务" field="position" min-width="100" :formatter="colNoData" />
-                        <vxe-column title="联系方式" field="contact" min-width="100" :formatter="colNoData" />
-                        <vxe-column title="备注" field="remark" min-width="100" :formatter="colNoData" />
-                        <vxe-column title="报名时间" align="center" field="createTime" min-width="100" :formatter="colNoData" />
-                        <vxe-column title="参会状态" min-width="100" fixed="right">
-                            <template #default="{ row }">
-                                <DictTag :options="dm_training_signup_status_list" :value="row?.signupStatusForPc"></DictTag>
-                            </template>
-                        </vxe-column>
-                    </vxe-table>
-                    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+            <div class="border-botttom pl-20">
+                <div class="d-flex">
+                    <template v-for="(item, index) in tabs" :key="index">
+                        <div class="tabs-item" @click="() => { activeName = item.value }" :class="{ checked: activeName === item.value }">{{ item.label }}</div>
+                    </template>
                 </div>
             </div>
+            <MeetingDetailAttend v-if="activeName === '1'" :form="form" />
+            <MeetingDetailInfo v-if="activeName === '2'" :form="form" />
         </div>
     </div>
 </template>
 
 <script setup name="meeting-detail" lang="ts">
-import { ref, reactive, onMounted } from 'vue';
-import { debounce } from 'lodash';
-import router from '@/router';
 import { trainingDetailById, trainingMembers } from '@/api/training';
-import { FileLook } from '@/views/models';
-import { colNoData } from '@/utils/noData';
+import router from '@/router';
+import { onMounted, ref } from 'vue';
+import { MeetingDetailInfo } from '../models';
+import MeetingDetailAttend from '../models/meeting-detail-attend.vue';
+
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { dm_training_join_type, yes_no, dm_training_cert, dm_training_status, dm_training_signup_status_list } = toRefs<any>(proxy?.useDict('dm_training_join_type', 'yes_no', 'dm_training_cert', 'dm_training_status', 'dm_training_signup_status_list'));
+const { lm_training_join_type, yes_no, lm_training_cert, lm_training_status, lm_training_signup_status_list } = toRefs<any>(proxy?.useDict('lm_training_join_type', 'yes_no', 'lm_training_cert', 'lm_training_status', 'lm_training_signup_status_list'));
 // 获取详情
+const tabs = ref([
+    { label: '参会人员信息', value: '1' },
+    { label: '会议信息', value: '2' },
+
+])
 
+const activeName = ref('1');
 const form = ref<any>(null);
 const query = useRoute().query;
 const getDetail = async () => {
@@ -170,3 +80,25 @@ onMounted(() => {
     getList();
 });
 </script>
+<style scoped lang="scss">
+.tabs-item {
+    margin-right: 20px;
+    padding: 8px 20px;
+    font-size: 14px;
+    border-color: #d7d7d7;
+    border-style: solid;
+    border-width: 1px 1px 0 1px;
+    cursor: pointer;
+    user-select: none;
+
+    &.checked {
+        color: #fff;
+        border-color: var(--el-color-primary);
+        background-color: var(--el-color-primary);
+    }
+}
+
+.border-botttom {
+    border-bottom: 1px solid #d7d7d7;
+}
+</style>

+ 121 - 33
src/views/training/meeting/index.vue

@@ -9,9 +9,19 @@
                             <el-form-item label="会议名称" prop="trainingName">
                                 <el-input v-model="queryParams.trainingName" placeholder="搜会议名称" clearable style="width: 160px" @keyup.enter="handleQuery" />
                             </el-form-item>
-                            <el-form-item label="培训方式" prop="joinType">
-                                <el-select v-model="queryParams.joinType" placeholder="请选择培训方式" clearable style="width: 160px" @change="handleQuery">
-                                    <el-option v-for="item in dm_training_join_type" :key="item.value" :label="item.label" :value="item.value" />
+                            <el-form-item label="会议方式" prop="joinType">
+                                <el-select v-model="queryParams.joinType" placeholder="请选择会议方式" clearable style="width: 160px" @change="handleQuery">
+                                    <el-option v-for="item in lm_training_join_type" :key="item.value" :label="item.label" :value="item.value" />
+                                </el-select>
+                            </el-form-item>
+                            <el-form-item label="报名状态" prop="joinStatus">
+                                <el-select v-model="queryParams.joinStatus" placeholder="请选择会报名状态" clearable style="width: 160px" @change="handleQuery">
+                                    <el-option v-for="item in lm_training_join_status" :key="item.value" :label="item.label" :value="item.value" />
+                                </el-select>
+                            </el-form-item>
+                            <el-form-item label="会议状态" prop="trainingStatus">
+                                <el-select v-model="queryParams.trainingStatus" placeholder="请选择会议状态" clearable style="width: 160px" @change="handleQuery">
+                                    <el-option v-for="item in lm_training_status" :key="item.value" :label="item.label" :value="item.value" />
                                 </el-select>
                             </el-form-item>
                             <el-form-item label="是否颁发证书" prop="certFlag">
@@ -36,43 +46,73 @@
                 </div>
                 <div class="flex1 ov-hd">
                     <vxe-table :loading="loading" border :data="dataList" min-height="0" max-height="100%">
-                        <vxe-column title="序号" align="center" type="seq" width="60" />
-                        <vxe-column field="trainingName" title="会议名称" :formatter="colNoData" />
-                        <vxe-column field="trainingTime" min-width="200" title="培训时间">
-                            <template #default="{ row }">{{ row.trainingStart  }}~{{ row.trainingEnd }}</template>
+                        <vxe-column title="序号" align="center" type="seq" width="50" />
+                        <vxe-column field="trainingName" title="会议名称" :formatter="colNoData" min-width="150" class-name="f-w-600" />
+                        <vxe-column field="trainingTime" min-width="370" title="会议时间" class-name="f-w-600">
+                            <template #default="{ row }">
+                                <div class="d-flex a-c">
+                                    {{ row.trainingStart.slice(0, -3) }}~{{ row.trainingEnd.slice(0, -3) }}
+                                    <DictTag :options="lm_training_status" :value="row?.trainingStatus"></DictTag>
+                                </div>
+                            </template>
                         </vxe-column>
-                        <vxe-column field="joinType" title="培训方式" width="80">
+                        <vxe-column field="signupsTime" min-width="350" title="报名时间" class-name="f-w-600">
                             <template #default="{ row }">
-                                {{ selectDictLabel(dm_training_join_type, row.joinType) }}
+                                <div class="d-flex a-c">
+                                    {{ row.signupStart.slice(0, -3) }}~{{ row.signupEnd.slice(0, -3) }}
+                                    <DictTag :options="lm_training_join_status" :value="row?.joinStatus"></DictTag>
+                                </div>
+                            </template>
+                        </vxe-column>
+                        <vxe-column field="joinType" title="会议方式" width="80">
+                            <template #default="{ row }">
+                                {{ selectDictLabel(lm_training_join_type, row.joinType) }}
                             </template>
                         </vxe-column>
                         <!-- <vxe-column field="trainingLocation" title="培训地点" align="center" min-width="150" /> -->
-                        <vxe-column field="certFlag" title="是否颁发证书" width="80">
+                        <vxe-column field="certFlag" title="是否颁发证书" width="60">
                             <template #default="{ row }">
                                 {{ selectDictLabel(yes_no, row.certFlag) }}
                             </template>
                         </vxe-column>
-                        <vxe-column field="joinCount" title="报名人数" width="80" />
-                        <vxe-column field="signCount" title="签到人数" width="80" />
+                        <vxe-column field="joinCount" title="报名人数" width="60" class-name="f-w-600" />
+                        <vxe-column field="signCount" title="签到人数" width="60" class-name="f-w-600" />
+                        <vxe-column field="waitCount" title="待审核人数" width="60" class-name="f-w-600" />
                         <vxe-column field="certCount" title="领取证书人数" width="80" />
-                        <vxe-column title="创建人" align="center" field="createByName" width="80" :formatter="colNoData" />
-                        <vxe-column title="创建时间" align="center" field="createTime" min-width="100" :formatter="colNoData" />
-                        <vxe-column field="certFlag" title="会议状态" width="80">
+                        <vxe-column field="certFlag" title="签到二维码" width="90" align="center">
                             <template #default="{ row }">
-                                <DictTag :options="dm_training_status" :value="row?.trainingStatus"></DictTag>
+                                <el-button @click="trainingSignIn(row)" :style="{ color: !['1', '0'].includes(row?.trainingStatus) ? '#999' : '#0079fe' }" text :disabled="!['1', '0'].includes(row?.trainingStatus)">查看</el-button>
                             </template>
                         </vxe-column>
-                        <vxe-column title="操作" width="300" fixed="right">
+                        <vxe-column title="临时报名通道" align="center" field="createByName" width="120" :formatter="colNoData">
                             <template #default="{ row }">
-                                <el-button v-if="['3'].includes(row?.trainingStatus)" type="primary" text @click="trainingOn(row)">上架</el-button>
-                                <span></span>
-                                <el-button v-if="!['3'].includes(row?.trainingStatus)" type="danger" text @click="trainingOff(row)">下架</el-button>
+                                <div v-if="row.tempStatus == '0'">
+                                    <el-popconfirm confirm-button-text="确认" cancel-button-text="取消" title="通过临时报名通道报名将不受时间限制,是否确认打开?" :disabled="row.trainingStatus == '2'" @confirm="confirmEvent(row)" @cancel="cancelEvent">
+                                        <template #reference>
+                                            <el-switch :model-value="row.trainingStatus == '2' ? '0' : row.tempStatus" @update:model-value="row.trainingStatus != '2' ? row.tempStatus = $event : null" :loading="loading1" active-value="1" inactive-value="0" :before-change="beforeChange1" :disabled="row.trainingStatus == '2'" />
+                                        </template>
+                                    </el-popconfirm>
+                                </div>
+                                <div v-if="row.tempStatus == '1'">
+                                    <el-switch v-if="+row.tempStatus" :model-value="row.trainingStatus == '2' ? '0' : row.tempStatus" @update:model-value="row.trainingStatus != '2' ? row.tempStatus = $event : null" :loading="loading1" :before-change="() => beforeChange2(row)" active-value="1" inactive-value="0" :disabled="row.trainingStatus == '2'" />
+                                </div>
+                                <el-button v-if="row.tempStatus == '1' && row.trainingStatus !== '2'" type="primary" text @click="temporary(row)" :disabled="row.trainingStatus == '2'">查看</el-button>
+                            </template>
+                        </vxe-column>
+                        <vxe-column title=" 创建人" align="center" field="createByName" width="70" :formatter="colNoData" />
+                        <vxe-column title="创建时间" align="center" field="createTime" width="160" :formatter="colNoData" />
+                        <vxe-column title="操作" width="240" fixed="right">
+                            <template #default="{ row }">
+                                <el-button v-if="!+row?.status" type="primary" text @click="trainingOn(row)">上架</el-button>
                                 <span></span>
-                                <el-button v-if="['3'].includes(row?.trainingStatus)" type="primary" @click="editRow(row)" text>编辑</el-button>
+                                <el-button v-if="+row?.status" type="danger" text @click="trainingOff(row)">下架</el-button>
                                 <span></span>
-                                <el-button v-if="['1', '0'].includes(row?.trainingStatus)" @click="trainingSignIn(row)" style="color: #0079fe;" text>签到二维码</el-button>
+                                <el-button v-if="!+row?.status" type="primary" @click="editRow(row)" text>编辑</el-button>
                                 <span></span>
-                                <el-button @click="router.push({ path: 'meeting-detail', query: { id: row?.id } })" style="color: #0079fe;" text>详情</el-button>
+                                <el-badge :value="row.waitCount" class="item" :offset="[-10, 5]" v-if="+row.waitCount">
+                                    <el-button @click="router.push({ path: 'meeting-detail', query: { id: row?.id } })" style="color: #0079fe;" text>人员管理及详情</el-button>
+                                </el-badge>
+                                <el-button v-else @click="router.push({ path: 'meeting-detail', query: { id: row?.id } })" style="color: #0079fe;" text>人员管理及详情</el-button>
                                 <span></span>
                                 <el-button text type="danger" @click="deleteRow(row)">删除</el-button>
                             </template>
@@ -83,18 +123,19 @@
             </div>
         </div>
     </div>
-    <SignInCode v-if="showSignIn" v-model:show="showSignIn" :info="rowInfo" :dict="{ dm_training_join_type }"></SignInCode>
+    <SignInCode v-if="showSignIn" v-model:show="showSignIn" :info="rowInfo" :dict="{ lm_training_join_type }"></SignInCode>
+    <TemporaryRegistration v-if="showTemporary" v-model:show="showTemporary" :info="temporaryRegistration" :dict="{ lm_training_join_type }"></TemporaryRegistration>
 </template>
 
 <script setup name="meeting" lang="ts">
-import { colNoData, colNoUnm } from '@/utils/noData';
+import { offOrNoTemp, publishTraining, queryTrainingCount, trainingDelete, trainingList, unpublishTraining } from '@/api/training';
+import { colNoData } from '@/utils/noData';
 import { searchTabs } from '@/views/models';
-import { trainingList, queryTrainingCount, trainingDelete, publishTraining, unpublishTraining } from '@/api/training';
-import { SignInCode } from '../models';
+import { SignInCode, TemporaryRegistration } from '../models';
 
 const router = useRouter();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { dm_training_join_type, yes_no, dm_training_cert, dm_training_status } = toRefs<any>(proxy?.useDict('dm_training_join_type', 'yes_no', 'dm_training_cert', 'dm_training_status'));
+const { lm_training_join_type, yes_no, lm_training_cert, lm_training_status,lm_training_join_status } = toRefs<any>(proxy?.useDict('lm_training_join_type', 'yes_no', 'lm_training_cert', 'lm_training_status',"lm_training_join_status"));
 const loading = ref(true);
 const showSearch = ref(true);
 const showSignIn = ref(false);
@@ -111,7 +152,8 @@ const data = reactive<any>({
     },
     rules: {}
 });
-
+const temporaryRegistration = ref()
+const showTemporary = ref(false)
 const { queryParams, form } = toRefs(data);
 /** 查询会员信息列表 */
 const getList = async () => {
@@ -121,7 +163,46 @@ const getList = async () => {
     total.value = res.total;
     loading.value = false;
 };
+const loading1 = ref(false)
+let resolvePromise: ((value: boolean) => void) | null = null;
+
+const beforeChange1 = (): Promise<boolean> => {
+    loading1.value = true;
+    return new Promise((resolve) => {
+        // 存储 resolve 以便在 confirm/cancel 时调用
+        resolvePromise = resolve;
+    });
+};
+const beforeChange2 = (row): Promise<boolean> => {
+    offOrNoTemps(row)
+    return new Promise((resolve) => {
+        resolve(true); // 手动返回 resolved Promise
+    });
+};
+const offOrNoTemps = async (row) => {
+    const res = await offOrNoTemp({
+        trainingId: row.id,
+        tempStatus: row.tempStatus == '1' ? '0' : '1'
+    })
+}
+
+
+const confirmEvent = (row) => {
+    if (resolvePromise) {
+        loading1.value = false;
+        resolvePromise(true); // 允许切换
+        resolvePromise = null; // 清空引用
+        offOrNoTemps(row)
+    }
+};
 
+const cancelEvent = () => {
+    if (resolvePromise) {
+        loading1.value = false;
+        resolvePromise(false); // 阻止切换
+        resolvePromise = null; // 清空引用
+    }
+};
 /** 搜索按钮操作 */
 const handleQuery = () => {
     queryParams.value.pageNum = 1;
@@ -134,10 +215,11 @@ const resetQuery = () => {
 };
 // 上架会议
 const trainingOn = async (row: any) => {
-    ElMessageBox.confirm(`确认要上架该会议吗?`, '上架提示', {
-        confirmButtonText: '确认',
-        cancelButtonText: '取消',
-        type: 'warning'
+    ElMessageBox.confirm(`上架前请确保会议信息、会议限制及要收集的报名信息正确,避免开始报名后信息错误的情况出现!`, '上架提示', {
+        confirmButtonText: '确认上架',
+        cancelButtonText: '我再看看',
+        type: 'warning',
+        customClass: 'custom-message-box', // 添加自定义类名
     }).then(async () => {
         const res = await publishTraining(row?.id);
         if (res) {
@@ -192,6 +274,12 @@ const trainingSignIn = (row: any) => {
     rowInfo.value = { ...row };
     showSignIn.value = true;
 };
+const temporary = (row: any) => {
+    temporaryRegistration.value = { ...row };
+    console.log(temporaryRegistration.value, 'temporaryRegistration.value');
+
+    showTemporary.value = true;
+};
 onMounted(() => {
     getMeetingCount();
     getList();

+ 7 - 1
src/views/training/models/index.ts

@@ -1 +1,7 @@
-export { default as SignInCode } from './sign-in-code.vue'; // 查看签到码
+export { default as SignInCode } from './sign-in-code.vue'; // 查看签到码
+export { default as MeetingDetailInfo } from './meeting-detail-info.vue';
+export { default as MeetingDetailattend } from './meeting-detail-attend.vue';
+export { default as TemporaryRegistration } from './temporary-registration.vue'; // 查看签到码
+export { default as MeetingCustom } from './meeting-custom.vue'; // 查看签到码
+export { default as registrationInfo } from './registration-info.vue'; 
+export { default as meetingCustomPreview } from './meeting-custom-preview.vue'; 

+ 146 - 0
src/views/training/models/meeting-custom-preview.vue

@@ -0,0 +1,146 @@
+<template>
+    <div class="pd-10 pl-15 border1">
+        <template v-if="field.type === '1' || field.type === '7' || field.type === '10'">
+            <div v-if="field.name.includes('text1-')" prop="label">
+                <el-tag type="info" size="small">单行文本</el-tag>
+                <div class="d-flex flex-cln pb-5 pt-5">
+                    <div class="pb-5 f-s-16 f-w-6 c-#606266">{{ field.label }}</div>
+                    <el-input class="pd-5 disabled-input" :placeholder="'请输入'" style="width: 340px; pointer-events: none ;" />
+                </div>
+            </div>
+            <div v-else-if="field.name.includes('text2-')" prop="label">
+                <el-tag type="info" size="small">多行文本</el-tag>
+                <div class="d-flex flex-cln pb-5 pt-5">
+                    <div class="pb-5 f-s-16 f-w-6 c-#606266">{{ field.label }}</div>
+                    <el-input
+                        class="pd-5 disabled-input"
+                        :placeholder="'请输入'"
+                        style="width: 340px; pointer-events: none ;"
+                        type="textarea"
+                        :autosize="{
+                            minRows: 4, maxRows: 7
+                        }"
+                    />
+                </div>
+            </div>
+            <div v-else-if="field.name.includes('desc1-')" prop="defValue" :rules="[{ required: true, message: '请输入描述', trigger: 'blur' }]">
+                <el-tag type="info" size="small">文本描述</el-tag>
+                <div class="pb-5 f-s-16 f-w-6 c-#606266 pt-5">{{ field.defValue }}</div>
+                <!-- <el-input
+                    class="pd-5 no-disabled-input"
+                    v-model="field.defValue"
+                    :placeholder="'请输入'"
+                    type="textarea"
+                    :autosize="{
+                            minRows: 4, maxRows: 7
+                        }"
+                    style="width: 340px;font-weight: 600;"
+                /> -->
+            </div>
+            <div class="d-flex flex-cln" v-else>
+                <div class="pb-5 f-s-16 f-w-6 c-#606266">{{ field.label }}</div>
+                <el-input class="pd-5 disabled-input" :placeholder="'请输入'" style="width: 340px; pointer-events: none ;" />
+            </div>
+        </template>
+        <template v-if="field.type === '4' || field.type === '5'">
+            <div v-if="field.name.includes('rad-')" prop="label" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
+                <div class="d-flex flex-cln">
+                    <div><el-tag type="info" size="small">单选</el-tag></div>
+                    <div class="pb-5 pt-5 f-s-16 f-w-6 c-#606266">{{ field.label }}</div>
+                </div>
+            </div>
+            <div
+                v-if="field.name.includes('rad-')"
+                v-for="(item, index) in field.options"
+                :key="index"
+                :prop="'options.' + index + '.label'"
+                :rules="{
+                        required: true,
+                        message: '请输入选项内容',
+                        trigger: 'blur',
+                    }"
+            >
+                <div class="pd-1 p-rtv d-flex a-c">
+                    <el-radio-group v-model="field.value" style="pointer-events: none ;">
+                        <el-radio :label="1"><span></span></el-radio>
+                    </el-radio-group>
+                    <div class="f-s-14 f-w-5 c-#606266">{{ item.label }}</div>
+                </div>
+            </div>
+            <div v-if="field.name.includes('che-')" prop="label">
+                <div class="d-flex flex-cln">
+                    <div><el-tag type="info" size="small">多选</el-tag></div>
+                    <div class="pb-5 pt-5 f-s-16 f-w-6 c-#606266">{{ field.label }}</div>
+                </div>
+            </div>
+            <div
+                v-if="field.name.includes('che-')"
+                v-for="(item, index) in field.options"
+                :key="index"
+                :prop="'options.' + index + '.label'"
+                :rules="{
+                        required: true,
+                        message: '请输入选项内容',
+                        trigger: 'blur',
+                    }"
+            >
+                <div class="pd-1 p-rtv d-flex a-c">
+                    <el-checkbox class="pd-5" :label="1" style="pointer-events: none ;"><span></span></el-checkbox>
+                    <div class="f-s-14 f-w-5 c-#606266">{{ item.label }}</div>
+                </div>
+            </div>
+            <div v-if="field.name.includes('sex-')">
+                <div class="pb-5 f-s-16 f-w-6 c-#606266">{{ field.label }}</div>
+                <el-radio-group>
+                    <el-radio value="1" v-for="(item, index) in field.options" :key="index">
+                        {{ item.label}}
+                    </el-radio>
+                </el-radio-group>
+            </div>
+        </template>
+        <template v-if="field.type === '3'">
+            <div class="pb-5 f-w-6 f-s-16 c-#606266">出生日期</div>
+            <el-time-select v-if="field.label == '出生日期'" style="width: 340px" start="08:30" step="00:15" end="18:30" placeholder="请输入" />
+        </template>
+        <template v-if="field.type === '6' || field.type === '8' || field.type === '9'">
+            <div prop="label">
+                <div class="d-flex flex-cln">
+                    <div>
+                        <el-tag type="info" class="" size="small" v-if="field.name.includes('pic1-')">图片</el-tag>
+                        <el-tag type="info" size="small" v-if="field.name.includes('file1-')">文件</el-tag>
+                        <el-tag type="info" size="small" v-if="field.name.includes('pic2-')">图文描述</el-tag>
+                    </div>
+                    <div class="pb-5 f-s-16 f-w-6 c-#606266 pt-5">{{ field.label }}</div>
+                    <!-- <el-input ref="titleInput" class="pd-5 no-disabled-input" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px;font-size: 16px;" clearable /> -->
+                </div>
+            </div>
+            <ImageUpload v-if="!field.name.includes('pic2')" v-model="field.defValue" :limit="1" isString class="pl-5 noup" :isShowTip="false"></ImageUpload>
+            <div prop="defValue" v-if="field.name.includes('pic2')">
+                <!-- <div>
+                    <ImageUpload v-model="field.defValue" :limit="3" isString class="pl-5 up" :isShowTip="false"></ImageUpload>
+                </div> -->
+                <div class="d-flex flex-wrap" v-if="field.defValue">
+                    <template v-for="(item, index) in field.defValue.split(',')" :key="index">
+                        <div class="mr-10 mb-10">
+                            <ImagePreview :src="item" :width="100" :height="100"></ImagePreview>
+                        </div>
+                    </template>
+                </div>
+            </div>
+            <div class="f-s-12 c-333 pl-5 pt-15" v-if="!field.name.includes('pic2')">参会者点击可上传文件/图片</div>
+            <div class="f-s-12 c-999 pl-5">单个图片/文件大小不超过10MB;</div>
+            <div class="f-s-12 c-999 pl-5">支持格式:gif、png、jpg、jpeg、bmp、doc、docx、pdf、xls、xlsx、ppt.pptx、txt、zip、gzip、rar、mp4、mov</div>
+        </template>
+    </div>
+</template>
+<script setup lang="ts">
+const prop = defineProps<{
+    field: any // 使用any简化类型,实际应为FieldDefinition
+}>()
+</script>
+<style scoped>
+.border1 {
+    border: 1px solid transparent;
+    background-color: #fff;
+}
+</style>

+ 283 - 0
src/views/training/models/meeting-custom.vue

@@ -0,0 +1,283 @@
+<template>
+    <div class="pd-10 pl-15 border1 drag-handle">
+        <el-form ref="formRef" label-width="auto" label-position="top" :model="field" :rules="rules" :scroll-to-error="true">
+            <template v-if="field.type === '1' || field.type === '7' || field.type === '10'">
+                <el-form-item v-if="field.name.includes('text1-')" prop="label">
+                    <div class="d-flex flex-cln w-100% " >
+                        <el-tag type="info" size="small" style="width: 70px;" class="drag-handle">单行文本</el-tag>
+                        <div class="pb-5 f-s-16 f-w-6 c-#606266 " @click="changeShowtitle()" v-show="!showtitle">{{ field.label }}</div>
+                        <el-input ref="titleInput" class="pd-5 no-disabled-input no-drag" v-show="showtitle || !field.label" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px;font-size: 16px;" clearable @blur="showtitle = false" />
+                    </div>
+                </el-form-item>
+                <el-input v-if="field.name.includes('text1-')" class="pd-5 disabled-input" :placeholder="'请输入'" style="width: 340px;margin-top: -8px; pointer-events: none ;" />
+                <el-form-item v-if="field.name.includes('text2-')" prop="label">
+                    <div class="d-flex flex-cln w-100%">
+                        <el-tag type="info" size="small" style="width: 70px;">多行文本</el-tag>
+                        <div class="d-flex flex-cln">
+                            <div class="pb-5 f-s-16 f-w-6 c-#606266" @click="changeShowtitle()" v-show="!showtitle">{{ field.label }}</div>
+                            <el-input ref="titleInput" class="pd-5 no-disabled-input no-drag" v-show="showtitle || !field.label" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px;font-size: 16px;" clearable @blur="showtitle = false" />
+                        </div>
+                    </div>
+                </el-form-item>
+                <el-input
+                    v-if="field.name.includes('text2-')"
+                    class="pd-5 disabled-input"
+                    :placeholder="'请输入'"
+                    style="width: 340px; pointer-events: none ;margin-top: -8px;"
+                    type="textarea"
+                    :autosize="{
+                            minRows: 4, maxRows: 7
+                        }"
+                />
+                <el-form-item v-if="field.name.includes('desc1-')" prop="defValue" :rules="[{ required: true, message: '请输入描述', trigger: 'blur' }]">
+                    <el-tag type="info" size="small">文本描述</el-tag>
+                    <el-input
+                        class="pd-5 no-disabled-input no-drag"
+                        v-model="field.defValue"
+                        :placeholder="'请输入'"
+                        type="textarea"
+                        :autosize="{
+                            minRows: 4, maxRows: 7
+                        }"
+                        style="width: 340px;font-weight: 600;"
+                    />
+                </el-form-item>
+                <div class="d-flex flex-cln" v-if="!['desc1-', 'text'].some(str => field.name.includes(str))">
+                    <div class="pb-5 f-s-16 f-w-6 c-#606266">{{ field.label }}</div>
+                    <el-input class="pd-5 disabled-input" :placeholder="'请输入'" style="width: 340px; pointer-events: none ;" />
+                </div>
+            </template>
+            <template v-if="field.type === '4' || field.type === '5'">
+                <el-form-item v-if="field.name.includes('rad-')" prop="label" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
+                    <div class="d-flex flex-cln w-100%">
+                        <div><el-tag type="info" size="small">单选</el-tag></div>
+                        <div class="pb-5 f-s-16 f-w-6 c-#606266" @click="changeShowtitle()" v-show="!showtitle">{{ field.label }}</div>
+                        <el-input ref="titleInput" class="pd-5 no-disabled-input no-drag" v-show="showtitle || !field.label" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px" clearable @blur="showtitle = false" />
+                    </div>
+                </el-form-item>
+                <el-form-item
+                    v-if="field.name.includes('rad-')"
+                    v-for="(item, index) in field.options"
+                    :key="index"
+                    :prop="'options.' + index + '.label'"
+                    :rules="{
+                        required: true,
+                        message: '请输入选项内容',
+                        trigger: 'blur',
+                    }"
+                >
+                    <div class="pd-1 p-rtv">
+                        <el-radio-group v-model="field.value" style="pointer-events: none ;">
+                            <el-radio :label="1"><span></span></el-radio>
+                        </el-radio-group>
+                        <el-input class="no-drag" type="text" v-model="item.label" style="width: 310px" :placeholder="'请输入选项内容'" @input="val => { item.label = val; item.value = val; }" />
+                        <el-icon class="ml-10 delete-btn" color="red" @click="removeOption(index)">
+                            <Close />
+                        </el-icon>
+                    </div>
+                </el-form-item>
+                <el-button v-if="field.name.includes('rad-')" type="primary" plain class="w-100% mt-10" @click="addOption()">新增选项</el-button>
+                <el-form-item v-if="field.name.includes('che-')" prop="label">
+                    <div class="d-flex flex-cln w-100%">
+                        <div><el-tag type="info" size="small">多选</el-tag></div>
+                        <div class="pb-5 f-s-16 f-w-6 c-#606266" @click="changeShowtitle()" v-show="!showtitle">{{ field.label }}</div>
+                        <el-input ref="titleInput" class="pd-5 no-disabled-input no-drag" v-show="showtitle || !field.label" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px" clearable @blur="showtitle = false" />
+                    </div>
+                </el-form-item>
+                <el-form-item
+                    v-if="field.name.includes('che-')"
+                    v-for="(item, index) in field.options"
+                    :key="index"
+                    :prop="'options.' + index + '.label'"
+                    :rules="{
+                        required: true,
+                        message: '请输入选项内容',
+                        trigger: 'blur',
+                    }"
+                >
+                    <div class="pd-1 p-rtv">
+                        <el-checkbox class="pd-5" :label="1" style="pointer-events: none ;"><span></span></el-checkbox>
+                        <el-input class="no-drag" type="text" v-model="item.label" style="width: 310px" :placeholder="'请输入选项内容'" @input="val => { item.label = val; item.value = val; }" />
+                        <el-icon class="ml-10 delete-btn" color="red" @click="removeOption(index)">
+                            <Close />
+                        </el-icon>
+                    </div>
+                </el-form-item>
+                <el-button v-if="field.name.includes('che-')" type="primary" plain class="w-100% mt-10" @click="addOption()">新增选项</el-button>
+                <div v-if="field.name.includes('sex-')">
+                    <div class="pb-5 f-s-16 f-w-6">{{ field.label }}</div>
+                    <el-radio-group :disabled="true">
+                        <el-radio value="1" v-for="(item, index) in field.options" :key="index">
+                            {{ item.label}}
+                        </el-radio>
+                    </el-radio-group>
+                </div>
+            </template>
+            <template v-if="field.type === '3'">
+                <div class="pb-5 f-w-6 f-s-16">出生日期</div>
+                <el-time-select v-if="field.label == '出生日期'" style="width: 340px;pointer-events: none " start="08:30" step="00:15" end="18:30" placeholder="请输入" />
+            </template>
+            <template v-if="field.type === '6' || field.type === '8' || field.type === '9'">
+                <el-form-item prop="label">
+                    <div class="d-flex flex-cln w-100%">
+                        <div>
+                            <el-tag type="info" class="" size="small" v-if="field.name.includes('pic1-')">图片</el-tag>
+                            <el-tag type="info" size="small" v-else-if="field.name.includes('file1-')">文件</el-tag>
+                            <el-tag type="info" size="small" v-else>图文描述</el-tag>
+                        </div>
+                        <div class="pb-5 f-s-16 f-w-6 c-#606266" @click="changeShowtitle()" v-show="!showtitle">{{ field.label }}</div>
+                        <el-input ref="titleInput" class="pd-5 no-disabled-input no-drag" v-show="showtitle || !field.label" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px;font-size: 16px;" clearable @blur="showtitle = false" />
+                    </div>
+                </el-form-item>
+                <ImageUpload v-if="!field.name.includes('pic2')" v-model="field.defValue" :limit="1" isString class="pl-5 noup" :isShowTip="false"></ImageUpload>
+                <el-form-item prop="defValue" v-if="field.name.includes('pic2')">
+                    <div>
+                        <ImageUpload v-model="field.defValue" :limit="3" isString class="pl-5 up" :isShowTip="false" :isShowPrompt="true"></ImageUpload>
+                    </div>
+                </el-form-item>
+                <div class="f-s-12 c-333 pl-5 pt-15" v-if="!field.name.includes('pic2')">参会者点击可上传文件/图片</div>
+                <div class="f-s-12 c-999 pl-5" v-if="!field.name.includes('pic2')">单个图片/文件大小不超过10MB;</div>
+                <div class="f-s-12 c-999 pl-5" v-if="field.name.includes('pic2')">单个图片大小不超过10MB;</div>
+                <div class="f-s-12 c-999 pl-5" v-if="!field.name.includes('pic2')">支持格式:gif、png、jpg、jpeg、bmp、doc、docx、pdf、xls、xlsx、ppt.pptx、txt、zip、gzip、rar、mp4、mov</div>
+                <div class="f-s-12 c-999 pl-5" v-if="field.name.includes('pic2')">支持格式:png、jpg、jpeg</div>
+            </template>
+        </el-form>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import { ElInput, ElFormItem } from 'element-plus'
+const prop = defineProps<{
+    field: any // 使用any简化类型,实际应为FieldDefinition
+}>()
+const formRef = ref()
+const rules = computed(() => {
+    const rules: any = {}
+    rules.label = [{ required: true, message: '请输入标题', trigger: 'change' }]
+    rules.defValue = [{ required: true, message: '请上传封面图', trigger: 'change' }]
+    return rules
+})
+const removeOption = (index: number) => {
+    prop.field.options.splice(index, 1)
+}
+const showtitle = ref(false)
+const titleInput = ref(null)
+const changeShowtitle = ()=>{
+    showtitle.value = true
+  nextTick(() => {
+    titleInput.value.focus()
+  })
+}
+const addOption = () => {
+    if (!prop.field.options) {
+        prop.field.options = []
+    }
+    prop.field.options.push({
+        label: '新增选项',
+        value: `新增选项`
+    })
+}
+defineExpose({
+    formRef,  // 暴露表单引用
+    validate: () => formRef.value.validate() // 暴露验证方法
+})
+</script>
+
+<style scoped>
+.meeting-custom {
+    padding: 10px
+}
+
+.border1 {
+    border: 1px solid #f2f2f2;
+    background-color: #fff;
+}
+
+.border2 {
+    border: 1px solid #f2f2f2;
+    border-radius: 50%;
+}
+
+.delete-btn {
+    position: absolute;
+    top: 15px;
+    right: 10px;
+    padding: 0 0px;
+    font-size: 12px;
+}
+
+.no-interaction {
+    pointer-events: none;
+}
+
+.no-interaction .el-input__inner {
+    background-color: #fff;
+    color: #606266;
+    cursor: default;
+}
+
+.no-interaction:hover {
+    cursor: default;
+}
+
+:deep(.disabled-input .el-input__inner) {
+    cursor: not-allowed;
+    /* 鼠标变成禁止符号 */
+    background-color: #fff !important;
+    /* 避免默认的灰色背景 */
+    color: #606266;
+    /* 保持文字颜色 */
+    border-color: #dcdfe6;
+    /* 保持边框颜色 */
+
+}
+
+:deep(.disabled-input .el-textarea__inner) {
+    cursor: not-allowed;
+    /* 鼠标变成禁止符号 */
+    background-color: #fff;
+    /* 避免默认的灰色背景 */
+    color: #606266;
+    /* 保持文字颜色 */
+    border-color: #dcdfe6;
+    /* 保持边框颜色 */
+    /* font-weight: bold !important; */
+}
+
+/* 去除 hover 效果 */
+/* .disabled-input .el-input__inner:hover {
+    border-color: #dcdfe6 !important;
+} */
+
+:deep(.no-disabled-input .el-input__inner) {
+    font-weight: bold !important;
+    
+}
+:deep(.no-disabled-input .el-textarea__inner){
+    font-weight: bold !important;
+    font-size: 16px;
+}
+:deep(.no-disabled-input .el-input__wrapper) {
+    /* box-shadow: none !important; */
+    font-weight: bold !important;
+    border-radius: 0;
+    border: 1px solid transparent !important;
+    /* 透明边框,避免高度变化 */
+    font-size: 16px;
+}
+
+:deep(.no-disabled-input .el-input__wrapper.is-focus) {
+    /* box-shadow: none !important; */
+    border: 1px solid #dcdfe6 !important;
+}
+
+:deep(.up .el-upload.el-upload--picture-card) {
+    border-color: #333
+}
+
+:deep(.noup .el-upload.el-upload--picture-card) {
+    border-color: transparent;
+    pointer-events: none;
+}
+</style>

+ 280 - 0
src/views/training/models/meeting-detail-attend.vue

@@ -0,0 +1,280 @@
+<template>
+    <div class="pd-16" style="overflow: auto;">
+        <div class="d-flex mb-16 flex-cln">
+            <div class="info-title">可参会单位类型</div>
+            <div class="bg-#fafafa pd-20">
+                <template v-for="item, index in form?.conditions?.typeCheck" :key="index">
+                    <span class="pr-5" v-if="item.check == '1'">
+                        {{ selectDictLabel(lm_check_join_type, item?.vipLevel)}}
+                    </span>
+                </template>
+            </div>
+            <div class="info-title">报名限制条件</div>
+            <div class="bg-#fafafa pd-20" v-if="form?.conditions?.totalCheck == '0' || !form?.conditions?.totalCheck">无</div>
+            <div class="bg-#fafafa pd-20 d-flex flex-cln" v-else>
+                <div class="pd-5 pb-15" v-if="form?.conditions?.total">报名总人数 : {{ form?.conditions?.total }}人</div>
+                <div class="d-flex">
+                    <div>
+                        <template v-for="(item, index) in form?.conditions?.levelCheck" :key="index">
+                            <div class="d-flex a-c pd-5" v-if="+item.check">
+                                <span>
+                                    所有
+                                    <span class="f-w-6">
+                                        {{ selectDictLabels(lm_check_join_type, item.vipLevel, ',') }}
+                                    </span>
+                                    参会人数 ≤{{ item.total }}
+                                </span>
+                            </div>
+                        </template>
+                    </div>
+                    <div>
+                        <template v-for="(item, index) in form?.conditions?.cpyCheck" :key="index">
+                            <div class="d-flex a-c pd-5" v-if="+item.check">
+                                <span>
+                                    <span class="f-w-6">{{ selectDictLabels(lm_check_join_type, item.vipLevel, ',') }}</span>
+                                    限制每家单位人数≤{{ item.total }}
+                                </span>
+                            </div>
+                        </template>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="d-flex mb-16 ">
+            <div class="info-title">
+                <span>参会人员信息</span>
+                <span class="f-s-14 c-666">(提交报名:{{ form?.submitCount || 0 }}人 | 审核通过:{{ form?.joinCount || 0 }}人 | 签到:{{ form?.signCount || 0 }}人 | 领取证书:{{ form?.certCount || 0 }}人)</span>
+            </div>
+        </div>
+        <div class="d-flex j-sb">
+            <div>
+                <searchTabs v-if="form?.conditions?.totalCheck == '1'" v-model="queryParams.res" @change="handleQuery" :list="tabs" key-label="name" key-count="num" key-value="type"></searchTabs>
+            </div>
+            <span style="width: 1px;"></span>
+            <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
+                <el-form-item label="姓名:" prop="name">
+                    <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable style="width: 180px" />
+                </el-form-item>
+                <el-form-item label="企业名称:" prop="company">
+                    <el-input v-model="queryParams.company" placeholder="请输入企业名称" clearable style="width: 180px" />
+                </el-form-item>
+                <el-form-item label="参会状态:" prop="signupStatus">
+                    <el-select v-model="queryParams.signupStatus" placeholder="请选择参会状态" clearable style="width: 180px">
+                        <el-option v-for="item in lm_training_signup_status_list" :key="item.value" :label="item.label" :value="item.value" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                    <el-button icon="Download" @click="exportSearch">导出参会人员信息</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <vxe-table :loading="loading" border :data="list" min-height="0">
+            <!-- 序号 -->
+            <vxe-column type="seq" width="60" title="序号" align="center" />
+            <vxe-column title="企业名称" field="company" min-width="100" :formatter="colNoData" />
+            <vxe-column title="单位类型" field="vipLevel" min-width="100" :formatter="colNoData" align="center">
+                <template #default="{ row }">
+                    <div class="d-flex a-c j-c">
+                        <DictTag :options="lm_check_join_type" :value="row?.vipLevel"></DictTag>
+                    </div>
+                </template>
+            </vxe-column>
+            <vxe-column title="姓名" field="name" min-width="100" :formatter="colNoData" />
+            <vxe-column title="职务" field="position" min-width="100" :formatter="colNoData" />
+            <vxe-column title="联系方式" field="contact" min-width="100" :formatter="colNoData" />
+            <vxe-column title="备注" field="remark" min-width="100" :formatter="colNoData" />
+            <vxe-column title="报名时间" align="center" field="createTime" min-width="100" :formatter="colNoData" />
+            <vxe-column title="报名信息" min-width="100" fixed="right">
+                <template #default="{ row }">
+                    <div class="c-s-p"><u @click="checkRegostrationInfo(row)">查看报名信息</u></div>
+                </template>
+            </vxe-column>
+            <vxe-column title="参会状态" min-width="100" fixed="right">
+                <template #default="{ row }">
+                    <DictTag :options="lm_training_signup_status_list" :value="row?.signupStatusForPc"></DictTag>
+                </template>
+            </vxe-column>
+            <vxe-column v-if="form?.conditions?.totalCheck == '1'" title="审核状态" min-width="100" fixed="right">
+                <template #default="{ row }">
+                    <div class="d-flex a-c ">
+                        <DictTag :class="{ 'c-red': row?.res === '0' }" :options="cpy_res_status" :value="row?.res" />
+                        <el-tooltip class="box-item" effect="dark" :content="row?.msg" placement="top">
+                            <el-icon v-show="row?.res == '2'">
+                                <QuestionFilled />
+                            </el-icon>
+                        </el-tooltip>
+                    </div>
+                </template>
+            </vxe-column>
+            <vxe-column v-if="form?.conditions?.totalCheck == '1'" title="操作" width="250" align="center" fixed="right">
+                <template #default="{ row }">
+                    <el-button type="danger" size="small" v-if="row?.res !== '0'" @click="openDialog(row)" style="color: white">重审</el-button>
+                    <el-button size="small" color="#33aeeb" @click="openDialog(row)" style="color: white" v-else>审核</el-button>
+                </template>
+            </vxe-column>
+        </vxe-table>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </div>
+    <el-dialog v-model="dialogVisible" title="审核信息" width="500" center>
+        <el-form ref="formRef" :model="fromvalue" :rules="rules" label-width="80px">
+            <el-form-item label="审核结果" prop="res">
+                <el-radio-group v-model="fromvalue.res">
+                    <el-radio value="1" border>通过</el-radio>
+                    <el-radio value="2" border :class="{ 'reject-radio': fromvalue.res === '2' }">不通过</el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <el-form-item label="理由" prop="msg" :rules="fromvalue.res === '2' ? rules.msgRequired : rules.msgOptional">
+                <el-input v-model="fromvalue.msg" type="textarea" :rows="4" placeholder="请填写不通过的理由" maxlength="50" show-word-limit />
+            </el-form-item>
+        </el-form>
+        <template #footer>
+            <div class="dialog-footer">
+                <el-button @click="dialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="signupApprovals()">确认</el-button>
+            </div>
+        </template>
+    </el-dialog>
+    <registrationInfo v-if="showTemporary" v-model:show="showTemporary" :info="temporaryRegistration"></registrationInfo>
+</template>
+<script setup name="MeetingDetailInfo" lang="ts">
+import { exportTrainingMembers, signupApproval, signupCount, trainingMembers } from '@/api/training';
+import { colNoData } from '@/utils/noData';
+import { searchTabs } from '@/views/models';
+import { debounce } from 'lodash';
+import { onMounted, reactive, ref } from 'vue';
+import registrationInfo from './registration-info.vue';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { cpy_res_status, lm_training_signup_status_list, lm_check_join_type } = toRefs<any>(proxy?.useDict('cpy_res_status', 'lm_training_signup_status_list', 'lm_check_join_type'));
+// 获取详情
+const props = defineProps({
+    form: {
+        type: Object,
+        default: () => ({})
+    }
+});
+
+const rules = reactive({
+    res: [
+        { required: true, message: '请选择审核结果', trigger: 'blur' }
+    ],
+    msgRequired: [ // For "不通过" (value="2")
+        { required: true, message: '请填写不通过的理由', trigger: 'blur' }
+    ],
+    msgOptional: [ // For "通过" (value="1")
+        { required: false, trigger: 'blur' }
+    ]
+})
+const showTemporary = ref(false);
+const dialogVisible = ref(false);
+const temporaryRegistration = ref();
+const fromvalue = ref({
+    targetId: '',
+    msg: ''
+})
+const checkRegostrationInfo = (row) => {
+    temporaryRegistration.value = row;
+    showTemporary.value = true;
+};
+const openDialog = (row) => {
+    fromvalue.value.targetId = row.id; // 保存当前行数据
+    fromvalue.value.msg = ''
+    dialogVisible.value = true;
+};
+const query = useRoute().query;
+
+const queryParams = ref<any>({
+    pageNum: 1,
+    pageSize: 10,
+    trainingId: query?.id || '',
+});
+const loading = ref(false);
+const total = ref(0);
+const list = ref<any>([]);
+const tabs = ref([]);
+const formRef = ref()
+const getExpertPersonCount = async () => {
+    const res = await signupCount(query?.id);
+    if (res?.code === 200) {
+        tabs.value = res.data;
+    }
+};
+const getList = async () => {
+    loading.value = true;
+    const res = await trainingMembers(queryParams.value);
+    if (!res || res.code !== 200) return;
+    list.value = res.rows;
+    console.log(list.value);
+
+    total.value = res.total;
+    loading.value = false;
+};
+const signupApprovals = async () => {
+    await formRef.value.validate()
+    const res = await signupApproval(fromvalue.value);
+    dialogVisible.value = false;
+    handleQuery();
+};
+const handleQuery = () => {
+    queryParams.value.pageNum = 1;
+    getList();
+    getExpertPersonCount()
+};
+const queryFormRef = ref<ElFormInstance>();
+const resetQuery = () => {
+    queryFormRef.value?.resetFields();
+    handleQuery();
+};
+const exportSearch = debounce(() => {
+    const params = {
+        ...queryParams.value
+    };
+    delete params.pageNum;
+    delete params.pageSize;
+    exportTrainingMembers(params);
+}, 500);
+onMounted(() => {
+    getList();
+    getExpertPersonCount();
+});
+</script>
+<style scoped lang="scss">
+.tabs-item {
+    margin-right: 20px;
+    padding: 8px 20px;
+    font-size: 14px;
+    border-color: #d7d7d7;
+    border-style: solid;
+    border-width: 1px 1px 0 1px;
+    cursor: pointer;
+    user-select: none;
+
+    &.checked {
+        color: #fff;
+        border-color: var(--el-color-primary);
+        background-color: var(--el-color-primary);
+    }
+}
+
+.border-botttom {
+    border-bottom: 1px solid #d7d7d7;
+}
+
+.reject-radio :deep(.el-radio__label) {
+    color: #F56C6C;
+}
+
+.reject-radio :deep(.el-radio__inner) {
+    border-color: #F56C6C;
+    background: #F56C6C;
+}
+
+.reject-radio :deep(.el-radio__border) {
+    border-color: #F56C6C;
+}
+
+:deep(.reject-radio.el-radio.is-bordered.is-checked) {
+    border-color: #F56C6C !important;
+}
+</style>

+ 213 - 0
src/views/training/models/meeting-detail-info.vue

@@ -0,0 +1,213 @@
+<template>
+    <div class="flex1 over-auto">
+        <div class="pd-16 ov-hd">
+            <div class="info-title f-s-18 c-333 f-w-7 pb-10">会议基本信息</div>
+            <el-descriptions :column="4">
+                <el-descriptions-item min-width="100px" label="会议名称:">{{ form?.trainingName || '-' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="会议时间:">{{ form?.trainingStart }}~{{ form?.trainingEnd}}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="报名时间:">{{ form?.signupStart }}~{{ form?.signupEnd }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="会议方式:">
+                    {{ selectDictLabel(lm_training_join_type, form?.joinType) || '-'}}
+                </el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="会议地点:">{{ form?.trainingLocation || '-' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="联系人:">{{ form?.contactName || '-' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="联系电话:">{{ form?.tel || '-' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="可参会单位类型:">
+                    <template v-for="item,index in form.conditions.typeCheck" :key="index">
+                        <span class="pr-5" v-if="item.check =='1'">{{ selectDictLabel(lm_check_join_type, item?.vipLevel) || '-'}}</span>
+                    </template>
+                </el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="报名人数:" v-if="form?.conditions.totalCheck == '1'">限制{{form?.conditions.total }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="报名人数:" v-else>不限制</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="限制条件:" v-if="form?.conditions">
+                    {{ form?.conditions?.levelTotalCheck == '1'?'按单位类型限制' : '' }}
+                    {{form.conditions.cpyTotalCheck == '1'? '按每家企业人数限制' : '' }}
+                    <span v-if="form?.conditions?.levelTotalCheck !== '1' && form.conditions.cpyTotalCheck !== '1'">{{ '-' }}</span>
+                </el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="是否电子手签:">{{ form?.eleSignature == '1' ? "是" : '否' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="是否发放积分:">{{ form?.pointsFlag == '1' ? "是" : '否' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="每成功参会(签到成功)1人发放积分数:">{{ form?.points || '-' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="创建人:">{{ form?.createByName || '-' }}</el-descriptions-item>
+                <el-descriptions-item min-width="100px" label="创建时间:">{{ form?.createTime || '-' }}</el-descriptions-item>
+            </el-descriptions>
+            <div class="d-flex f-s-14 c-666 mb-10">
+                <div class="flex1">
+                    <div class="c-333 mb-10">
+                        会议详情:
+                        <span v-if="!form?.description">-</span>
+                    </div>
+                    <div v-if="form?.description">{{ form?.description || '-' }}</div>
+                </div>
+                <div v-if="form?.attachments" class="flex1">
+                    <div class="c-333 mb-10">
+                        与会须知:
+                        <span v-if="!form?.notice">-</span>
+                    </div>
+                    <div v-if="form?.notice">{{ form?.notice || '-' }}</div>
+                </div>
+            </div>
+            <div class="d-flex f-s-14 c-666 mb-10">
+                <div class="flex1">
+                    <div class="c-333 mb-10">
+                        会议备注:
+                        <span v-if="!form?.remark">-</span>
+                    </div>
+                    <div v-if="form?.remark">{{ form?.remark || '-' }}</div>
+                </div>
+                <div v-if="form?.attachments" class="flex1">
+                    <div class="c-333 mb-10">
+                        相关文件:
+                        <span v-if="form?.attachments.length==0">-</span>
+                    </div>
+                    <FileLook v-if="form?.attachments.length!==0" v-model="form.attachments" :span="12"></FileLook>
+                </div>
+            </div>
+            <div class="d-flex f-s-14 c-666">
+                <div class="flex1">
+                    <div class="c-333 mb-10">
+                        微信群聊二维码:
+                        <span v-if="!form?.wechatQrCodeUrl">-</span>
+                    </div>
+                    <div class="d-flex flex-wrap" v-if="form?.wechatQrCodeUrl ">
+                        <ImagePreview :src="form?.wechatQrCodeUrl " :width="100" :height="100"></ImagePreview>
+                    </div>
+                </div>
+                <div class="flex1">
+                    <div class="c-333 mb-10">
+                        封面图:
+                        <span v-if="!form?.coverImgUrl">-</span>
+                    </div>
+                    <div v-if="form?.coverImgUrl">
+                        <ImagePreview :src="form?.coverImgUrl" :width="100" :height="100"></ImagePreview>
+                    </div>
+                </div>
+                <div class="flex1">
+                    <div class="c-333 mb-10">
+                        会议图:
+                        <span v-if="form?.trainingImg.length == 0">-</span>
+                    </div>
+                    <div class="d-flex flex-wrap" v-if="form?.trainingImg.length !== 0">
+                        <template v-for="(item, index) in form?.trainingImg" :key="index">
+                            <div class="mr-10 mb-10">
+                                <ImagePreview :src="item" :width="100" :height="100"></ImagePreview>
+                            </div>
+                        </template>
+                    </div>
+                </div>
+            </div>
+            <div class="f-s-14">
+                <div class="c-333 mb-10">是否颁发证书: {{ +form?.certFlag ? '是' : '否' }}</div>
+                <vxe-table v-if="+form?.certFlag" border :data="form?.certificateInfo" min-height="0">
+                    <!-- 序号 -->
+                    <vxe-column type="seq" width="60" title="序号" align="center" />
+                    <vxe-column title="证书名称" min-width="100">
+                        <template #default="{ row }">
+                            <DictTag :options="lm_training_cert" :value="row?.certType"></DictTag>
+                        </template>
+                    </vxe-column>
+                    <vxe-column title="证书模板">
+                        <template #default="{ row }">
+                            <ImagePreview :src="row?.certImgUrl" :width="100"></ImagePreview>
+                        </template>
+                    </vxe-column>
+                </vxe-table>
+            </div>
+            <div class="f-s-14">
+                <div class="c-333 mb-10">报名信息:</div>
+                <div class="w-400 h-700 border over-auto">
+                    <div class="pd-10 border bg-#fafafa">
+                        <div class="pt-10  f-s-20 f-w-6 d-flex j-c a-c flex-cln ">报名信息</div>
+                        <div class="f-s-12 f-w-4 d-flex j-start c-red">此页面为意向人报名时所见页面:</div>
+                    </div>
+                    <template v-for="(item, index) in fixedField" :key="index">
+                        <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
+                            <span class="c-#606266 f-s-16 f-w-6">{{ item.label }}</span>
+                            <span class="f-s-14">请输入</span>
+                        </div>
+                    </template>
+                    <template v-for="(field, index) in fields" :key="index">
+                        <div class="meeting-custom-wrapper">
+                            <meetingCustomPreview :field="field" style="pointer-events: none" v-if="field" />
+                        </div>
+                    </template>
+                    <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
+                        <span class="c-#606266 f-s-16 f-w-6">备注</span>
+                        <span>请输入</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup name="MeetingDetailInfo" lang="ts">
+import { FileLook } from '@/views/models';
+import { onMounted, ref } from 'vue';
+import meetingCustomPreview from './meeting-custom-preview.vue';
+import { FieldDefinition } from './type';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { lm_training_join_type, lm_check_join_type, lm_training_cert, lm_training_status, lm_training_signup_status_list } = toRefs<any>(proxy?.useDict('lm_training_join_type', 'lm_check_join_type', 'lm_training_cert', 'lm_training_status', 'lm_training_signup_status_list'));
+const props = defineProps({
+    form: {
+        type: Object,
+        default: () => ({})
+    },
+});
+const fields = ref<FieldDefinition[]>([])
+const fixedField = ref<FieldDefinition[]>([{
+    name: `ent-${generateSecureRandomString()}`,
+    label: '企业名称', type: '1',
+    required: '1', readonly: '0',
+}, {
+    name: `name-${generateSecureRandomString()}`,
+    label: '姓名', type: '1', readonly: '0',
+    required: '1'
+}, {
+    name: `pos-${generateSecureRandomString()}`,
+    label: '职务', type: '1', readonly: '0',
+    required: '1'
+}, {
+    name: `Con-${generateSecureRandomString()}`,
+    label: '联系方式', type: '1', readonly: '0',
+    required: '1'
+}])
+function generateSecureRandomString(length = 8) {
+    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+    const randomValues = new Uint32Array(length);
+    window.crypto.getRandomValues(randomValues);
+    let result = '';
+    randomValues.forEach((value) => {
+        result += chars[value % chars.length];
+    });
+    return result;
+}
+onMounted(() => {
+    fields.value = props.form.questions
+});
+</script>
+<style scoped lang="scss">
+.tabs-item {
+    margin-right: 20px;
+    padding: 8px 20px;
+    font-size: 14px;
+    border-color: #d7d7d7;
+    border-style: solid;
+    border-width: 1px 1px 0 1px;
+    cursor: pointer;
+    user-select: none;
+
+    &.checked {
+        color: #fff;
+        border-color: var(--el-color-primary);
+        background-color: var(--el-color-primary);
+    }
+}
+
+.border-botttom {
+    border-bottom: 1px solid #d7d7d7;
+}
+
+.border {
+    border: 1px solid #dcdfe6;
+}
+</style>

+ 366 - 0
src/views/training/models/meeting-editors.vue

@@ -0,0 +1,366 @@
+<template>
+    <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer
+        @hide="close" :width="1200" :z-index="1002">
+        <div class="bg-fff flex1 ov-hd d-flex flex-cln" style="height: 80vh">
+            <div class="flex1 over-auto d-flex">
+                <div class="w-300 ">
+                    <div class="info-title f-s-18 c-333 f-w-7">常用信息</div>
+                    <div class="pd-10 f-s-16">个人信息</div>
+                    <div>
+                        <el-button class="mb-18 ml-10 w-80" @click="addCustoms({
+                            name: `id-${generateSecureRandomString()}`,
+                            label: '身份证', type: '1', required: '1', readonly: '0',
+                            pattern: '^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$'
+                        })">
+                            身份证
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `birth-${generateSecureRandomString()}`, readonly: '0', required: '1',
+                            label: '出生日期', type: '3'
+                        })">
+                            出生日期
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `sex-${generateSecureRandomString()}`,
+                            label: '性别', type: '4', required: '1', readonly: '0',
+                            options: [
+                                { label: '男', value: '男' }, { label: '女', value: '女' }
+                            ]
+                        })">
+                            性别
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `old-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '年龄', type: '1',
+                        })">
+                            年龄
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `edu-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '学历', type: '1',
+                        })">
+                            学历
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `uni-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '大学', type: '1',
+                        })">
+                            大学
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `pro-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '专业', type: '1',
+                        })">
+                            专业
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `ind-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '行业', type: '1',
+                        })">
+                            行业
+                        </el-button>
+                    </div>
+                    <div class="pd-10 f-s-16">联系方式</div>
+                    <div>
+                        <el-button class="mb-18 ml-10 w-80" @click="addCustoms({
+                            name: `wx-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '微信号', type: '1',
+                        })">
+                            微信号
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `qq-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: 'QQ号', type: '1',
+                        })">
+                            QQ号
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `eml-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '邮箱', type: '1',
+                            pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
+                        })">
+                            邮箱
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `add-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '地址', type: '1',
+                        })">
+                            地址
+                        </el-button>
+                    </div>
+                    <div class="info-title f-s-18 c-333 f-w-7">自定义信息</div>
+                    <div class="pd-10 f-s-16">选择</div>
+                    <div>
+                        <el-button class="mb-18 ml-10 w-80" @click="addCustoms({
+                            name: `rad-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '请输入标题', type: '4', options: [
+                                { label: '选项一', value: '选项一' }, { label: '选项二', value: '选项二' }, { label: '选项三', value: '选项三' }
+                            ]
+                        })">
+                            单选
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `che-${generateSecureRandomString()}`, required: '1', readonly: '0',
+                            label: '请输入标题', type: '5', options: [
+                                { label: '选项一', value: '选项一' }, { label: '选项二', value: '选项二' }, { label: '选项三', value: '选项三' }
+                            ]
+                        })">
+                            多选
+                        </el-button>
+                    </div>
+                    <div class="pd-10 f-s-16">文本输入</div>
+                    <div>
+                        <el-button class="mb-18 ml-10 w-80" @click="addCustoms({
+                            name: `text1-${generateSecureRandomString()}`, type: '1', required: '1', readonly: '0', label: '请输入标题',
+                        })">
+                            单行文本
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `text2-${generateSecureRandomString()}`, type: '7', required: '1', readonly: '0', label: '请输入标题',
+                        })">
+                            多行文本
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `desc1-${generateSecureRandomString()}`, type: '10', readonly: '1', required: '0', label: '文本描述', defValue: '请输入内容',
+                        })">
+                            文本描述
+                        </el-button>
+                    </div>
+                    <div class="pd-10 f-s-16">其他</div>
+                    <div>
+                        <el-button class="mb-18 ml-10 w-80" @click="addCustoms({
+                            name: `pic1-${generateSecureRandomString()}`, type: '8', required: '1', readonly: '0', label: '请输入标题'
+                        })">
+                            图片
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `file1-${generateSecureRandomString()}`, type: '6', required: '1', readonly: '0', label: '请输入标题'
+                        })">
+                            文件
+                        </el-button>
+                        <el-button class="mb-18 w-80" @click="addCustoms({
+                            name: `pic2-${generateSecureRandomString()}`, type: '9', required: '0', readonly: '0', label: '请输入内容'
+                        })">
+                            图文描述
+                        </el-button>
+                    </div>
+                </div>
+                <div class="w-300"></div>
+                <div class="w-400 border mt-100 over-auto">
+                    <div class="pd-10 border bg-#fafafa">
+                        <div class="pt-10  f-s-20 f-w-6 d-flex j-c a-c flex-cln ">报名信息</div>
+                        <div class="f-s-12 f-w-4 d-flex j-start c-red">此页面为意向人报名时所见页面:</div>
+                    </div>
+                    <template v-for="(item, index) in fixedField" :key="index">
+                        <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
+                            <span class="c-#606266 f-s-16 f-w-6">{{ item.label }}</span>
+                            <span>(固定字段,不可编辑)</span>
+                        </div>
+                    </template>
+                    <VueDraggable ref="el" v-model="fields" handle=".drag-handle" :filter="'.no-drag'">
+                        <template v-for="(field, index) in fields" :key="index">
+                            <div class="meeting-custom-wrapper" :class="{ 'active-border': activeField === field }"
+                                @click="setActive(field)" :ref="el => setFieldRef(el, index)">
+                                <MeetingCustom :field="field" ref="childRef" v-if="field" />
+                                <img class="ml-10 delete-btn c-s-p" :src="shanchu" @click.stop="removeField(index)" />
+                            </div>
+                        </template>
+                    </VueDraggable>
+                    <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
+                        <span class="c-#606266 f-s-16 f-w-6">备注</span>
+                        <span>(固定字段,不可编辑)</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <template #footer>
+            <div class="d-flex a-c j-c">
+                <el-button @click="cancel">取消</el-button>
+                <el-button @click="saveArray" type="primary">保存</el-button>
+            </div>
+        </template>
+    </vxe-modal>
+</template>
+
+<script setup name="lmmeeting-meeting-add" lang="ts">
+import shanchu from '@/assets/images/shanchu.png';
+import { cloneDeep } from 'lodash';
+import { onMounted, reactive, ref, watch } from 'vue';
+import { VueDraggable } from 'vue-draggable-plus';
+import { useRouter } from 'vue-router';
+// 需要添加以下导入
+import { propTypes } from '@/utils/propTypes';
+import MeetingCustom from './meeting-custom.vue';
+import { FieldDefinition } from './type';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { lm_training_join_type, yes_no, lm_training_cert, vip_level } = toRefs<any>(proxy?.useDict('lm_training_join_type', 'yes_no', 'lm_training_cert', 'vip_level'));
+const props = defineProps({
+    field: propTypes.any,
+    show: propTypes.bool.def(false),
+    info: propTypes.array.def([]),
+    dict: propTypes.object.def({}),
+    width: propTypes.number.def(1200),
+    title: propTypes.string.def('编辑报名需收集信息')
+});
+const dialogVisible = ref(false);
+const fields = ref<FieldDefinition[]>([]);
+const activeField = ref<any>(null);
+const childRef = ref();
+const emit = defineEmits(['update:show', 'close', 'success', 'update:info']);
+const close = () => {
+    emit('update:show', false);
+    emit('close', false);
+};
+const setActive = (field: any) => {
+    activeField.value = field;
+};
+const fieldRefs = ref([])
+
+// 设置字段引用
+const setFieldRef = (el, index) => {
+    if (el) {
+        fieldRefs.value[index] = el
+    }
+}
+
+const addCustoms = async (value) => {
+    const newField = new FieldDefinition(value)
+    fields.value.push(newField)
+    activeField.value = newField
+    // 等待DOM更新
+    await nextTick()
+
+    // 获取新增字段的索引
+    const newIndex = fields.value.length - 1
+
+    // 滚动到新增字段位置
+    if (fieldRefs.value[newIndex]) {
+        fieldRefs.value[newIndex].scrollIntoView({
+            behavior: 'smooth',
+            block: 'nearest'
+        })
+    }
+}
+function generateSecureRandomString(length = 8) {
+    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+    const randomValues = new Uint32Array(length);
+    window.crypto.getRandomValues(randomValues);
+    let result = '';
+    randomValues.forEach((value) => {
+        result += chars[value % chars.length];
+    });
+    return result;
+}
+
+const removeField = (index: number) => {
+    fields.value.splice(index, 1);
+    if (activeField.value === fields[index]) {
+        activeField.value = null;
+    }
+};
+const router = useRouter();
+const route = useRoute();
+
+const rules = reactive({
+    // 自动生成全部
+    trainingName: [{ required: true, message: '请输入会议名称', trigger: 'blur' }],
+    trainingTime: [{ required: true, message: '请选择培训时间', trigger: 'blur' }],
+    joinType: [{ required: true, message: '请选择培训方式', trigger: 'change' }],
+    trainingLocation: [{ required: true, message: '请输入培训地点', trigger: 'blur' }],
+    certFlag: [{ required: true, message: '请选择是否颁发证书', trigger: 'change' }],
+    certificateInfo: [{ required: true, message: '请选择证书名称', trigger: 'change' }],
+    description: [{ required: true, message: '请输入培训详情', trigger: 'blur' }],
+    // coverImg: [{ required: true, message: '请上传封面图', trigger: 'change' }],
+    // trainingImg: [{ required: true, message: '请上传会议图', trigger: 'change' }],
+    contactName: [{ required: true, message: '请输入会议联系人', trigger: 'blur' }],
+    tel: [{ required: true, message: '请输入联系电话', trigger: 'blur' }]
+});
+const formRef = ref();
+const cancel = () => {
+    emit('update:show', false);
+    emit('close', false);
+};
+const saveArray = async () => {
+    try {
+        // 使用 Promise.all 等待所有验证完成
+        await Promise.all(childRef.value.map((i) => i.validate()));
+        // 所有验证完成后,存储数据并关闭
+        // localStorage.setItem('RegistrationInformation', JSON.stringify(fields.value));
+        // props.info = fields.value
+        emit('update:info', fields.value);
+        emit('update:show', false);
+        emit('close', false);
+    } catch (error) {
+        console.error('验证或保存失败:', error);
+    }
+};
+const fixedField = ref<FieldDefinition[]>([
+    {
+        name: `ent-${generateSecureRandomString()}`,
+        label: '企业名称',
+        type: '1',
+        required: '1',
+        readonly: '0'
+    },
+    {
+        name: `name-${generateSecureRandomString()}`,
+        label: '姓名',
+        type: '1',
+        readonly: '0',
+        required: '1'
+    },
+    {
+        name: `pos-${generateSecureRandomString()}`,
+        label: '职务',
+        type: '1',
+        readonly: '0',
+        required: '1'
+    },
+    {
+        name: `Con-${generateSecureRandomString()}`,
+        label: '联系方式',
+        type: '1',
+        readonly: '0',
+        required: '1'
+    }
+]);
+onMounted(() => {
+    fields.value = cloneDeep(props.info);
+});
+watch(
+    () => props.show,
+    (val) => {
+        dialogVisible.value = val;
+    },
+    { immediate: true }
+);
+</script>
+<style scoped>
+.border {
+    border: 1px solid #f2f2f2;
+    border-radius: 6px;
+}
+
+.border1 {
+    border: 1px solid #f2f2f2;
+}
+
+.active-border {
+    border: 1px solid #b6e7d9;
+    border-radius: 4px;
+    box-shadow: 0px 0px 3px 0px #009932;
+}
+
+.meeting-custom-wrapper {
+    position: relative;
+    /* padding: 10px; */
+}
+
+.delete-btn {
+    position: absolute;
+    top: 15px;
+    right: 5px;
+    padding: 0 5px;
+    font-size: 12px;
+}
+</style>

+ 74 - 0
src/views/training/models/registration-info.vue

@@ -0,0 +1,74 @@
+<template>
+    <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer @hide="close" :width="width" :z-index="1002">
+        <template #default>
+            <div>
+                <div v-if="info" ref="codeImgRef" style="width: 400px; margin: 0 auto;">
+                    <div class="mb-6 f-w-6 f-s-16 c-333">报名信息</div>
+                    <div class="mb-6">企业名称:{{ info?.company || '-' }}</div>
+                    <div class="mb-6">姓名:{{ info?.name || '-' }}</div>
+                    <div class="mb-6">职务:{{ info?.position || '-' }}</div>
+                    <div class="mb-6">联系电话:{{ info?.contact || '-' }}</div>
+                    <div v-for="item, index in info.questionAnswer" :key="index" class="d-flex" :style="['6', '8', '9', '10'].includes(item.type) ? { 'flex-direction': 'column' } : {}">
+                        <div class="d-flex mb-6">
+                            <div class=" f-s-14" v-if="item.type !== '10' && item.type !== '9'">{{ item.label }}:</div>
+                        </div>
+                        <div class="ov-hd mb-6  f-s-14" v-if="!['5', '6', '8', '9', '10'].includes(item.type)">
+                            {{item?.value || '-' }}
+                        </div>
+                        <div class="ov-hd mb-6  f-s-14" v-if="item.type == '5'">
+                            {{item?.value.join(',') || '-' }}
+                        </div>
+                        <div class="ov-hd mb-6  f-s-14" v-if="item.type == '6'">
+                            <FileLook v-model="item.value" :span="24"></FileLook>
+                        </div>
+                        <div class=" mb-6  f-s-14 d-flex flex-wrap" v-if="item.type == '8'">
+                            <template v-for="(items, index) in item.value" :key="index">
+                                <div class="mr-10 mb-10">
+                                    <ImagePreview :src="items" :width="100" :height="100"></ImagePreview>
+                                </div>
+                            </template>
+                        </div>
+                        <!-- <div class="ov-hd mb-6  f-s-14 pt-10 pb-10 d-flex flex-wrap" v-if="item.type == '9'">
+                            <template v-for="(items, index) in item.defValue.split(',')" :key="index">
+                                <div class="mr-10 mb-10">
+                                    <ImagePreview :src="items" :width="100" :height="100"></ImagePreview>
+                                </div>
+                            </template>
+                        </div> -->
+                        <!-- <div class="ov-hd mb-6  f-s-14 pt-10 pb-10" v-if="item.type == '10'">
+                            {{ item?.defValue }}
+                        </div> -->
+                    </div>
+                </div>
+            </div>
+            <div class="d-flex j-c"></div>
+        </template>
+    </vxe-modal>
+</template>
+
+<script setup name="SignInCode" lang="ts">
+import { propTypes } from '@/utils/propTypes';
+import { FileLook } from '@/views/models';
+const emit = defineEmits(['update:show', 'close', 'success']);
+const props = defineProps({
+    show: propTypes.bool.def(false),
+    title: propTypes.string.def(' '),
+    width: propTypes.number.def(500),
+    info: propTypes.any.def(null),
+    dict: propTypes.object.def({})
+});
+
+const dialogVisible = ref(false);
+const close = () => {
+    emit('update:show', false);
+    emit('close', false);
+};
+const codeImgRef = ref<HTMLElement | null>(null);
+watch(
+    () => props.show,
+    (val) => {
+        dialogVisible.value = val;
+    },
+    { immediate: true }
+);
+</script>

+ 10 - 6
src/views/training/models/sign-in-code.vue

@@ -1,15 +1,17 @@
 <template>
-    <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer @hide="close" :width="width" :z-index="1002">
+    <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer
+        @hide="close" :width="width" :z-index="1002">
         <template #default>
             <div>
-                <div v-if="info" ref="codeImgRef" style="width: 400px; margin: 0 auto;">
+                <div v-if="info" ref="codeImgRef" style="width: 400px; margin: 0 auto;padding: 10px;">
                     <div class="mb-6 f-w-6 f-s-16 c-333">{{ info?.trainingName }}</div>
-                    <div class="mb-6">培训时间:{{ info?.trainingStart  }}~{{ info?.trainingEnd }}</div>
-                    <div class="mb-6">培训方式:{{ selectDictLabel(dict.dm_training_join_type, info.joinType) }}</div>
+                    <div class="mb-6">会议时间:{{ info?.trainingStart.slice(0, -3) }}~{{ info?.trainingEnd.slice(0, -3) }}</div>
+                    <div class="mb-6">会议方式:{{ selectDictLabel(dict.lm_training_join_type, info.joinType) }}</div>
                     <div class="mb-6">联系电话:{{ info?.tel }}</div>
                     <div>签到二维码</div>
-                    <div class="d-flex j-c a-c pd-20">
-                        <vueQr :text="VITE_APP_SHARE_QR_CODE_URL + '/meeting-sign-in?meetid=' + info?.id" :size="300"></vueQr>
+                    <div class="d-flex j-c a-c pd-20 ">
+                        <vueQr :text="VITE_APP_SHARE_QR_CODE_URL + '/meeting-sign-in?meetid=' + info?.id" :size="300">
+                        </vueQr>
                     </div>
                 </div>
             </div>
@@ -61,3 +63,5 @@ watch(
     { immediate: true }
 );
 </script>
+<style scoped>
+</style>

+ 63 - 0
src/views/training/models/temporary-registration.vue

@@ -0,0 +1,63 @@
+<template>
+    <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer @hide="close" :width="width" :z-index="1002">
+        <template #default>
+            <div>
+                <div v-if="info" ref="codeImgRef" style="width: 400px; margin: 0 auto;">
+                    <div class="mb-6 f-w-6 f-s-16 c-333">临时报名通道</div>
+                    <div class="mb-6">会议名称:{{ info?.trainingName }}</div>
+                    <div class="mb-6">会议时间:{{ info?.trainingStart }}~{{ info?.trainingEnd }}</div>
+                    <div class="mb-6">会议方式:{{ selectDictLabel(dict.lm_training_join_type, info.joinType) }}</div>
+                    <div class="mb-6">联系电话:{{ info?.tel }}</div>
+                    <div class="d-flex j-c a-c pd-10">
+                        <vueQr :text="VITE_APP_SHARE_QR_CODE_URL + '/enroll?id=' + info?.id + '&tempJoin=1'" :size="300" colorDark="orange"></vueQr>
+                    </div>
+                </div>
+            </div>
+
+            <div class="d-flex j-c">
+                <!-- <el-button @click="saveImg">保存临时报名二维码</el-button> -->
+            </div>
+        </template>
+    </vxe-modal>
+</template>
+
+<script setup name="SignInCode" lang="ts">
+import { propTypes } from '@/utils/propTypes';
+import vueQr from 'vue-qr/src/packages/vue-qr.vue';
+import html2canvas from 'html2canvas';
+const emit = defineEmits(['update:show', 'close', 'success']);
+const props = defineProps({
+    show: propTypes.bool.def(false),
+    title: propTypes.string.def(' '),
+    width: propTypes.number.def(500),
+    info: propTypes.any.def(null),
+    dict: propTypes.object.def({})
+});
+const VITE_APP_SHARE_QR_CODE_URL = ref(import.meta.env.VITE_APP_SHARE_QR_CODE_URL);
+const dialogVisible = ref(false);
+const close = () => {
+    emit('update:show', false);
+    emit('close', false);
+};
+const codeImgRef = ref<HTMLElement | null>(null);
+const saveImg = () => {
+    html2canvas(codeImgRef.value, {
+        useCORS: true,
+        allowTaint: false,
+        scale: 2
+    }).then((canvas) => {
+        const url = canvas.toDataURL('image/png');
+        const a: any = document.createElement('a');
+        a.download = `${props?.info?.trainingName}-临时报名二维码.png`;
+        a.href = url;
+        a.click();
+    });
+};
+watch(
+    () => props.show,
+    (val) => {
+        dialogVisible.value = val;
+    },
+    { immediate: true }
+);
+</script>

+ 63 - 0
src/views/training/models/type.ts

@@ -0,0 +1,63 @@
+/**
+ * 表单字段类型定义
+ */
+export enum FieldType {
+    STRING = 1, // 字符串
+    NUMBER = 2, // 数字
+    DATE = 3, // 日期
+    RADIO = 4, // 单选
+    CHECKBOX = 5, // 多选
+    FILE = 6, // 文件上传
+    CustomInfor = 7, //多行文本
+    PICK = 8, //图片上传
+    PICKANDDEC = 9, //图文描述
+    TEXTANDDEC = 10 //文本描述
+}
+
+/**
+ * 表单字段定义类
+ */
+export class FieldDefinition {
+    /** 字段名称 (可选) */
+    name?: string;
+
+    /** 字段类型 (可选) */
+    type?: string;
+
+    /** 字段标签 (可选) */
+    label?: string;
+
+    /** 是否必填 (可选) */
+    required?: string;
+
+    /** 是否只读 (可选) */
+    readonly?: string;
+
+    /** 可选项(可选) */
+    options?: Array<{
+        label: string;
+        value: string;
+    }>;
+
+    /** 正则表达式 (可选) */
+    pattern?: string;
+
+    /** 默认值 (可选) */
+    defvalue?: string | any[];
+
+    /** 字段排序 (可选) */
+    sort?: number;
+
+    /** 字段描述 (可选) */
+    description?: string;
+
+    constructor(data?: Partial<FieldDefinition>) {
+        // 初始化所有字段为undefined
+        Object.keys(this).forEach((key) => {
+            this[key] = undefined;
+        });
+        if (data) {
+            Object.assign(this, data);
+        }
+    }
+}

+ 4 - 4
vite/plugins/unocss.ts

@@ -6,8 +6,8 @@ export default () => {
         rules: [
             [/^mg-([\.\d]+)$/, ([_, num]) => ({ margin: `${num}px` })],
             [/^pd-([\.\d]+)$/, ([_, num]) => ({ padding: `${num}px` })],
-            [/^bg-([\w-]+)$/, ([_, color]) => ({ 'background-color': color })],
-            [/^c-#([\w-]+)$/, ([_, color]) => ({ color: color })],
+            [/^bg-(#[\w-]+)$/, ([_, color]) => ({ 'background-color': color })],
+            [/^c-(#[\w-]+)$/, ([_, color]) => ({ color: color })],
             [/^bc-([\w-]+)$/, ([_, color]) => ({ 'border-color': color })],
             [/^rounded-([\.\d]+)$/, ([_, num]) => ({ 'border-radius': `${num}px` })],
             // 下边距
@@ -71,8 +71,8 @@ export default () => {
             [/^pr-([\.\d]+)$/, ([_, num]) => ({ 'padding-right': `${num}px` })],
             // 字体大小
             [/^f-s-([\.\d]+)$/, ([_, num]) => ({ 'font-size': `${num}px` })],
-            // 字体加粗
-            [/^f-w-([\.\d]+)$/, ([_, num]) => ({ 'font-weight': `${num}` })],
+            // 字体加粗 f-w-5 f-w-500
+            [/^f-w-([\.\d]+)$/, ([_, num]) => ({ 'font-weight': `${+num < 100 ? +num * 100 : num}` })],
             // 宽
             [/^w-([\.\d]+)$/, ([_, num]) => ({ width: `${num}px` })],
             // 高