huangxw 5 月之前
父節點
當前提交
43fd3be086

+ 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",

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

@@ -80,6 +80,30 @@ 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
     });
 };

二進制
src/assets/images/shanchu.png


+ 304 - 51
src/views/training/meeting-add/index.vue

@@ -22,14 +22,26 @@
                             </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-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>
+                            </el-col>
+                            <el-col :span="6">
+                                <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:ss" 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 dm_training_join_type" :key="item.value"
+                                            :label="item.value">{{ item.label }}</el-radio>
                                     </el-radio-group>
                                 </el-form-item>
                             </el-col>
@@ -40,7 +52,8 @@
                             </el-col>
                             <el-col :span="6">
                                 <el-form-item label="会议联系人" prop="contactName">
-                                    <el-input v-model="form.contactName" maxlength="40" placeholder="请输入会议联系人" clearable />
+                                    <el-input v-model="form.contactName" maxlength="40" placeholder="请输入会议联系人"
+                                        clearable />
                                 </el-form-item>
                             </el-col>
                             <el-col :span="6">
@@ -48,11 +61,103 @@
                                     <el-input v-model="form.tel" maxlength="20" placeholder="请输入联系电话" clearable />
                                 </el-form-item>
                             </el-col>
+                            <el-col :span="6">
+                                <el-form-item label="报名人数" prop="registrantsNumber">
+                                    <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>
+                                        <el-input class="pl-10" v-model="form.conditions.total" maxlength="20"
+                                            placeholder="请输入报名人数" clearable
+                                            :disabled="form.conditions.totalCheck !== '1'" />
+                                    </div>
+                                </el-form-item>
+                            </el-col>
+                            <el-col :span="12" v-if="form.conditions.totalCheck == '1'">
+                                <el-form-item label="限制条件" prop="restrictiveConditions">
+                                    <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">
+                                                        <el-checkbox v-model="item.check" true-value="1" false-value="0"
+                                                            :label="'所有' + selectDictLabels(vip_level, item.vipLevel, ',') + '参会人数≤'"
+                                                            size="large" />
+                                                        <el-input class="pl-10" v-model="item.total" maxlength="20"
+                                                            placeholder="请输入报名人数" :disabled="!+item.check" />
+                                                    </div>
+                                                </template>
+                                            </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">
+                                                        <el-checkbox v-model="item.check"
+                                                            :label="selectDictLabels(vip_level, 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>
+                                    </div>
+                                    <div class="d-flex border">
+                                        <div v-if="form.conditions.levelTotalCheck == '1' && form.conditions.cpyTotalCheck == '1'"
+                                            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">
+                                                    <el-checkbox v-model="item.check"
+                                                        :label="'所有' + selectDictLabels(vip_level, 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 v-if="form.conditions.levelTotalCheck == '1' && form.conditions.cpyTotalCheck == '1'"
+                                            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">
+                                                    <el-checkbox v-model="item.check"
+                                                        :label="selectDictLabels(vip_level, 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>
+                                </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-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-input v-model="form.description" :rows="4" type="textarea"
+                                        placeholder="请输入培训详情" />
                                 </el-form-item>
                             </el-col>
                             <el-col :span="12">
@@ -60,6 +165,11 @@
                                     <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="40"></ImageUpload>
+                                </el-form-item>
+                            </el-col>
                             <el-col :span="12">
                                 <el-form-item label="封面图" prop="coverImg">
                                     <ImageUpload v-model="form.coverImg" :limit="1"></ImageUpload>
@@ -70,74 +180,186 @@
                                     <ImageUpload v-model="form.trainingImg" :fileSize="40"></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>
+                                    <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>此模块报名审核通过后才可查看</div>
+                                    </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% pr-80">
+                            <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 lm_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="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 lm_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>
                             </template>
