meeting-editors.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. <template>
  2. <vxe-modal v-model="dialogVisible" :title="title" show-zoom resize show-footer destroy-on-close transfer @hide="close" :width="1200" :z-index="1002">
  3. <div class="bg-fff flex1 ov-hd d-flex flex-cln" style="height: 80vh">
  4. <div class="flex1 over-auto d-flex">
  5. <div class="w-300 ">
  6. <div class="info-title f-s-18 c-333 f-w-7">常用信息</div>
  7. <div class="pd-10 f-s-16">个人信息</div>
  8. <div>
  9. <el-button
  10. class="mb-18 ml-10 w-80"
  11. @click="addCustoms({
  12. name: `id-${generateSecureRandomString()}`,
  13. label: '身份证', type: '1', required: '1', readonly: '0',
  14. 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]$'
  15. })"
  16. >
  17. 身份证
  18. </el-button>
  19. <el-button
  20. class="mb-18 w-80"
  21. @click="addCustoms({
  22. name: `birth-${generateSecureRandomString()}`, readonly: '0', required: '1',
  23. label: '出生日期', type: '3'
  24. })"
  25. >
  26. 出生日期
  27. </el-button>
  28. <el-button
  29. class="mb-18 w-80"
  30. @click="addCustoms({
  31. name: `sex-${generateSecureRandomString()}`,
  32. label: '性别', type: '4', required: '1', readonly: '0',
  33. options: [
  34. { label: '男', value: '男' }, { label: '女', value: '女' }
  35. ]
  36. })"
  37. >
  38. 性别
  39. </el-button>
  40. <el-button
  41. class="mb-18 w-80"
  42. @click="addCustoms({
  43. name: `old-${generateSecureRandomString()}`, required: '1', readonly: '0',
  44. label: '年龄', type: '1',
  45. })"
  46. >
  47. 年龄
  48. </el-button>
  49. <el-button
  50. class="mb-18 w-80"
  51. @click="addCustoms({
  52. name: `edu-${generateSecureRandomString()}`, required: '1', readonly: '0',
  53. label: '学历', type: '1',
  54. })"
  55. >
  56. 学历
  57. </el-button>
  58. <el-button
  59. class="mb-18 w-80"
  60. @click="addCustoms({
  61. name: `uni-${generateSecureRandomString()}`, required: '1', readonly: '0',
  62. label: '大学', type: '1',
  63. })"
  64. >
  65. 大学
  66. </el-button>
  67. <el-button
  68. class="mb-18 w-80"
  69. @click="addCustoms({
  70. name: `pro-${generateSecureRandomString()}`, required: '1', readonly: '0',
  71. label: '专业', type: '1',
  72. })"
  73. >
  74. 专业
  75. </el-button>
  76. <el-button
  77. class="mb-18 w-80"
  78. @click="addCustoms({
  79. name: `ind-${generateSecureRandomString()}`, required: '1', readonly: '0',
  80. label: '行业', type: '1',
  81. })"
  82. >
  83. 行业
  84. </el-button>
  85. </div>
  86. <div class="pd-10 f-s-16">联系方式</div>
  87. <div>
  88. <el-button
  89. class="mb-18 ml-10 w-80"
  90. @click="addCustoms({
  91. name: `wx-${generateSecureRandomString()}`, required: '1', readonly: '0',
  92. label: '微信号', type: '1',
  93. })"
  94. >
  95. 微信号
  96. </el-button>
  97. <el-button
  98. class="mb-18 w-80"
  99. @click="addCustoms({
  100. name: `qq-${generateSecureRandomString()}`, required: '1', readonly: '0',
  101. label: 'QQ号', type: '1',
  102. })"
  103. >
  104. QQ号
  105. </el-button>
  106. <el-button
  107. class="mb-18 w-80"
  108. @click="addCustoms({
  109. name: `eml-${generateSecureRandomString()}`, required: '1', readonly: '0',
  110. label: '邮箱', type: '1',
  111. pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
  112. })"
  113. >
  114. 邮箱
  115. </el-button>
  116. <el-button
  117. class="mb-18 w-80"
  118. @click="addCustoms({
  119. name: `add-${generateSecureRandomString()}`, required: '1', readonly: '0',
  120. label: '地址', type: '1',
  121. })"
  122. >
  123. 地址
  124. </el-button>
  125. </div>
  126. <div class="info-title f-s-18 c-333 f-w-7">自定义信息</div>
  127. <div class="pd-10 f-s-16">选择</div>
  128. <div>
  129. <el-button
  130. class="mb-18 ml-10 w-80"
  131. @click="addCustoms({
  132. name: `rad-${generateSecureRandomString()}`, required: '1', readonly: '0',
  133. label: '请输入标题', type: '4', options: [
  134. { label: '选项一', value: '选项一' }, { label: '选项二', value: '选项二' }, { label: '选项三', value: '选项三' }
  135. ]
  136. })"
  137. >
  138. 单选
  139. </el-button>
  140. <el-button
  141. class="mb-18 w-80"
  142. @click="addCustoms({
  143. name: `che-${generateSecureRandomString()}`, required: '1', readonly: '0',
  144. label: '请输入标题', type: '5', options: [
  145. { label: '选项一', value: '选项一' }, { label: '选项二', value: '选项二' }, { label: '选项三', value: '选项三' }
  146. ]
  147. })"
  148. >
  149. 多选
  150. </el-button>
  151. </div>
  152. <div class="pd-10 f-s-16">文本输入</div>
  153. <div>
  154. <el-button
  155. class="mb-18 ml-10 w-80"
  156. @click="addCustoms({
  157. name: `text1-${generateSecureRandomString()}`, type: '1', required: '1', readonly: '0', label: '请输入标题',
  158. })"
  159. >
  160. 单行文本
  161. </el-button>
  162. <el-button
  163. class="mb-18 w-80"
  164. @click="addCustoms({
  165. name: `text2-${generateSecureRandomString()}`, type: '7', required: '1', readonly: '0', label: '请输入标题',
  166. })"
  167. >
  168. 多行文本
  169. </el-button>
  170. <el-button
  171. class="mb-18 w-80"
  172. @click="addCustoms({
  173. name: `desc1-${generateSecureRandomString()}`, type: '10', readonly: '1', required: '0', label: '文本描述', defValue: '请输入内容',
  174. })"
  175. >
  176. 文本描述
  177. </el-button>
  178. </div>
  179. <div class="pd-10 f-s-16">其他</div>
  180. <div>
  181. <el-button
  182. class="mb-18 ml-10 w-80"
  183. @click="addCustoms({
  184. name: `pic1-${generateSecureRandomString()}`, type: '8', required: '1', readonly: '0', label: '请输入标题'
  185. })"
  186. >
  187. 图片
  188. </el-button>
  189. <el-button
  190. class="mb-18 w-80"
  191. @click="addCustoms({
  192. name: `file1-${generateSecureRandomString()}`, type: '6', required: '1', readonly: '0', label: '请输入标题'
  193. })"
  194. >
  195. 文件
  196. </el-button>
  197. <el-button
  198. class="mb-18 w-80"
  199. @click="addCustoms({
  200. name: `pic2-${generateSecureRandomString()}`, type: '9', required: '0', readonly: '0', label: '请输入内容'
  201. })"
  202. >
  203. 图文描述
  204. </el-button>
  205. </div>
  206. </div>
  207. <div class="w-300"></div>
  208. <div class="w-400 border mt-100 over-auto">
  209. <div class="pd-10 border bg-#fafafa">
  210. <div class="pt-10 f-s-20 f-w-6 d-flex j-c a-c flex-cln ">报名信息</div>
  211. <div class="f-s-12 f-w-4 d-flex j-start c-red">此页面为意向人报名时所见页面:</div>
  212. </div>
  213. <template v-for="(item, index) in fixedField" :key="index">
  214. <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
  215. <span class="c-#606266 f-s-16 f-w-6">{{ item.label }}</span>
  216. <span>(固定字段,不可编辑)</span>
  217. </div>
  218. </template>
  219. <VueDraggable ref="el" v-model="fields" handle=".drag-handle" :filter="'.no-drag'">
  220. <template v-for="(field, index) in fields" :key="index">
  221. <div class="meeting-custom-wrapper" :class="{ 'active-border': activeField === field }" @click="setActive(field)" :ref="el => setFieldRef(el, index)">
  222. <MeetingCustom :field="field" ref="childRef" v-if="field" />
  223. <img class="ml-10 delete-btn c-s-p" :src="shanchu" @click.stop="removeField(index)" />
  224. </div>
  225. </template>
  226. </VueDraggable>
  227. <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
  228. <span class="c-#606266 f-s-16 f-w-6">备注</span>
  229. <span>(固定字段,不可编辑)</span>
  230. </div>
  231. </div>
  232. </div>
  233. </div>
  234. <template #footer>
  235. <div class="d-flex a-c j-c">
  236. <el-button @click="cancel">取消</el-button>
  237. <el-button @click="saveArray" type="primary">保存</el-button>
  238. </div>
  239. </template>
  240. </vxe-modal>
  241. </template>
  242. <script setup name="lmmeeting-meeting-add" lang="ts">
  243. import shanchu from '@/assets/images/shanchu.png';
  244. import { VueDraggable } from 'vue-draggable-plus';
  245. import { ref, reactive, onMounted, watch } from 'vue';
  246. import { useRouter } from 'vue-router';
  247. import { cloneDeep } from 'lodash';
  248. // 需要添加以下导入
  249. import MeetingCustom from './meeting-custom.vue';
  250. import { propTypes } from '@/utils/propTypes';
  251. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  252. 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'));
  253. import { FieldDefinition } from './type';
  254. const props = defineProps({
  255. field: propTypes.any,
  256. show: propTypes.bool.def(false),
  257. info: propTypes.array.def([]),
  258. dict: propTypes.object.def({}),
  259. width: propTypes.number.def(1200),
  260. title: propTypes.string.def('编辑报名需收集信息')
  261. });
  262. const dialogVisible = ref(false);
  263. const fields = ref<FieldDefinition[]>([]);
  264. const activeField = ref<any>(null);
  265. const childRef = ref();
  266. const emit = defineEmits(['update:show', 'close', 'success', 'update:info']);
  267. const close = () => {
  268. emit('update:show', false);
  269. emit('close', false);
  270. };
  271. const setActive = (field: any) => {
  272. activeField.value = field;
  273. };
  274. const fieldRefs = ref([])
  275. // 设置字段引用
  276. const setFieldRef = (el, index) => {
  277. if (el) {
  278. fieldRefs.value[index] = el
  279. }
  280. }
  281. const addCustoms = async (value) => {
  282. const newField = new FieldDefinition(value)
  283. fields.value.push(newField)
  284. activeField.value = newField
  285. // 等待DOM更新
  286. await nextTick()
  287. // 获取新增字段的索引
  288. const newIndex = fields.value.length - 1
  289. // 滚动到新增字段位置
  290. if (fieldRefs.value[newIndex]) {
  291. fieldRefs.value[newIndex].scrollIntoView({
  292. behavior: 'smooth',
  293. block: 'nearest'
  294. })
  295. }
  296. }
  297. function generateSecureRandomString(length = 8) {
  298. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  299. const randomValues = new Uint32Array(length);
  300. window.crypto.getRandomValues(randomValues);
  301. let result = '';
  302. randomValues.forEach((value) => {
  303. result += chars[value % chars.length];
  304. });
  305. return result;
  306. }
  307. const removeField = (index: number) => {
  308. fields.value.splice(index, 1);
  309. if (activeField.value === fields[index]) {
  310. activeField.value = null;
  311. }
  312. };
  313. const router = useRouter();
  314. const route = useRoute();
  315. const rules = reactive({
  316. // 自动生成全部
  317. trainingName: [{ required: true, message: '请输入会议名称', trigger: 'blur' }],
  318. trainingTime: [{ required: true, message: '请选择培训时间', trigger: 'blur' }],
  319. joinType: [{ required: true, message: '请选择培训方式', trigger: 'change' }],
  320. trainingLocation: [{ required: true, message: '请输入培训地点', trigger: 'blur' }],
  321. certFlag: [{ required: true, message: '请选择是否颁发证书', trigger: 'change' }],
  322. certificateInfo: [{ required: true, message: '请选择证书名称', trigger: 'change' }],
  323. description: [{ required: true, message: '请输入培训详情', trigger: 'blur' }],
  324. // coverImg: [{ required: true, message: '请上传封面图', trigger: 'change' }],
  325. // trainingImg: [{ required: true, message: '请上传会议图', trigger: 'change' }],
  326. contactName: [{ required: true, message: '请输入会议联系人', trigger: 'blur' }],
  327. tel: [{ required: true, message: '请输入联系电话', trigger: 'blur' }]
  328. });
  329. const formRef = ref();
  330. const cancel = () => {
  331. emit('update:show', false);
  332. emit('close', false);
  333. };
  334. const saveArray = async () => {
  335. try {
  336. // 使用 Promise.all 等待所有验证完成
  337. await Promise.all(childRef.value.map((i) => i.validate()));
  338. // 所有验证完成后,存储数据并关闭
  339. // localStorage.setItem('RegistrationInformation', JSON.stringify(fields.value));
  340. // props.info = fields.value
  341. emit('update:info', fields.value);
  342. emit('update:show', false);
  343. emit('close', false);
  344. } catch (error) {
  345. console.error('验证或保存失败:', error);
  346. }
  347. };
  348. const fixedField = ref<FieldDefinition[]>([
  349. {
  350. name: `ent-${generateSecureRandomString()}`,
  351. label: '企业名称',
  352. type: '1',
  353. required: '1',
  354. readonly: '0'
  355. },
  356. {
  357. name: `name-${generateSecureRandomString()}`,
  358. label: '姓名',
  359. type: '1',
  360. readonly: '0',
  361. required: '1'
  362. },
  363. {
  364. name: `pos-${generateSecureRandomString()}`,
  365. label: '职务',
  366. type: '1',
  367. readonly: '0',
  368. required: '1'
  369. },
  370. {
  371. name: `Con-${generateSecureRandomString()}`,
  372. label: '联系方式',
  373. type: '1',
  374. readonly: '0',
  375. required: '1'
  376. }
  377. ]);
  378. onMounted(() => {
  379. fields.value = cloneDeep(props.info);
  380. });
  381. watch(
  382. () => props.show,
  383. (val) => {
  384. dialogVisible.value = val;
  385. },
  386. { immediate: true }
  387. );
  388. </script>
  389. <style scoped>
  390. .border {
  391. border: 1px solid #f2f2f2;
  392. border-radius: 6px;
  393. }
  394. .border1 {
  395. border: 1px solid #f2f2f2;
  396. }
  397. .active-border {
  398. border: 1px solid #b6e7d9;
  399. border-radius: 4px;
  400. box-shadow: 0px 0px 3px 0px #009932;
  401. }
  402. .meeting-custom-wrapper {
  403. position: relative;
  404. /* padding: 10px; */
  405. }
  406. .delete-btn {
  407. position: absolute;
  408. top: 15px;
  409. right: 5px;
  410. padding: 0 5px;
  411. font-size: 12px;
  412. }
  413. </style>