index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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 { computed, defineComponent, unref, ref } from 'vue';
  60. import { BasicForm, FormSchema, ApiSelect } from '/@/components/Form/index';
  61. import { CollapseContainer } from '/@/components/Container';
  62. import { useMessage } from '/@/hooks/web/useMessage';
  63. import { PageWrapper } from '/@/components/Page';
  64. import { optionsListApi } from '/@/api/demo/select';
  65. import { useDebounceFn } from '@vueuse/core';
  66. import { treeOptionsListApi } from '/@/api/demo/tree';
  67. import { Select } from 'ant-design-vue';
  68. import { cloneDeep } from 'lodash-es';
  69. const valueSelectA = ref<string[]>([]);
  70. const valueSelectB = ref<string[]>([]);
  71. const options = ref<Recordable[]>([]);
  72. for (let i = 1; i < 10; i++) options.value.push({ label: '选项' + i, value: `${i}` });
  73. const optionsA = computed(() => {
  74. return cloneDeep(unref(options)).map((op) => {
  75. op.disabled = unref(valueSelectB).indexOf(op.value) !== -1;
  76. return op;
  77. });
  78. });
  79. const optionsB = computed(() => {
  80. return cloneDeep(unref(options)).map((op) => {
  81. op.disabled = unref(valueSelectA).indexOf(op.value) !== -1;
  82. return op;
  83. });
  84. });
  85. const provincesOptions = [
  86. {
  87. id: 'guangdong',
  88. label: '广东省',
  89. value: '1',
  90. key: '1',
  91. },
  92. {
  93. id: 'jiangsu',
  94. label: '江苏省',
  95. value: '2',
  96. key: '2',
  97. },
  98. ];
  99. const citiesOptionsData = {
  100. guangdong: [
  101. {
  102. label: '珠海市',
  103. value: '1',
  104. key: '1',
  105. },
  106. {
  107. label: '深圳市',
  108. value: '2',
  109. key: '2',
  110. },
  111. {
  112. label: '广州市',
  113. value: '3',
  114. key: '3',
  115. },
  116. ],
  117. jiangsu: [
  118. {
  119. label: '南京市',
  120. value: '1',
  121. key: '1',
  122. },
  123. {
  124. label: '无锡市',
  125. value: '2',
  126. key: '2',
  127. },
  128. {
  129. label: '苏州市',
  130. value: '3',
  131. key: '3',
  132. },
  133. ],
  134. };
  135. const schemas: FormSchema[] = [
  136. {
  137. field: 'divider-basic',
  138. component: 'Divider',
  139. label: '基础字段',
  140. colProps: {
  141. span: 24,
  142. },
  143. },
  144. {
  145. field: 'field1',
  146. component: 'Input',
  147. label: '字段1',
  148. colProps: {
  149. span: 8,
  150. },
  151. // componentProps:{},
  152. // can func
  153. componentProps: ({ schema, formModel }) => {
  154. console.log('form:', schema);
  155. console.log('formModel:', formModel);
  156. return {
  157. placeholder: '自定义placeholder',
  158. onChange: (e: any) => {
  159. console.log(e);
  160. },
  161. };
  162. },
  163. renderComponentContent: () => {
  164. return {
  165. prefix: () => 'pSlot',
  166. suffix: () => 'sSlot',
  167. };
  168. },
  169. },
  170. {
  171. field: 'field2',
  172. component: 'Input',
  173. label: '带后缀',
  174. defaultValue: '111',
  175. colProps: {
  176. span: 8,
  177. },
  178. componentProps: {
  179. onChange: (e: any) => {
  180. console.log(e);
  181. },
  182. },
  183. suffix: '天',
  184. },
  185. {
  186. field: 'field3',
  187. component: 'DatePicker',
  188. label: '字段3',
  189. colProps: {
  190. span: 8,
  191. },
  192. },
  193. {
  194. field: 'field4',
  195. component: 'Select',
  196. label: '字段4',
  197. colProps: {
  198. span: 8,
  199. },
  200. componentProps: {
  201. options: [
  202. {
  203. label: '选项1',
  204. value: '1',
  205. key: '1',
  206. },
  207. {
  208. label: '选项2',
  209. value: '2',
  210. key: '2',
  211. },
  212. ],
  213. },
  214. },
  215. {
  216. field: 'field5',
  217. component: 'CheckboxGroup',
  218. label: '字段5',
  219. colProps: {
  220. span: 8,
  221. },
  222. componentProps: {
  223. options: [
  224. {
  225. label: '选项1',
  226. value: '1',
  227. },
  228. {
  229. label: '选项2',
  230. value: '2',
  231. },
  232. ],
  233. },
  234. },
  235. {
  236. field: 'field7',
  237. component: 'RadioGroup',
  238. label: '字段7',
  239. colProps: {
  240. span: 8,
  241. },
  242. componentProps: {
  243. options: [
  244. {
  245. label: '选项1',
  246. value: '1',
  247. },
  248. {
  249. label: '选项2',
  250. value: '2',
  251. },
  252. ],
  253. },
  254. },
  255. {
  256. field: 'field8',
  257. component: 'Checkbox',
  258. label: '字段8',
  259. colProps: {
  260. span: 8,
  261. },
  262. renderComponentContent: 'Check',
  263. },
  264. {
  265. field: 'field9',
  266. component: 'Switch',
  267. label: '字段9',
  268. colProps: {
  269. span: 8,
  270. },
  271. },
  272. {
  273. field: 'field10',
  274. component: 'RadioButtonGroup',
  275. label: '字段10',
  276. colProps: {
  277. span: 8,
  278. },
  279. componentProps: {
  280. options: [
  281. {
  282. label: '选项1',
  283. value: '1',
  284. },
  285. {
  286. label: '选项2',
  287. value: '2',
  288. },
  289. ],
  290. },
  291. },
  292. {
  293. field: 'field11',
  294. component: 'Cascader',
  295. label: '字段11',
  296. colProps: {
  297. span: 8,
  298. },
  299. componentProps: {
  300. options: [
  301. {
  302. value: 'zhejiang',
  303. label: 'Zhejiang',
  304. children: [
  305. {
  306. value: 'hangzhou',
  307. label: 'Hangzhou',
  308. children: [
  309. {
  310. value: 'xihu',
  311. label: 'West Lake',
  312. },
  313. ],
  314. },
  315. ],
  316. },
  317. {
  318. value: 'jiangsu',
  319. label: 'Jiangsu',
  320. children: [
  321. {
  322. value: 'nanjing',
  323. label: 'Nanjing',
  324. children: [
  325. {
  326. value: 'zhonghuamen',
  327. label: 'Zhong Hua Men',
  328. },
  329. ],
  330. },
  331. ],
  332. },
  333. ],
  334. },
  335. },
  336. {
  337. field: 'divider-api-select',
  338. component: 'Divider',
  339. label: '远程下拉演示',
  340. colProps: {
  341. span: 24,
  342. },
  343. },
  344. {
  345. field: 'field30',
  346. component: 'ApiSelect',
  347. label: '懒加载远程下拉',
  348. required: true,
  349. componentProps: {
  350. // more details see /src/components/Form/src/components/ApiSelect.vue
  351. api: optionsListApi,
  352. params: {
  353. id: 1,
  354. },
  355. resultField: 'list',
  356. // use name as label
  357. labelField: 'name',
  358. // use id as value
  359. valueField: 'id',
  360. // not request untill to select
  361. immediate: false,
  362. onChange: (e) => {
  363. console.log('selected:', e);
  364. },
  365. // atfer request callback
  366. onOptionsChange: (options) => {
  367. console.log('get options', options.length, options);
  368. },
  369. },
  370. colProps: {
  371. span: 8,
  372. },
  373. defaultValue: '0',
  374. },
  375. {
  376. field: 'field31',
  377. component: 'Input',
  378. label: '下拉本地搜索',
  379. helpMessage: ['ApiSelect组件', '远程数据源本地搜索', '只发起一次请求获取所有选项'],
  380. required: true,
  381. slot: 'localSearch',
  382. colProps: {
  383. span: 8,
  384. },
  385. defaultValue: '0',
  386. },
  387. {
  388. field: 'field32',
  389. component: 'Input',
  390. label: '下拉远程搜索',
  391. helpMessage: ['ApiSelect组件', '将关键词发送到接口进行远程搜索'],
  392. required: true,
  393. slot: 'remoteSearch',
  394. colProps: {
  395. span: 8,
  396. },
  397. defaultValue: '0',
  398. },
  399. {
  400. field: 'field33',
  401. component: 'ApiTreeSelect',
  402. label: '远程下拉树',
  403. helpMessage: ['ApiTreeSelect组件', '使用接口提供的数据生成选项'],
  404. required: true,
  405. componentProps: {
  406. api: treeOptionsListApi,
  407. resultField: 'list',
  408. },
  409. colProps: {
  410. span: 8,
  411. },
  412. },
  413. {
  414. field: 'field34',
  415. component: 'ApiRadioGroup',
  416. label: '远程Radio',
  417. helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
  418. required: true,
  419. componentProps: {
  420. api: optionsListApi,
  421. params: {
  422. count: 2,
  423. },
  424. resultField: 'list',
  425. // use name as label
  426. labelField: 'name',
  427. // use id as value
  428. valueField: 'id',
  429. },
  430. defaultValue: '1',
  431. colProps: {
  432. span: 8,
  433. },
  434. },
  435. {
  436. field: 'field35',
  437. component: 'ApiRadioGroup',
  438. label: '远程Radio',
  439. helpMessage: ['ApiRadioGroup组件', '使用接口提供的数据生成选项'],
  440. required: true,
  441. componentProps: {
  442. api: optionsListApi,
  443. params: {
  444. count: 2,
  445. },
  446. resultField: 'list',
  447. // use name as label
  448. labelField: 'name',
  449. // use id as value
  450. valueField: 'id',
  451. isBtn: true,
  452. },
  453. colProps: {
  454. span: 8,
  455. },
  456. },
  457. // {
  458. // field: 'field36',
  459. // component: 'ApiTree',
  460. // label: '远程Tree',
  461. // helpMessage: ['ApiTree组件', '使用接口提供的数据生成选项'],
  462. // required: true,
  463. // componentProps: {
  464. // api: treeOptionsListApi,
  465. // params: {
  466. // count: 2,
  467. // },
  468. // afterFetch: (v) => {
  469. // //do something
  470. // return v;
  471. // },
  472. // resultField: 'list',
  473. // },
  474. // colProps: {
  475. // span: 8,
  476. // },
  477. // },
  478. {
  479. field: 'divider-linked',
  480. component: 'Divider',
  481. label: '字段联动',
  482. colProps: {
  483. span: 24,
  484. },
  485. },
  486. {
  487. field: 'province',
  488. component: 'Select',
  489. label: '省份',
  490. colProps: {
  491. span: 8,
  492. },
  493. componentProps: ({ formModel, formActionType }) => {
  494. return {
  495. options: provincesOptions,
  496. placeholder: '省份与城市联动',
  497. onChange: (e: any) => {
  498. // console.log(e)
  499. let citiesOptions =
  500. e == 1
  501. ? citiesOptionsData[provincesOptions[0].id]
  502. : citiesOptionsData[provincesOptions[1].id];
  503. // console.log(citiesOptions)
  504. if (e === undefined) {
  505. citiesOptions = [];
  506. }
  507. formModel.city = undefined; // reset city value
  508. const { updateSchema } = formActionType;
  509. updateSchema({
  510. field: 'city',
  511. componentProps: {
  512. options: citiesOptions,
  513. },
  514. });
  515. },
  516. };
  517. },
  518. },
  519. {
  520. field: 'city',
  521. component: 'Select',
  522. label: '城市',
  523. colProps: {
  524. span: 8,
  525. },
  526. componentProps: {
  527. options: [], // defalut []
  528. placeholder: '省份与城市联动',
  529. },
  530. },
  531. {
  532. field: 'divider-selects',
  533. component: 'Divider',
  534. label: '互斥多选',
  535. helpMessage: ['两个Select共用数据源', '但不可选择对方已选中的项目'],
  536. colProps: {
  537. span: 24,
  538. },
  539. },
  540. {
  541. field: 'selectA',
  542. component: 'Select',
  543. label: '互斥SelectA',
  544. slot: 'selectA',
  545. defaultValue: [],
  546. colProps: {
  547. span: 8,
  548. },
  549. },
  550. {
  551. field: 'selectB',
  552. component: 'Select',
  553. label: '互斥SelectB',
  554. slot: 'selectB',
  555. defaultValue: [],
  556. colProps: {
  557. span: 8,
  558. },
  559. },
  560. {
  561. field: 'divider-deconstruct',
  562. component: 'Divider',
  563. label: '字段解构',
  564. helpMessage: ['如果组件的值是 array 或者 object', '可以根据 ES6 的解构语法分别取值'],
  565. colProps: {
  566. span: 24,
  567. },
  568. },
  569. {
  570. field: '[startTime, endTime]',
  571. label: '时间范围',
  572. component: 'RangePicker',
  573. componentProps: {
  574. format: 'YYYY-MM-DD HH:mm:ss',
  575. placeholder: ['开始时间', '结束时间'],
  576. showTime: { format: 'HH:mm:ss' },
  577. },
  578. },
  579. {
  580. field: 'divider-others',
  581. component: 'Divider',
  582. label: '其它',
  583. colProps: {
  584. span: 24,
  585. },
  586. },
  587. {
  588. field: 'field20',
  589. component: 'InputNumber',
  590. label: '字段20',
  591. required: true,
  592. colProps: {
  593. span: 8,
  594. },
  595. },
  596. {
  597. field: 'field21',
  598. component: 'Slider',
  599. label: '字段21',
  600. componentProps: {
  601. min: 0,
  602. max: 100,
  603. range: true,
  604. marks: {
  605. 20: '20°C',
  606. 60: '60°C',
  607. },
  608. },
  609. colProps: {
  610. span: 8,
  611. },
  612. },
  613. {
  614. field: 'field22',
  615. component: 'Rate',
  616. label: '字段22',
  617. defaultValue: 3,
  618. colProps: {
  619. span: 8,
  620. },
  621. componentProps: {
  622. disabled: false,
  623. allowHalf: true,
  624. },
  625. },
  626. ];
  627. export default defineComponent({
  628. components: { BasicForm, CollapseContainer, PageWrapper, ApiSelect, ASelect: Select },
  629. setup() {
  630. const check = ref(null);
  631. const { createMessage } = useMessage();
  632. const keyword = ref<string>('');
  633. const searchParams = computed<Recordable>(() => {
  634. return { keyword: unref(keyword) };
  635. });
  636. function onSearch(value: string) {
  637. keyword.value = value;
  638. }
  639. return {
  640. schemas,
  641. optionsListApi,
  642. optionsA,
  643. optionsB,
  644. valueSelectA,
  645. valueSelectB,
  646. onSearch: useDebounceFn(onSearch, 300),
  647. searchParams,
  648. handleReset: () => {
  649. keyword.value = '';
  650. },
  651. handleSubmit: (values: any) => {
  652. console.log('values', values);
  653. createMessage.success('click search,values:' + JSON.stringify(values));
  654. },
  655. check,
  656. };
  657. },
  658. });
  659. </script>