1
0

index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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. },
  141. {
  142. field: 'field1',
  143. component: 'Input',
  144. label: '字段1',
  145. colProps: {
  146. span: 8,
  147. },
  148. // componentProps:{},
  149. // can func
  150. componentProps: ({ schema, formModel }) => {
  151. console.log('form:', schema);
  152. console.log('formModel:', formModel);
  153. return {
  154. placeholder: '自定义placeholder',
  155. onChange: (e: any) => {
  156. console.log(e);
  157. },
  158. };
  159. },
  160. renderComponentContent: () => {
  161. return {
  162. prefix: () => 'pSlot',
  163. suffix: () => 'sSlot',
  164. };
  165. },
  166. },
  167. {
  168. field: 'field2',
  169. component: 'Input',
  170. label: '带后缀',
  171. defaultValue: '111',
  172. colProps: {
  173. span: 8,
  174. },
  175. componentProps: {
  176. onChange: (e: any) => {
  177. console.log(e);
  178. },
  179. },
  180. suffix: '天',
  181. },
  182. {
  183. field: 'field3',
  184. component: 'DatePicker',
  185. label: '字段3',
  186. colProps: {
  187. span: 8,
  188. },
  189. },
  190. {
  191. field: 'field4',
  192. component: 'Select',
  193. label: '字段4',
  194. colProps: {
  195. span: 8,
  196. },
  197. componentProps: {
  198. options: [
  199. {
  200. label: '选项1',
  201. value: '1',
  202. key: '1',
  203. },
  204. {
  205. label: '选项2',
  206. value: '2',
  207. key: '2',
  208. },
  209. ],
  210. },
  211. },
  212. {
  213. field: 'field5',
  214. component: 'CheckboxGroup',
  215. label: '字段5',
  216. colProps: {
  217. span: 8,
  218. },
  219. componentProps: {
  220. options: [
  221. {
  222. label: '选项1',
  223. value: '1',
  224. },
  225. {
  226. label: '选项2',
  227. value: '2',
  228. },
  229. ],
  230. },
  231. },
  232. {
  233. field: 'field7',
  234. component: 'RadioGroup',
  235. label: '字段7',
  236. colProps: {
  237. span: 8,
  238. },
  239. componentProps: {
  240. options: [
  241. {
  242. label: '选项1',
  243. value: '1',
  244. },
  245. {
  246. label: '选项2',
  247. value: '2',
  248. },
  249. ],
  250. },
  251. },
  252. {
  253. field: 'field8',
  254. component: 'Checkbox',
  255. label: '字段8',
  256. colProps: {
  257. span: 8,
  258. },
  259. renderComponentContent: 'Check',
  260. },
  261. {
  262. field: 'field9',
  263. component: 'Switch',
  264. label: '字段9',
  265. colProps: {
  266. span: 8,
  267. },
  268. },
  269. {
  270. field: 'field10',
  271. component: 'RadioButtonGroup',
  272. label: '字段10',
  273. colProps: {
  274. span: 8,
  275. },
  276. componentProps: {
  277. options: [
  278. {
  279. label: '选项1',
  280. value: '1',
  281. },
  282. {
  283. label: '选项2',
  284. value: '2',
  285. },
  286. ],
  287. },
  288. },
  289. {
  290. field: 'field11',
  291. component: 'Cascader',
  292. label: '字段11',
  293. colProps: {
  294. span: 8,
  295. },
  296. componentProps: {
  297. options: [
  298. {
  299. value: 'zhejiang',
  300. label: 'Zhejiang',
  301. children: [
  302. {
  303. value: 'hangzhou',
  304. label: 'Hangzhou',
  305. children: [
  306. {
  307. value: 'xihu',
  308. label: 'West Lake',
  309. },
  310. ],
  311. },
  312. ],
  313. },
  314. {
  315. value: 'jiangsu',
  316. label: 'Jiangsu',
  317. children: [
  318. {
  319. value: 'nanjing',
  320. label: 'Nanjing',
  321. children: [
  322. {
  323. value: 'zhonghuamen',
  324. label: 'Zhong Hua Men',
  325. },
  326. ],
  327. },
  328. ],
  329. },
  330. ],
  331. },
  332. },
  333. {
  334. field: 'divider-api-select',
  335. component: 'Divider',
  336. label: '远程下拉演示',
  337. },
  338. {
  339. field: 'field30',
  340. component: 'ApiSelect',
  341. label: '懒加载远程下拉',
  342. required: true,
  343. componentProps: {
  344. // more details see /src/components/Form/src/components/ApiSelect.vue
  345. api: optionsListApi,
  346. params: {
  347. id: 1,
  348. },
  349. resultField: 'list',
  350. // use name as label
  351. labelField: 'name',
  352. // use id as value
  353. valueField: 'id',
  354. // not request untill to select
  355. immediate: false,
  356. onChange: (e) => {
  357. console.log('selected:', e);
  358. },
  359. // atfer request callback
  360. onOptionsChange: (options) => {
  361. console.log('get options', options.length, options);
  362. },
  363. },
  364. colProps: {
  365. span: 8,
  366. },
  367. defaultValue: '0',
  368. },
  369. {
  370. field: 'field31',
  371. component: 'Input',
  372. label: '下拉本地搜索',
  373. helpMessage: ['ApiSelect组件', '远程数据源本地搜索', '只发起一次请求获取所有选项'],
  374. required: true,
  375. slot: 'localSearch',
  376. colProps: {
  377. span: 8,
  378. },
  379. defaultValue: '0',
  380. },
  381. {
  382. field: 'field32',
  383. component: 'Input',
  384. label: '下拉远程搜索',
  385. helpMessage: ['ApiSelect组件', '将关键词发送到接口进行远程搜索'],
  386. required: true,
  387. slot: 'remoteSearch',
  388. colProps: {
  389. span: 8,
  390. },
  391. defaultValue: '0',
  392. },
  393. {
  394. field: 'field33',
  395. component: 'ApiTreeSelect',
  396. label: '远程下拉树',
  397. helpMessage: ['ApiTreeSelect组件', '使用接口提供的数据生成选项'],
  398. required: true,
  399. componentProps: {
  400. api: treeOptionsListApi,
  401. resultField: 'list',
  402. },
  403. colProps: {
  404. span: 8,
  405. },
  406. },
  407. {
  408. field: 'divider-linked',
  409. component: 'Divider',
  410. label: '字段联动',
  411. },
  412. {
  413. field: 'province',
  414. component: 'Select',
  415. label: '省份',
  416. colProps: {
  417. span: 8,
  418. },
  419. componentProps: ({ formModel, formActionType }) => {
  420. return {
  421. options: provincesOptions,
  422. placeholder: '省份与城市联动',
  423. onChange: (e: any) => {
  424. // console.log(e)
  425. let citiesOptions =
  426. e == 1
  427. ? citiesOptionsData[provincesOptions[0].id]
  428. : citiesOptionsData[provincesOptions[1].id];
  429. // console.log(citiesOptions)
  430. if (e === undefined) {
  431. citiesOptions = [];
  432. }
  433. formModel.city = undefined; // reset city value
  434. const { updateSchema } = formActionType;
  435. updateSchema({
  436. field: 'city',
  437. componentProps: {
  438. options: citiesOptions,
  439. },
  440. });
  441. },
  442. };
  443. },
  444. },
  445. {
  446. field: 'city',
  447. component: 'Select',
  448. label: '城市',
  449. colProps: {
  450. span: 8,
  451. },
  452. componentProps: {
  453. options: [], // defalut []
  454. placeholder: '省份与城市联动',
  455. },
  456. },
  457. {
  458. field: 'divider-selects',
  459. component: 'Divider',
  460. label: '互斥多选',
  461. helpMessage: ['两个Select共用数据源', '但不可选择对方已选中的项目'],
  462. },
  463. {
  464. field: 'selectA',
  465. component: 'Select',
  466. label: '互斥SelectA',
  467. slot: 'selectA',
  468. defaultValue: [],
  469. colProps: {
  470. span: 8,
  471. },
  472. },
  473. {
  474. field: 'selectB',
  475. component: 'Select',
  476. label: '互斥SelectB',
  477. slot: 'selectB',
  478. defaultValue: [],
  479. colProps: {
  480. span: 8,
  481. },
  482. },
  483. {
  484. field: 'divider-others',
  485. component: 'Divider',
  486. label: '其它',
  487. },
  488. {
  489. field: 'field20',
  490. component: 'InputNumber',
  491. label: '字段20',
  492. required: true,
  493. colProps: {
  494. span: 8,
  495. },
  496. },
  497. {
  498. field: 'field21',
  499. component: 'Slider',
  500. label: '字段21',
  501. componentProps: {
  502. min: 0,
  503. max: 100,
  504. range: true,
  505. marks: {
  506. 20: '20°C',
  507. 60: '60°C',
  508. },
  509. },
  510. colProps: {
  511. span: 8,
  512. },
  513. },
  514. {
  515. field: 'field22',
  516. component: 'Rate',
  517. label: '字段22',
  518. defaultValue: 3,
  519. colProps: {
  520. span: 8,
  521. },
  522. componentProps: {
  523. disabled: false,
  524. allowHalf: true,
  525. },
  526. },
  527. ];
  528. export default defineComponent({
  529. components: { BasicForm, CollapseContainer, PageWrapper, ApiSelect, ASelect: Select },
  530. setup() {
  531. const check = ref(null);
  532. const { createMessage } = useMessage();
  533. const keyword = ref<string>('');
  534. const searchParams = computed<Recordable>(() => {
  535. return { keyword: unref(keyword) };
  536. });
  537. function onSearch(value: string) {
  538. keyword.value = value;
  539. }
  540. return {
  541. schemas,
  542. optionsListApi,
  543. optionsA,
  544. optionsB,
  545. valueSelectA,
  546. valueSelectB,
  547. onSearch: useDebounceFn(onSearch, 300),
  548. searchParams,
  549. handleReset: () => {
  550. keyword.value = '';
  551. },
  552. handleSubmit: (values: any) => {
  553. createMessage.success('click search,values:' + JSON.stringify(values));
  554. },
  555. check,
  556. };
  557. },
  558. });
  559. </script>