index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <div class="p-3">
  3. <div class="bg-fff flex1 ov-hd d-flex flex-cln">
  4. <div class="d-flex a-c pd-16 border-bottom">
  5. <div class="f-s-20 c-333 f-w-7 mr-10">{{ form.id ? '编辑' : '新增' }}会议</div>
  6. <el-button @click="router.go(-1)" type="primary" text>
  7. <el-icon>
  8. <Back />
  9. </el-icon>
  10. 返回上一级
  11. </el-button>
  12. </div>
  13. <div class="flex1 over-auto">
  14. <el-form ref="formRef" label-width="auto" label-position="top" :model="form" :rules="rules">
  15. <div class="pd-16 border-bottom ov-hd">
  16. <div class="info-title mb-10">会议信息</div>
  17. <el-row :gutter="20">
  18. <el-col :span="6">
  19. <el-form-item label="会议名称" prop="trainingName">
  20. <el-input v-model="form.trainingName" placeholder="请输入会议名称" clearable />
  21. </el-form-item>
  22. </el-col>
  23. <el-col :span="6">
  24. <el-form-item label="会议时间" prop="trainingTime">
  25. <el-date-picker v-model="form.trainingTime" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" date-format="YYYY-MM-DD" time-format="HH:mm" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
  26. </el-form-item>
  27. </el-col>
  28. <el-col :span="6">
  29. <el-form-item label="报名时间" prop="signupTime">
  30. <el-date-picker v-model="form.signupsTime" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" date-format="YYYY-MM-DD" time-format="HH:mm" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
  31. </el-form-item>
  32. </el-col>
  33. <el-col :span="6">
  34. <el-form-item label="会议方式" prop="joinType">
  35. <!-- 单选框 -->
  36. <el-radio-group v-model="form.joinType">
  37. <el-radio v-for="item in lm_training_join_type" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
  38. </el-radio-group>
  39. </el-form-item>
  40. </el-col>
  41. <el-col v-if="form.joinType === '0'" :span="6">
  42. <el-form-item label="会议地点" prop="trainingLocation">
  43. <el-input v-model="form.trainingLocation" placeholder="请输入会议地点" clearable />
  44. </el-form-item>
  45. </el-col>
  46. <el-col :span="6">
  47. <el-form-item label="会议联系人" prop="contactName">
  48. <el-input v-model="form.contactName" maxlength="40" placeholder="请输入会议联系人" clearable />
  49. </el-form-item>
  50. </el-col>
  51. <el-col :span="6">
  52. <el-form-item label="联系电话" prop="tel">
  53. <el-input v-model="form.tel" maxlength="20" placeholder="请输入联系电话" clearable />
  54. </el-form-item>
  55. </el-col>
  56. <el-col :span="6">
  57. <el-form-item label="报名人数" prop="conditions.totalCheck">
  58. <div class="d-flex a-c">
  59. <el-radio-group v-model="form.conditions.totalCheck" style="flex-wrap: nowrap">
  60. <el-radio label="0">不限制</el-radio>
  61. <el-radio label="1">限制</el-radio>
  62. </el-radio-group>
  63. </div>
  64. </el-form-item>
  65. </el-col>
  66. <el-col :span="12" v-if="form.conditions.totalCheck == '1'">
  67. <el-form-item prop="restrictiveConditions">
  68. <template #label>
  69. <span>限制条件</span>
  70. <span class="c-999 f-s-12 f-w-4">(不限制的条件可不填)</span>
  71. </template>
  72. <div class="d-flex flex-cln pl-10">
  73. <div class="d-flex">
  74. <div class="c-#606266 f-w-6" style="">报名总人数限制:</div>
  75. <el-input class="flex1 pl-5" v-model="form.conditions.total" maxlength="20" placeholder="请输入报名人数" clearable :disabled="form.conditions.totalCheck !== '1'" style="max-width: 200px;" />
  76. </div>
  77. <div class="c-#606266 f-w-6" style="">条件限制:</div>
  78. <div class="d-flex">
  79. <div class="d-flex flex-cln">
  80. <el-checkbox v-model="form.conditions.levelTotalCheck" label="按盟员单位等级限制" size="large" true-value="1" false-value="0" />
  81. <div v-if="form.conditions.levelTotalCheck == '1' && form.conditions.cpyTotalCheck == '0'" class="pl-10 pr-10 pt-5 pb-5 border">
  82. <template v-for="(item, index) in form.conditions.levelCheck" :key="index">
  83. <div class="d-flex a-c">
  84. <el-checkbox v-model="item.check" true-value="1" false-value="0" :label="'所有' + selectDictLabels(vip_level, item.vipLevel, ',') + '参会人数≤'" size="large" />
  85. <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
  86. </div>
  87. </template>
  88. </div>
  89. </div>
  90. <div class="d-flex flex-cln ml-20">
  91. <el-checkbox v-model="form.conditions.cpyTotalCheck" label="按每家单位人数限制" size="large" true-value="1" false-value="0" />
  92. <div v-if="form.conditions.cpyTotalCheck == '1' && form.conditions.levelTotalCheck == '0'" class="pl-10 pr-10 pt-5 pb-5 border">
  93. <template v-for="(item, index) in form.conditions.cpyCheck" :key="index">
  94. <div class="d-flex a-c">
  95. <el-checkbox v-model="item.check" :label="selectDictLabels(vip_level, item.vipLevel, ',') + '限制每家单位人数≤'" size="large" true-value="1" false-value="0" />
  96. <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
  97. </div>
  98. </template>
  99. </div>
  100. </div>
  101. </div>
  102. <div class="d-flex border" v-if="form.conditions.levelTotalCheck == '1' && form.conditions.cpyTotalCheck == '1'">
  103. <div class="pl-10 pr-10 pt-5 pb-5 ">
  104. <template v-for="(item, index) in form.conditions.levelCheck" :key="index">
  105. <div class="d-flex a-c">
  106. <el-checkbox v-model="item.check" :label="'所有' + selectDictLabels(vip_level, item.vipLevel, ',') + '参会人数≤'" size="large" true-value="1" false-value="0" />
  107. <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
  108. </div>
  109. </template>
  110. </div>
  111. <div class="pl-10 pr-10 pt-5 pb-5">
  112. <template v-for="(item, index) in form.conditions.cpyCheck" :key="index">
  113. <div class="d-flex a-c">
  114. <el-checkbox v-model="item.check" :label="selectDictLabels(vip_level, item.vipLevel, ',') + '限制每家单位人数≤'" size="large" true-value="1" false-value="0" />
  115. <el-input class="pl-10" v-model="item.total" maxlength="20" placeholder="请输入报名人数" :disabled="!+item.check" />
  116. </div>
  117. </template>
  118. </div>
  119. </div>
  120. </div>
  121. </el-form-item>
  122. </el-col>
  123. <el-col :span="6">
  124. <el-form-item label="是否电子手签" prop="eleSignature">
  125. <div class="d-flex a-c">
  126. <el-radio-group v-model="form.eleSignature" style="flex-wrap: nowrap">
  127. <el-radio label="1">是</el-radio>
  128. <el-radio label="0">否</el-radio>
  129. </el-radio-group>
  130. </div>
  131. </el-form-item>
  132. </el-col>
  133. </el-row>
  134. <el-row :gutter="20">
  135. <el-col :span="12">
  136. <el-form-item label="会议详情" prop="description">
  137. <el-input v-model="form.description" :rows="4" type="textarea" placeholder="请输入会议详情" />
  138. </el-form-item>
  139. </el-col>
  140. <el-col :span="12">
  141. <el-form-item label="与会须知" prop="description">
  142. <el-input v-model="form.notice" :rows="4" type="textarea" placeholder="请输入与会须知" />
  143. </el-form-item>
  144. </el-col>
  145. <el-col :span="12">
  146. <el-form-item label="会议微信群聊二维码" prop="wechatQrCode">
  147. <ImageUpload v-model="form.wechatQrCode" :fileSize="40"></ImageUpload>
  148. </el-form-item>
  149. </el-col>
  150. <el-col :span="12">
  151. <el-form-item label="封面图" prop="coverImg">
  152. <ImageUpload v-model="form.coverImg" :limit="1"></ImageUpload>
  153. </el-form-item>
  154. </el-col>
  155. <el-col :span="12">
  156. <el-form-item label="会议图" prop="trainingImg">
  157. <ImageUpload v-model="form.trainingImg" :fileSize="40"></ImageUpload>
  158. </el-form-item>
  159. </el-col>
  160. <el-col :span="12">
  161. <el-form-item label="会议备注" prop="remark">
  162. <el-input v-model="form.remark" :rows="4" type="textarea" placeholder="请输入会议备注" />
  163. </el-form-item>
  164. </el-col>
  165. <el-col :span="12">
  166. <el-form-item label="相关文件" prop="attachments">
  167. <template #label>
  168. <span>相关文件</span>
  169. <span class="c-999 f-s-12 f-w-4">(此模块报名审核通过后才可查看)</span>
  170. </template>
  171. <div class="d-flex flex-cln mt-10 f-s-12 c-#606266">
  172. <FileUpload v-model="form.attachments" format="array" :limit="20" :fileSize="100"></FileUpload>
  173. </div>
  174. </el-form-item>
  175. </el-col>
  176. </el-row>
  177. </div>
  178. <div class="pd-16 border-bottom d-flex">
  179. <div class="w-50% ov-at h-1000">
  180. <div class="info-title mb-10">证书信息</div>
  181. <div class="d-flex j-start a-c">
  182. <el-form-item label="是否颁发证书" prop="certFlag">
  183. <el-radio-group v-model="form.certFlag">
  184. <el-radio v-for="item in yes_no" :key="item.value" :label="item.value">
  185. {{item.label }}
  186. </el-radio>
  187. </el-radio-group>
  188. </el-form-item>
  189. <div v-if="+form.certFlag" class="ml-20">
  190. <el-button @click="addCertInfo" type="primary">新增证书</el-button>
  191. </div>
  192. </div>
  193. <template v-if="form.certFlag">
  194. <template v-for="(item, index) in form.certificateInfo" :key="index">
  195. <el-row :gutter="20" class="bg-#f4f4f4 pd-16 mb-10">
  196. <el-col :span="10">
  197. <el-form-item label="证书名称" :prop="`certificateInfo.${index}.certType`" :rules="[{ required: true, message: '请选择证书名称', trigger: 'change' }]">
  198. <el-select v-model="item.certType" placeholder="证书名称" clearable>
  199. <el-option v-for="item in lm_training_cert" :key="item.value" :label="item.label" :value="item.value" />
  200. </el-select>
  201. </el-form-item>
  202. </el-col>
  203. <el-col :span="10">
  204. <el-form-item :prop="`certificateInfo.${index}.certImg`" :rules="[{ required: true, message: '请上传证书图片', trigger: 'change' }]">
  205. <template #label>
  206. <span>证书模板图片</span>
  207. <el-button @click="goEditor()" type="primary" text>去编辑模板图片</el-button>
  208. </template>
  209. <ImageUpload v-model="item.certImg" :fileSize="40" :limit="1"></ImageUpload>
  210. </el-form-item>
  211. </el-col>
  212. <el-col :span="4">
  213. <el-form-item>
  214. <el-button type="danger" @click="deleteItem(index)">删除</el-button>
  215. </el-form-item>
  216. </el-col>
  217. </el-row>
  218. </template>
  219. </template>
  220. </div>
  221. <div class="w-50% d-flex flex-cln j-c a-c pl-20">
  222. <el-button type="primary" class="w-100%" plain style="height: 70px; margin-bottom: 20px;" @click="showSignIn = true">点击去编辑报名信息></el-button>
  223. <div class="w-400 h-700 border over-auto">
  224. <div class="pd-10 border bg-#fafafa">
  225. <div class="pt-10 f-s-20 f-w-6 d-flex j-c a-c flex-cln ">报名信息</div>
  226. <div class="f-s-12 f-w-4 d-flex j-start c-red">此页面为意向人报名时所见页面:</div>
  227. </div>
  228. <template v-for="(item, index) in fixedField" :key="index">
  229. <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
  230. <span class="c-#606266 f-s-16 f-w-6">{{ item.label }}</span>
  231. <span class="f-s-14">请输入</span>
  232. </div>
  233. </template>
  234. <template v-for="(field, index) in fields" :key="index">
  235. <div class="meeting-custom-wrapper" @click.stop="showSignIn = true">
  236. <meetingCustomPreview :field="field" style="pointer-events: none" v-if="field" />
  237. </div>
  238. </template>
  239. <div class="pd-15 border1 c-#D7D7D7 d-flex j-sb">
  240. <span class="c-#606266 f-s-16 f-w-6">备注</span>
  241. <span class="f-s-14">请输入</span>
  242. </div>
  243. </div>
  244. </div>
  245. </div>
  246. </el-form>
  247. <MeetingEditors v-if="showSignIn" v-model:show="showSignIn" v-model:info="fields" />
  248. </div>
  249. <div class="d-flex a-c j-c pd-16">
  250. <el-button @click="Cancel">取消</el-button>
  251. <el-button @click="save" type="primary">提交</el-button>
  252. </div>
  253. </div>
  254. </div>
  255. </template>
  256. <script setup name="lmmeeting-meeting-add" lang="ts">
  257. import { ref, reactive, onMounted } from 'vue';
  258. import { debounce } from 'lodash';
  259. import { useRouter } from 'vue-router';
  260. import { trainingAdd, trainingDetail, trainingUpdate } from '@/api/training';
  261. import { FieldDefinition } from '../models/type'
  262. // 需要添加以下导入
  263. import meetingCustomPreview from '../models/meeting-custom-preview.vue'
  264. import MeetingEditors from '../models/meeting-editors.vue'
  265. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  266. const { lm_training_join_type, yes_no, lm_training_cert, vip_level } = toRefs<any>(proxy?.useDict('lm_training_join_type', 'yes_no', 'lm_training_cert', 'vip_level'));
  267. const fields = ref<FieldDefinition[]>([])
  268. const showSignIn = ref(false);
  269. const fixedField = ref<FieldDefinition[]>([{
  270. name: `ent-${generateSecureRandomString()}`,
  271. label: '企业名称', type: '1',
  272. required: '1', readonly: '0',
  273. }, {
  274. name: `name-${generateSecureRandomString()}`,
  275. label: '姓名', type: '1', readonly: '0',
  276. required: '1'
  277. }, {
  278. name: `pos-${generateSecureRandomString()}`,
  279. label: '职务', type: '1', readonly: '0',
  280. required: '1'
  281. }, {
  282. name: `Con-${generateSecureRandomString()}`,
  283. label: '联系方式', type: '1', readonly: '0',
  284. required: '1'
  285. }])
  286. function generateSecureRandomString(length = 8) {
  287. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  288. const randomValues = new Uint32Array(length);
  289. window.crypto.getRandomValues(randomValues);
  290. let result = '';
  291. randomValues.forEach((value) => {
  292. result += chars[value % chars.length];
  293. });
  294. return result;
  295. }
  296. const Cancel = () => {
  297. router.go(-1)
  298. }
  299. const router = useRouter();
  300. const route = useRoute();
  301. const form = ref<any>({
  302. id: undefined,
  303. conditions: {
  304. levelTotalCheck: '0',
  305. cpyTotalCheck: '0',
  306. levelCheck: [{
  307. vipLevel: '1',
  308. check: "0",
  309. total: ''
  310. }, {
  311. vipLevel: '3',
  312. check: "0",
  313. total: ''
  314. }, {
  315. vipLevel: '5',
  316. check: "0",
  317. total: ''
  318. }],
  319. cpyCheck: [{
  320. vipLevel: '1',
  321. check: "0",
  322. total: ''
  323. }, {
  324. vipLevel: '3',
  325. check: "0",
  326. total: ''
  327. }, {
  328. vipLevel: '5',
  329. check: "0",
  330. total: ''
  331. }]
  332. }
  333. });
  334. const rules = reactive({
  335. // 自动生成全部
  336. trainingName: [{ required: true, message: '请输入会议名称', trigger: 'blur' }],
  337. trainingTime: [{ required: true, message: '请选择会议时间', trigger: 'blur' }],
  338. joinType: [{ required: true, message: '请选择会议方式', trigger: 'change' }],
  339. trainingLocation: [{ required: true, message: '请输入会议地点', trigger: 'blur' }],
  340. 'conditions.totalCheck':[{ required: true, message: '请选择是否限制报名人数', trigger: 'change' }],
  341. certFlag: [{ required: true, message: '请选择是否颁发证书', trigger: 'change' }],
  342. certificateInfo: [{ required: true, message: '请选择证书名称', trigger: 'change' }],
  343. description: [{ required: true, message: '请输入培训详情', trigger: 'blur' }],
  344. eleSignature: [{ required: true, message: '请选择是否电子手签', trigger: 'blur' }],
  345. // coverImg: [{ required: true, message: '请上传封面图', trigger: 'change' }],
  346. // trainingImg: [{ required: true, message: '请上传会议图', trigger: 'change' }],
  347. contactName: [{ required: true, message: '请输入会议联系人', trigger: 'blur' }],
  348. tel: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
  349. });
  350. const formRef = ref();
  351. const save = debounce(async () => {
  352. console.log('123123');
  353. await formRef.value.validate();
  354. const params = {
  355. ...form.value,
  356. trainingStart: form.value.trainingTime ? form.value.trainingTime[0] : undefined,
  357. trainingEnd: form.value.trainingTime ? form.value.trainingTime[1] : undefined,
  358. signupStart: form.value.signupsTime ? form.value.signupsTime[0] : undefined,
  359. signupEnd: form.value.signupsTime ? form.value.signupsTime[1] : undefined,
  360. certificateInfo: +form.value.certFlag ? form.value.certificateInfo : undefined,
  361. questions: fields.value.map((item, index) => ({
  362. ...item,
  363. sort: index + 1 // 从 1 开始
  364. }))
  365. };
  366. const res = form.value.id ? await trainingUpdate(params) : await trainingAdd(params);
  367. if (res && res.code === 200) {
  368. router.go(-1);
  369. }
  370. }, 500);
  371. const goEditor = () => {
  372. window.open('https://lm.yujin.shuziyunyao.com/poster#/editor', '_blank');
  373. }
  374. const addCertInfo = () => {
  375. if (!form.value.certificateInfo) {
  376. form.value.certificateInfo = [];
  377. }
  378. form.value.certificateInfo.push({
  379. certName: undefined,
  380. certImg: undefined
  381. });
  382. };
  383. const deleteItem = (index: number) => {
  384. form.value.certificateInfo.splice(index, 1);
  385. };
  386. // 获取专家详情
  387. const getMeetingDetail = async () => {
  388. if (route.query?.id) {
  389. const res = await trainingDetail(route.query?.id as string);
  390. if (!res || res.code !== 200) return;
  391. form.value = {
  392. ...res.data,
  393. trainingTime: res.data.trainingStart && res.data.trainingEnd ? [res.data.trainingStart, res.data.trainingEnd] : undefined,
  394. signupsTime: res.data.signupStart && res.data.signupEnd ? [res.data.signupStart, res.data.signupEnd] : undefined
  395. };
  396. fields.value = res.data.questions
  397. }
  398. };
  399. onMounted(() => {
  400. getMeetingDetail();
  401. });
  402. </script>
  403. <style scoped>
  404. .border {
  405. border: 1px solid #dcdfe6;
  406. }
  407. </style>