index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. <template>
  2. <PageWrapper title="表单基础示例" contentFullHeight>
  3. <CollapseContainer title="基础示例">
  4. <BasicForm
  5. autoFocusFirstItem
  6. :labelWidth="200"
  7. :schemas="schemas"
  8. :actionColOptions="{ span: 24 }"
  9. @submit="handleSubmit"
  10. @reset="handleReset"
  11. >
  12. <template #selectA="{ model, field }">
  13. <a-select
  14. :options="optionsA"
  15. mode="multiple"
  16. v-model:value="model[field]"
  17. @change="valueSelectA = model[field]"
  18. allowClear
  19. />
  20. </template>
  21. <template #selectB="{ model, field }">
  22. <a-select
  23. :options="optionsB"
  24. mode="multiple"
  25. v-model:value="model[field]"
  26. @change="valueSelectB = model[field]"
  27. allowClear
  28. />
  29. </template>
  30. <template #localSearch="{ model, field }">
  31. <ApiSelect
  32. :api="optionsListApi"
  33. showSearch
  34. v-model:value="model[field]"
  35. optionFilterProp="label"
  36. resultField="list"
  37. labelField="name"
  38. valueField="id"
  39. />
  40. </template>
  41. <template #remoteSearch="{ model, field }">
  42. <ApiSelect
  43. :api="optionsListApi"
  44. showSearch
  45. v-model:value="model[field]"
  46. :filterOption="false"
  47. resultField="list"
  48. labelField="name"
  49. valueField="id"
  50. :params="searchParams"
  51. @search="onSearch"
  52. />
  53. </template>
  54. </BasicForm>
  55. </CollapseContainer>
  56. </PageWrapper>
  57. </template>
  58. <script lang="ts">
  59. import { type Recordable } from '@vben/types';
  60. import { computed, defineComponent, unref, ref } from 'vue';
  61. import { BasicForm, FormSchema, ApiSelect } from '/@/components/Form/index';
  62. import { CollapseContainer } from '/@/components/Container';
  63. import { useMessage } from '/@/hooks/web/useMessage';
  64. import { PageWrapper } from '/@/components/Page';
  65. import { optionsListApi } from '/@/api/demo/select';
  66. import { useDebounceFn } from '@vueuse/core';
  67. import { treeOptionsListApi } from '/@/api/demo/tree';
  68. import { Select, type SelectProps } from 'ant-design-vue';
  69. import { cloneDeep } from 'lodash-es';
  70. import { areaRecord } from '/@/api/demo/cascader';
  71. import { uploadApi } from '/@/api/sys/upload';
  72. // import { isArray } from '/@/utils/is';
  73. const valueSelectA = ref<string[]>([]);
  74. const valueSelectB = ref<string[]>([]);
  75. const options = ref<Required<SelectProps>['options']>([]);
  76. for (let i = 1; i < 10; i++) options.value.push({ label: '选项' + i, value: `${i}` });
  77. const optionsA = computed(() => {
  78. return cloneDeep(unref(options)).map((op) => {
  79. op.disabled = unref(valueSelectB).indexOf(op.value as string) !== -1;
  80. return op;
  81. });
  82. });
  83. const optionsB = computed(() => {
  84. return cloneDeep(unref(options)).map((op) => {
  85. op.disabled = unref(valueSelectA).indexOf(op.value as string) !== -1;
  86. return op;
  87. });
  88. });
  89. const provincesOptions = [
  90. {
  91. id: 'guangdong',
  92. label: '广东省',
  93. value: '1',
  94. key: '1',
  95. },
  96. {
  97. id: 'jiangsu',
  98. label: '江苏省',
  99. value: '2',
  100. key: '2',
  101. },
  102. ];
  103. const citiesOptionsData = {
  104. guangdong: [
  105. {
  106. label: '珠海市',
  107. value: '1',
  108. key: '1',
  109. },
  110. {
  111. label: '深圳市',
  112. value: '2',
  113. key: '2',
  114. },
  115. {
  116. label: '广州市',
  117. value: '3',
  118. key: '3',
  119. },
  120. ],
  121. jiangsu: [
  122. {
  123. label: '南京市',
  124. value: '1',
  125. key: '1',
  126. },
  127. {
  128. label: '无锡市',
  129. value: '2',
  130. key: '2',
  131. },
  132. {
  133. label: '苏州市',
  134. value: '3',
  135. key: '3',
  136. },
  137. ],
  138. };
  139. const schemas: FormSchema[] = [
  140. {
  141. field: 'divider-basic',
  142. component: 'Divider',
  143. label: '基础字段',
  144. colProps: {
  145. span: 24,
  146. },
  147. },
  148. {
  149. field: 'field1',
  150. component: 'Input',
  151. label: '字段1',
  152. colProps: {
  153. span: 8,
  154. },
  155. // componentProps:{},
  156. // can func
  157. componentProps: ({ schema, formModel }) => {
  158. console.log('form:', schema);
  159. console.log('formModel:', formModel);
  160. return {
  161. placeholder: '自定义placeholder',
  162. onChange: (e: any) => {
  163. console.log(e);
  164. },
  165. };
  166. },
  167. renderComponentContent: () => {
  168. return {
  169. prefix: () => 'pSlot',
  170. suffix: () => 'sSlot',
  171. };
  172. },
  173. },
  174. {
  175. field: 'field2',
  176. component: 'Input',
  177. label: '带后缀',
  178. defaultValue: '111',
  179. colProps: {
  180. span: 8,
  181. },
  182. componentProps: {
  183. onChange: (e: any) => {
  184. console.log(e);
  185. },
  186. },
  187. suffix: '天',
  188. },
  189. {
  190. field: 'fieldsc',
  191. component: 'Upload',
  192. label: '上传',
  193. colProps: {
  194. span: 8,
  195. },
  196. rules: [{ required: true, message: '请选择上传文件' }],
  197. componentProps: {
  198. api: uploadApi,
  199. },
  200. },
  201. {
  202. field: 'field3',
  203. component: 'DatePicker',
  204. label: '字段3',
  205. colProps: {
  206. span: 8,
  207. },
  208. },
  209. {
  210. field: 'field4',
  211. component: 'Select',
  212. label: '字段4',
  213. colProps: {
  214. span: 8,
  215. },
  216. componentProps: {
  217. options: [
  218. {
  219. label: '选项1',
  220. value: '1',
  221. key: '1',
  222. },
  223. {
  224. label: '选项2',
  225. value: '2',
  226. key: '2',
  227. },
  228. ],
  229. },
  230. },
  231. {
  232. field: 'field5',
  233. component: 'CheckboxGroup',
  234. label: '字段5',
  235. colProps: {
  236. span: 8,
  237. },
  238. componentProps: {
  239. options: [
  240. {
  241. label: '选项1',
  242. value: '1',
  243. },
  244. {
  245. label: '选项2',
  246. value: '2',
  247. },
  248. ],
  249. },
  250. },
  251. {
  252. field: 'field7',
  253. component: 'RadioGroup',
  254. label: '字段7',
  255. colProps: {
  256. span: 8,
  257. },
  258. componentProps: {
  259. options: [
  260. {
  261. label: '选项1',
  262. value: '1',
  263. },
  264. {
  265. label: '选项2',
  266. value: '2',
  267. },
  268. ],
  269. },
  270. },
  271. {
  272. field: 'field8',
  273. component: 'Checkbox',
  274. label: '字段8',
  275. colProps: {
  276. span: 8,
  277. },
  278. renderComponentContent: 'Check',
  279. },
  280. {
  281. field: 'field9',
  282. component: 'Switch',
  283. label: '字段9',
  284. colProps: {
  285. span: 8,
  286. },
  287. },
  288. {
  289. field: 'field10',
  290. component: 'RadioButtonGroup',
  291. label: '字段10',
  292. colProps: {
  293. span: 8,
  294. },
  295. componentProps: {
  296. options: [
  297. {
  298. label: '选项1',
  299. value: '1',
  300. },
  301. {
  302. label: '选项2',
  303. value: '2',
  304. },
  305. ],
  306. onChange: (e, v) => {
  307. console.log('RadioButtonGroup====>:', e, v);
  308. },
  309. },
  310. },
  311. {
  312. field: 'field11',
  313. component: 'Cascader',
  314. label: '字段11',
  315. colProps: {
  316. span: 8,
  317. },
  318. componentProps: {
  319. options: [
  320. {
  321. value: 'zhejiang',
  322. label: 'Zhejiang',
  323. children: [
  324. {
  325. value: 'hangzhou',
  326. label: 'Hangzhou',
  327. children: [
  328. {
  329. value: 'xihu',
  330. label: 'West Lake',
  331. },
  332. ],
  333. },
  334. ],
  335. },
  336. {
  337. value: 'jiangsu',
  338. label: 'Jiangsu',
  339. children: [
  340. {
  341. value: 'nanjing',
  342. label: 'Nanjing',
  343. children: [
  344. {
  345. value: 'zhonghuamen',
  346. label: 'Zhong Hua Men',
  347. },
  348. ],
  349. },
  350. ],
  351. },
  352. ],
  353. },
  354. },
  355. {
  356. field: 'divider-api-select',
  357. component: 'Divider',
  358. label: '远程下拉演示',
  359. colProps: {
  360. span: 24,
  361. },
  362. },
  363. {
  364. field: 'field30',
  365. component: 'ApiSelect',
  366. label: '懒加载远程下拉',
  367. required: true,
  368. componentProps: {
  369. // more details see /src/components/Form/src/components/ApiSelect.vue
  370. api: optionsListApi,
  371. params: {
  372. id: 1,
  373. },
  374. resultField: 'list',
  375. // use name as label
  376. labelField: 'name',
  377. // use id as value
  378. valueField: 'id',
  379. // not request untill to select
  380. immediate: true,
  381. onChange: (e, v) => {
  382. console.log('ApiSelect====>:', e, v);
  383. },
  384. // atfer request callback
  385. onOptionsChange: (options) => {
  386. console.log('get options', options.length, options);
  387. },
  388. },
  389. colProps: {
  390. span: 8,
  391. },
  392. defaultValue: '0',
  393. },
  394. {
  395. field: 'field8',
  396. component: 'ApiCascader',
  397. label: '联动ApiCascader',
  398. required: true,
  399. colProps: {
  400. span: 8,
  401. },
  402. componentProps: {
  403. api: areaRecord,
  404. apiParamKey: 'parentCode',
  405. dataField: 'data',
  406. labelField: 'name',
  407. valueField: 'code',
  408. initFetchParams: {
  409. parentCode: '',
  410. },
  411. isLeaf: (record) => {
  412. return !(record.levelType < 3);
  413. },
  414. onChange: (e, ...v) => {
  415. console.log('ApiCascader====>:', e, v);
  416. },
  417. },
  418. },
  419. {
  420. field: 'field31',
  421. component: 'Input',
  422. label: '下拉本地搜索',
  423. helpMessage: ['ApiSelect组件', '远程数据源本地搜索', '只发起一次请求获取所有选项'],
  424. required: true,
  425. slot: 'localSearch',
  426. colProps: {
  427. span: 8,
  428. },
  429. defaultValue: '0',
  430. },
  431. {
  432. field: 'field32',
  433. component: 'Input',
  434. label: '下拉远程搜索',
  435. helpMessage: ['ApiSelect组件', '将关键词发送到接口进行远程搜索'],
  436. required: true,
  437. slot: 'remoteSearch',
  438. colProps: {
  439. span: 8,
  440. },
  441. defaultValue: '0',
  442. },
  443. {
  444. field: 'field33',
  445. component: 'ApiTreeSelect',
  446. label: '远程下拉树',
  447. helpMessage: ['ApiTreeSelect组件', '使用接口提供的数据生成选项'],
  448. required: true,
  449. componentProps: {
  450. api: treeOptionsListApi,
  451. resultField: 'list',
  452. onChange: (e, v) => {
  453. console.log('ApiTreeSelect====>:', e, v);
  454. },
  455. },
  456. colProps: {
  457. span: 8,
  458. },
  459. },
  460. {
  461. field: 'field33',
  462. component: 'ApiTreeSelect',
  463. label: '远程懒加载下拉树',
  464. helpMessage: ['ApiTreeSelect组件', '使用接口提供的数据生成选项'],
  465. required: true,
  466. componentProps: {
  467. api: () => {
  468. return new Promise((resolve) => {
  469. resolve([
  470. {
  471. title: 'Parent Node',
  472. value: '0-0',
  473. },
  474. ]);
  475. });
  476. },
  477. async: true,
  478. onChange: (e, v) => {
  479. console.log('ApiTreeSelect====>:', e, v);
  480. },
  481. onLoadData: ({ treeData, resolve, treeNode }) => {
  482. console.log('treeNode====>:', treeNode);
  483. setTimeout(() => {
  484. const children: Recordable[] = [
  485. { title: `Child Node ${treeNode.eventKey}-0`, value: `${treeNode.eventKey}-0` },
  486. { title: `Child Node ${treeNode.eventKey}-1`, value: `${treeNode.eventKey}-1` },
  487. ];
  488. children.forEach((item) => {
  489. item.isLeaf = false;
  490. item.children = [];
  491. });
  492. treeNode.dataRef.children = children;
  493. treeData.value = [...treeData.value];
  494. resolve();
  495. return;
  496. }, 300);
  497. },
  498. },
  499. colProps: {
  500. span: 8,
  501. },
  502. },
  503. {
  504. field: 'field34',
  505. component: 'ApiRadioGroup',
  506. label: '远程Radio',
  507. helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
  508. required: true,
  509. componentProps: {
  510. api: optionsListApi,
  511. params: {
  512. count: 2,
  513. },
  514. resultField: 'list',
  515. // use name as label
  516. labelField: 'name',
  517. // use id as value
  518. valueField: 'id',
  519. },
  520. defaultValue: '1',
  521. colProps: {
  522. span: 8,
  523. },
  524. },
  525. {
  526. field: 'field35',
  527. component: 'ApiRadioGroup',
  528. label: '远程Radio',
  529. helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
  530. required: true,
  531. componentProps: {
  532. api: optionsListApi,
  533. params: {
  534. count: 2,
  535. },
  536. resultField: 'list',
  537. // use name as label
  538. labelField: 'name',
  539. // use id as value
  540. valueField: 'id',
  541. isBtn: true,
  542. onChange: (e, v) => {
  543. console.log('ApiRadioGroup====>:', e, v);
  544. },
  545. },
  546. colProps: {
  547. span: 8,
  548. },
  549. },
  550. {
  551. field: 'field36',
  552. component: 'ApiTree',
  553. label: '远程Tree',
  554. helpMessage: ['ApiTree组件', '使用接口提供的数据生成选项'],
  555. required: true,
  556. componentProps: {
  557. api: treeOptionsListApi,
  558. params: {
  559. count: 2,
  560. },
  561. afterFetch: (v) => {
  562. //do something
  563. return v;
  564. },
  565. resultField: 'list',
  566. },
  567. colProps: {
  568. span: 8,
  569. },
  570. },
  571. {
  572. field: 'divider-linked',
  573. component: 'Divider',
  574. label: '字段联动',
  575. colProps: {
  576. span: 24,
  577. },
  578. },
  579. {
  580. field: 'province',
  581. component: 'Select',
  582. label: '省份',
  583. colProps: {
  584. span: 8,
  585. },
  586. componentProps: ({ formModel, formActionType }) => {
  587. return {
  588. options: provincesOptions,
  589. placeholder: '省份与城市联动',
  590. onChange: (e: any) => {
  591. // console.log(e)
  592. let citiesOptions =
  593. e == 1
  594. ? citiesOptionsData[provincesOptions[0].id]
  595. : citiesOptionsData[provincesOptions[1].id];
  596. // console.log(citiesOptions)
  597. if (e === undefined) {
  598. citiesOptions = [];
  599. }
  600. formModel.city = undefined; // reset city value
  601. const { updateSchema } = formActionType;
  602. updateSchema({
  603. field: 'city',
  604. componentProps: {
  605. options: citiesOptions,
  606. },
  607. });
  608. },
  609. };
  610. },
  611. },
  612. {
  613. field: 'city',
  614. component: 'Select',
  615. label: '城市',
  616. colProps: {
  617. span: 8,
  618. },
  619. componentProps: {
  620. options: [], // defalut []
  621. placeholder: '省份与城市联动',
  622. },
  623. },
  624. {
  625. field: 'divider-selects',
  626. component: 'Divider',
  627. label: '互斥多选',
  628. helpMessage: ['两个Select共用数据源', '但不可选择对方已选中的项目'],
  629. colProps: {
  630. span: 24,
  631. },
  632. },
  633. {
  634. field: 'selectA',
  635. component: 'Select',
  636. label: '互斥SelectA',
  637. slot: 'selectA',
  638. defaultValue: [],
  639. colProps: {
  640. span: 8,
  641. },
  642. },
  643. {
  644. field: 'selectB',
  645. component: 'Select',
  646. label: '互斥SelectB',
  647. slot: 'selectB',
  648. defaultValue: [],
  649. colProps: {
  650. span: 8,
  651. },
  652. },
  653. {
  654. field: 'divider-deconstruct',
  655. component: 'Divider',
  656. label: '字段解构',
  657. helpMessage: ['如果组件的值是 array 或者 object', '可以根据 ES6 的解构语法分别取值'],
  658. colProps: {
  659. span: 24,
  660. },
  661. },
  662. {
  663. field: '[startTime, endTime]',
  664. label: '时间范围',
  665. component: 'TimeRangePicker',
  666. componentProps: {
  667. format: 'HH:mm:ss',
  668. placeholder: ['开始时间', '结束时间'],
  669. },
  670. },
  671. {
  672. field: '[startDate, endDate]',
  673. label: '日期范围',
  674. component: 'RangePicker',
  675. componentProps: {
  676. format: 'YYYY-MM-DD',
  677. placeholder: ['开始日期', '结束日期'],
  678. },
  679. },
  680. {
  681. field: '[startDateTime, endDateTime]',
  682. label: '日期时间范围',
  683. component: 'RangePicker',
  684. componentProps: {
  685. format: 'YYYY-MM-DD HH:mm:ss',
  686. placeholder: ['开始日期、时间', '结束日期、时间'],
  687. showTime: { format: 'HH:mm:ss' },
  688. },
  689. },
  690. {
  691. field: 'divider-others',
  692. component: 'Divider',
  693. label: '其它',
  694. colProps: {
  695. span: 24,
  696. },
  697. },
  698. {
  699. field: 'field20',
  700. component: 'InputNumber',
  701. label: '字段20',
  702. required: true,
  703. colProps: {
  704. span: 8,
  705. },
  706. },
  707. {
  708. field: 'field21',
  709. component: 'Slider',
  710. label: '字段21',
  711. componentProps: {
  712. min: 0,
  713. max: 100,
  714. range: true,
  715. marks: {
  716. 20: '20°C',
  717. 60: '60°C',
  718. },
  719. },
  720. colProps: {
  721. span: 8,
  722. },
  723. },
  724. {
  725. field: 'field22',
  726. component: 'Rate',
  727. label: '字段22',
  728. defaultValue: 3,
  729. colProps: {
  730. span: 8,
  731. },
  732. componentProps: {
  733. disabled: false,
  734. allowHalf: true,
  735. },
  736. },
  737. {
  738. field: 'field23',
  739. component: 'ImageUpload',
  740. label: '上传图片',
  741. required: true,
  742. defaultValue: [
  743. 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  744. ],
  745. componentProps: {
  746. api: uploadApi,
  747. accept: ['png', 'jpeg', 'jpg'],
  748. maxSize: 2,
  749. maxNumber: 1,
  750. },
  751. // rules: [
  752. // {
  753. // required: true,
  754. // trigger: 'change',
  755. // validator(_, value) {
  756. // if (isArray(value) && value.length > 0) {
  757. // return Promise.resolve();
  758. // } else {
  759. // return Promise.reject('请选择上传图片');
  760. // }
  761. // },
  762. // },
  763. // ],
  764. },
  765. ];
  766. export default defineComponent({
  767. components: { BasicForm, CollapseContainer, PageWrapper, ApiSelect, ASelect: Select },
  768. setup() {
  769. const check = ref(null);
  770. const { createMessage } = useMessage();
  771. const keyword = ref<string>('');
  772. const searchParams = computed<Recordable<string>>(() => {
  773. return { keyword: unref(keyword) };
  774. });
  775. function onSearch(value: string) {
  776. keyword.value = value;
  777. }
  778. return {
  779. schemas,
  780. optionsListApi,
  781. optionsA,
  782. optionsB,
  783. valueSelectA,
  784. valueSelectB,
  785. onSearch: useDebounceFn(onSearch, 300),
  786. searchParams,
  787. handleReset: () => {
  788. keyword.value = '';
  789. },
  790. handleSubmit: (values: any) => {
  791. console.log('values', values);
  792. createMessage.success('click search,values:' + JSON.stringify(values));
  793. },
  794. check,
  795. };
  796. },
  797. });
  798. </script>