form.mdc 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820
  1. ---
  2. description: cl-form 组件示例
  3. globs: *.tsx, *.ts, *.vue
  4. ---
  5. ## 层级显示 示例
  6. ```vue
  7. <template>
  8. <div class="scope">
  9. <div class="h">
  10. <el-tag size="small" effect="dark" disable-transitions>children</el-tag>
  11. <span>层级显示</span>
  12. </div>
  13. <div class="c">
  14. <el-button @click="open">预览</el-button>
  15. <demo-code :files="['form/children.vue']" />
  16. <!-- 自定义表单组件 -->
  17. <cl-form ref="Form"></cl-form>
  18. </div>
  19. <div class="f">
  20. <span class="date">2024-01-01</span>
  21. </div>
  22. </div>
  23. </template>
  24. <script setup lang="ts">
  25. import { useForm } from '@cool-vue/crud';
  26. const Form = useForm();
  27. function open() {
  28. Form.value?.open({
  29. title: '层级显示',
  30. items: [
  31. {
  32. label: '姓名',
  33. prop: 'name',
  34. component: {
  35. name: 'el-input'
  36. }
  37. },
  38. {
  39. label: '年龄',
  40. prop: 'age',
  41. value: 18,
  42. component: {
  43. name: 'el-input-number'
  44. }
  45. },
  46. // 基础信息
  47. {
  48. component: {
  49. //【很重要】使用 cl-form-card 组件渲染,也可以使用自定义
  50. name: 'cl-form-card',
  51. props: {
  52. // 标题
  53. label: '基础信息',
  54. // 是否展开,默认 true
  55. expand: true
  56. }
  57. },
  58. children: [
  59. {
  60. label: '账号',
  61. prop: 'account',
  62. component: {
  63. name: 'el-input'
  64. }
  65. },
  66. {
  67. label: '密码',
  68. prop: 'password',
  69. component: {
  70. name: 'el-input'
  71. }
  72. }
  73. ]
  74. },
  75. // 其他信息
  76. {
  77. component: {
  78. name: 'cl-form-card',
  79. props: {
  80. label: '其他信息',
  81. expand: false
  82. }
  83. },
  84. children: [
  85. {
  86. label: '身份证',
  87. prop: 'idcard',
  88. component: {
  89. name: 'el-input'
  90. }
  91. },
  92. {
  93. label: '学校',
  94. prop: 'school',
  95. component: {
  96. name: 'el-input'
  97. }
  98. },
  99. {
  100. label: '专业',
  101. prop: 'major',
  102. component: {
  103. name: 'el-input'
  104. }
  105. }
  106. ]
  107. }
  108. ],
  109. on: {
  110. submit(data, { close }) {
  111. close();
  112. }
  113. }
  114. });
  115. }
  116. </script>
  117. ```
  118. ## 组件渲染 示例
  119. ```vue
  120. <template>
  121. <div class="scope">
  122. <div class="h">
  123. <el-tag size="small" effect="dark" disable-transitions>component</el-tag>
  124. <span>组件渲染</span>
  125. </div>
  126. <div class="c">
  127. <el-button @click="open">预览</el-button>
  128. <demo-code
  129. :files="[
  130. 'form/component/index.vue',
  131. 'form/component/select-labels.vue',
  132. 'form/component/select-status.vue',
  133. 'form/component/select-work.vue',
  134. 'form/component/select-work2.vue'
  135. ]"
  136. />
  137. <!-- 自定义表单组件 -->
  138. <cl-form ref="Form">
  139. <!-- 年龄插槽 -->
  140. <template #slot-age="{ scope }">
  141. <!-- scope 为表单值 -->
  142. <el-input-number v-model="scope.age" :min="18" :max="100"></el-input-number>
  143. </template>
  144. </cl-form>
  145. </div>
  146. <div class="f">
  147. <span class="date">2024-01-01</span>
  148. </div>
  149. </div>
  150. </template>
  151. <script setup lang="ts">
  152. import { useForm } from '@cool-vue/crud';
  153. import { ElMessage } from 'element-plus';
  154. import SelectWork from './select-work2.vue';
  155. import SelectLabels from './select-labels.vue';
  156. import SelectStatus from './select-status.vue';
  157. const Form = useForm();
  158. function open() {
  159. Form.value?.open({
  160. title: '组件配置',
  161. items: [
  162. {
  163. label: '昵称',
  164. prop: 'name',
  165. // 组件配置方式1:标签名(方便,但是不建议组件全局注册)
  166. value: '神仙',
  167. component: {
  168. // 必须是“全局注册”的组件名,如 element-plus 的 el-input、el-date-picker 等
  169. name: 'el-input'
  170. }
  171. },
  172. {
  173. label: '手机号',
  174. prop: 'phone',
  175. value: '13255022000',
  176. component: {
  177. name: 'el-input',
  178. // 自定义插槽
  179. slots: {
  180. prepend() {
  181. return '+86';
  182. }
  183. }
  184. }
  185. },
  186. {
  187. label: '年龄',
  188. prop: 'age',
  189. // 组件配置方式2:插槽(万能,就是代码多写点)
  190. value: 18,
  191. component: {
  192. // 必须是 "slot-" 开头
  193. name: 'slot-age'
  194. }
  195. },
  196. // -- start 组件配置方式3:组件实例(不想全局注册,但又想组件化)
  197. {
  198. label: '工作',
  199. prop: 'work',
  200. value: '设计',
  201. component: {
  202. // 双向绑定
  203. vm: SelectWork
  204. }
  205. },
  206. {
  207. label: '标签',
  208. prop: 'labels',
  209. value: ['多金', '深情'],
  210. component: {
  211. // scope[prop]绑定
  212. vm: SelectLabels
  213. }
  214. },
  215. {
  216. label: '状态',
  217. prop: 'status',
  218. value: 1,
  219. component: {
  220. // useForm 绑定
  221. vm: SelectStatus
  222. }
  223. }
  224. // -- end
  225. ],
  226. on: {
  227. submit(data, { close }) {
  228. ElMessage.info(
  229. `${data.name || '无名'}(${data.age || 18}岁)工作:${data.work || '无'}`
  230. );
  231. close();
  232. }
  233. }
  234. });
  235. }
  236. </script>
  237. ```
  238. ## select-labels 示例
  239. ```vue
  240. <template>
  241. <!--【很重要】直接绑定表单值 scope[prop] -->
  242. <!-- !符号,只是为了类型提示不错误 -->
  243. <el-select v-model="scope[prop!]" multiple>
  244. <el-option
  245. v-for="(item, index) in list"
  246. :key="index"
  247. :label="item.label"
  248. :value="item.label"
  249. />
  250. </el-select>
  251. </template>
  252. <!--【很重要】必须要有name,避免注册后和其他冲突 -->
  253. <script setup lang="ts">
  254. defineOptions({
  255. name: 'select-labels'
  256. });
  257. import { ref } from 'vue';
  258. const props = defineProps({
  259. scope: null, // 表单值
  260. prop: String // 表单项配置的 prop
  261. });
  262. // 选项列表
  263. const list = ref<{ label: string; value: string }[]>([
  264. {
  265. label: '帅气',
  266. value: '帅气' // 测试直接使用label,真实情况可能是1,2,3,4或者id
  267. },
  268. {
  269. label: '多金',
  270. value: '多金'
  271. },
  272. {
  273. label: '深情',
  274. value: '深情'
  275. }
  276. ]);
  277. </script>
  278. ```
  279. ## select-status 示例
  280. ```vue
  281. <template>
  282. <!--【很重要】直接绑定status,或者使用 form[prop!] -->
  283. <el-radio-group v-model="form.status">
  284. <el-radio v-for="(item, index) in list" :key="index" :value="item.value">
  285. {{ item.label }}
  286. </el-radio>
  287. </el-radio-group>
  288. </template>
  289. <!--【很重要】必须要有name,避免注册后和其他冲突 -->
  290. <script setup lang="ts">
  291. defineOptions({
  292. name: 'select-status'
  293. });
  294. import { useForm } from '@cool-vue/crud';
  295. import { computed, ref } from 'vue';
  296. const props = defineProps({
  297. scope: null, // 表单值
  298. prop: String // 表单项配置的 prop
  299. });
  300. // 使用 useForm,能直接获取到上级的表单实例,
  301. // 比如操作表单的 Form.value?.submit、Form.value?.close等
  302. // 获取表单值,Form.value?.form
  303. const Form = useForm();
  304. // 表单值,包一层不会太难受
  305. const form = computed(() => Form.value?.form || {});
  306. // 选项列表
  307. const list = ref<{ label: string; value: number }[]>([
  308. {
  309. label: '很好',
  310. value: 1
  311. },
  312. {
  313. label: '不舒服',
  314. value: 2
  315. },
  316. {
  317. label: '要嘎了',
  318. value: 3
  319. }
  320. ]);
  321. </script>
  322. ```
  323. ## select-work 示例
  324. ```vue
  325. <template>
  326. <el-select v-model="active" @change="onChange">
  327. <el-option
  328. v-for="(item, index) in list"
  329. :key="index"
  330. :label="item.label"
  331. :value="item.label"
  332. />
  333. </el-select>
  334. </template>
  335. <!-- 【很重要】必须要有name,避免注册后和其他冲突 -->
  336. <script setup lang="ts">
  337. defineOptions({
  338. name: 'select-work'
  339. });
  340. import { ref, watch } from 'vue';
  341. const props = defineProps({
  342. modelValue: String
  343. });
  344. const emit = defineEmits(['update:modelValue', 'change']);
  345. //【很重要】绑定值
  346. // 这种方式虽然麻烦,但是可扩展性高,一些复杂的数据结构可以按这种方式绑定值
  347. const active = ref();
  348. // 选项列表
  349. const list = ref<{ label: string; value: string }[]>([
  350. {
  351. label: '倒茶',
  352. value: '倒茶' // 测试直接使用label,真实情况可能是1,2,3,4或者id
  353. },
  354. {
  355. label: '设计',
  356. value: '设计'
  357. },
  358. {
  359. label: '开发',
  360. value: '开发'
  361. }
  362. ]);
  363. //【很重要】更新绑定值,表单提交才能得到选择后的
  364. function onChange(val: string) {
  365. emit('update:modelValue', val);
  366. emit('change', val);
  367. }
  368. //【很重要】使用监听的方式,避免表单打开数据是异步获取的情况
  369. watch(
  370. () => props.modelValue,
  371. val => {
  372. // 设置选中的值
  373. active.value = val;
  374. },
  375. {
  376. immediate: true
  377. }
  378. );
  379. </script>
  380. ```
  381. ## select-work2 示例
  382. ```vue
  383. <template>
  384. <el-select v-model="active">
  385. <el-option
  386. v-for="(item, index) in list"
  387. :key="index"
  388. :label="item.label"
  389. :value="item.label"
  390. />
  391. </el-select>
  392. </template>
  393. <!-- 【很重要】必须要有name,避免注册后和其他冲突 -->
  394. <script setup lang="ts">
  395. defineOptions({
  396. name: 'select-work2'
  397. });
  398. import { ref, useModel } from 'vue';
  399. const props = defineProps({
  400. modelValue: String
  401. });
  402. //【很重要】绑定值,使用 useModel 的方式双向绑定
  403. const active = useModel(props, 'modelValue');
  404. // 选项列表
  405. const list = ref<{ label: string; value: string }[]>([
  406. {
  407. label: '倒茶',
  408. value: '倒茶' // 测试直接使用label,真实情况可能是1,2,3,4或者id
  409. },
  410. {
  411. label: '设计',
  412. value: '设计'
  413. },
  414. {
  415. label: '开发',
  416. value: '开发'
  417. }
  418. ]);
  419. </script>
  420. ```
  421. ## 参数配置 示例
  422. ```vue
  423. <template>
  424. <div class="scope">
  425. <div class="h">
  426. <el-tag size="small" effect="dark" disable-transitions>config</el-tag>
  427. <span>参数配置</span>
  428. </div>
  429. <div class="c">
  430. <el-button @click="open">预览</el-button>
  431. <demo-code :files="['form/config.vue']" />
  432. <!-- 自定义表单组件 -->
  433. <cl-form ref="Form">
  434. <!-- 按钮插槽 -->
  435. <template #slot-btns>
  436. <el-button type="danger">按钮插槽</el-button>
  437. </template>
  438. </cl-form>
  439. </div>
  440. <div class="f">
  441. <span class="date">2024-01-01</span>
  442. </div>
  443. </div>
  444. </template>
  445. <script setup lang="ts">
  446. import { useForm } from '@cool-vue/crud';
  447. import { ElMessage } from 'element-plus';
  448. const Form = useForm();
  449. function open() {
  450. Form.value?.open({
  451. title: '参数配置',
  452. // 打开是否重置表单
  453. isReset: false,
  454. // 默认表单值
  455. form: {
  456. nickName: '神仙都没用'
  457. },
  458. // 表单配置
  459. props: {
  460. // 标签宽度
  461. labelWidth: '120px',
  462. // 标签位置
  463. labelPosition: 'top'
  464. },
  465. // 窗口的高。配置后,在窗口内部滚动。默认整个页面滚动
  466. height: '60vh',
  467. // 窗口的宽,默认 50%
  468. width: '60%',
  469. // 窗口设置
  470. dialog: {
  471. // 是否隐藏头部
  472. hideHeader: false,
  473. // 顶部操作按钮,默认["fullscreen", "close"]
  474. // fullscreen 全屏
  475. // close 关闭
  476. controls: ['close']
  477. },
  478. // 底部操作按钮
  479. op: {
  480. // 默认靠右布局
  481. justify: 'flex-end',
  482. // 保存按钮文字
  483. saveButtonText: '提交',
  484. // 关闭按钮文字
  485. closeButtonText: '关闭',
  486. // 是否隐藏
  487. hidden: false,
  488. // 按钮配置
  489. buttons: [
  490. // 自定义
  491. {
  492. label: '自定义按钮',
  493. onClick() {
  494. ElMessage.success('自定义按钮点击');
  495. }
  496. },
  497. // close 关闭
  498. 'close',
  499. // save 保存
  500. 'save',
  501. // 插槽使用,配合 template,往上看 cl-form 组件
  502. 'slot-btns'
  503. ]
  504. },
  505. // 表单项配置
  506. items: [
  507. {
  508. label: '昵称',
  509. prop: 'nickName',
  510. component: {
  511. name: 'el-input'
  512. }
  513. }
  514. ],
  515. // 事件
  516. on: {
  517. submit(data, { close }) {
  518. close();
  519. }
  520. }
  521. });
  522. }
  523. </script>
  524. ```
  525. ## 内嵌CRUD 示例
  526. ```vue
  527. <template>
  528. <div class="scope">
  529. <div class="h">
  530. <el-tag size="small" effect="dark" disable-transitions>crud</el-tag>
  531. <span>内嵌CRUD</span>
  532. </div>
  533. <div class="c">
  534. <el-button @click="open">预览</el-button>
  535. <demo-code :files="['form/crud.vue']" />
  536. <!-- 自定义表单组件 -->
  537. <cl-form ref="Form">
  538. <template #slot-crud>
  539. <cl-crud ref="Crud" border>
  540. <cl-row>
  541. <!-- 刷新按钮 -->
  542. <cl-refresh-btn />
  543. <!-- 新增按钮 -->
  544. <cl-add-btn />
  545. <!-- 删除按钮 -->
  546. <cl-multi-delete-btn />
  547. <cl-flex1 />
  548. <!-- 关键字搜索 -->
  549. <cl-search-key placeholder="搜索姓名、手机号" />
  550. </cl-row>
  551. <cl-row>
  552. <!-- 数据表格 -->
  553. <cl-table ref="Table" />
  554. </cl-row>
  555. <cl-row>
  556. <cl-flex1 />
  557. <!-- 分页控件 -->
  558. <cl-pagination />
  559. </cl-row>
  560. <!-- 新增、编辑 -->
  561. <cl-upsert ref="Upsert" />
  562. </cl-crud>
  563. </template>
  564. </cl-form>
  565. </div>
  566. <div class="f">
  567. <span class="date">2024-01-01</span>
  568. </div>
  569. </div>
  570. </template>
  571. <script setup lang="ts">
  572. import { useCrud, useForm, useTable, useUpsert } from '@cool-vue/crud';
  573. // cl-upsert
  574. const Upsert = useUpsert({
  575. items: [
  576. {
  577. label: '姓名',
  578. prop: 'name',
  579. component: {
  580. name: 'el-input'
  581. }
  582. },
  583. {
  584. label: '创建时间',
  585. prop: 'createTime',
  586. component: {
  587. name: 'el-date-picker'
  588. }
  589. }
  590. ]
  591. });
  592. // cl-table
  593. const Table = useTable({
  594. autoHeight: false,
  595. columns: [
  596. {
  597. type: 'selection'
  598. },
  599. {
  600. label: '姓名',
  601. prop: 'name',
  602. minWidth: 140
  603. },
  604. {
  605. label: '手机号',
  606. prop: 'phone',
  607. minWidth: 140
  608. },
  609. {
  610. type: 'op'
  611. }
  612. ]
  613. });
  614. // cl-crud
  615. const Crud = useCrud(
  616. {
  617. service: 'test'
  618. },
  619. app => {
  620. app.refresh({
  621. size: 10
  622. });
  623. }
  624. );
  625. const Form = useForm();
  626. function open() {
  627. Form.value?.open({
  628. title: '内嵌CRUD',
  629. props: {
  630. labelPosition: 'top'
  631. },
  632. dialog: {
  633. height: '70vh',
  634. width: '1000px'
  635. },
  636. items: [
  637. {
  638. label: '姓名',
  639. prop: 'name',
  640. component: {
  641. name: 'el-input',
  642. props: {
  643. placeholder: '请填写姓名'
  644. }
  645. },
  646. rules: {
  647. required: true,
  648. message: '姓名不能为空'
  649. }
  650. },
  651. {
  652. label: '内嵌 cl-crud',
  653. component: {
  654. name: 'slot-crud'
  655. }
  656. }
  657. ],
  658. on: {
  659. submit() {
  660. Form.value?.close();
  661. }
  662. }
  663. });
  664. }
  665. </script>
  666. ```
  667. ## 组件禁用 示例
  668. ```vue
  669. <template>
  670. <div class="scope">
  671. <div class="h">
  672. <el-tag size="small" effect="dark" disable-transitions>disabled</el-tag>
  673. <span>组件禁用</span>
  674. </div>
  675. <div class="c">
  676. <el-button @click="open">预览</el-button>
  677. <demo-code :files="['form/disabled.vue']" />
  678. <!-- 自定义表单组件 -->
  679. <cl-form ref="Form"></cl-form>
  680. </div>
  681. <div class="f">
  682. <span class="date">2024-01-01</span>
  683. </div>
  684. </div>
  685. </template>
  686. <script setup lang="ts">
  687. import { useForm } from '@cool-vue/crud';
  688. const Form = useForm();
  689. function open() {
  690. Form.value?.open({
  691. title: '组件禁用',
  692. items: [
  693. {
  694. label: '账号',
  695. prop: 'account',
  696. component: {
  697. name: 'el-input',
  698. props: {
  699. // 设置 boolean 值控制组件的禁用状态(前提是组件支持这个参数,element 的组件几乎都有)
  700. disabled: true
  701. }
  702. }
  703. },
  704. {
  705. label: '密码',
  706. prop: 'password',
  707. component: {
  708. name: 'el-input'
  709. }
  710. }
  711. ],
  712. on: {
  713. open() {
  714. // 通用 setProps 方法去设置 disabled, 1.5s后禁用
  715. setTimeout(() => {
  716. Form.value?.setProps('password', { disabled: true });
  717. }, 1500);
  718. },
  719. submit(data, { close }) {
  720. close();
  721. }
  722. }
  723. });
  724. }
  725. </script>
  726. ```
  727. ## 组件事件 示例
  728. ```vue
  729. <template>
  730. <div class="scope">
  731. <div class="h">
  732. <el-tag size="small" effect="dark" disable-transitions>event</el-tag>
  733. <span>组件事件</span>
  734. </div>
  735. <div class="c">
  736. <el-button @click="open">预览</el-button>
  737. <demo-code :files="['form/event.vue']" />
  738. <!-- 自定义表单组件 -->
  739. <cl-form ref="Form"></cl-form>
  740. </div>
  741. <div class="f">
  742. <span class="date">2024-01-01</span>
  743. </div>
  744. </div>
  745. </template>
  746. <script setup lang="ts">
  747. import { useForm } from '@cool-vue/crud';
  748. import { ElMessage } from 'element-plus';
  749. const Form = useForm();
  750. function open() {
  751. Form.value?.open({
  752. title: '组件事件',
  753. items: [
  754. {
  755. label: '账号',
  756. prop: 'account',
  757. component: {
  758. name: 'el-input',
  759. props: {
  760. // 组件内 emit 的用 on[name] 接收,如 onChange、onInput、onBlur 等
  761. // 前提是组件内有触发事件
  762. onBlur() {
  763. ElMessage.info('账号检查中');
  764. }
  765. }
  766. }
  767. },
  768. {
  769. label: '是否实名',
  770. prop: 'status',
  771. value: 1,
  772. component: {
  773. name: 'el-radio-group',
  774. options: [
  775. {
  776. label: '关闭',
  777. value: 0
  778. },
  779. {
  780. label: '开启',
  781. value: 1
  782. }
  783. ],
  784. props: {
  785. // 值改变事件
  786. onChange(val: number) {
  787. if (val == 1) {
  788. // 显示表单项
  789. Form.value?.showItem('idcard');
  790. } else {
  791. // 隐藏表单项
  792. Form.value?.hideItem('idcard');
  793. // 清空值
  794. Form.value?.setForm('idcard', undefined);
  795. }
  796. }
  797. }
  798. }
  799. },
  800. {
  801. label: '身份证',
  802. prop: 'idcard',
  803. component: {
  804. name: 'el-input'
  805. }
  806. }
  807. ],
  808. on: {
  809. submit(data, { close }) {
  810. close();
  811. }
  812. }
  813. });
  814. }
  815. </script>
  816. ```
  817. ## 分组显示 示例
  818. ```vue
  819. <template>
  820. <div class="scope">
  821. <div class="h">
  822. <el-tag size="small" effect="dark" disable-transitions>group</el-tag>
  823. <span>分组显示</span>
  824. </div>
  825. <div class="c">
  826. <el-button @click="open">预览</el-button>
  827. <demo-code :files="['form/group.vue']" />
  828. <!-- 自定义表单组件 -->
  829. <cl-form ref="Form"></cl-form>
  830. </div>
  831. <div class="f">
  832. <span class="date">2024-01-01</span>
  833. </div>
  834. </div>
  835. </template>
  836. <script setup lang="ts">
  837. import { useForm } from '@cool-vue/crud';
  838. const Form = useForm();
  839. function open() {
  840. Form.value?.open({
  841. title: '分组显示',
  842. items: [
  843. {
  844. //【很重要】必须为 tabs
  845. type: 'tabs',
  846. props: {
  847. // 分组样式
  848. type: 'card',
  849. // 分组列表,必须是 { label, value } 的数组格式
  850. labels: [
  851. {
  852. label: '基础信息', // 标题
  853. value: 'base' // 唯一标识
  854. },
  855. {
  856. label: '认证信息',
  857. value: 'auth'
  858. }
  859. ]
  860. }
  861. },
  862. // 基础信息
  863. {
  864. group: 'base', // 标识
  865. label: '账号',
  866. prop: 'account',
  867. required: true,
  868. component: {
  869. name: 'el-input'
  870. }
  871. },
  872. {
  873. group: 'base', // 标识
  874. label: '密码',
  875. prop: 'password',
  876. required: true,
  877. component: {
  878. name: 'el-input'
  879. }
  880. },
  881. // 其他信息 group = other
  882. {
  883. group: 'auth', // 标识
  884. label: '身份证',
  885. prop: 'idcard',
  886. required: true,
  887. component: {
  888. name: 'el-input'
  889. }
  890. },
  891. {
  892. group: 'auth', // 标识
  893. label: '学校',
  894. prop: 'school',
  895. component: {
  896. name: 'el-input'
  897. }
  898. },
  899. {
  900. group: 'auth', // 标识
  901. label: '专业',
  902. prop: 'major',
  903. component: {
  904. name: 'el-input'
  905. }
  906. }
  907. ],
  908. on: {
  909. //【提示】当第一组验证通过后,会自动切换到下一组展示,直到全部通过才可提交
  910. submit(data, { close }) {
  911. close();
  912. }
  913. }
  914. });
  915. }
  916. </script>
  917. ```
  918. ## 隐藏/显示 示例
  919. ```vue
  920. <template>
  921. <div class="scope">
  922. <div class="h">
  923. <el-tag size="small" effect="dark" disable-transitions>hidden</el-tag>
  924. <span>隐藏/显示</span>
  925. </div>
  926. <div class="c">
  927. <el-button @click="open">预览</el-button>
  928. <demo-code :files="['form/hidden.vue']" />
  929. <!-- 自定义表单组件 -->
  930. <cl-form ref="Form"></cl-form>
  931. </div>
  932. <div class="f">
  933. <span class="date">2024-01-01</span>
  934. </div>
  935. </div>
  936. </template>
  937. <script setup lang="ts">
  938. import { useForm } from '@cool-vue/crud';
  939. const Form = useForm();
  940. function open() {
  941. Form.value?.open({
  942. title: '隐藏/显示',
  943. items: [
  944. {
  945. label: '状态',
  946. prop: 'status',
  947. value: 0,
  948. component: {
  949. name: 'el-radio-group',
  950. options: [
  951. {
  952. label: '关闭',
  953. value: 0
  954. },
  955. {
  956. label: '开启',
  957. value: 1
  958. }
  959. ]
  960. }
  961. },
  962. {
  963. label: '账号',
  964. prop: 'account',
  965. component: {
  966. name: 'el-input'
  967. }
  968. },
  969. {
  970. //【很重要】是否隐藏
  971. hidden({ scope }) {
  972. // scope 为表单值
  973. // 返回一个 boolean 来控制当前表单项的隐藏/显示
  974. return scope.status != 1;
  975. },
  976. label: '密码',
  977. prop: 'password',
  978. component: {
  979. name: 'el-input'
  980. }
  981. }
  982. ],
  983. on: {
  984. submit(data, { close }) {
  985. close();
  986. }
  987. }
  988. });
  989. }
  990. </script>
  991. ```
  992. ## 布局 示例
  993. ```vue
  994. <template>
  995. <div class="scope">
  996. <div class="h">
  997. <el-tag size="small" effect="dark" disable-transitions>layout</el-tag>
  998. <span>布局</span>
  999. </div>
  1000. <div class="c">
  1001. <el-button @click="open">预览</el-button>
  1002. <demo-code :files="['form/layout.vue']" />
  1003. <!-- 自定义表单组件 -->
  1004. <cl-form ref="Form"></cl-form>
  1005. </div>
  1006. <div class="f">
  1007. <span class="date">2024-01-01</span>
  1008. </div>
  1009. </div>
  1010. </template>
  1011. <script setup lang="ts">
  1012. import { useForm } from '@cool-vue/crud';
  1013. const Form = useForm();
  1014. function open() {
  1015. Form.value?.open({
  1016. title: '布局',
  1017. items: [
  1018. {
  1019. //【span】参考文档:https://element-plus.gitee.io/zh-CN/component/layout.html
  1020. // 使用 1/24 分栏,默认 24
  1021. span: 12,
  1022. label: '昵称',
  1023. prop: 'nickname',
  1024. component: {
  1025. name: 'el-input'
  1026. }
  1027. },
  1028. {
  1029. span: 12,
  1030. label: '手机号',
  1031. prop: 'phone',
  1032. component: {
  1033. name: 'el-input',
  1034. props: {
  1035. maxlength: 11
  1036. }
  1037. }
  1038. },
  1039. {
  1040. //【flex】使宽度不填充满
  1041. flex: false,
  1042. label: '标签',
  1043. prop: 'label',
  1044. component: {
  1045. name: 'el-input'
  1046. }
  1047. },
  1048. {
  1049. label: '状态',
  1050. prop: 'status',
  1051. value: 1,
  1052. component: {
  1053. name: 'el-radio-group',
  1054. options: [
  1055. {
  1056. label: '开启',
  1057. value: 1
  1058. },
  1059. {
  1060. label: '关闭',
  1061. value: 0
  1062. }
  1063. ]
  1064. }
  1065. },
  1066. {
  1067. label: '备注',
  1068. prop: 'remark',
  1069. component: {
  1070. name: 'el-input',
  1071. props: {
  1072. type: 'textarea',
  1073. rows: 4
  1074. }
  1075. }
  1076. }
  1077. ],
  1078. on: {
  1079. submit(data, { close }) {
  1080. close();
  1081. }
  1082. }
  1083. });
  1084. }
  1085. </script>
  1086. ```
  1087. ## 起步 示例
  1088. ```vue
  1089. <template>
  1090. <div class="scope">
  1091. <div class="h">
  1092. <el-tag size="small" effect="dark" disable-transitions>open</el-tag>
  1093. <span>起步</span>
  1094. </div>
  1095. <div class="c">
  1096. <el-button @click="open">预览</el-button>
  1097. <demo-code :files="['form/open.vue']" />
  1098. <!-- 自定义表单组件 -->
  1099. <!--【很重要】ref 一定要对应 useForm 定义的值 -->
  1100. <cl-form ref="Form"></cl-form>
  1101. </div>
  1102. <div class="f">
  1103. <span class="date">2024-01-01</span>
  1104. </div>
  1105. </div>
  1106. </template>
  1107. <script setup lang="tsx">
  1108. import { useForm } from '@cool-vue/crud';
  1109. const Form = useForm();
  1110. function open() {
  1111. Form.value?.open({
  1112. title: '起步',
  1113. items: [
  1114. {
  1115. label: '昵称',
  1116. // 绑定值的标识,表单提交及回显会自动根据 prop 获取对应的值
  1117. prop: 'nickname',
  1118. // 组件绑定
  1119. component: {
  1120. // 必须是“全局注册”的组件名,如 element-plus 的 el-input、el-date-picker 等
  1121. name: 'el-input',
  1122. // 绑定的组件参数配置,如 clearable、placeholder 等
  1123. // 组件内 emit 的用 on[name] 接收,如 onChange、onInput、onBlur 等
  1124. props: {
  1125. placeholder: '请输入昵称',
  1126. clearable: true,
  1127. onChange(value: string) {}
  1128. }
  1129. }
  1130. },
  1131. {
  1132. prop: 'age',
  1133. component: {
  1134. name: 'el-input-number'
  1135. },
  1136. // 默认值,第一次打开有效
  1137. value: 18
  1138. }
  1139. ],
  1140. on: {
  1141. // 打开时触发
  1142. open() {
  1143. console.log(Form.value?.validateField);
  1144. },
  1145. // 关闭时触发。当配置该方法时,关闭事件会被阻断,使用 done() 关闭窗口
  1146. close(action, done) {
  1147. // action 为关闭窗口的触发动作 "save" | "close"
  1148. // done 关闭事件
  1149. done();
  1150. },
  1151. // 提交时触发
  1152. submit(data, { done, close }) {
  1153. // data 为表单值
  1154. // done 关闭加载事件、但不关闭窗口
  1155. // close 关闭窗口
  1156. close();
  1157. }
  1158. }
  1159. });
  1160. }
  1161. </script>
  1162. ```
  1163. ## 选项框配置 示例
  1164. ```vue
  1165. <template>
  1166. <div class="scope">
  1167. <div class="h">
  1168. <el-tag size="small" effect="dark" disable-transitions>options</el-tag>
  1169. <span>选项框配置</span>
  1170. </div>
  1171. <div class="c">
  1172. <el-button @click="open">预览</el-button>
  1173. <demo-code :files="['form/options.vue']" />
  1174. <!-- 自定义表单组件 -->
  1175. <cl-form ref="Form"></cl-form>
  1176. </div>
  1177. <div class="f">
  1178. <span class="date">2024-01-01</span>
  1179. </div>
  1180. </div>
  1181. </template>
  1182. <script setup lang="ts">
  1183. import { useForm } from '@cool-vue/crud';
  1184. import { computed, reactive } from 'vue';
  1185. const Form = useForm();
  1186. // 觉得麻烦就 any,如 { user: [] as any[] }
  1187. const options = reactive<{ [key: string]: { label: string; value: any }[] }>({
  1188. user: []
  1189. });
  1190. function open() {
  1191. Form.value?.open({
  1192. title: '选项框配置',
  1193. items: [
  1194. {
  1195. label: '下拉框',
  1196. prop: 'select',
  1197. component: {
  1198. name: 'el-select',
  1199. props: {
  1200. clearable: true // 可清除
  1201. },
  1202. options: [
  1203. {
  1204. label: 'javascript',
  1205. value: 1
  1206. },
  1207. {
  1208. label: 'vue',
  1209. value: 2
  1210. },
  1211. {
  1212. label: 'html',
  1213. value: 3
  1214. },
  1215. {
  1216. label: 'css',
  1217. value: 4
  1218. }
  1219. ]
  1220. }
  1221. },
  1222. {
  1223. label: '单选框',
  1224. prop: 'radio',
  1225. value: 1,
  1226. component: {
  1227. name: 'el-radio-group',
  1228. options: [
  1229. {
  1230. label: '手机',
  1231. value: 1
  1232. },
  1233. {
  1234. label: '电脑',
  1235. value: 2
  1236. },
  1237. {
  1238. label: '电视',
  1239. value: 3
  1240. }
  1241. ]
  1242. }
  1243. },
  1244. {
  1245. label: '多选框',
  1246. prop: 'checkbox',
  1247. value: [2, 3],
  1248. component: {
  1249. name: 'el-checkbox-group',
  1250. options: [
  1251. {
  1252. label: '咖啡',
  1253. value: 1
  1254. },
  1255. {
  1256. label: '汉堡',
  1257. value: 2
  1258. },
  1259. {
  1260. label: '炸鸡',
  1261. value: 3
  1262. },
  1263. {
  1264. label: '奶茶',
  1265. value: 4
  1266. }
  1267. ]
  1268. }
  1269. },
  1270. {
  1271. label: '动态配置1',
  1272. prop: 'd1',
  1273. component: {
  1274. name: 'el-select',
  1275. // 动态设置方法1,在 on.open 事件配置 options
  1276. options: []
  1277. }
  1278. },
  1279. {
  1280. label: '动态配置2',
  1281. prop: 'd2',
  1282. component: {
  1283. name: 'el-select',
  1284. // 动态设置方法2,使用 computed 更新 options
  1285. options: computed(() => options.user)
  1286. }
  1287. }
  1288. ],
  1289. on: {
  1290. open() {
  1291. // 模拟 1.5s 后取的数据
  1292. setTimeout(() => {
  1293. // 动态设置方法1,使用 setOptions 方法设置
  1294. // d1 为 prop 值
  1295. Form.value?.setOptions('d1', [
  1296. {
  1297. label: '😊',
  1298. value: 1
  1299. },
  1300. {
  1301. label: '😭',
  1302. value: 2
  1303. },
  1304. {
  1305. label: '😘',
  1306. value: 3
  1307. }
  1308. ]);
  1309. // 动态设置方法2,直接设置 options.user,由 computed 更新
  1310. options.user = [
  1311. {
  1312. label: '💰',
  1313. value: 1
  1314. },
  1315. {
  1316. label: '🚗',
  1317. value: 2
  1318. }
  1319. ];
  1320. }, 1500);
  1321. },
  1322. submit(data, { close }) {
  1323. close();
  1324. }
  1325. }
  1326. });
  1327. }
  1328. </script>
  1329. ```
  1330. ## 插件的使用 示例
  1331. ```vue
  1332. <template>
  1333. <div class="scope">
  1334. <div class="h">
  1335. <el-tag size="small" effect="dark" disable-transitions>plugin</el-tag>
  1336. <span>插件的使用</span>
  1337. </div>
  1338. <div class="c">
  1339. <el-button @click="open('manager')">管理者</el-button>
  1340. <el-button @click="open('user')">用户</el-button>
  1341. <demo-code :files="['form/plugin/index.vue', 'form/plugin/role.ts']" />
  1342. <!-- 自定义表单组件 -->
  1343. <cl-form ref="Form"></cl-form>
  1344. </div>
  1345. <div class="f">
  1346. <span class="date">2024-01-01</span>
  1347. </div>
  1348. </div>
  1349. </template>
  1350. <script setup lang="ts">
  1351. import { useForm } from '@cool-vue/crud';
  1352. import { setRole } from './role';
  1353. const Form = useForm();
  1354. function open(role: string) {
  1355. Form.value?.open(
  1356. {
  1357. title: '插件的使用',
  1358. items: [
  1359. {
  1360. label: '姓名',
  1361. prop: 'name',
  1362. required: true,
  1363. component: {
  1364. name: 'el-input'
  1365. }
  1366. },
  1367. {
  1368. // 自定义参数 role,匹配插件传入的角色
  1369. role: 'user',
  1370. label: '面试职位',
  1371. prop: 'work',
  1372. value: 1,
  1373. component: {
  1374. name: 'el-radio-group',
  1375. options: [
  1376. {
  1377. label: '前端开发',
  1378. value: 1
  1379. },
  1380. {
  1381. label: '后端开发',
  1382. value: 2
  1383. },
  1384. {
  1385. label: 'UI设计',
  1386. value: 3
  1387. }
  1388. ]
  1389. }
  1390. },
  1391. {
  1392. role: 'user',
  1393. label: '期望薪资',
  1394. prop: 'salary',
  1395. value: 5000,
  1396. component: {
  1397. name: 'el-input-number',
  1398. props: {
  1399. min: 2000,
  1400. max: 100000
  1401. }
  1402. }
  1403. },
  1404. {
  1405. role: 'manager',
  1406. label: '入职时间',
  1407. prop: 'date',
  1408. component: {
  1409. name: 'el-date-picker'
  1410. }
  1411. },
  1412. {
  1413. role: 'manager',
  1414. label: '负责人',
  1415. prop: 'head',
  1416. component: {
  1417. name: 'el-input'
  1418. }
  1419. }
  1420. ],
  1421. on: {
  1422. submit(data, { done, close }) {
  1423. close();
  1424. }
  1425. }
  1426. },
  1427. [
  1428. // 自定义插件,角色权限控制
  1429. setRole(role)
  1430. ]
  1431. );
  1432. }
  1433. </script>
  1434. ```
  1435. ## 必填项配置 示例
  1436. ```vue
  1437. <template>
  1438. <div class="scope">
  1439. <div class="h">
  1440. <el-tag size="small" effect="dark" disable-transitions>required</el-tag>
  1441. <span>必填项配置</span>
  1442. </div>
  1443. <div class="c">
  1444. <el-button @click="open">预览</el-button>
  1445. <demo-code :files="['form/required.vue']" />
  1446. <!-- 自定义表单组件 -->
  1447. <cl-form ref="Form"></cl-form>
  1448. </div>
  1449. <div class="f">
  1450. <span class="date">2024-01-01</span>
  1451. </div>
  1452. </div>
  1453. </template>
  1454. <script setup lang="ts">
  1455. import { useForm } from '@cool-vue/crud';
  1456. const Form = useForm();
  1457. function open() {
  1458. Form.value?.open({
  1459. title: '必填项配置',
  1460. items: [
  1461. {
  1462. label: '昵称',
  1463. prop: 'nickname',
  1464. component: {
  1465. name: 'el-input'
  1466. },
  1467. // 是否必填,默认判断绑定值是否空
  1468. required: true
  1469. },
  1470. {
  1471. label: '手机号',
  1472. prop: 'phone',
  1473. component: {
  1474. name: 'el-input',
  1475. props: {
  1476. maxlength: 11
  1477. }
  1478. },
  1479. // 自定义规则
  1480. // 基础用法可参考:https://element-plus.gitee.io/zh-CN/component/form.html
  1481. // 高级用法可参考:https://github.com/yiminghe/async-validator
  1482. rules: [
  1483. {
  1484. required: true,
  1485. validator: (rule, value, callback) => {
  1486. if (value === '') {
  1487. callback(new Error('手机号不能为空'));
  1488. } else if (!/^1[3456789]\d{9}$/.test(value)) {
  1489. callback(new Error('手机号格式错误'));
  1490. } else {
  1491. callback();
  1492. }
  1493. }
  1494. }
  1495. ]
  1496. }
  1497. ],
  1498. on: {
  1499. submit(data, { close }) {
  1500. close();
  1501. }
  1502. }
  1503. });
  1504. }
  1505. </script>
  1506. ```
  1507. ## 添加/删除表单项 示例
  1508. ```vue
  1509. <template>
  1510. <div class="scope">
  1511. <div class="h">
  1512. <el-tag size="small" effect="dark" disable-transitions>rules</el-tag>
  1513. <span>添加/删除表单项</span>
  1514. </div>
  1515. <div class="c">
  1516. <el-button @click="open">预览</el-button>
  1517. <demo-code :files="['form/rules.vue']" />
  1518. <!-- 自定义表单组件 -->
  1519. <cl-form ref="Form">
  1520. <template #slot-cert="{ scope }">
  1521. <div class="cert">
  1522. <!--【很重要】prop、rules 配置格式如下 -->
  1523. <el-form-item
  1524. v-for="(item, index) in scope.cert"
  1525. :key="index"
  1526. :label="`证书${index + 1}`"
  1527. :prop="`cert.${index}.label`"
  1528. :rules="{
  1529. message: `请填写证书${index + 1}`,
  1530. required: true
  1531. }"
  1532. >
  1533. <div class="row">
  1534. <!-- 输入框 -->
  1535. <el-input v-model="item.label" placeholder="请填写证书"></el-input>
  1536. <!-- 删除行 -->
  1537. <el-icon @click="rowDel(index)">
  1538. <delete />
  1539. </el-icon>
  1540. </div>
  1541. </el-form-item>
  1542. <!-- 添加行 -->
  1543. <el-row type="flex" justify="end">
  1544. <el-button @click="rowAdd()">添加证书</el-button>
  1545. </el-row>
  1546. </div>
  1547. </template>
  1548. </cl-form>
  1549. </div>
  1550. <div class="f">
  1551. <span class="date">2024-01-01</span>
  1552. </div>
  1553. </div>
  1554. </template>
  1555. <script setup lang="ts">
  1556. import { useForm } from '@cool-vue/crud';
  1557. import { Delete } from '@element-plus/icons-vue';
  1558. const Form = useForm();
  1559. function open() {
  1560. Form.value?.open({
  1561. title: '添加/删除表单项',
  1562. items: [
  1563. {
  1564. label: '昵称',
  1565. prop: 'nickname',
  1566. component: {
  1567. name: 'el-input'
  1568. },
  1569. required: true
  1570. },
  1571. {
  1572. prop: 'cert',
  1573. //【很重要】默认数据格式,以实际业务为主。
  1574. value: [
  1575. {
  1576. label: ''
  1577. }
  1578. ],
  1579. component: {
  1580. name: 'slot-cert'
  1581. }
  1582. }
  1583. ],
  1584. on: {
  1585. submit(data, { close }) {
  1586. close();
  1587. }
  1588. }
  1589. });
  1590. }
  1591. function rowAdd() {
  1592. Form.value?.form.cert.push({
  1593. label: ''
  1594. });
  1595. }
  1596. function rowDel(index: number) {
  1597. Form.value?.form.cert.splice(index, 1);
  1598. }
  1599. </script>
  1600. <style lang="scss" scoped>
  1601. .cert {
  1602. .row {
  1603. display: flex;
  1604. align-items: center;
  1605. .el-input {
  1606. flex: 1;
  1607. margin-right: 10px;
  1608. }
  1609. .el-icon {
  1610. cursor: pointer;
  1611. &:hover {
  1612. color: red;
  1613. }
  1614. }
  1615. }
  1616. }
  1617. </style>
  1618. ```