lisy 5 mesiacov pred
rodič
commit
977bacb406

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

BIN
src/assets/images/shanchu.png


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

@@ -88,9 +88,8 @@
                                                         <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" />
+                                                        <el-input class="pl-10" v-model="item.total" maxlength="20"
+                                                            placeholder="请输入报名人数" :disabled="!+item.check" />
                                                     </div>
                                                 </template>
                                             </div>
@@ -108,7 +107,7 @@
                                                             :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" />
+                                                            placeholder="请输入报名人数" :disabled="!+item.check" />
                                                     </div>
                                                 </template>
 
@@ -124,7 +123,7 @@
                                                         :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" />
+                                                        placeholder="请输入报名人数" :disabled="!+item.check" />
                                                 </div>
                                             </template>
                                         </div>
@@ -136,7 +135,7 @@
                                                         :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" />
+                                                        placeholder="请输入报名人数" :disabled="!+item.check" />
                                                 </div>
                                             </template>
                                         </div>
@@ -198,68 +197,136 @@
                             </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
+                    <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>
+                                    </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, 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>({
@@ -304,14 +371,13 @@ const rules = reactive({
     certFlag: [{ required: true, message: '请选择是否颁发证书', trigger: 'change' }],
     certificateInfo: [{ required: true, message: '请选择证书名称', trigger: 'change' }],
     description: [{ required: true, message: '请输入培训详情', trigger: 'blur' }],
+    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 levelTotalCheck = ref("0")
-const cpyTotalCheck = ref("0")
 const save = debounce(async () => {
     await formRef.value.validate();
     const params = {
@@ -321,15 +387,22 @@ const save = debounce(async () => {
         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 = [];
@@ -352,12 +425,27 @@ const getMeetingDetail = async () => {
             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 {

+ 1 - 1
src/views/training/meeting/index.vue

@@ -81,7 +81,7 @@
                         <vxe-column title="临时报名通道" align="center" field="createByName" width="120"
                             :formatter="colNoData">
                             <template #default="{ row }">
-                                <el-popconfirm confirm-button-text="修改" cancel-button-text="取消" title="是否修改临时报名的状态?"
+                                <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"

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

@@ -1,4 +1,5 @@
 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 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>

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

+ 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);
+        }
+    }
+}