-                        </template>
+                        </div>
+
+                        <div class="w-50% pl-80 pr-80 d-flex flex-cln j-c a-c">
+                            <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-#BBBBBB">此页面为意向人报名时所见页面:</div>
+                                </div>
+                                <template v-for="(field, index) in fixedField" :key="index">
+                                    <div class="meeting-custom-wrapper">
+                                        <MeetingCustom :field="field" @click="showSignIn = true" v-if="field" />
+                                    </div>
+                                </template>
+                                <template v-for="(field, index) in fields" :key="index">
+                                    <div class="meeting-custom-wrapper" @click.stop="showSignIn = true">
+                                        <MeetingCustom :field="field" style="pointer-events: none" v-if="field" />
+                                    </div>
+                                </template>
+                            </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="lmmeeting-meeting-add" lang="ts">
 import { ref, reactive, onMounted } from 'vue';
 import { debounce } from 'lodash';
 import { useRouter } from 'vue-router';
 import { trainingAdd, trainingDetail, trainingUpdate } from '@/api/training';
+import { checkPermi } from '@/utils/permission';
+import { FieldDefinition, FieldType } from '../models/type'
+// 需要添加以下导入
+import MeetingCustom from '../models/meeting-custom.vue'
+import MeetingEditors from '../models/meeting-editors.vue'
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { dm_training_join_type, yes_no, lm_training_cert } = toRefs<any>(proxy?.useDict('dm_training_join_type', 'yes_no', 'lm_training_cert'));
+const { dm_training_join_type, yes_no, lm_training_cert, vip_level } = toRefs<any>(proxy?.useDict('dm_training_join_type', 'yes_no', 'lm_training_cert', 'vip_level'));
+const fields = ref<FieldDefinition[]>([])
+const changefields = ref(true)
+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'
+}])
+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 = () => {
+    // localStorage.removeItem('RegistrationInformation');
+    router.go(-1)
+}
 const router = useRouter();
 const route = useRoute();
 const form = ref<any>({
-    id: undefined
+    id: undefined,
+    conditions: {
+        levelTotalCheck: '0',
+        cpyTotalCheck: '0',
+        levelCheck: [{
+            vipLevel: '1',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '3',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '5',
+            check: "0",
+            total: ''
+        }],
+        cpyCheck: [{
+            vipLevel: '1',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '3',
+            check: "0",
+            total: ''
+        }, {
+            vipLevel: '5',
+            check: "0",
+            total: ''
+        }]
+    }
 });
 const rules = reactive({
     // 自动生成全部
@@ -148,29 +370,38 @@ const rules = reactive({
     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' }],
+    // 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();
     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) {
+        // localStorage.removeItem('RegistrationInformation');
+
         router.go(-1);
     }
 }, 500);
 const goEditor = () => {
     window.open('https://lm.yujin.shuziyunyao.com/poster#/editor', '_blank');
 }
+
 const addCertInfo = () => {
     if (!form.value.certificateInfo) {
         form.value.certificateInfo = [];
@@ -190,11 +421,33 @@ 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
     }
 };
 onMounted(() => {
     getMeetingDetail();
+    // if (localStorage.getItem('RegistrationInformation')) {
+    //     fields.value = JSON.parse(localStorage.getItem('RegistrationInformation'))
+    // }
 });
+// watch(
+//     () => showSignIn.value,
+//     () => {
+//         if (localStorage.getItem('RegistrationInformation')) {
+//             fields.value = JSON.parse(localStorage.getItem('RegistrationInformation'))
+//         } else {
+//             console.log(fields.value);
+//         }
+
+//     },
+//     { immediate: true }
+// );
 </script>
+<style scoped>
+.border {
+    border: 1px solid #dcdfe6;
+}
+</style>

+ 39 - 117
src/views/training/meeting-detail/index.vue

@@ -1,7 +1,7 @@
 <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="d-flex a-c pd-16">
                 <div class="f-s-20 c-333 f-w-7 mr-10">会议详情</div>
                 <el-button @click="router.go(-1)" type="primary" text>
                     <el-icon>
@@ -10,138 +10,38 @@
                     返回上一级
                 </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="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>
-                    <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-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="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 { MeetingDetailInfo } from '../models';
 import router from '@/router';
 import { exportTrainingMembers, trainingDetailById, trainingMembers } from '@/api/training';
 import { FileLook } from '@/views/models';
 import { colNoData } from '@/utils/noData';
+import MeetingDetailAttend from '../models/meeting-detail-attend.vue'
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { dm_training_join_type, yes_no, lm_training_cert, dm_training_status, dm_training_signup_status_list } = toRefs<any>(proxy?.useDict('dm_training_join_type', 'yes_no', 'lm_training_cert', 'dm_training_status', 'dm_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 () => {
@@ -190,3 +90,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>

+ 106 - 26
src/views/training/meeting/index.vue

@@ -7,16 +7,21 @@
                     <div class="flex1 ov-hd d-flex j-ed">
                         <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
                             <el-form-item label="会议名称" prop="trainingName">
-                                <el-input v-model="queryParams.trainingName" placeholder="搜会议名称" clearable style="width: 160px" @keyup.enter="handleQuery" />
+                                <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-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-select>
                             </el-form-item>
                             <el-form-item label="是否颁发证书" prop="certFlag">
-                                <el-select v-model="queryParams.certFlag" placeholder="请选择是否颁发证书" clearable style="width: 160px" @change="handleQuery">
-                                    <el-option v-for="item in yes_no" :key="item.value" :label="item.label" :value="item.value" />
+                                <el-select v-model="queryParams.certFlag" placeholder="请选择是否颁发证书" clearable
+                                    style="width: 160px" @change="handleQuery">
+                                    <el-option v-for="item in yes_no" :key="item.value" :label="item.label"
+                                        :value="item.value" />
                                 </el-select>
                             </el-form-item>
                             <el-form-item>
@@ -30,16 +35,20 @@
             <div class="d-flex flex1 ov-hd flex-cln pd-16">
                 <div class="d-flex j-sb mb-16">
                     <div>
-                        <searchTabs v-model="queryParams.status" @change="handleQuery" :list="tabs" key-label="name" key-count="num" key-value="type"></searchTabs>
+                        <searchTabs v-model="queryParams.status" @change="handleQuery" :list="tabs" key-label="name"
+                            key-count="num" key-value="type"></searchTabs>
                     </div>
                     <el-button type="primary" @click="router.push({ path: 'lmmeetingadd' })">新增会议</el-button>
                 </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 field="trainingName" title="会议名称" :formatter="colNoData" min-width="150"/>
+                        <vxe-column field="trainingTime" min-width="150" title="培训时间">
+                            <template #default="{ row }">{{ row.trainingStart }}~{{ row.trainingEnd }}</template>
+                        </vxe-column>
+                        <vxe-column field="signupsTime" min-width="150" title="报名时间">
+                            <template #default="{ row }">{{ row.signupStart }}~{{ row.signupEnd }}</template>
                         </vxe-column>
                         <vxe-column field="joinType" title="培训方式" width="80">
                             <template #default="{ row }">
@@ -47,50 +56,83 @@
                             </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" />
+                        <vxe-column field="signCount" title="签到人数" width="60" />
                         <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">
                             <template #default="{ row }">
                                 <DictTag :options="dm_training_status" :value="row?.trainingStatus"></DictTag>
                             </template>
                         </vxe-column>
-                        <vxe-column title="操作" width="300" fixed="right">
+                        <vxe-column field="certFlag" title="签到二维码" width="90" align="center">
                             <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>
+                                <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="临时报名通道" align="center" field="createByName" width="120"
+                            :formatter="colNoData">
+                            <template #default="{ row }">
+                                <el-popconfirm confirm-button-text="修改" cancel-button-text="取消" title="是否修改临时报名的状态?" :disabled="row.trainingStatus == '2'"
+                                    @confirm="confirmEvent(row)" @cancel="cancelEvent">
+                                    <template #reference>
+                                        <el-switch v-model="row.tempStatus" :loading="loading1" active-value="1"
+                                            inactive-value="0" :before-change="beforeChange1" :disabled="row.trainingStatus == '2'"/>
+                                    </template>
+                                </el-popconfirm>
+                                <el-button v-if="row.tempStatus == '1'" 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="340" fixed="right" align="center">
+                            <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="primary" @click="editRow(row)" text>编辑</el-button>
+                                <el-button v-if="!['3'].includes(row?.trainingStatus)" 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="['3'].includes(row?.trainingStatus)" type="primary"
+                                    @click="editRow(row)" text>编辑</el-button>
                                 <span></span>
-                                <el-button @click="router.push({ path: 'lmmeetingdetail', query: { id: row?.id } })" style="color: #0079fe;" text>详情</el-button>
+                                <el-button @click="router.push({ path: 'lmmeetingdetail', query: { id: row?.id } })"
+                                    style="color: #0079fe;" text>人员管理及详情</el-button>
                                 <span></span>
                                 <el-button text type="danger" @click="deleteRow(row)">删除</el-button>
                             </template>
                         </vxe-column>
                     </vxe-table>
                 </div>
-                <pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+                <pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+                    @pagination="getList" />
             </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="{ dm_training_join_type }">
+    </SignInCode>
+    <TemporaryRegistration v-if="showTemporary" v-model:show="showTemporary" :info="temporaryRegistration"
+        :dict="{ dm_training_join_type }">
+    </TemporaryRegistration>
 </template>
 
 <script setup name="meeting" lang="ts">
 import { colNoData, colNoUnm } from '@/utils/noData';
 import { searchTabs } from '@/views/models';
-import { trainingList, queryTrainingCount, trainingDelete, publishTraining, unpublishTraining } from '@/api/training';
+import { trainingList, queryTrainingCount, trainingDelete, publishTraining, unpublishTraining, offOrNoTemp } from '@/api/training';
 import { SignInCode } from '../models';
+import TemporaryRegistration from '../models/temporary-registration.vue'
 
 const router = useRouter();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -111,7 +153,8 @@ const data = reactive<any>({
     },
     rules: {}
 });
-
+const temporaryRegistration = ref()
+const showTemporary = ref(false)
 const { queryParams, form } = toRefs(data);
 /** 查询会员信息列表 */
 const getList = async () => {
@@ -121,7 +164,39 @@ 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 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;
@@ -192,8 +267,13 @@ const trainingSignIn = (row: any) => {
     rowInfo.value = { ...row };
     showSignIn.value = true;
 };
+// 打开临时报名二维码
+const temporary = (row: any) => {
+    temporaryRegistration.value = { ...row };
+    showTemporary.value = true;
+};
 onMounted(() => {
     getMeetingCount();
     getList();
 });
-</script>
+</script>

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

@@ -1 +1,5 @@
-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'; // 查看签到码

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

@@ -0,0 +1,166 @@
+<template>
+    <div class="pd-10 pl-15 border1">
+        <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">
+                    <el-tag type="primary" size="small">单行文本</el-tag>
+                    <el-input class="pd-5" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px" />
+                    <el-input class="pd-5" v-model="field.value" :placeholder="'请输入内容'" style="width: 340px"
+                        :disabled="true" />
+                </el-form-item>
+                <el-form-item v-else-if="field.name.includes('text2-')" prop="label">
+                    <el-tag type="primary" size="small">多行文本</el-tag>
+                    <el-input class="pd-5" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px" />
+                    <el-input class="pd-5" v-model="field.value" :placeholder="'请输入内容'" type="textarea" :disabled="true"
+                        style="width: 340px" />
+                </el-form-item>
+                <el-form-item v-else-if="field.name.includes('desc1-')" prop="defValue">
+                    <el-tag type="primary" size="small">文本描述</el-tag>
+                    <el-input class="pd-5" v-model="field.defValue" :placeholder="'请输入内容'" type="textarea"
+                        style="width: 340px" />
+                </el-form-item>
+                <div class="d-flex flex-cln" v-else>
+                    <div class="pb-5">{{ field.label }}</div>
+                    <el-input v-model="field.value" :placeholder="'请输入' + field.label" :disabled="true"
+                        style="width: 340px" />
+                </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' }]">
+                    <el-tag type="primary" size="small">单选</el-tag>
+                    <el-input class="pd-5" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px" />
+                </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" :disabled="true">
+                            <el-radio></el-radio>
+                        </el-radio-group>
+                        <el-input type="text" v-model="item.label" style="width: 310px" :placeholder="'请输入选项内容'"
+                            @input="val => { item.label = val; item.value = val; }" />
+                        <img class="ml-10 delete-btn" :src="shanchu" @click="removeOption(index)">
+                    </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">
+                    <el-tag type="primary" size="small">多选</el-tag>
+                    <el-input class="pd-5" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px" />
+                </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" :disabled="true"></el-checkbox>
+                        <el-input type="text" v-model="item.label" style="width: 310px" :placeholder="'请输入选项内容'"
+                            @input="val => { item.label = val; item.value = val; }" />
+                        <img class="ml-10 delete-btn" :src="shanchu" @click="removeOption(index)">
+                    </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">{{ 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">出生日期</div>
+                <el-time-select v-if="field.label == '出生日期'" style="width: 340px" start="08:30" step="00:15" end="18:30"
+                    placeholder="Select time" :disabled="true" />
+            </template>
+            <template v-if="field.type === '6' || field.type === '8' || field.type === '9'">
+                <el-form-item prop="label">
+                    <el-tag type="primary" class="" size="small" v-if="field.name.includes('pic1-')">图片</el-tag>
+                    <el-tag type="primary" size="small" v-else-if="field.name.includes('file1-')">文件</el-tag>
+                    <el-tag type="primary" size="small" v-else>图文描述</el-tag>
+                    <el-input class="pd-5 pl-0" v-model="field.label" :placeholder="'请输入标题'" style="width: 340px"
+                        props="label" />
+                    <div v-if="!field.name.includes('pic2')" class="pd-5 h-50 w-50 bg-#fafafa d-flex a-c j-c"> +
+                    </div>
+                </el-form-item>
+                <el-form-item prop="defValue" v-if="field.name.includes('pic2')">
+                    <div>
+                        <ImageUpload v-model="field.defValue" :limit="3" isString class="pl-5">
+                        </ImageUpload>
+                    </div>
+                </el-form-item>
+                <div class="f-s-12 c-999">参会者点击可上传文件/图片</div>
+                <div class="f-s-12 c-999">单个图片/文件大小不超过100MB;</div>
+                <div class="f-s-12 c-999">
+                    支持格式:gif、png、jpg、jpeg、bmp、doc、docx、pdf、xls、xlsx、ppt.pptx、txt、zip、gzip、rar、mp4、mov</div>
+            </template>
+        </el-form>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { FieldType } from './type'
+import { ref, onMounted } from 'vue';
+import { ElInput, ElFormItem } from 'element-plus'
+import shanchu from '@/assets/images/shanchu.png'
+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 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;
+}
+</style>

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

@@ -0,0 +1,204 @@
+<template>
+    <div class="pd-16">
+        <div class="d-flex mb-16 flex-cln">
+            <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" v-else>
+                <div>总参会人数 ≤ {{ form?.conditions?.total }}</div>
+                <div> <template v-for="(item, index) in form?.conditions?.levelCheck" :key="index">
+                        <div class="d-flex a-c pd-5">
+                            <span v-if="+item.check">所有<span class="f-w-6">{{ selectDictLabels(vip_level, 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">
+                            <span v-if="+item.check"><span class="f-w-6">{{ selectDictLabels(vip_level, item.vipLevel,
+                                ',') }}</span>限制每家单位人数≤{{ item.total
+                                    }}</span>
+                        </div>
+                    </template></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 a-c j-sb">
+            <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>
+            <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 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="vipLevel" min-width="100" :formatter="colNoData" align="center">
+                <template #default="{ row }">
+                    <div class="d-flex a-c j-c">
+                        <DictTag :options="vip_level" :value="row?.signupStatusForPc">
+                        </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 }">
+                    <DictTag :options="dm_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 }">
+                    <DictTag :options="cpy_res_status" :value="row?.res">
+                    </DictTag>
+                </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)">重审</el-button>
+                    <el-button size="small" type="danger" @click="openDialog(row)" 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>
+        <span class="f-s-16 mb-15">请填写审核原因:</span>
+        <el-input v-model="dialoginput" type="textarea" :rows="4"></el-input>
+        <template #footer>
+            <div class="dialog-footer">
+                <el-button type="primary" @click="signupApprovals('1')">通过</el-button>
+                <el-button type="danger" @click="signupApprovals('2')">
+                    不通过
+                </el-button>
+            </div>
+        </template>
+    </el-dialog>
+</template>
+<script setup name="MeetingDetailInfo" lang="ts">
+import { ref, reactive, onMounted } from 'vue';
+import { signupCount, trainingMembers, signupApproval } from '@/api/training';
+import { searchTabs } from '@/views/models';
+import { colNoData } from '@/utils/noData';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { cpy_res_status, dm_training_signup_status_list, vip_level } = toRefs<any>(proxy?.useDict('cpy_res_status', 'dm_training_signup_status_list', 'vip_level'));
+// 获取详情
+const props = defineProps({
+    form: {
+        type: Object,
+        default: () => ({})
+    },
+});
+
+const dialogVisible = ref(false)
+const dialoginput = ref()
+const currentRow = ref()
+const openDialog = (row) => {
+    currentRow.value = row;  // 保存当前行数据
+    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 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;
+    total.value = res.total;
+    loading.value = false;
+};
+const signupApprovals = async (type: String) => {
+    const res = await signupApproval({
+        targetId: currentRow.value.id,
+        res: type,
+        msg: dialoginput.value
+    })
+    dialogVisible.value = false
+    handleQuery()
+}
+const handleQuery = () => {
+    queryParams.value.pageNum = 1;
+    getList();
+};
+const queryFormRef = ref<ElFormInstance>();
+const resetQuery = () => {
+    queryFormRef.value?.resetFields();
+    handleQuery();
+};
+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;
+}
+</style>

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

@@ -0,0 +1,130 @@
+<template>
+    <div class="flex1 over-auto">
+        <div class="pd-16 ov-hd">
+            <div><span class="c-primary">|</span> 会议基本信息</div>
+            <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="报名时间:">{{ form?.signupStart }}~{{ form?.signupEnd
+                }}</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="报名人数:" v-if="form?.conditions.totalCheck
+                    == '1'">限制 {{
+                        form?.conditions.total }}</el-descriptions-item>
+                <el-descriptions-item label="报名人数:" v-else> 不限制</el-descriptions-item>
+                <el-descriptions-item label="限制条件:" v-if="form?.conditions">{{ form?.conditions?.levelTotalCheck == '1'
+                    ?
+                    '按盟员单位等级限制' : '' }} {{
+                        form.conditions.cpyTotalCheck == '1'
+                            ? '按每家企业人数限制' : '' }}</el-descriptions-item>
+                <el-descriptions-item label="是否电子手签:">{{ form?.eleSignature == '1' ? "是" : '否' }}</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 class="flex1">
+                    <div class="c-333 mb-10">会议备注:</div>
+                    <div>{{ form?.remark || '-' }}</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 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?.wechatQrCode" :key="index">
+                            <div class="mr-10 mb-10">
+                                <ImagePreview :src="item" :width="100"></ImagePreview>
+                            </div>
+                        </template>
+                    </div>
+                </div>
+                <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>
+        </div>
+    </div>
+</template>
+
+<script setup name="MeetingDetailInfo" lang="ts">
+import { FileLook } from '@/views/models';
+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 props = defineProps({
+    form: {
+        type: Object,
+        default: () => ({})
+    },
+});
+
+</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>

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

