index_form.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  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;
  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. .form_box {
  267. width: 1200px;
  268. margin: 20px auto;
  269. border: dashed 1px #000;
  270. padding: 20px;
  271. }
  272. .form_box :deep(.avatar-uploader) {
  273. border: 1px dashed var(--el-border-color);
  274. border-radius: 6px;
  275. cursor: pointer;
  276. position: relative;
  277. overflow: hidden;
  278. width: 178px;
  279. height: 178px;
  280. }
  281. .form_box :deep(.avatar-uploader:hover) {
  282. border-color: var(--el-color-primary);
  283. }
  284. .form_box :deep(.avatar-uploader .avatar) {
  285. width: 100%;
  286. height: 100%;
  287. display: block;
  288. }
  289. .form_box :deep(.avatar-uploader .el-upload) {
  290. width: 100%;
  291. height: 100%;
  292. }
  293. .form_box :deep(.el-button--primary) {
  294. padding: 0 11px;
  295. }
  296. @media screen and (min-width:1200px) {
  297. /*pc_1440*/
  298. @media screen and (max-width:1440px) {
  299. /*1200*/
  300. }
  301. .pc_none {
  302. display: none;
  303. }
  304. }
  305. @media screen and (max-width:599px) {}
  306. @media screen and (max-width:320px) {}
  307. .form_box .el-form-item {
  308. margin-bottom: 20px!important;
  309. }
  310. .avatar-uploader-placeholder {
  311. display: flex;
  312. flex-direction: column;
  313. justify-content: center;
  314. align-items: center;
  315. width: 100%;
  316. height: 100%;
  317. }
  318. .avatar-placeholder-img {
  319. width: 50px;
  320. height: 50px;
  321. opacity: 0.5;
  322. }
  323. .upload-tip {
  324. margin-top: 8px;
  325. color: #8c939d;
  326. font-size: 12px;
  327. }
  328. .form-actions :deep(.el-form-item__content) {
  329. justify-content: center;
  330. }
  331. .form-actions .el-button + .el-button {
  332. margin-left: 20px;
  333. padding: 0 11px;
  334. }
  335. .captcha-wrapper {
  336. display: flex;
  337. align-items: center;
  338. width: 100%;
  339. }
  340. .captcha-img {
  341. margin-left: 10px;
  342. cursor: pointer;
  343. height: 32px; /* 与el-input默认高度对齐 */
  344. border-radius: 4px;
  345. }
  346. .form_box :deep(.el-checkbox) {
  347. margin-right: 20px;
  348. }
  349. .form_box :deep(.el-radio) {
  350. margin-right: 20px;
  351. }
  352. /* 弹窗footer按钮居中 */
  353. .dialog-footer-center {
  354. display: flex;
  355. justify-content: center;
  356. align-items: center;
  357. }
  358. .dialog-confirm-btn {
  359. padding: 0 32px;
  360. }
  361. .custom-skeleton-multi {
  362. position: relative;
  363. width: 100%;
  364. height: 700px;
  365. background: #f5f6fa;
  366. border-radius: 8px;
  367. overflow: hidden;
  368. }
  369. .custom-skeleton-char {
  370. position: absolute;
  371. color: #e5e6eb;
  372. font-weight: bold;
  373. letter-spacing: 10px;
  374. user-select: none;
  375. pointer-events: none;
  376. transition: all 0.3s;
  377. }
  378. </style>
  379. <script setup>
  380. //页面是否已经加载完毕
  381. const pageLoading = ref(true)
  382. const submitLoading = ref(false)
  383. const showCaptcha = ref(false)
  384. const captchaImage = ref('')
  385. const captchaId = ref('')
  386. const dialogVisible = ref(false)
  387. //1.加载页面必备组件 start---------------------------------------->
  388. import { ref, onMounted, reactive, nextTick, onBeforeUpdate } from 'vue';
  389. import { useSeoMeta } from '#imports';
  390. import { ElForm, ElFormItem, ElInput, ElRadioGroup, ElRadio,
  391. ElCheckboxGroup, ElCheckbox, ElUpload, ElMessage,
  392. ElSelect, ElOption, ElDatePicker, ElButton, genFileId } from 'element-plus'
  393. const { $webUrl, $CwebUrl } = useNuxtApp();
  394. // 表单引用
  395. const formRef = ref(null)
  396. // 上传组件引用
  397. const uploadRefs = ref({});
  398. const fileLists = ref({});
  399. onBeforeUpdate(() => {
  400. uploadRefs.value = {};
  401. });
  402. let adImg = ref({})
  403. //广告1
  404. let url = `${$webUrl}/web/getWebsiteAdvertisement?ad_tag=tsbb_index_1`
  405. const responseAd1 = await fetch(url, {
  406. headers: {
  407. 'Content-Type': 'application/json',
  408. 'Userurl': $CwebUrl,
  409. 'Origin': $CwebUrl
  410. }
  411. });
  412. const resultAd1 = await responseAd1.json();
  413. adImg.value = resultAd1.data[0];
  414. let adImg_2 = ref({})
  415. //广告_2
  416. let url_2 = `${$webUrl}/web/getWebsiteAdvertisement?ad_tag=tsbb_index_2`
  417. const responseAd_2 = await fetch(url_2, {
  418. headers: {
  419. 'Content-Type': 'application/json',
  420. 'Userurl': $CwebUrl,
  421. 'Origin': $CwebUrl
  422. }
  423. });
  424. const resultAd_2 = await responseAd_2.json();
  425. adImg_2.value = resultAd_2.data[0];
  426. // 动态验证规则
  427. const rules_js = ref({})
  428. const get_from_data_1 = reactive({
  429. value:{}
  430. });
  431. const chinese_calendar = reactive({});
  432. //列表头
  433. const table_head = reactive({
  434. value:[]
  435. });
  436. //列表内容
  437. const table_body = reactive({
  438. value:[]
  439. });
  440. // 表单数据 - 使用ref而不是reactive来避免响应式问题
  441. const formData = ref({})
  442. const route = useRoute()
  443. // 从路由获取 table_id,如果不存在则使用默认值
  444. const table_id = ref(route.query.table_id || 59)
  445. // 得到id
  446. const recive_id = reactive({
  447. value:route.query.id || route.params.id
  448. })
  449. // 工具函数定义
  450. function getFieldName(field) {
  451. if (field.field) return field.field;
  452. const possibleFieldNames = ['name', 'field_name', 'key', 'id'];
  453. for (const name of possibleFieldNames) {
  454. if (field[name]) return field[name];
  455. }
  456. return undefined;
  457. }
  458. // 检查字段类型的函数,支持数组格式
  459. function checkFieldType(field, type) {
  460. if (!field.field_type) return false;
  461. if (Array.isArray(field.field_type)) {
  462. return field.field_type.map(Number).includes(Number(type));
  463. }
  464. return Number(field.field_type) === Number(type);
  465. }
  466. // 检查是否为数组类型字段(field_type为5或包含5的数组)
  467. function isArrayTypeField(field) {
  468. return checkFieldType(field, 5);
  469. }
  470. // 生成动态验证规则
  471. function generateValidationRules() {
  472. const rules = {}
  473. table_head.value.forEach((field, index) => {
  474. const fieldRules = []
  475. const fieldName = getFieldName(field)
  476. // 必填验证
  477. if (field.is_check === 1) {
  478. fieldRules.push({
  479. required: true,
  480. message: `${field.title}不能为空`,
  481. trigger: ['blur', 'change']
  482. })
  483. }
  484. // 正则验证
  485. if (field.regular && field.regular.trim()) {
  486. try {
  487. const regex = parseRegExp(field.regular)
  488. fieldRules.push({
  489. validator: (rule, value, callback) => {
  490. if (value && !regex.test(value)) {
  491. callback(new Error(`${field.title}格式不正确`))
  492. } else {
  493. callback()
  494. }
  495. },
  496. trigger: ['blur', 'change']
  497. })
  498. } catch (error) {
  499. console.error(`正则表达式错误: ${field.regular}`, error)
  500. }
  501. }
  502. if (fieldRules.length > 0 && fieldName) {
  503. rules[fieldName] = fieldRules
  504. }
  505. })
  506. // 添加验证码规则
  507. if (showCaptcha.value) {
  508. rules['code'] = [
  509. { required: true, message: '验证码不能为空', trigger: 'blur' }
  510. ]
  511. }
  512. rules_js.value = rules
  513. }
  514. // 刷新验证码
  515. async function refreshCaptcha() {
  516. try {
  517. // 拼接 GET 参数
  518. const params = new URLSearchParams({ table_id: table_id.value });
  519. const url = `${$webUrl}/form/getWebGlobalTableFieldList?${params.toString()}`;
  520. const response = await fetch(url, {
  521. method: 'GET',
  522. headers: {
  523. 'Content-Type': 'application/json',
  524. 'Userurl': $CwebUrl,
  525. 'Origin': $CwebUrl
  526. }
  527. });
  528. const get_from_data = await response.json();
  529. // 只处理验证码相关的数据
  530. if (get_from_data.data?.table?.is_code === 1 && get_from_data.data?.code?.img) {
  531. showCaptcha.value = true
  532. captchaId.value = get_from_data.data.code.code_uniqid
  533. // 确保base64字符串包含正确的前缀
  534. if (get_from_data.data.code.img.startsWith('data:image')) {
  535. captchaImage.value = get_from_data.data.code.img
  536. } else {
  537. captchaImage.value = 'data:image/png;base64,' + get_from_data.data.code.img
  538. }
  539. // 清空验证码输入框
  540. formData.value.code = ''
  541. } else {
  542. showCaptcha.value = false
  543. captchaImage.value = ''
  544. captchaId.value = ''
  545. }
  546. } catch (error) {
  547. console.error('刷新验证码失败:', error);
  548. ElMessage.error('刷新验证码失败,请重试');
  549. }
  550. }
  551. // 获取form所有数据
  552. async function get_from_data_fun() {
  553. pageLoading.value = true
  554. try {
  555. // 拼接 GET 参数
  556. const params = new URLSearchParams({ table_id: table_id.value });
  557. const url = `${$webUrl}/form/getWebGlobalTableFieldList?${params.toString()}`;
  558. const response = await fetch(url, {
  559. method: 'GET',
  560. headers: {
  561. 'Content-Type': 'application/json',
  562. 'Userurl': $CwebUrl,
  563. 'Origin': $CwebUrl
  564. }
  565. });
  566. const get_from_data = await response.json();
  567. // 检查是否需要显示验证码(仅在初始化时)
  568. if (get_from_data.data?.table?.is_code === 1 && get_from_data.data?.code?.img) {
  569. showCaptcha.value = true
  570. captchaId.value = get_from_data.data.code.code_uniqid
  571. // 确保base64字符串包含正确的前缀
  572. if (get_from_data.data.code.img.startsWith('data:image')) {
  573. captchaImage.value = get_from_data.data.code.img
  574. } else {
  575. captchaImage.value = 'data:image/png;base64,' + get_from_data.data.code.img
  576. }
  577. } else {
  578. showCaptcha.value = false
  579. captchaImage.value = ''
  580. captchaId.value = ''
  581. }
  582. // 赋值表头和表体
  583. table_head.value = get_from_data.data?.fields || [];
  584. table_body.value = get_from_data.data?.table || [];
  585. get_from_data_1.value = get_from_data;
  586. // 统一 field_type 为数字或数字数组
  587. table_head.value.forEach(field => {
  588. if (typeof field.field_type === 'string') {
  589. if (field.field_type.includes(',')) {
  590. field.field_type = field.field_type.split(',').map(Number);
  591. } else {
  592. field.field_type = Number(field.field_type);
  593. }
  594. }
  595. });
  596. // 如果API返回的数据为空,使用默认数据
  597. if (table_head.value.length === 0) {
  598. table_head.value = [
  599. {
  600. field: 'name',
  601. title: '姓名',
  602. field_type: 1,
  603. is_check: 1
  604. },
  605. {
  606. field: 'email',
  607. title: '邮箱',
  608. field_type: 1,
  609. is_check: 1
  610. },
  611. {
  612. field: 'hobbies',
  613. title: '爱好',
  614. field_type: [5],
  615. is_check: 0,
  616. option_value: {1: "阅读", 2: "运动", 3: "音乐", 4: "旅游"}
  617. }
  618. ]
  619. }
  620. // 详细检查每个字段的结构
  621. table_head.value.forEach((field, index) => {
  622. if (field.option_value) {
  623. }
  624. })
  625. // 初始化表单数据
  626. table_head.value.forEach(field => {
  627. const fieldName = getFieldName(field)
  628. if (!fieldName) {
  629. console.warn(`字段 ${field.title} 无法确定字段名,跳过初始化`)
  630. return
  631. }
  632. // 根据字段类型初始化不同的默认值
  633. if (isArrayTypeField(field)) {
  634. // 数组类型字段(field_type为5或包含5的数组)初始化为空数组
  635. formData.value[fieldName] = []
  636. } else if (checkFieldType(field, 7) || checkFieldType(field, 8)) {
  637. formData.value[fieldName] = ''
  638. fileLists.value[fieldName] = []
  639. }
  640. else {
  641. // 其他类型初始化为空字符串
  642. formData.value[fieldName] = ''
  643. }
  644. })
  645. // 单独初始化验证码字段
  646. if (showCaptcha.value) {
  647. formData.value.code = ''
  648. }
  649. // 生成验证规则
  650. generateValidationRules()
  651. pageLoading.value = false
  652. } catch (error) {
  653. console.error('请求失败:', error);
  654. // 使用默认数据
  655. table_head.value = [
  656. {
  657. field: 'name',
  658. title: '姓名',
  659. field_type: 1,
  660. is_check: 1
  661. },
  662. {
  663. field: 'email',
  664. title: '邮箱',
  665. field_type: 1,
  666. is_check: 1
  667. },
  668. {
  669. field: 'hobbies',
  670. title: '爱好',
  671. field_type: [5],
  672. is_check: 0,
  673. option_value: {1: "阅读", 2: "运动", 3: "音乐", 4: "旅游"}
  674. }
  675. ]
  676. // 初始化默认数据
  677. table_head.value.forEach(field => {
  678. const fieldName = getFieldName(field)
  679. if (isArrayTypeField(field)) {
  680. formData.value[fieldName] = []
  681. } else if (checkFieldType(field, 7) || checkFieldType(field, 8)) {
  682. formData.value[fieldName] = ''
  683. fileLists.value[fieldName] = []
  684. }
  685. else {
  686. formData.value[fieldName] = ''
  687. }
  688. })
  689. // 单独初始化验证码字段
  690. if (showCaptcha.value) {
  691. formData.value.code = ''
  692. }
  693. generateValidationRules()
  694. pageLoading.value = false
  695. ElMessage.warning('API请求失败,使用默认数据')
  696. }
  697. }
  698. // 提交表单
  699. async function submitForm() {
  700. if (!formRef.value) {
  701. ElMessage.error('表单引用不存在')
  702. return
  703. }
  704. try {
  705. submitLoading.value = true
  706. // 手动检查必填字段
  707. const requiredFields = table_head.value.filter(field => field.is_check === 1)
  708. const missingFields = requiredFields.filter(field => {
  709. const fieldName = getFieldName(field)
  710. if (!fieldName) {
  711. console.warn(`字段 ${field.title} 无法确定字段名`)
  712. return true
  713. }
  714. const value = formData.value[fieldName]
  715. const isEmpty = value === '' || value === null || value === undefined || (Array.isArray(value) && value.length === 0)
  716. return isEmpty
  717. })
  718. if (missingFields.length > 0) {
  719. ElMessage.error(`请填写必填字段: ${missingFields.map(f => f.title).join(', ')}`)
  720. return
  721. }
  722. // 表单验证 - 使用正确的验证方式
  723. await formRef.value.validate(async (valid, fields) => {
  724. if (valid) {
  725. // 构建提交数据
  726. const otherDataPayload = { 'table_id': table_id.value };
  727. const dataToSubmit = { ...formData.value };
  728. if (showCaptcha.value) {
  729. otherDataPayload.code = dataToSubmit.code;
  730. otherDataPayload.code_uniqid = captchaId.value;
  731. delete dataToSubmit.code;
  732. }
  733. const submitData = {
  734. otherData: otherDataPayload,
  735. data: dataToSubmit
  736. }
  737. // 这里可以调用实际的提交接口
  738. const response = await fetch(`${$webUrl}/form/addWebGlobalTableData`, {
  739. method: 'POST',
  740. headers: {
  741. 'Content-Type': 'application/json',
  742. 'Userurl': $CwebUrl,
  743. 'Origin': $CwebUrl
  744. },
  745. body: JSON.stringify(submitData)
  746. })
  747. const result = await response.json();
  748. if(result.code==200){
  749. dialogVisible.value = true
  750. return
  751. }else{
  752. ElMessage.error(result.message)
  753. return
  754. }
  755. } else {
  756. ElMessage.error('请检查表单填写是否正确')
  757. }
  758. })
  759. } catch (error) {
  760. console.error('提交失败:', error)
  761. ElMessage.error('提交失败,请重试')
  762. } finally {
  763. submitLoading.value = false
  764. }
  765. }
  766. // 重置表单
  767. async function resetForm() {
  768. await get_from_data_fun();
  769. ElMessage.success('表单已重置');
  770. }
  771. // 下拉选择对象转数组
  772. function select_arr_fun(option_obj){
  773. if (!option_obj || typeof option_obj !== 'object') return []
  774. let new_arr = []
  775. // 遍历对象,key为value,value为label
  776. Object.keys(option_obj).forEach(key => {
  777. let the_in_obj = {}
  778. the_in_obj.label = option_obj[key]
  779. the_in_obj.value = +key // 转换为数字
  780. new_arr.push(the_in_obj)
  781. })
  782. return new_arr
  783. }
  784. // 单选按钮对象转数组
  785. function radio_arr_fun(option_obj) {
  786. if (!option_obj || typeof option_obj !== 'object') return []
  787. let new_arr = []
  788. // 遍历对象,key为value,value为label
  789. Object.keys(option_obj).forEach(key => {
  790. new_arr.push([option_obj[key], key]) // [label, value] 格式
  791. })
  792. return new_arr
  793. }
  794. // 复选框对象转数组
  795. function checkbox_arr_fun(option_obj) {
  796. if (!option_obj || typeof option_obj !== 'object') return []
  797. let new_arr = []
  798. // 遍历对象,key为value,value为label
  799. Object.keys(option_obj).forEach(key => {
  800. let the_in_obj = {}
  801. the_in_obj.label = option_obj[key]
  802. the_in_obj.value = String(key) // 确保值为字符串类型
  803. new_arr.push(the_in_obj)
  804. })
  805. return new_arr
  806. }
  807. // 文件上传相关
  808. // 文件超出限制时的处理
  809. const handleExceed = (files, fieldName) => {
  810. const upload = uploadRefs.value[fieldName];
  811. if (upload) {
  812. upload.clearFiles();
  813. const file = files[0];
  814. file.uid = genFileId();
  815. upload.handleStart(file);
  816. upload.submit();
  817. }
  818. };
  819. // 普通文件上传成功
  820. const handleFileSuccess = (res, file, fieldName) => {
  821. if (res.data && res.data.imgUrl) {
  822. formData.value[fieldName] = res.data.imgUrl;
  823. if (fileLists.value[fieldName]) {
  824. fileLists.value[fieldName] = [{
  825. name: file.name,
  826. url: res.data.imgUrl
  827. }];
  828. }
  829. }
  830. };
  831. // 文件移除
  832. const handleFileRemove = (fieldName) => {
  833. formData.value[fieldName] = '';
  834. if(fileLists.value[fieldName]) {
  835. fileLists.value[fieldName] = [];
  836. }
  837. };
  838. // img上传 8
  839. const handleAvatarSuccess = (res, fieldName) => {
  840. if (res.data && res.data.imgUrl) {
  841. formData.value[fieldName] = res.data.imgUrl
  842. }
  843. }
  844. const beforeAvatarUpload = (file) => {
  845. const isJPG = file.type === 'image/jpeg'
  846. const isPNG = file.type === 'image/png'
  847. const isLt2M = file.size / 1024 / 1024 < 2
  848. if (!isJPG && !isPNG) {
  849. ElMessage.error('只能上传 JPG/PNG 格式!')
  850. return false
  851. }
  852. if (!isLt2M) {
  853. ElMessage.error('图片大小不能超过 2MB!')
  854. return false
  855. }
  856. return (isJPG || isPNG) && isLt2M
  857. }
  858. // 页面加载时获取数据
  859. onMounted(async () => {
  860. await get_from_data_fun()
  861. // 确保表单引用存在
  862. await nextTick()
  863. if (formRef.value) {
  864. } else {
  865. }
  866. })
  867. // 复选框变化处理
  868. function handleCheckboxChange(field, value) {
  869. // 这里可以根据需要添加更多的逻辑处理
  870. }
  871. function parseRegExp(str) {
  872. if (typeof str === 'string' && str.startsWith('/') && str.lastIndexOf('/') > 0) {
  873. const lastSlash = str.lastIndexOf('/');
  874. const pattern = str.slice(1, lastSlash);
  875. const flags = str.slice(lastSlash + 1);
  876. return new RegExp(pattern, flags);
  877. }
  878. return new RegExp(str);
  879. }
  880. function onDialogConfirm() {
  881. dialogVisible.value = false;
  882. resetForm();
  883. }
  884. const skeletonChars = Array.from({ length: 16 }).map(() => ({
  885. top: Math.random() * 80 + 5, // 5%~85%
  886. left: Math.random() * 80 + 5, // 5%~85%
  887. fontSize: Math.random() * 60 + 60, // 60~120px
  888. opacity: Math.random() * 0.4 + 0.3, // 0.3~0.7
  889. rotate: Math.random() * 60 - 30 // -30~30deg
  890. }));
  891. </script>