index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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 v-for="(i, index) in formConfig" :key="index">
  14. <van-field
  15. v-if="i.type == 'input'"
  16. v-model="formData[i.prop]"
  17. :label="i.label"
  18. :name="i.prop"
  19. :type="i.itemType ? i.itemType : 'text'"
  20. :placeholder="i.placeholder ? i.placeholder : '请输入'"
  21. :clearable="i.clearable ? i.clearable : false"
  22. :rules="getRules(i.prop)"
  23. >
  24. </van-field>
  25. <van-field v-if="i.type == 'switch'" :label="i.label" :name="i.prop">
  26. <template #input>
  27. <van-switch v-model="formData[i.prop]" />
  28. </template>
  29. </van-field>
  30. <!-- 多选checkbox -->
  31. <van-field
  32. v-if="i.type == 'checkbox'"
  33. :label="i.label"
  34. :name="i.prop"
  35. :rules="getRules(i.prop)"
  36. >
  37. <template #input>
  38. <van-checkbox-group
  39. v-model="formData[i.prop]"
  40. direction="horizontal"
  41. >
  42. <van-checkbox
  43. shape="square"
  44. v-for="j in i.data"
  45. :key="j.value"
  46. :name="j.value"
  47. >{{ j.text }}</van-checkbox
  48. >
  49. </van-checkbox-group>
  50. </template>
  51. </van-field>
  52. <!-- 单选radio -->
  53. <van-field
  54. v-if="i.type == 'radio'"
  55. :label="i.label"
  56. :name="i.prop"
  57. :rules="getRules(i.prop)"
  58. >
  59. <template #input>
  60. <van-radio-group
  61. v-model="formData[i.prop]"
  62. direction="horizontal"
  63. >
  64. <van-radio
  65. v-for="j in i.data"
  66. :key="j.value"
  67. :name="j.value || j.id"
  68. >{{ j.label || j.title }}</van-radio
  69. >
  70. </van-radio-group>
  71. </template>
  72. </van-field>
  73. <!-- 单选 -->
  74. <van-field
  75. v-if="i.type == 'picker' && i.itemType == 'onePicker'"
  76. :label="i.label"
  77. :name="i.prop"
  78. v-model="formData[i.prop + 'Name']"
  79. is-link
  80. readonly
  81. @click="i.showPicker = true"
  82. :rules="getRules(i.prop)"
  83. >
  84. </van-field>
  85. <van-popup
  86. v-model:show="i.showPicker"
  87. round
  88. position="bottom"
  89. v-if="i.type == 'picker' && i.itemType == 'onePicker'"
  90. >
  91. <van-picker
  92. :columns="i.pickerOption.columns"
  93. @cancel="i.showPicker = false"
  94. @confirm="(option) => onConfirmPicker(option, i, index)"
  95. />
  96. </van-popup>
  97. <!-- 时间选择器 -->
  98. <van-field
  99. v-if="i.type == 'picker' && i.itemType == 'datePicker'"
  100. :label="i.label"
  101. :name="i.prop"
  102. v-model="formData[i.prop]"
  103. is-link
  104. readonly
  105. @click="i.showPicker = true"
  106. :rules="getRules(i.prop)"
  107. >
  108. </van-field>
  109. <van-popup
  110. v-model:show="i.showPicker"
  111. round
  112. position="bottom"
  113. v-if="i.type == 'picker' && i.itemType == 'datePicker'"
  114. >
  115. <van-date-picker
  116. @confirm="(option) => onConfirmPicker(option, i, index)"
  117. @cancel="i.showPicker = false"
  118. :min-date="i.minDate"
  119. :max-date="i.maxDate"
  120. :columns-type="i.columnsType"
  121. />
  122. </van-popup>
  123. <!-- 级联 城市 -->
  124. <van-field
  125. v-if="i.type == 'cascader' && i.itemType == 'city'"
  126. :label="i.label"
  127. :name="i.prop"
  128. v-model="formData[i.prop + 'Name']"
  129. is-link
  130. readonly
  131. @click="i.showPicker = true"
  132. :rules="getRules(i.prop)"
  133. />
  134. <van-popup
  135. v-if="i.type == 'cascader' && i.itemType == 'city'"
  136. v-model:show="i.showPicker"
  137. round
  138. position="bottom"
  139. >
  140. <van-cascader
  141. v-model="formData[i.prop]"
  142. :title="i.title ? i.title : '请选择'"
  143. :options="cityOption"
  144. @close="i.showPicker = false"
  145. @change="(option) => cityOnChange(option, i, index)"
  146. @finish="(option) => (i.finishFn ? i.finishFn(option) : () => {})"
  147. />
  148. </van-popup>
  149. <!-- 级联 公共 -->
  150. <van-field
  151. v-if="i.type == 'cascader' && i.itemType == 'common'"
  152. :label="i.label"
  153. :name="i.prop"
  154. v-model="formData[i.prop + 'Name']"
  155. is-link
  156. readonly
  157. @click="i.showPicker = true"
  158. :rules="getRules(i.prop)"
  159. />
  160. <van-popup
  161. v-if="i.type == 'cascader' && i.itemType == 'common'"
  162. v-model:show="i.showPicker"
  163. round
  164. position="bottom"
  165. >
  166. <van-cascader
  167. v-model="formData[i.prop]"
  168. :title="i.title ? i.title : '请选择'"
  169. :options="i.options"
  170. :field-names="i.fieldNames ? i.fieldNames : fieldNames"
  171. @close="i.showPicker = false"
  172. @change="(option) => commonOnChange(option, i, index)"
  173. @finish="
  174. (option) => (i.finishFn ? i.finishFn(i, option) : () => {})
  175. "
  176. />
  177. <!-- @change="
  178. (option) => (i.onChangeFn ? i.onChangeFn(i, option) : () => {})
  179. "
  180. @finish="
  181. (option) => (i.finishFn ? i.finishFn(i, option) : () => {})
  182. " -->
  183. </van-popup>
  184. <!-- 插槽 -->
  185. <van-field v-if="i.type == 'slot'" :label="i.label">
  186. <template #input>
  187. <div>
  188. <slot :name="i.slotName"> {{ i.slotName }}插槽占位符 </slot>
  189. </div>
  190. </template>
  191. </van-field>
  192. </div>
  193. </van-cell-group>
  194. <!-- 循环业务数据 -->
  195. <van-cell-group
  196. inset
  197. v-for="(item, index) in formData[btnConfigCopy.prop]"
  198. :key="index"
  199. style="margin-top: 10px !important"
  200. >
  201. <div>
  202. 明细{{ index + 1 }} <span @click="handleRemove(index)">删除</span>
  203. </div>
  204. <!-- 循环表单数据 -->
  205. <div v-for="(i, sonIndex) in btnConfigCopy.listConfig" :key="i.prop">
  206. <van-field
  207. v-if="i.type == 'input'"
  208. v-model="formData[btnConfigCopy.prop][index][i.prop]"
  209. :label="i.label"
  210. :name="i.prop"
  211. :type="i.itemType ? i.itemType : 'text'"
  212. :placeholder="i.placeholder ? i.placeholder : '请输入'"
  213. :clearable="i.clearable ? i.clearable : false"
  214. :rules="getRules(i.prop)"
  215. >
  216. </van-field>
  217. <!-- 单选 -->
  218. <van-field
  219. v-if="i.type == 'picker' && i.itemType == 'onePicker'"
  220. :label="i.label"
  221. :name="i.prop"
  222. v-model="formData[btnConfigCopy.prop][index][i.prop + 'Name']"
  223. is-link
  224. readonly
  225. @click="handleListItemClick(i, index, sonIndex)"
  226. :rules="getRules(i.prop)"
  227. >
  228. </van-field>
  229. <!-- 时间选择器 -->
  230. <van-field
  231. v-if="i.type == 'picker' && i.itemType == 'datePicker'"
  232. :label="i.label"
  233. :name="i.prop"
  234. v-model="formData[btnConfigCopy.prop][index][i.prop]"
  235. is-link
  236. readonly
  237. @click="handleListItemClick(i, index, sonIndex)"
  238. :rules="getRules(i.prop)"
  239. >
  240. </van-field>
  241. </div>
  242. </van-cell-group>
  243. <!-- 单独写个循环,保证弹窗唯一 -->
  244. <div v-for="(item, index) in btnConfigCopy.listConfig" :key="index">
  245. <van-popup
  246. v-model:show="item.showPicker"
  247. round
  248. position="bottom"
  249. v-if="item.type == 'picker' && item.itemType == 'onePicker'"
  250. >
  251. <van-picker
  252. :columns="item.pickerOption.columns"
  253. @cancel="item.showPicker = false"
  254. @confirm="(option) => onConfirmListPicker(option, item)"
  255. />
  256. </van-popup>
  257. <van-popup
  258. v-model:show="item.showPicker"
  259. round
  260. position="bottom"
  261. v-if="item.type == 'picker' && item.itemType == 'datePicker'"
  262. >
  263. <van-date-picker
  264. @confirm="(option) => onConfirmListPicker(option, item)"
  265. @cancel="item.showPicker = false"
  266. :min-date="item.minDate"
  267. :max-date="item.maxDate"
  268. :columns-type="item.columnsType"
  269. />
  270. </van-popup>
  271. </div>
  272. <!-- 按钮 -->
  273. <div class="btn-box">
  274. <van-button
  275. :plain="btnConfigCopy.plain ? btnConfigCopy.plain : false"
  276. :type="btnConfigCopy.itemType ? btnConfigCopy.itemType : 'primary'"
  277. :size="btnConfigCopy.size ? btnConfigCopy.size : 'small'"
  278. style="width: 100%; border: none"
  279. @click="handlePush()"
  280. >
  281. <template #icon>
  282. <van-icon
  283. :name="btnConfigCopy.icon ? btnConfigCopy.icon : 'plus'"
  284. :size="12"
  285. />
  286. </template>
  287. {{
  288. btnConfigCopy.btnName ? btnConfigCopy.btnName : "添加"
  289. }}</van-button
  290. >
  291. </div>
  292. <div style="margin: 16px">
  293. <van-button round block type="primary" native-type="submit">
  294. 提交
  295. </van-button>
  296. </div>
  297. </van-form>
  298. </div>
  299. </template>
  300. <script setup>
  301. import { showLoadingToast, closeToast, showNotify } from "vant";
  302. import {
  303. ref,
  304. getCurrentInstance,
  305. onMounted,
  306. reactive,
  307. computed,
  308. toRefs,
  309. watch,
  310. } from "vue";
  311. const props = defineProps({
  312. modelValue: {
  313. type: Object,
  314. default: false,
  315. },
  316. formConfig: {
  317. type: Array,
  318. default: false,
  319. },
  320. formOption: {
  321. type: Object,
  322. default: false,
  323. },
  324. rules: {
  325. type: Object,
  326. default: false,
  327. },
  328. });
  329. const proxy = getCurrentInstance().proxy;
  330. const { formConfig, formOption, rules } = toRefs(props);
  331. const formData = computed(() => {
  332. return proxy.modelValue;
  333. });
  334. //
  335. const fieldNames = { text: "label", value: "id" };
  336. const emit = defineEmits(["update:modelValue"]);
  337. const onSubmit = () => {
  338. emit("onSubmit");
  339. };
  340. // 获取验证规则
  341. const getRules = (prop) => {
  342. if (rules.value.hasOwnProperty(prop) && rules.value[prop]) {
  343. return rules.value[prop];
  344. }
  345. };
  346. const getReadonly = (i) => {
  347. return i.readonly ? i.readonly : i.name == "picker" ? true : false;
  348. };
  349. // 国家初始化
  350. const cityOption = ref([]);
  351. const cityOptionInit = () => {
  352. proxy.post("/areaInfo/list", formData.value).then((res) => {
  353. cityOption.value = res.data.map((item, index) => {
  354. return {
  355. ...item,
  356. index: index,
  357. text: item.chineseName,
  358. value: item.id,
  359. children: [],
  360. };
  361. });
  362. });
  363. };
  364. let btnConfigCopy = {};
  365. const btnPickerList = ref([]);
  366. const formDataInit = () => {
  367. var map = {
  368. input: "",
  369. radio: "",
  370. switch: false,
  371. checkbox: [],
  372. date: "",
  373. picker: "",
  374. cascader: "",
  375. };
  376. // 判断是否需要按钮
  377. if (formOption.value.btnConfig && formOption.value.btnConfig.isNeed) {
  378. formData.value[formOption.value.btnConfig.prop] = [];
  379. btnConfigCopy = { ...formOption.value.btnConfig };
  380. if (
  381. formOption.value.btnConfig.listConfig &&
  382. formOption.value.btnConfig.listConfig.length > 0
  383. ) {
  384. btnPickerList.value = formOption.value.btnConfig.listConfig
  385. .filter((x) => x.type === "picker")
  386. .map((x) => x.itemType);
  387. }
  388. }
  389. for (let i = 0; i < formConfig.value.length; i++) {
  390. const element = formConfig.value[i];
  391. if (element.type === "slot") {
  392. continue;
  393. }
  394. if (element.type === "cascader" && element.itemType === "city") {
  395. cityOptionInit();
  396. }
  397. if (element.type === "picker" || element.type === "cascader") {
  398. formData.value[element.prop] = map[element.type];
  399. formData.value[element.prop + "Name"] = map[element.type];
  400. }
  401. if (map[element.type] != undefined) {
  402. formData.value[element.prop] = map[element.type];
  403. }
  404. }
  405. emit("update:modelValue", formData.value);
  406. console.log(formData.value, "aws");
  407. };
  408. formDataInit();
  409. // 选择框 确定事件
  410. const onConfirmPicker = (option, item, index) => {
  411. switch (item.itemType) {
  412. case "onePicker": {
  413. formData.value[item.prop + "Name"] = option.selectedOptions[0].text;
  414. formData.value[item.prop] = option.selectedOptions[0].value;
  415. formConfig.value[index].showPicker = false;
  416. }
  417. case "datePicker": {
  418. formData.value[item.prop] = option.selectedValues.join(
  419. item.split ? item.split : "-"
  420. );
  421. formConfig.value[index].showPicker = false;
  422. }
  423. }
  424. };
  425. const currentIndex = ref(-1);
  426. const currentSonIndex = ref(-1);
  427. const handleListItemClick = (i, index, sonIndex) => {
  428. currentIndex.value = index;
  429. currentSonIndex.value = sonIndex;
  430. btnConfigCopy.listConfig[sonIndex].showPicker = true;
  431. };
  432. const onConfirmListPicker = (option, item) => {
  433. switch (item.itemType) {
  434. case "onePicker": {
  435. formData.value[btnConfigCopy.prop][currentIndex.value][
  436. item.prop + "Name"
  437. ] = option.selectedOptions[0].text;
  438. formData.value[btnConfigCopy.prop][currentIndex.value][item.prop] =
  439. option.selectedOptions[0].value;
  440. btnConfigCopy.listConfig[currentSonIndex.value].showPicker = false;
  441. }
  442. case "datePicker": {
  443. formData.value[btnConfigCopy.prop][currentIndex.value][item.prop] =
  444. option.selectedValues.join(item.split ? item.split : "-");
  445. btnConfigCopy.listConfig[currentSonIndex.value].showPicker = false;
  446. }
  447. }
  448. };
  449. // push事件
  450. const handlePush = () => {
  451. if (btnConfigCopy.clickFn && typeof btnConfigCopy.clickFn == "function") {
  452. btnConfigCopy.clickFn();
  453. }
  454. };
  455. // remove
  456. const handleRemove = (index) => {
  457. formData.value[btnConfigCopy.prop].splice(index, 1);
  458. };
  459. // 拉去城市最近数据及处理
  460. const getAreaInfo = (selectedOptions, item, index) => {
  461. showLoadingToast("加载中...");
  462. proxy
  463. .post("/areaInfo/list", { parentId: selectedOptions.value })
  464. .then((res) => {
  465. let countryIndex = selectedOptions.selectedOptions[0].index;
  466. let provinceIndex =
  467. selectedOptions.tabIndex === 1
  468. ? selectedOptions.selectedOptions[1].index
  469. : null;
  470. let cityIndex =
  471. selectedOptions.tabIndex === 2
  472. ? selectedOptions.selectedOptions[2].index
  473. : null;
  474. //已经没有下级数据
  475. if (res.data.length === 0) {
  476. if (selectedOptions.tabIndex === 1) {
  477. formData.value[item.prop + "Name"] = selectedOptions.selectedOptions
  478. .map((item) => item.text)
  479. .join(" ");
  480. formConfig.value[index].showPicker = false;
  481. formData.value.selectedOptions = selectedOptions;
  482. return;
  483. }
  484. }
  485. if (selectedOptions.tabIndex === 2) {
  486. formData.value[item.prop + "Name"] = selectedOptions.selectedOptions
  487. .map((item) => item.text)
  488. .join(" ");
  489. formConfig.value[index].showPicker = false;
  490. formData.value.selectedOptions = selectedOptions;
  491. return;
  492. }
  493. if (selectedOptions.tabIndex === 0) {
  494. cityOption.value[countryIndex].children = res.data.map(
  495. (item, index) => {
  496. return {
  497. ...item,
  498. index: index,
  499. text: item.name,
  500. value: item.id,
  501. };
  502. }
  503. );
  504. } else if (selectedOptions.tabIndex === 1) {
  505. cityOption.value[countryIndex].children[provinceIndex].children =
  506. res.data.map((item, index) => {
  507. return {
  508. ...item,
  509. index: index,
  510. text: item.name,
  511. value: item.id,
  512. };
  513. });
  514. } else if (selectedOptions.tabIndex === 2) {
  515. cityOption.value[countryIndex].children[provinceIndex].children[
  516. cityIndex
  517. ].children = res.data.map((item, index) => {
  518. return {
  519. ...item,
  520. index: index,
  521. text: item.name,
  522. value: item.id,
  523. };
  524. });
  525. }
  526. closeToast();
  527. });
  528. };
  529. // 城市变动事件
  530. const cityOnChange = (selectedOptions, item, index) => {
  531. getAreaInfo(selectedOptions, item, index);
  532. };
  533. const commonOnChange = ({ selectedOptions }, item, index) => {
  534. formData.value[item.prop + "Name"] =
  535. selectedOptions[selectedOptions.length - 1].label;
  536. };
  537. const testForm = ref(null); // 延迟使用,因为还没有返回跟挂载
  538. watch(
  539. formData.value,
  540. (val) => {
  541. emit("update:modelValue", val);
  542. },
  543. {
  544. deep: true,
  545. }
  546. );
  547. </script>
  548. <style lang="scss" scoped>
  549. .btn-box {
  550. margin-top: 10px;
  551. width: 100%;
  552. text-align: center;
  553. }
  554. </style>