@@ -0,0 +1,304 @@
+<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="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>
+                <div class="flex1 over-auto d-flex">
+                    <div class="w-300 ">
+                        <div class="pd-10 f-s-18 c-333 f-w-7 mr-10"><span class="c-primary pd-10">|</span>常用信息</div>
+                        <div class="pd-10 f-s-16">个人信息</div>
+                        <div class="pl-10 d-flex f-w-w">
+                            <div class="pl-10 d-flex f-w-w">
+                                <el-button class="mb-25 ml-10 w-80" @click="addCustoms({
+                                    name: `id-${generateSecureRandomString()}`,
+                                    label: '身份证', type: '1', required: '0', 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-25 w-80" @click="addCustoms({
+                                    name: `birth-${generateSecureRandomString()}`, readonly: '0', required: '0',
+                                    label: '出生日期', type: '3'
+                                })">出生日期</el-button>
+                                <el-button class="mb-25 w-80" @click="addCustoms({
+                                    name: `sex-${generateSecureRandomString()}`,
+                                    label: '性别', type: '4', required: '0', readonly: '0',
+                                    options: [
+                                        { label: '男', value: '' }, { label: '女', value: '' }
+                                    ]
+                                })">性别</el-button>
+                                <el-button class="mb-25 w-80" @click="addCustoms({
+                                    name: `old-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                    label: '年龄', type: '1',
+                                })">年龄</el-button>
+                                <el-button class="mb-25 w-80" @click="addCustoms({
+                                    name: `edu-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                    label: '学历', type: '1',
+                                })">学历</el-button>
+                                <el-button class="mb-25 w-80" @click="addCustoms({
+                                    name: `uni-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                    label: '大学', type: '1',
+                                })">大学</el-button>
+                                <el-button class="mb-25 w-80" @click="addCustoms({
+                                    name: `pro-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                    label: '专业', type: '1',
+                                })">专业</el-button>
+                                <el-button class="mb-25 w-80" @click="addCustoms({
+                                    name: `ind-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                    label: '行业', type: '1',
+                                })">行业</el-button>
+                            </div>
+                        </div>
+                        <div class="pd-10 f-s-16">联系方式</div>
+                        <div class="pl-10 d-flex f-w-w">
+                            <el-button class="mb-25 ml-10 w-80" @click="addCustoms({
+                                name: `wx-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                label: '微信号', type: '1',
+                            })">微信号</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `qq-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                label: 'QQ号', type: '1',
+                            })">QQ号</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `eml-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                label: '邮箱', type: '1',
+                                pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
+                            })">邮箱</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `add-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                label: '地址', type: '1',
+                            })">地址</el-button>
+                        </div>
+                        <div class="pd-10 f-s-16 c-333 f-w-7 mr-10"><span class="c-primary pd-10">|</span>自定义信息</div>
+                        <div class="pd-10 f-s-16">选择</div>
+                        <div class="pl-10 d-flex f-w-w">
+                            <el-button class="mb-25 ml-10 w-80" @click="addCustoms({
+                                name: `rad-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                label: '', type: '4', options: [
+                                    { label: '', value: '' }, { label: '', value: '' }, { label: '', value: '' }
+                                ]
+                            })">单选</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `che-${generateSecureRandomString()}`, required: '0', readonly: '0',
+                                label: '', type: '5', options: [
+                                    { label: '', value: '' }, { label: '', value: '' }, { label: '', value: '' }
+                                ]
+                            })">多选</el-button>
+                        </div>
+                        <div class="pd-10 f-s-16">文本输入</div>
+                        <div class="pl-10 d-flex f-w-w">
+                            <el-button class="mb-25 ml-10 w-80" @click="addCustoms({
+                                name: `text1-${generateSecureRandomString()}`, type: '1', required: '0', readonly: '0',
+                            })">单行文本</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `text2-${generateSecureRandomString()}`, type: '7', required: '0', readonly: '0',
+                            })">多行文本</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `desc1-${generateSecureRandomString()}`, type: '10', readonly: '1', required: '0', label: '文本描述'
+                            })">文本描述</el-button>
+                        </div>
+                        <div class="pd-10 f-s-16">其他</div>
+                        <div class="pl-10 d-flex f-w-w">
+                            <el-button class="mb-25 ml-10 w-80" @click="addCustoms({
+                                name: `pic1-${generateSecureRandomString()}`, type: '8', required: '0', readonly: '0',
+                            })">图片</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `file1-${generateSecureRandomString()}`, type: '6', required: '0', readonly: '0',
+                            })">文件</el-button>
+                            <el-button class="mb-25 w-80" @click="addCustoms({
+                                name: `pic2-${generateSecureRandomString()}`, type: '9', required: '0', readonly: '0',
+                            })">图文描述</el-button>
+                        </div>
+                    </div>
+                    <div class="w-300"></div>
+                    <div class="w-400 h-700 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-#BBBBBB">此页面为意向人报名时所见页面:</div>
+                        </div>
+                        <template v-for="(item, index) in fixedField" :key="index">
+                            <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
+                                <span>{{ item.label }}</span>
+                                <span>(不可编辑)</span>
+                            </div>
+                        </template>
+                        <VueDraggable ref="el" v-model="fields">
+                            <template v-for="(field, index) in fields" :key="index">
+                                <div class="meeting-custom-wrapper" :class="{ 'active-border': activeField === field }"
+                                    @click="setActive(field)">
+                                    <MeetingCustom :field="field" ref="childRef" v-if="field" />
+                                    <el-button class="delete-btn" type="danger" text @click.stop="removeField(index)">
+                                        删除
+                                    </el-button>
+                                </div>
+                            </template>
+                        </VueDraggable>
+                    </div>
+                </div>
+                <div class="d-flex a-c j-c pd-16">
+                    <el-button @click="cancel">取消</el-button>
+                    <el-button @click="saveArray" type="primary">保存</el-button>
+                </div>
+            </div>
+        </div>
+    </vxe-modal>
+</template>
+
+<script setup name="lmmeeting-meeting-add" lang="ts">
+import { VueDraggable } from 'vue-draggable-plus'
+import { ref, reactive, onMounted, watch } from 'vue';
+import { debounce } from 'lodash';
+import { useRouter } from 'vue-router';
+// 需要添加以下导入
+import MeetingCustom from './meeting-custom.vue'
+import { propTypes } from '@/utils/propTypes';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { dm_training_join_type, yes_no, lm_training_cert, vip_level } = toRefs<any>(proxy?.useDict('dm_training_join_type', 'yes_no', 'lm_training_cert', 'vip_level'));
+import { FieldDefinition, FieldType } from './type'
+const props = defineProps<{
+    field?: any; // 使用 any 简化类型(实际应替换为具体类型,如 FieldDefinition)
+    show?: boolean; // 可选参数,默认 false
+    title?: string; // 可选参数,默认 ' '
+    width?: number; // 可选参数,默认 500
+    info?: any[]; // 可选参数,默认 []
+    dict?: Record<string, any>; // 可选参数,默认 {}
+}>();
+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 addCustoms = (value) => {
+    const newField = new FieldDefinition(value);
+    fields.value.push(newField);
+    activeField.value = newField; // 自动选中新增字段
+};
+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(() => {
+    // if (localStorage.getItem('RegistrationInformation')) {
+    //     fields.value = JSON.parse(localStorage.getItem('RegistrationInformation'))
+    // } else { console.log(props, '00000');
+    //     fields.value = props.info
+    // }
+    fields.value = 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: 5px;
+    right: 5px;
+    padding: 0 5px;
+    font-size: 12px;
+}
+</style>

+ 9 - 5
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">培训时间:{{ info?.trainingStart }}~{{ info?.trainingEnd }}</div>
                     <div class="mb-6">培训方式:{{ selectDictLabel(dict.dm_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>

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

@@ -0,0 +1,65 @@
+<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.dm_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);
+        }
+    }
+}