index_form.vue 31 KB

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