index.vue 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205
  1. <template>
  2. <div>
  3. <van-form
  4. @submit="onSubmit"
  5. :disabled="formOption.disabled"
  6. :readonly="formOption.readonly"
  7. :label-width="formOption.labelWidth"
  8. :label-align="formOption.labelAlign || 'top'"
  9. :scroll-to-error="formOption.scroll"
  10. ref="testForm"
  11. >
  12. <van-cell-group inset>
  13. <div
  14. v-for="(i, index) in formConfig"
  15. :key="index"
  16. :style="i.style || ''"
  17. >
  18. <van-field v-if="i.type == 'title'" style="background: #ecebeb">
  19. <template #input>
  20. <div class="_title">
  21. {{ i.title }}
  22. </div>
  23. </template>
  24. </van-field>
  25. <van-field
  26. v-if="i.type == 'input'"
  27. v-model="formData[i.prop]"
  28. :label="i.label"
  29. :name="i.prop"
  30. :type="i.itemType ? i.itemType : 'text'"
  31. :placeholder="i.placeholder ? i.placeholder : '请输入'"
  32. :clearable="i.clearable ? i.clearable : false"
  33. :readonly="getFieldReadonly(i)"
  34. :rules="getRules(i)"
  35. :required="getRequired(i)"
  36. :right-icon="i.isNeedRightBtn ? i.rightIcon : ''"
  37. @click-right-icon="i.isNeedRightBtn ? i.rightIconClick() : () => {}"
  38. @blur="
  39. i.isNeedBlurMethon ? i.blurMethon(formData[i.prop]) : () => {}
  40. "
  41. @input="
  42. () => {
  43. return i.inputFn ? i.inputFn(formData[i.prop]) : () => {};
  44. }
  45. "
  46. >
  47. </van-field>
  48. <!-- switch -->
  49. <van-field
  50. v-if="i.type == 'switch'"
  51. :label="i.label"
  52. :name="i.prop"
  53. :required="getRequired(i)"
  54. >
  55. <template #input>
  56. <van-switch v-model="formData[i.prop]" />
  57. </template>
  58. </van-field>
  59. <!-- 多选checkbox -->
  60. <van-field
  61. v-if="i.type == 'checkbox'"
  62. :label="i.label"
  63. :name="i.prop"
  64. :rules="getRules(i)"
  65. :required="getRequired(i)"
  66. >
  67. <template #input>
  68. <van-checkbox-group
  69. v-model="formData[i.prop]"
  70. direction="horizontal"
  71. >
  72. <van-checkbox
  73. shape="square"
  74. v-for="j in i.data"
  75. :key="j.value"
  76. :name="j.value"
  77. >{{ j.text }}</van-checkbox
  78. >
  79. </van-checkbox-group>
  80. </template>
  81. </van-field>
  82. <!-- 单选radio -->
  83. <van-field
  84. v-if="i.type == 'radio'"
  85. :label="i.label"
  86. :name="i.prop"
  87. :rules="getRules(i)"
  88. :required="getRequired(i)"
  89. >
  90. <template #input>
  91. <van-radio-group
  92. v-model="formData[i.prop]"
  93. direction="horizontal"
  94. >
  95. <van-radio
  96. v-for="j in i.data"
  97. :key="j.value"
  98. :name="j.value || j.id"
  99. >{{ j.label || j.title }}</van-radio
  100. >
  101. </van-radio-group>
  102. </template>
  103. </van-field>
  104. <!-- 单选 -->
  105. <van-field
  106. v-if="i.type == 'picker' && i.itemType == 'onePicker'"
  107. v-show="!i.showStatus"
  108. :label="i.label"
  109. :name="i.prop"
  110. v-model="formData[i.prop + 'Name']"
  111. is-link
  112. :readonly="true"
  113. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  114. @click="
  115. () =>
  116. formOption.readonly || i.readonly ? '' : (i.showPicker = true)
  117. "
  118. :rules="getRules(i)"
  119. :required="getRequired(i)"
  120. >
  121. </van-field>
  122. <van-popup
  123. v-model:show="i.showPicker"
  124. round
  125. position="bottom"
  126. v-if="i.type == 'picker' && i.itemType == 'onePicker'"
  127. >
  128. <div class="searchBox">
  129. <van-search
  130. v-if="i.isNeedSearch && i.isNeedSearch === true"
  131. v-model="i.searchKeyword"
  132. show-action
  133. label=""
  134. placeholder="请输入搜索关键词"
  135. @search="onSearchData(i)"
  136. >
  137. <template #action>
  138. <div @click="onSearchData(i)">搜索</div>
  139. </template>
  140. </van-search>
  141. </div>
  142. <van-picker
  143. :columns="i.data"
  144. :columns-field-names="
  145. i.fieldNames ? i.fieldNames : onePickerFieldNames
  146. "
  147. @cancel="i.showPicker = false"
  148. @confirm="
  149. (option) =>
  150. i.changeFn
  151. ? i.changeFn(option, i, index)
  152. : onConfirmPicker(option, i, index)
  153. "
  154. />
  155. </van-popup>
  156. <!-- 多选 -->
  157. <van-field
  158. v-if="i.type == 'multipleChoice' && i.itemType == 'multiple'"
  159. v-show="!i.showStatus"
  160. :label="i.label"
  161. :name="i.prop"
  162. v-model="formData[i.prop + 'Name']"
  163. is-link
  164. :readonly="true"
  165. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  166. @click="() => (!formOption.readonly ? (i.showPicker = true) : '')"
  167. :rules="getRules(i)"
  168. :required="getRequired(i)"
  169. >
  170. </van-field>
  171. <van-popup
  172. v-model:show="i.showPicker"
  173. round
  174. position="bottom"
  175. :style="{ height: '40%' }"
  176. v-if="i.type == 'multipleChoice' && i.itemType == 'multiple'"
  177. >
  178. <van-checkbox-group
  179. v-model="formData[i.prop]"
  180. @change="
  181. changeCheckboxGroup(formData, i.prop, i.data, i.fieldNames)
  182. "
  183. class="multipleChoice"
  184. >
  185. <van-checkbox
  186. v-for="item in i.data"
  187. :key="item.value"
  188. :name="item.value"
  189. >{{ item.text }}</van-checkbox
  190. >
  191. </van-checkbox-group>
  192. </van-popup>
  193. <!-- 时间选择器 -->
  194. <van-field
  195. v-if="i.type == 'picker' && i.itemType == 'datePicker'"
  196. :label="i.label"
  197. :name="i.prop"
  198. v-model="formData[i.prop]"
  199. is-link
  200. :readonly="true"
  201. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  202. @click="() => (!formOption.readonly ? (i.showPicker = true) : '')"
  203. :rules="getRules(i)"
  204. :required="getRequired(i)"
  205. >
  206. </van-field>
  207. <van-popup
  208. v-model:show="i.showPicker"
  209. round
  210. position="bottom"
  211. v-if="i.type == 'picker' && i.itemType == 'datePicker'"
  212. >
  213. <van-date-picker
  214. v-model="currentDate"
  215. @confirm="(option) => onConfirmPicker(option, i, index)"
  216. @cancel="i.showPicker = false"
  217. :min-date="i.minDate"
  218. :max-date="i.maxDate"
  219. :columns-type="i.columnsType"
  220. />
  221. </van-popup>
  222. <!-- 时间选择器 带时分秒 -->
  223. <van-field
  224. v-if="i.type == 'picker' && i.itemType == 'datePickerTime'"
  225. :label="i.label"
  226. :name="i.prop"
  227. v-model="formData[i.prop]"
  228. is-link
  229. :readonly="true"
  230. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  231. @click="
  232. () =>
  233. !formOption.readonly
  234. ? i.needDefault
  235. ? defaultTimeFn(i, index)
  236. : (i.showPicker = true)
  237. : ''
  238. "
  239. :rules="getRules(i)"
  240. :required="getRequired(i)"
  241. >
  242. </van-field>
  243. <van-popup
  244. v-model:show="i.showPicker"
  245. round
  246. position="bottom"
  247. v-if="i.type == 'picker' && i.itemType == 'datePickerTime'"
  248. >
  249. <van-picker-group
  250. :tabs="['日期', '时间']"
  251. @confirm="() => datePickerTimeConfirm(i, index)"
  252. @cancel="i.showPicker = false"
  253. >
  254. <van-date-picker
  255. v-model="datePickerDateArr"
  256. :columns-type="i.columnsType"
  257. />
  258. <van-time-picker
  259. v-model="datePickerTimeArr"
  260. :columns-type="['hour', 'minute', 'second']"
  261. />
  262. </van-picker-group>
  263. </van-popup>
  264. <!-- 级联 城市 -->
  265. <van-field
  266. v-if="i.type == 'cascader' && i.itemType == 'city'"
  267. :label="i.label"
  268. :name="i.prop"
  269. v-model="formData[i.prop + 'Name']"
  270. is-link
  271. :readonly="true"
  272. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  273. @click="() => (!formOption.readonly ? (i.showPicker = true) : '')"
  274. :rules="getRules(i)"
  275. :required="getRequired(i)"
  276. />
  277. <van-popup
  278. v-if="i.type == 'cascader' && i.itemType == 'city'"
  279. v-model:show="i.showPicker"
  280. round
  281. position="bottom"
  282. >
  283. <van-cascader
  284. v-model="formData[i.prop]"
  285. :title="i.title ? i.title : '请选择'"
  286. :options="cityOption"
  287. @close="i.showPicker = false"
  288. @change="(option) => cityOnChange(option, i, index)"
  289. @finish="
  290. (option) =>
  291. i.finishFn ? i.finishFn(option) : () => (i.showPicker = false)
  292. "
  293. />
  294. </van-popup>
  295. <!-- 级联 公共 -->
  296. <van-field
  297. v-if="i.type == 'cascader' && i.itemType == 'common'"
  298. :label="i.label"
  299. :name="i.prop"
  300. v-model="formData[i.prop + 'Name']"
  301. is-link
  302. :readonly="true"
  303. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  304. @click="() => (!formOption.readonly ? (i.showPicker = true) : '')"
  305. :rules="getRules(i)"
  306. :required="getRequired(i)"
  307. />
  308. <van-popup
  309. v-if="i.type == 'cascader' && i.itemType == 'common'"
  310. v-model:show="i.showPicker"
  311. round
  312. position="bottom"
  313. >
  314. <van-cascader
  315. v-model="formData[i.prop]"
  316. :title="i.title ? i.title : '请选择'"
  317. :options="i.data"
  318. :field-names="i.fieldNames ? i.fieldNames : fieldNames"
  319. @close="i.showPicker = false"
  320. @change="(option) => commonOnChange(option, i, index)"
  321. @finish="
  322. (option) =>
  323. i.finishFn ? i.finishFn(i, option) : handleCommonFinish(index)
  324. "
  325. />
  326. </van-popup>
  327. <!-- 文件上传 -->
  328. <van-field
  329. name="uploader"
  330. v-if="i.type == 'upload'"
  331. :label="i.label"
  332. :readonly="i.readonly ? true : false"
  333. >
  334. <template #input>
  335. <van-uploader
  336. v-model="formData[i.prop]"
  337. :after-read="afterRead"
  338. multiple
  339. :show-upload="i.showUpload === undefined ? true : i.showUpload"
  340. :max-count="9"
  341. :max-size="5 * 1024 * 1024"
  342. @oversize="onOversize"
  343. />
  344. </template>
  345. </van-field>
  346. <!-- 插槽 -->
  347. <van-field
  348. v-if="i.type == 'slot'"
  349. :label="i.label"
  350. :rules="getRules(i)"
  351. :required="getRequired(i)"
  352. >
  353. <template #input>
  354. <div style="width: 100%">
  355. <slot :name="i.slotName"> {{ i.slotName }}插槽占位符 </slot>
  356. </div>
  357. </template>
  358. </van-field>
  359. </div>
  360. </van-cell-group>
  361. <!-- 循环业务数据 -->
  362. <van-cell-group
  363. inset
  364. v-for="(item, index) in formData[btnConfigCopy.prop]"
  365. :key="index"
  366. >
  367. <div class="row">
  368. <div>{{ btnConfigCopy.listTitle || "明细" }}{{ index + 1 }}</div>
  369. <van-button
  370. plain
  371. type="primary"
  372. @click="handleRemove(index, btnConfigCopy)"
  373. size="mini"
  374. style="border: none; background: #ecebeb"
  375. :disabled="formOption.readonly"
  376. v-if="
  377. formOption.btnConfig !== undefined && formOption.btnConfig.isNeed
  378. "
  379. >删除</van-button
  380. >
  381. </div>
  382. <!-- 循环表单数据 -->
  383. <div v-for="(i, sonIndex) in btnConfigCopy.listConfig" :key="i.prop">
  384. <van-field
  385. v-if="i.type == 'input'"
  386. v-model="formData[btnConfigCopy.prop][index][i.prop]"
  387. :label="i.label"
  388. :name="i.prop"
  389. :type="i.itemType ? i.itemType : 'text'"
  390. :placeholder="i.placeholder ? i.placeholder : '请输入'"
  391. :clearable="i.clearable ? i.clearable : false"
  392. :readonly="getFieldReadonly(i)"
  393. :rules="getRules(i)"
  394. :required="getRequired(i)"
  395. @change="
  396. (val) => {
  397. return i.changeFn ? i.changeFn(index, val) : () => {};
  398. }
  399. "
  400. >
  401. </van-field>
  402. <!-- 单选 -->
  403. <van-field
  404. v-if="i.type == 'picker' && i.itemType == 'onePicker'"
  405. :label="i.label"
  406. :name="i.prop"
  407. v-model="formData[btnConfigCopy.prop][index][i.prop + 'Name']"
  408. is-link
  409. :readonly="true"
  410. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  411. @click="handleListItemClick(i, index, sonIndex)"
  412. :rules="getRules(i)"
  413. :required="getRequired(i)"
  414. >
  415. <template #input v-if="i.isShowScanCode">
  416. <div style="display: flex; height: 24px">
  417. <div style="width: calc(100vw - 100px)">
  418. {{ formData[btnConfigCopy.prop][index][i.prop + "Name"] }}
  419. </div>
  420. <div style="width: 100px; float: right; margin-top: -20px">
  421. <van-button
  422. plain
  423. type="primary"
  424. @click.native.stop="i.scanCode(index)"
  425. size="mini"
  426. style="border: none"
  427. >扫码</van-button
  428. >
  429. </div>
  430. </div>
  431. </template>
  432. </van-field>
  433. <!-- 时间选择器 -->
  434. <van-field
  435. v-if="i.type == 'picker' && i.itemType == 'datePicker'"
  436. :label="i.label"
  437. :name="i.prop"
  438. v-model="formData[btnConfigCopy.prop][index][i.prop]"
  439. is-link
  440. :readonly="true"
  441. :placeholder="i.placeholder ? i.placeholder : '请选择'"
  442. @click="handleListItemClick(i, index, sonIndex)"
  443. :rules="getRules(i)"
  444. :required="getRequired(i)"
  445. >
  446. </van-field>
  447. </div>
  448. </van-cell-group>
  449. <!-- 单独写个循环,保证弹窗唯一 -->
  450. <div v-for="(item, index) in btnConfigCopy.listConfig" :key="index">
  451. <van-popup
  452. v-model:show="item.showPicker"
  453. round
  454. position="bottom"
  455. v-if="item.type == 'picker' && item.itemType == 'onePicker'"
  456. >
  457. <div class="searchBox">
  458. <van-search
  459. v-if="item.isNeedSearch && item.isNeedSearch === true"
  460. v-model="item.searchKeyword"
  461. show-action
  462. label=""
  463. placeholder="请输入搜索关键词"
  464. @search="onSearchData(item)"
  465. >
  466. <template #action>
  467. <div @click="onSearchData(item)">搜索</div>
  468. </template>
  469. </van-search>
  470. </div>
  471. <van-picker
  472. :columns="item.data"
  473. :columns-field-names="
  474. item.fieldNames ? item.fieldNames : onePickerFieldNames
  475. "
  476. @cancel="item.showPicker = false"
  477. @confirm="
  478. (option) =>
  479. item.changeFn
  480. ? item.changeFn(
  481. option,
  482. item,
  483. currentIndex,
  484. currentSonIndex,
  485. btnConfigCopy.prop
  486. )
  487. : onConfirmListPicker(option, item)
  488. "
  489. />
  490. </van-popup>
  491. <van-popup
  492. v-model:show="item.showPicker"
  493. round
  494. position="bottom"
  495. v-if="item.type == 'picker' && item.itemType == 'datePicker'"
  496. >
  497. <van-date-picker
  498. @confirm="(option) => onConfirmListPicker(option, item)"
  499. @cancel="item.showPicker = false"
  500. :min-date="item.minDate"
  501. :max-date="item.maxDate"
  502. :columns-type="item.columnsType"
  503. />
  504. </van-popup>
  505. </div>
  506. <!-- 按钮 -->
  507. <div
  508. class="btn-box"
  509. v-if="formOption.btnConfig !== undefined && formOption.btnConfig.isNeed"
  510. >
  511. <van-button
  512. :plain="btnConfigCopy.plain ? btnConfigCopy.plain : false"
  513. :type="btnConfigCopy.itemType ? btnConfigCopy.itemType : 'primary'"
  514. :size="btnConfigCopy.size ? btnConfigCopy.size : 'small'"
  515. style="width: 100%; border: none"
  516. @click="handlePush()"
  517. >
  518. <template #icon>
  519. <van-icon
  520. :name="btnConfigCopy.icon ? btnConfigCopy.icon : 'plus'"
  521. :size="12"
  522. />
  523. </template>
  524. {{
  525. btnConfigCopy.btnName ? btnConfigCopy.btnName : "添加"
  526. }}</van-button
  527. >
  528. </div>
  529. <div style="margin: 16px" v-show="!formOption.hiddenSubmitBtn">
  530. <van-button round block type="primary" native-type="submit">
  531. {{ formOption.submitBtnText || "提交" }}
  532. </van-button>
  533. </div>
  534. <div style="margin: 16px" v-show="formOption.otherBtn">
  535. <van-button round block type="warning" @click="handleOtherBtnClick">
  536. {{ formOption.otherBtnText || "其他" }}
  537. </van-button>
  538. </div>
  539. </van-form>
  540. </div>
  541. </template>
  542. <script setup>
  543. import { showLoadingToast, closeToast, showNotify } from "vant";
  544. import { formatDate } from "@/utils/auth";
  545. import {
  546. ref,
  547. getCurrentInstance,
  548. onMounted,
  549. reactive,
  550. computed,
  551. toRefs,
  552. watch,
  553. } from "vue";
  554. const props = defineProps({
  555. modelValue: {
  556. type: Object,
  557. default: false,
  558. },
  559. formConfig: {
  560. type: Array,
  561. default: false,
  562. },
  563. formOption: {
  564. type: Object,
  565. default: false,
  566. },
  567. rules: {
  568. type: Object,
  569. default: false,
  570. },
  571. });
  572. const proxy = getCurrentInstance().proxy;
  573. const { formConfig, formOption, rules } = toRefs(props);
  574. const formData = computed(() => {
  575. return proxy.modelValue;
  576. });
  577. const cityOption = ref([]);
  578. const fieldNames = { text: "label", value: "id", children: "children" };
  579. const onePickerFieldNames = {
  580. text: "text",
  581. value: "value",
  582. };
  583. const emit = defineEmits(["update:modelValue"]);
  584. const onSubmit = () => {
  585. emit("onSubmit");
  586. };
  587. const handleOtherBtnClick = () => {
  588. emit("otherBtnClick");
  589. };
  590. // 获取验证规则
  591. const getRules = (i) => {
  592. if (
  593. rules.value.hasOwnProperty(i.prop) &&
  594. rules.value[i.prop] &&
  595. !getFieldReadonly(i) &&
  596. !formOption.readonly
  597. ) {
  598. return rules.value[i.prop];
  599. }
  600. };
  601. const getRequired = (i) => {
  602. if (
  603. rules.value.hasOwnProperty(i.prop) &&
  604. rules.value[i.prop] &&
  605. !getFieldReadonly(i) &&
  606. !formOption.readonly
  607. ) {
  608. return true;
  609. }
  610. return false;
  611. };
  612. const getFieldReadonly = (i) => {
  613. if (i.readonly && i.readonly === true) {
  614. return true;
  615. } else {
  616. if (formOption.value.readonly && formOption.value.readonly === true) {
  617. return true;
  618. } else {
  619. return false;
  620. }
  621. }
  622. };
  623. // 国家初始化
  624. const cityOptionInit = () => {
  625. proxy.post("/customizeArea/list", { parentId: "0" }).then((res) => {
  626. cityOption.value = res.data.map((item, index) => {
  627. return {
  628. ...item,
  629. index: index,
  630. text: item.name,
  631. value: item.id,
  632. children: [],
  633. };
  634. });
  635. });
  636. };
  637. const recursionFn = (arr, val, valueAtt, childrenAtt) => {
  638. for (let i = 0; i < arr.length; i++) {
  639. const e = arr[i];
  640. if (e[valueAtt] !== val) {
  641. if (e[childrenAtt] && e[childrenAtt].length > 0) {
  642. const current = recursionFn(e.children, val, valueAtt, childrenAtt);
  643. if (current) {
  644. return current;
  645. }
  646. }
  647. } else {
  648. return e;
  649. }
  650. }
  651. };
  652. const selectDataEcho = (item, val) => {
  653. if (item.type === "picker" && item.itemType === "onePicker") {
  654. const textAtt = item.fieldNames
  655. ? item.fieldNames.text
  656. : onePickerFieldNames.text;
  657. const valueAtt = item.fieldNames
  658. ? item.fieldNames.value
  659. : onePickerFieldNames.value;
  660. const current = item.data.find((x) => x[valueAtt] === val);
  661. return current ? current[textAtt] : "";
  662. } else if (item.type === "cascader" && item.itemType === "common") {
  663. const textAtt = item.fieldNames ? item.fieldNames.text : fieldNames.text;
  664. const valueAtt = item.fieldNames ? item.fieldNames.value : fieldNames.value;
  665. const childrenAtt = item.fieldNames
  666. ? item.fieldNames.children
  667. : fieldNames.children;
  668. const arr = item.data ? item.data : [];
  669. const current = recursionFn(arr, val, valueAtt, childrenAtt);
  670. return current ? current[textAtt] : "";
  671. }
  672. };
  673. let btnConfigCopy = {};
  674. let callNum = ref(0);
  675. let callListNum = ref(0);
  676. const formDataListShowLabel = () => {
  677. for (let i = 0; i < formData.value[btnConfigCopy.prop].length; i++) {
  678. for (let j = 0; j < btnConfigCopy.listConfig.length; j++) {
  679. const jele = btnConfigCopy.listConfig[j];
  680. if (jele.type === "picker" && jele.itemType !== "datePicker") {
  681. if (jele.data && jele.data.length > 0) {
  682. formData.value[btnConfigCopy.prop][i][jele.prop + "Name"] =
  683. selectDataEcho(
  684. jele,
  685. formData.value[btnConfigCopy.prop][i][jele.prop]
  686. );
  687. } else {
  688. if (callListNum.value <= 3) {
  689. setTimeout(() => {
  690. callListNum.value++;
  691. return formDataListShowLabel();
  692. }, 1500);
  693. }
  694. return;
  695. }
  696. }
  697. }
  698. }
  699. };
  700. const formDataListShowLabelOne = () => {
  701. for (let i = 0; i < formData.value[btnConfigCopy.prop].length; i++) {
  702. for (let j = 0; j < btnConfigCopy.listConfig.length; j++) {
  703. const jele = btnConfigCopy.listConfig[j];
  704. if (jele.type === "picker" && jele.itemType !== "datePicker") {
  705. if (jele.data && jele.data.length > 0) {
  706. console.log(jele.data, "wwwww");
  707. formData.value[btnConfigCopy.prop][i][jele.prop + "Name"] =
  708. selectDataEcho(
  709. jele,
  710. formData.value[btnConfigCopy.prop][i][jele.prop]
  711. );
  712. }
  713. }
  714. }
  715. }
  716. };
  717. // 循环 label值回显
  718. const formDataShowLabel = () => {
  719. for (let i = 0; i < formConfig.value.length; i++) {
  720. const element = formConfig.value[i];
  721. if (element.type === "picker" && element.itemType !== "datePicker") {
  722. if (element.data && element.data.length > 0) {
  723. formData.value[element.prop + "Name"] = selectDataEcho(
  724. element,
  725. formData.value[element.prop]
  726. );
  727. } else {
  728. if (callNum.value <= 3) {
  729. setTimeout(() => {
  730. callNum.value++;
  731. return formDataShowLabel();
  732. }, 1500);
  733. }
  734. return;
  735. }
  736. } else if (element.type === "cascader" && element.itemType === "common") {
  737. if (element.data && element.data.length > 0) {
  738. formData.value[element.prop + "Name"] = selectDataEcho(
  739. element,
  740. formData.value[element.prop]
  741. );
  742. } else {
  743. if (callNum.value <= 3) {
  744. setTimeout(() => {
  745. callNum.value++;
  746. return formDataShowLabel();
  747. }, 1500);
  748. }
  749. return;
  750. }
  751. }
  752. }
  753. };
  754. const formDataShowLabelOne = () => {
  755. for (let i = 0; i < formConfig.value.length; i++) {
  756. const element = formConfig.value[i];
  757. if (element.type === "picker" && element.itemType !== "datePicker") {
  758. if (element.data && element.data.length > 0) {
  759. formData.value[element.prop + "Name"] = selectDataEcho(
  760. element,
  761. formData.value[element.prop]
  762. );
  763. }
  764. } else if (element.type === "cascader" && element.itemType === "common") {
  765. if (element.data && element.data.length > 0) {
  766. formData.value[element.prop + "Name"] = selectDataEcho(
  767. element,
  768. formData.value[element.prop]
  769. );
  770. }
  771. }
  772. }
  773. };
  774. const formDataInit = () => {
  775. var map = {
  776. input: "",
  777. radio: "",
  778. switch: false,
  779. checkbox: [],
  780. date: "",
  781. picker: "",
  782. cascader: "",
  783. upload: [],
  784. };
  785. // 判断是否需要按钮
  786. if (
  787. formOption.value.btnConfig &&
  788. Object.keys(formOption.value.btnConfig).length > 0
  789. ) {
  790. btnConfigCopy = { ...formOption.value.btnConfig };
  791. if (formData.value[btnConfigCopy.prop] === undefined) {
  792. formData.value[btnConfigCopy.prop] = [];
  793. }
  794. }
  795. // 初始化默认值
  796. let cityStatus = true;
  797. for (let i = 0; i < formConfig.value.length; i++) {
  798. const element = formConfig.value[i];
  799. if (
  800. element.type === "cascader" &&
  801. element.itemType === "city" &&
  802. cityStatus
  803. ) {
  804. cityStatus = false;
  805. cityOptionInit();
  806. }
  807. if (
  808. formData.value[element.prop] === undefined ||
  809. formData.value[element.prop] === ""
  810. ) {
  811. if (element.type === "slot") {
  812. continue;
  813. } else if (element.type === "picker" || element.type === "cascader") {
  814. if (element.itemType !== "datePicker") {
  815. formData.value[element.prop] = map[element.type];
  816. formData.value[element.prop + "Name"] = map[element.type];
  817. }
  818. } else if (map[element.type] != undefined) {
  819. formData.value[element.prop] = map[element.type];
  820. }
  821. }
  822. }
  823. formDataShowLabel();
  824. emit("update:modelValue", formData.value);
  825. };
  826. formDataInit();
  827. // 选择框 确定事件
  828. const onConfirmPicker = (option, item, index) => {
  829. if (option.selectedOptions[0]) {
  830. switch (item.itemType) {
  831. case "onePicker": {
  832. formData.value[item.prop + "Name"] =
  833. option.selectedOptions[0][
  834. item.fieldNames.text
  835. ? item.fieldNames.text
  836. : onePickerFieldNames.text
  837. ];
  838. formData.value[item.prop] =
  839. option.selectedOptions[0][
  840. item.fieldNames.value
  841. ? item.fieldNames.value
  842. : onePickerFieldNames.value
  843. ];
  844. formConfig.value[index].showPicker = false;
  845. }
  846. case "datePicker": {
  847. formData.value[item.prop] = option.selectedValues.join(
  848. item.split ? item.split : "-"
  849. );
  850. formConfig.value[index].showPicker = false;
  851. }
  852. }
  853. } else {
  854. formConfig.value[index].showPicker = false;
  855. }
  856. };
  857. const currentIndex = ref(-1);
  858. const currentSonIndex = ref(-1);
  859. const handleListItemClick = (i, index, sonIndex) => {
  860. if (i.readonly !== undefined && i.readonly === true) {
  861. return;
  862. } else {
  863. if (formOption.value.readonly) {
  864. return;
  865. } else {
  866. currentIndex.value = index;
  867. currentSonIndex.value = sonIndex;
  868. btnConfigCopy.listConfig[sonIndex].showPicker = true;
  869. }
  870. }
  871. };
  872. const onConfirmListPicker = (option, item) => {
  873. switch (item.itemType) {
  874. case "onePicker": {
  875. formData.value[btnConfigCopy.prop][currentIndex.value][
  876. item.prop + "Name"
  877. ] =
  878. option.selectedOptions[0][
  879. item.fieldNames.text ? item.fieldNames.text : onePickerFieldNames.text
  880. ];
  881. formData.value[btnConfigCopy.prop][currentIndex.value][item.prop] =
  882. option.selectedOptions[0][
  883. item.fieldNames.value
  884. ? item.fieldNames.value
  885. : onePickerFieldNames.value
  886. ];
  887. btnConfigCopy.listConfig[currentSonIndex.value].showPicker = false;
  888. }
  889. case "datePicker": {
  890. formData.value[btnConfigCopy.prop][currentIndex.value][item.prop] =
  891. option.selectedValues.join(item.split ? item.split : "-");
  892. btnConfigCopy.listConfig[currentSonIndex.value].showPicker = false;
  893. }
  894. }
  895. };
  896. // push事件
  897. const handlePush = () => {
  898. if (btnConfigCopy.clickFn && typeof btnConfigCopy.clickFn == "function") {
  899. btnConfigCopy.clickFn();
  900. }
  901. };
  902. // remove
  903. const handleRemove = (index, item) => {
  904. if (item.deleteFn) {
  905. item.deleteFn(index);
  906. } else {
  907. formData.value[btnConfigCopy.prop].splice(index, 1);
  908. }
  909. };
  910. // 拉去城市最近数据及处理
  911. const getAreaInfo = (selectedOptions, item, index) => {
  912. showLoadingToast("加载中...");
  913. proxy
  914. .post("/customizeArea/list", { parentId: selectedOptions.value })
  915. .then((res) => {
  916. let countryIndex = selectedOptions.selectedOptions[0].index;
  917. let provinceIndex =
  918. selectedOptions.tabIndex === 1
  919. ? selectedOptions.selectedOptions[1].index
  920. : null;
  921. let cityIndex =
  922. selectedOptions.tabIndex === 2
  923. ? selectedOptions.selectedOptions[2].index
  924. : null;
  925. //已经没有下级数据
  926. if (res.data.length === 0) {
  927. if (selectedOptions.tabIndex === 1) {
  928. formData.value[item.prop + "Name"] = selectedOptions.selectedOptions
  929. .map((item) => item.text)
  930. .join(" ");
  931. formConfig.value[index].showPicker = false;
  932. formData.value.cityObj = selectedOptions;
  933. return;
  934. }
  935. }
  936. if (selectedOptions.tabIndex === 2) {
  937. formData.value[item.prop + "Name"] = selectedOptions.selectedOptions
  938. .map((item) => item.text)
  939. .join(" ");
  940. formConfig.value[index].showPicker = false;
  941. formData.value.cityObj = selectedOptions;
  942. return;
  943. }
  944. if (selectedOptions.tabIndex === 0) {
  945. cityOption.value[countryIndex].children = res.data.map(
  946. (item, index) => {
  947. return {
  948. ...item,
  949. index: index,
  950. text: item.name,
  951. value: item.id,
  952. };
  953. }
  954. );
  955. } else if (selectedOptions.tabIndex === 1) {
  956. cityOption.value[countryIndex].children[provinceIndex].children =
  957. res.data.map((item, index) => {
  958. return {
  959. ...item,
  960. index: index,
  961. text: item.name,
  962. value: item.id,
  963. };
  964. });
  965. } else if (selectedOptions.tabIndex === 2) {
  966. cityOption.value[countryIndex].children[provinceIndex].children[
  967. cityIndex
  968. ].children = res.data.map((item, index) => {
  969. return {
  970. ...item,
  971. index: index,
  972. text: item.name,
  973. value: item.id,
  974. };
  975. });
  976. }
  977. closeToast();
  978. });
  979. };
  980. // 城市变动事件
  981. const cityOnChange = (options, item, index) => {
  982. getAreaInfo(options, item, index);
  983. };
  984. const commonOnChange = ({ selectedOptions }, item, index) => {
  985. const textAtt = item.fieldNames ? item.fieldNames.text : fieldNames.text;
  986. formData.value[item.prop + "Name"] =
  987. selectedOptions[selectedOptions.length - 1][textAtt];
  988. };
  989. const handleCommonFinish = (index) => {
  990. formConfig.value[index].showPicker = false;
  991. };
  992. // 文件上传
  993. const onOversize = () => {
  994. showToast("文件大小不能超过 5MB");
  995. };
  996. const afterRead = (file) => {
  997. if (file && file.length > 0) {
  998. for (let i = 0; i < file.length; i++) {
  999. file[i].status = "uploading";
  1000. file[i].message = "上传中...";
  1001. proxy.post("/fileInfo/getSing", { fileName: file[i].file.name }).then(
  1002. (res) => {
  1003. let forms = new FormData();
  1004. forms.append("file", file[i].file);
  1005. proxy
  1006. .post("https://winfaster.obs.cn-south-1.myhuaweicloud.com", {
  1007. ...res.data.uploadBody,
  1008. file: forms.get("file"),
  1009. })
  1010. .then(
  1011. () => {
  1012. file[i].id = res.data.id;
  1013. file[i].url = res.data.fileUrl;
  1014. file[i].fileName = res.data.fileName;
  1015. delete file[i].status;
  1016. delete file[i].message;
  1017. },
  1018. () => {
  1019. file[i].status = "failed";
  1020. file[i].message = "上传失败";
  1021. }
  1022. );
  1023. },
  1024. () => {
  1025. file[i].status = "failed";
  1026. file[i].message = "上传失败";
  1027. }
  1028. );
  1029. }
  1030. } else {
  1031. file.status = "uploading";
  1032. file.message = "上传中...";
  1033. proxy.post("/fileInfo/getSing", { fileName: file.file.name }).then(
  1034. (res) => {
  1035. let forms = new FormData();
  1036. forms.append("file", file.file);
  1037. proxy
  1038. .post("https://winfaster.obs.cn-south-1.myhuaweicloud.com", {
  1039. ...res.data.uploadBody,
  1040. file: forms.get("file"),
  1041. })
  1042. .then(
  1043. () => {
  1044. file.id = res.data.id;
  1045. file.url = res.data.fileUrl;
  1046. file.fileName = res.data.fileName;
  1047. delete file.status;
  1048. delete file.message;
  1049. },
  1050. () => {
  1051. file.status = "failed";
  1052. file.message = "上传失败";
  1053. }
  1054. );
  1055. },
  1056. () => {
  1057. file.status = "failed";
  1058. file.message = "上传失败";
  1059. }
  1060. );
  1061. }
  1062. };
  1063. const testForm = ref(null); // 延迟使用,因为还没有返回跟挂载
  1064. watch(
  1065. formData.value,
  1066. (val) => {
  1067. emit("update:modelValue", val);
  1068. },
  1069. {
  1070. deep: true,
  1071. }
  1072. );
  1073. watch(
  1074. () => formData.value[btnConfigCopy.prop],
  1075. (val) => {
  1076. if (
  1077. formOption.value.btnConfig !== undefined &&
  1078. !formOption.value.btnConfig.isNeed &&
  1079. val &&
  1080. val.length > 0
  1081. ) {
  1082. formDataListShowLabel();
  1083. }
  1084. }
  1085. );
  1086. const changeCheckboxGroup = (form, label, data, fieldNames) => {
  1087. let text = "";
  1088. if (form[label] && form[label].length > 0) {
  1089. form[label].map((item) => {
  1090. let list = data.filter((itemData) => itemData[fieldNames.value] === item);
  1091. if (list && list.length > 0) {
  1092. if (text) {
  1093. text = text + "," + list[0][fieldNames.text];
  1094. } else {
  1095. text = list[0][fieldNames.text];
  1096. }
  1097. }
  1098. });
  1099. }
  1100. form[label + "Name"] = text;
  1101. };
  1102. const datePickerDateArr = ref([]);
  1103. const datePickerTimeArr = ref([]);
  1104. const datePickerTimeConfirm = (item, index) => {
  1105. formData.value[item.prop] =
  1106. datePickerDateArr.value.join("-") + " " + datePickerTimeArr.value.join(":");
  1107. formConfig.value[index].showPicker = false;
  1108. };
  1109. const defaultTimeFn = (item, index) => {
  1110. datePickerDateArr.value = formatDate(
  1111. new Date(formData.value[item.prop]),
  1112. "yyyy-MM-dd"
  1113. ).split("-");
  1114. datePickerTimeArr.value = formatDate(
  1115. new Date(formData.value[item.prop]),
  1116. "hh:mm:ss"
  1117. ).split(":");
  1118. formConfig.value[index].showPicker = true;
  1119. };
  1120. const onSearchData = (item) => {
  1121. item.onSearchData(item.searchKeyword);
  1122. };
  1123. const currentDate = ref(["2023", "01", "01"]);
  1124. const initCurrentDate = () => {
  1125. const date = new Date();
  1126. const year = date.getFullYear();
  1127. const month = date.getMonth() + 1;
  1128. const day = date.getDate();
  1129. currentDate.value = [year + "", month + "", day + ""];
  1130. };
  1131. initCurrentDate();
  1132. const validateForm = async () => {
  1133. try {
  1134. const flag = await testForm.value.validate();
  1135. return flag;
  1136. } catch (err) {
  1137. return true;
  1138. }
  1139. };
  1140. defineExpose({
  1141. formDataShowLabelOne,
  1142. formDataListShowLabelOne,
  1143. btnConfigCopy,
  1144. validateForm,
  1145. });
  1146. </script>
  1147. <style lang="scss" scoped>
  1148. .btn-box {
  1149. width: 100%;
  1150. text-align: center;
  1151. background: #f2f2f2;
  1152. padding: 10px 0;
  1153. }
  1154. .row {
  1155. color: #999999;
  1156. background: #ecebeb;
  1157. height: 32px;
  1158. line-height: 32px;
  1159. display: flex;
  1160. justify-content: space-between;
  1161. align-items: center;
  1162. padding: 0 15px;
  1163. }
  1164. ._title {
  1165. font-size: 14px;
  1166. font-weight: 700;
  1167. }
  1168. ::v-deep {
  1169. .multipleChoice {
  1170. margin-bottom: 60px;
  1171. .van-checkbox {
  1172. justify-content: center;
  1173. margin: 10px 40px;
  1174. }
  1175. }
  1176. .van-form {
  1177. margin-top: 0px !important;
  1178. }
  1179. }
  1180. ::v-deep {
  1181. .searchBox {
  1182. .van-field__label--top {
  1183. width: 20px !important;
  1184. }
  1185. }
  1186. }
  1187. </style>