index_form.vue 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. <template>
  2. <main class="index_main">
  3. <HomePageHead></HomePageHead>
  4. <HomePageNavigation></HomePageNavigation>
  5. <el-skeleton v-if="pageLoading" :rows="24" :animated="true" style="margin: 20px;">
  6. <template #template>
  7. <div class="custom-skeleton-multi">
  8. <span
  9. v-for="(item, idx) in skeletonChars"
  10. :key="idx"
  11. class="custom-skeleton-char"
  12. :style="{
  13. top: item.top + '%',
  14. left: item.left + '%',
  15. fontSize: item.fontSize + 'px',
  16. opacity: item.opacity,
  17. transform: `rotate(${item.rotate}deg)`
  18. }"
  19. >政讯通</span>
  20. </div>
  21. </template>
  22. </el-skeleton>
  23. <el-form v-else :model="formData" :rules="rules_js" ref="formRef" label-width="120px" class="form_box">
  24. <el-form-item
  25. v-for="(per_obj,per_index) in table_head.value"
  26. :key="per_index"
  27. :prop="getFieldName(per_obj)"
  28. :label="per_obj.title + ':'"
  29. :required="per_obj.is_check === 1"
  30. >
  31. <!-- 文本文字1 -->
  32. <el-input
  33. v-if="checkFieldType(per_obj, 1)"
  34. v-model="formData[getFieldName(per_obj)]"
  35. :placeholder="`请输入${per_obj.title}`"
  36. />
  37. <!-- 多行textarea 2-->
  38. <el-input
  39. v-if="checkFieldType(per_obj, 2)"
  40. v-model="formData[getFieldName(per_obj)]"
  41. type="textarea"
  42. :autosize="{ minRows: 2, maxRows: 4 }"
  43. :placeholder="`请输入${per_obj.title}`"
  44. />
  45. <!-- 单选按钮 3 -->
  46. <el-radio-group
  47. v-if="checkFieldType(per_obj, 3)"
  48. v-model="formData[getFieldName(per_obj)]">
  49. <el-radio
  50. v-for="per_3_obj in radio_arr_fun(per_obj.option_value)"
  51. :key="`${getFieldName(per_obj)}_${per_3_obj[1]}`"
  52. :label="per_3_obj[1]">
  53. {{ per_3_obj[0] }}
  54. </el-radio>
  55. </el-radio-group>
  56. <!-- 下拉选择 4 -->
  57. <el-select
  58. v-if="checkFieldType(per_obj, 4)"
  59. placeholder="请选择"
  60. v-model="formData[getFieldName(per_obj)]"
  61. style="width: 100%"
  62. >
  63. <el-option
  64. v-for="per_4_obj in select_arr_fun(per_obj.option_value)"
  65. :key="`${getFieldName(per_obj)}_${per_4_obj.value}`"
  66. :label="per_4_obj.label"
  67. :value="per_4_obj.value">
  68. </el-option>
  69. </el-select>
  70. <!-- 复选框5 -->
  71. <el-checkbox-group
  72. v-if="checkFieldType(per_obj, 5)"
  73. v-model="formData[getFieldName(per_obj)]"
  74. @change="(value) => handleCheckboxChange(getFieldName(per_obj), value)"
  75. >
  76. <el-checkbox
  77. v-for="per_5_obj in checkbox_arr_fun(per_obj.option_value)"
  78. :key="`${getFieldName(per_obj)}_${per_5_obj.value}`"
  79. :label="per_5_obj.value">
  80. {{ per_5_obj.label }}
  81. </el-checkbox>
  82. </el-checkbox-group>
  83. <!-- 日期选择器 6 -->
  84. <el-date-picker
  85. v-if="checkFieldType(per_obj, 6)"
  86. v-model="formData[getFieldName(per_obj)]"
  87. type="date"
  88. value-format="YYYY-MM-DD"
  89. format="YYYY-MM-DD"
  90. placeholder="选择日期"
  91. style="width: 100%">
  92. </el-date-picker>
  93. <!-- 单文件上传 7 (新) -->
  94. <el-upload
  95. v-if="checkFieldType(per_obj, 7)"
  96. :ref="(el) => uploadRefs[getFieldName(per_obj)] = el"
  97. :action="`${$webUrl}/public/uploadFile`"
  98. :limit="1"
  99. :on-exceed="(files) => handleExceed(files, getFieldName(per_obj))"
  100. :on-success="(res, file) => handleFileSuccess(res, file, getFieldName(per_obj))"
  101. :on-remove="() => handleFileRemove(getFieldName(per_obj))"
  102. :file-list="fileLists[getFieldName(per_obj)] || []"
  103. >
  104. <el-button type="primary">点击上传文件</el-button>
  105. <template #tip>
  106. <div class="el-upload__tip">
  107. 只能上传一个文件,新文件将覆盖旧文件。
  108. </div>
  109. </template>
  110. </el-upload>
  111. <!-- img上传 8 -->
  112. <el-upload
  113. v-if="checkFieldType(per_obj, 8)"
  114. class="avatar-uploader"
  115. :action="`${$webUrl}/public/uploadFile`"
  116. :show-file-list="false"
  117. :on-success="(res) => handleAvatarSuccess(res, getFieldName(per_obj))"
  118. :before-upload="beforeAvatarUpload"
  119. >
  120. <img v-if="formData[getFieldName(per_obj)]" :src="formData[getFieldName(per_obj)]" class="avatar" >
  121. <div v-else class="avatar-uploader-placeholder">
  122. <img src="http://img.bjzxtw.org.cn/master/image/noImage.png" class="avatar-placeholder-img" alt="上传头像">
  123. <span class="upload-tip">点击上传</span>
  124. </div>
  125. </el-upload>
  126. </el-form-item>
  127. <!-- 验证码 -->
  128. <el-form-item v-if="showCaptcha" prop="code" label="验证码:">
  129. <div class="captcha-wrapper">
  130. <el-input v-model="formData.code" placeholder="请输入验证码" @keyup.enter="submitForm"></el-input>
  131. <img v-if="captchaImage" :src="captchaImage" @click="refreshCaptcha" alt="验证码" class="captcha-img" title="点击刷新">
  132. </div>
  133. </el-form-item>
  134. <el-form-item class="form-actions">
  135. <el-button type="primary" @click="submitForm" :loading="submitLoading">提交</el-button>
  136. <el-button @click="resetForm">重置</el-button>
  137. </el-form-item>
  138. </el-form>
  139. <HomeFoot></HomeFoot>
  140. </main>
  141. <el-dialog v-model="dialogVisible" title="提示" width="300px" :close-on-click-modal="false" :show-close="false" align-center>
  142. <div style="text-align:center;">表单提交成功!</div>
  143. <template #footer>
  144. <div class="dialog-footer-center">
  145. <el-button type="primary" @click="onDialogConfirm" class="dialog-confirm-btn">确认</el-button>
  146. </div>
  147. </template>
  148. </el-dialog>
  149. </template>
  150. <style scoped>
  151. @charset "utf-8";
  152. * {
  153. margin: 0;
  154. padding: 0;
  155. font-family: "微软雅黑", "microsoft yahei";
  156. }
  157. ul,
  158. ol {
  159. list-style: none;
  160. }
  161. a:active {
  162. text-decoration: none;
  163. }
  164. a:hover {
  165. text-decoration: none;
  166. }
  167. a:visited {
  168. text-decoration: none;
  169. }
  170. a:link {
  171. text-decoration: none;
  172. }
  173. a:focus {
  174. text-decoration: none;
  175. }
  176. body {
  177. position: relative;
  178. }
  179. .clearfix {
  180. overflow: hidden;
  181. }
  182. .clearfix_2::after {
  183. content: '';
  184. display: block;
  185. height: 0;
  186. visibility: hidden;
  187. clear: both;
  188. }
  189. .hiddenColor {
  190. visibility: hidden;
  191. }
  192. .hand {
  193. cursor: pointer;
  194. }
  195. .aTag_parent {
  196. position: relative;
  197. }
  198. .aTag_parent>a,
  199. .aTag {
  200. display: block;
  201. width: 100%;
  202. height: 100%;
  203. position: absolute;
  204. z-index: 99;
  205. border: 0px;
  206. top: 0px;
  207. left: 0px;
  208. background: rgba(0, 0, 0, 0);
  209. }
  210. .dot1 {
  211. display: block;
  212. word-break: keep-all;
  213. white-space: nowrap;
  214. overflow: hidden;
  215. text-overflow: ellipsis;
  216. }
  217. .dot2 {
  218. overflow: hidden;
  219. display: -webkit-box;
  220. -webkit-box-orient: vertical;
  221. -webkit-line-clamp: 2;
  222. }
  223. .dot3{
  224. overflow: hidden;
  225. display: -webkit-box;
  226. -webkit-box-orient: vertical;
  227. -webkit-line-clamp: 3;
  228. }
  229. input,
  230. img {
  231. border: none;
  232. }
  233. .cover100 img {
  234. display: block;
  235. width: 100%;
  236. height: 100%;
  237. object-fit: cover;
  238. }
  239. .back100 {
  240. background-size: 100% 100%;
  241. background-repeat: no-repeat;
  242. }
  243. article,
  244. aside,
  245. footer,
  246. header,
  247. time,
  248. video,
  249. main,
  250. nav,
  251. h4,
  252. h3,
  253. section {
  254. display: block;
  255. }
  256. .index_main {
  257. margin: 0 auto 0px;
  258. }
  259. .slow_6 {
  260. -webkit-transition: all .6s;
  261. -moz-transition: all .6s;
  262. -ms-transition: all .6s;
  263. -o-transition: all .6s;
  264. transition: all .6s;
  265. }
  266. .index_main {
  267. width: 1200px;
  268. }
  269. .form_box {
  270. border: dashed 1px #000;
  271. padding: 20px;
  272. margin: 20px;
  273. }
  274. .form_box :deep(.avatar-uploader) {
  275. border: 1px dashed var(--el-border-color);
  276. border-radius: 6px;
  277. cursor: pointer;
  278. position: relative;
  279. overflow: hidden;
  280. width: 178px;
  281. height: 178px;
  282. }
  283. .form_box :deep(.avatar-uploader:hover) {
  284. border-color: var(--el-color-primary);
  285. }
  286. .form_box :deep(.avatar-uploader .avatar) {
  287. width: 100%;
  288. height: 100%;
  289. display: block;
  290. }
  291. .form_box :deep(.avatar-uploader .el-upload) {
  292. width: 100%;
  293. height: 100%;
  294. }
  295. .form_box :deep(.el-button--primary) {
  296. padding: 0 11px;
  297. }
  298. @media screen and (min-width:1200px) {
  299. /*pc_1440*/
  300. @media screen and (max-width:1440px) {
  301. /*1200*/
  302. }
  303. .pc_none {
  304. display: none;
  305. }
  306. }
  307. @media screen and (max-width:599px) {}
  308. @media screen and (max-width:320px) {}
  309. .form_box .el-form-item {
  310. margin-bottom: 20px!important;
  311. }
  312. .avatar-uploader-placeholder {
  313. display: flex;
  314. flex-direction: column;
  315. justify-content: center;
  316. align-items: center;
  317. width: 100%;
  318. height: 100%;
  319. }
  320. .avatar-placeholder-img {
  321. width: 50px;
  322. height: 50px;
  323. opacity: 0.5;
  324. }
  325. .upload-tip {
  326. margin-top: 8px;
  327. color: #8c939d;
  328. font-size: 12px;
  329. }
  330. .form-actions :deep(.el-form-item__content) {
  331. justify-content: center;
  332. }
  333. .form-actions .el-button + .el-button {
  334. margin-left: 20px;
  335. padding: 0 11px;
  336. }
  337. .captcha-wrapper {
  338. display: flex;
  339. align-items: center;
  340. width: 100%;
  341. }
  342. .captcha-img {
  343. margin-left: 10px;
  344. cursor: pointer;
  345. height: 32px; /* 与el-input默认高度对齐 */
  346. border-radius: 4px;
  347. }
  348. .form_box :deep(.el-checkbox) {
  349. margin-right: 20px;
  350. }
  351. .form_box :deep(.el-radio) {
  352. margin-right: 20px;
  353. }
  354. /* 弹窗footer按钮居中 */
  355. .dialog-footer-center {
  356. display: flex;
  357. justify-content: center;
  358. align-items: center;
  359. }
  360. .dialog-confirm-btn {
  361. padding: 0 32px;
  362. }
  363. .custom-skeleton-multi {
  364. position: relative;
  365. width: 100%;
  366. height: 700px;
  367. background: #f5f6fa;
  368. border-radius: 8px;
  369. overflow: hidden;
  370. }
  371. .custom-skeleton-char {
  372. position: absolute;
  373. color: #e5e6eb;
  374. font-weight: bold;
  375. letter-spacing: 10px;
  376. user-select: none;
  377. pointer-events: none;
  378. transition: all 0.3s;
  379. }
  380. </style>
  381. <script setup>
  382. //页面是否已经加载完毕
  383. const pageLoading = ref(true)
  384. const submitLoading = ref(false)
  385. const showCaptcha = ref(false)
  386. const captchaImage = ref('')
  387. const captchaId = ref('')
  388. const dialogVisible = ref(false)
  389. //1.加载页面必备组件 start---------------------------------------->
  390. import { ref, onMounted, reactive, nextTick, onBeforeUpdate } from 'vue';
  391. import { useSeoMeta } from '#imports';
  392. import { ElForm, ElFormItem, ElInput, ElRadioGroup, ElRadio,
  393. ElCheckboxGroup, ElCheckbox, ElUpload, ElMessage,
  394. ElSelect, ElOption, ElDatePicker, ElButton, genFileId } from 'element-plus'
  395. const { $webUrl, $CwebUrl } = useNuxtApp();
  396. // 表单引用
  397. const formRef = ref(null)
  398. // 上传组件引用
  399. const uploadRefs = ref({});
  400. const fileLists = ref({});
  401. onBeforeUpdate(() => {
  402. uploadRefs.value = {};
  403. });
  404. let adImg = ref({})
  405. //广告1
  406. let url = `${$webUrl}/web/getWebsiteAdvertisement?ad_tag=tsbb_index_1`
  407. const responseAd1 = await fetch(url, {
  408. headers: {
  409. 'Content-Type': 'application/json',
  410. 'Userurl': $CwebUrl,
  411. 'Origin': $CwebUrl
  412. }
  413. });
  414. const resultAd1 = await responseAd1.json();
  415. adImg.value = resultAd1.data[0];
  416. let adImg_2 = ref({})
  417. //广告_2
  418. let url_2 = `${$webUrl}/web/getWebsiteAdvertisement?ad_tag=tsbb_index_2`
  419. const responseAd_2 = await fetch(url_2, {
  420. headers: {
  421. 'Content-Type': 'application/json',
  422. 'Userurl': $CwebUrl,
  423. 'Origin': $CwebUrl
  424. }
  425. });
  426. const resultAd_2 = await responseAd_2.json();
  427. adImg_2.value = resultAd_2.data[0];
  428. // 动态验证规则
  429. const rules_js = ref({})
  430. const get_from_data_1 = reactive({
  431. value:{}
  432. });
  433. const chinese_calendar = reactive({});
  434. //列表头
  435. const table_head = reactive({
  436. value:[]
  437. });
  438. //列表内容
  439. const table_body = reactive({
  440. value:[]
  441. });
  442. // 表单数据 - 使用ref而不是reactive来避免响应式问题
  443. const formData = ref({})
  444. const route = useRoute()
  445. // 从路由获取 table_id,如果不存在则使用默认值
  446. const table_id = ref(route.query.table_id || 59)
  447. // 得到id
  448. const recive_id = reactive({
  449. value:route.query.id || route.params.id
  450. })
  451. // 工具函数定义
  452. function getFieldName(field) {
  453. if (field.field) return field.field;
  454. const possibleFieldNames = ['name', 'field_name', 'key', 'id'];
  455. for (const name of possibleFieldNames) {
  456. if (field[name]) return field[name];
  457. }
  458. return undefined;
  459. }
  460. // 检查字段类型的函数,支持数组格式
  461. function checkFieldType(field, type) {
  462. if (!field.field_type) return false;
  463. if (Array.isArray(field.field_type)) {
  464. return field.field_type.map(Number).includes(Number(type));
  465. }
  466. return Number(field.field_type) === Number(type);
  467. }
  468. // 检查是否为数组类型字段(field_type为5或包含5的数组)
  469. function isArrayTypeField(field) {
  470. return checkFieldType(field, 5);
  471. }
  472. // 生成动态验证规则
  473. function generateValidationRules() {
  474. const rules = {}
  475. table_head.value.forEach((field, index) => {
  476. const fieldRules = []
  477. const fieldName = getFieldName(field)
  478. // 必填验证
  479. if (field.is_check === 1) {
  480. fieldRules.push({
  481. required: true,
  482. message: `${field.title}不能为空`,
  483. trigger: ['blur', 'change']
  484. })
  485. }
  486. // 正则验证
  487. if (field.regular && field.regular.trim()) {
  488. try {
  489. const regex = parseRegExp(field.regular)
  490. fieldRules.push({
  491. validator: (rule, value, callback) => {
  492. if (value && !regex.test(value)) {
  493. callback(new Error(`${field.title}格式不正确`))
  494. } else {
  495. callback()
  496. }
  497. },
  498. trigger: ['blur', 'change']
  499. })
  500. } catch (error) {
  501. console.error(`正则表达式错误: ${field.regular}`, error)
  502. }
  503. }
  504. if (fieldRules.length > 0 && fieldName) {
  505. rules[fieldName] = fieldRules
  506. }
  507. })
  508. // 添加验证码规则
  509. if (showCaptcha.value) {
  510. rules['code'] = [
  511. { required: true, message: '验证码不能为空', trigger: 'blur' }
  512. ]
  513. }
  514. rules_js.value = rules
  515. }
  516. // 刷新验证码
  517. async function refreshCaptcha() {
  518. try {
  519. // 拼接 GET 参数
  520. const params = new URLSearchParams({ table_id: table_id.value });
  521. const url = `${$webUrl}/form/getWebGlobalTableFieldList?${params.toString()}`;
  522. const response = await fetch(url, {
  523. method: 'GET',
  524. headers: {
  525. 'Content-Type': 'application/json',
  526. 'Userurl': $CwebUrl,
  527. 'Origin': $CwebUrl
  528. }
  529. });
  530. const get_from_data = await response.json();
  531. // 只处理验证码相关的数据
  532. if (get_from_data.data?.table?.is_code === 1 && get_from_data.data?.code?.img) {
  533. showCaptcha.value = true
  534. captchaId.value = get_from_data.data.code.code_uniqid
  535. // 确保base64字符串包含正确的前缀
  536. if (get_from_data.data.code.img.startsWith('data:image')) {
  537. captchaImage.value = get_from_data.data.code.img
  538. } else {
  539. captchaImage.value = 'data:image/png;base64,' + get_from_data.data.code.img
  540. }
  541. // 清空验证码输入框
  542. formData.value.code = ''
  543. } else {
  544. showCaptcha.value = false
  545. captchaImage.value = ''
  546. captchaId.value = ''
  547. }
  548. } catch (error) {
  549. console.error('刷新验证码失败:', error);
  550. ElMessage.error('刷新验证码失败,请重试');
  551. }
  552. }
  553. // 获取form所有数据
  554. async function get_from_data_fun() {
  555. pageLoading.value = true
  556. try {
  557. // 拼接 GET 参数
  558. const params = new URLSearchParams({ table_id: table_id.value });
  559. const url = `${$webUrl}/form/getWebGlobalTableFieldList?${params.toString()}`;
  560. const response = await fetch(url, {
  561. method: 'GET',
  562. headers: {
  563. 'Content-Type': 'application/json',
  564. 'Userurl': $CwebUrl,
  565. 'Origin': $CwebUrl
  566. }
  567. });
  568. const get_from_data = await response.json();
  569. // 检查是否需要显示验证码(仅在初始化时)
  570. if (get_from_data.data?.table?.is_code === 1 && get_from_data.data?.code?.img) {
  571. showCaptcha.value = true
  572. captchaId.value = get_from_data.data.code.code_uniqid
  573. // 确保base64字符串包含正确的前缀
  574. if (get_from_data.data.code.img.startsWith('data:image')) {
  575. captchaImage.value = get_from_data.data.code.img
  576. } else {
  577. captchaImage.value = 'data:image/png;base64,' + get_from_data.data.code.img
  578. }
  579. } else {
  580. showCaptcha.value = false
  581. captchaImage.value = ''
  582. captchaId.value = ''
  583. }
  584. // 赋值表头和表体
  585. table_head.value = get_from_data.data?.fields || [];
  586. table_body.value = get_from_data.data?.table || [];
  587. get_from_data_1.value = get_from_data;
  588. // 统一 field_type 为数字或数字数组
  589. table_head.value.forEach(field => {
  590. if (typeof field.field_type === 'string') {
  591. if (field.field_type.includes(',')) {
  592. field.field_type = field.field_type.split(',').map(Number);
  593. } else {
  594. field.field_type = Number(field.field_type);
  595. }
  596. }
  597. });
  598. // 如果API返回的数据为空,使用默认数据
  599. if (table_head.value.length === 0) {
  600. table_head.value = [
  601. {
  602. field: 'name',
  603. title: '姓名',
  604. field_type: 1,
  605. is_check: 1
  606. },
  607. {
  608. field: 'email',
  609. title: '邮箱',
  610. field_type: 1,
  611. is_check: 1
  612. },
  613. {
  614. field: 'hobbies',
  615. title: '爱好',
  616. field_type: [5],
  617. is_check: 0,
  618. option_value: {1: "阅读", 2: "运动", 3: "音乐", 4: "旅游"}
  619. }
  620. ]
  621. }
  622. // 详细检查每个字段的结构
  623. table_head.value.forEach((field, index) => {
  624. if (field.option_value) {
  625. }
  626. })
  627. // 初始化表单数据
  628. table_head.value.forEach(field => {
  629. const fieldName = getFieldName(field)
  630. if (!fieldName) {
  631. console.warn(`字段 ${field.title} 无法确定字段名,跳过初始化`)
  632. return
  633. }
  634. // 根据字段类型初始化不同的默认值
  635. if (isArrayTypeField(field)) {
  636. // 数组类型字段(field_type为5或包含5的数组)初始化为空数组
  637. formData.value[fieldName] = []
  638. } else if (checkFieldType(field, 7) || checkFieldType(field, 8)) {
  639. formData.value[fieldName] = ''
  640. fileLists.value[fieldName] = []
  641. }
  642. else {
  643. // 其他类型初始化为空字符串
  644. formData.value[fieldName] = ''
  645. }
  646. })
  647. // 单独初始化验证码字段
  648. if (showCaptcha.value) {
  649. formData.value.code = ''
  650. }
  651. // 生成验证规则
  652. generateValidationRules()
  653. pageLoading.value = false
  654. } catch (error) {
  655. console.error('请求失败:', error);
  656. // 使用默认数据
  657. table_head.value = [
  658. {
  659. field: 'name',
  660. title: '姓名',
  661. field_type: 1,
  662. is_check: 1
  663. },
  664. {
  665. field: 'email',
  666. title: '邮箱',
  667. field_type: 1,
  668. is_check: 1
  669. },
  670. {
  671. field: 'hobbies',
  672. title: '爱好',
  673. field_type: [5],
  674. is_check: 0,
  675. option_value: {1: "阅读", 2: "运动", 3: "音乐", 4: "旅游"}
  676. }
  677. ]
  678. // 初始化默认数据
  679. table_head.value.forEach(field => {
  680. const fieldName = getFieldName(field)
  681. if (isArrayTypeField(field)) {
  682. formData.value[fieldName] = []
  683. } else if (checkFieldType(field, 7) || checkFieldType(field, 8)) {
  684. formData.value[fieldName] = ''
  685. fileLists.value[fieldName] = []
  686. }
  687. else {
  688. formData.value[fieldName] = ''
  689. }
  690. })
  691. // 单独初始化验证码字段
  692. if (showCaptcha.value) {
  693. formData.value.code = ''
  694. }
  695. generateValidationRules()
  696. pageLoading.value = false
  697. ElMessage.warning('API请求失败,使用默认数据')
  698. }
  699. }
  700. // 提交表单
  701. async function submitForm() {
  702. if (!formRef.value) {
  703. ElMessage.error('表单引用不存在')
  704. return
  705. }
  706. try {
  707. submitLoading.value = true
  708. // 手动检查必填字段
  709. const requiredFields = table_head.value.filter(field => field.is_check === 1)
  710. const missingFields = requiredFields.filter(field => {
  711. const fieldName = getFieldName(field)
  712. if (!fieldName) {
  713. console.warn(`字段 ${field.title} 无法确定字段名`)
  714. return true
  715. }
  716. const value = formData.value[fieldName]
  717. const isEmpty = value === '' || value === null || value === undefined || (Array.isArray(value) && value.length === 0)
  718. return isEmpty
  719. })
  720. if (missingFields.length > 0) {
  721. ElMessage.error(`请填写必填字段: ${missingFields.map(f => f.title).join(', ')}`)
  722. return
  723. }
  724. // 表单验证 - 使用正确的验证方式
  725. await formRef.value.validate(async (valid, fields) => {
  726. if (valid) {
  727. // 构建提交数据
  728. const otherDataPayload = { 'table_id': table_id.value };
  729. const dataToSubmit = { ...formData.value };
  730. if (showCaptcha.value) {
  731. otherDataPayload.code = dataToSubmit.code;
  732. otherDataPayload.code_uniqid = captchaId.value;
  733. delete dataToSubmit.code;
  734. }
  735. const submitData = {
  736. otherData: otherDataPayload,
  737. data: dataToSubmit
  738. }
  739. // 这里可以调用实际的提交接口
  740. const response = await fetch(`${$webUrl}/form/addWebGlobalTableData`, {
  741. method: 'POST',
  742. headers: {
  743. 'Content-Type': 'application/json',
  744. 'Userurl': $CwebUrl,
  745. 'Origin': $CwebUrl
  746. },
  747. body: JSON.stringify(submitData)
  748. })
  749. const result = await response.json();
  750. if(result.code==200){
  751. dialogVisible.value = true
  752. return
  753. }else{
  754. ElMessage.error(result.message)
  755. return
  756. }
  757. } else {
  758. ElMessage.error('请检查表单填写是否正确')
  759. }
  760. })
  761. } catch (error) {
  762. console.error('提交失败:', error)
  763. ElMessage.error('提交失败,请重试')
  764. } finally {
  765. submitLoading.value = false
  766. }
  767. }
  768. // 重置表单
  769. async function resetForm() {
  770. await get_from_data_fun();
  771. ElMessage.success('表单已重置');
  772. }
  773. // 下拉选择对象转数组
  774. function select_arr_fun(option_obj){
  775. if (!option_obj || typeof option_obj !== 'object') return []
  776. let new_arr = []
  777. // 遍历对象,key为value,value为label
  778. Object.keys(option_obj).forEach(key => {
  779. let the_in_obj = {}
  780. the_in_obj.label = option_obj[key]
  781. the_in_obj.value = +key // 转换为数字
  782. new_arr.push(the_in_obj)
  783. })
  784. return new_arr
  785. }
  786. // 单选按钮对象转数组
  787. function radio_arr_fun(option_obj) {
  788. if (!option_obj || typeof option_obj !== 'object') return []
  789. let new_arr = []
  790. // 遍历对象,key为value,value为label
  791. Object.keys(option_obj).forEach(key => {
  792. new_arr.push([option_obj[key], key]) // [label, value] 格式
  793. })
  794. return new_arr
  795. }
  796. // 复选框对象转数组
  797. function checkbox_arr_fun(option_obj) {
  798. if (!option_obj || typeof option_obj !== 'object') return []
  799. let new_arr = []
  800. // 遍历对象,key为value,value为label
  801. Object.keys(option_obj).forEach(key => {
  802. let the_in_obj = {}
  803. the_in_obj.label = option_obj[key]
  804. the_in_obj.value = String(key) // 确保值为字符串类型
  805. new_arr.push(the_in_obj)
  806. })
  807. return new_arr
  808. }
  809. // 文件上传相关
  810. // 文件超出限制时的处理
  811. const handleExceed = (files, fieldName) => {
  812. const upload = uploadRefs.value[fieldName];
  813. if (upload) {
  814. upload.clearFiles();
  815. const file = files[0];
  816. file.uid = genFileId();
  817. upload.handleStart(file);
  818. upload.submit();
  819. }
  820. };
  821. // 普通文件上传成功
  822. const handleFileSuccess = (res, file, fieldName) => {
  823. if (res.data && res.data.imgUrl) {
  824. formData.value[fieldName] = res.data.imgUrl;
  825. if (fileLists.value[fieldName]) {
  826. fileLists.value[fieldName] = [{
  827. name: file.name,
  828. url: res.data.imgUrl
  829. }];
  830. }
  831. }
  832. };
  833. // 文件移除
  834. const handleFileRemove = (fieldName) => {
  835. formData.value[fieldName] = '';
  836. if(fileLists.value[fieldName]) {
  837. fileLists.value[fieldName] = [];
  838. }
  839. };
  840. // img上传 8
  841. const handleAvatarSuccess = (res, fieldName) => {
  842. if (res.data && res.data.imgUrl) {
  843. formData.value[fieldName] = res.data.imgUrl
  844. }
  845. }
  846. const beforeAvatarUpload = (file) => {
  847. const isJPG = file.type === 'image/jpeg'
  848. const isPNG = file.type === 'image/png'
  849. const isLt2M = file.size / 1024 / 1024 < 2
  850. if (!isJPG && !isPNG) {
  851. ElMessage.error('只能上传 JPG/PNG 格式!')
  852. return false
  853. }
  854. if (!isLt2M) {
  855. ElMessage.error('图片大小不能超过 2MB!')
  856. return false
  857. }
  858. return (isJPG || isPNG) && isLt2M
  859. }
  860. // 页面加载时获取数据
  861. onMounted(async () => {
  862. await get_from_data_fun()
  863. // 确保表单引用存在
  864. await nextTick()
  865. if (formRef.value) {
  866. } else {
  867. }
  868. })
  869. // 复选框变化处理
  870. function handleCheckboxChange(field, value) {
  871. // 这里可以根据需要添加更多的逻辑处理
  872. }
  873. function parseRegExp(str) {
  874. if (typeof str === 'string' && str.startsWith('/') && str.lastIndexOf('/') > 0) {
  875. const lastSlash = str.lastIndexOf('/');
  876. const pattern = str.slice(1, lastSlash);
  877. const flags = str.slice(lastSlash + 1);
  878. return new RegExp(pattern, flags);
  879. }
  880. return new RegExp(str);
  881. }
  882. function onDialogConfirm() {
  883. dialogVisible.value = false;
  884. resetForm();
  885. }
  886. const skeletonChars = Array.from({ length: 16 }).map(() => ({
  887. top: Math.random() * 80 + 5, // 5%~85%
  888. left: Math.random() * 80 + 5, // 5%~85%
  889. fontSize: Math.random() * 60 + 60, // 60~120px
  890. opacity: Math.random() * 0.4 + 0.3, // 0.3~0.7
  891. rotate: Math.random() * 60 - 30 // -30~30deg
  892. }));
  893. </script>