Commit c5b39f2c16f9aa56431844e9c01600a2d2f3848c
Committed by
GitHub
1 parent
4c0f2038
feat: 增加表单设计器 (#2533)
Showing
47 changed files
with
7840 additions
and
2 deletions
package.json
@@ -73,7 +73,8 @@ | @@ -73,7 +73,8 @@ | ||
73 | "vxe-table": "^4.3.9", | 73 | "vxe-table": "^4.3.9", |
74 | "vxe-table-plugin-export-xlsx": "^3.0.4", | 74 | "vxe-table-plugin-export-xlsx": "^3.0.4", |
75 | "xe-utils": "^3.5.7", | 75 | "xe-utils": "^3.5.7", |
76 | - "xlsx": "^0.18.5" | 76 | + "xlsx": "^0.18.5", |
77 | + "vuedraggable": "^4.1.0" | ||
77 | }, | 78 | }, |
78 | "devDependencies": { | 79 | "devDependencies": { |
79 | "@commitlint/cli": "^16.2.3", | 80 | "@commitlint/cli": "^16.2.3", |
src/router/routes/modules/form-design/main.ts
0 → 100644
1 | +import type { AppRouteModule } from '/@/router/types'; | ||
2 | + | ||
3 | +import { LAYOUT } from '/@/router/constant'; | ||
4 | + | ||
5 | +const permission: AppRouteModule = { | ||
6 | + path: '/form-designer', | ||
7 | + name: 'Form-designer', | ||
8 | + component: LAYOUT, | ||
9 | + meta: { | ||
10 | + orderNo: 10000, | ||
11 | + icon: 'icon:add-circle', | ||
12 | + title: '表单设计', | ||
13 | + }, | ||
14 | + children: [ | ||
15 | + { | ||
16 | + path: 'design', | ||
17 | + name: 'Design', | ||
18 | + meta: { | ||
19 | + title: '表单设计', | ||
20 | + }, | ||
21 | + component: () => import('/@/views/form-design/index.vue'), | ||
22 | + }, | ||
23 | + { | ||
24 | + path: 'example1', | ||
25 | + name: 'Example1', | ||
26 | + meta: { | ||
27 | + title: '示例', | ||
28 | + }, | ||
29 | + component: () => import('/@/views/form-design/examples/baseForm.vue'), | ||
30 | + }, | ||
31 | + ], | ||
32 | +}; | ||
33 | + | ||
34 | +export default permission; |
src/views/form-design/assets/iconfont/index.js
0 → 100644
1 | +!(function (c) { | ||
2 | + var h, | ||
3 | + l, | ||
4 | + a, | ||
5 | + t, | ||
6 | + v, | ||
7 | + o = | ||
8 | + '<svg><symbol id="icon-month" viewBox="0 0 1053 1024"><path d="M380.342857 58.514286m28.379429 0l235.812571 0q28.379429 0 28.379429 28.379428l0 1.755429q0 28.379429-28.379429 28.379428l-235.812571 0q-28.379429 0-28.379429-28.379428l0-1.755429q0-28.379429 28.379429-28.379428Z" ></path><path d="M881.225143 58.514286H819.2a29.257143 29.257143 0 0 0-29.257143 29.257143 29.257143 29.257143 0 0 0 29.257143 29.257142h62.902857A113.517714 113.517714 0 0 1 994.742857 230.546286v621.421714A113.517714 113.517714 0 0 1 881.225143 965.485714H172.032A113.517714 113.517714 0 0 1 58.514286 851.968V230.546286A113.517714 113.517714 0 0 1 172.032 117.028571H234.057143a29.257143 29.257143 0 0 0 29.257143-29.257142 29.257143 29.257143 0 0 0-29.257143-29.257143H172.032A172.032 172.032 0 0 0 0 230.546286v621.421714A172.032 172.032 0 0 0 172.032 1024h709.193143A172.032 172.032 0 0 0 1053.257143 851.968V230.546286A172.032 172.032 0 0 0 881.225143 58.514286z" ></path><path d="M321.828571 175.542857a29.257143 29.257143 0 0 1-29.257142-29.257143V29.257143a29.257143 29.257143 0 0 1 58.514285 0v117.028571a29.257143 29.257143 0 0 1-29.257143 29.257143zM731.428571 175.542857a29.257143 29.257143 0 0 1-29.257142-29.257143V29.257143a29.257143 29.257143 0 0 1 58.514285 0v117.028571a29.257143 29.257143 0 0 1-29.257143 29.257143z" ></path><path d="M83.968 220.598857m29.257143 0l826.806857 0q29.257143 0 29.257143 29.257143l0 0q0 29.257143-29.257143 29.257143l-826.806857 0q-29.257143 0-29.257143-29.257143l0 0q0-29.257143 29.257143-29.257143Z" ></path><path d="M398.774857 424.228571h25.161143a26.624 26.624 0 0 1 24.868571 17.261715l64.658286 178.761143c9.069714 25.746286 16.969143 52.077714 26.038857 78.409142h2.633143c8.777143-26.331429 16.091429-52.662857 25.161143-78.409142l63.780571-178.468572a26.624 26.624 0 0 1 24.868572-17.554286h25.746286a26.331429 26.331429 0 0 1 26.331428 26.331429v334.994286a26.331429 26.331429 0 0 1-26.331428 26.331428h-4.096a26.331429 26.331429 0 0 1-26.624-26.331428V620.251429c0-34.816 4.973714-84.845714 7.899428-120.246858h-2.048l-31.012571 89.526858-61.147429 167.643428a26.331429 26.331429 0 0 1-24.868571 17.261714 26.331429 26.331429 0 0 1-24.868572-17.261714l-61.44-167.643428-30.72-89.526858h-2.048c2.633143 35.401143 7.314286 85.430857 7.314286 120.246858v165.302857a26.331429 26.331429 0 0 1-26.331429 26.331428h-2.048a26.331429 26.331429 0 0 1-26.331428-26.331428V450.56a26.331429 26.331429 0 0 1 25.453714-26.331429z" ></path></symbol><symbol id="icon-date-range" viewBox="0 0 1075 1024"><path d="M691.2 240.64h-256v76.8h-51.2V240.64H204.8V870.4h716.8V240.64h-179.2v76.8h-51.2V240.64z m0-51.2V112.64h51.2v76.8H972.8V921.6H153.6V189.44h230.4V112.64h51.2v76.8h256z m-355.328 370.176H358.4v182.784h-29.952v-146.688c-11.008 9.984-24.832 17.408-41.728 22.272v-29.696c8.192-2.048 16.896-5.632 26.112-10.752 9.216-5.632 16.896-11.52 23.04-17.92z m58.368 104.448h129.28v24.064H394.24v-24.064z m220.672-108.032c18.432 0 33.536 4.352 44.8 13.312 11.008 8.96 16.64 21.248 16.64 37.12 0 19.968-10.24 33.28-30.464 39.936 10.752 3.328 19.2 8.192 24.832 14.848 6.144 6.912 9.216 15.872 9.216 26.624 0 16.896-5.888 30.72-17.664 41.472-12.288 11.008-28.416 16.64-48.384 16.64-18.944 0-34.304-4.864-45.824-14.592-12.8-10.752-19.968-26.624-21.504-47.104h30.464c0.512 11.776 4.096 20.992 11.264 27.392 6.4 5.888 14.848 8.96 25.344 8.96 11.52 0 20.736-3.328 27.392-9.728a29.5424 29.5424 0 0 0 8.96-21.76c0-10.496-3.328-18.176-9.472-23.04-6.144-5.12-15.104-7.424-26.88-7.424h-12.8v-22.528h12.8c10.752 0 18.944-2.304 24.576-6.912 5.376-4.608 8.192-11.52 8.192-20.48 0-8.96-2.56-15.616-7.424-20.224-5.376-4.608-13.312-6.912-23.808-6.912-10.752 0-18.944 2.56-24.832 7.936-6.144 5.376-9.728 13.568-10.752 24.576h-29.44c1.536-18.432 8.192-32.768 20.48-43.008 11.52-10.24 26.368-15.104 44.288-15.104z m151.808 0c21.248 0 37.888 8.704 49.92 26.624 11.264 16.896 16.896 39.68 16.896 68.352 0 28.672-5.632 51.456-16.896 68.352-12.032 17.664-28.672 26.624-49.92 26.624-21.504 0-38.144-8.96-49.92-26.624-11.264-16.896-16.896-39.68-16.896-68.352 0-28.672 5.632-51.456 16.896-68.352 11.776-17.92 28.416-26.624 49.92-26.624z m0 25.344c-14.592 0-24.832 7.936-30.72 24.32-4.096 11.008-6.144 26.112-6.144 45.312 0 18.944 2.048 34.048 6.144 45.312 5.888 16.128 16.128 24.32 30.72 24.32 14.336 0 24.576-8.192 30.72-24.32 4.096-11.264 6.144-26.368 6.144-45.312 0-19.2-2.048-34.304-6.144-45.312-6.144-16.384-16.384-24.32-30.72-24.32zM204.8 399.36h716.8v51.2H204.8v-51.2z" ></path></symbol><symbol id="icon-month-range" viewBox="0 0 1024 1024"><path d="M130.15552 945.3056c-29.22496-13.69088-43.82208-29.68576-55.77728-61.12256-7.22944-18.9952-7.99744-59.8016-6.71232-355.10784l1.45408-333.63968 15.39072-22.20032C107.4176 140.2112 144.72192 122.88 192.9216 122.88H230.4v25.1904c0 24.27904-0.60416 25.32864-16.64 28.77952-42.96704 9.25184-77.40928 38.88128-88.45824 76.09856C119.0912 273.8688 117.76 324.5056 117.76 540.16c0 290.9184 0.78848 299.0336 31.86176 328.5248 38.73792 36.77184 20.224 34.816 347.01824 36.61824 333.3632 1.83296 336.59904 1.56672 371.87072-30.9504 37.76-34.80576 37.72928-34.51904 37.72928-334.19264 0-299.392 0-299.4176-37.504-333.98784-15.26784-14.06976-30.34624-22.36928-48.63488-26.75712L793.6 173.06112V121.6256l45.58848 2.18112c38.85568 1.85856 48.67584 4.224 66.51392 16.0256 11.50976 7.62368 27.2896 23.45984 35.05152 35.19488l14.12608 21.33504 1.41824 335.8208 1.42336 335.81568-14.22336 28.04736c-9.35936 18.46272-21.80608 33.31072-36.41344 43.4432l-22.20032 15.39072-364.35968 1.30048-364.3648 1.30048zM230.4 569.7536c-35.2-26.48064-65.82784-49.95072-68.06528-52.15232-2.23744-2.2016 27.01824-27.34592 65.01376-55.87456l69.08416-51.8656 1.54624 26.74688 1.54112 26.752 80.64 1.408 80.64 1.41312v96.76288l-80.64 1.408-80.64 1.408-2.56 26.07104-2.56 26.07616z m495.62112 22.7584l-1.54112-26.752-80.64-1.408-80.64-1.41312V466.176l80.64-1.408 80.64-1.408 1.54112-26.752 1.54624-26.74688 68.85376 51.69664C834.29376 489.99424 865.28 513.8432 865.28 514.56c0 0.7168-30.98624 24.56576-68.85888 53.00224l-68.85376 51.69664zM291.84 120.32V66.56h107.52v107.52H291.84zM455.68 148.48v-25.6h112.64v51.2H455.68z m168.96-28.16V66.56h107.52v107.52h-107.52z" ></path></symbol><symbol id="icon-plus" viewBox="0 0 1024 1024"><path d="M512 992C246.912 992 32 777.088 32 512 32 246.912 246.912 32 512 32c265.088 0 480 214.912 480 480 0 265.088-214.912 480-480 480z m0-64c229.76 0 416-186.24 416-416S741.76 96 512 96 96 282.24 96 512s186.24 416 416 416z" ></path><path d="M256 544a32 32 0 0 1 0-64h512a32 32 0 0 1 0 64H256z" ></path><path d="M480 256a32 32 0 0 1 64 0v512a32 32 0 0 1-64 0V256z" ></path></symbol><symbol id="icon-input" viewBox="0 0 1024 1024"><path d="M819.438021 825.26268l-615.014188 0c-6.753821 0-12.279674 5.525853-12.279674 12.279674l0 1.547239c0 6.753821 5.525853 12.279674 12.279674 12.279674l615.014188 0c6.753821 0 12.279674-5.525853 12.279674-12.279674l0-1.547239C831.717695 830.788533 826.191842 825.26268 819.438021 825.26268zM819.438021 649.614218l-615.014188 0c-6.753821 0-12.279674 5.525853-12.279674 12.279674l0 1.547239c0 6.753821 5.525853 12.279674 12.279674 12.279674l615.014188 0c6.753821 0 12.279674-5.525853 12.279674-12.279674l0-1.547239C831.717695 655.140071 826.191842 649.614218 819.438021 649.614218zM498.778884 92.111884l-322.183863 0c-62.562894 0-85.86153 23.422456-85.86153 85.86153l0 669.481707c0 65.574484 21.252023 85.86153 85.86153 85.86153l669.482731 0c69.544913 0 85.86153-20.969591 85.86153-85.86153L931.939281 524.024871c0-7.369851 5.974062-13.343913 13.343913-13.343913l0 0c7.369851 0 13.343913 5.974062 13.343913 13.343913l0 350.119099c0 47.223534-38.637995 85.86153-85.86153 85.86153l-722.859405 0c-47.224558 0-85.86153-38.637995-85.86153-85.86153L64.044642 151.284565c0-47.224558 38.637995-85.86153 85.86153-85.86153l348.871689 0c7.369851 0 13.343913 5.974062 13.343913 13.343913l0 0C512.122797 86.136799 506.148735 92.111884 498.778884 92.111884zM472.798163 580.788689c-9.897418 0-19.338441-3.927449-26.37981-11.076266-7.953136-8.074909-11.745509-19.153222-10.402931-30.394241l41.026392-351.561961c1.680269-14.298658 11.180644-26.082028 24.795732-30.762631 13.612019-4.678556 28.345582-1.223874 38.454824 9.014304l85.470627 86.446861 179.020256-176.361707c14.459317-14.32117 37.981033-14.217816 52.410674 0.257873l90.713025 91.753727c6.966669 7.03523 10.773368 16.361643 10.720156 26.264177-0.054235 9.93221-3.981684 19.250436-11.056823 26.241664l-178.98751 176.334077 82.721003 83.698261c10.089799 10.240225 13.341866 24.994254 8.501628 38.518269-4.847401 13.541411-16.750499 22.89443-31.063483 24.406876l-352.045985 37.018102C475.391221 580.721151 474.089575 580.787666 472.798163 580.788689zM514.046612 180.552146c-1.777483 0-3.203972 0.399089-3.89368 0.635473-1.608637 0.553609-6.92062 2.87549-7.702426 9.543354l-41.029462 351.58652c-0.419556 3.510964 0.756223 6.940063 3.222391 9.444093 2.473331 2.511193 5.887081 3.736091 9.390881 3.381004l352.023472-37.016055c6.697539-0.707105 9.087982-5.986341 9.660011-7.586792 0.571005-1.594311 2.068102-7.16519-2.623757-11.927657l-85.255733-86.263689c-4.128017-4.177136-6.380314-9.712199-6.340405-15.584953 0.038886-5.872754 2.364861-11.378142 6.548136-15.498996l181.558056-178.865737c2.197038-2.171456 3.423983-5.076622 3.440355-8.169053 0.017396-3.062755-1.160429-5.947456-3.314489-8.123005l-0.007163-0.008186-90.683349-91.724051c-4.465708-4.479011-11.784394-4.5312-16.277732-0.080841L641.142266 273.215592c-8.614192 8.485255-22.534226 8.392134-31.032784-0.206708l-88.015589-89.019453C519.397481 181.257204 516.411473 180.552146 514.046612 180.552146z" ></path></symbol><symbol id="icon-upload" viewBox="0 0 1024 1024"><path d="M1022.955204 556.24776c0 100.19191-81.516572 181.698249-181.718715 181.698249l-185.637977 0c-11.2973 0-20.466124-9.168824-20.466124-20.466124 0-11.307533 9.168824-20.466124 20.466124-20.466124l185.637977 0c77.628008 0 140.786467-63.148226 140.786467-140.766001 0-77.423347-62.841234-140.448776-140.203182-140.766001-0.419556 0.030699-0.828878 0.051165-1.248434 0.061398-5.935176 0.153496-11.665691-2.302439-15.666818-6.702656-4.001127-4.41045-5.884011-10.345626-5.157463-16.250102 1.330298-10.806113 1.944282-19.760043 1.944282-28.192086 0-60.763922-23.658839-117.874641-66.617234-160.833035-42.968627-42.958394-100.089579-66.617234-160.843268-66.617234-47.368844 0-92.742241 14.449084-131.208321 41.781592-37.616736 26.738991-65.952084 63.700811-81.925894 106.884332-2.425236 6.54916-8.012488 11.399631-14.827707 12.893658-6.815219 1.483794-13.927197-0.603751-18.859533-5.536087-19.289322-19.340487-44.943608-29.982872-72.245418-29.982872-56.322773 0-102.146425 45.813419-102.146425 102.125959 0 0.317225 0.040932 0.982374 0.092098 1.627057 0.061398 0.920976 0.122797 1.831718 0.153496 2.762927 0.337691 9.465582-5.863545 17.928325-15.001669 20.455891-32.356942 8.943696-61.541635 28.550243-82.181721 55.217602-21.305235 27.516704-32.571836 60.508096-32.571836 95.41307 0 86.244246 70.188572 156.422585 156.443052 156.422585l169.981393 0c11.2973 0 20.466124 9.15859 20.466124 20.466124 0 11.2973-9.168824 20.466124-20.466124 20.466124l-169.981393 0c-108.828614 0-197.3753-88.536452-197.3753-197.354833 0-44.053332 14.223956-85.712127 41.126676-120.473839 22.809495-29.450752 53.897537-52.086285 88.710414-64.816215 5.065366-74.322729 67.149353-133.2447 142.751215-133.2447 28.386514 0 55.504128 8.217149 78.651314 23.52581 19.657712-39.868009 48.842405-74.169233 85.497233-100.212376 45.434795-32.295544 99.004875-49.354058 154.918325-49.354058 71.692832 0 139.087778 27.915793 189.782368 78.600149 50.694589 50.694589 78.610382 118.089535 78.610382 189.782368 0 3.704368-0.102331 7.470135-0.296759 11.368932C952.633602 386.245901 1022.955204 463.188294 1022.955204 556.24776z" ></path><path d="M629.258611 589.106122c-3.990894 3.990894-9.230222 5.996574-14.46955 5.996574s-10.478655-2.00568-14.46955-5.996574l-67.087954-67.077721 0 358.689289c0 11.307533-9.15859 20.466124-20.466124 20.466124-11.307533 0-20.466124-9.15859-20.466124-20.466124l0-358.689289-67.087954 67.077721c-7.992021 7.992021-20.947078 7.992021-28.939099 0s-7.992021-20.957311 0-28.949332l102.023628-102.013395c7.992021-7.992021 20.947078-7.992021 28.939099 0l102.023628 102.013395C637.250632 568.148811 637.250632 581.114101 629.258611 589.106122z" ></path></symbol><symbol id="icon-alert" viewBox="0 0 1026 1024"><path d="M1004.657 801.716 602.263 91.599c-49.213-86.817-129.646-86.817-178.866 0L21.004 801.716c-49.207 86.906-8.949 157.798 89.388 157.798l804.877 0C1013.606 959.514 1053.825 888.622 1004.657 801.716zM544.635 832.216l-63.649 0 0-63.649 63.649 0L544.635 832.216zM544.635 641.27l-63.649 0L480.986 259.377l63.649 0L544.635 641.27z" ></path></symbol><symbol id="icon-radio" viewBox="0 0 1024 1024"><path d="M507.699 16.766c-271.033 0-491.52 220.487-491.52 491.52s220.487 491.52 491.52 491.52 491.52-220.487 491.52-491.52-220.487-491.52-491.52-491.52zM507.699 958.846c-248.436 0-450.56-202.124-450.56-450.56s202.124-450.56 450.56-450.56 450.56 202.124 450.56 450.56-202.124 450.56-450.56 450.56z" ></path><path d="M331.339 508.286c0 97.401 78.959 176.361 176.361 176.361 97.401 0 176.361-78.959 176.361-176.361 0-97.401-78.959-176.361-176.361-176.361-97.401 0-176.361 78.959-176.361 176.361z" ></path></symbol><symbol id="icon-switch" viewBox="0 0 1024 1024"><path d="M281.6 403.2l83.2 0 0 83.2L281.6 486.4 281.6 403.2zM960 512c0 108.8-83.2 192-192 192L256 704c-108.8 0-192-83.2-192-192s83.2-192 192-192l512 0C876.8 320 960 403.2 960 512zM448 486.4 384 486.4 384 403.2l51.2 0L435.2 384 204.8 384l0 19.2 57.6 0 0 83.2L192 486.4 192 512l70.4 0c0 32-6.4 51.2-12.8 64C236.8 588.8 217.6 608 192 620.8 198.4 627.2 204.8 633.6 211.2 640 236.8 620.8 256 601.6 268.8 582.4 275.2 569.6 281.6 544 281.6 512l83.2 0 0 128L384 640 384 512l64 0L448 486.4zM928 512c0-89.6-70.4-160-160-160S608 422.4 608 512s70.4 160 160 160S928 601.6 928 512z" ></path></symbol><symbol id="icon-rate" viewBox="0 0 1332 1024"><path d="M1315.41416 444.123297l-193.019175 192.916777c-12.825279 12.79968-18.661933 31.231219-15.641209 49.304367l45.566861 272.377191c6.01585 36.0183-21.861853 65.252769-53.528262 65.252769-8.345391 0-16.972376-2.047949-25.317767-6.527837l-238.586035-128.611185a53.349066 53.349066 0 0 0-50.609935-0.076798l-238.662833 128.508787a52.990675 52.990675 0 0 1-25.343367 6.297443c-31.666408 0-59.697708-29.61846-53.681858-65.636759l45.720457-272.300393C513.207015 629.20667 460.98432 665.58336 460.98432 665.58336c-2.150346-0.742381-21.503462-1.049574-38.911027 12.0317L281.7888 767.9808c-7.60301 5.683058-42.955726 25.59936-51.19872 25.59936-21.247469 0-26.060148-26.674533-25.59936-51.19872v-179.19552c0.255994-13.516462-12.79968-26.879328-23.705007-34.661533L17.040219 411.074523c-27.442514-19.634709-20.453889-62.692833 11.852504-72.830179l193.326366-60.82408c12.825279-4.0191 22.911427-14.335642 26.956127-27.263318l61.156871-195.041524c5.887853-18.713132 22.527437-29.695258 39.269418-29.695258 12.364491 0 24.754581 4.0191 32.946376 15.794806l115.683508 163.17032c7.577411 10.854129 19.916302 11.18692 33.099973 11.186921h0.588785l108.822879 4.556686c-8.089398 15.718007-22.885828 32.767181-40.088597 35.327117l-148.450689 15.9996L358.58688 127.9968l-34.866328 147.477913c3.19992-10.137347-12.441289 58.648134-78.692433 79.460414L102.59328 383.9904l123.337717 78.103648c32.511187 23.244219 56.625784 60.440089 55.857803 101.091872v127.9968l94.180046-78.948426C419.052569 579.902302 492.727527 589.322867 460.98432 588.78528l-153.59616-153.59616c16.40919 16.40919-64.663983-46.258044 25.59936-76.79808l179.19552-25.59936 76.79808-25.59936c28.594485-4.249494 76.081298-100.631084 51.19872-51.19872l120.675384-224.941576A53.60506 53.60506 0 0 1 809.596405 0c19.378716 0 38.757431 10.367741 48.715582 31.052024l119.318617 247.827404a54.449839 54.449839 0 0 0 40.907777 30.488838l266.796531 39.730207c44.568486 6.655834 62.360041 62.79523 30.079248 95.024824z m-308.088298-55.038624c-43.160521-6.425439-80.509987-34.584735-99.863104-74.826929l-97.866353-203.898903L691.37856 307.19232c10.39334-21.60586-51.19872 76.79808-51.19872 76.79808l-246.137846 37.835854 170.542936 149.884253 25.752956 113.277168C571.496757 767.929602 537.7824 947.176321 537.7824 947.176321l208.276393-129.148772c-18.482738 9.958151 8.575786-9.369366 61.899253-9.57416-58.80173 0.742381 39.474213-3.839904 63.128022 8.908577L1075.368961 921.576961l-51.19872-281.592961c3.251119 19.481113-15.154821-2.227144 43.698107-61.105672l157.256869-157.20567-217.799355-32.587985z" ></path></symbol><symbol id="icon-button" viewBox="0 0 1024 1024"><path d="M368 518.4c-3.2-6.4-6.4-9.6-12.8-16-3.2-3.2-9.6-6.4-12.8-9.6 3.2-3.2 6.4-3.2 6.4-6.4 3.2-3.2 3.2-6.4 6.4-9.6 0-3.2 3.2-6.4 3.2-12.8v-12.8c0-9.6-3.2-19.2-6.4-28.8-3.2-9.6-9.6-16-12.8-22.4-6.4-6.4-12.8-9.6-19.2-12.8-6.4-3.2-16-3.2-22.4-3.2H224v252.8h80c9.6 0 19.2-3.2 28.8-6.4 9.6-3.2 16-9.6 22.4-16 6.4-6.4 12.8-16 16-25.6 3.2-9.6 6.4-19.2 6.4-32 0-6.4 0-16-3.2-22.4s-3.2-12.8-6.4-16z m-96-89.6h16c6.4 0 12.8 3.2 16 6.4 6.4 3.2 6.4 9.6 6.4 19.2s-3.2 16-6.4 19.2c-3.2 6.4-9.6 6.4-16 6.4h-16v-51.2z m54.4 144c-3.2 3.2-3.2 6.4-6.4 9.6-3.2 3.2-6.4 3.2-9.6 6.4-3.2 0-6.4 3.2-12.8 3.2H272v-64h25.6c9.6 0 16 3.2 22.4 9.6 6.4 6.4 9.6 12.8 9.6 25.6 0 0-3.2 6.4-3.2 9.6zM748.8 480v48c-6.4-6.4-9.6-16-16-25.6-6.4-9.6-12.8-16-19.2-22.4L640 384h-41.6v252.8h51.2v-160l19.2 28.8c6.4 9.6 12.8 19.2 22.4 28.8l83.2 108.8H800v-256h-51.2V480z" fill="#333333" ></path><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" fill="#333333" ></path><path d="M393.6 432h64v204.8H512V432h64v-48H393.6z" fill="#333333" ></path></symbol><symbol id="icon-color-picker" viewBox="0 0 1024 1024"><path d="M953.856 292.352c-3.584-3.584-8.192-5.632-13.312-5.632-12.288 0-26.624 10.752-35.84 20.48L563.2 638.464c-12.288 12.288-58.368 87.04-60.416 90.112l-7.68 12.288 12.288-7.68c3.072-2.048 78.848-48.64 91.648-60.416l341.504-331.776c14.848-14.848 26.624-35.328 13.312-48.64zM740.864 395.264c0 27.648-22.528 50.176-50.176 50.176s-50.176-22.528-50.176-50.176 22.528-50.176 50.176-50.176 50.176 22.528 50.176 50.176zM215.04 570.368c-27.648 0-50.176-22.528-50.176-50.176s22.528-50.176 50.176-50.176 50.176 22.528 50.176 50.176c0 28.16-22.528 50.176-50.176 50.176zM290.304 395.264c0-27.648 22.528-50.176 50.176-50.176s50.176 22.528 50.176 50.176-22.528 50.176-50.176 50.176-50.176-22.528-50.176-50.176zM465.408 345.088c0-27.648 22.528-50.176 50.176-50.176s50.176 22.528 50.176 50.176-22.528 50.176-50.176 50.176-50.176-22.528-50.176-50.176z" ></path><path d="M858.112 514.048c-23.04 148.992-163.84 305.152-357.376 305.152-36.864 0-76.288-7.168-107.52-19.968-33.792-13.312-49.152-34.304-51.712-45.056-1.536-5.632-3.072-11.264-4.608-17.408-4.608-18.432-9.728-33.792-20.48-51.2-18.432-30.72-45.056-36.864-64-36.864-7.168 0-14.848 1.024-22.528 2.56-12.8 3.072-25.6 4.608-37.376 4.608-25.088 0-77.824-16.896-78.848-61.44-6.656-228.352 190.976-352.256 379.904-373.76 13.312-1.536 27.648-2.048 42.496-2.048 53.248 0 111.616 10.24 152.576 26.112 9.216 3.584 14.336 6.144 39.424 18.432 25.6 12.8 46.592 30.72 62.976 47.616l30.208-48.128c-17.92-16.896-40.448-34.304-67.072-47.616-25.088-12.288-31.744-15.36-43.52-19.968-47.616-18.432-114.176-30.208-174.08-30.208-16.896 0-33.792 1.024-49.152 2.56-115.712 13.312-220.672 58.88-296.448 129.024-87.04 79.36-130.048 184.832-126.976 304.64 1.024 34.304 15.36 62.976 40.96 82.432 22.528 16.896 52.736 26.112 87.04 26.112 16.384 0 33.28-2.048 50.176-6.144 3.584-1.024 6.656-1.024 9.728-1.024 11.264 0 16.896 10.24 27.136 49.152 1.536 6.656 3.584 13.312 5.12 19.968 9.216 31.744 39.424 58.88 86.528 77.824 38.4 15.36 85.504 24.064 129.536 24.064 54.272 0 106.496-10.24 155.648-30.208 46.08-18.944 88.576-45.568 125.952-80.384 35.84-33.28 65.536-71.68 88.064-115.2 22.528-43.008 36.864-88.576 42.496-135.168 0.512-2.048 0.512-4.096 0.512-6.656v-1.536c0-2.56 0.512-4.608 0.512-7.168l-55.296 16.896z" ></path></symbol><symbol id="icon-editor" viewBox="0 0 1024 1024"><path d="M218.316 307.727h87.886v205.06h-29.297v29.295h117.179v-29.294H364.79V307.727h87.882v29.293h29.294v-87.882H189.022v87.882h29.294v-29.293z m322.242 58.59h292.945v58.588H540.558v-58.588z m0 117.177h292.945v58.588H540.558v-58.588z m-351.536 117.18h644.481v58.588h-644.48v-58.587z m0 117.176h644.481v58.588h-644.48V717.85z m351.536-468.713h292.945v58.589H540.558v-58.589z m420.923 713.13H61.045V63.309h900.436v898.958z m-864.62-35.816h828.804V99.125H96.861V926.45z" ></path></symbol><symbol id="icon-time-picker" viewBox="0 0 1024 1024"><path d="M942.4 476.8c-16-209.6-185.6-379.2-395.2-395.2-265.6-20.8-486.4 200-465.6 465.6 16 209.6 185.6 379.2 395.2 395.2 265.6 20.8 486.4-200 465.6-465.6z m-64 73.6c-17.6 172.8-156.8 312-328 328-233.6 22.4-427.2-171.2-404.8-404.8 17.6-172.8 156.8-312 328-328 233.6-22.4 427.2 171.2 404.8 404.8z" ></path><path d="M544 483.2V241.6c0-14.4-8-27.2-20.8-32-22.4-6.4-43.2 9.6-43.2 30.4v262.4c0 1.6 0 1.6 1.6 3.2 0 1.6 0 1.6 1.6 3.2 0 1.6 1.6 1.6 1.6 3.2s0 1.6 1.6 3.2 3.2 3.2 3.2 4.8L624 654.4c9.6 9.6 25.6 14.4 38.4 6.4 20.8-11.2 24-36.8 8-51.2L544 483.2z" ></path></symbol><symbol id="icon-uploadImage" viewBox="0 0 1024 1024"><path d="M390.4 317.184a73.184 73.184 0 1 0-146.368 0.064 73.184 73.184 0 0 0 146.368-0.064z m243.328 373.856h60.512v-60.512c0-44.576 26.72-82.816 64.896-100.192l-10.112-34.688v-2.432h0.096l-66.176-199.968-231.04 298.112-61.92-104.896-245.728 247.968h402.464a109.76 109.76 0 0 1 87.072-43.424zM1024 163.84C1024 73.728 950.272 0 860.16 0H163.84C73.728 0 0 73.728 0 163.84v696.32C0 950.272 73.728 1024 163.84 1024l491.52-0.032 0.192 0.032a30.72 30.72 0 0 0 0-61.44l-0.192 0.032v-0.032H163.84a102.496 102.496 0 0 1-102.4-102.4V163.84c0-56.48 45.92-102.4 102.4-102.4h696.32c56.48 0 102.4 45.92 102.4 102.4v488.544c0 0.384-0.192 0.64-0.192 0.992a30.72 30.72 0 0 0 61.44 0l-0.096-0.384H1024V163.84z m-31.904 606.752H835.52v-156.576a30.72 30.72 0 0 0-61.44 0v156.576h-156.576a30.72 30.72 0 0 0 0 61.44h156.576v156.576a30.72 30.72 0 0 0 61.44 0v-156.576h156.576a30.72 30.72 0 0 0 0-61.44z" fill="" ></path></symbol><symbol id="icon-checkbox" viewBox="0 0 1024 1024"><path d="M756.341948 867.328011H111.773992A111.064992 111.064992 0 0 1 0.788 756.342018V111.774062A111.064992 111.064992 0 0 1 111.773992 0.78807H686.629953a39.384997 39.384997 0 0 1 0 78.768995H111.773992a32.294998 32.294998 0 0 0-32.216997 32.216997v644.489956a32.294998 32.294998 0 0 0 32.216997 32.215998h644.489956a32.294998 32.294998 0 0 0 32.215998-32.216998V566.981031a39.384997 39.384997 0 0 1 78.769995 0v189.281987a111.064992 111.064992 0 0 1-110.907993 111.064993z" ></path><path d="M984.614933 1024H293.72998a39.384997 39.384997 0 0 1 0-78.769995h651.499955v-383.999973a39.384997 39.384997 0 0 1 78.769995 0v423.384971A39.384997 39.384997 0 0 1 984.614933 1024z m0-1023.99993a39.541997 39.541997 0 0 0-27.883998 11.499999L455.443969 512.788035a39.384997 39.384997 0 0 0 55.767996 55.767996L1012.499931 67.270065A39.384997 39.384997 0 0 0 984.614933 0.00007z" ></path><path d="M262.773982 278.528051a39.384997 39.384997 0 0 0-27.883998 67.189995l222.759985 222.759985a39.462997 39.462997 0 0 0 55.767996 0.078 39.384997 39.384997 0 0 0 0-55.689996L290.57998 290.02805a38.911997 38.911997 0 0 0-27.805998-11.499999z" ></path></symbol><symbol id="icon-slider" viewBox="0 0 1024 1024"><path d="M426.196 206c12.113 0 21.804 8.1 21.804 18s-9.812 18-21.804 18H85.804C73.812 242 64 233.9 64 224s9.812-18 21.804-18h340.392z m514.251 288c10.754 0 19.553 8.1 19.553 18s-8.799 18-19.553 18H425.553C414.799 530 406 521.9 406 512s8.799-18 19.553-18h514.894zM942 782c9.9 0 18 8.1 18 18s-8.1 18-18 18H578c-1.2 0-2.4-0.2-3.6-0.4C566.1 862.2 527 896 480 896c-53 0-96-43-96-96s43-96 96-96c47 0 86.1 33.8 94.4 78.4 1.2-0.3 2.4-0.4 3.6-0.4h364z m-462 78c33.1 0 60-26.9 60-60s-26.9-60-60-60-60 26.9-60 60 26.9 60 60 60z m96-540c-53 0-96-43-96-96s43-96 96-96c47 0 86.2 33.9 94.4 78.6 1.5-0.4 3-0.6 4.6-0.6h267c9.9 0 18 8.1 18 18s-8.1 18-18 18H675c-1.6 0-3.1-0.2-4.6-0.6-8.2 44.7-47.3 78.6-94.4 78.6z m0-156c-33.1 0-60 26.9-60 60s26.9 60 60 60 60-26.9 60-60-26.9-60-60-60zM332.206 782c10.887 0 19.794 8.1 19.794 18s-8.907 18-19.794 18H83.794C72.907 818 64 809.9 64 800s8.907-18 19.794-18h248.412zM278 416c53 0 96 43 96 96s-43 96-96 96c-46.9 0-85.9-33.6-94.3-78H82c-9.9 0-18-8.1-18-18s8.1-18 18-18h101.7c8.4-44.4 47.4-78 94.3-78z m0 156c33.1 0 60-26.9 60-60s-26.9-60-60-60-60 26.9-60 60 26.9 60 60 60z" ></path></symbol><symbol id="icon-txt" viewBox="0 0 1024 1024"><path d="M901.12 0H122.88C53.248 0 0 53.248 0 122.88v778.24c0 65.536 53.248 122.88 122.88 122.88h778.24c69.632 0 122.88-57.344 122.88-122.88V122.88c0-69.632-53.248-122.88-122.88-122.88z m81.92 901.12c0 45.056-36.864 81.92-81.92 81.92H122.88c-45.056 0-81.92-36.864-81.92-81.92V122.88c0-45.056 36.864-81.92 81.92-81.92h778.24c45.056 0 81.92 36.864 81.92 81.92v778.24z" fill="#666666" ></path><path d="M163.84 163.84c-20.48 0-40.96-20.48-40.96-40.96v81.92c0 20.48 20.48 40.96 40.96 40.96s40.96-20.48 40.96-40.96V163.84H163.84zM204.8 122.88v40.96h40.96c20.48 0 40.96-20.48 40.96-40.96s-20.48-40.96-40.96-40.96H163.84c20.48 0 40.96 20.48 40.96 40.96z" fill="#9EB9FD" ></path><path d="M163.84 163.84h40.96V122.88c0-20.48-20.48-40.96-40.96-40.96s-40.96 20.48-40.96 40.96 20.48 40.96 40.96 40.96z" fill="#9EB9FD" ></path><path d="M507.904 286.72H266.24c-12.288 0-20.48 8.192-20.48 20.48v8.192c0 12.288 8.192 20.48 20.48 20.48h221.184v-28.672c0-12.288 8.192-20.48 20.48-20.48zM757.76 286.72h-241.664c12.288 0 20.48 8.192 20.48 20.48v28.672h221.184c12.288 0 20.48-8.192 20.48-20.48v-8.192c0-12.288-8.192-20.48-20.48-20.48zM487.424 798.72c0 12.288 8.192 20.48 20.48 20.48h8.192c12.288 0 20.48-8.192 20.48-20.48V335.872h-49.152v462.848z" fill="#666666" ></path><path d="M516.096 286.72h-8.192c-12.288 0-20.48 8.192-20.48 20.48v28.672h49.152v-28.672c0-12.288-8.192-20.48-20.48-20.48z" fill="#666666" ></path></symbol><symbol id="icon-number" viewBox="0 0 1024 1024"><path d="M595.2 597.333333c-8.533333-4.266667-17.066667-2.133333-23.466667 4.266667l-32 32c-8.533333 8.533333-8.533333 21.333333 0 29.866667 6.4 6.4 17.066667 8.533333 25.6 4.266666v119.466667c0 12.8 8.533333 21.333333 21.333334 21.333333s21.333333-8.533333 21.333333-21.333333V618.666667c0-10.666667-6.4-19.2-12.8-21.333334zM797.866667 657.066667c0-38.4-29.866667-68.266667-68.266667-68.266667S661.333333 618.666667 661.333333 657.066667c0 12.8 8.533333 21.333333 21.333334 21.333333s21.333333-8.533333 21.333333-21.333333c0-14.933333 10.666667-25.6 25.6-25.6s25.6 10.666667 25.6 25.6c0 6.4-2.133333 12.8-6.4 17.066666L665.6 768c-6.4 6.4-6.4 14.933333-4.266667 23.466667 4.266667 8.533333 10.666667 12.8 19.2 12.8h93.866667c12.8 0 21.333333-8.533333 21.333333-21.333334s-8.533333-21.333333-21.333333-21.333333h-46.933333l51.2-57.6c12.8-14.933333 19.2-32 19.2-46.933333zM960 676.266667c8.533333-10.666667 12.8-23.466667 12.8-38.4 0-34.133333-27.733333-61.866667-61.866667-61.866667s-61.866667 27.733333-61.866666 61.866667c0 12.8 8.533333 21.333333 21.333333 21.333333s21.333333-8.533333 21.333333-21.333333c0-10.666667 8.533333-19.2 19.2-19.2s19.2 8.533333 19.2 19.2-8.533333 19.2-19.2 19.2c-12.8 0-21.333333 8.533333-21.333333 21.333333s8.533333 21.333333 21.333333 21.333333c19.2 0 34.133333 14.933333 34.133334 34.133334 0 19.2-14.933333 34.133333-34.133334 34.133333s-34.133333-14.933333-34.133333-34.133333c0-12.8-8.533333-21.333333-21.333333-21.333334s-21.333333 8.533333-21.333334 21.333334c0 42.666667 34.133333 76.8 76.8 76.8s76.8-34.133333 76.8-76.8c-2.133333-23.466667-12.8-42.666667-27.733333-57.6z" ></path><path d="M426.666667 725.333333H128V298.666667h768v149.333333c0 23.466667 19.2 42.666667 42.666667 42.666667s42.666667-19.2 42.666666-42.666667v-192c0-23.466667-19.2-42.666667-42.666666-42.666667H85.333333c-23.466667 0-42.666667 19.2-42.666666 42.666667v512c0 23.466667 19.2 42.666667 42.666666 42.666667h341.333334c23.466667 0 42.666667-19.2 42.666666-42.666667s-19.2-42.666667-42.666666-42.666667z" ></path><path d="M320 405.333333c12.8 0 21.333333-8.533333 21.333333-21.333333s-8.533333-21.333333-21.333333-21.333333h-85.333333c-12.8 0-21.333333 8.533333-21.333334 21.333333s8.533333 21.333333 21.333334 21.333333h21.333333v213.333334h-21.333333c-12.8 0-21.333333 8.533333-21.333334 21.333333s8.533333 21.333333 21.333334 21.333333h85.333333c12.8 0 21.333333-8.533333 21.333333-21.333333s-8.533333-21.333333-21.333333-21.333333h-21.333333V405.333333h21.333333z" ></path></symbol><symbol id="icon-grid" viewBox="0 0 1024 1024"><path d="M810.398678 895.365289H204.122975a115.094215 115.094215 0 0 1-115.094215-115.094215V242.713388a115.094215 115.094215 0 0 1 115.094215-115.094214h606.275703a115.094215 115.094215 0 0 1 115.094215 115.094214v537.557686a115.432727 115.432727 0 0 1-115.094215 115.094215zM204.122975 195.321653a47.391736 47.391736 0 0 0-47.391735 47.391735v537.557686a47.391736 47.391736 0 0 0 47.391735 47.391736h606.275703a47.391736 47.391736 0 0 0 47.391735-47.391736V242.713388a47.391736 47.391736 0 0 0-47.391735-47.391735z" ></path><path d="M348.667769 165.871074h67.702479v690.56529h-67.702479zM594.427769 165.871074h67.702479v690.56529h-67.702479z" ></path></symbol><symbol id="icon-shurukuang" viewBox="0 0 1024 1024"><path d="M1014.06208 132.352a129.792 129.792 0 0 1 7.68 25.6A156.672 156.672 0 0 1 1024.55808 185.856a83.456 83.456 0 0 1-5.888 30.208A86.784 86.784 0 0 1 998.95808 245.248c-9.216 9.472-17.664 17.408-25.6 25.6l-19.2 18.432c-6.4 6.4-12.032 11.776-17.664 16.384l-217.6-217.856 33.024-30.72c12.8-12.032 23.552-21.504 32-28.672A108.288 108.288 0 0 1 819.75808 9.472a102.4 102.4 0 0 1 34.048-4.608 128 128 0 0 1 32.768 5.12 175.36 175.36 0 0 1 27.392 10.496 221.184 221.184 0 0 1 54.016 44.8 257.536 257.536 0 0 1 46.08 67.072zM106.79808 700.672c4.608-4.608 15.616-15.616 32.512-33.28l64.256-64.512 84.992-84.992 94.208-94.464 250.368-250.368 217.856 218.88-250.368 250.368-93.184 94.464C476.46208 768 448.55808 794.88 423.47008 819.2l-61.696 62.464c-16.128 16.128-25.6 25.6-28.928 27.904-7.936 6.912-16.896 14.336-26.88 22.016a132.608 132.608 0 0 1-31.488 18.688 484.352 484.352 0 0 1-47.616 19.712c-20.992 7.936-43.008 15.36-65.792 22.784s-44.8 13.824-65.28 19.2-35.84 8.96-46.08 10.496c-20.992 2.304-34.816 0-41.984-9.216a55.296 55.296 0 0 1-5.632-43.264 386.304 386.304 0 0 1 11.008-47.104c5.888-20.48 12.032-41.984 18.688-64s12.8-42.496 19.2-61.184a250.368 250.368 0 0 1 15.104-38.4 190.976 190.976 0 0 1 15.616-29.696 195.84 195.84 0 0 1 25.6-29.696zM495.15008 921.6h104.192v102.4h-104.192z m211.712 0h103.936v102.4h-103.936z m212.48 0H1024.55808v102.4h-105.216z m0 0" ></path></symbol><symbol id="icon-select" viewBox="0 0 1024 1024"><path d="M787.472 1024H236.528A236.8 236.8 0 0 1 0 787.504V236.496A236.8 236.8 0 0 1 236.528 0h550.944a59.408 59.408 0 0 1 0 118.8H236.528a117.84 117.84 0 0 0-117.728 117.68v551.024a117.84 117.84 0 0 0 117.728 117.68h550.944a117.84 117.84 0 0 0 117.728-117.68V342.272a59.408 59.408 0 1 1 118.8 0v445.232A236.8 236.8 0 0 1 787.472 1024z" fill="#999999" ></path><path d="M745.92 443.568l-209.6 227.52a34.176 34.176 0 0 1-48.992 0l-209.6-227.52a40.16 40.16 0 0 1 24.496-66.4h419.744A33.6 33.6 0 0 1 753.6 401.6a37.28 37.28 0 0 1-7.68 41.968z" fill="#12ADA9" ></path></symbol><symbol id="icon-cascader" viewBox="0 0 1024 1024"><path d="M661.377376 411.069935V475.664516H314.175312v395.654882h557.144086V475.664516h-48.447312V411.069935h48.447312c35.674839 0 64.594581 28.919742 64.59458 64.594581v395.654882c0 35.674839-28.919742 64.594581-64.59458 64.59458h-557.144086c-35.674839 0-64.600086-28.919742-64.600086-64.59458V475.664516c0-35.674839 28.925247-64.594581 64.600086-64.594581h347.202064z m48.447312-322.983913c35.674839 0 64.600086 28.919742 64.600086 64.59458v403.731269c0 35.674839-28.925247 64.594581-64.600086 64.594581H362.622624v-64.594581h347.202064V152.680602h-557.144086v403.731269h48.447312V621.006452h-48.447312c-35.674839 0-64.594581-28.919742-64.59458-64.594581V152.680602C88.086022 117.005763 117.005763 88.086022 152.680602 88.086022h557.144086z" fill="#3A3A3A" ></path></symbol><symbol id="icon-tree-select" viewBox="0 0 1024 1024"><path d="M160 751.7h199.8c13.2 0.2 23.9 10.9 24 24.1 0.1 13.2-10.6 24-23.9 24.1H136.1c-13.1 0.1-23.9-10.4-24.1-23.5V319.9H88.1c-13.3 0-24.1-10.7-24.1-24V200c0-13.3 10.7-24.1 24-24.1H183.9c13.3 0 24.1 10.7 24.1 24V295.8c0 13.3-10.7 24.1-24 24.1h-24v175.9h199.8c13.2 0.2 23.9 10.9 24 24.1 0.1 13.2-10.6 24-23.9 24.1H160v207.7z m144-551.6c0-13.3 10.7-24.1 24-24.1h608c13.3 0 24.1 10.7 24.1 24V295.9c0 13.3-10.7 24.1-24 24.1h-608c-13.3 0-24.1-10.7-24.1-24V200.1z m176 272.1c-0.1-13.2 10.6-24 23.9-24.1H936c13.3 0 24.1 10.7 24.1 24V568c0.1 13.2-10.6 24-23.9 24.1H504.1c-13.3 0-24.1-10.7-24.1-24V472.2z m0 256c-0.1-13.2 10.6-24 23.9-24.1H936c13.3 0 24.1 10.7 24.1 24V824c0.1 13.2-10.5 24-23.6 24.1H504.1c-13.3 0-24.1-10.7-24.1-24V728.2z" ></path></symbol><symbol id="icon-tabs" viewBox="0 0 1024 1024"><path d="M376.6 494.8c5.1 4.8 7.9 11.7 7.6 18.7 0.4 7.1-2.5 14-7.6 18.7-5.3 4.7-12.3 7.2-19.3 6.9h-69.6v235.1c0.2 7.4-2.9 14.4-8.4 19.1-5.5 5.4-12.9 8.3-20.5 8.1-7.6 0.3-15.1-2.7-20.5-8.1-5.3-4.9-8.2-11.9-8.1-19.1v-235H161c-7.2 0.3-14.2-2.3-19.3-7.3-5.2-4.8-8-11.7-7.6-18.7-0.3-7 2.5-13.7 7.6-18.3 5.3-4.8 12.2-7.3 19.3-7h198.4c6.3 0 12.4 2.5 17.2 6.9z m215.7 70.1c5.2 5.1 8 12.2 7.7 19.6v189.9c0.3 7.2-2.5 14.2-7.7 19.1-4.8 5.3-11.7 8.3-18.9 8.1-7.1 0.1-13.9-2.7-18.9-7.7-4.8-5.2-7.6-12-7.6-19.1-18 19.5-42.9 30.8-69.2 31.4-19.7 0.3-38.9-5.3-55.5-15.9-17.2-10.7-31.2-26.1-40.3-44.4-19.3-40.4-19.3-87.6 0-128 8.9-18.4 22.9-33.8 40.3-44.4 16-10.5 34.8-16.1 53.9-15.9 26.3-0.1 51.8 9.8 71.2 27.7-0.2-7.3 2.6-14.4 7.7-19.6 10.6-10.2 27.2-10.2 37.8 0l-0.5-0.8z m-61.2 168.7c24.1-31.6 24.1-75.6 0-107.2-11.5-14.3-28.9-22.3-47.1-21.6-17.9-0.5-35.1 7.4-46.3 21.6-12.1 15-18.5 34-18.1 53.4-0.6 19.5 5.7 38.6 17.7 53.8 11.6 13.9 28.8 21.7 46.7 21.2 18.1 0.4 35.3-7.4 47.1-21.2zM835.4 573c17.1 10.7 31 25.9 40.3 44 9.9 19.8 14.9 41.8 14.5 64 0.4 22.3-4.6 44.4-14.5 64.4-9 18.3-23 33.7-40.3 44.4-16 10.6-34.8 16.1-53.9 15.9-13.9 0.2-27.7-2.9-40.3-9-11.6-5-22.1-12.2-31-21.2v2.8c0.2 7.2-2.5 14.2-7.5 19.4-5 5.1-11.9 8-19.1 7.9-7.1 0.3-14-2.5-18.9-7.7-5.2-5.1-8-12.2-7.6-19.6V497.2c-0.2-7.3 2.5-14.4 7.6-19.6 10.6-10.3 27.2-10.3 37.8 0 5.1 5.2 7.9 12.2 7.6 19.6v93.3c8-9.8 18-17.7 29.4-23.2 12.4-6.8 26.2-10.3 40.3-10.2 19.7-0.2 39 5.3 55.6 15.9z m-15.7 163c12.2-15.1 18.6-34.3 18.1-53.8 0.6-19.4-5.7-38.3-17.7-53.4-12-13.4-29-21.1-46.9-21.1s-34.9 7.7-46.9 21.1c-24.1 31.6-24.1 75.6 0 107.2 11.6 14.1 29 22.1 47.1 21.6 18.3-0.2 35.4-9.1 46.3-24v2.4zM708.1 253.5c-15.5 0-28-12.5-28-28V113.4c-0.1-8 0-15.9 0-23.8V77.9c0-15.5 12.5-28 28-28H886c17 0 32.9 6.6 45 18.6 11.3 11.3 17.8 26 18.6 41.8 0.1 0.7 0.1 1.4 0.1 2.1v113.2c0 15.5-12.5 28-28 28H708.1z m28-56h157.4v-84.1c0-2-0.8-3.9-2.2-5.4-1.4-1.4-3.4-2.2-5.4-2.2H736.1v91.7zM371.9 254.6c-15.5 0-28-12.5-28-28V78.9c0-15.4 12.5-27.9 27.9-28l213.4-1.1h0.1c17 0 32.9 6.6 45 18.6 12 12.1 18.6 28 18.6 45v113.2c0 15.5-12.5 28-28 28h-249z m28-56.1h193v-85.2c0-2-0.8-3.9-2.2-5.4-1.4-1.4-3.3-2.2-5.3-2.2l-185.5 0.9v91.9z" ></path><path d="M72.4 974.2c-17.3 0-33.5-6.7-45.7-18.7-12.2-12.1-18.9-28.1-18.9-45.1V113.6c0-35.2 29-63.8 64.6-63.8h169c17.3 0 33.5 6.6 45.7 18.7 12.2 12 18.9 28.1 18.9 45.1V283c0 2 0.8 3.9 2.3 5.4 1.5 1.4 3.4 2.2 5.4 2.2h637.9c17.3 0 33.5 6.6 45.7 18.7 12.2 12 18.9 28 18.9 45.1v556c0 17.1-6.7 33.1-18.9 45.1s-28.4 18.7-45.7 18.7H72.4z m7.7-860.6c-4.3 0-7.7 3.4-7.7 7.6v788.7c0 2 0.8 4 2.3 5.4 1.4 1.4 3.4 2.2 5.4 2.2h871.5c2.1 0 4-0.8 5.4-2.2 1.5-1.4 2.3-3.3 2.3-5.4V350.6c0-2-0.8-3.9-2.2-5.3-1.5-1.5-3.4-2.3-5.4-2.3h-638c-17.3 0-33.5-6.7-45.7-18.7-12.2-12-18.9-28.1-18.9-45.1V121.1c0-2-0.8-3.9-2.3-5.4-1.4-1.4-3.4-2.2-5.4-2.2H80.1z" ></path></symbol><symbol id="icon-slot" viewBox="0 0 1024 1024"><path d="M395.264 446.464l-42.496 45.568c-3.584 4.096-3.584 10.24 0 14.336l42.496 45.568c6.656 6.656 6.656 16.896-1.024 23.04-6.144 5.632-15.872 4.608-22.016-1.024l-53.76-53.76c-11.776-11.776-11.776-31.232 0-43.008l53.76-53.76c6.144-6.144 15.36-6.656 22.016-1.024 7.168 7.168 7.68 17.408 1.024 24.064zM616.448 234.496H282.624c-24.576 0-44.544 19.968-44.544 44.544v59.904c0 24.576 19.968 44.544 44.544 44.544h333.824c24.576 0 44.544-19.968 44.544-44.544V278.528c-0.512-24.064-20.48-44.032-44.544-44.032zM267.264 331.264v-46.08c0-12.288 9.728-22.016 22.016-22.016H609.28c12.288 0 22.016 9.728 22.016 22.016v46.08c0 12.288-9.728 22.016-22.016 22.016H289.792c-12.8 0.512-22.528-9.728-22.528-22.016zM622.08 634.88H288.768c-24.576 0-44.544 19.968-44.544 44.544v59.904c0 24.576 19.968 44.544 44.544 44.544h333.824c24.576 0 44.544-19.968 44.544-44.544v-59.904c-0.512-25.088-20.48-44.544-45.056-44.544z m-348.672 96.768v-46.08c0-12.288 9.728-22.016 22.016-22.016h320c12.288 0 22.016 9.728 22.016 22.016v46.08c0 12.288-9.728 22.016-22.016 22.016H295.424c-12.288 0-22.016-9.728-22.016-22.016z" fill="#4F4F4F" ></path><path d="M711.168 653.824v159.744c0 14.336-11.264 25.6-25.6 25.6H208.896c-14.336 0-25.6-11.264-25.6-25.6V202.24c0-14.336 11.264-25.6 25.6-25.6h476.672c14.336 0 25.6 11.264 25.6 25.6v162.816c0 9.728 7.68 17.408 17.408 17.408s17.408-7.68 17.408-17.408V199.168c0-31.232-25.088-56.32-56.32-56.32H206.848c-31.232 0-56.32 25.088-56.32 56.32v616.96c0 31.232 25.088 56.32 56.32 56.32h482.304c31.232 0 56.32-25.088 56.32-56.32v-162.304c0-9.728-7.68-17.408-17.408-17.408-9.216 0.512-16.896 8.192-16.896 17.408z" fill="#4F4F4F" ></path><path d="M880.128 425.984h-333.824c-24.576 0-44.544 19.968-44.544 44.544v59.904c0 24.576 19.968 44.544 44.544 44.544h333.824c24.576 0 44.544-19.968 44.544-44.544V470.016c0-24.064-19.968-44.032-44.544-44.032z m-348.672 96.768v-46.08c0-12.288 9.728-22.016 22.016-22.016h320c12.288 0 22.016 9.728 22.016 22.016v46.08c0 12.288-9.728 22.016-22.016 22.016h-320c-12.288 0.512-22.016-9.216-22.016-22.016z" fill="#515151" ></path></symbol><symbol id="icon-date-picker" viewBox="0 0 1024 1024"><path d="M674.7136 195.6864a25.6 25.6 0 0 1 25.1904 20.992l0.4096 4.608V281.6H819.2a76.8 76.8 0 0 1 76.4416 69.4272L896 358.4v409.6a76.8 76.8 0 0 1-76.8 76.8H204.8A76.8 76.8 0 0 1 128 768V358.4A76.8 76.8 0 0 1 204.8 281.6h128V221.2864a25.6 25.6 0 0 1 50.7904-4.608l0.4096 4.608V281.6h265.1136V221.2864a25.6 25.6 0 0 1 25.6-25.6z m170.0864 330.496h-665.6V768a25.6 25.6 0 0 0 20.992 25.1904L204.8 793.6h614.4a25.6 25.6 0 0 0 25.6-25.6v-241.8176zM649.1136 332.8H384v60.3136a25.6 25.6 0 0 1-50.7904 4.608l-0.4096-4.608V332.8H204.8a25.6 25.6 0 0 0-25.1904 20.992L179.2 358.4v116.5824h665.6V358.4a25.6 25.6 0 0 0-20.992-25.1904L819.2 332.8h-118.8864v60.3136a25.6 25.6 0 0 1-50.7904 4.608l-0.4096-4.608V332.8z" ></path></symbol><symbol id="icon-textarea" viewBox="0 0 1220 1024"><path d="M791.315692 24.654769h347.844923v68.056616h-136.11323v378.092307h-75.618462v-378.092307h-136.113231V24.654769z m3.780923 578.481231v78.769231H63.488v-78.769231h731.608615zM394.318769 54.902154L452.923077 18.983385l119.099077 168.251077L693.011692 18.983385l58.604308 35.918769-136.113231 192.827077 136.113231 190.936615-58.604308 35.918769-119.099077-168.251077-119.099077 168.251077-58.604307-35.918769 136.113231-190.936615-138.003693-192.827077zM18.116923 24.654769h347.844923v68.056616H229.848615v378.092307h-75.618461v-378.092307H18.116923V24.654769z m1196.662154 992.492308H63.488v-78.769231h1151.291077v78.769231z m0-176.443077H63.488v-78.769231h1151.291077v78.769231z" ></path></symbol></svg>', | ||
9 | + i = (i = document.getElementsByTagName('script'))[i.length - 1].getAttribute('data-injectcss'), | ||
10 | + s = function (c, h) { | ||
11 | + h.parentNode.insertBefore(c, h); | ||
12 | + }; | ||
13 | + if (i && !c.__iconfont__svg__cssinject__) { | ||
14 | + c.__iconfont__svg__cssinject__ = !0; | ||
15 | + try { | ||
16 | + document.write( | ||
17 | + '<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>', | ||
18 | + ); | ||
19 | + } catch (c) { | ||
20 | + console && console.log(c); | ||
21 | + } | ||
22 | + } | ||
23 | + | ||
24 | + function e() { | ||
25 | + v || ((v = !0), a()); | ||
26 | + } | ||
27 | + | ||
28 | + function z() { | ||
29 | + try { | ||
30 | + t.documentElement.doScroll('left'); | ||
31 | + } catch (c) { | ||
32 | + return void setTimeout(z, 50); | ||
33 | + } | ||
34 | + e(); | ||
35 | + } | ||
36 | + (h = function () { | ||
37 | + var c, h; | ||
38 | + ((h = document.createElement('div')).innerHTML = o), | ||
39 | + (o = null), | ||
40 | + (c = h.getElementsByTagName('svg')[0]) && | ||
41 | + (c.setAttribute('aria-hidden', 'true'), | ||
42 | + (c.style.position = 'absolute'), | ||
43 | + (c.style.width = 0), | ||
44 | + (c.style.height = 0), | ||
45 | + (c.style.overflow = 'hidden'), | ||
46 | + (h = c), | ||
47 | + (c = document.body).firstChild ? s(h, c.firstChild) : c.appendChild(h)); | ||
48 | + }), | ||
49 | + document.addEventListener | ||
50 | + ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState) | ||
51 | + ? setTimeout(h, 0) | ||
52 | + : ((l = function () { | ||
53 | + document.removeEventListener('DOMContentLoaded', l, !1), h(); | ||
54 | + }), | ||
55 | + document.addEventListener('DOMContentLoaded', l, !1)) | ||
56 | + : document.attachEvent && | ||
57 | + ((a = h), | ||
58 | + (t = c.document), | ||
59 | + (v = !1), | ||
60 | + z(), | ||
61 | + (t.onreadystatechange = function () { | ||
62 | + 'complete' == t.readyState && ((t.onreadystatechange = null), e()); | ||
63 | + })); | ||
64 | +})(window); |
src/views/form-design/components/VFormCreate/components/FormRender.vue
0 → 100644
1 | +<template> | ||
2 | + <!-- <component :is="layoutTag" v-bind="schema.colProps"> --> | ||
3 | + <template v-if="['Grid'].includes(schema.component)"> | ||
4 | + <Row class="grid-row"> | ||
5 | + <Col | ||
6 | + class="grid-col" | ||
7 | + v-for="(colItem, index) in schema.columns" | ||
8 | + :key="index" | ||
9 | + :span="colItem.span" | ||
10 | + > | ||
11 | + <FormRender | ||
12 | + v-for="(item, k) in colItem.children" | ||
13 | + :key="k" | ||
14 | + :schema="item" | ||
15 | + :formData="formData" | ||
16 | + :formConfig="formConfig" | ||
17 | + :setFormModel="setFormModel" | ||
18 | + /> | ||
19 | + </Col> | ||
20 | + </Row> | ||
21 | + </template> | ||
22 | + <VFormItem | ||
23 | + v-else | ||
24 | + :formConfig="formConfig" | ||
25 | + :schema="schema" | ||
26 | + :formData="formData" | ||
27 | + :setFormModel="setFormModel" | ||
28 | + @change="$emit('change', { schema: schema, value: $event })" | ||
29 | + @submit="$emit('submit', schema)" | ||
30 | + @reset="$emit('reset')" | ||
31 | + > | ||
32 | + <template | ||
33 | + v-if="schema.componentProps && schema.componentProps.slotName" | ||
34 | + #[schema.componentProps!.slotName] | ||
35 | + > | ||
36 | + <slot :name="schema.componentProps!.slotName"></slot> | ||
37 | + </template> | ||
38 | + </VFormItem> | ||
39 | + <!-- </component> --> | ||
40 | +</template> | ||
41 | +<script lang="ts"> | ||
42 | + import { defineComponent, PropType } from 'vue'; | ||
43 | + import { IVFormComponent, IFormConfig } from '../../../typings/v-form-component'; | ||
44 | + import VFormItem from '../../VFormItem/index.vue'; | ||
45 | + import { Row, Col } from 'ant-design-vue'; | ||
46 | + | ||
47 | + export default defineComponent({ | ||
48 | + name: 'FormRender', | ||
49 | + components: { | ||
50 | + VFormItem, | ||
51 | + Row, | ||
52 | + Col, | ||
53 | + }, | ||
54 | + props: { | ||
55 | + formData: { | ||
56 | + type: Object, | ||
57 | + default: () => ({}), | ||
58 | + }, | ||
59 | + schema: { | ||
60 | + type: Object as PropType<IVFormComponent>, | ||
61 | + default: () => ({}), | ||
62 | + }, | ||
63 | + formConfig: { | ||
64 | + type: Object as PropType<IFormConfig>, | ||
65 | + default: () => [] as IFormConfig[], | ||
66 | + }, | ||
67 | + setFormModel: { | ||
68 | + type: Function as PropType<(key: string, value: any) => void>, | ||
69 | + default: null, | ||
70 | + }, | ||
71 | + }, | ||
72 | + emits: ['change', 'submit', 'reset'], | ||
73 | + setup(_props) {}, | ||
74 | + }); | ||
75 | +</script> | ||
76 | + | ||
77 | +<style> | ||
78 | + .v-form-render-item { | ||
79 | + overflow: hidden; | ||
80 | + } | ||
81 | +</style> |
src/views/form-design/components/VFormCreate/index.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/29 | ||
4 | + * @Description: 表单渲染器,根据json生成表单 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="v-form-container"> | ||
8 | + <Form class="v-form-model" ref="eFormModel" :model="formModel" v-bind="formModelProps"> | ||
9 | + <Row> | ||
10 | + <!-- <component :is="wrapperComp"> --> | ||
11 | + <FormRender | ||
12 | + v-for="(schema, index) of noHiddenList" | ||
13 | + :key="index" | ||
14 | + :schema="schema" | ||
15 | + :formConfig="formConfig" | ||
16 | + :formData="formModelNew" | ||
17 | + @change="handleChange" | ||
18 | + :setFormModel="setFormModel" | ||
19 | + @submit="handleSubmit" | ||
20 | + @reset="resetFields" | ||
21 | + > | ||
22 | + <template v-if="schema && schema.componentProps" #[`schema.componentProps!.slotName`]> | ||
23 | + <slot | ||
24 | + :name="schema.componentProps!.slotName" | ||
25 | + v-bind="{ formModel: formModel, field: schema.field, schema }" | ||
26 | + ></slot> | ||
27 | + </template> | ||
28 | + </FormRender> | ||
29 | + <!-- </component> --> | ||
30 | + </Row> | ||
31 | + </Form> | ||
32 | + </div> | ||
33 | +</template> | ||
34 | +<script lang="ts"> | ||
35 | + import { computed, defineComponent, PropType, provide, ref, unref } from 'vue'; | ||
36 | + import FormRender from './components/FormRender.vue'; | ||
37 | + import { IFormConfig, AForm } from '../../typings/v-form-component'; | ||
38 | + import { Form, Row, Col } from 'ant-design-vue'; | ||
39 | + import { useFormInstanceMethods } from '../../hooks/useFormInstanceMethods'; | ||
40 | + import { IProps, IVFormMethods, useVFormMethods } from '../../hooks/useVFormMethods'; | ||
41 | + import { useVModel } from '@vueuse/core'; | ||
42 | + import { omit } from 'lodash-es'; | ||
43 | + | ||
44 | + export default defineComponent({ | ||
45 | + name: 'VFormCreate', | ||
46 | + components: { | ||
47 | + FormRender, | ||
48 | + Form, | ||
49 | + Row, | ||
50 | + }, | ||
51 | + props: { | ||
52 | + fApi: { | ||
53 | + type: Object, | ||
54 | + }, | ||
55 | + formModel: { | ||
56 | + type: Object, | ||
57 | + default: () => ({}), | ||
58 | + }, | ||
59 | + formConfig: { | ||
60 | + type: Object as PropType<IFormConfig>, | ||
61 | + required: true, | ||
62 | + }, | ||
63 | + }, | ||
64 | + emits: ['submit', 'change', 'update:fApi', 'update:formModel'], | ||
65 | + setup(props, context) { | ||
66 | + const wrapperComp = props.formConfig.layout == 'vertical' ? Col : Row; | ||
67 | + const { emit } = context; | ||
68 | + const eFormModel = ref<AForm | null>(null); | ||
69 | + | ||
70 | + const formModelNew = computed({ | ||
71 | + get: () => props.formModel, | ||
72 | + set: (value) => emit('update:formModel', value), | ||
73 | + }); | ||
74 | + | ||
75 | + const noHiddenList = computed(() => { | ||
76 | + return ( | ||
77 | + props.formConfig.schemas && | ||
78 | + props.formConfig.schemas.filter((item) => item.hidden !== true) | ||
79 | + ); | ||
80 | + }); | ||
81 | + | ||
82 | + const fApi = useVModel(props, 'fApi', emit); | ||
83 | + | ||
84 | + const { submit, validate, clearValidate, resetFields, validateField } = | ||
85 | + useFormInstanceMethods(props, formModelNew, context, eFormModel); | ||
86 | + | ||
87 | + const { linkOn, ...methods } = useVFormMethods( | ||
88 | + { formConfig: props.formConfig, formData: props.formModel } as unknown as IProps, | ||
89 | + context, | ||
90 | + eFormModel, | ||
91 | + { | ||
92 | + submit, | ||
93 | + validate, | ||
94 | + validateField, | ||
95 | + resetFields, | ||
96 | + clearValidate, | ||
97 | + }, | ||
98 | + ); | ||
99 | + | ||
100 | + fApi.value = methods; | ||
101 | + | ||
102 | + const handleChange = (_event) => { | ||
103 | + const { schema, value } = _event; | ||
104 | + const { field } = unref(schema); | ||
105 | + | ||
106 | + linkOn[field!]?.forEach((formItem) => { | ||
107 | + // console.log('handleChange', formItem, field, value); | ||
108 | + formItem.update?.(value, formItem, fApi.value as IVFormMethods); | ||
109 | + }); | ||
110 | + }; | ||
111 | + /** | ||
112 | + * 获取表单属性 | ||
113 | + */ | ||
114 | + const formModelProps = computed( | ||
115 | + () => omit(props.formConfig, ['disabled', 'labelWidth', 'schemas']) as Recordable, | ||
116 | + ); | ||
117 | + | ||
118 | + const handleSubmit = () => { | ||
119 | + submit(); | ||
120 | + }; | ||
121 | + | ||
122 | + provide('formModel', formModelNew); | ||
123 | + const setFormModel = (key, value) => { | ||
124 | + formModelNew.value[key] = value; | ||
125 | + }; | ||
126 | + | ||
127 | + provide<(key: String, value: any) => void>('setFormModelMethod', setFormModel); | ||
128 | + | ||
129 | + // 把祖先组件的方法项注入到子组件中,子组件可通过inject获取 | ||
130 | + return { | ||
131 | + eFormModel, | ||
132 | + submit, | ||
133 | + validate, | ||
134 | + validateField, | ||
135 | + resetFields, | ||
136 | + clearValidate, | ||
137 | + handleChange, | ||
138 | + formModelProps, | ||
139 | + handleSubmit, | ||
140 | + setFormModel, | ||
141 | + formModelNew, | ||
142 | + wrapperComp, | ||
143 | + noHiddenList, | ||
144 | + }; | ||
145 | + }, | ||
146 | + }); | ||
147 | +</script> | ||
148 | + | ||
149 | +<style lang="less" scoped> | ||
150 | + .v-form-model { | ||
151 | + overflow: hidden; | ||
152 | + } | ||
153 | +</style> |
src/views/form-design/components/VFormDesign/components/CodeModal.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/12/7 | ||
4 | + * @Description: 渲染代码 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <Modal | ||
8 | + title="代码" | ||
9 | + :footer="null" | ||
10 | + :visible="visible" | ||
11 | + @cancel="visible = false" | ||
12 | + wrapClassName="v-code-modal" | ||
13 | + style="top: 20px" | ||
14 | + width="850px" | ||
15 | + :destroyOnClose="true" | ||
16 | + > | ||
17 | + <PreviewCode :editorJson="editorVueJson" fileFormat="vue" /> | ||
18 | + </Modal> | ||
19 | +</template> | ||
20 | +<script lang="ts"> | ||
21 | + import { computed, defineComponent, reactive, toRefs } from 'vue'; | ||
22 | + import { formatRules, removeAttrs } from '../../../utils'; | ||
23 | + import PreviewCode from './PreviewCode.vue'; | ||
24 | + import { IFormConfig } from '../../../typings/v-form-component'; | ||
25 | + import { Modal } from 'ant-design-vue'; | ||
26 | + | ||
27 | + const codeVueFront = `<template> | ||
28 | + <div> | ||
29 | + <v-form-create | ||
30 | + :formConfig="formConfig" | ||
31 | + :formData="formData" | ||
32 | + v-model="fApi" | ||
33 | + /> | ||
34 | + <a-button @click="submit">提交</a-button> | ||
35 | + </div> | ||
36 | +</template> | ||
37 | +<script> | ||
38 | + | ||
39 | +export default { | ||
40 | + name: 'Demo', | ||
41 | + data () { | ||
42 | + return { | ||
43 | + fApi:{}, | ||
44 | + formData:{}, | ||
45 | + formConfig: `; | ||
46 | + /* eslint-disable */ | ||
47 | + let codeVueLast = ` | ||
48 | + } | ||
49 | + }, | ||
50 | + methods: { | ||
51 | + async submit() { | ||
52 | + const data = await this.fApi.submit() | ||
53 | + console.log(data) | ||
54 | + } | ||
55 | + } | ||
56 | +} | ||
57 | +<\/script>`; | ||
58 | + // | ||
59 | + export default defineComponent({ | ||
60 | + name: 'CodeModal', | ||
61 | + components: { PreviewCode, Modal }, | ||
62 | + setup() { | ||
63 | + const state = reactive({ | ||
64 | + visible: false, | ||
65 | + jsonData: {} as IFormConfig, | ||
66 | + }); | ||
67 | + | ||
68 | + const showModal = (formConfig: IFormConfig) => { | ||
69 | + formConfig.schemas && formatRules(formConfig.schemas); | ||
70 | + state.visible = true; | ||
71 | + state.jsonData = formConfig; | ||
72 | + }; | ||
73 | + | ||
74 | + const editorVueJson = computed(() => { | ||
75 | + return codeVueFront + JSON.stringify(removeAttrs(state.jsonData), null, '\t') + codeVueLast; | ||
76 | + }); | ||
77 | + | ||
78 | + return { ...toRefs(state), editorVueJson, showModal }; | ||
79 | + }, | ||
80 | + }); | ||
81 | +</script> |
src/views/form-design/components/VFormDesign/components/ComponentProps.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/26 | ||
4 | + * @Description: 组件属性控件 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="properties-content"> | ||
8 | + <div class="properties-body" v-if="formConfig.currentItem"> | ||
9 | + <Empty class="hint-box" v-if="!formConfig.currentItem.key" description="未选择组件" /> | ||
10 | + | ||
11 | + <Form label-align="left" layout="vertical"> | ||
12 | + <!-- 循环遍历渲染组件属性 --> | ||
13 | + | ||
14 | + <div v-if="formConfig.currentItem && formConfig.currentItem.componentProps"> | ||
15 | + <FormItem v-for="item in inputOptions" :key="item.name" :label="item.label"> | ||
16 | + <!-- 处理数组属性,placeholder --> | ||
17 | + | ||
18 | + <div v-if="item.children"> | ||
19 | + <component | ||
20 | + v-for="(child, index) of item.children" | ||
21 | + :key="index" | ||
22 | + v-bind="child.componentProps" | ||
23 | + :is="child.component" | ||
24 | + v-model:value="formConfig.currentItem.componentProps[item.name][index]" | ||
25 | + /> | ||
26 | + </div> | ||
27 | + <!-- 如果不是数组,则正常处理属性值 --> | ||
28 | + <component | ||
29 | + v-else | ||
30 | + class="component-prop" | ||
31 | + v-bind="item.componentProps" | ||
32 | + :is="item.component" | ||
33 | + v-model:value="formConfig.currentItem.componentProps[item.name]" | ||
34 | + /> | ||
35 | + </FormItem> | ||
36 | + <!-- </Row> --> | ||
37 | + <FormItem label="控制属性"> | ||
38 | + <Col v-for="item in controlOptions" :key="item.name"> | ||
39 | + <Checkbox | ||
40 | + v-if="showControlAttrs(item.includes)" | ||
41 | + v-bind="item.componentProps" | ||
42 | + v-model:checked="formConfig.currentItem.componentProps[item.name]" | ||
43 | + > | ||
44 | + {{ item.label }} | ||
45 | + </Checkbox> | ||
46 | + </Col> | ||
47 | + </FormItem> | ||
48 | + </div> | ||
49 | + <FormItem label="关联字段"> | ||
50 | + <Select | ||
51 | + mode="multiple" | ||
52 | + v-model:value="formConfig.currentItem['link']" | ||
53 | + :options="linkOptions" | ||
54 | + /> | ||
55 | + </FormItem> | ||
56 | + | ||
57 | + <FormItem | ||
58 | + label="选项" | ||
59 | + v-if=" | ||
60 | + [ | ||
61 | + 'Select', | ||
62 | + 'CheckboxGroup', | ||
63 | + 'RadioGroup', | ||
64 | + 'TreeSelect', | ||
65 | + 'Cascader', | ||
66 | + 'AutoComplete', | ||
67 | + ].includes(formConfig.currentItem.component) | ||
68 | + " | ||
69 | + > | ||
70 | + <FormOptions /> | ||
71 | + </FormItem> | ||
72 | + | ||
73 | + <FormItem label="栅格" v-if="['Grid'].includes(formConfig.currentItem.component)"> | ||
74 | + <FormOptions /> | ||
75 | + </FormItem> | ||
76 | + </Form> | ||
77 | + </div> | ||
78 | + </div> | ||
79 | +</template> | ||
80 | +<script lang="ts"> | ||
81 | + import { | ||
82 | + Empty, | ||
83 | + Input, | ||
84 | + Form, | ||
85 | + FormItem, | ||
86 | + Switch, | ||
87 | + Checkbox, | ||
88 | + Select, | ||
89 | + InputNumber, | ||
90 | + RadioGroup, | ||
91 | + } from 'ant-design-vue'; | ||
92 | + import RadioButtonGroup from '/@/components/Form/src/components/RadioButtonGroup.vue'; | ||
93 | + import { Col, Row } from 'ant-design-vue'; | ||
94 | + import { computed, defineComponent, ref, watch } from 'vue'; | ||
95 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
96 | + import { | ||
97 | + baseComponentControlAttrs, | ||
98 | + baseComponentAttrs, | ||
99 | + baseComponentCommonAttrs, | ||
100 | + componentPropsFuncs, | ||
101 | + } from '../../VFormDesign/config/componentPropsConfig'; | ||
102 | + import FormOptions from './FormOptions.vue'; | ||
103 | + import { formItemsForEach, remove } from '../../../utils'; | ||
104 | + import { IBaseFormAttrs } from '../config/formItemPropsConfig'; | ||
105 | + | ||
106 | + export default defineComponent({ | ||
107 | + name: 'ComponentProps', | ||
108 | + components: { | ||
109 | + FormOptions, | ||
110 | + Empty, | ||
111 | + Input, | ||
112 | + Form, | ||
113 | + FormItem, | ||
114 | + Switch, | ||
115 | + Checkbox, | ||
116 | + Select, | ||
117 | + InputNumber, | ||
118 | + RadioGroup, | ||
119 | + RadioButtonGroup, | ||
120 | + Col, | ||
121 | + Row, | ||
122 | + }, | ||
123 | + setup() { | ||
124 | + // 让compuated属性自动更新 | ||
125 | + // const dummyUpdate = ref(0); | ||
126 | + | ||
127 | + const allOptions = ref([] as Omit<IBaseFormAttrs, 'tag'>[]); | ||
128 | + const showControlAttrs = (includes: string[] | undefined) => { | ||
129 | + if (!includes) return true; | ||
130 | + return includes.includes(formConfig.value.currentItem!.component); | ||
131 | + }; | ||
132 | + | ||
133 | + const { formConfig } = useFormDesignState(); | ||
134 | + | ||
135 | + if (formConfig.value.currentItem) { | ||
136 | + formConfig.value.currentItem.componentProps = | ||
137 | + formConfig.value.currentItem.componentProps || {}; | ||
138 | + } | ||
139 | + | ||
140 | + watch( | ||
141 | + () => formConfig.value.currentItem?.field, | ||
142 | + (_newValue, oldValue) => { | ||
143 | + formConfig.value.schemas && | ||
144 | + formItemsForEach(formConfig.value.schemas, (item) => { | ||
145 | + if (item.link) { | ||
146 | + const index = item.link.findIndex((linkItem) => linkItem === oldValue); | ||
147 | + index !== -1 && remove(item.link, index); | ||
148 | + } | ||
149 | + }); | ||
150 | + }, | ||
151 | + ); | ||
152 | + | ||
153 | + watch( | ||
154 | + () => formConfig.value.currentItem && formConfig.value.currentItem.component, | ||
155 | + () => { | ||
156 | + allOptions.value = []; | ||
157 | + baseComponentControlAttrs.forEach((item) => { | ||
158 | + item.category = 'control'; | ||
159 | + if (!item.includes) { | ||
160 | + // 如果属性没有include,所有的控件都适用 | ||
161 | + | ||
162 | + allOptions.value.push(item); | ||
163 | + } else if (item.includes.includes(formConfig.value.currentItem!.component)) { | ||
164 | + // 如果有include,检查是否包含了当前控件类型 | ||
165 | + allOptions.value.push(item); | ||
166 | + } | ||
167 | + }); | ||
168 | + | ||
169 | + baseComponentCommonAttrs.forEach((item) => { | ||
170 | + item.category = 'input'; | ||
171 | + if (item.includes) { | ||
172 | + if (item.includes.includes(formConfig.value.currentItem!.component)) { | ||
173 | + allOptions.value.push(item); | ||
174 | + } | ||
175 | + } else if (item.exclude) { | ||
176 | + if (!item.exclude.includes(formConfig.value.currentItem!.component)) { | ||
177 | + allOptions.value.push(item); | ||
178 | + } | ||
179 | + } else { | ||
180 | + allOptions.value.push(item); | ||
181 | + } | ||
182 | + }); | ||
183 | + | ||
184 | + baseComponentAttrs[formConfig.value.currentItem!.component] && | ||
185 | + baseComponentAttrs[formConfig.value.currentItem!.component].forEach(async (item) => { | ||
186 | + if (item.component) { | ||
187 | + if (['Switch', 'Checkbox', 'Radio'].includes(item.component)) { | ||
188 | + item.category = 'control'; | ||
189 | + allOptions.value.push(item); | ||
190 | + } else { | ||
191 | + item.category = 'input'; | ||
192 | + allOptions.value.push(item); | ||
193 | + } | ||
194 | + } | ||
195 | + }); | ||
196 | + }, | ||
197 | + { | ||
198 | + immediate: true, | ||
199 | + }, | ||
200 | + ); | ||
201 | + // 控制性的选项 | ||
202 | + const controlOptions = computed(() => { | ||
203 | + return allOptions.value.filter((item) => { | ||
204 | + return item.category == 'control'; | ||
205 | + }); | ||
206 | + }); | ||
207 | + | ||
208 | + // 非控制性选择 | ||
209 | + const inputOptions = computed(() => { | ||
210 | + return allOptions.value.filter((item) => { | ||
211 | + return item.category == 'input'; | ||
212 | + }); | ||
213 | + }); | ||
214 | + | ||
215 | + watch( | ||
216 | + () => formConfig.value.currentItem!.componentProps, | ||
217 | + () => { | ||
218 | + const func = componentPropsFuncs[formConfig.value.currentItem!.component]; | ||
219 | + if (func) { | ||
220 | + func(formConfig.value.currentItem!.componentProps, allOptions.value); | ||
221 | + } | ||
222 | + }, | ||
223 | + { | ||
224 | + immediate: true, | ||
225 | + deep: true, | ||
226 | + }, | ||
227 | + ); | ||
228 | + const linkOptions = computed(() => { | ||
229 | + return ( | ||
230 | + formConfig.value.schemas && | ||
231 | + formConfig.value.schemas | ||
232 | + .filter((item) => item.key !== formConfig.value.currentItem!.key) | ||
233 | + .map(({ label, field }) => ({ label: label + '/' + field, value: field })) | ||
234 | + ); | ||
235 | + }); | ||
236 | + return { | ||
237 | + formConfig, | ||
238 | + showControlAttrs, | ||
239 | + linkOptions, | ||
240 | + controlOptions, | ||
241 | + inputOptions, | ||
242 | + }; | ||
243 | + }, | ||
244 | + }); | ||
245 | +</script> |
src/views/form-design/components/VFormDesign/components/FormItemColumnProps.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/24 | ||
4 | + * @Description: 表单项属性 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="properties-content"> | ||
8 | + <div class="properties-body" v-if="formConfig.currentItem"> | ||
9 | + <Empty class="hint-box" v-if="!formConfig.currentItem.key" description="未选择控件" /> | ||
10 | + <Form v-else label-align="left" layout="vertical"> | ||
11 | + <div v-for="item of baseItemColumnProps" :key="item.name"> | ||
12 | + <FormItem :label="item.label" v-if="showProps(item.exclude)"> | ||
13 | + <component | ||
14 | + v-if="formConfig.currentItem.colProps" | ||
15 | + class="component-props" | ||
16 | + v-bind="item.componentProps" | ||
17 | + :is="item.component" | ||
18 | + v-model:value="formConfig.currentItem.colProps[item.name]" | ||
19 | + /> | ||
20 | + </FormItem> | ||
21 | + </div> | ||
22 | + </Form> | ||
23 | + </div> | ||
24 | + </div> | ||
25 | +</template> | ||
26 | +<script lang="ts"> | ||
27 | + import { defineComponent } from 'vue'; | ||
28 | + import { baseItemColumnProps } from '../config/formItemPropsConfig'; | ||
29 | + | ||
30 | + import { Empty, Input, Form, FormItem, Switch, Checkbox, Select, Slider } from 'ant-design-vue'; | ||
31 | + import RuleProps from './RuleProps.vue'; | ||
32 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
33 | + import { isArray } from 'lodash-es'; | ||
34 | + | ||
35 | + export default defineComponent({ | ||
36 | + name: 'FormItemProps', | ||
37 | + components: { | ||
38 | + RuleProps, | ||
39 | + Empty, | ||
40 | + Input, | ||
41 | + Form, | ||
42 | + FormItem, | ||
43 | + Switch, | ||
44 | + Checkbox, | ||
45 | + Select, | ||
46 | + Slider, | ||
47 | + }, | ||
48 | + // props: {} as PropsOptions, | ||
49 | + | ||
50 | + setup() { | ||
51 | + const { formConfig } = useFormDesignState(); | ||
52 | + const showProps = (exclude: string[] | undefined) => { | ||
53 | + if (!exclude) { | ||
54 | + return true; | ||
55 | + } | ||
56 | + | ||
57 | + return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true; | ||
58 | + }; | ||
59 | + return { | ||
60 | + baseItemColumnProps, | ||
61 | + formConfig, | ||
62 | + showProps, | ||
63 | + }; | ||
64 | + }, | ||
65 | + }); | ||
66 | +</script> |
src/views/form-design/components/VFormDesign/components/FormItemProps.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/24 | ||
4 | + * @Description: 表单项属性,控件属性面板 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="properties-content"> | ||
8 | + <div class="properties-body" v-if="formConfig.currentItem?.itemProps"> | ||
9 | + <Empty class="hint-box" v-if="!formConfig.currentItem.key" description="未选择控件" /> | ||
10 | + <Form v-else label-align="left" layout="vertical"> | ||
11 | + <div v-for="item of baseFormItemProps" :key="item.name"> | ||
12 | + <FormItem :label="item.label" v-if="showProps(item.exclude)"> | ||
13 | + <component | ||
14 | + class="component-props" | ||
15 | + v-bind="item.componentProps" | ||
16 | + :is="item.component" | ||
17 | + v-model:value="formConfig.currentItem[item.name]" | ||
18 | + /> | ||
19 | + </FormItem> | ||
20 | + </div> | ||
21 | + <div v-for="item of advanceFormItemProps" :key="item.name"> | ||
22 | + <FormItem :label="item.label" v-if="showProps(item.exclude)"> | ||
23 | + <component | ||
24 | + class="component-props" | ||
25 | + v-bind="item.componentProps" | ||
26 | + :is="item.component" | ||
27 | + v-model:value="formConfig.currentItem.itemProps[item.name]" | ||
28 | + /> | ||
29 | + </FormItem> </div | ||
30 | + ><div v-for="item of advanceFormItemColProps" :key="item.name"> | ||
31 | + <FormItem :label="item.label" v-if="showProps(item.exclude)"> | ||
32 | + <component | ||
33 | + class="component-props" | ||
34 | + v-bind="item.componentProps" | ||
35 | + :is="item.component" | ||
36 | + v-model:value="formConfig.currentItem.itemProps[item.name]['span']" | ||
37 | + /> | ||
38 | + </FormItem> | ||
39 | + </div> | ||
40 | + <FormItem label="控制属性" v-if="controlPropsList.length"> | ||
41 | + <Col v-for="item of controlPropsList" :key="item.name"> | ||
42 | + <Checkbox v-model:checked="formConfig.currentItem.itemProps[item.name]"> | ||
43 | + {{ item.label }} | ||
44 | + </Checkbox> | ||
45 | + </Col> | ||
46 | + </FormItem> | ||
47 | + <FormItem label="是否必选" v-if="!['Grid'].includes(formConfig.currentItem.component)"> | ||
48 | + <Switch v-model:checked="formConfig.currentItem.itemProps['required']" /> | ||
49 | + <Input | ||
50 | + v-if="formConfig.currentItem.itemProps['required']" | ||
51 | + v-model:value="formConfig.currentItem.itemProps['message']" | ||
52 | + placeholder="请输入必选提示" | ||
53 | + /> | ||
54 | + </FormItem> | ||
55 | + <FormItem | ||
56 | + v-if="!['Grid'].includes(formConfig.currentItem.component)" | ||
57 | + label="校验规则" | ||
58 | + :class="{ 'form-rule-props': !!formConfig.currentItem.itemProps['rules'] }" | ||
59 | + > | ||
60 | + <RuleProps /> | ||
61 | + </FormItem> | ||
62 | + </Form> | ||
63 | + </div> | ||
64 | + </div> | ||
65 | +</template> | ||
66 | +<script lang="ts"> | ||
67 | + import { computed, defineComponent, watch } from 'vue'; | ||
68 | + import { | ||
69 | + baseFormItemControlAttrs, | ||
70 | + baseFormItemProps, | ||
71 | + advanceFormItemProps, | ||
72 | + advanceFormItemColProps, | ||
73 | + } from '../../VFormDesign/config/formItemPropsConfig'; | ||
74 | + | ||
75 | + import { | ||
76 | + Empty, | ||
77 | + Input, | ||
78 | + Form, | ||
79 | + FormItem, | ||
80 | + Switch, | ||
81 | + Checkbox, | ||
82 | + Select, | ||
83 | + Slider, | ||
84 | + Col, | ||
85 | + RadioGroup, | ||
86 | + } from 'ant-design-vue'; | ||
87 | + import RuleProps from './RuleProps.vue'; | ||
88 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
89 | + import { isArray } from 'lodash-es'; | ||
90 | + | ||
91 | + export default defineComponent({ | ||
92 | + name: 'FormItemProps', | ||
93 | + components: { | ||
94 | + RuleProps, | ||
95 | + Empty, | ||
96 | + Input, | ||
97 | + Form, | ||
98 | + FormItem, | ||
99 | + Switch, | ||
100 | + Checkbox, | ||
101 | + Select, | ||
102 | + Slider, | ||
103 | + Col, | ||
104 | + RadioGroup, | ||
105 | + }, | ||
106 | + // props: {} as PropsOptions, | ||
107 | + | ||
108 | + setup() { | ||
109 | + const { formConfig } = useFormDesignState(); | ||
110 | + | ||
111 | + watch( | ||
112 | + () => formConfig.value, | ||
113 | + () => { | ||
114 | + if (formConfig.value.currentItem) { | ||
115 | + formConfig.value.currentItem.itemProps = formConfig.value.currentItem.itemProps || {}; | ||
116 | + formConfig.value.currentItem.itemProps.labelCol = | ||
117 | + formConfig.value.currentItem.itemProps.labelCol || {}; | ||
118 | + formConfig.value.currentItem.itemProps.wrapperCol = | ||
119 | + formConfig.value.currentItem.itemProps.wrapperCol || {}; | ||
120 | + } | ||
121 | + }, | ||
122 | + { deep: true, immediate: true }, | ||
123 | + ); | ||
124 | + const showProps = (exclude: string[] | undefined) => { | ||
125 | + if (!exclude) { | ||
126 | + return true; | ||
127 | + } | ||
128 | + return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true; | ||
129 | + }; | ||
130 | + | ||
131 | + const controlPropsList = computed(() => { | ||
132 | + // console.log('const list2 = computed(() => {'); | ||
133 | + return baseFormItemControlAttrs.filter((item) => { | ||
134 | + return showProps(item.exclude); | ||
135 | + }); | ||
136 | + }); | ||
137 | + | ||
138 | + return { | ||
139 | + baseFormItemProps, | ||
140 | + advanceFormItemProps, | ||
141 | + advanceFormItemColProps, | ||
142 | + formConfig, | ||
143 | + controlPropsList, | ||
144 | + showProps, | ||
145 | + }; | ||
146 | + }, | ||
147 | + }); | ||
148 | +</script> |
src/views/form-design/components/VFormDesign/components/FormNode.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/19 | ||
4 | + * @Description: 拖拽节点控件 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div | ||
8 | + class="drag-move-box" | ||
9 | + @click.stop="handleSelectItem" | ||
10 | + :class="{ active: schema.key === formConfig.currentItem?.key }" | ||
11 | + > | ||
12 | + <div class="form-item-box"> | ||
13 | + <VFormItem :formConfig="formConfig" :schema="schema" /> | ||
14 | + </div> | ||
15 | + <div class="show-key-box"> | ||
16 | + {{ schema.label + (schema.field ? '/' + schema.field : '') }} | ||
17 | + </div> | ||
18 | + <FormNodeOperate :schema="schema" :currentItem="formConfig.currentItem" /> | ||
19 | + </div> | ||
20 | +</template> | ||
21 | +<script lang="ts"> | ||
22 | + import { defineComponent, reactive, toRefs, PropType } from 'vue'; | ||
23 | + import { IVFormComponent } from '../../../typings/v-form-component'; | ||
24 | + import FormNodeOperate from './FormNodeOperate.vue'; | ||
25 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
26 | + import VFormItem from '../../VFormItem/index.vue'; | ||
27 | + // import VFormItem from '../../VFormItem/vFormItem.vue'; | ||
28 | + export default defineComponent({ | ||
29 | + name: 'FormNode', | ||
30 | + components: { | ||
31 | + VFormItem, | ||
32 | + FormNodeOperate, | ||
33 | + }, | ||
34 | + props: { | ||
35 | + schema: { | ||
36 | + type: Object as PropType<IVFormComponent>, | ||
37 | + required: true, | ||
38 | + }, | ||
39 | + }, | ||
40 | + setup(props) { | ||
41 | + const { formConfig, formDesignMethods } = useFormDesignState(); | ||
42 | + const state = reactive({}); | ||
43 | + // 获取 formDesignMethods | ||
44 | + const handleSelectItem = () => { | ||
45 | + // 调用 formDesignMethods | ||
46 | + formDesignMethods.handleSetSelectItem(props.schema); | ||
47 | + }; | ||
48 | + return { | ||
49 | + ...toRefs(state), | ||
50 | + handleSelectItem, | ||
51 | + formConfig, | ||
52 | + }; | ||
53 | + }, | ||
54 | + }); | ||
55 | +</script> |
src/views/form-design/components/VFormDesign/components/FormNodeOperate.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/11 | ||
4 | + * @Description: 节点操作复制删除控件 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="copy-delete-box"> | ||
8 | + <a class="copy" :class="activeClass" @click.stop="handleCopy"> | ||
9 | + <Icon icon="ant-design:copy-outlined" /> | ||
10 | + </a> | ||
11 | + <a class="delete" :class="activeClass" @click.stop="handleDelete"> | ||
12 | + <Icon icon="ant-design:delete-outlined" /> | ||
13 | + </a> | ||
14 | + </div> | ||
15 | +</template> | ||
16 | + | ||
17 | +<script lang="ts"> | ||
18 | + import { computed, defineComponent } from 'vue'; | ||
19 | + import { IVFormComponent } from '../../../typings/v-form-component'; | ||
20 | + import { remove } from '../../../utils'; | ||
21 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
22 | + import Icon from '/@/components/Icon/index'; | ||
23 | + | ||
24 | + export default defineComponent({ | ||
25 | + name: 'FormNodeOperate', | ||
26 | + components: { | ||
27 | + Icon, | ||
28 | + }, | ||
29 | + props: { | ||
30 | + schema: { | ||
31 | + type: Object, | ||
32 | + default: () => ({}), | ||
33 | + }, | ||
34 | + currentItem: { | ||
35 | + type: Object, | ||
36 | + default: () => ({}), | ||
37 | + }, | ||
38 | + }, | ||
39 | + setup(props) { | ||
40 | + const { formConfig, formDesignMethods } = useFormDesignState(); | ||
41 | + const activeClass = computed(() => { | ||
42 | + return props.schema.key === props.currentItem.key ? 'active' : 'unactivated'; | ||
43 | + }); | ||
44 | + /** | ||
45 | + * 删除当前项 | ||
46 | + */ | ||
47 | + const handleDelete = () => { | ||
48 | + const traverse = (schemas: IVFormComponent[]) => { | ||
49 | + schemas.some((formItem, index) => { | ||
50 | + const { component, key } = formItem; | ||
51 | + // 处理栅格和标签页布局 | ||
52 | + ['Grid', 'Tabs'].includes(component) && | ||
53 | + formItem.columns?.forEach((item) => traverse(item.children)); | ||
54 | + if (key === props.currentItem.key) { | ||
55 | + let params: IVFormComponent = | ||
56 | + schemas.length === 1 | ||
57 | + ? { component: '' } | ||
58 | + : schemas.length - 1 > index | ||
59 | + ? schemas[index + 1] | ||
60 | + : schemas[index - 1]; | ||
61 | + formDesignMethods.handleSetSelectItem(params); | ||
62 | + remove(schemas, index); | ||
63 | + return true; | ||
64 | + } | ||
65 | + }); | ||
66 | + }; | ||
67 | + traverse(formConfig.value!.schemas); | ||
68 | + }; | ||
69 | + | ||
70 | + const handleCopy = () => { | ||
71 | + formDesignMethods.handleCopy(); | ||
72 | + }; | ||
73 | + return { activeClass, handleDelete, handleCopy }; | ||
74 | + }, | ||
75 | + }); | ||
76 | +</script> |
src/views/form-design/components/VFormDesign/components/FormOptions.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <div v-if="['Grid'].includes(formConfig.currentItem!.component)"> | ||
4 | + <div v-for="(item, index) of formConfig.currentItem!['columns']" :key="index"> | ||
5 | + <div class="options-box"> | ||
6 | + <Input v-model:value="item.span" class="options-value" /> | ||
7 | + <a class="options-delete" @click="deleteGridOptions(index)"> | ||
8 | + <!-- <a-icon type="delete" /> --> | ||
9 | + <Icon icon="ant-design:delete-outlined" /> | ||
10 | + </a> | ||
11 | + </div> | ||
12 | + </div> | ||
13 | + <a @click="addGridOptions"> | ||
14 | + <Icon icon="ant-design:file-add-outlined" /> | ||
15 | + 添加栅格 | ||
16 | + </a> | ||
17 | + </div> | ||
18 | + <div v-else> | ||
19 | + <div v-for="(item, index) of formConfig.currentItem!.componentProps![key]" :key="index"> | ||
20 | + <div class="options-box"> | ||
21 | + <Input v-model:value="item.label" /> | ||
22 | + <Input v-model:value="item.value" class="options-value" /> | ||
23 | + <a class="options-delete" @click="deleteOptions(index)"> | ||
24 | + <!-- <a-icon type="delete" /> --> | ||
25 | + <Icon icon="ant-design:delete-outlined" /> | ||
26 | + </a> | ||
27 | + </div> | ||
28 | + </div> | ||
29 | + <a @click="addOptions"> | ||
30 | + <Icon icon="ant-design:file-add-outlined" /> | ||
31 | + 添加选项 | ||
32 | + </a> | ||
33 | + </div> | ||
34 | + </div> | ||
35 | +</template> | ||
36 | + | ||
37 | +<script lang="ts"> | ||
38 | + import { defineComponent, reactive, toRefs } from 'vue'; | ||
39 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
40 | + import { remove } from '../../../utils'; | ||
41 | + import message from '../../../utils/message'; | ||
42 | + import { Input } from 'ant-design-vue'; | ||
43 | + import Icon from '/@/components/Icon/index'; | ||
44 | + export default defineComponent({ | ||
45 | + name: 'FormOptions', | ||
46 | + components: { Input, Icon }, | ||
47 | + // props: {}, | ||
48 | + setup() { | ||
49 | + const state = reactive({}); | ||
50 | + const { formConfig } = useFormDesignState(); | ||
51 | + const key = formConfig.value.currentItem?.component === 'TreeSelect' ? 'treeData' : 'options'; | ||
52 | + const addOptions = () => { | ||
53 | + if (!formConfig.value.currentItem?.componentProps?.[key]) | ||
54 | + formConfig.value.currentItem!.componentProps![key] = []; | ||
55 | + const len = formConfig.value.currentItem?.componentProps?.[key].length + 1; | ||
56 | + formConfig.value.currentItem!.componentProps![key].push({ | ||
57 | + label: `选项${len}`, | ||
58 | + value: '' + len, | ||
59 | + }); | ||
60 | + }; | ||
61 | + const deleteOptions = (index: number) => { | ||
62 | + remove(formConfig.value.currentItem?.componentProps?.[key], index); | ||
63 | + }; | ||
64 | + | ||
65 | + const addGridOptions = () => { | ||
66 | + formConfig.value.currentItem?.['columns']?.push({ | ||
67 | + span: 12, | ||
68 | + children: [], | ||
69 | + }); | ||
70 | + }; | ||
71 | + const deleteGridOptions = (index: number) => { | ||
72 | + if (index === 0) return message.warning('请至少保留一个栅格'); | ||
73 | + | ||
74 | + remove(formConfig.value.currentItem!['columns']!, index); | ||
75 | + }; | ||
76 | + return { | ||
77 | + ...toRefs(state), | ||
78 | + formConfig, | ||
79 | + addOptions, | ||
80 | + deleteOptions, | ||
81 | + key, | ||
82 | + deleteGridOptions, | ||
83 | + addGridOptions, | ||
84 | + }; | ||
85 | + }, | ||
86 | + }); | ||
87 | +</script> | ||
88 | + | ||
89 | +<style lang="less" scoped> | ||
90 | + .options-box { | ||
91 | + display: flex; | ||
92 | + align-items: center; | ||
93 | + margin-bottom: 5px; | ||
94 | + | ||
95 | + .options-value { | ||
96 | + margin: 0 8px; | ||
97 | + } | ||
98 | + | ||
99 | + .options-delete { | ||
100 | + width: 30px; | ||
101 | + height: 30px; | ||
102 | + flex-shrink: 0; | ||
103 | + line-height: 30px; | ||
104 | + text-align: center; | ||
105 | + border-radius: 50%; | ||
106 | + background: #f5f5f5; | ||
107 | + color: #666; | ||
108 | + | ||
109 | + &:hover { | ||
110 | + background: #ff4d4f; | ||
111 | + } | ||
112 | + } | ||
113 | + } | ||
114 | +</style> |
src/views/form-design/components/VFormDesign/components/FormProps.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/23 | ||
4 | + * @Description: 右侧属性面板控件 表单属性面板 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="properties-content"> | ||
8 | + <Form class="properties-body" label-align="left" layout="vertical"> | ||
9 | + <!-- <e-upload v-model="fileList"></e-upload>--> | ||
10 | + | ||
11 | + <FormItem label="表单布局"> | ||
12 | + <RadioGroup button-style="solid" v-model:value="formConfig.layout"> | ||
13 | + <RadioButton value="horizontal">水平</RadioButton> | ||
14 | + <RadioButton value="vertical" :disabled="formConfig.labelLayout === 'Grid'"> | ||
15 | + 垂直 | ||
16 | + </RadioButton> | ||
17 | + <RadioButton value="inline" :disabled="formConfig.labelLayout === 'Grid'"> | ||
18 | + 行内 | ||
19 | + </RadioButton> | ||
20 | + </RadioGroup> | ||
21 | + </FormItem> | ||
22 | + | ||
23 | + <!-- <Row> --> | ||
24 | + <FormItem label="标签布局"> | ||
25 | + <RadioGroup | ||
26 | + buttonStyle="solid" | ||
27 | + v-model:value="formConfig.labelLayout" | ||
28 | + @change="lableLayoutChange" | ||
29 | + > | ||
30 | + <RadioButton value="flex">固定</RadioButton> | ||
31 | + <RadioButton value="Grid" :disabled="formConfig.layout !== 'horizontal'"> | ||
32 | + 栅格 | ||
33 | + </RadioButton> | ||
34 | + </RadioGroup> | ||
35 | + </FormItem> | ||
36 | + <!-- </Row> --> | ||
37 | + <FormItem label="标签宽度(px)" v-show="formConfig.labelLayout === 'flex'"> | ||
38 | + <InputNumber | ||
39 | + :style="{ width: '100%' }" | ||
40 | + v-model:value="formConfig.labelWidth" | ||
41 | + :min="0" | ||
42 | + :step="1" | ||
43 | + /> | ||
44 | + </FormItem> | ||
45 | + <div v-if="formConfig.labelLayout === 'Grid'"> | ||
46 | + <FormItem label="labelCol"> | ||
47 | + <Slider v-model:value="formConfig.labelCol!.span" :max="24" /> | ||
48 | + </FormItem> | ||
49 | + <FormItem label="wrapperCol"> | ||
50 | + <Slider v-model:value="formConfig.wrapperCol!.span" :max="24" /> | ||
51 | + </FormItem> | ||
52 | + | ||
53 | + <FormItem label="标签对齐"> | ||
54 | + <RadioGroup button-style="solid" v-model:value="formConfig.labelAlign"> | ||
55 | + <RadioButton value="left">靠左</RadioButton> | ||
56 | + <RadioButton value="right">靠右</RadioButton> | ||
57 | + </RadioGroup> | ||
58 | + </FormItem> | ||
59 | + | ||
60 | + <FormItem label="控件大小"> | ||
61 | + <RadioGroup button-style="solid" v-model:value="formConfig.size"> | ||
62 | + <RadioButton value="default">默认</RadioButton> | ||
63 | + <RadioButton value="small">小</RadioButton> | ||
64 | + <RadioButton value="large">大</RadioButton> | ||
65 | + </RadioGroup> | ||
66 | + </FormItem> | ||
67 | + </div> | ||
68 | + <FormItem label="表单属性"> | ||
69 | + <Col | ||
70 | + ><Checkbox v-model:checked="formConfig.colon" v-if="formConfig.layout == 'horizontal'" | ||
71 | + >label后面显示冒号</Checkbox | ||
72 | + ></Col | ||
73 | + > | ||
74 | + <Col><Checkbox v-model:checked="formConfig.disabled">禁用</Checkbox></Col> | ||
75 | + <Col><Checkbox v-model:checked="formConfig.hideRequiredMark">隐藏必选标记</Checkbox></Col> | ||
76 | + </FormItem> | ||
77 | + </Form> | ||
78 | + </div> | ||
79 | +</template> | ||
80 | +<script lang="ts"> | ||
81 | + import { defineComponent } from 'vue'; | ||
82 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
83 | + import { InputNumber, Slider, Checkbox, Col, RadioChangeEvent } from 'ant-design-vue'; | ||
84 | + // import RadioButtonGroup from '/@/components/RadioButtonGroup.vue'; | ||
85 | + import { Form, FormItem, Radio } from 'ant-design-vue'; | ||
86 | + export default defineComponent({ | ||
87 | + name: 'FormProps', | ||
88 | + components: { | ||
89 | + InputNumber, | ||
90 | + Slider, | ||
91 | + Checkbox, | ||
92 | + // RadioButtonGroup, | ||
93 | + RadioGroup: Radio.Group, | ||
94 | + RadioButton: Radio.Button, | ||
95 | + Form, | ||
96 | + FormItem, | ||
97 | + Col, | ||
98 | + }, | ||
99 | + setup() { | ||
100 | + // const labelColspan = computed(()=>) | ||
101 | + const { formConfig } = useFormDesignState(); | ||
102 | + | ||
103 | + formConfig.value = formConfig.value || { | ||
104 | + labelCol: { span: 24 }, | ||
105 | + wrapperCol: { span: 24 }, | ||
106 | + }; | ||
107 | + | ||
108 | + const lableLayoutChange = (e: RadioChangeEvent) => { | ||
109 | + if (e.target.value === 'Grid') { | ||
110 | + formConfig.value.layout = 'horizontal'; | ||
111 | + } | ||
112 | + }; | ||
113 | + | ||
114 | + return { formConfig, lableLayoutChange }; | ||
115 | + }, | ||
116 | + }); | ||
117 | +</script> |
src/views/form-design/components/VFormDesign/components/ImportJsonModal.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/12/7 | ||
4 | + * @Description: 导入JSON模板 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <Modal | ||
8 | + title="JSON数据" | ||
9 | + :visible="visible" | ||
10 | + @ok="handleImportJson" | ||
11 | + @cancel="handleCancel" | ||
12 | + cancelText="关闭" | ||
13 | + :destroyOnClose="true" | ||
14 | + wrapClassName="v-code-modal" | ||
15 | + style="top: 20px" | ||
16 | + :width="850" | ||
17 | + > | ||
18 | + <p class="hint-box">导入格式如下:</p> | ||
19 | + <div class="v-json-box"> | ||
20 | + <!-- <CodeEditor style="height: 100%" ref="myEditor" v-model="json"></CodeEditor> --> | ||
21 | + <CodeEditor v-model:value="json" ref="myEditor" :mode="MODE.JSON" /> | ||
22 | + </div> | ||
23 | + | ||
24 | + <template #footer> | ||
25 | + <a-button @click="handleCancel">取消</a-button> | ||
26 | + <Upload | ||
27 | + class="upload-button" | ||
28 | + :beforeUpload="beforeUpload" | ||
29 | + :showUploadList="false" | ||
30 | + accept="application/json" | ||
31 | + > | ||
32 | + <a-button type="primary">导入json文件</a-button> | ||
33 | + </Upload> | ||
34 | + <a-button type="primary" @click="handleImportJson">确定</a-button> | ||
35 | + </template> | ||
36 | + </Modal> | ||
37 | +</template> | ||
38 | +<script lang="ts"> | ||
39 | + import { defineComponent, reactive, toRefs } from 'vue'; | ||
40 | + // import message from '../../../utils/message'; | ||
41 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
42 | + // import { codemirror } from 'vue-codemirror-lite'; | ||
43 | + import { IFormConfig } from '../../../typings/v-form-component'; | ||
44 | + import { formItemsForEach, generateKey } from '../../../utils'; | ||
45 | + import { CodeEditor, MODE } from '/@/components/CodeEditor'; | ||
46 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
47 | + import { Upload, Modal } from 'ant-design-vue'; | ||
48 | + | ||
49 | + export default defineComponent({ | ||
50 | + name: 'ImportJsonModal', | ||
51 | + components: { | ||
52 | + CodeEditor, | ||
53 | + Upload, | ||
54 | + Modal, | ||
55 | + }, | ||
56 | + setup() { | ||
57 | + const { createMessage } = useMessage(); | ||
58 | + | ||
59 | + const state = reactive({ | ||
60 | + visible: false, | ||
61 | + json: `{ | ||
62 | + "schemas": [ | ||
63 | + { | ||
64 | + "component": "input", | ||
65 | + "label": "输入框", | ||
66 | + "field": "input_2", | ||
67 | + "span": 24, | ||
68 | + "props": { | ||
69 | + "type": "text" | ||
70 | + } | ||
71 | + } | ||
72 | + ], | ||
73 | + "layout": "horizontal", | ||
74 | + "labelLayout": "flex", | ||
75 | + "labelWidth": 100, | ||
76 | + "labelCol": {}, | ||
77 | + "wrapperCol": {} | ||
78 | +}`, | ||
79 | + jsonData: { | ||
80 | + schemas: {}, | ||
81 | + config: {}, | ||
82 | + }, | ||
83 | + handleSetSelectItem: null, | ||
84 | + }); | ||
85 | + const { formDesignMethods } = useFormDesignState(); | ||
86 | + const handleCancel = () => { | ||
87 | + state.visible = false; | ||
88 | + }; | ||
89 | + const showModal = () => { | ||
90 | + state.visible = true; | ||
91 | + }; | ||
92 | + const handleImportJson = () => { | ||
93 | + // 导入JSON | ||
94 | + console.log(state.json); | ||
95 | + try { | ||
96 | + const editorJsonData = JSON.parse(state.json) as IFormConfig; | ||
97 | + editorJsonData.schemas && | ||
98 | + formItemsForEach(editorJsonData.schemas, (formItem) => { | ||
99 | + generateKey(formItem); | ||
100 | + }); | ||
101 | + formDesignMethods.setFormConfig({ | ||
102 | + ...editorJsonData, | ||
103 | + activeKey: 1, | ||
104 | + currentItem: { component: '' }, | ||
105 | + }); | ||
106 | + handleCancel(); | ||
107 | + createMessage.success('导入成功'); | ||
108 | + } catch { | ||
109 | + createMessage.error('导入失败,数据格式不对'); | ||
110 | + } | ||
111 | + }; | ||
112 | + const beforeUpload = (e: File) => { | ||
113 | + // 通过json文件导入 | ||
114 | + const reader = new FileReader(); | ||
115 | + reader.readAsText(e); | ||
116 | + reader.onload = function () { | ||
117 | + state.json = this.result as string; | ||
118 | + handleImportJson(); | ||
119 | + }; | ||
120 | + return false; | ||
121 | + }; | ||
122 | + | ||
123 | + return { | ||
124 | + handleImportJson, | ||
125 | + beforeUpload, | ||
126 | + handleCancel, | ||
127 | + showModal, | ||
128 | + ...toRefs(state), | ||
129 | + MODE, | ||
130 | + }; | ||
131 | + }, | ||
132 | + }); | ||
133 | +</script> | ||
134 | + | ||
135 | +<style lang="less" scoped> | ||
136 | + .upload-button { | ||
137 | + margin: 0 10px; | ||
138 | + } | ||
139 | +</style> |
src/views/form-design/components/VFormDesign/components/JsonModal.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/23 | ||
4 | + * @Description: 渲染JSON数据 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <Modal | ||
8 | + title="JSON数据" | ||
9 | + :footer="null" | ||
10 | + :visible="visible" | ||
11 | + @cancel="handleCancel" | ||
12 | + :destroyOnClose="true" | ||
13 | + wrapClassName="v-code-modal" | ||
14 | + style="top: 20px" | ||
15 | + width="850px" | ||
16 | + > | ||
17 | + <PreviewCode :editorJson="editorJson" /> | ||
18 | + </Modal> | ||
19 | +</template> | ||
20 | +<script lang="ts"> | ||
21 | + import { computed, defineComponent, reactive, toRefs } from 'vue'; | ||
22 | + import PreviewCode from './PreviewCode.vue'; | ||
23 | + import { IFormConfig } from '../../../typings/v-form-component'; | ||
24 | + import { formatRules, removeAttrs } from '../../../utils'; | ||
25 | + import { Modal } from 'ant-design-vue'; | ||
26 | + | ||
27 | + export default defineComponent({ | ||
28 | + name: 'JsonModal', | ||
29 | + components: { | ||
30 | + PreviewCode, | ||
31 | + Modal, | ||
32 | + }, | ||
33 | + emits: ['cancel'], | ||
34 | + setup(_props, { emit }) { | ||
35 | + const state = reactive<{ | ||
36 | + visible: boolean; | ||
37 | + jsonData: IFormConfig; | ||
38 | + }>({ | ||
39 | + visible: false, // 控制json数据弹框显示 | ||
40 | + jsonData: {} as IFormConfig, // json数据 | ||
41 | + }); | ||
42 | + /** | ||
43 | + * 显示Json数据弹框 | ||
44 | + * @param jsonData | ||
45 | + */ | ||
46 | + const showModal = (jsonData: IFormConfig) => { | ||
47 | + formatRules(jsonData.schemas); | ||
48 | + state.jsonData = jsonData; | ||
49 | + state.visible = true; | ||
50 | + }; | ||
51 | + | ||
52 | + // 计算json数据 | ||
53 | + const editorJson = computed(() => { | ||
54 | + return JSON.stringify(removeAttrs(state.jsonData), null, '\t'); | ||
55 | + }); | ||
56 | + | ||
57 | + // 关闭弹框 | ||
58 | + const handleCancel = () => { | ||
59 | + state.visible = false; | ||
60 | + emit('cancel'); | ||
61 | + }; | ||
62 | + | ||
63 | + return { ...toRefs(state), editorJson, handleCancel, showModal }; | ||
64 | + }, | ||
65 | + }); | ||
66 | +</script> |
src/views/form-design/components/VFormDesign/components/LayoutItem.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/19 | ||
4 | + * @Description: 表单项布局控件 | ||
5 | + * 千万不要在template下面的第一行加注释,因为这里拖动的第一个元素 | ||
6 | +--> | ||
7 | + | ||
8 | +<template> | ||
9 | + <Col v-bind="colPropsComputed"> | ||
10 | + <template v-if="['Grid'].includes(schema.component)"> | ||
11 | + <div | ||
12 | + class="grid-box" | ||
13 | + :class="{ active: schema.key === currentItem.key }" | ||
14 | + @click.stop="handleSetSelectItem(schema)" | ||
15 | + > | ||
16 | + <Row class="grid-row" v-bind="schema.componentProps"> | ||
17 | + <Col | ||
18 | + class="grid-col" | ||
19 | + v-for="(colItem, index) in schema.columns" | ||
20 | + :key="index" | ||
21 | + :span="colItem.span" | ||
22 | + > | ||
23 | + <!-- <div class="draggable-box"> --> | ||
24 | + <!-- <div class="list-main"> --> | ||
25 | + <draggable | ||
26 | + class="list-main draggable-box" | ||
27 | + :component-data="{ name: 'list', tag: 'div', type: 'transition-group' }" | ||
28 | + v-bind="{ | ||
29 | + group: 'form-draggable', | ||
30 | + ghostClass: 'moving', | ||
31 | + animation: 180, | ||
32 | + handle: '.drag-move', | ||
33 | + }" | ||
34 | + item-key="key" | ||
35 | + v-model="colItem.children" | ||
36 | + @start="$emit('dragStart', $event, colItem.children)" | ||
37 | + @add="$emit('handleColAdd', $event, colItem.children)" | ||
38 | + > | ||
39 | + <!-- <transition-group tag="div" name="list" class="list-main"> --> | ||
40 | + <template #item="{ element }"> | ||
41 | + <LayoutItem | ||
42 | + class="drag-move" | ||
43 | + :schema="element" | ||
44 | + :current-item="currentItem" | ||
45 | + @handle-copy="$emit('handle-copy')" | ||
46 | + @handle-delete="$emit('handle-delete')" | ||
47 | + /> | ||
48 | + </template> | ||
49 | + <!-- </transition-group> --> | ||
50 | + </draggable> | ||
51 | + <!-- </div> --> | ||
52 | + <!-- </div> --> | ||
53 | + </Col> | ||
54 | + </Row> | ||
55 | + <FormNodeOperate :schema="schema" :currentItem="currentItem" /> | ||
56 | + </div> | ||
57 | + </template> | ||
58 | + <FormNode | ||
59 | + v-else | ||
60 | + :key="schema.key" | ||
61 | + :schema="schema" | ||
62 | + :current-item="currentItem" | ||
63 | + @handle-copy="$emit('handle-copy')" | ||
64 | + @handle-delete="$emit('handle-delete')" | ||
65 | + /> | ||
66 | + </Col> | ||
67 | +</template> | ||
68 | +<script lang="ts"> | ||
69 | + import { computed, defineComponent, PropType, reactive, toRefs } from 'vue'; | ||
70 | + import draggable from 'vuedraggable'; | ||
71 | + import FormNode from './FormNode.vue'; | ||
72 | + import FormNodeOperate from './FormNodeOperate.vue'; | ||
73 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
74 | + import { IVFormComponent } from '../../../typings/v-form-component'; | ||
75 | + import { Row, Col } from 'ant-design-vue'; | ||
76 | + export default defineComponent({ | ||
77 | + name: 'LayoutItem', | ||
78 | + components: { | ||
79 | + FormNode, | ||
80 | + FormNodeOperate, | ||
81 | + draggable, | ||
82 | + Row, | ||
83 | + Col, | ||
84 | + }, | ||
85 | + props: { | ||
86 | + schema: { | ||
87 | + type: Object as PropType<IVFormComponent>, | ||
88 | + required: true, | ||
89 | + }, | ||
90 | + currentItem: { | ||
91 | + type: Object, | ||
92 | + required: true, | ||
93 | + }, | ||
94 | + }, | ||
95 | + emits: ['dragStart', 'handleColAdd', 'handle-copy', 'handle-delete'], | ||
96 | + setup(props) { | ||
97 | + const { | ||
98 | + formDesignMethods: { handleSetSelectItem }, | ||
99 | + formConfig, | ||
100 | + } = useFormDesignState(); | ||
101 | + const state = reactive({}); | ||
102 | + const colPropsComputed = computed(() => { | ||
103 | + const { colProps = {} } = props.schema; | ||
104 | + return colProps; | ||
105 | + }); | ||
106 | + | ||
107 | + const list1 = computed(() => props.schema.columns); | ||
108 | + | ||
109 | + // 计算布局元素,水平模式下为ACol,非水平模式下为div | ||
110 | + const layoutTag = computed(() => { | ||
111 | + return formConfig.value.layout === 'horizontal' ? 'Col' : 'div'; | ||
112 | + }); | ||
113 | + | ||
114 | + return { | ||
115 | + ...toRefs(state), | ||
116 | + colPropsComputed, | ||
117 | + handleSetSelectItem, | ||
118 | + layoutTag, | ||
119 | + list1, | ||
120 | + }; | ||
121 | + }, | ||
122 | + }); | ||
123 | +</script> | ||
124 | +<style lang="less"> | ||
125 | + @import url(../styles/variable.less); | ||
126 | + | ||
127 | + .layout-width { | ||
128 | + width: 100%; | ||
129 | + } | ||
130 | + | ||
131 | + .hidden-item { | ||
132 | + background-color: rgb(240, 191, 195); | ||
133 | + //opacity: 0.5; | ||
134 | + } | ||
135 | +</style> |
src/views/form-design/components/VFormDesign/components/PreviewCode.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <div class="v-json-box"> | ||
4 | + <CodeEditor :value="editorJson" ref="myEditor" :mode="MODE.JSON" /> | ||
5 | + </div> | ||
6 | + <div class="copy-btn-box"> | ||
7 | + <a-button | ||
8 | + @click="handleCopyJson" | ||
9 | + type="primary" | ||
10 | + class="copy-btn" | ||
11 | + data-clipboard-action="copy" | ||
12 | + :data-clipboard-text="editorJson" | ||
13 | + > | ||
14 | + 复制数据 | ||
15 | + </a-button> | ||
16 | + <a-button @click="handleExportJson" type="primary">导出代码</a-button> | ||
17 | + </div> | ||
18 | + </div> | ||
19 | +</template> | ||
20 | + | ||
21 | +<script lang="ts"> | ||
22 | + import { defineComponent, reactive, toRefs, unref } from 'vue'; | ||
23 | + import { CodeEditor, MODE } from '/@/components/CodeEditor'; | ||
24 | + | ||
25 | + import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; | ||
26 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
27 | + export default defineComponent({ | ||
28 | + name: 'PreviewCode', | ||
29 | + components: { | ||
30 | + CodeEditor, | ||
31 | + }, | ||
32 | + props: { | ||
33 | + fileFormat: { | ||
34 | + type: String, | ||
35 | + default: 'json', | ||
36 | + }, | ||
37 | + editorJson: { | ||
38 | + type: String, | ||
39 | + default: '', | ||
40 | + }, | ||
41 | + }, | ||
42 | + setup(props) { | ||
43 | + const state = reactive({ | ||
44 | + visible: false, | ||
45 | + }); | ||
46 | + | ||
47 | + const exportData = (data: string, fileName = `file.${props.fileFormat}`) => { | ||
48 | + let content = 'data:text/csv;charset=utf-8,'; | ||
49 | + content += data; | ||
50 | + const encodedUri = encodeURI(content); | ||
51 | + const actions = document.createElement('a'); | ||
52 | + actions.setAttribute('href', encodedUri); | ||
53 | + actions.setAttribute('download', fileName); | ||
54 | + actions.click(); | ||
55 | + }; | ||
56 | + | ||
57 | + const handleExportJson = () => { | ||
58 | + exportData(props.editorJson); | ||
59 | + }; | ||
60 | + const { clipboardRef, copiedRef } = useCopyToClipboard(); | ||
61 | + const { createMessage } = useMessage(); | ||
62 | + | ||
63 | + const handleCopyJson = () => { | ||
64 | + // 复制数据 | ||
65 | + const value = props.editorJson; | ||
66 | + if (!value) { | ||
67 | + createMessage.warning('代码为空!'); | ||
68 | + return; | ||
69 | + } | ||
70 | + clipboardRef.value = value; | ||
71 | + if (unref(copiedRef)) { | ||
72 | + createMessage.warning('复制成功!'); | ||
73 | + } | ||
74 | + }; | ||
75 | + | ||
76 | + return { | ||
77 | + ...toRefs(state), | ||
78 | + exportData, | ||
79 | + handleCopyJson, | ||
80 | + handleExportJson, | ||
81 | + MODE, | ||
82 | + }; | ||
83 | + }, | ||
84 | + }); | ||
85 | +</script> | ||
86 | + | ||
87 | +<style lang="less" scoped> | ||
88 | + // modal复制按钮样式 | ||
89 | + .copy-btn-box { | ||
90 | + padding-top: 8px; | ||
91 | + text-align: center; | ||
92 | + | ||
93 | + .copy-btn { | ||
94 | + margin-right: 8px; | ||
95 | + } | ||
96 | + } | ||
97 | +</style> |
src/views/form-design/components/VFormDesign/components/RuleProps.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/25 | ||
4 | + * @Description: 正则校验选项组件 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="rule-props-content"> | ||
8 | + <Form v-if="formConfig.currentItem && formConfig.currentItem['rules']"> | ||
9 | + <div | ||
10 | + v-for="(item, index) of formConfig.currentItem['rules']" | ||
11 | + :key="index" | ||
12 | + class="rule-props-item" | ||
13 | + > | ||
14 | + <Icon | ||
15 | + icon="ant-design:close-circle-filled" | ||
16 | + class="rule-props-item-close" | ||
17 | + @click="removeRule(index)" | ||
18 | + /> | ||
19 | + <FormItem label="正则" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }"> | ||
20 | + <AutoComplete | ||
21 | + v-model:value="item.pattern" | ||
22 | + placeholder="请输入正则表达式" | ||
23 | + :dataSource="patternDataSource" | ||
24 | + /> | ||
25 | + </FormItem> | ||
26 | + <FormItem label="文案" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }"> | ||
27 | + <Input v-model:value="item.message" placeholder="请输入提示文案" /> | ||
28 | + </FormItem> | ||
29 | + </div> | ||
30 | + </Form> | ||
31 | + <a @click="addRules"> | ||
32 | + <Icon icon="ant-design:file-add-outlined" /> | ||
33 | + 添加正则 | ||
34 | + </a> | ||
35 | + </div> | ||
36 | +</template> | ||
37 | +<script lang="ts"> | ||
38 | + import { ref, defineComponent } from 'vue'; | ||
39 | + import { remove } from '../../../utils'; | ||
40 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
41 | + import { isArray } from 'lodash-es'; | ||
42 | + import { Form, FormItem, AutoComplete, Input } from 'ant-design-vue'; | ||
43 | + import Icon from '/@/components/Icon'; | ||
44 | + | ||
45 | + export default defineComponent({ | ||
46 | + name: 'RuleProps', | ||
47 | + components: { | ||
48 | + Form, | ||
49 | + FormItem, | ||
50 | + AutoComplete, | ||
51 | + Input, | ||
52 | + Icon, | ||
53 | + }, | ||
54 | + setup() { | ||
55 | + // 获取祖先组件的状态 | ||
56 | + const { formConfig } = useFormDesignState(); | ||
57 | + // 抽离 currentItem | ||
58 | + /** | ||
59 | + * 添加正则校验,判断当前组件的rules是不是数组,如果不是数组,使用set方法重置成数组,然后添加正则校验 | ||
60 | + */ | ||
61 | + const addRules = () => { | ||
62 | + if (!isArray(formConfig.value.currentItem!.rules)) | ||
63 | + formConfig.value.currentItem!['rules'] = []; | ||
64 | + formConfig.value.currentItem!.rules?.push({ pattern: '', message: '' }); | ||
65 | + }; | ||
66 | + /** | ||
67 | + * 删除正则校验,当正则规则为0时,删除rules属性 | ||
68 | + * @param index {number} 需要删除的规则下标 | ||
69 | + */ | ||
70 | + const removeRule = (index: number) => { | ||
71 | + remove(formConfig.value.currentItem!.rules as Array<any>, index); | ||
72 | + if (formConfig.value.currentItem!.rules?.length === 0) | ||
73 | + delete formConfig.value.currentItem!['rules']; | ||
74 | + }; | ||
75 | + | ||
76 | + const patternDataSource = ref([ | ||
77 | + { | ||
78 | + value: '/^(?:(?:\\+|00)86)?1[3-9]\\d{9}$/', | ||
79 | + text: '手机号码', | ||
80 | + }, | ||
81 | + { | ||
82 | + value: '/^((ht|f)tps?:\\/\\/)?[\\w-]+(\\.[\\w-]+)+:\\d{1,5}\\/?$/', | ||
83 | + text: '网址带端口号', | ||
84 | + }, | ||
85 | + { | ||
86 | + value: | ||
87 | + '/^(((ht|f)tps?):\\/\\/)?[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&:/~+#-\\(\\)]*[\\w@?^=%&/~+#-\\(\\)])?$/', | ||
88 | + text: '网址带参数', | ||
89 | + }, | ||
90 | + { | ||
91 | + value: '/^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/', | ||
92 | + text: '统一社会信用代码', | ||
93 | + }, | ||
94 | + { | ||
95 | + value: '/^(s[hz]|S[HZ])(000[\\d]{3}|002[\\d]{3}|300[\\d]{3}|600[\\d]{3}|60[\\d]{4})$/', | ||
96 | + text: '股票代码', | ||
97 | + }, | ||
98 | + { | ||
99 | + value: '/^([a-f\\d]{32}|[A-F\\d]{32})$/', | ||
100 | + text: 'md5格式(32位)', | ||
101 | + }, | ||
102 | + { | ||
103 | + value: '/^[a-f\\d]{4}(?:[a-f\\d]{4}-){4}[a-f\\d]{12}$/i', | ||
104 | + text: 'GUID/UUID', | ||
105 | + }, | ||
106 | + { | ||
107 | + value: '/^\\d+(?:\\.\\d+){2}$/', | ||
108 | + text: '版本号(x.y.z)格式', | ||
109 | + }, | ||
110 | + { | ||
111 | + value: | ||
112 | + '/^https?:\\/\\/(.+\\/)+.+(\\.(swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|mp4))$/i', | ||
113 | + text: '视频链接地址', | ||
114 | + }, | ||
115 | + { | ||
116 | + value: '/^https?:\\/\\/(.+\\/)+.+(\\.(gif|png|jpg|jpeg|webp|svg|psd|bmp|tif))$/i', | ||
117 | + text: '图片链接地址', | ||
118 | + }, | ||
119 | + { | ||
120 | + value: '/^-?\\d+(,\\d{3})*(\\.\\d{1,2})?$/', | ||
121 | + text: '数字/货币金额(支持负数、千分位分隔符)', | ||
122 | + }, | ||
123 | + { | ||
124 | + value: | ||
125 | + '/(?:^[1-9]([0-9]+)?(?:\\.[0-9]{1,2})?$)|(?:^(?:0)$)|(?:^[0-9]\\.[0-9](?:[0-9])?$)/', | ||
126 | + text: '数字/货币金额', | ||
127 | + }, | ||
128 | + { | ||
129 | + value: '/^[1-9]\\d{9,29}$/', | ||
130 | + text: '银行卡号', | ||
131 | + }, | ||
132 | + { | ||
133 | + value: '/^(?:[\u4e00-\u9fa5·]{2,16})$/', | ||
134 | + text: '中文姓名', | ||
135 | + }, | ||
136 | + { | ||
137 | + value: '/(^[a-zA-Z][a-zA-Z\\s]{0,20}[a-zA-Z]$)/', | ||
138 | + text: '英文姓名', | ||
139 | + }, | ||
140 | + { | ||
141 | + value: | ||
142 | + '/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z](?:((\\d{5}[A-HJK])|([A-HJK][A-HJ-NP-Z0-9][0-9]{4}))|[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳])$/', | ||
143 | + text: '车牌号(新能源)', | ||
144 | + }, | ||
145 | + { | ||
146 | + value: | ||
147 | + '/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]$/', | ||
148 | + text: '车牌号(非新能源)', | ||
149 | + }, | ||
150 | + { | ||
151 | + value: | ||
152 | + '/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/', | ||
153 | + text: '车牌号(新能源+非新能源)', | ||
154 | + }, | ||
155 | + { | ||
156 | + value: | ||
157 | + '/^(([^<>()[\\]\\\\.,;:\\s@"]+(\\.[^<>()[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/', | ||
158 | + text: 'email(邮箱)', | ||
159 | + }, | ||
160 | + { | ||
161 | + value: '/^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$/', | ||
162 | + text: '座机', | ||
163 | + }, | ||
164 | + { | ||
165 | + value: | ||
166 | + '/^[1-9]\\d{5}(?:18|19|20)\\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\\d|30|31)\\d{3}[\\dXx]$/', | ||
167 | + text: '身份证号', | ||
168 | + }, | ||
169 | + { | ||
170 | + value: | ||
171 | + '/(^[EeKkGgDdSsPpHh]\\d{8}$)|(^(([Ee][a-fA-F])|([DdSsPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))\\d{7}$)/', | ||
172 | + text: '护照', | ||
173 | + }, | ||
174 | + { | ||
175 | + value: | ||
176 | + '/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/', | ||
177 | + text: '中文汉字', | ||
178 | + }, | ||
179 | + { | ||
180 | + value: '/^\\d+\\.\\d+$/', | ||
181 | + text: '小数', | ||
182 | + }, | ||
183 | + { | ||
184 | + value: '/^\\d{1,}$/', | ||
185 | + text: '数字', | ||
186 | + }, | ||
187 | + { | ||
188 | + value: '/^[1-9][0-9]{4,10}$/', | ||
189 | + text: 'qq号', | ||
190 | + }, | ||
191 | + { | ||
192 | + value: '/^[A-Za-z0-9]+$/', | ||
193 | + text: '数字字母组合', | ||
194 | + }, | ||
195 | + { | ||
196 | + value: '/^[a-zA-Z]+$/', | ||
197 | + text: '英文字母', | ||
198 | + }, | ||
199 | + { | ||
200 | + value: '/^[a-z]+$/', | ||
201 | + text: '小写英文字母', | ||
202 | + }, | ||
203 | + { | ||
204 | + value: '/^[A-Z]+$/', | ||
205 | + text: '大写英文字母', | ||
206 | + }, | ||
207 | + { | ||
208 | + value: '/^[a-zA-Z0-9_-]{4,16}$/', | ||
209 | + text: '用户名校验,4到16位(字母,数字,下划线,减号)', | ||
210 | + }, | ||
211 | + { | ||
212 | + value: '/^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/', | ||
213 | + text: '16进制颜色', | ||
214 | + }, | ||
215 | + { | ||
216 | + value: '/^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/', | ||
217 | + text: '微信号', | ||
218 | + }, | ||
219 | + { | ||
220 | + value: '/^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\\d{4}$/', | ||
221 | + text: '邮政编码(中国)', | ||
222 | + }, | ||
223 | + { | ||
224 | + value: '/^[^A-Za-z]*$/', | ||
225 | + text: '不能包含字母', | ||
226 | + }, | ||
227 | + { | ||
228 | + value: '/^\\+?[1-9]\\d*$/', | ||
229 | + text: '正整数,不包含0', | ||
230 | + }, | ||
231 | + { | ||
232 | + value: '/^-[1-9]\\d*$/', | ||
233 | + text: '负整数,不包含0', | ||
234 | + }, | ||
235 | + { | ||
236 | + value: '/^-?[0-9]\\d*$/', | ||
237 | + text: '整数', | ||
238 | + }, | ||
239 | + { | ||
240 | + value: '/^(-?\\d+)(\\.\\d+)?$/', | ||
241 | + text: '浮点数', | ||
242 | + }, | ||
243 | + { | ||
244 | + value: '/^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$/', | ||
245 | + text: 'email(支持中文邮箱)', | ||
246 | + }, | ||
247 | + ]); | ||
248 | + | ||
249 | + return { addRules, removeRule, formConfig, patternDataSource }; | ||
250 | + }, | ||
251 | + }); | ||
252 | +</script> | ||
253 | + | ||
254 | +<style lang="less" scoped> | ||
255 | + :deep(.icon) { | ||
256 | + width: 1em; | ||
257 | + height: 1em; | ||
258 | + vertical-align: -0.15em; | ||
259 | + fill: currentColor; | ||
260 | + overflow: hidden; | ||
261 | + } | ||
262 | + | ||
263 | + .rule-props-content { | ||
264 | + :deep(.ant-form-item) { | ||
265 | + margin-bottom: 0; | ||
266 | + } | ||
267 | + | ||
268 | + .rule-props-item { | ||
269 | + position: relative; | ||
270 | + background-color: #f0eded; | ||
271 | + padding: 3px 2px; | ||
272 | + border-radius: 5px; | ||
273 | + margin-bottom: 5px; | ||
274 | + | ||
275 | + :deep(.ant-form-item) { | ||
276 | + border: 0 !important; | ||
277 | + } | ||
278 | + | ||
279 | + &-close { | ||
280 | + position: absolute; | ||
281 | + top: -5px; | ||
282 | + right: -5px; | ||
283 | + color: #ccc; | ||
284 | + cursor: pointer; | ||
285 | + border-radius: 7px; | ||
286 | + background-color: #a3a0a0; | ||
287 | + z-index: 999; | ||
288 | + | ||
289 | + &:hover { | ||
290 | + color: #00c; | ||
291 | + } | ||
292 | + } | ||
293 | + } | ||
294 | + } | ||
295 | +</style> |
src/views/form-design/components/VFormDesign/config/componentPropsConfig.ts
0 → 100644
1 | +import { IBaseFormAttrs } from './formItemPropsConfig'; | ||
2 | + | ||
3 | +interface IBaseComponentProps { | ||
4 | + [key: string]: IBaseFormAttrs[]; | ||
5 | +} | ||
6 | + | ||
7 | +type BaseFormAttrs = Omit<IBaseFormAttrs, 'tag'>; | ||
8 | + | ||
9 | +export const baseComponentControlAttrs: Omit<IBaseFormAttrs, 'tag'>[] = [ | ||
10 | + { | ||
11 | + // 没有disabled属性的控件不能作为form控件 | ||
12 | + name: 'disabled', | ||
13 | + label: '禁用', | ||
14 | + }, | ||
15 | + { | ||
16 | + // 没有disabled属性的控件不能作为form控件 | ||
17 | + name: 'autofocus', | ||
18 | + label: '自动获取焦点', | ||
19 | + includes: [ | ||
20 | + 'Input', | ||
21 | + 'Select', | ||
22 | + 'InputTextArea', | ||
23 | + 'InputNumber', | ||
24 | + 'DatePicker', | ||
25 | + 'RangePicker', | ||
26 | + 'MonthPicker', | ||
27 | + 'TimePicker', | ||
28 | + 'Cascader', | ||
29 | + 'TreeSelect', | ||
30 | + 'Switch', | ||
31 | + 'AutoComplete', | ||
32 | + 'Slider', | ||
33 | + ], | ||
34 | + }, | ||
35 | + { | ||
36 | + name: 'allowClear', | ||
37 | + label: '可清除', | ||
38 | + includes: [ | ||
39 | + 'Input', | ||
40 | + 'Select', | ||
41 | + 'InputTextArea', | ||
42 | + 'InputNumber', | ||
43 | + 'DatePicker', | ||
44 | + 'RangePicker', | ||
45 | + 'MonthPicker', | ||
46 | + 'TimePicker', | ||
47 | + 'Cascader', | ||
48 | + 'TreeSelect', | ||
49 | + 'AutoComplete', | ||
50 | + ], | ||
51 | + }, | ||
52 | + { name: 'fullscreen', label: '全屏', includes: ['Calendar'] }, | ||
53 | + { | ||
54 | + name: 'showSearch', | ||
55 | + label: '可搜索', | ||
56 | + includes: ['Select', 'TreeSelect', 'Cascader', 'Transfer'], | ||
57 | + }, | ||
58 | + { | ||
59 | + name: 'showTime', | ||
60 | + label: '显示时间', | ||
61 | + includes: ['DatePicker', 'RangePicker', 'MonthPicker'], | ||
62 | + }, | ||
63 | + { | ||
64 | + name: 'range', | ||
65 | + label: '双向滑动', | ||
66 | + includes: [], | ||
67 | + }, | ||
68 | + { | ||
69 | + name: 'allowHalf', | ||
70 | + label: '允许半选', | ||
71 | + includes: ['Rate'], | ||
72 | + }, | ||
73 | + { | ||
74 | + name: 'multiple', | ||
75 | + label: '多选', | ||
76 | + includes: ['Select', 'TreeSelect', 'Upload'], | ||
77 | + }, | ||
78 | + { | ||
79 | + name: 'directory', | ||
80 | + label: '文件夹', | ||
81 | + includes: ['Upload'], | ||
82 | + }, | ||
83 | + { | ||
84 | + name: 'withCredentials', | ||
85 | + label: '携带cookie', | ||
86 | + includes: ['Upload'], | ||
87 | + }, | ||
88 | + { | ||
89 | + name: 'bordered', | ||
90 | + label: '是否有边框', | ||
91 | + includes: ['Select', 'Input'], | ||
92 | + }, | ||
93 | + { | ||
94 | + name: 'defaultActiveFirstOption', | ||
95 | + label: '高亮第一个选项', | ||
96 | + component: 'Checkbox', | ||
97 | + includes: ['Select', 'AutoComplete'], | ||
98 | + }, | ||
99 | + { | ||
100 | + name: 'dropdownMatchSelectWidth', | ||
101 | + label: '下拉菜单和选择器同宽', | ||
102 | + component: 'Checkbox', | ||
103 | + includes: ['Select', 'TreeSelect', 'AutoComplete'], | ||
104 | + }, | ||
105 | +]; | ||
106 | + | ||
107 | +//共用属性 | ||
108 | +export const baseComponentCommonAttrs: Omit<IBaseFormAttrs, 'tag'>[] = [ | ||
109 | + { | ||
110 | + name: 'size', | ||
111 | + label: '尺寸', | ||
112 | + component: 'RadioGroup', | ||
113 | + componentProps: { | ||
114 | + options: [ | ||
115 | + { | ||
116 | + label: '默认', | ||
117 | + value: 'default', | ||
118 | + }, | ||
119 | + { | ||
120 | + label: '大', | ||
121 | + value: 'large', | ||
122 | + }, | ||
123 | + { | ||
124 | + label: '小', | ||
125 | + value: 'small', | ||
126 | + }, | ||
127 | + ], | ||
128 | + }, | ||
129 | + includes: ['InputNumber', 'Input', 'Cascader', 'Button'], | ||
130 | + }, | ||
131 | + { | ||
132 | + name: 'placeholder', | ||
133 | + label: '占位符', | ||
134 | + component: 'Input', | ||
135 | + componentProps: { | ||
136 | + placeholder: '请输入占位符', | ||
137 | + }, | ||
138 | + includes: [ | ||
139 | + 'AutoComplete', | ||
140 | + 'InputTextArea', | ||
141 | + 'InputNumber', | ||
142 | + 'Input', | ||
143 | + 'InputTextArea', | ||
144 | + 'Select', | ||
145 | + 'DatePicker', | ||
146 | + 'MonthPicker', | ||
147 | + 'TimePicker', | ||
148 | + 'TreeSelect', | ||
149 | + 'Cascader', | ||
150 | + ], | ||
151 | + }, | ||
152 | + { | ||
153 | + name: 'style', | ||
154 | + label: '样式', | ||
155 | + component: 'Input', | ||
156 | + componentProps: { | ||
157 | + placeholder: '请输入样式', | ||
158 | + }, | ||
159 | + }, | ||
160 | + { | ||
161 | + name: 'open', | ||
162 | + label: '一直展开下拉菜单', | ||
163 | + component: 'RadioGroup', | ||
164 | + componentProps: { | ||
165 | + options: [ | ||
166 | + { | ||
167 | + label: '默认', | ||
168 | + value: undefined, | ||
169 | + }, | ||
170 | + { | ||
171 | + label: '是', | ||
172 | + value: true, | ||
173 | + }, | ||
174 | + { | ||
175 | + label: '否', | ||
176 | + value: false, | ||
177 | + }, | ||
178 | + ], | ||
179 | + }, | ||
180 | + includes: ['Select', 'AutoComplete'], | ||
181 | + }, | ||
182 | +]; | ||
183 | + | ||
184 | +const componentAttrs: IBaseComponentProps = { | ||
185 | + AutoComplete: [ | ||
186 | + { | ||
187 | + name: 'backfill', | ||
188 | + label: '自动回填', | ||
189 | + component: 'Switch', | ||
190 | + componentProps: { | ||
191 | + span: 8, | ||
192 | + }, | ||
193 | + }, | ||
194 | + { | ||
195 | + name: 'defaultOpen', | ||
196 | + label: '是否默认展开下拉菜单', | ||
197 | + component: 'Checkbox', | ||
198 | + }, | ||
199 | + ], | ||
200 | + IconPicker: [ | ||
201 | + { | ||
202 | + name: 'mode', | ||
203 | + label: '模式', | ||
204 | + component: 'RadioGroup', | ||
205 | + componentProps: { | ||
206 | + options: [ | ||
207 | + { label: 'ICONIFY', value: null }, | ||
208 | + { label: 'SVG', value: 'svg' }, | ||
209 | + // { label: '组合', value: 'combobox' }, | ||
210 | + ], | ||
211 | + }, | ||
212 | + }, | ||
213 | + ], | ||
214 | + | ||
215 | + // https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#%3Cinput%3E_types | ||
216 | + Input: [ | ||
217 | + { | ||
218 | + name: 'type', | ||
219 | + label: '类型', | ||
220 | + component: 'Select', | ||
221 | + componentProps: { | ||
222 | + options: [ | ||
223 | + { value: 'text', label: '文本' }, | ||
224 | + { value: 'search', label: '搜索' }, | ||
225 | + { value: 'number', label: '数字' }, | ||
226 | + { value: 'range', label: '数字范围' }, | ||
227 | + { value: 'password', label: '密码' }, | ||
228 | + { value: 'date', label: '日期' }, | ||
229 | + { value: 'datetime-local', label: '日期-无时区' }, | ||
230 | + { value: 'time', label: '时间' }, | ||
231 | + { value: 'month', label: '年月' }, | ||
232 | + { value: 'week', label: '星期' }, | ||
233 | + { value: 'email', label: '邮箱' }, | ||
234 | + { value: 'url', label: 'URL' }, | ||
235 | + { value: 'tel', label: '电话号码' }, | ||
236 | + { value: 'file', label: '文件' }, | ||
237 | + { value: 'button', label: '按钮' }, | ||
238 | + { value: 'submit', label: '提交按钮' }, | ||
239 | + { value: 'reset', label: '重置按钮' }, | ||
240 | + { value: 'radio', label: '单选按钮' }, | ||
241 | + { value: 'checkbox', label: '复选框' }, | ||
242 | + { value: 'color', label: '颜色' }, | ||
243 | + { value: 'image', label: '图像' }, | ||
244 | + { value: 'hidden', label: '隐藏' }, | ||
245 | + ], | ||
246 | + }, | ||
247 | + }, | ||
248 | + { | ||
249 | + name: 'defaultValue', | ||
250 | + label: '默认值', | ||
251 | + component: 'Input', | ||
252 | + componentProps: { | ||
253 | + type: 'text', | ||
254 | + placeholder: '请输入默认值', | ||
255 | + }, | ||
256 | + }, | ||
257 | + { | ||
258 | + name: 'prefix', | ||
259 | + label: '前缀', | ||
260 | + component: 'Input', | ||
261 | + componentProps: { | ||
262 | + type: 'text', | ||
263 | + placeholder: '请输入前缀', | ||
264 | + }, | ||
265 | + }, | ||
266 | + { | ||
267 | + name: 'suffix', | ||
268 | + label: '后缀', | ||
269 | + component: 'Input', | ||
270 | + componentProps: { | ||
271 | + type: 'text', | ||
272 | + placeholder: '请输入后缀', | ||
273 | + }, | ||
274 | + }, | ||
275 | + { | ||
276 | + name: 'addonBefore', | ||
277 | + label: '前置标签', | ||
278 | + component: 'Input', | ||
279 | + componentProps: { | ||
280 | + type: 'text', | ||
281 | + placeholder: '请输入前置标签', | ||
282 | + }, | ||
283 | + }, | ||
284 | + { | ||
285 | + name: 'addonAfter', | ||
286 | + label: '后置标签', | ||
287 | + component: 'Input', | ||
288 | + componentProps: { | ||
289 | + type: 'text', | ||
290 | + placeholder: '请输入后置标签', | ||
291 | + }, | ||
292 | + }, | ||
293 | + { | ||
294 | + name: 'maxLength', | ||
295 | + label: '最大长度', | ||
296 | + component: 'InputNumber', | ||
297 | + componentProps: { | ||
298 | + type: 'text', | ||
299 | + placeholder: '请输入最大长度', | ||
300 | + }, | ||
301 | + }, | ||
302 | + ], | ||
303 | + | ||
304 | + InputNumber: [ | ||
305 | + { | ||
306 | + name: 'defaultValue', | ||
307 | + label: '默认值', | ||
308 | + component: 'InputNumber', | ||
309 | + componentProps: { | ||
310 | + placeholder: '请输入默认值', | ||
311 | + }, | ||
312 | + }, | ||
313 | + { | ||
314 | + name: 'min', | ||
315 | + label: '最小值', | ||
316 | + component: 'InputNumber', | ||
317 | + componentProps: { | ||
318 | + type: 'text', | ||
319 | + placeholder: '请输入最小值', | ||
320 | + }, | ||
321 | + }, | ||
322 | + { | ||
323 | + name: 'max', | ||
324 | + label: '最大值', | ||
325 | + component: 'InputNumber', | ||
326 | + componentProps: { | ||
327 | + type: 'text', | ||
328 | + placeholder: '请输入最大值', | ||
329 | + }, | ||
330 | + }, | ||
331 | + { | ||
332 | + name: 'precision', | ||
333 | + label: '数值精度', | ||
334 | + component: 'InputNumber', | ||
335 | + componentProps: { | ||
336 | + type: 'text', | ||
337 | + placeholder: '请输入最大值', | ||
338 | + }, | ||
339 | + }, | ||
340 | + { | ||
341 | + name: 'step', | ||
342 | + label: '步长', | ||
343 | + component: 'InputNumber', | ||
344 | + componentProps: { | ||
345 | + type: 'text', | ||
346 | + placeholder: '请输入步长', | ||
347 | + }, | ||
348 | + }, | ||
349 | + { | ||
350 | + name: 'decimalSeparator', | ||
351 | + label: '小数点', | ||
352 | + component: 'Input', | ||
353 | + componentProps: { type: 'text', placeholder: '请输入小数点' }, | ||
354 | + }, | ||
355 | + { | ||
356 | + name: 'addonBefore', | ||
357 | + label: '前置标签', | ||
358 | + component: 'Input', | ||
359 | + componentProps: { | ||
360 | + type: 'text', | ||
361 | + placeholder: '请输入前置标签', | ||
362 | + }, | ||
363 | + }, | ||
364 | + { | ||
365 | + name: 'addonAfter', | ||
366 | + label: '后置标签', | ||
367 | + component: 'Input', | ||
368 | + componentProps: { | ||
369 | + type: 'text', | ||
370 | + placeholder: '请输入后置标签', | ||
371 | + }, | ||
372 | + }, | ||
373 | + { | ||
374 | + name: 'controls', | ||
375 | + label: '是否显示增减按钮', | ||
376 | + component: 'Checkbox', | ||
377 | + }, | ||
378 | + { | ||
379 | + name: 'keyboard', | ||
380 | + label: '是否启用键盘快捷行为', | ||
381 | + component: 'Checkbox', | ||
382 | + }, | ||
383 | + { | ||
384 | + name: 'stringMode', | ||
385 | + label: '字符值模式', | ||
386 | + component: 'Checkbox', | ||
387 | + }, | ||
388 | + { | ||
389 | + name: 'bordered', | ||
390 | + label: '是否有边框', | ||
391 | + component: 'Checkbox', | ||
392 | + }, | ||
393 | + ], | ||
394 | + InputTextArea: [ | ||
395 | + { | ||
396 | + name: 'defaultValue', | ||
397 | + label: '默认值', | ||
398 | + component: 'Input', | ||
399 | + componentProps: { | ||
400 | + type: 'text', | ||
401 | + placeholder: '请输入默认值', | ||
402 | + }, | ||
403 | + }, | ||
404 | + { | ||
405 | + name: 'maxlength', | ||
406 | + label: '最大长度', | ||
407 | + component: 'InputNumber', | ||
408 | + componentProps: { | ||
409 | + placeholder: '请输入最大长度', | ||
410 | + }, | ||
411 | + }, | ||
412 | + { | ||
413 | + name: 'minlength', | ||
414 | + label: '最小长度', | ||
415 | + component: 'InputNumber', | ||
416 | + componentProps: { | ||
417 | + placeholder: '请输入最小长度', | ||
418 | + }, | ||
419 | + }, | ||
420 | + { | ||
421 | + name: 'cols', | ||
422 | + label: '可见列数', | ||
423 | + component: 'InputNumber', | ||
424 | + componentProps: { | ||
425 | + placeholder: '请输入可见列数', | ||
426 | + min: 0, | ||
427 | + }, | ||
428 | + }, | ||
429 | + { | ||
430 | + name: 'rows', | ||
431 | + label: '可见行数', | ||
432 | + component: 'InputNumber', | ||
433 | + componentProps: { | ||
434 | + placeholder: '请输入可见行数', | ||
435 | + min: 0, | ||
436 | + }, | ||
437 | + }, | ||
438 | + { | ||
439 | + name: 'minlength', | ||
440 | + label: '最小长度', | ||
441 | + component: 'InputNumber', | ||
442 | + componentProps: { | ||
443 | + placeholder: '请输入最小长度', | ||
444 | + }, | ||
445 | + }, | ||
446 | + { | ||
447 | + name: 'autosize', | ||
448 | + label: '自适应内容高度', | ||
449 | + component: 'Checkbox', | ||
450 | + }, | ||
451 | + { | ||
452 | + name: 'showCount', | ||
453 | + label: '是否展示字数', | ||
454 | + component: 'Checkbox', | ||
455 | + }, | ||
456 | + { | ||
457 | + name: 'readonly', | ||
458 | + label: '是否只读', | ||
459 | + component: 'Checkbox', | ||
460 | + }, | ||
461 | + { | ||
462 | + name: 'spellcheck', | ||
463 | + label: '读写检查', | ||
464 | + component: 'Checkbox', | ||
465 | + }, | ||
466 | + { | ||
467 | + name: 'autocomplete', | ||
468 | + label: '是否自动完成', | ||
469 | + component: 'RadioGroup', | ||
470 | + componentProps: { | ||
471 | + options: [ | ||
472 | + { label: '正常', value: null }, | ||
473 | + { label: '开', value: 'on' }, | ||
474 | + { label: '关', value: 'off' }, | ||
475 | + ], | ||
476 | + }, | ||
477 | + }, | ||
478 | + { | ||
479 | + name: 'autocorrect', | ||
480 | + label: '是否自动纠错', | ||
481 | + component: 'RadioGroup', | ||
482 | + componentProps: { | ||
483 | + options: [ | ||
484 | + { label: '正常', value: null }, | ||
485 | + { label: '开', value: 'on' }, | ||
486 | + { label: '关', value: 'off' }, | ||
487 | + ], | ||
488 | + }, | ||
489 | + }, | ||
490 | + ], | ||
491 | + Select: [ | ||
492 | + { | ||
493 | + name: 'mode', | ||
494 | + label: '选择模式(默认单选)', | ||
495 | + component: 'RadioGroup', | ||
496 | + componentProps: { | ||
497 | + options: [ | ||
498 | + { label: '单选', value: null }, | ||
499 | + { label: '多选', value: 'multiple' }, | ||
500 | + { label: '标签', value: 'tags' }, | ||
501 | + // { label: '组合', value: 'combobox' }, | ||
502 | + ], | ||
503 | + }, | ||
504 | + }, | ||
505 | + { | ||
506 | + name: 'autoClearSearchValue', | ||
507 | + label: '是否在选中项后清空搜索框', | ||
508 | + component: 'Checkbox', | ||
509 | + }, | ||
510 | + { | ||
511 | + name: 'labelInValue', | ||
512 | + label: '选项的label包装到value中', | ||
513 | + component: 'Checkbox', | ||
514 | + }, | ||
515 | + { | ||
516 | + name: 'showArrow', | ||
517 | + label: '显示下拉小箭头', | ||
518 | + component: 'Checkbox', | ||
519 | + }, | ||
520 | + { | ||
521 | + name: 'defaultOpen', | ||
522 | + label: '默认展开下拉菜单', | ||
523 | + component: 'Checkbox', | ||
524 | + }, | ||
525 | + ], | ||
526 | + Checkbox: [ | ||
527 | + { | ||
528 | + name: 'indeterminate', | ||
529 | + label: '设置indeterminate状态', | ||
530 | + component: 'Checkbox', | ||
531 | + }, | ||
532 | + ], | ||
533 | + CheckboxGroup: [], | ||
534 | + RadioGroup: [ | ||
535 | + { | ||
536 | + name: 'defaultValue', | ||
537 | + label: '默认值', | ||
538 | + component: 'Input', | ||
539 | + componentProps: { | ||
540 | + placeholder: '请输入默认值', | ||
541 | + }, | ||
542 | + }, | ||
543 | + { | ||
544 | + name: 'buttonStyle', | ||
545 | + label: 'RadioButton的风格样式', | ||
546 | + component: 'RadioGroup', | ||
547 | + componentProps: { | ||
548 | + options: [ | ||
549 | + { | ||
550 | + label: 'outline', | ||
551 | + value: 'outline', | ||
552 | + }, | ||
553 | + { | ||
554 | + label: 'solid', | ||
555 | + value: 'solid', | ||
556 | + }, | ||
557 | + ], | ||
558 | + }, | ||
559 | + }, | ||
560 | + { | ||
561 | + name: 'optionType', | ||
562 | + label: 'options类型', | ||
563 | + component: 'RadioGroup', | ||
564 | + componentProps: { | ||
565 | + options: [ | ||
566 | + { | ||
567 | + label: '默认', | ||
568 | + value: 'default', | ||
569 | + }, | ||
570 | + { | ||
571 | + label: '按钮', | ||
572 | + value: 'button', | ||
573 | + }, | ||
574 | + ], | ||
575 | + //根据其它选项的值更新自身控件配置值 | ||
576 | + //compProp当前组件的属性, | ||
577 | + //configProps,当且组件的所有配置选项 | ||
578 | + //self,当前配置的componentProps属性 | ||
579 | + //返回真值进行更新 | ||
580 | + // _propsFunc: (compProp, configProps, self) => { | ||
581 | + // console.log("i'm called"); | ||
582 | + // console.log(compProp, configProps, self); | ||
583 | + // if (compProp['buttonStyle'] && compProp['buttonStyle'] == 'outline') { | ||
584 | + // if (!self['disabled']) { | ||
585 | + // self['disabled'] = true; | ||
586 | + // return 1; | ||
587 | + // } | ||
588 | + // } else { | ||
589 | + // if (self['disabled']) { | ||
590 | + // self['disabled'] = false; | ||
591 | + // return 1; | ||
592 | + // } | ||
593 | + // } | ||
594 | + | ||
595 | + // // return prop.optionType == 'button'; | ||
596 | + // }, | ||
597 | + }, | ||
598 | + }, | ||
599 | + { | ||
600 | + name: 'size', | ||
601 | + label: '尺寸', | ||
602 | + component: 'RadioGroup', | ||
603 | + componentProps: { | ||
604 | + options: [ | ||
605 | + { | ||
606 | + label: '默认', | ||
607 | + value: 'default', | ||
608 | + }, | ||
609 | + { | ||
610 | + label: '大', | ||
611 | + value: 'large', | ||
612 | + }, | ||
613 | + { | ||
614 | + label: '小', | ||
615 | + value: 'small', | ||
616 | + }, | ||
617 | + ], | ||
618 | + }, | ||
619 | + }, | ||
620 | + ], | ||
621 | + DatePicker: [ | ||
622 | + { | ||
623 | + name: 'format', | ||
624 | + label: '展示格式(format)', | ||
625 | + component: 'Input', | ||
626 | + componentProps: { | ||
627 | + placeholder: 'YYYY-MM-DD', | ||
628 | + }, | ||
629 | + }, | ||
630 | + { | ||
631 | + name: 'valueFormat', | ||
632 | + label: '绑定值格式(valueFormat)', | ||
633 | + component: 'Input', | ||
634 | + componentProps: { | ||
635 | + placeholder: 'YYYY-MM-DD', | ||
636 | + }, | ||
637 | + }, | ||
638 | + ], | ||
639 | + RangePicker: [ | ||
640 | + { | ||
641 | + name: 'placeholder', | ||
642 | + label: '占位符', | ||
643 | + children: [ | ||
644 | + { | ||
645 | + name: '', | ||
646 | + label: '', | ||
647 | + component: 'Input', | ||
648 | + }, | ||
649 | + { | ||
650 | + name: '', | ||
651 | + label: '', | ||
652 | + component: 'Input', | ||
653 | + }, | ||
654 | + ], | ||
655 | + }, | ||
656 | + { | ||
657 | + name: 'format', | ||
658 | + label: '展示格式(format)', | ||
659 | + component: 'Input', | ||
660 | + componentProps: { | ||
661 | + placeholder: 'YYYY-MM-DD HH:mm:ss', | ||
662 | + }, | ||
663 | + }, | ||
664 | + { | ||
665 | + name: 'valueFormat', | ||
666 | + label: '绑定值格式(valueFormat)', | ||
667 | + component: 'Input', | ||
668 | + componentProps: { | ||
669 | + placeholder: 'YYYY-MM-DD', | ||
670 | + }, | ||
671 | + }, | ||
672 | + ], | ||
673 | + MonthPicker: [ | ||
674 | + { | ||
675 | + name: 'format', | ||
676 | + label: '展示格式(format)', | ||
677 | + component: 'Input', | ||
678 | + componentProps: { | ||
679 | + placeholder: 'YYYY-MM', | ||
680 | + }, | ||
681 | + }, | ||
682 | + { | ||
683 | + name: 'valueFormat', | ||
684 | + label: '绑定值格式(valueFormat)', | ||
685 | + component: 'Input', | ||
686 | + componentProps: { | ||
687 | + placeholder: 'YYYY-MM', | ||
688 | + }, | ||
689 | + }, | ||
690 | + ], | ||
691 | + TimePicker: [ | ||
692 | + { | ||
693 | + name: 'format', | ||
694 | + label: '展示格式(format)', | ||
695 | + component: 'Input', | ||
696 | + componentProps: { | ||
697 | + placeholder: 'YYYY-MM', | ||
698 | + }, | ||
699 | + }, | ||
700 | + { | ||
701 | + name: 'valueFormat', | ||
702 | + label: '绑定值格式(valueFormat)', | ||
703 | + component: 'Input', | ||
704 | + componentProps: { | ||
705 | + placeholder: 'YYYY-MM', | ||
706 | + }, | ||
707 | + }, | ||
708 | + ], | ||
709 | + Slider: [ | ||
710 | + { | ||
711 | + name: 'defaultValue', | ||
712 | + label: '默认值', | ||
713 | + component: 'InputNumber', | ||
714 | + componentProps: { | ||
715 | + placeholder: '请输入默认值', | ||
716 | + }, | ||
717 | + }, | ||
718 | + { | ||
719 | + name: 'min', | ||
720 | + label: '最小值', | ||
721 | + component: 'InputNumber', | ||
722 | + componentProps: { | ||
723 | + placeholder: '请输入最小值', | ||
724 | + }, | ||
725 | + }, | ||
726 | + { | ||
727 | + name: 'max', | ||
728 | + label: '最大值', | ||
729 | + component: 'InputNumber', | ||
730 | + componentProps: { | ||
731 | + placeholder: '请输入最大值', | ||
732 | + }, | ||
733 | + }, | ||
734 | + { | ||
735 | + name: 'step', | ||
736 | + label: '步长', | ||
737 | + component: 'InputNumber', | ||
738 | + componentProps: { | ||
739 | + placeholder: '请输入步长', | ||
740 | + }, | ||
741 | + }, | ||
742 | + { | ||
743 | + name: 'tooltipPlacement', | ||
744 | + label: 'Tooltip 展示位置', | ||
745 | + component: 'Select', | ||
746 | + componentProps: { | ||
747 | + options: [ | ||
748 | + { value: 'top', label: '上' }, | ||
749 | + { value: 'left', label: '左' }, | ||
750 | + { value: 'right', label: '右' }, | ||
751 | + { value: 'bottom', label: '下' }, | ||
752 | + { value: 'topLeft', label: '上右' }, | ||
753 | + { value: 'topRight', label: '上左' }, | ||
754 | + { value: 'bottomLeft', label: '右下' }, | ||
755 | + { value: 'bottomRight', label: '左下' }, | ||
756 | + { value: 'leftTop', label: '左下' }, | ||
757 | + { value: 'leftBottom', label: '左上' }, | ||
758 | + { value: 'rightTop', label: '右下' }, | ||
759 | + { value: 'rightBottom', label: '右上' }, | ||
760 | + ], | ||
761 | + }, | ||
762 | + }, | ||
763 | + { | ||
764 | + name: 'tooltipVisible', | ||
765 | + label: '始终显示Tooltip', | ||
766 | + component: 'Checkbox', | ||
767 | + }, | ||
768 | + { | ||
769 | + name: 'dots', | ||
770 | + label: '只能拖拽到刻度上', | ||
771 | + component: 'Checkbox', | ||
772 | + }, | ||
773 | + { | ||
774 | + name: 'range', | ||
775 | + label: '双滑块模式', | ||
776 | + component: 'Checkbox', | ||
777 | + }, | ||
778 | + { | ||
779 | + name: 'reverse', | ||
780 | + label: '反向坐标轴', | ||
781 | + component: 'Checkbox', | ||
782 | + }, | ||
783 | + { | ||
784 | + name: 'vertical', | ||
785 | + label: '垂直方向', | ||
786 | + component: 'Checkbox', | ||
787 | + }, | ||
788 | + { | ||
789 | + name: 'included', | ||
790 | + label: '值为包含关系', | ||
791 | + component: 'Checkbox', | ||
792 | + }, | ||
793 | + ], | ||
794 | + Rate: [ | ||
795 | + { | ||
796 | + name: 'defaultValue', | ||
797 | + label: '默认值', | ||
798 | + component: 'InputNumber', | ||
799 | + componentProps: { | ||
800 | + placeholder: '请输入默认值', | ||
801 | + }, | ||
802 | + }, | ||
803 | + { | ||
804 | + name: 'character', | ||
805 | + label: '自定义字符', | ||
806 | + component: 'Input', | ||
807 | + componentProps: { | ||
808 | + placeholder: '请输入自定义字符', | ||
809 | + }, | ||
810 | + }, | ||
811 | + { | ||
812 | + name: 'count', | ||
813 | + label: 'start 总数', | ||
814 | + component: 'InputNumber', | ||
815 | + componentProps: { | ||
816 | + placeholder: '请输入自定义字符', | ||
817 | + }, | ||
818 | + }, | ||
819 | + ], | ||
820 | + Switch: [ | ||
821 | + { | ||
822 | + name: 'checkedChildren', | ||
823 | + label: '选中时的内容', | ||
824 | + component: 'Input', | ||
825 | + componentProps: { | ||
826 | + placeholder: '请输入选中时的内容', | ||
827 | + }, | ||
828 | + }, | ||
829 | + { | ||
830 | + name: 'checkedValue', | ||
831 | + label: '选中时的值', | ||
832 | + component: 'Input', | ||
833 | + componentProps: { | ||
834 | + placeholder: '请输入选中时的值', | ||
835 | + }, | ||
836 | + }, | ||
837 | + { | ||
838 | + name: 'unCheckedChildren', | ||
839 | + label: '非选中时的内容', | ||
840 | + component: 'Input', | ||
841 | + componentProps: { | ||
842 | + placeholder: '请输入非选中时的内容', | ||
843 | + }, | ||
844 | + }, | ||
845 | + { | ||
846 | + name: 'unCheckedValue', | ||
847 | + label: '非选中时的值', | ||
848 | + component: 'Input', | ||
849 | + componentProps: { | ||
850 | + placeholder: '请输入非选中时的值', | ||
851 | + }, | ||
852 | + }, | ||
853 | + { | ||
854 | + name: 'loading', | ||
855 | + label: '加载中的开关', | ||
856 | + component: 'Checkbox', | ||
857 | + }, | ||
858 | + { | ||
859 | + name: 'size', | ||
860 | + label: '尺寸', | ||
861 | + component: 'RadioGroup', | ||
862 | + componentProps: { | ||
863 | + options: [ | ||
864 | + { | ||
865 | + label: '默认', | ||
866 | + value: 'default', | ||
867 | + }, | ||
868 | + { | ||
869 | + label: '小', | ||
870 | + value: 'small', | ||
871 | + }, | ||
872 | + ], | ||
873 | + }, | ||
874 | + }, | ||
875 | + ], | ||
876 | + TreeSelect: [ | ||
877 | + { | ||
878 | + name: 'defaultValue', | ||
879 | + label: '默认值', | ||
880 | + component: 'Input', | ||
881 | + componentProps: { | ||
882 | + placeholder: '请输入默认值', | ||
883 | + }, | ||
884 | + }, | ||
885 | + { | ||
886 | + name: 'searchPlaceholder', | ||
887 | + label: '搜索框默认文字', | ||
888 | + component: 'Input', | ||
889 | + componentProps: { | ||
890 | + placeholder: '请输入搜索框默认文字', | ||
891 | + }, | ||
892 | + }, | ||
893 | + { | ||
894 | + name: 'treeNodeFilterProp', | ||
895 | + label: '输入项过滤对应的 treeNode 属性', | ||
896 | + component: 'Input', | ||
897 | + componentProps: { | ||
898 | + defaultValue: 'value', | ||
899 | + }, | ||
900 | + }, | ||
901 | + { | ||
902 | + name: 'treeNodeLabelProp', | ||
903 | + label: '作为显示的 prop 设置', | ||
904 | + component: 'Input', | ||
905 | + componentProps: { | ||
906 | + defaultValue: 'title', | ||
907 | + }, | ||
908 | + }, | ||
909 | + { | ||
910 | + name: 'dropdownClassName', | ||
911 | + label: '下拉菜单的 className 属性', | ||
912 | + component: 'Input', | ||
913 | + componentProps: { | ||
914 | + placeholder: '请输入下拉菜单的 className 属性', | ||
915 | + }, | ||
916 | + }, | ||
917 | + | ||
918 | + { | ||
919 | + name: 'labelInValue', | ||
920 | + label: '选项的label包装到value中', | ||
921 | + component: 'Checkbox', | ||
922 | + }, | ||
923 | + { | ||
924 | + name: 'treeIcon', | ||
925 | + label: '展示TreeNode title前的图标', | ||
926 | + component: 'Checkbox', | ||
927 | + }, | ||
928 | + { | ||
929 | + name: 'treeCheckable', | ||
930 | + label: '选项可勾选', | ||
931 | + component: 'Checkbox', | ||
932 | + }, | ||
933 | + { | ||
934 | + name: 'treeCheckStrictly', | ||
935 | + label: '节点选择完全受控', | ||
936 | + component: 'Checkbox', | ||
937 | + }, | ||
938 | + { | ||
939 | + name: 'treeDefaultExpandAll', | ||
940 | + label: '默认展开所有', | ||
941 | + component: 'Checkbox', | ||
942 | + }, | ||
943 | + { | ||
944 | + name: 'treeLine', | ||
945 | + label: '是否展示线条样式', | ||
946 | + component: 'Checkbox', | ||
947 | + }, | ||
948 | + { | ||
949 | + name: 'maxTagCount', | ||
950 | + label: '最多显示多少个 tag', | ||
951 | + component: 'InputNumber', | ||
952 | + componentProps: { | ||
953 | + placeholder: '最多显示多少个 tag', | ||
954 | + }, | ||
955 | + }, | ||
956 | + { | ||
957 | + name: 'size', | ||
958 | + label: '尺寸', | ||
959 | + component: 'RadioGroup', | ||
960 | + componentProps: { | ||
961 | + options: [ | ||
962 | + { | ||
963 | + label: '默认', | ||
964 | + value: 'default', | ||
965 | + }, | ||
966 | + { | ||
967 | + label: '小', | ||
968 | + value: 'small', | ||
969 | + }, | ||
970 | + ], | ||
971 | + }, | ||
972 | + }, | ||
973 | + ], | ||
974 | + Cascader: [ | ||
975 | + { | ||
976 | + name: 'expandTrigger', | ||
977 | + label: '次级展开方式(默认click)', | ||
978 | + component: 'RadioGroup', | ||
979 | + componentProps: { | ||
980 | + options: [ | ||
981 | + { | ||
982 | + label: 'click', | ||
983 | + value: 'click', | ||
984 | + }, | ||
985 | + { | ||
986 | + label: 'hover', | ||
987 | + value: 'hover', | ||
988 | + }, | ||
989 | + ], | ||
990 | + }, | ||
991 | + }, | ||
992 | + ], | ||
993 | + Button: [ | ||
994 | + { | ||
995 | + name: 'type', | ||
996 | + label: '类型', | ||
997 | + component: 'RadioGroup', | ||
998 | + componentProps: { | ||
999 | + options: [ | ||
1000 | + { | ||
1001 | + label: 'default', | ||
1002 | + value: 'default', | ||
1003 | + }, | ||
1004 | + { | ||
1005 | + label: 'primary', | ||
1006 | + value: 'primary', | ||
1007 | + }, | ||
1008 | + { | ||
1009 | + label: 'danger', | ||
1010 | + value: 'danger', | ||
1011 | + }, | ||
1012 | + { | ||
1013 | + label: 'dashed', | ||
1014 | + value: 'dashed', | ||
1015 | + }, | ||
1016 | + ], | ||
1017 | + }, | ||
1018 | + }, | ||
1019 | + { | ||
1020 | + name: 'handle', | ||
1021 | + label: '操作', | ||
1022 | + component: 'RadioGroup', | ||
1023 | + componentProps: { | ||
1024 | + options: [ | ||
1025 | + { | ||
1026 | + label: '提交', | ||
1027 | + value: 'submit', | ||
1028 | + }, | ||
1029 | + { | ||
1030 | + label: '重置', | ||
1031 | + value: 'reset', | ||
1032 | + }, | ||
1033 | + ], | ||
1034 | + }, | ||
1035 | + }, | ||
1036 | + ], | ||
1037 | + Upload: [ | ||
1038 | + { | ||
1039 | + name: 'action', | ||
1040 | + label: '上传地址', | ||
1041 | + component: 'Input', | ||
1042 | + }, | ||
1043 | + { | ||
1044 | + name: 'name', | ||
1045 | + label: '附件参数名(name)', | ||
1046 | + component: 'Input', | ||
1047 | + }, | ||
1048 | + ], | ||
1049 | + ColorPicker: [ | ||
1050 | + { | ||
1051 | + name: 'defaultValue', | ||
1052 | + label: '默认值', | ||
1053 | + component: 'AColorPicker', | ||
1054 | + }, | ||
1055 | + ], | ||
1056 | + slot: [ | ||
1057 | + { | ||
1058 | + name: 'slotName', | ||
1059 | + label: '插槽名称', | ||
1060 | + component: 'Input', | ||
1061 | + }, | ||
1062 | + ], | ||
1063 | + Transfer: [ | ||
1064 | + // { | ||
1065 | + // name: 'operations', | ||
1066 | + // label: '操作文案集合,顺序从上至下', | ||
1067 | + // component: 'Input', | ||
1068 | + // componentProps: { | ||
1069 | + // type: 'text', | ||
1070 | + // // defaultValue: ['>', '<'], | ||
1071 | + // }, | ||
1072 | + // }, | ||
1073 | + // { | ||
1074 | + // name: 'titles', | ||
1075 | + // label: '标题集合,顺序从左至右', | ||
1076 | + // component: 'Input', | ||
1077 | + // componentProps: { | ||
1078 | + // type: 'text', | ||
1079 | + // // defaultValue: ['', ''], | ||
1080 | + // }, | ||
1081 | + // }, | ||
1082 | + { | ||
1083 | + name: 'oneWay', | ||
1084 | + label: '展示为单向样式', | ||
1085 | + component: 'Checkbox', | ||
1086 | + }, | ||
1087 | + { | ||
1088 | + name: 'pagination', | ||
1089 | + label: '使用分页样式', | ||
1090 | + component: 'Checkbox', | ||
1091 | + }, | ||
1092 | + { | ||
1093 | + name: 'showSelectAll', | ||
1094 | + label: '展示全选勾选框', | ||
1095 | + component: 'Checkbox', | ||
1096 | + }, | ||
1097 | + ], | ||
1098 | +}; | ||
1099 | + | ||
1100 | +function deleteProps(list: Omit<IBaseFormAttrs, 'tag'>[], key: string) { | ||
1101 | + list.forEach((element, index) => { | ||
1102 | + if (element.name == key) { | ||
1103 | + list.splice(index, 1); | ||
1104 | + } | ||
1105 | + }); | ||
1106 | +} | ||
1107 | + | ||
1108 | +componentAttrs['StrengthMeter'] = componentAttrs['Input']; | ||
1109 | +componentAttrs['StrengthMeter'].push({ | ||
1110 | + name: 'visibilityToggle', | ||
1111 | + label: '是否显示切换按钮', | ||
1112 | + component: 'Checkbox', | ||
1113 | +}); | ||
1114 | + | ||
1115 | +deleteProps(componentAttrs['StrengthMeter'], 'type'); | ||
1116 | +deleteProps(componentAttrs['StrengthMeter'], 'prefix'); | ||
1117 | +deleteProps(componentAttrs['StrengthMeter'], 'defaultValue'); | ||
1118 | +deleteProps(componentAttrs['StrengthMeter'], 'suffix'); | ||
1119 | +//组件属性 | ||
1120 | +// name 控件的属性 | ||
1121 | +export const baseComponentAttrs: IBaseComponentProps = componentAttrs; | ||
1122 | + | ||
1123 | +//在所有的选项中查找需要配置项 | ||
1124 | +const findCompoentProps = (props, name) => { | ||
1125 | + const idx = props.findIndex((value: BaseFormAttrs, _index) => { | ||
1126 | + return value.name == name; | ||
1127 | + }); | ||
1128 | + if (idx) { | ||
1129 | + if (props[idx].componentProps) { | ||
1130 | + return props[idx].componentProps; | ||
1131 | + } | ||
1132 | + } | ||
1133 | +}; | ||
1134 | + | ||
1135 | +// 根据其它选项的值更新自身控件配置值 | ||
1136 | +export const componentPropsFuncs = { | ||
1137 | + RadioGroup: (compProp, options: BaseFormAttrs[]) => { | ||
1138 | + const props = findCompoentProps(options, 'size'); | ||
1139 | + if (props) { | ||
1140 | + if (compProp['optionType'] && compProp['optionType'] != 'button') { | ||
1141 | + props['disabled'] = true; | ||
1142 | + compProp['size'] = null; | ||
1143 | + } else { | ||
1144 | + props['disabled'] = false; | ||
1145 | + } | ||
1146 | + } | ||
1147 | + }, | ||
1148 | +}; |
src/views/form-design/components/VFormDesign/config/formItemPropsConfig.ts
0 → 100644
1 | +import { IAnyObject } from '../../../typings/base-type'; | ||
2 | +import { baseComponents, customComponents } from '../../../core/formItemConfig'; | ||
3 | + | ||
4 | +export const globalConfigState: { span: number } = { | ||
5 | + span: 24, | ||
6 | +}; | ||
7 | +export interface IBaseFormAttrs { | ||
8 | + name: string; // 字段名 | ||
9 | + label: string; // 字段标签 | ||
10 | + component?: string; // 属性控件 | ||
11 | + componentProps?: IAnyObject; // 传递给控件的属性 | ||
12 | + exclude?: string[]; // 需要排除的控件 | ||
13 | + includes?: string[]; // 符合条件的组件 | ||
14 | + on?: IAnyObject; | ||
15 | + children?: IBaseFormAttrs[]; | ||
16 | + category?: 'control' | 'input'; | ||
17 | +} | ||
18 | + | ||
19 | +export interface IBaseFormItemControlAttrs extends IBaseFormAttrs { | ||
20 | + target?: 'props' | 'options'; // 绑定到对象下的某个目标key中 | ||
21 | +} | ||
22 | + | ||
23 | +export const baseItemColumnProps: IBaseFormAttrs[] = [ | ||
24 | + { | ||
25 | + name: 'span', | ||
26 | + label: '栅格数', | ||
27 | + component: 'Slider', | ||
28 | + on: { | ||
29 | + change(value: number) { | ||
30 | + globalConfigState.span = value; | ||
31 | + }, | ||
32 | + }, | ||
33 | + componentProps: { | ||
34 | + max: 24, | ||
35 | + min: 0, | ||
36 | + marks: { 12: '' }, | ||
37 | + }, | ||
38 | + }, | ||
39 | + | ||
40 | + { | ||
41 | + name: 'offset', | ||
42 | + label: '栅格左侧的间隔格数', | ||
43 | + component: 'Slider', | ||
44 | + componentProps: { | ||
45 | + max: 24, | ||
46 | + min: 0, | ||
47 | + marks: { 12: '' }, | ||
48 | + }, | ||
49 | + }, | ||
50 | + { | ||
51 | + name: 'order', | ||
52 | + label: '栅格顺序,flex 布局模式下有效', | ||
53 | + component: 'Slider', | ||
54 | + componentProps: { | ||
55 | + max: 24, | ||
56 | + min: 0, | ||
57 | + marks: { 12: '' }, | ||
58 | + }, | ||
59 | + }, | ||
60 | + { | ||
61 | + name: 'pull', | ||
62 | + label: '栅格向左移动格数', | ||
63 | + component: 'Slider', | ||
64 | + componentProps: { | ||
65 | + max: 24, | ||
66 | + min: 0, | ||
67 | + marks: { 12: '' }, | ||
68 | + }, | ||
69 | + }, | ||
70 | + { | ||
71 | + name: 'push', | ||
72 | + label: '栅格向右移动格数', | ||
73 | + component: 'Slider', | ||
74 | + componentProps: { | ||
75 | + max: 24, | ||
76 | + min: 0, | ||
77 | + marks: { 12: '' }, | ||
78 | + }, | ||
79 | + }, | ||
80 | + { | ||
81 | + name: 'xs', | ||
82 | + label: '<576px 响应式栅格', | ||
83 | + component: 'Slider', | ||
84 | + componentProps: { | ||
85 | + max: 24, | ||
86 | + min: 0, | ||
87 | + marks: { 12: '' }, | ||
88 | + }, | ||
89 | + }, | ||
90 | + { | ||
91 | + name: 'sm', | ||
92 | + label: '≥576px 响应式栅格', | ||
93 | + component: 'Slider', | ||
94 | + componentProps: { | ||
95 | + max: 24, | ||
96 | + min: 0, | ||
97 | + marks: { 12: '' }, | ||
98 | + }, | ||
99 | + }, | ||
100 | + { | ||
101 | + name: 'md', | ||
102 | + label: '≥768p 响应式栅格', | ||
103 | + component: 'Slider', | ||
104 | + | ||
105 | + componentProps: { | ||
106 | + max: 24, | ||
107 | + min: 0, | ||
108 | + marks: { 12: '' }, | ||
109 | + }, | ||
110 | + }, | ||
111 | + { | ||
112 | + name: 'lg', | ||
113 | + label: '≥992px 响应式栅格', | ||
114 | + component: 'Slider', | ||
115 | + componentProps: { | ||
116 | + max: 24, | ||
117 | + min: 0, | ||
118 | + marks: { 12: '' }, | ||
119 | + }, | ||
120 | + }, | ||
121 | + { | ||
122 | + name: 'xl', | ||
123 | + label: '≥1200px 响应式栅格', | ||
124 | + component: 'Slider', | ||
125 | + componentProps: { | ||
126 | + max: 24, | ||
127 | + min: 0, | ||
128 | + marks: { 12: '' }, | ||
129 | + }, | ||
130 | + }, | ||
131 | + { | ||
132 | + name: 'xxl', | ||
133 | + label: '≥1600px 响应式栅格', | ||
134 | + component: 'Slider', | ||
135 | + componentProps: { | ||
136 | + max: 24, | ||
137 | + min: 0, | ||
138 | + marks: { 12: '' }, | ||
139 | + }, | ||
140 | + }, | ||
141 | + { | ||
142 | + name: '≥2000px', | ||
143 | + label: '≥1600px 响应式栅格', | ||
144 | + component: 'Slider', | ||
145 | + componentProps: { | ||
146 | + max: 24, | ||
147 | + min: 0, | ||
148 | + marks: { 12: '' }, | ||
149 | + }, | ||
150 | + }, | ||
151 | +]; | ||
152 | + | ||
153 | +// 控件属性面板的配置项 | ||
154 | +export const advanceFormItemColProps: IBaseFormAttrs[] = [ | ||
155 | + { | ||
156 | + name: 'labelCol', | ||
157 | + label: '标签col', | ||
158 | + component: 'Slider', | ||
159 | + componentProps: { | ||
160 | + max: 24, | ||
161 | + min: 0, | ||
162 | + marks: { 12: '' }, | ||
163 | + }, | ||
164 | + exclude: ['Grid'], | ||
165 | + }, | ||
166 | + { | ||
167 | + name: 'wrapperCol', | ||
168 | + label: '控件-span', | ||
169 | + component: 'Slider', | ||
170 | + componentProps: { | ||
171 | + max: 24, | ||
172 | + min: 0, | ||
173 | + marks: { 12: '' }, | ||
174 | + }, | ||
175 | + exclude: ['Grid'], | ||
176 | + }, | ||
177 | +]; | ||
178 | +// 控件属性面板的配置项 | ||
179 | +export const baseFormItemProps: IBaseFormAttrs[] = [ | ||
180 | + { | ||
181 | + // 动态的切换控件的类型 | ||
182 | + name: 'component', | ||
183 | + label: '控件-FormItem', | ||
184 | + component: 'Select', | ||
185 | + componentProps: { | ||
186 | + options: baseComponents | ||
187 | + .concat(customComponents) | ||
188 | + .map((item) => ({ value: item.component, label: item.label })), | ||
189 | + }, | ||
190 | + }, | ||
191 | + { | ||
192 | + name: 'label', | ||
193 | + label: '标签', | ||
194 | + component: 'Input', | ||
195 | + componentProps: { | ||
196 | + type: 'Input', | ||
197 | + placeholder: '请输入标签', | ||
198 | + }, | ||
199 | + exclude: ['Grid'], | ||
200 | + }, | ||
201 | + { | ||
202 | + name: 'field', | ||
203 | + label: '字段标识', | ||
204 | + component: 'Input', | ||
205 | + componentProps: { | ||
206 | + type: 'InputTextArea', | ||
207 | + placeholder: '请输入字段标识', | ||
208 | + }, | ||
209 | + exclude: ['Grid'], | ||
210 | + }, | ||
211 | + { | ||
212 | + name: 'helpMessage', | ||
213 | + label: 'helpMessage', | ||
214 | + component: 'Input', | ||
215 | + componentProps: { | ||
216 | + placeholder: '请输入提示信息', | ||
217 | + }, | ||
218 | + exclude: ['Grid'], | ||
219 | + }, | ||
220 | +]; | ||
221 | + | ||
222 | +// 控件属性面板的配置项 | ||
223 | +export const advanceFormItemProps: IBaseFormAttrs[] = [ | ||
224 | + { | ||
225 | + name: 'labelAlign', | ||
226 | + label: '标签对齐', | ||
227 | + component: 'RadioGroup', | ||
228 | + componentProps: { | ||
229 | + options: [ | ||
230 | + { | ||
231 | + label: '靠左', | ||
232 | + value: 'left', | ||
233 | + }, | ||
234 | + { | ||
235 | + label: '靠右', | ||
236 | + value: 'right', | ||
237 | + }, | ||
238 | + ], | ||
239 | + }, | ||
240 | + exclude: ['Grid'], | ||
241 | + }, | ||
242 | + | ||
243 | + { | ||
244 | + name: 'help', | ||
245 | + label: 'help', | ||
246 | + component: 'Input', | ||
247 | + componentProps: { | ||
248 | + placeholder: '请输入提示信息', | ||
249 | + }, | ||
250 | + exclude: ['Grid'], | ||
251 | + }, | ||
252 | + { | ||
253 | + name: 'extra', | ||
254 | + label: '额外消息', | ||
255 | + component: 'Input', | ||
256 | + componentProps: { | ||
257 | + type: 'InputTextArea', | ||
258 | + placeholder: '请输入额外消息', | ||
259 | + }, | ||
260 | + exclude: ['Grid'], | ||
261 | + }, | ||
262 | + { | ||
263 | + name: 'validateTrigger', | ||
264 | + label: 'validateTrigger', | ||
265 | + component: 'Input', | ||
266 | + componentProps: { | ||
267 | + type: 'InputTextArea', | ||
268 | + placeholder: '请输入validateTrigger', | ||
269 | + }, | ||
270 | + exclude: ['Grid'], | ||
271 | + }, | ||
272 | + { | ||
273 | + name: 'validateStatus', | ||
274 | + label: '校验状态', | ||
275 | + component: 'RadioGroup', | ||
276 | + componentProps: { | ||
277 | + options: [ | ||
278 | + { | ||
279 | + label: '默认', | ||
280 | + value: '', | ||
281 | + }, | ||
282 | + { | ||
283 | + label: '成功', | ||
284 | + value: 'success', | ||
285 | + }, | ||
286 | + { | ||
287 | + label: '警告', | ||
288 | + value: 'warning', | ||
289 | + }, | ||
290 | + { | ||
291 | + label: '错误', | ||
292 | + value: 'error', | ||
293 | + }, | ||
294 | + { | ||
295 | + label: '校验中', | ||
296 | + value: 'validating', | ||
297 | + }, | ||
298 | + ], | ||
299 | + }, | ||
300 | + exclude: ['Grid'], | ||
301 | + }, | ||
302 | +]; | ||
303 | + | ||
304 | +export const baseFormItemControlAttrs: IBaseFormItemControlAttrs[] = [ | ||
305 | + { | ||
306 | + name: 'required', | ||
307 | + label: '必填项', | ||
308 | + component: 'Checkbox', | ||
309 | + exclude: ['alert'], | ||
310 | + }, | ||
311 | + { | ||
312 | + name: 'hidden', | ||
313 | + label: '隐藏', | ||
314 | + component: 'Checkbox', | ||
315 | + exclude: ['alert'], | ||
316 | + }, | ||
317 | + { | ||
318 | + name: 'hiddenLabel', | ||
319 | + component: 'Checkbox', | ||
320 | + exclude: ['Grid'], | ||
321 | + label: '隐藏标签', | ||
322 | + }, | ||
323 | + { | ||
324 | + name: 'colon', | ||
325 | + label: 'label后面显示冒号', | ||
326 | + component: 'Checkbox', | ||
327 | + componentProps: {}, | ||
328 | + exclude: ['Grid'], | ||
329 | + }, | ||
330 | + { | ||
331 | + name: 'hasFeedback', | ||
332 | + label: '输入反馈', | ||
333 | + component: 'Checkbox', | ||
334 | + componentProps: {}, | ||
335 | + includes: ['Input'], | ||
336 | + }, | ||
337 | + { | ||
338 | + name: 'autoLink', | ||
339 | + label: '自动关联', | ||
340 | + component: 'Checkbox', | ||
341 | + componentProps: {}, | ||
342 | + includes: ['Input'], | ||
343 | + }, | ||
344 | + { | ||
345 | + name: 'validateFirst', | ||
346 | + label: '检验证错误停止', | ||
347 | + component: 'Checkbox', | ||
348 | + componentProps: {}, | ||
349 | + includes: ['Input'], | ||
350 | + }, | ||
351 | +]; |
src/views/form-design/components/VFormDesign/index.vue
0 → 100644
1 | +<template> | ||
2 | + <!-- <div class="v-form-design-container"> --> | ||
3 | + <!-- <header class="v-form-design-header">{{ title }}</header> --> | ||
4 | + | ||
5 | + <Layout> | ||
6 | + <LayoutSider | ||
7 | + class="left" | ||
8 | + theme="light" | ||
9 | + collapsible | ||
10 | + collapsedWidth="0" | ||
11 | + width="270" | ||
12 | + :zeroWidthTriggerStyle="{ 'margin-top': '-70px' }" | ||
13 | + breakpoint="md" | ||
14 | + > | ||
15 | + <CollapseContainer title="基础控件"> | ||
16 | + <CollapseItem | ||
17 | + :list="baseComponents" | ||
18 | + :handleListPush="handleListPushDrag" | ||
19 | + @add-attrs="handleAddAttrs" | ||
20 | + @handle-list-push="handleListPush" | ||
21 | + /> | ||
22 | + </CollapseContainer> | ||
23 | + <CollapseContainer title="自定义控件"> | ||
24 | + <CollapseItem | ||
25 | + :list="customComponents" | ||
26 | + @add-attrs="handleAddAttrs" | ||
27 | + :handleListPush="handleListPushDrag" | ||
28 | + @handle-list-push="handleListPush" | ||
29 | + /> | ||
30 | + </CollapseContainer> | ||
31 | + <CollapseContainer title="布局控件"> | ||
32 | + <CollapseItem | ||
33 | + :list="layoutComponents" | ||
34 | + :handleListPush="handleListPushDrag" | ||
35 | + @add-attrs="handleAddAttrs" | ||
36 | + @handle-list-push="handleListPush" | ||
37 | + /> | ||
38 | + </CollapseContainer> | ||
39 | + </LayoutSider> | ||
40 | + <LayoutContent> | ||
41 | + <Toolbar | ||
42 | + @handle-open-json-modal="handleOpenModal(jsonModal!)" | ||
43 | + @handle-open-import-json-modal="handleOpenModal(importJsonModal!)" | ||
44 | + @handle-preview="handleOpenModal(eFormPreview!)" | ||
45 | + @handle-preview2="handleOpenModal(eFormPreview2!)" | ||
46 | + @handle-open-code-modal="handleOpenModal(codeModal!)" | ||
47 | + @handle-clear-form-items="handleClearFormItems" | ||
48 | + /> | ||
49 | + <FormComponentPanel | ||
50 | + :current-item="formConfig.currentItem" | ||
51 | + :data="formConfig" | ||
52 | + @handle-set-select-item="handleSetSelectItem" | ||
53 | + /> | ||
54 | + </LayoutContent> | ||
55 | + <LayoutSider | ||
56 | + class="right" | ||
57 | + collapsible | ||
58 | + :reverseArrow="true" | ||
59 | + theme="light" | ||
60 | + collapsedWidth="0" | ||
61 | + width="270" | ||
62 | + :zeroWidthTriggerStyle="{ 'margin-top': '-70px' }" | ||
63 | + breakpoint="lg" | ||
64 | + > | ||
65 | + <!-- <div class="right" onselectstart="return false"> --> | ||
66 | + <PropsPanel ref="propsPanel" :activeKey="formConfig.activeKey"> | ||
67 | + <template v-for="item of formConfig.schemas" #[`${item.component}Props`]="data"> | ||
68 | + <slot | ||
69 | + :name="`${item.component}Props`" | ||
70 | + v-bind="{ formItem: data, props: data.componentProps }" | ||
71 | + ></slot> | ||
72 | + </template> | ||
73 | + </PropsPanel> | ||
74 | + <!-- </div> --> | ||
75 | + </LayoutSider> | ||
76 | + </Layout> | ||
77 | + | ||
78 | + <JsonModal ref="jsonModal" /> | ||
79 | + <CodeModal ref="codeModal" /> | ||
80 | + <ImportJsonModal ref="importJsonModal" /> | ||
81 | + <VFormPreview ref="eFormPreview" :formConfig="formConfig" /> | ||
82 | + <VFormPreview2 ref="eFormPreview2" :formConfig="formConfig" /> | ||
83 | + <!-- </div> --> | ||
84 | +</template> | ||
85 | + | ||
86 | +<script lang="ts" setup> | ||
87 | + import CollapseItem from './modules/CollapseItem.vue'; | ||
88 | + import FormComponentPanel from './modules/FormComponentPanel.vue'; | ||
89 | + import JsonModal from './components/JsonModal.vue'; | ||
90 | + import VFormPreview from '../VFormPreview/index.vue'; | ||
91 | + import VFormPreview2 from '../VFormPreview/useForm.vue'; | ||
92 | + | ||
93 | + import Toolbar from './modules/Toolbar.vue'; | ||
94 | + import PropsPanel from './modules/PropsPanel.vue'; | ||
95 | + import ImportJsonModal from './components/ImportJsonModal.vue'; | ||
96 | + import CodeModal from './components/CodeModal.vue'; | ||
97 | + | ||
98 | + import 'codemirror/mode/javascript/javascript'; | ||
99 | + | ||
100 | + import { ref, provide, Ref } from 'vue'; | ||
101 | + import { Layout, LayoutContent, LayoutSider } from 'ant-design-vue'; | ||
102 | + | ||
103 | + // import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'; | ||
104 | + import { IVFormComponent, IFormConfig, PropsTabKey } from '../../typings/v-form-component'; | ||
105 | + import { formItemsForEach, generateKey } from '../../utils'; | ||
106 | + import { cloneDeep } from 'lodash-es'; | ||
107 | + import { baseComponents, customComponents, layoutComponents } from '../../core/formItemConfig'; | ||
108 | + import { useRefHistory, UseRefHistoryReturn } from '@vueuse/core'; | ||
109 | + // import { IAnyObject } from '../../typings/base-type'; | ||
110 | + import { globalConfigState } from './config/formItemPropsConfig'; | ||
111 | + import { IFormDesignMethods, IPropsPanel, IToolbarMethods } from '../../typings/form-type'; | ||
112 | + | ||
113 | + import { CollapseContainer } from '/@/components/Container/index'; | ||
114 | + defineProps({ | ||
115 | + title: { | ||
116 | + type: String, | ||
117 | + default: 'v-form-antd表单设计器', | ||
118 | + }, | ||
119 | + }); | ||
120 | + // 子组件实例 | ||
121 | + const propsPanel = ref<null | IPropsPanel>(null); | ||
122 | + const jsonModal = ref<null | IToolbarMethods>(null); | ||
123 | + const importJsonModal = ref<null | IToolbarMethods>(null); | ||
124 | + const eFormPreview = ref<null | IToolbarMethods>(null); | ||
125 | + const eFormPreview2 = ref<null | IToolbarMethods>(null); | ||
126 | + | ||
127 | + const codeModal = ref<null | IToolbarMethods>(null); | ||
128 | + | ||
129 | + const formModel = ref({}); | ||
130 | + // endregion | ||
131 | + const formConfig = ref<IFormConfig>({ | ||
132 | + // 表单配置 | ||
133 | + schemas: [], | ||
134 | + layout: 'horizontal', | ||
135 | + labelLayout: 'flex', | ||
136 | + labelWidth: 100, | ||
137 | + labelCol: {}, | ||
138 | + wrapperCol: {}, | ||
139 | + currentItem: { | ||
140 | + component: '', | ||
141 | + componentProps: {}, | ||
142 | + }, | ||
143 | + activeKey: 1, | ||
144 | + }); | ||
145 | + // const _state = reactive<IState>({ | ||
146 | + // locale: zhCN, // 国际化 | ||
147 | + // baseComponents, // 基础控件列表 | ||
148 | + // layoutComponents, // 布局组件列表 | ||
149 | + // customComponents, | ||
150 | + // propsPanel, | ||
151 | + // jsonModal, | ||
152 | + // eFormPreview, | ||
153 | + // eFormPreview2, | ||
154 | + // importJsonModal, | ||
155 | + // codeModal, | ||
156 | + // }); | ||
157 | + const setFormConfig = (config: IFormConfig) => { | ||
158 | + //外部导入时,可能会缺少必要的信息。 | ||
159 | + config.schemas = config.schemas || []; | ||
160 | + config.schemas.forEach((item) => { | ||
161 | + item.colProps = item.colProps || { span: 24 }; | ||
162 | + item.componentProps = item.componentProps || {}; | ||
163 | + item.itemProps = item.itemProps || {}; | ||
164 | + }); | ||
165 | + formConfig.value = config; | ||
166 | + }; | ||
167 | + // 获取历史记录,用于撤销和重构 | ||
168 | + const historyReturn = useRefHistory(formConfig, { | ||
169 | + deep: true, | ||
170 | + capacity: 20, | ||
171 | + parse: (val: IFormConfig) => { | ||
172 | + // 使用lodash.cloneDeep重新拷贝数据,把currentItem指向选中项 | ||
173 | + const formConfig = cloneDeep(val); | ||
174 | + const { currentItem, schemas } = formConfig; | ||
175 | + // 从formItems中查找选中项 | ||
176 | + | ||
177 | + const item = schemas && schemas.find((item) => item.key === currentItem?.key); | ||
178 | + // 如果有,则赋值给当前项,如果没有,则切换属性面板 | ||
179 | + if (item) { | ||
180 | + formConfig.currentItem = item; | ||
181 | + } | ||
182 | + return formConfig; | ||
183 | + }, | ||
184 | + }); | ||
185 | + | ||
186 | + /** | ||
187 | + * 选中表单项 | ||
188 | + * @param schema 当前选中的表单项 | ||
189 | + */ | ||
190 | + const handleSetSelectItem = (schema: IVFormComponent) => { | ||
191 | + formConfig.value.currentItem = schema; | ||
192 | + handleChangePropsTabs( | ||
193 | + schema.key ? (formConfig.value.activeKey! === 1 ? 2 : formConfig.value.activeKey!) : 1, | ||
194 | + ); | ||
195 | + }; | ||
196 | + | ||
197 | + const setGlobalConfigState = (formItem: IVFormComponent) => { | ||
198 | + formItem.colProps = formItem.colProps || {}; | ||
199 | + formItem.colProps.span = globalConfigState.span; | ||
200 | + // console.log('setGlobalConfigState', formItem); | ||
201 | + }; | ||
202 | + | ||
203 | + /** | ||
204 | + * 添加属性 | ||
205 | + * @param schemas | ||
206 | + * @param index | ||
207 | + */ | ||
208 | + const handleAddAttrs = (_formItems: IVFormComponent[], _index: number) => { | ||
209 | + // const item = schemas[index]; | ||
210 | + // setGlobalConfigState(item); | ||
211 | + // generateKey(item); | ||
212 | + // handleListPush(item); | ||
213 | + }; | ||
214 | + | ||
215 | + const handleListPushDrag = (item: IVFormComponent) => { | ||
216 | + const formItem = cloneDeep(item); | ||
217 | + setGlobalConfigState(formItem); | ||
218 | + generateKey(formItem); | ||
219 | + // if (!formConfig.value.currentItem?.key) { | ||
220 | + // formConfig.value.schemas.push(formItem); | ||
221 | + // handleSetSelectItem(formItem); | ||
222 | + // return formItem; | ||
223 | + // } | ||
224 | + // handleCopy(formItem, false); | ||
225 | + // handleCopy(formItem, false); | ||
226 | + return formItem; | ||
227 | + }; | ||
228 | + /** | ||
229 | + * 单击控件时添加到面板中 | ||
230 | + * @param item {IVFormComponent} 当前点击的组件 | ||
231 | + */ | ||
232 | + const handleListPush = (item: IVFormComponent) => { | ||
233 | + // console.log('handleListPush', item); | ||
234 | + const formItem = cloneDeep(item); | ||
235 | + setGlobalConfigState(formItem); | ||
236 | + generateKey(formItem); | ||
237 | + if (!formConfig.value.currentItem?.key) { | ||
238 | + handleSetSelectItem(formItem); | ||
239 | + formConfig.value.schemas && formConfig.value.schemas.push(formItem); | ||
240 | + | ||
241 | + return; | ||
242 | + } | ||
243 | + handleCopy(formItem, false); | ||
244 | + }; | ||
245 | + | ||
246 | + /** | ||
247 | + * 复制表单项,如果表单项为栅格布局,则遍历所有自表单项重新生成key | ||
248 | + * @param {IVFormComponent} formItem | ||
249 | + * @return {IVFormComponent} | ||
250 | + */ | ||
251 | + const copyFormItem = (formItem: IVFormComponent) => { | ||
252 | + const newFormItem = cloneDeep(formItem); | ||
253 | + if (newFormItem.component === 'Grid') { | ||
254 | + formItemsForEach([formItem], (item) => { | ||
255 | + generateKey(item); | ||
256 | + }); | ||
257 | + } | ||
258 | + return newFormItem; | ||
259 | + }; | ||
260 | + /** | ||
261 | + * 复制或者添加表单,isCopy为true时则复制表单 | ||
262 | + * @param item {IVFormComponent} 当前点击的组件 | ||
263 | + * @param isCopy {boolean} 是否复制 | ||
264 | + */ | ||
265 | + const handleCopy = ( | ||
266 | + item: IVFormComponent = formConfig.value.currentItem as IVFormComponent, | ||
267 | + isCopy = true, | ||
268 | + ) => { | ||
269 | + const key = formConfig.value.currentItem?.key; | ||
270 | + /** | ||
271 | + * 遍历当表单项配置,如果是复制,则复制一份表单项,如果不是复制,则直接添加到表单项中 | ||
272 | + * @param schemas | ||
273 | + */ | ||
274 | + const traverse = (schemas: IVFormComponent[]) => { | ||
275 | + // 使用some遍历,找到目标后停止遍历 | ||
276 | + schemas.some((formItem: IVFormComponent, index: number) => { | ||
277 | + if (formItem.key === key) { | ||
278 | + // 判断是不是复制 | ||
279 | + isCopy | ||
280 | + ? schemas.splice(index, 0, copyFormItem(formItem)) | ||
281 | + : schemas.splice(index + 1, 0, item); | ||
282 | + const event = { | ||
283 | + newIndex: index + 1, | ||
284 | + }; | ||
285 | + // 添加到表单项中 | ||
286 | + handleBeforeColAdd(event, schemas, isCopy); | ||
287 | + return true; | ||
288 | + } | ||
289 | + if (['Grid', 'Tabs'].includes(formItem.component)) { | ||
290 | + // 栅格布局 | ||
291 | + formItem.columns?.forEach((item) => { | ||
292 | + traverse(item.children); | ||
293 | + }); | ||
294 | + } | ||
295 | + }); | ||
296 | + }; | ||
297 | + if (formConfig.value.schemas) { | ||
298 | + traverse(formConfig.value.schemas); | ||
299 | + } | ||
300 | + }; | ||
301 | + | ||
302 | + /** | ||
303 | + * 添加到表单中 | ||
304 | + * @param newIndex {object} 事件对象 | ||
305 | + * @param schemas {IVFormComponent[]} 表单项列表 | ||
306 | + * @param isCopy {boolean} 是否复制 | ||
307 | + */ | ||
308 | + const handleBeforeColAdd = ({ newIndex }: any, schemas: IVFormComponent[], isCopy = false) => { | ||
309 | + const item = schemas[newIndex]; | ||
310 | + isCopy && generateKey(item); | ||
311 | + handleSetSelectItem(item); | ||
312 | + }; | ||
313 | + | ||
314 | + /** | ||
315 | + * 打开模态框 | ||
316 | + * @param Modal {IToolbarMethods} | ||
317 | + */ | ||
318 | + const handleOpenModal = (Modal: IToolbarMethods) => { | ||
319 | + const config = cloneDeep(formConfig.value); | ||
320 | + Modal?.showModal(config); | ||
321 | + }; | ||
322 | + /** | ||
323 | + * 切换属性面板 | ||
324 | + * @param key | ||
325 | + */ | ||
326 | + const handleChangePropsTabs = (key: PropsTabKey) => { | ||
327 | + formConfig.value.activeKey = key; | ||
328 | + }; | ||
329 | + /** | ||
330 | + * 清空表单项列表 | ||
331 | + */ | ||
332 | + const handleClearFormItems = () => { | ||
333 | + formConfig.value.schemas = []; | ||
334 | + handleSetSelectItem({ component: '' }); | ||
335 | + }; | ||
336 | + | ||
337 | + const setFormModel = (key, value) => (formModel.value[key] = value); | ||
338 | + provide('formModel', formModel); | ||
339 | + // 把祖先组件的方法项注入到子组件中,子组件可通过inject获取 | ||
340 | + provide<(key: String, value: any) => void>('setFormModelMethod', setFormModel); | ||
341 | + // region 注入给子组件的属性 | ||
342 | + // provide('currentItem', formConfig.value.currentItem) | ||
343 | + | ||
344 | + // 把表单配置项注入到子组件中,子组件可通过inject获取,获取到的数据为响应式 | ||
345 | + provide<Ref<IFormConfig>>('formConfig', formConfig); | ||
346 | + | ||
347 | + // 注入历史记录 | ||
348 | + provide<UseRefHistoryReturn<any, any>>('historyReturn', historyReturn); | ||
349 | + | ||
350 | + // 把祖先组件的方法项注入到子组件中,子组件可通过inject获取 | ||
351 | + provide<IFormDesignMethods>('formDesignMethods', { | ||
352 | + handleBeforeColAdd, | ||
353 | + handleCopy, | ||
354 | + handleListPush, | ||
355 | + handleSetSelectItem, | ||
356 | + handleAddAttrs, | ||
357 | + setFormConfig, | ||
358 | + }); | ||
359 | + | ||
360 | + // endregion | ||
361 | +</script> | ||
362 | + | ||
363 | +<style lang="less" scoped> | ||
364 | + // @import url(./styles/variable.less); | ||
365 | +</style> |
src/views/form-design/components/VFormDesign/modules/CollapseItem.vue
0 → 100644
1 | +<template> | ||
2 | + <div> | ||
3 | + <draggable | ||
4 | + tag="ul" | ||
5 | + :model-value="list" | ||
6 | + v-bind="{ | ||
7 | + group: { name: 'form-draggable', pull: 'clone', put: false }, | ||
8 | + sort: false, | ||
9 | + clone: cloneItem, | ||
10 | + animation: 180, | ||
11 | + ghostClass: 'moving', | ||
12 | + }" | ||
13 | + item-key="type" | ||
14 | + @start="handleStart($event, list)" | ||
15 | + @add="handleAdd" | ||
16 | + > | ||
17 | + <template #item="{ element, index }"> | ||
18 | + <li | ||
19 | + class="bs-box text-ellipsis" | ||
20 | + @dragstart="$emit('add-attrs', list, index)" | ||
21 | + @click="$emit('handle-list-push', element)" | ||
22 | + > | ||
23 | + <!-- <svg v-if="element.icon.indexOf('icon-') > -1" class="icon" aria-hidden="true"> | ||
24 | + <use :xlink:href="`#${element.icon}`" /> | ||
25 | + </svg> --> | ||
26 | + <Icon :icon="element.icon" /> | ||
27 | + {{ element.label }}</li | ||
28 | + ></template | ||
29 | + > | ||
30 | + </draggable> | ||
31 | + </div> | ||
32 | +</template> | ||
33 | +<script lang="ts"> | ||
34 | + import { defineComponent, reactive } from 'vue'; | ||
35 | + import { IVFormComponent } from '../../../typings/v-form-component'; | ||
36 | + import draggable from 'vuedraggable'; | ||
37 | + // import { toRefs } from '@vueuse/core'; | ||
38 | + import { Icon } from '/@/components/Icon'; | ||
39 | + | ||
40 | + export default defineComponent({ | ||
41 | + name: 'CollapseItem', | ||
42 | + components: { draggable, Icon }, | ||
43 | + props: { | ||
44 | + list: { | ||
45 | + type: [Array] as PropType<IVFormComponent[]>, | ||
46 | + default: () => [], | ||
47 | + }, | ||
48 | + handleListPush: { | ||
49 | + type: Function as PropType<(item: IVFormComponent) => void>, | ||
50 | + default: null, | ||
51 | + }, | ||
52 | + }, | ||
53 | + setup(props, { emit }) { | ||
54 | + const state = reactive({}); | ||
55 | + const handleStart = (e: any, list1: IVFormComponent[]) => { | ||
56 | + emit('start', list1[e.oldIndex].component); | ||
57 | + }; | ||
58 | + const handleAdd = (e: any) => { | ||
59 | + console.log(e); | ||
60 | + }; | ||
61 | + // https://github.com/SortableJS/vue.draggable.next | ||
62 | + // https://github.com/SortableJS/vue.draggable.next/blob/master/example/components/custom-clone.vue | ||
63 | + const cloneItem = (one) => { | ||
64 | + return props.handleListPush(one); | ||
65 | + }; | ||
66 | + return { state, handleStart, handleAdd, cloneItem }; | ||
67 | + }, | ||
68 | + }); | ||
69 | +</script> | ||
70 | + | ||
71 | +<style lang="less" scoped> | ||
72 | + @import url(../styles/variable.less); | ||
73 | + | ||
74 | + ul { | ||
75 | + padding: 5px; | ||
76 | + list-style: none; | ||
77 | + display: flex; | ||
78 | + margin-bottom: 0; | ||
79 | + flex-wrap: wrap; | ||
80 | + // background: #efefef; | ||
81 | + | ||
82 | + li { | ||
83 | + padding: 8px 12px; | ||
84 | + transition: all 0.3s; | ||
85 | + width: calc(50% - 6px); | ||
86 | + margin: 2.7px; | ||
87 | + height: 36px; | ||
88 | + line-height: 20px; | ||
89 | + cursor: move; | ||
90 | + border: 1px solid @border-color; | ||
91 | + border-radius: 3px; | ||
92 | + | ||
93 | + &:hover { | ||
94 | + color: @primary-color; | ||
95 | + border: 1px solid @primary-color; | ||
96 | + position: relative; | ||
97 | + // z-index: 1; | ||
98 | + box-shadow: 0 2px 6px @primary-color; | ||
99 | + } | ||
100 | + } | ||
101 | + } | ||
102 | + | ||
103 | + svg { | ||
104 | + display: inline !important; | ||
105 | + } | ||
106 | +</style> |
src/views/form-design/components/VFormDesign/modules/FormComponentPanel.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/18 | ||
4 | + * @Description: 中间表单布局面板 | ||
5 | + * https://github.com/SortableJS/vue.draggable.next/issues/138 | ||
6 | +--> | ||
7 | +<template> | ||
8 | + <div class="form-panel v-form-container"> | ||
9 | + <Empty | ||
10 | + class="empty-text" | ||
11 | + v-show="formConfig.schemas.length === 0" | ||
12 | + description="从左侧选择控件添加" | ||
13 | + /> | ||
14 | + <Form v-bind="formConfig"> | ||
15 | + <div class="draggable-box"> | ||
16 | + <draggable | ||
17 | + class="list-main ant-row" | ||
18 | + group="form-draggable" | ||
19 | + :component-data="{ name: 'list', tag: 'div', type: 'transition-group' }" | ||
20 | + ghostClass="moving" | ||
21 | + :animation="180" | ||
22 | + handle=".drag-move" | ||
23 | + v-model="formConfig.schemas" | ||
24 | + item-key="key" | ||
25 | + @add="addItem" | ||
26 | + @start="handleDragStart" | ||
27 | + > | ||
28 | + <template #item="{ element }"> | ||
29 | + <LayoutItem | ||
30 | + class="drag-move" | ||
31 | + :schema="element" | ||
32 | + :data="formConfig" | ||
33 | + :current-item="formConfig.currentItem || {}" | ||
34 | + /> | ||
35 | + </template> | ||
36 | + </draggable> | ||
37 | + </div> | ||
38 | + </Form> | ||
39 | + </div> | ||
40 | +</template> | ||
41 | +<script lang="ts"> | ||
42 | + import draggable from 'vuedraggable'; | ||
43 | + import { defineComponent, computed } from 'vue'; | ||
44 | + import LayoutItem from '../components/LayoutItem.vue'; | ||
45 | + import { cloneDeep } from 'lodash-es'; | ||
46 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
47 | + import { Form, Empty } from 'ant-design-vue'; | ||
48 | + | ||
49 | + export default defineComponent({ | ||
50 | + name: 'FormComponentPanel', | ||
51 | + components: { | ||
52 | + LayoutItem, | ||
53 | + draggable, | ||
54 | + Form, | ||
55 | + Empty, | ||
56 | + }, | ||
57 | + emits: ['handleSetSelectItem'], | ||
58 | + setup(_, { emit }) { | ||
59 | + const { formConfig } = useFormDesignState() as Recordable; | ||
60 | + | ||
61 | + /** | ||
62 | + * 拖拽完成事件 | ||
63 | + * @param newIndex | ||
64 | + */ | ||
65 | + const addItem = ({ newIndex }: any) => { | ||
66 | + formConfig.value.schemas = formConfig.value.schemas || []; | ||
67 | + | ||
68 | + const schemas = formConfig.value.schemas; | ||
69 | + schemas[newIndex] = cloneDeep(schemas[newIndex]); | ||
70 | + emit('handleSetSelectItem', schemas[newIndex]); | ||
71 | + }; | ||
72 | + | ||
73 | + /** | ||
74 | + * 拖拽开始事件 | ||
75 | + * @param e {Object} 事件对象 | ||
76 | + */ | ||
77 | + const handleDragStart = (e: any) => { | ||
78 | + emit('handleSetSelectItem', formConfig.value.schemas[e.oldIndex]); | ||
79 | + }; | ||
80 | + | ||
81 | + // 获取祖先组件传递的currentItem | ||
82 | + | ||
83 | + // 计算布局元素,水平模式下为ACol,非水平模式下为div | ||
84 | + const layoutTag = computed(() => { | ||
85 | + return formConfig.value.layout === 'horizontal' ? 'Col' : 'div'; | ||
86 | + }); | ||
87 | + | ||
88 | + return { | ||
89 | + addItem, | ||
90 | + handleDragStart, | ||
91 | + formConfig, | ||
92 | + layoutTag, | ||
93 | + }; | ||
94 | + }, | ||
95 | + }); | ||
96 | +</script> | ||
97 | + | ||
98 | +<style lang="less" scoped> | ||
99 | + @import url(../styles/variable.less); | ||
100 | + @import url(../styles/drag.less); | ||
101 | + | ||
102 | + .v-form-container { | ||
103 | + // 内联布局样式 | ||
104 | + .ant-form-inline { | ||
105 | + .list-main { | ||
106 | + display: flex; | ||
107 | + flex-wrap: wrap; | ||
108 | + justify-content: flex-start; | ||
109 | + align-content: flex-start; | ||
110 | + | ||
111 | + .layout-width { | ||
112 | + width: 100%; | ||
113 | + } | ||
114 | + } | ||
115 | + | ||
116 | + .ant-form-item-control-wrapper { | ||
117 | + min-width: 175px !important; | ||
118 | + } | ||
119 | + } | ||
120 | + } | ||
121 | + | ||
122 | + .form-panel { | ||
123 | + position: relative; | ||
124 | + height: 100%; | ||
125 | + | ||
126 | + .empty-text { | ||
127 | + color: #aaa; | ||
128 | + height: 150px; | ||
129 | + top: -10%; | ||
130 | + left: 0; | ||
131 | + right: 0; | ||
132 | + bottom: 0; | ||
133 | + margin: auto; | ||
134 | + position: absolute; | ||
135 | + z-index: 100; | ||
136 | + } | ||
137 | + | ||
138 | + .draggable-box { | ||
139 | + // width: 100%; | ||
140 | + .drag-move { | ||
141 | + cursor: move; | ||
142 | + min-height: 62px; | ||
143 | + } | ||
144 | + | ||
145 | + .list-main { | ||
146 | + overflow: auto; | ||
147 | + height: 100%; | ||
148 | + // 列表动画 | ||
149 | + .list-enter-active { | ||
150 | + transition: all 0.5s; | ||
151 | + } | ||
152 | + | ||
153 | + .list-leave-active { | ||
154 | + transition: all 0.3s; | ||
155 | + } | ||
156 | + | ||
157 | + .list-enter, | ||
158 | + .list-leave-to { | ||
159 | + opacity: 0; | ||
160 | + transform: translateX(-100px); | ||
161 | + } | ||
162 | + | ||
163 | + .list-enter { | ||
164 | + height: 30px; | ||
165 | + } | ||
166 | + } | ||
167 | + } | ||
168 | + } | ||
169 | +</style> |
src/views/form-design/components/VFormDesign/modules/PropsPanel.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/19 | ||
4 | + * @Description: 右侧属性配置面板 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div> | ||
8 | + <Tabs v-model:activeKey="formConfig.activeKey" :tabBarStyle="{ margin: 0 }"> | ||
9 | + <TabPane :key="1" tab="表单"> | ||
10 | + <FormProps /> | ||
11 | + </TabPane> | ||
12 | + <TabPane :key="2" tab="控件"> | ||
13 | + <FormItemProps /> | ||
14 | + </TabPane> | ||
15 | + <TabPane :key="3" tab="栅格"> | ||
16 | + <ComponentColumnProps /> | ||
17 | + </TabPane> | ||
18 | + <TabPane :key="4" tab="组件"> | ||
19 | + <slot v-if="slotProps" :name="slotProps.component + 'Props'"></slot> | ||
20 | + <ComponentProps v-else /> | ||
21 | + </TabPane> | ||
22 | + </Tabs> | ||
23 | + </div> | ||
24 | +</template> | ||
25 | +<script lang="ts"> | ||
26 | + import { computed, defineComponent } from 'vue'; | ||
27 | + import FormProps from '../components/FormProps.vue'; | ||
28 | + import FormItemProps from '../components/FormItemProps.vue'; | ||
29 | + import ComponentProps from '../components/ComponentProps.vue'; | ||
30 | + import ComponentColumnProps from '../components/FormItemColumnProps.vue'; | ||
31 | + import { useFormDesignState } from '../../../hooks/useFormDesignState'; | ||
32 | + import { customComponents } from '../../../core/formItemConfig'; | ||
33 | + import { TabPane, Tabs } from 'ant-design-vue'; | ||
34 | + type ChangeTabKey = 1 | 2; | ||
35 | + export interface IPropsPanel { | ||
36 | + changeTab: (key: ChangeTabKey) => void; | ||
37 | + } | ||
38 | + export default defineComponent({ | ||
39 | + name: 'PropsPanel', | ||
40 | + components: { | ||
41 | + FormProps, | ||
42 | + FormItemProps, | ||
43 | + ComponentProps, | ||
44 | + ComponentColumnProps, | ||
45 | + Tabs, | ||
46 | + TabPane, | ||
47 | + }, | ||
48 | + setup() { | ||
49 | + const { formConfig } = useFormDesignState(); | ||
50 | + const slotProps = computed(() => { | ||
51 | + return customComponents.find( | ||
52 | + (item) => item.component === formConfig.value.currentItem?.component, | ||
53 | + ); | ||
54 | + }); | ||
55 | + return { formConfig, customComponents, slotProps }; | ||
56 | + }, | ||
57 | + }); | ||
58 | +</script> | ||
59 | + | ||
60 | +<style lang="less" scoped> | ||
61 | + @import url(../styles/variable.less); | ||
62 | + | ||
63 | + :deep(.ant-tabs) { | ||
64 | + box-sizing: border-box; | ||
65 | + | ||
66 | + form { | ||
67 | + width: 100%; | ||
68 | + position: absolute; | ||
69 | + height: calc(100% - 50px); | ||
70 | + margin-right: 10px; | ||
71 | + overflow-y: auto; | ||
72 | + overflow-x: hidden; | ||
73 | + } | ||
74 | + | ||
75 | + .hint-box { | ||
76 | + margin-top: 200px; | ||
77 | + } | ||
78 | + | ||
79 | + .ant-form-item, | ||
80 | + .ant-slider-with-marks { | ||
81 | + margin-left: 10px; | ||
82 | + margin-right: 20px; | ||
83 | + margin-bottom: 0; | ||
84 | + } | ||
85 | + | ||
86 | + .ant-form-item { | ||
87 | + // width: 100%; | ||
88 | + margin-bottom: 0; | ||
89 | + | ||
90 | + .ant-form-item-label { | ||
91 | + line-height: 2; | ||
92 | + vertical-align: text-top; | ||
93 | + } | ||
94 | + } | ||
95 | + | ||
96 | + .ant-input-number { | ||
97 | + width: 100%; | ||
98 | + } | ||
99 | + } | ||
100 | +</style> |
src/views/form-design/components/VFormDesign/modules/Toolbar.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/23 | ||
4 | + * @Description: 工具栏 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <div class="operating-area"> | ||
8 | + <!-- 头部操作按钮区域 start --> | ||
9 | + <!-- 操作左侧区域 start --> | ||
10 | + <div class="left-btn-box"> | ||
11 | + <Tooltip v-for="item in toolbarsConfigs" :title="item.title" :key="item.icon"> | ||
12 | + <a @click="$emit(item.event)" class="toolbar-text"> | ||
13 | + <!-- <a-icon :type="item.icon" /> --> | ||
14 | + <Icon :icon="item.icon" /> | ||
15 | + </a> | ||
16 | + </Tooltip> | ||
17 | + <Divider type="vertical" /> | ||
18 | + <Tooltip title="撤销"> | ||
19 | + <a :class="{ disabled: !canUndo }" :disabled="!canUndo" @click="undo"> | ||
20 | + <!-- <a-icon type="undo" /> --> | ||
21 | + <Icon icon="ant-design:undo-outlined" /> | ||
22 | + </a> | ||
23 | + </Tooltip> | ||
24 | + <Tooltip title="重做"> | ||
25 | + <a :class="{ disabled: !canRedo }" :disabled="!canRedo" @click="redo"> | ||
26 | + <!-- <a-icon type="redo" /> --> | ||
27 | + <Icon icon="ant-design:redo-outlined" /> | ||
28 | + </a> | ||
29 | + </Tooltip> | ||
30 | + </div> | ||
31 | + </div> | ||
32 | + <!-- 操作区域 start --> | ||
33 | +</template> | ||
34 | +<script lang="ts"> | ||
35 | + import { defineComponent, inject, reactive, toRefs } from 'vue'; | ||
36 | + import { UseRefHistoryReturn } from '@vueuse/core'; | ||
37 | + import { IFormConfig } from '../../../typings/v-form-component'; | ||
38 | + import { Tooltip, Divider } from 'ant-design-vue'; | ||
39 | + import Icon from '/@/components/Icon/index'; | ||
40 | + | ||
41 | + interface IToolbarsConfig { | ||
42 | + type: string; | ||
43 | + title: string; | ||
44 | + icon: string; | ||
45 | + event: string; | ||
46 | + } | ||
47 | + | ||
48 | + export default defineComponent({ | ||
49 | + name: 'OperatingArea', | ||
50 | + components: { | ||
51 | + Tooltip, | ||
52 | + Icon, | ||
53 | + Divider, | ||
54 | + }, | ||
55 | + setup() { | ||
56 | + const state = reactive<{ | ||
57 | + toolbarsConfigs: IToolbarsConfig[]; | ||
58 | + }>({ | ||
59 | + toolbarsConfigs: [ | ||
60 | + { | ||
61 | + title: '预览', | ||
62 | + type: 'preview', | ||
63 | + event: 'handlePreview', | ||
64 | + icon: 'ant-design:chrome-filled', | ||
65 | + }, | ||
66 | + { | ||
67 | + title: '预览2', | ||
68 | + type: 'preview', | ||
69 | + event: 'handlePreview2', | ||
70 | + icon: 'ant-design:chrome-filled', | ||
71 | + }, | ||
72 | + { | ||
73 | + title: '导入', | ||
74 | + type: 'importJson', | ||
75 | + event: 'handleOpenImportJsonModal', | ||
76 | + icon: 'ant-design:import-outlined', | ||
77 | + }, | ||
78 | + { | ||
79 | + title: '生成JSON', | ||
80 | + type: 'exportJson', | ||
81 | + event: 'handleOpenJsonModal', | ||
82 | + icon: 'ant-design:export-outlined', | ||
83 | + }, | ||
84 | + { | ||
85 | + title: '生成代码', | ||
86 | + type: 'exportCode', | ||
87 | + event: 'handleOpenCodeModal', | ||
88 | + icon: 'ant-design:code-filled', | ||
89 | + }, | ||
90 | + { | ||
91 | + title: '清空', | ||
92 | + type: 'reset', | ||
93 | + event: 'handleClearFormItems', | ||
94 | + icon: 'ant-design:clear-outlined', | ||
95 | + }, | ||
96 | + ], | ||
97 | + }); | ||
98 | + const historyRef = inject('historyReturn') as UseRefHistoryReturn<IFormConfig, IFormConfig>; | ||
99 | + | ||
100 | + const { undo, redo, canUndo, canRedo } = historyRef; | ||
101 | + return { ...toRefs(state), undo, redo, canUndo, canRedo }; | ||
102 | + }, | ||
103 | + }); | ||
104 | +</script> | ||
105 | + | ||
106 | +<style lang="less" scoped> | ||
107 | + //noinspection CssUnknownTarget | ||
108 | + @import url('../styles/variable.less'); | ||
109 | + | ||
110 | + .operating-area { | ||
111 | + border-bottom: 2px solid @border-color; | ||
112 | + font-size: 16px; | ||
113 | + text-align: left; | ||
114 | + height: @operating-area-height; | ||
115 | + line-height: @operating-area-height; | ||
116 | + padding: 0 12px; | ||
117 | + display: flex; | ||
118 | + justify-content: space-between; | ||
119 | + align-content: center; | ||
120 | + padding-left: 30px; | ||
121 | + | ||
122 | + a { | ||
123 | + color: #666; | ||
124 | + margin: 0 5px; | ||
125 | + | ||
126 | + &.disabled, | ||
127 | + &.disabled:hover { | ||
128 | + color: #ccc; | ||
129 | + } | ||
130 | + | ||
131 | + &:hover { | ||
132 | + color: @primary-color; | ||
133 | + } | ||
134 | + | ||
135 | + > span { | ||
136 | + font-size: 14px; | ||
137 | + padding-left: 2px; | ||
138 | + } | ||
139 | + } | ||
140 | + } | ||
141 | +</style> |
src/views/form-design/components/VFormDesign/styles/drag.less
0 → 100644
1 | +.draggable-box { | ||
2 | + height: 100%; | ||
3 | + overflow: auto; | ||
4 | + | ||
5 | + :deep(.list-main) { | ||
6 | + overflow: hidden; | ||
7 | + min-height: 100%; | ||
8 | + padding: 5px; | ||
9 | + position: relative; | ||
10 | + background: #fafafa; | ||
11 | + // border : 1px #ccc dashed; | ||
12 | + | ||
13 | + .moving { | ||
14 | + // 拖放移动中 | ||
15 | + // outline-width: 0; | ||
16 | + min-height: 35px; | ||
17 | + box-sizing: border-box; | ||
18 | + overflow: hidden; | ||
19 | + padding: 0 !important; | ||
20 | + // margin : 3px 0; | ||
21 | + position: relative; | ||
22 | + | ||
23 | + &::before { | ||
24 | + content: ''; | ||
25 | + height: 5px; | ||
26 | + width: 100%; | ||
27 | + background: @primary-color; | ||
28 | + position: absolute; | ||
29 | + top: 0; | ||
30 | + right: 0; | ||
31 | + } | ||
32 | + } | ||
33 | + | ||
34 | + .drag-move-box { | ||
35 | + position: relative; | ||
36 | + box-sizing: border-box; | ||
37 | + padding: 8px; | ||
38 | + overflow: hidden; | ||
39 | + transition: all 0.3s; | ||
40 | + min-height: 60px; | ||
41 | + | ||
42 | + &:hover { | ||
43 | + background: @primary-hover-bg-color; | ||
44 | + } | ||
45 | + | ||
46 | + // 选择时 start | ||
47 | + &::before { | ||
48 | + content: ''; | ||
49 | + height: 5px; | ||
50 | + width: 100%; | ||
51 | + background: @primary-color; | ||
52 | + position: absolute; | ||
53 | + top: 0; | ||
54 | + right: -100%; | ||
55 | + transition: all 0.3s; | ||
56 | + } | ||
57 | + | ||
58 | + &.active { | ||
59 | + background: @primary-hover-bg-color; | ||
60 | + outline-offset: 0; | ||
61 | + | ||
62 | + &::before { | ||
63 | + right: 0; | ||
64 | + } | ||
65 | + } | ||
66 | + | ||
67 | + // 选择时 end | ||
68 | + .form-item-box { | ||
69 | + position: relative; | ||
70 | + box-sizing: border-box; | ||
71 | + word-wrap: break-word; | ||
72 | + | ||
73 | + &::before { | ||
74 | + content: ''; | ||
75 | + position: absolute; | ||
76 | + width: 100%; | ||
77 | + height: 100%; | ||
78 | + top: 0; | ||
79 | + left: 0; | ||
80 | + // z-index: 888; | ||
81 | + } | ||
82 | + | ||
83 | + .ant-form-item { | ||
84 | + // 修改ant form-item的margin为padding | ||
85 | + margin: 0; | ||
86 | + padding-bottom: 6px; | ||
87 | + } | ||
88 | + } | ||
89 | + | ||
90 | + .show-key-box { | ||
91 | + // 显示key | ||
92 | + position: absolute; | ||
93 | + bottom: 2px; | ||
94 | + right: 5px; | ||
95 | + font-size: 14px; | ||
96 | + // z-index: 999; | ||
97 | + color: @primary-color; | ||
98 | + } | ||
99 | + | ||
100 | + .copy, | ||
101 | + .delete { | ||
102 | + position: absolute; | ||
103 | + top: 0; | ||
104 | + width: 30px; | ||
105 | + height: 30px; | ||
106 | + line-height: 30px; | ||
107 | + text-align: center; | ||
108 | + color: #fff; | ||
109 | + // z-index: 989; | ||
110 | + transition: all 0.3s; | ||
111 | + | ||
112 | + &.unactivated { | ||
113 | + opacity: 0 !important; | ||
114 | + pointer-events: none; | ||
115 | + } | ||
116 | + | ||
117 | + &.active { | ||
118 | + opacity: 1 !important; | ||
119 | + } | ||
120 | + } | ||
121 | + | ||
122 | + .copy { | ||
123 | + border-radius: 0 0 0 8px; | ||
124 | + right: 30px; | ||
125 | + background: @primary-color; | ||
126 | + } | ||
127 | + | ||
128 | + .delete { | ||
129 | + right: 0; | ||
130 | + background: @primary-color; | ||
131 | + } | ||
132 | + } | ||
133 | + | ||
134 | + .grid-box { | ||
135 | + position: relative; | ||
136 | + box-sizing: border-box; | ||
137 | + padding: 5px; | ||
138 | + background: @layout-background-color; | ||
139 | + width: 100%; | ||
140 | + transition: all 0.3s; | ||
141 | + overflow: hidden; | ||
142 | + | ||
143 | + .form-item-box { | ||
144 | + position: relative; | ||
145 | + box-sizing: border-box; | ||
146 | + | ||
147 | + .ant-form-item { | ||
148 | + // 修改ant form-item的margin为padding | ||
149 | + margin: 0; | ||
150 | + padding-bottom: 15px; | ||
151 | + } | ||
152 | + } | ||
153 | + | ||
154 | + .grid-row { | ||
155 | + background: @layout-background-color; | ||
156 | + | ||
157 | + .grid-col { | ||
158 | + .draggable-box { | ||
159 | + min-height: 80px; | ||
160 | + min-width: 50px; | ||
161 | + border: 1px #ccc dashed; | ||
162 | + background: #fff; | ||
163 | + | ||
164 | + .list-main { | ||
165 | + min-height: 83px; | ||
166 | + position: relative; | ||
167 | + border: 1px #ccc dashed; | ||
168 | + } | ||
169 | + } | ||
170 | + } | ||
171 | + } | ||
172 | + | ||
173 | + // 选择时 start | ||
174 | + &::before { | ||
175 | + content: ''; | ||
176 | + height: 5px; | ||
177 | + width: 100%; | ||
178 | + background: transparent; | ||
179 | + position: absolute; | ||
180 | + top: 0; | ||
181 | + right: -100%; | ||
182 | + transition: all 0.3s; | ||
183 | + } | ||
184 | + | ||
185 | + &.active { | ||
186 | + background: @layout-hover-bg-color; | ||
187 | + outline-offset: 0; | ||
188 | + | ||
189 | + &::before { | ||
190 | + background: @layout-color; | ||
191 | + right: 0; | ||
192 | + } | ||
193 | + } | ||
194 | + // 选择时 end | ||
195 | + > .copy-delete-box { | ||
196 | + > .copy, | ||
197 | + > .delete { | ||
198 | + position: absolute; | ||
199 | + top: 0; | ||
200 | + width: 30px; | ||
201 | + height: 30px; | ||
202 | + line-height: 30px; | ||
203 | + text-align: center; | ||
204 | + color: #fff; | ||
205 | + // z-index: 989; | ||
206 | + transition: all 0.3s; | ||
207 | + | ||
208 | + &.unactivated { | ||
209 | + opacity: 0 !important; | ||
210 | + pointer-events: none; | ||
211 | + } | ||
212 | + | ||
213 | + &.active { | ||
214 | + opacity: 1 !important; | ||
215 | + } | ||
216 | + } | ||
217 | + | ||
218 | + > .copy { | ||
219 | + border-radius: 0 0 0 8px; | ||
220 | + right: 30px; | ||
221 | + background: @layout-color; | ||
222 | + } | ||
223 | + | ||
224 | + > .delete { | ||
225 | + right: 0; | ||
226 | + background: @layout-color; | ||
227 | + } | ||
228 | + } | ||
229 | + } | ||
230 | + } | ||
231 | +} |
src/views/form-design/components/VFormDesign/styles/v-form-design.less
0 → 100644
1 | +.v-form-design-container { | ||
2 | + // height: 100%; | ||
3 | + width: 100%; | ||
4 | + // overflow: hidden; | ||
5 | + display: flex; | ||
6 | + flex-direction: column; | ||
7 | + | ||
8 | + & > .v-form-design-header { | ||
9 | + height: @header-height; | ||
10 | + line-height: @header-height; | ||
11 | + background: @primary-color; | ||
12 | + text-align: center; | ||
13 | + font-size: 20px; | ||
14 | + color: #fff; | ||
15 | + } | ||
16 | + | ||
17 | + :deep(.icon) { | ||
18 | + width: 1em; | ||
19 | + height: 1em; | ||
20 | + vertical-align: -0.15em; | ||
21 | + fill: currentcolor; | ||
22 | + // overflow: hidden; | ||
23 | + } | ||
24 | + | ||
25 | + .content { | ||
26 | + position: relative; | ||
27 | + flex: 1; | ||
28 | + // margin-top: 5px; | ||
29 | + display: flex; | ||
30 | + // flex-flow: row nowrap; | ||
31 | + // height: 100%; | ||
32 | + // overflow: hidden; | ||
33 | + box-sizing: border-box; | ||
34 | + | ||
35 | + .left, | ||
36 | + .right { | ||
37 | + width: @left-right-width; | ||
38 | + box-shadow: 0 0 1px 1px #ccc; | ||
39 | + // overflow: hidden; | ||
40 | + // height: 100%; | ||
41 | + // height: 600px; | ||
42 | + // | ||
43 | + border: 1px solid green; | ||
44 | + // overflow-y: auto; | ||
45 | + // :deep(.ant-tabs) { | ||
46 | + // height: 100%; | ||
47 | + // .ant-tabs-content-holder { | ||
48 | + // // display: flex; | ||
49 | + // // flex-flow: column; | ||
50 | + // height: 100%; | ||
51 | + // .ant-tabs-content { | ||
52 | + // // flex:1; | ||
53 | + // // height: 0; | ||
54 | + // // overflow-y: auto; | ||
55 | + // // overflow-x: hidden; | ||
56 | + // display: flex; | ||
57 | + // flex-flow: column; | ||
58 | + // height: 100%; | ||
59 | + // } | ||
60 | + // } | ||
61 | + // } | ||
62 | + } | ||
63 | + | ||
64 | + :deep(.right) { | ||
65 | + // & > div { | ||
66 | + // height: 100%; | ||
67 | + // } | ||
68 | + // overflow: hidden; | ||
69 | + // & > div { | ||
70 | + // height: 100%; | ||
71 | + // .ant-tabs-content-holder{ | ||
72 | + // height: 100%; | ||
73 | + // .ant-tabs-content{ | ||
74 | + // height: 100%; | ||
75 | + // .ant-tabs-tabpane{ | ||
76 | + // height: 100%; | ||
77 | + // } | ||
78 | + // } | ||
79 | + // } | ||
80 | + // } | ||
81 | + | ||
82 | + .ant-tabs { | ||
83 | + width: 280px; | ||
84 | + height: 100%; | ||
85 | + // overflow: hidden; | ||
86 | + | ||
87 | + .ant-tabs-content-holder { | ||
88 | + // display: flex; | ||
89 | + // flex-flow: column; | ||
90 | + // height: 100%; | ||
91 | + // overflow: hidden; | ||
92 | + | ||
93 | + .ant-tabs-content { | ||
94 | + // flex:1; | ||
95 | + // height: 0; | ||
96 | + // overflow-y: auto; | ||
97 | + // overflow-x: hidden; | ||
98 | + | ||
99 | + height: 100%; | ||
100 | + // overflow: hidden; | ||
101 | + .ant-tabs-tabpane { | ||
102 | + .properties-content { | ||
103 | + // height: 100%; | ||
104 | + | ||
105 | + // overflow: auto; | ||
106 | + // overflow: hidden; | ||
107 | + | ||
108 | + // background: #fff; | ||
109 | + .properties-body { | ||
110 | + box-sizing: border-box; | ||
111 | + // height: 100%; | ||
112 | + | ||
113 | + // display: flex; | ||
114 | + // flex-flow: column; | ||
115 | + | ||
116 | + form { | ||
117 | + position: absolute; | ||
118 | + // height: 400px; | ||
119 | + height: calc(100% - 50px); | ||
120 | + // height: 100%; | ||
121 | + // flex: 1; | ||
122 | + // height: 0; | ||
123 | + margin-right: 10px; | ||
124 | + // overflow: auto; | ||
125 | + overflow-y: auto; | ||
126 | + overflow-x: hidden; | ||
127 | + // overflow: auto; | ||
128 | + } | ||
129 | + // overflow: auto; | ||
130 | + // height: 100%; | ||
131 | + // padding: 8px 16px; | ||
132 | + .hint-box { | ||
133 | + margin-top: 200px; | ||
134 | + } | ||
135 | + | ||
136 | + .ant-form-item, | ||
137 | + .ant-slider-with-marks { | ||
138 | + margin-left: 10px; | ||
139 | + margin-right: 20px; | ||
140 | + margin-bottom: 0; | ||
141 | + } | ||
142 | + | ||
143 | + .ant-form-item { | ||
144 | + // box-sizing: border-box; | ||
145 | + width: 100%; | ||
146 | + margin-bottom: 0; | ||
147 | + // padding: 2px 0; | ||
148 | + border-bottom: 1px solid @border-color; | ||
149 | + | ||
150 | + .ant-form-item-label { | ||
151 | + line-height: 2; | ||
152 | + vertical-align: text-top; | ||
153 | + } | ||
154 | + } | ||
155 | + } | ||
156 | + } | ||
157 | + } | ||
158 | + } | ||
159 | + } | ||
160 | + } | ||
161 | + } | ||
162 | + | ||
163 | + :deep(.left) { | ||
164 | + .ant-collapse { | ||
165 | + border: 0; | ||
166 | + | ||
167 | + .ant-collapse-header { | ||
168 | + padding: 7px 0 7px 40px; | ||
169 | + } | ||
170 | + | ||
171 | + .ant-collapse-content-box { | ||
172 | + padding: 0; | ||
173 | + } | ||
174 | + } | ||
175 | + | ||
176 | + ul { | ||
177 | + padding: 5px; | ||
178 | + list-style: none; | ||
179 | + display: flex; | ||
180 | + margin-bottom: 0; | ||
181 | + flex-wrap: wrap; | ||
182 | + // background: #efefef; | ||
183 | + | ||
184 | + li { | ||
185 | + padding: 8px 12px; | ||
186 | + transition: all 0.3s; | ||
187 | + width: calc(50% - 6px); | ||
188 | + margin: 2.7px; | ||
189 | + height: 36px; | ||
190 | + line-height: 20px; | ||
191 | + cursor: move; | ||
192 | + border: 1px solid @border-color; | ||
193 | + border-radius: 3px; | ||
194 | + | ||
195 | + &:hover { | ||
196 | + color: @primary-color; | ||
197 | + border: 1px solid @primary-color; | ||
198 | + position: relative; | ||
199 | + // z-index: 1; | ||
200 | + box-shadow: 0 2px 6px @primary-color; | ||
201 | + } | ||
202 | + } | ||
203 | + } | ||
204 | + } | ||
205 | + | ||
206 | + :deep(.node-panel) { | ||
207 | + box-shadow: 0 0 1px 1px #ccc; | ||
208 | + flex: 1; | ||
209 | + margin: 0 8px; | ||
210 | + overflow: hidden; | ||
211 | + | ||
212 | + .draggable-box { | ||
213 | + height: 100%; | ||
214 | + overflow: auto; | ||
215 | + | ||
216 | + .list-main { | ||
217 | + overflow: hidden; | ||
218 | + min-height: 100%; | ||
219 | + padding: 5px; | ||
220 | + position: relative; | ||
221 | + background: #fafafa; | ||
222 | + // border : 1px #ccc dashed; | ||
223 | + | ||
224 | + .moving { | ||
225 | + // 拖放移动中 | ||
226 | + // outline-width: 0; | ||
227 | + min-height: 35px; | ||
228 | + box-sizing: border-box; | ||
229 | + overflow: hidden; | ||
230 | + padding: 0 !important; | ||
231 | + // margin : 3px 0; | ||
232 | + position: relative; | ||
233 | + | ||
234 | + &::before { | ||
235 | + content: ''; | ||
236 | + height: 5px; | ||
237 | + width: 100%; | ||
238 | + background: @primary-color; | ||
239 | + position: absolute; | ||
240 | + top: 0; | ||
241 | + right: 0; | ||
242 | + } | ||
243 | + } | ||
244 | + | ||
245 | + .drag-move-box { | ||
246 | + position: relative; | ||
247 | + box-sizing: border-box; | ||
248 | + padding: 8px; | ||
249 | + overflow: hidden; | ||
250 | + transition: all 0.3s; | ||
251 | + min-height: 60px; | ||
252 | + | ||
253 | + &:hover { | ||
254 | + background: @primary-hover-bg-color; | ||
255 | + } | ||
256 | + | ||
257 | + // 选择时 start | ||
258 | + &::before { | ||
259 | + content: ''; | ||
260 | + height: 5px; | ||
261 | + width: 100%; | ||
262 | + background: @primary-color; | ||
263 | + position: absolute; | ||
264 | + top: 0; | ||
265 | + right: -100%; | ||
266 | + transition: all 0.3s; | ||
267 | + } | ||
268 | + | ||
269 | + &.active { | ||
270 | + background: @primary-hover-bg-color; | ||
271 | + outline-offset: 0; | ||
272 | + | ||
273 | + &::before { | ||
274 | + right: 0; | ||
275 | + } | ||
276 | + } | ||
277 | + | ||
278 | + // 选择时 end | ||
279 | + .form-item-box { | ||
280 | + position: relative; | ||
281 | + box-sizing: border-box; | ||
282 | + word-wrap: break-word; | ||
283 | + | ||
284 | + &::before { | ||
285 | + content: ''; | ||
286 | + position: absolute; | ||
287 | + width: 100%; | ||
288 | + height: 100%; | ||
289 | + top: 0; | ||
290 | + left: 0; | ||
291 | + // z-index: 888; | ||
292 | + } | ||
293 | + | ||
294 | + .ant-form-item { | ||
295 | + // 修改ant form-item的margin为padding | ||
296 | + margin: 0; | ||
297 | + padding-bottom: 6px; | ||
298 | + } | ||
299 | + } | ||
300 | + | ||
301 | + .show-key-box { | ||
302 | + // 显示key | ||
303 | + position: absolute; | ||
304 | + bottom: 2px; | ||
305 | + right: 5px; | ||
306 | + font-size: 14px; | ||
307 | + // z-index: 999; | ||
308 | + color: @primary-color; | ||
309 | + } | ||
310 | + | ||
311 | + .copy, | ||
312 | + .delete { | ||
313 | + position: absolute; | ||
314 | + top: 0; | ||
315 | + width: 30px; | ||
316 | + height: 30px; | ||
317 | + line-height: 30px; | ||
318 | + text-align: center; | ||
319 | + color: #fff; | ||
320 | + // z-index: 989; | ||
321 | + transition: all 0.3s; | ||
322 | + | ||
323 | + &.unactivated { | ||
324 | + opacity: 0 !important; | ||
325 | + pointer-events: none; | ||
326 | + } | ||
327 | + | ||
328 | + &.active { | ||
329 | + opacity: 1 !important; | ||
330 | + } | ||
331 | + } | ||
332 | + | ||
333 | + .copy { | ||
334 | + border-radius: 0 0 0 8px; | ||
335 | + right: 30px; | ||
336 | + background: @primary-color; | ||
337 | + } | ||
338 | + | ||
339 | + .delete { | ||
340 | + right: 0; | ||
341 | + background: @primary-color; | ||
342 | + } | ||
343 | + } | ||
344 | + | ||
345 | + .grid-box { | ||
346 | + position: relative; | ||
347 | + box-sizing: border-box; | ||
348 | + padding: 5px; | ||
349 | + background: @layout-background-color; | ||
350 | + width: 100%; | ||
351 | + transition: all 0.3s; | ||
352 | + overflow: hidden; | ||
353 | + | ||
354 | + .form-item-box { | ||
355 | + position: relative; | ||
356 | + box-sizing: border-box; | ||
357 | + | ||
358 | + .ant-form-item { | ||
359 | + // 修改ant form-item的margin为padding | ||
360 | + margin: 0; | ||
361 | + padding-bottom: 15px; | ||
362 | + } | ||
363 | + } | ||
364 | + | ||
365 | + .grid-row { | ||
366 | + background: @layout-background-color; | ||
367 | + | ||
368 | + .grid-col { | ||
369 | + .draggable-box { | ||
370 | + min-height: 80px; | ||
371 | + min-width: 50px; | ||
372 | + border: 1px #ccc dashed; | ||
373 | + background: #fff; | ||
374 | + | ||
375 | + .list-main { | ||
376 | + min-height: 83px; | ||
377 | + position: relative; | ||
378 | + border: 1px #ccc dashed; | ||
379 | + } | ||
380 | + } | ||
381 | + } | ||
382 | + } | ||
383 | + | ||
384 | + // 选择时 start | ||
385 | + &::before { | ||
386 | + content: ''; | ||
387 | + height: 5px; | ||
388 | + width: 100%; | ||
389 | + background: transparent; | ||
390 | + position: absolute; | ||
391 | + top: 0; | ||
392 | + right: -100%; | ||
393 | + transition: all 0.3s; | ||
394 | + } | ||
395 | + | ||
396 | + &.active { | ||
397 | + background: @layout-hover-bg-color; | ||
398 | + outline-offset: 0; | ||
399 | + | ||
400 | + &::before { | ||
401 | + background: @layout-color; | ||
402 | + right: 0; | ||
403 | + } | ||
404 | + } | ||
405 | + // 选择时 end | ||
406 | + > .copy-delete-box { | ||
407 | + > .copy, | ||
408 | + > .delete { | ||
409 | + position: absolute; | ||
410 | + top: 0; | ||
411 | + width: 30px; | ||
412 | + height: 30px; | ||
413 | + line-height: 30px; | ||
414 | + text-align: center; | ||
415 | + color: #fff; | ||
416 | + // z-index: 989; | ||
417 | + transition: all 0.3s; | ||
418 | + | ||
419 | + &.unactivated { | ||
420 | + opacity: 0 !important; | ||
421 | + pointer-events: none; | ||
422 | + } | ||
423 | + | ||
424 | + &.active { | ||
425 | + opacity: 1 !important; | ||
426 | + } | ||
427 | + } | ||
428 | + | ||
429 | + > .copy { | ||
430 | + border-radius: 0 0 0 8px; | ||
431 | + right: 30px; | ||
432 | + background: @layout-color; | ||
433 | + } | ||
434 | + | ||
435 | + > .delete { | ||
436 | + right: 0; | ||
437 | + background: @layout-color; | ||
438 | + } | ||
439 | + } | ||
440 | + } | ||
441 | + } | ||
442 | + } | ||
443 | + } | ||
444 | + } | ||
445 | + | ||
446 | + ::-webkit-scrollbar { | ||
447 | + /* 滚动条整体样式 */ | ||
448 | + width: 6px; | ||
449 | + | ||
450 | + /* 高宽分别对应横竖滚动条的尺寸 */ | ||
451 | + height: 6px; | ||
452 | + scrollbar-arrow-color: red; | ||
453 | + } | ||
454 | + | ||
455 | + ::-webkit-scrollbar-thumb { | ||
456 | + /* 滚动条里面小方块 */ | ||
457 | + border-radius: 5px; | ||
458 | + box-shadow: inset 0 0 5px rgb(0 0 0 / 20%); | ||
459 | + background: rgb(0 0 0 / 20%); | ||
460 | + scrollbar-arrow-color: red; | ||
461 | + } | ||
462 | + | ||
463 | + ::-webkit-scrollbar-track { | ||
464 | + /* 滚动条里面轨道 */ | ||
465 | + box-shadow: inset 0 0 5px rgb(0 0 0 / 20%); | ||
466 | + border-radius: 0; | ||
467 | + background: rgb(0 0 0 / 10%); | ||
468 | + } | ||
469 | +} | ||
470 | + | ||
471 | +// code盒子样式 | ||
472 | +.v-json-box { | ||
473 | + height: 570px; | ||
474 | + overflow: auto; | ||
475 | + | ||
476 | + .vue-codemirror-wrap { | ||
477 | + height: 100%; | ||
478 | + | ||
479 | + .CodeMirror-wrap { | ||
480 | + height: 100%; | ||
481 | + background: #f6f6f6; | ||
482 | + | ||
483 | + .CodeMirror-scroll { | ||
484 | + height: 100%; | ||
485 | + width: 100%; | ||
486 | + } | ||
487 | + | ||
488 | + pre.CodeMirror-line, | ||
489 | + .CodeMirror-linenumber { | ||
490 | + min-height: 21px; | ||
491 | + line-height: 21px; | ||
492 | + } | ||
493 | + } | ||
494 | + } | ||
495 | +} | ||
496 | + | ||
497 | +// code-modal盒子样式 | ||
498 | +.v-code-modal { | ||
499 | + .ant-modal-body { | ||
500 | + padding: 12px; | ||
501 | + } | ||
502 | +} | ||
503 | + | ||
504 | +.v-form-container { | ||
505 | + // 内联布局样式 | ||
506 | + .ant-form-inline { | ||
507 | + .list-main { | ||
508 | + display: flex; | ||
509 | + flex-wrap: wrap; | ||
510 | + justify-content: flex-start; | ||
511 | + align-content: flex-start; | ||
512 | + | ||
513 | + .layout-width { | ||
514 | + width: 100%; | ||
515 | + } | ||
516 | + } | ||
517 | + | ||
518 | + .ant-form-item-control-wrapper { | ||
519 | + min-width: 175px !important; | ||
520 | + } | ||
521 | + } | ||
522 | +} |
src/views/form-design/components/VFormDesign/styles/variable.less
0 → 100644
1 | +// 表单设计器样式 | ||
2 | +@primary-color: #13c2c2; | ||
3 | +@layout-color: #9867f7; | ||
4 | + | ||
5 | +@primary-background-color: fade(@primary-color, 6%); | ||
6 | +@primary-hover-bg-color: fade(@primary-color, 20%); | ||
7 | +@layout-background-color: fade(@layout-color, 12%); | ||
8 | +@layout-hover-bg-color: fade(@layout-color, 24%); | ||
9 | + | ||
10 | +@title-text-color: #fff; | ||
11 | +@border-color: #ccc; | ||
12 | + | ||
13 | +@left-right-width: 280px; | ||
14 | +@header-height: 56px; | ||
15 | +@operating-area-height: 45px; |
src/views/form-design/components/VFormItem/index.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/19 | ||
4 | + * @Description: | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <Col v-bind="colPropsComputed"> | ||
8 | + <FormItem v-bind="{ ...formItemProps }"> | ||
9 | + <template #label v-if="!formItemProps.hiddenLabel && schema.component !== 'Divider'"> | ||
10 | + <Tooltip> | ||
11 | + <span>{{ schema.label }}</span> | ||
12 | + <template #title v-if="schema.helpMessage" | ||
13 | + ><span>{{ schema.helpMessage }}</span></template | ||
14 | + > | ||
15 | + <Icon v-if="schema.helpMessage" class="ml-5" icon="ant-design:question-circle-outlined" /> | ||
16 | + </Tooltip> | ||
17 | + </template> | ||
18 | + | ||
19 | + <slot | ||
20 | + v-if="schema.componentProps && schema.componentProps?.slotName" | ||
21 | + :name="schema.componentProps.slotName" | ||
22 | + v-bind="schema" | ||
23 | + ></slot> | ||
24 | + <Divider | ||
25 | + v-else-if="schema.component == 'Divider' && schema.label && !formItemProps.hiddenLabel" | ||
26 | + >{{ schema.label }}</Divider | ||
27 | + > | ||
28 | + <!-- 部分控件需要一个空div --> | ||
29 | + <div | ||
30 | + ><component | ||
31 | + class="v-form-item-wrapper" | ||
32 | + :is="componentItem" | ||
33 | + v-bind="{ ...cmpProps, ...asyncProps }" | ||
34 | + :schema="schema" | ||
35 | + :style="schema.width ? { width: schema.width } : {}" | ||
36 | + @change="handleChange" | ||
37 | + @click="handleClick(schema)" | ||
38 | + /></div> | ||
39 | + | ||
40 | + <span v-if="['Button'].includes(schema.component)">{{ schema.label }}</span> | ||
41 | + </FormItem> | ||
42 | + </Col> | ||
43 | +</template> | ||
44 | +<script lang="ts"> | ||
45 | + import { defineComponent, reactive, toRefs, computed, PropType, unref } from 'vue'; | ||
46 | + import { componentMap } from '../../core/formItemConfig'; | ||
47 | + import { IVFormComponent, IFormConfig } from '../../typings/v-form-component'; | ||
48 | + import { asyncComputed } from '@vueuse/core'; | ||
49 | + import { handleAsyncOptions } from '../../utils'; | ||
50 | + import { omit } from 'lodash-es'; | ||
51 | + import { Tooltip, FormItem, Divider, Col } from 'ant-design-vue'; | ||
52 | + | ||
53 | + // import FormItem from '/@/components/Form/src/components/FormItem.vue'; | ||
54 | + | ||
55 | + import { Icon } from '/@/components/Icon'; | ||
56 | + import { useFormModelState } from '../../hooks/useFormDesignState'; | ||
57 | + export default defineComponent({ | ||
58 | + name: 'VFormItem', | ||
59 | + components: { | ||
60 | + Tooltip, | ||
61 | + Icon, | ||
62 | + FormItem, | ||
63 | + Divider, | ||
64 | + Col, | ||
65 | + }, | ||
66 | + | ||
67 | + props: { | ||
68 | + formData: { | ||
69 | + type: Object, | ||
70 | + default: () => ({}), | ||
71 | + }, | ||
72 | + schema: { | ||
73 | + type: Object as PropType<IVFormComponent>, | ||
74 | + required: true, | ||
75 | + }, | ||
76 | + formConfig: { | ||
77 | + type: Object as PropType<IFormConfig>, | ||
78 | + required: true, | ||
79 | + }, | ||
80 | + }, | ||
81 | + emits: ['update:form-data', 'change'], | ||
82 | + setup(props, { emit }) { | ||
83 | + const state = reactive({ | ||
84 | + componentMap, | ||
85 | + }); | ||
86 | + | ||
87 | + const { formModel: formData1, setFormModel } = useFormModelState(); | ||
88 | + const colPropsComputed = computed(() => { | ||
89 | + const { colProps = {} } = props.schema; | ||
90 | + return colProps; | ||
91 | + }); | ||
92 | + const formItemProps = computed(() => { | ||
93 | + const { formConfig } = unref(props); | ||
94 | + let { field, required, rules, labelCol, wrapperCol } = unref(props.schema); | ||
95 | + const { colon } = props.formConfig; | ||
96 | + | ||
97 | + const { itemProps } = unref(props.schema); | ||
98 | + | ||
99 | + //<editor-fold desc="布局属性"> | ||
100 | + labelCol = labelCol | ||
101 | + ? labelCol | ||
102 | + : formConfig.layout === 'horizontal' | ||
103 | + ? formConfig.labelLayout === 'flex' | ||
104 | + ? { style: `width:${formConfig.labelWidth}px` } | ||
105 | + : formConfig.labelCol | ||
106 | + : {}; | ||
107 | + | ||
108 | + wrapperCol = wrapperCol | ||
109 | + ? wrapperCol | ||
110 | + : formConfig.layout === 'horizontal' | ||
111 | + ? formConfig.labelLayout === 'flex' | ||
112 | + ? { style: 'width:auto;flex:1' } | ||
113 | + : formConfig.wrapperCol | ||
114 | + : {}; | ||
115 | + | ||
116 | + const style = | ||
117 | + formConfig.layout === 'horizontal' && formConfig.labelLayout === 'flex' | ||
118 | + ? { display: 'flex' } | ||
119 | + : {}; | ||
120 | + | ||
121 | + /** | ||
122 | + * 将字符串正则格式化成正则表达式 | ||
123 | + */ | ||
124 | + | ||
125 | + const newConfig = Object.assign( | ||
126 | + {}, | ||
127 | + { | ||
128 | + name: field, | ||
129 | + style: { ...style }, | ||
130 | + colon, | ||
131 | + required, | ||
132 | + rules, | ||
133 | + labelCol, | ||
134 | + wrapperCol, | ||
135 | + }, | ||
136 | + itemProps, | ||
137 | + ); | ||
138 | + if (!itemProps?.labelCol?.span) { | ||
139 | + newConfig.labelCol = labelCol; | ||
140 | + } | ||
141 | + if (!itemProps?.wrapperCol?.span) { | ||
142 | + newConfig.wrapperCol = wrapperCol; | ||
143 | + } | ||
144 | + if (!itemProps?.rules) { | ||
145 | + newConfig.rules = rules; | ||
146 | + } | ||
147 | + return newConfig; | ||
148 | + }) as Recordable; | ||
149 | + | ||
150 | + const componentItem = computed(() => componentMap.get(props.schema.component as string)); | ||
151 | + | ||
152 | + // console.log('component change:', props.schema.component, componentItem.value); | ||
153 | + const handleClick = (schema: IVFormComponent) => { | ||
154 | + if (schema.component === 'Button' && schema.componentProps?.handle) | ||
155 | + emit(schema.componentProps?.handle); | ||
156 | + }; | ||
157 | + /** | ||
158 | + * 处理异步属性,异步属性会导致一些属性渲染错误,如defaultValue异步加载会导致渲染不出来,故而此处只处理options,treeData,同步属性在cmpProps中处理 | ||
159 | + */ | ||
160 | + const asyncProps = asyncComputed(async () => { | ||
161 | + let { options, treeData } = props.schema.componentProps ?? {}; | ||
162 | + if (options) options = await handleAsyncOptions(options); | ||
163 | + if (treeData) treeData = await handleAsyncOptions(treeData); | ||
164 | + return { | ||
165 | + options, | ||
166 | + treeData, | ||
167 | + }; | ||
168 | + }); | ||
169 | + | ||
170 | + /** | ||
171 | + * 处理同步属性 | ||
172 | + */ | ||
173 | + const cmpProps = computed(() => { | ||
174 | + const isCheck = | ||
175 | + props.schema && ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component); | ||
176 | + let { field } = props.schema; | ||
177 | + | ||
178 | + let { disabled, ...attrs } = | ||
179 | + omit(props.schema.componentProps, ['options', 'treeData']) ?? {}; | ||
180 | + | ||
181 | + disabled = props.formConfig.disabled || disabled; | ||
182 | + | ||
183 | + return { | ||
184 | + ...attrs, | ||
185 | + disabled, | ||
186 | + [isCheck ? 'checked' : 'value']: formData1.value[field!], | ||
187 | + }; | ||
188 | + }); | ||
189 | + | ||
190 | + const handleChange = function (e) { | ||
191 | + const isCheck = ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component); | ||
192 | + const target = e ? e.target : null; | ||
193 | + const value = target ? (isCheck ? target.checked : target.value) : e; | ||
194 | + setFormModel(props.schema.field!, value); | ||
195 | + emit('change', value); | ||
196 | + }; | ||
197 | + return { | ||
198 | + ...toRefs(state), | ||
199 | + componentItem, | ||
200 | + formItemProps, | ||
201 | + handleClick, | ||
202 | + asyncProps, | ||
203 | + cmpProps, | ||
204 | + handleChange, | ||
205 | + colPropsComputed, | ||
206 | + }; | ||
207 | + }, | ||
208 | + }); | ||
209 | +</script> | ||
210 | + | ||
211 | +<style lang="less" scoped> | ||
212 | + .ml-5 { | ||
213 | + margin-left: 5px; | ||
214 | + } | ||
215 | + | ||
216 | + // form字段中的标签有ant-col,不能使用width:100% | ||
217 | + :deep(.ant-col) { | ||
218 | + width: auto; | ||
219 | + } | ||
220 | + | ||
221 | + .ant-form-item:not(.ant-form-item-with-help) { | ||
222 | + margin-bottom: 20px; | ||
223 | + } | ||
224 | + | ||
225 | + // .w-full { | ||
226 | + // width: 100% !important; | ||
227 | + // } | ||
228 | +</style> |
src/views/form-design/components/VFormItem/vFormItem.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/19 | ||
4 | + * @Description: | ||
5 | + `<FormItem` | ||
6 | + :tableAction="tableAction" | ||
7 | + :formActionType="formActionType" | ||
8 | + :schema="schema2" | ||
9 | + :formProps="getProps" | ||
10 | + :allDefaultValues="defaultValueRef" | ||
11 | + :formModel="formModel" | ||
12 | + :setFormModel="setFormModel" | ||
13 | + > | ||
14 | + | ||
15 | + <FormItem | ||
16 | + :tableAction="tableAction" | ||
17 | + :formActionType="formActionType" | ||
18 | + :schema="schemaNew" | ||
19 | + :formProps="getProps" | ||
20 | + :allDefaultValues="defaultValueRef" | ||
21 | + :formModel="formModel" | ||
22 | + > | ||
23 | +--> | ||
24 | + | ||
25 | +<template> | ||
26 | + <FormItem :schema="schemaNew" :formProps="getProps"> | ||
27 | + <template #[item]="data" v-for="item in Object.keys($slots)"> | ||
28 | + <slot :name="item" v-bind="data || {}"></slot> | ||
29 | + </template> | ||
30 | + </FormItem> | ||
31 | +</template> | ||
32 | +<script lang="ts"> | ||
33 | + import { computed, defineComponent, unref } from 'vue'; | ||
34 | + import { IFormConfig, IVFormComponent } from '../../typings/v-form-component'; | ||
35 | + import { FormProps, FormSchema } from '/@/components/Form'; | ||
36 | + | ||
37 | + import FormItem from '/@/components/Form/src/components/FormItem.vue'; | ||
38 | + | ||
39 | + export default defineComponent({ | ||
40 | + name: 'VFormItem', | ||
41 | + components: { | ||
42 | + FormItem, | ||
43 | + }, | ||
44 | + props: { | ||
45 | + formData: { | ||
46 | + type: Object, | ||
47 | + default: () => ({}), | ||
48 | + }, | ||
49 | + schema: { | ||
50 | + type: Object as PropType<IVFormComponent>, | ||
51 | + required: true, | ||
52 | + }, | ||
53 | + formConfig: { | ||
54 | + type: Object as PropType<IFormConfig>, | ||
55 | + required: true, | ||
56 | + }, | ||
57 | + }, | ||
58 | + setup(props) { | ||
59 | + const schema = computed(() => { | ||
60 | + const schema: FormSchema = { | ||
61 | + ...unref(props.schema), | ||
62 | + } as FormSchema; | ||
63 | + | ||
64 | + return schema; | ||
65 | + }); | ||
66 | + | ||
67 | + // Get the basic configuration of the form | ||
68 | + const getProps = computed((): FormProps => { | ||
69 | + return { ...unref(props.formConfig) } as FormProps; | ||
70 | + }); | ||
71 | + return { | ||
72 | + schemaNew: schema, | ||
73 | + getProps, | ||
74 | + }; | ||
75 | + }, | ||
76 | + }); | ||
77 | +</script> | ||
78 | + | ||
79 | +<style lang="less" scoped></style> |
src/views/form-design/components/VFormPreview/index.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/29 | ||
4 | + * @Description: 渲染组件,无法使用Vben的组件 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <Modal | ||
8 | + title="预览(标准Ant控件)" | ||
9 | + :visible="visible" | ||
10 | + @ok="handleGetData" | ||
11 | + @cancel="handleCancel" | ||
12 | + okText="获取数据" | ||
13 | + cancelText="关闭" | ||
14 | + style="top: 20px" | ||
15 | + :destroyOnClose="true" | ||
16 | + :width="900" | ||
17 | + > | ||
18 | + <VFormCreate | ||
19 | + :form-config="formConfig" | ||
20 | + v-model:fApi="fApi" | ||
21 | + v-model:formModel="formModel" | ||
22 | + @submit="onSubmit" | ||
23 | + > | ||
24 | + <template #slotName="{ formModel, field }"> | ||
25 | + <a-input v-model:value="formModel[field]" placeholder="我是插槽传递的输入框" /> | ||
26 | + </template> | ||
27 | + </VFormCreate> | ||
28 | + <JsonModal ref="jsonModal" /> | ||
29 | + </Modal> | ||
30 | +</template> | ||
31 | +<script lang="ts"> | ||
32 | + import { defineComponent, reactive, ref, toRaw, toRefs } from 'vue'; | ||
33 | + import { IFormConfig } from '../../typings/v-form-component'; | ||
34 | + import { IAnyObject } from '../../typings/base-type'; | ||
35 | + import VFormCreate from '../VFormCreate/index.vue'; | ||
36 | + import { formatRules } from '../../utils'; | ||
37 | + import { IVFormMethods } from '../../hooks/useVFormMethods'; | ||
38 | + import JsonModal from '../VFormDesign/components/JsonModal.vue'; | ||
39 | + import { IToolbarMethods } from '../../typings/form-type'; | ||
40 | + import { Modal } from 'ant-design-vue'; | ||
41 | + export default defineComponent({ | ||
42 | + name: 'VFormPreview', | ||
43 | + components: { | ||
44 | + JsonModal, | ||
45 | + VFormCreate, | ||
46 | + Modal, | ||
47 | + }, | ||
48 | + setup() { | ||
49 | + const jsonModal = ref<IToolbarMethods | null>(null); | ||
50 | + const state = reactive<{ | ||
51 | + formModel: IAnyObject; | ||
52 | + visible: boolean; | ||
53 | + formConfig: IFormConfig; | ||
54 | + fApi: IVFormMethods; | ||
55 | + }>({ | ||
56 | + formModel: {}, | ||
57 | + formConfig: {} as IFormConfig, | ||
58 | + visible: false, | ||
59 | + fApi: {} as IVFormMethods, | ||
60 | + }); | ||
61 | + | ||
62 | + /** | ||
63 | + * 显示Json数据弹框 | ||
64 | + * @param jsonData | ||
65 | + */ | ||
66 | + const showModal = (jsonData: IFormConfig) => { | ||
67 | + // console.log('showModal-', jsonData); | ||
68 | + formatRules(jsonData.schemas); | ||
69 | + state.formConfig = jsonData; | ||
70 | + state.visible = true; | ||
71 | + }; | ||
72 | + | ||
73 | + /** | ||
74 | + * 获取表单数据 | ||
75 | + * @return {Promise<void>} | ||
76 | + */ | ||
77 | + const handleCancel = () => { | ||
78 | + // console.log('handleCancel'); | ||
79 | + state.visible = false; | ||
80 | + state.formModel = {}; | ||
81 | + }; | ||
82 | + const handleGetData = async () => { | ||
83 | + // console.log('handleGetData'); | ||
84 | + console.log(toRaw(state.formModel)); | ||
85 | + const _data = await state.fApi.submit?.(); | ||
86 | + // console.log('handleGetData', 'end'); | ||
87 | + jsonModal.value?.showModal?.(_data); | ||
88 | + // jsonModal.value?.showModal?.(toRaw(state.formModel)); | ||
89 | + }; | ||
90 | + | ||
91 | + const onSubmit = (_data: IAnyObject) => { | ||
92 | + // console.log('-> data', data); | ||
93 | + }; | ||
94 | + const onCancel = () => { | ||
95 | + state.formModel = {}; | ||
96 | + }; | ||
97 | + return { | ||
98 | + handleGetData, | ||
99 | + handleCancel, | ||
100 | + ...toRefs(state), | ||
101 | + showModal, | ||
102 | + jsonModal, | ||
103 | + onSubmit, | ||
104 | + onCancel, | ||
105 | + }; | ||
106 | + }, | ||
107 | + }); | ||
108 | +</script> |
src/views/form-design/components/VFormPreview/useForm.vue
0 → 100644
1 | +<!-- | ||
2 | + * @Author: ypt | ||
3 | + * @Date: 2021/11/29 | ||
4 | + * @Description: 使用vbenForm的功能进行渲染 | ||
5 | +--> | ||
6 | +<template> | ||
7 | + <Modal | ||
8 | + title="预览(VbenForm)" | ||
9 | + :visible="state.visible" | ||
10 | + @ok="handleGetData" | ||
11 | + @cancel="handleCancel" | ||
12 | + okText="获取数据" | ||
13 | + cancelText="关闭" | ||
14 | + style="top: 20px" | ||
15 | + :destroyOnClose="true" | ||
16 | + :width="900" | ||
17 | + > | ||
18 | + <BasicForm v-bind="attrs" @register="registerForm" /> | ||
19 | + <JsonModal ref="jsonModal" /> | ||
20 | + </Modal> | ||
21 | +</template> | ||
22 | +<script lang="ts" setup> | ||
23 | + import { BasicForm, useForm } from '/@/components/Form/index'; | ||
24 | + import { reactive, ref, computed } from 'vue'; | ||
25 | + import { IFormConfig } from '../../typings/v-form-component'; | ||
26 | + import { IAnyObject } from '../../typings/base-type'; | ||
27 | + import JsonModal from '../VFormDesign/components/JsonModal.vue'; | ||
28 | + import { IToolbarMethods } from '../../typings/form-type'; | ||
29 | + import { Modal } from 'ant-design-vue'; | ||
30 | + | ||
31 | + const jsonModal = ref<IToolbarMethods | null>(null); | ||
32 | + const state = reactive<{ | ||
33 | + formModel: IAnyObject; | ||
34 | + visible: boolean; | ||
35 | + formConfig: IFormConfig; | ||
36 | + }>({ | ||
37 | + formModel: {}, | ||
38 | + formConfig: {} as IFormConfig, | ||
39 | + visible: false, | ||
40 | + }); | ||
41 | + | ||
42 | + const attrs = computed(() => { | ||
43 | + return { | ||
44 | + ...state.formConfig, | ||
45 | + } as Recordable; | ||
46 | + }); | ||
47 | + | ||
48 | + /** | ||
49 | + * 显示Json数据弹框 | ||
50 | + * @param jsonData | ||
51 | + */ | ||
52 | + const showModal = (jsonData: IFormConfig) => { | ||
53 | + state.formConfig = jsonData; | ||
54 | + state.visible = true; | ||
55 | + }; | ||
56 | + | ||
57 | + //表单 | ||
58 | + const [registerForm, { validate }] = useForm(); | ||
59 | + | ||
60 | + const handleCancel = () => { | ||
61 | + state.visible = false; | ||
62 | + }; | ||
63 | + /** | ||
64 | + * 获取表单数据 | ||
65 | + * @return {Promise<void>} | ||
66 | + */ | ||
67 | + const handleGetData = async () => { | ||
68 | + let data = await validate(); | ||
69 | + console.log(data); | ||
70 | + jsonModal.value?.showModal?.(data); | ||
71 | + }; | ||
72 | + | ||
73 | + defineExpose({ showModal }); | ||
74 | +</script> |
src/views/form-design/components/index.ts
0 → 100644
1 | +import type { Component } from 'vue'; | ||
2 | +import { ComponentType } from '/@/components/Form/src/types'; | ||
3 | +import { IconPicker } from '/@/components/Icon/index'; | ||
4 | +/** | ||
5 | + * Component list, register here to setting it in the form | ||
6 | + */ | ||
7 | +import { | ||
8 | + Input, | ||
9 | + Button, | ||
10 | + Select, | ||
11 | + Radio, | ||
12 | + Checkbox, | ||
13 | + AutoComplete, | ||
14 | + Cascader, | ||
15 | + DatePicker, | ||
16 | + InputNumber, | ||
17 | + Switch, | ||
18 | + TimePicker, | ||
19 | + // ColorPicker, | ||
20 | + TreeSelect, | ||
21 | + Slider, | ||
22 | + Rate, | ||
23 | + Divider, | ||
24 | + Calendar, | ||
25 | + Transfer, | ||
26 | +} from 'ant-design-vue'; | ||
27 | + | ||
28 | +//ant-desing本身的Form控件库 | ||
29 | + | ||
30 | +const componentMap = new Map<string, Component>(); | ||
31 | +componentMap.set('Radio', Radio); | ||
32 | +componentMap.set('Button', Button); | ||
33 | +componentMap.set('Calendar', Calendar); | ||
34 | +componentMap.set('Input', Input); | ||
35 | +componentMap.set('InputGroup', Input.Group); | ||
36 | +componentMap.set('InputPassword', Input.Password); | ||
37 | +componentMap.set('InputSearch', Input.Search); | ||
38 | +componentMap.set('InputTextArea', Input.TextArea); | ||
39 | +componentMap.set('InputNumber', InputNumber); | ||
40 | +componentMap.set('AutoComplete', AutoComplete); | ||
41 | + | ||
42 | +componentMap.set('Select', Select); | ||
43 | +componentMap.set('TreeSelect', TreeSelect); | ||
44 | +componentMap.set('Switch', Switch); | ||
45 | +componentMap.set('RadioGroup', Radio.Group); | ||
46 | +componentMap.set('Checkbox', Checkbox); | ||
47 | +componentMap.set('CheckboxGroup', Checkbox.Group); | ||
48 | +componentMap.set('Cascader', Cascader); | ||
49 | +componentMap.set('Slider', Slider); | ||
50 | +componentMap.set('Rate', Rate); | ||
51 | +componentMap.set('Transfer', Transfer); | ||
52 | +componentMap.set('DatePicker', DatePicker); | ||
53 | +componentMap.set('MonthPicker', DatePicker.MonthPicker); | ||
54 | +componentMap.set('RangePicker', DatePicker.RangePicker); | ||
55 | +componentMap.set('WeekPicker', DatePicker.WeekPicker); | ||
56 | +componentMap.set('TimePicker', TimePicker); | ||
57 | + | ||
58 | +componentMap.set('ColorPicker', TimePicker); | ||
59 | + | ||
60 | +componentMap.set('IconPicker', IconPicker); | ||
61 | +componentMap.set('Divider', Divider); | ||
62 | + | ||
63 | +export function add(compName: ComponentType, component: Component) { | ||
64 | + componentMap.set(compName, component); | ||
65 | +} | ||
66 | + | ||
67 | +export function del(compName: ComponentType) { | ||
68 | + componentMap.delete(compName); | ||
69 | +} | ||
70 | + | ||
71 | +export { componentMap }; |
src/views/form-design/core/formItemConfig.ts
0 → 100644
1 | +/** | ||
2 | + * @name: formItemConfig | ||
3 | + * @author: ypt | ||
4 | + * @date: 2021/11/18 16:25 | ||
5 | + * @description:表单配置 | ||
6 | + */ | ||
7 | +import { IVFormComponent } from '../typings/v-form-component'; | ||
8 | +import { isArray } from 'lodash-es'; | ||
9 | +import { componentMap as VbenCmp, add } from '/@/components/Form/src/componentMap'; | ||
10 | +import { ComponentType } from '/@/components/Form/src/types'; | ||
11 | + | ||
12 | +import { componentMap as Cmp } from '../components'; | ||
13 | +import { Component } from 'vue'; | ||
14 | + | ||
15 | +const componentMap = new Map<string, Component>(); | ||
16 | + | ||
17 | +//如果有其它控件,可以在这里初始化 | ||
18 | + | ||
19 | +//注册Ant控件库 | ||
20 | +Cmp.forEach((value, key) => { | ||
21 | + componentMap.set(key, value); | ||
22 | + if (VbenCmp[key] == null) { | ||
23 | + add(key as ComponentType, value); | ||
24 | + } | ||
25 | +}); | ||
26 | +//注册vben控件库 | ||
27 | +VbenCmp.forEach((value, key) => { | ||
28 | + componentMap.set(key, value); | ||
29 | +}); | ||
30 | + | ||
31 | +export { componentMap }; | ||
32 | + | ||
33 | +/** | ||
34 | + * 设置自定义表单控件 | ||
35 | + * @param {IVFormComponent | IVFormComponent[]} config | ||
36 | + */ | ||
37 | +export function setFormDesignComponents(config: IVFormComponent | IVFormComponent[]) { | ||
38 | + if (isArray(config)) { | ||
39 | + config.forEach((item) => { | ||
40 | + const { componentInstance: component, ...rest } = item; | ||
41 | + componentMap[item.component] = component; | ||
42 | + customComponents.push(Object.assign({ props: {} }, rest)); | ||
43 | + }); | ||
44 | + } else { | ||
45 | + const { componentInstance: component, ...rest } = config; | ||
46 | + componentMap[config.component] = component; | ||
47 | + customComponents.push(Object.assign({ props: {} }, rest)); | ||
48 | + } | ||
49 | +} | ||
50 | + | ||
51 | +//外部设置的自定义控件 | ||
52 | +export const customComponents: IVFormComponent[] = []; | ||
53 | + | ||
54 | +// 左侧控件列表与初始化的控件属性 | ||
55 | +// props.slotName,会在formitem级别生成一个slot,并绑定当前record值 | ||
56 | +// 属性props,类型为对象,不能为undefined或是null。 | ||
57 | +export const baseComponents: IVFormComponent[] = [ | ||
58 | + { | ||
59 | + component: 'InputCountDown', | ||
60 | + label: '倒计时输入', | ||
61 | + icon: 'line-md:iconify2', | ||
62 | + colProps: { span: 24 }, | ||
63 | + field: '', | ||
64 | + componentProps: {}, | ||
65 | + }, | ||
66 | + { | ||
67 | + component: 'IconPicker', | ||
68 | + label: '图标选择器', | ||
69 | + icon: 'line-md:iconify2', | ||
70 | + colProps: { span: 24 }, | ||
71 | + field: '', | ||
72 | + componentProps: {}, | ||
73 | + }, | ||
74 | + { | ||
75 | + component: 'StrengthMeter', | ||
76 | + label: '密码强度', | ||
77 | + icon: 'wpf:password1', | ||
78 | + colProps: { span: 24 }, | ||
79 | + field: '', | ||
80 | + componentProps: {}, | ||
81 | + }, | ||
82 | + { | ||
83 | + component: 'AutoComplete', | ||
84 | + label: '自动完成', | ||
85 | + icon: 'wpf:password1', | ||
86 | + colProps: { span: 24 }, | ||
87 | + field: '', | ||
88 | + componentProps: { | ||
89 | + placeholder: '请输入正则表达式', | ||
90 | + options: [ | ||
91 | + { | ||
92 | + value: '/^(?:(?:\\+|00)86)?1[3-9]\\d{9}$/', | ||
93 | + label: '手机号码', | ||
94 | + }, | ||
95 | + { | ||
96 | + value: '/^((ht|f)tps?:\\/\\/)?[\\w-]+(\\.[\\w-]+)+:\\d{1,5}\\/?$/', | ||
97 | + label: '网址带端口号', | ||
98 | + }, | ||
99 | + ], | ||
100 | + }, | ||
101 | + }, | ||
102 | + { | ||
103 | + component: 'Divider', | ||
104 | + label: '分割线', | ||
105 | + icon: 'radix-icons:divider-horizontal', | ||
106 | + colProps: { span: 24 }, | ||
107 | + field: '', | ||
108 | + componentProps: { | ||
109 | + orientation: 'center', | ||
110 | + dashed: true, | ||
111 | + }, | ||
112 | + }, | ||
113 | + { | ||
114 | + component: 'Checkbox', | ||
115 | + label: '复选框', | ||
116 | + icon: 'ant-design:check-circle-outlined', | ||
117 | + colProps: { span: 24 }, | ||
118 | + field: '', | ||
119 | + }, | ||
120 | + { | ||
121 | + component: 'CheckboxGroup', | ||
122 | + label: '复选框-组', | ||
123 | + icon: 'ant-design:check-circle-filled', | ||
124 | + field: '', | ||
125 | + colProps: { span: 24 }, | ||
126 | + componentProps: { | ||
127 | + options: [ | ||
128 | + { | ||
129 | + label: '选项1', | ||
130 | + value: '1', | ||
131 | + }, | ||
132 | + { | ||
133 | + label: '选项2', | ||
134 | + value: '2', | ||
135 | + }, | ||
136 | + ], | ||
137 | + }, | ||
138 | + }, | ||
139 | + { | ||
140 | + component: 'Input', | ||
141 | + label: '输入框', | ||
142 | + icon: 'bi:input-cursor-text', | ||
143 | + field: '', | ||
144 | + colProps: { span: 24 }, | ||
145 | + componentProps: { | ||
146 | + type: 'text', | ||
147 | + }, | ||
148 | + }, | ||
149 | + { | ||
150 | + component: 'InputNumber', | ||
151 | + label: '数字输入框', | ||
152 | + icon: 'ant-design:field-number-outlined', | ||
153 | + field: '', | ||
154 | + colProps: { span: 24 }, | ||
155 | + componentProps: { style: 'width:200px' }, | ||
156 | + }, | ||
157 | + { | ||
158 | + component: 'InputTextArea', | ||
159 | + label: '文本域', | ||
160 | + icon: 'ant-design:file-text-filled', | ||
161 | + field: '', | ||
162 | + colProps: { span: 24 }, | ||
163 | + componentProps: {}, | ||
164 | + }, | ||
165 | + { | ||
166 | + component: 'Select', | ||
167 | + label: '下拉选择', | ||
168 | + icon: 'gg:select', | ||
169 | + field: '', | ||
170 | + colProps: { span: 24 }, | ||
171 | + componentProps: { | ||
172 | + options: [ | ||
173 | + { | ||
174 | + label: '选项1', | ||
175 | + value: '1', | ||
176 | + }, | ||
177 | + { | ||
178 | + label: '选项2', | ||
179 | + value: '2', | ||
180 | + }, | ||
181 | + ], | ||
182 | + }, | ||
183 | + }, | ||
184 | + | ||
185 | + { | ||
186 | + component: 'Radio', | ||
187 | + label: '单选框', | ||
188 | + icon: 'ant-design:check-circle-outlined', | ||
189 | + field: '', | ||
190 | + colProps: { span: 24 }, | ||
191 | + componentProps: {}, | ||
192 | + }, | ||
193 | + { | ||
194 | + component: 'RadioGroup', | ||
195 | + label: '单选框-组', | ||
196 | + icon: 'carbon:radio-button-checked', | ||
197 | + field: '', | ||
198 | + colProps: { span: 24 }, | ||
199 | + componentProps: { | ||
200 | + options: [ | ||
201 | + { | ||
202 | + label: '选项1', | ||
203 | + value: '1', | ||
204 | + }, | ||
205 | + { | ||
206 | + label: '选项2', | ||
207 | + value: '2', | ||
208 | + }, | ||
209 | + ], | ||
210 | + }, | ||
211 | + }, | ||
212 | + { | ||
213 | + component: 'DatePicker', | ||
214 | + label: '日期选择', | ||
215 | + icon: 'healthicons:i-schedule-school-date-time-outline', | ||
216 | + field: '', | ||
217 | + colProps: { span: 24 }, | ||
218 | + componentProps: {}, | ||
219 | + }, | ||
220 | + { | ||
221 | + component: 'RangePicker', | ||
222 | + label: '日期范围', | ||
223 | + icon: 'healthicons:i-schedule-school-date-time-outline', | ||
224 | + field: '', | ||
225 | + colProps: { span: 24 }, | ||
226 | + componentProps: { | ||
227 | + placeholder: ['开始日期', '结束日期'], | ||
228 | + }, | ||
229 | + }, | ||
230 | + { | ||
231 | + component: 'MonthPicker', | ||
232 | + label: '月份选择', | ||
233 | + icon: 'healthicons:i-schedule-school-date-time-outline', | ||
234 | + field: '', | ||
235 | + colProps: { span: 24 }, | ||
236 | + componentProps: { | ||
237 | + placeholder: '请选择月份', | ||
238 | + }, | ||
239 | + }, | ||
240 | + { | ||
241 | + component: 'TimePicker', | ||
242 | + label: '时间选择', | ||
243 | + icon: 'healthicons:i-schedule-school-date-time', | ||
244 | + field: '', | ||
245 | + colProps: { span: 24 }, | ||
246 | + componentProps: {}, | ||
247 | + }, | ||
248 | + { | ||
249 | + component: 'Slider', | ||
250 | + label: '滑动输入条', | ||
251 | + icon: 'vaadin:slider', | ||
252 | + field: '', | ||
253 | + colProps: { span: 24 }, | ||
254 | + componentProps: {}, | ||
255 | + }, | ||
256 | + { | ||
257 | + component: 'Rate', | ||
258 | + label: '评分', | ||
259 | + icon: 'ic:outline-star-rate', | ||
260 | + field: '', | ||
261 | + colProps: { span: 24 }, | ||
262 | + componentProps: {}, | ||
263 | + }, | ||
264 | + { | ||
265 | + component: 'Switch', | ||
266 | + label: '开关', | ||
267 | + icon: 'entypo:switch', | ||
268 | + field: '', | ||
269 | + colProps: { span: 24 }, | ||
270 | + componentProps: {}, | ||
271 | + }, | ||
272 | + { | ||
273 | + component: 'TreeSelect', | ||
274 | + label: '树形选择', | ||
275 | + icon: 'clarity:tree-view-line', | ||
276 | + field: '', | ||
277 | + colProps: { span: 24 }, | ||
278 | + componentProps: { | ||
279 | + treeData: [ | ||
280 | + { | ||
281 | + label: '选项1', | ||
282 | + value: '1', | ||
283 | + children: [ | ||
284 | + { | ||
285 | + label: '选项三', | ||
286 | + value: '1-1', | ||
287 | + }, | ||
288 | + ], | ||
289 | + }, | ||
290 | + { | ||
291 | + label: '选项2', | ||
292 | + value: '2', | ||
293 | + }, | ||
294 | + ], | ||
295 | + }, | ||
296 | + }, | ||
297 | + { | ||
298 | + component: 'Upload', | ||
299 | + label: '上传', | ||
300 | + icon: 'ant-design:upload-outlined', | ||
301 | + field: '', | ||
302 | + colProps: { span: 24 }, | ||
303 | + componentProps: { | ||
304 | + api: () => 1, | ||
305 | + }, | ||
306 | + }, | ||
307 | + { | ||
308 | + component: 'Cascader', | ||
309 | + label: '级联选择', | ||
310 | + icon: 'ant-design:check-outlined', | ||
311 | + field: '', | ||
312 | + colProps: { span: 24 }, | ||
313 | + componentProps: { | ||
314 | + options: [ | ||
315 | + { | ||
316 | + label: '选项1', | ||
317 | + value: '1', | ||
318 | + children: [ | ||
319 | + { | ||
320 | + label: '选项三', | ||
321 | + value: '1-1', | ||
322 | + }, | ||
323 | + ], | ||
324 | + }, | ||
325 | + { | ||
326 | + label: '选项2', | ||
327 | + value: '2', | ||
328 | + }, | ||
329 | + ], | ||
330 | + }, | ||
331 | + }, | ||
332 | + // { | ||
333 | + // component: 'Button', | ||
334 | + // label: '按钮', | ||
335 | + // icon: 'dashicons:button', | ||
336 | + // field: '', | ||
337 | + // colProps: { span: 24 }, | ||
338 | + // hiddenLabel: true, | ||
339 | + // componentProps: {}, | ||
340 | + // }, | ||
341 | + { | ||
342 | + component: 'ColorPicker', | ||
343 | + label: '颜色选择器', | ||
344 | + icon: 'carbon:color-palette', | ||
345 | + field: '', | ||
346 | + colProps: { span: 24 }, | ||
347 | + componentProps: { | ||
348 | + defaultValue: '', | ||
349 | + value: '', | ||
350 | + }, | ||
351 | + }, | ||
352 | + | ||
353 | + { | ||
354 | + component: 'slot', | ||
355 | + label: '插槽', | ||
356 | + icon: 'vs:timeslot-question', | ||
357 | + field: '', | ||
358 | + colProps: { span: 24 }, | ||
359 | + componentProps: { | ||
360 | + slotName: 'slotName', | ||
361 | + }, | ||
362 | + }, | ||
363 | +]; | ||
364 | + | ||
365 | +// https://next.antdv.com/components/transfer-cn | ||
366 | +const transferControl = { | ||
367 | + component: 'Transfer', | ||
368 | + label: '穿梭框', | ||
369 | + icon: 'bx:bx-transfer-alt', | ||
370 | + field: '', | ||
371 | + colProps: { span: 24 }, | ||
372 | + componentProps: { | ||
373 | + render: (item) => item.title, | ||
374 | + dataSource: [ | ||
375 | + { | ||
376 | + key: 'key-1', | ||
377 | + title: '标题1', | ||
378 | + description: '描述', | ||
379 | + disabled: false, | ||
380 | + chosen: true, | ||
381 | + }, | ||
382 | + { | ||
383 | + key: 'key-2', | ||
384 | + title: 'title2', | ||
385 | + description: 'description2', | ||
386 | + disabled: true, | ||
387 | + }, | ||
388 | + { | ||
389 | + key: 'key-3', | ||
390 | + title: '标题3', | ||
391 | + description: '描述3', | ||
392 | + disabled: false, | ||
393 | + chosen: true, | ||
394 | + }, | ||
395 | + ], | ||
396 | + }, | ||
397 | +}; | ||
398 | + | ||
399 | +baseComponents.push(transferControl); | ||
400 | + | ||
401 | +export const layoutComponents: IVFormComponent[] = [ | ||
402 | + { | ||
403 | + field: '', | ||
404 | + component: 'Grid', | ||
405 | + label: '栅格布局', | ||
406 | + icon: 'icon-grid', | ||
407 | + componentProps: {}, | ||
408 | + columns: [ | ||
409 | + { | ||
410 | + span: 12, | ||
411 | + children: [], | ||
412 | + }, | ||
413 | + { | ||
414 | + span: 12, | ||
415 | + children: [], | ||
416 | + }, | ||
417 | + ], | ||
418 | + colProps: { span: 24 }, | ||
419 | + options: { | ||
420 | + gutter: 0, | ||
421 | + }, | ||
422 | + }, | ||
423 | +]; |
src/views/form-design/core/iconConfig.ts
0 → 100644
1 | +const iconConfig = { | ||
2 | + filled: [ | ||
3 | + 'account-book', | ||
4 | + 'alert', | ||
5 | + 'alipay-circle', | ||
6 | + 'alipay-square', | ||
7 | + 'aliwangwang', | ||
8 | + 'amazon-circle', | ||
9 | + 'android', | ||
10 | + 'amazon-square', | ||
11 | + 'api', | ||
12 | + 'appstore', | ||
13 | + 'audio', | ||
14 | + 'apple', | ||
15 | + 'backward', | ||
16 | + 'bank', | ||
17 | + 'behance-circle', | ||
18 | + 'bell', | ||
19 | + 'behance-square', | ||
20 | + 'book', | ||
21 | + 'box-plot', | ||
22 | + 'bug', | ||
23 | + 'bulb', | ||
24 | + 'calculator', | ||
25 | + 'build', | ||
26 | + 'calendar', | ||
27 | + 'camera', | ||
28 | + 'car', | ||
29 | + 'caret-down', | ||
30 | + 'caret-left', | ||
31 | + 'caret-right', | ||
32 | + 'carry-out', | ||
33 | + 'caret-up', | ||
34 | + 'check-circle', | ||
35 | + 'check-square', | ||
36 | + 'chrome', | ||
37 | + 'ci-circle', | ||
38 | + 'clock-circle', | ||
39 | + 'close-circle', | ||
40 | + 'cloud', | ||
41 | + 'close-square', | ||
42 | + 'code-sandbox-square', | ||
43 | + 'code-sandbox-circle', | ||
44 | + 'code', | ||
45 | + 'codepen-circle', | ||
46 | + 'compass', | ||
47 | + 'codepen-square', | ||
48 | + 'contacts', | ||
49 | + 'container', | ||
50 | + 'control', | ||
51 | + 'copy', | ||
52 | + 'copyright-circle', | ||
53 | + 'credit-card', | ||
54 | + 'crown', | ||
55 | + 'customer-service', | ||
56 | + 'dashboard', | ||
57 | + 'delete', | ||
58 | + 'diff', | ||
59 | + 'dingtalk-circle', | ||
60 | + 'database', | ||
61 | + 'dingtalk-square', | ||
62 | + 'dislike', | ||
63 | + 'dollar-circle', | ||
64 | + 'down-circle', | ||
65 | + 'down-square', | ||
66 | + 'dribbble-circle', | ||
67 | + 'dribbble-square', | ||
68 | + 'dropbox-circle', | ||
69 | + 'dropbox-square', | ||
70 | + 'environment', | ||
71 | + 'edit', | ||
72 | + 'exclamation-circle', | ||
73 | + 'euro-circle', | ||
74 | + 'experiment', | ||
75 | + 'eye-invisible', | ||
76 | + 'eye', | ||
77 | + 'facebook', | ||
78 | + 'fast-backward', | ||
79 | + 'fast-forward', | ||
80 | + 'file-add', | ||
81 | + 'file-excel', | ||
82 | + 'file-exclamation', | ||
83 | + 'file-image', | ||
84 | + 'file-markdown', | ||
85 | + 'file-pdf', | ||
86 | + 'file-ppt', | ||
87 | + 'file-text', | ||
88 | + 'file-unknown', | ||
89 | + 'file-word', | ||
90 | + 'file-zip', | ||
91 | + 'file', | ||
92 | + 'filter', | ||
93 | + 'fire', | ||
94 | + 'flag', | ||
95 | + 'folder-add', | ||
96 | + 'folder', | ||
97 | + 'folder-open', | ||
98 | + 'forward', | ||
99 | + 'frown', | ||
100 | + 'fund', | ||
101 | + 'funnel-plot', | ||
102 | + 'gift', | ||
103 | + 'github', | ||
104 | + 'gitlab', | ||
105 | + 'golden', | ||
106 | + 'google-circle', | ||
107 | + 'google-plus-circle', | ||
108 | + 'google-plus-square', | ||
109 | + 'google-square', | ||
110 | + 'hdd', | ||
111 | + 'heart', | ||
112 | + 'highlight', | ||
113 | + 'home', | ||
114 | + 'hourglass', | ||
115 | + 'html5', | ||
116 | + 'idcard', | ||
117 | + 'ie-circle', | ||
118 | + 'ie-square', | ||
119 | + 'info-circle', | ||
120 | + 'instagram', | ||
121 | + 'insurance', | ||
122 | + 'interaction', | ||
123 | + 'interation', | ||
124 | + 'layout', | ||
125 | + 'left-circle', | ||
126 | + 'left-square', | ||
127 | + 'like', | ||
128 | + 'linkedin', | ||
129 | + 'lock', | ||
130 | + 'mail', | ||
131 | + 'medicine-box', | ||
132 | + 'medium-circle', | ||
133 | + 'medium-square', | ||
134 | + 'meh', | ||
135 | + 'message', | ||
136 | + 'minus-circle', | ||
137 | + 'minus-square', | ||
138 | + 'mobile', | ||
139 | + 'money-collect', | ||
140 | + 'pause-circle', | ||
141 | + 'pay-circle', | ||
142 | + 'notification', | ||
143 | + 'phone', | ||
144 | + 'picture', | ||
145 | + 'pie-chart', | ||
146 | + 'play-circle', | ||
147 | + 'play-square', | ||
148 | + 'plus-circle', | ||
149 | + 'plus-square', | ||
150 | + 'pound-circle', | ||
151 | + 'printer', | ||
152 | + 'profile', | ||
153 | + 'project', | ||
154 | + 'pushpin', | ||
155 | + 'property-safety', | ||
156 | + 'qq-circle', | ||
157 | + 'qq-square', | ||
158 | + 'question-circle', | ||
159 | + 'read', | ||
160 | + 'reconciliation', | ||
161 | + 'red-envelope', | ||
162 | + 'reddit-circle', | ||
163 | + 'reddit-square', | ||
164 | + 'rest', | ||
165 | + 'right-circle', | ||
166 | + 'rocket', | ||
167 | + 'right-square', | ||
168 | + 'safety-certificate', | ||
169 | + 'save', | ||
170 | + 'schedule', | ||
171 | + 'security-scan', | ||
172 | + 'setting', | ||
173 | + 'shop', | ||
174 | + 'shopping', | ||
175 | + 'sketch-circle', | ||
176 | + 'sketch-square', | ||
177 | + 'skin', | ||
178 | + 'slack-circle', | ||
179 | + 'skype', | ||
180 | + 'slack-square', | ||
181 | + 'sliders', | ||
182 | + 'smile', | ||
183 | + 'snippets', | ||
184 | + 'sound', | ||
185 | + 'star', | ||
186 | + 'step-backward', | ||
187 | + 'step-forward', | ||
188 | + 'stop', | ||
189 | + 'switcher', | ||
190 | + 'tablet', | ||
191 | + 'tag', | ||
192 | + 'tags', | ||
193 | + 'taobao-circle', | ||
194 | + 'taobao-square', | ||
195 | + 'tool', | ||
196 | + 'thunderbolt', | ||
197 | + 'trademark-circle', | ||
198 | + 'twitter-circle', | ||
199 | + 'trophy', | ||
200 | + 'twitter-square', | ||
201 | + 'unlock', | ||
202 | + 'up-circle', | ||
203 | + 'up-square', | ||
204 | + 'usb', | ||
205 | + 'video-camera', | ||
206 | + 'wallet', | ||
207 | + 'warning', | ||
208 | + 'wechat', | ||
209 | + 'weibo-circle', | ||
210 | + 'windows', | ||
211 | + 'yahoo', | ||
212 | + 'weibo-square', | ||
213 | + 'yuque', | ||
214 | + 'youtube', | ||
215 | + 'zhihu-circle', | ||
216 | + 'zhihu-square', | ||
217 | + ], | ||
218 | + outlined: [ | ||
219 | + 'account-book', | ||
220 | + 'alert', | ||
221 | + 'alipay-circle', | ||
222 | + 'aliwangwang', | ||
223 | + 'android', | ||
224 | + 'api', | ||
225 | + 'appstore', | ||
226 | + 'audio', | ||
227 | + 'apple', | ||
228 | + 'backward', | ||
229 | + 'bank', | ||
230 | + 'bell', | ||
231 | + 'behance-square', | ||
232 | + 'book', | ||
233 | + 'box-plot', | ||
234 | + 'bug', | ||
235 | + 'bulb', | ||
236 | + 'calculator', | ||
237 | + 'build', | ||
238 | + 'calendar', | ||
239 | + 'camera', | ||
240 | + 'car', | ||
241 | + 'caret-down', | ||
242 | + 'caret-left', | ||
243 | + 'caret-right', | ||
244 | + 'carry-out', | ||
245 | + 'caret-up', | ||
246 | + 'check-circle', | ||
247 | + 'check-square', | ||
248 | + 'chrome', | ||
249 | + 'clock-circle', | ||
250 | + 'close-circle', | ||
251 | + 'cloud', | ||
252 | + 'close-square', | ||
253 | + 'code', | ||
254 | + 'codepen-circle', | ||
255 | + 'compass', | ||
256 | + 'contacts', | ||
257 | + 'container', | ||
258 | + 'control', | ||
259 | + 'copy', | ||
260 | + 'credit-card', | ||
261 | + 'crown', | ||
262 | + 'customer-service', | ||
263 | + 'dashboard', | ||
264 | + 'delete', | ||
265 | + 'diff', | ||
266 | + 'database', | ||
267 | + 'dislike', | ||
268 | + 'down-circle', | ||
269 | + 'down-square', | ||
270 | + 'dribbble-square', | ||
271 | + 'environment', | ||
272 | + 'edit', | ||
273 | + 'exclamation-circle', | ||
274 | + 'experiment', | ||
275 | + 'eye-invisible', | ||
276 | + 'eye', | ||
277 | + 'facebook', | ||
278 | + 'fast-backward', | ||
279 | + 'fast-forward', | ||
280 | + 'file-add', | ||
281 | + 'file-excel', | ||
282 | + 'file-exclamation', | ||
283 | + 'file-image', | ||
284 | + 'file-markdown', | ||
285 | + 'file-pdf', | ||
286 | + 'file-ppt', | ||
287 | + 'file-text', | ||
288 | + 'file-unknown', | ||
289 | + 'file-word', | ||
290 | + 'file-zip', | ||
291 | + 'file', | ||
292 | + 'filter', | ||
293 | + 'fire', | ||
294 | + 'flag', | ||
295 | + 'folder-add', | ||
296 | + 'folder', | ||
297 | + 'folder-open', | ||
298 | + 'forward', | ||
299 | + 'frown', | ||
300 | + 'fund', | ||
301 | + 'funnel-plot', | ||
302 | + 'gift', | ||
303 | + 'github', | ||
304 | + 'gitlab', | ||
305 | + 'hdd', | ||
306 | + 'heart', | ||
307 | + 'highlight', | ||
308 | + 'home', | ||
309 | + 'hourglass', | ||
310 | + 'html5', | ||
311 | + 'idcard', | ||
312 | + 'info-circle', | ||
313 | + 'instagram', | ||
314 | + 'insurance', | ||
315 | + 'interaction', | ||
316 | + 'interation', | ||
317 | + 'layout', | ||
318 | + 'left-circle', | ||
319 | + 'left-square', | ||
320 | + 'like', | ||
321 | + 'linkedin', | ||
322 | + 'lock', | ||
323 | + 'mail', | ||
324 | + 'medicine-box', | ||
325 | + 'meh', | ||
326 | + 'message', | ||
327 | + 'minus-circle', | ||
328 | + 'minus-square', | ||
329 | + 'mobile', | ||
330 | + 'money-collect', | ||
331 | + 'pause-circle', | ||
332 | + 'pay-circle', | ||
333 | + 'notification', | ||
334 | + 'phone', | ||
335 | + 'picture', | ||
336 | + 'pie-chart', | ||
337 | + 'play-circle', | ||
338 | + 'play-square', | ||
339 | + 'plus-circle', | ||
340 | + 'plus-square', | ||
341 | + 'printer', | ||
342 | + 'profile', | ||
343 | + 'project', | ||
344 | + 'pushpin', | ||
345 | + 'property-safety', | ||
346 | + 'question-circle', | ||
347 | + 'read', | ||
348 | + 'reconciliation', | ||
349 | + 'red-envelope', | ||
350 | + 'rest', | ||
351 | + 'right-circle', | ||
352 | + 'rocket', | ||
353 | + 'right-square', | ||
354 | + 'safety-certificate', | ||
355 | + 'save', | ||
356 | + 'schedule', | ||
357 | + 'security-scan', | ||
358 | + 'setting', | ||
359 | + 'shop', | ||
360 | + 'shopping', | ||
361 | + 'skin', | ||
362 | + 'skype', | ||
363 | + 'slack-square', | ||
364 | + 'sliders', | ||
365 | + 'smile', | ||
366 | + 'snippets', | ||
367 | + 'sound', | ||
368 | + 'star', | ||
369 | + 'step-backward', | ||
370 | + 'step-forward', | ||
371 | + 'stop', | ||
372 | + 'switcher', | ||
373 | + 'tablet', | ||
374 | + 'tag', | ||
375 | + 'tags', | ||
376 | + 'taobao-circle', | ||
377 | + 'tool', | ||
378 | + 'thunderbolt', | ||
379 | + 'trophy', | ||
380 | + 'unlock', | ||
381 | + 'up-circle', | ||
382 | + 'up-square', | ||
383 | + 'usb', | ||
384 | + 'video-camera', | ||
385 | + 'wallet', | ||
386 | + 'warning', | ||
387 | + 'wechat', | ||
388 | + 'weibo-circle', | ||
389 | + 'windows', | ||
390 | + 'yahoo', | ||
391 | + 'weibo-square', | ||
392 | + 'yuque', | ||
393 | + 'youtube', | ||
394 | + 'alibaba', | ||
395 | + 'align-center', | ||
396 | + 'align-left', | ||
397 | + 'align-right', | ||
398 | + 'alipay', | ||
399 | + 'aliyun', | ||
400 | + 'amazon', | ||
401 | + 'ant-cloud', | ||
402 | + 'apartment', | ||
403 | + 'ant-design', | ||
404 | + 'area-chart', | ||
405 | + 'arrow-left', | ||
406 | + 'arrow-down', | ||
407 | + 'arrow-up', | ||
408 | + 'arrows-alt', | ||
409 | + 'arrow-right', | ||
410 | + 'audit', | ||
411 | + 'bar-chart', | ||
412 | + 'barcode', | ||
413 | + 'bars', | ||
414 | + 'behance', | ||
415 | + 'bg-colors', | ||
416 | + 'block', | ||
417 | + 'bold', | ||
418 | + 'border-bottom', | ||
419 | + 'border-left', | ||
420 | + 'border-outer', | ||
421 | + 'border-inner', | ||
422 | + 'border-right', | ||
423 | + 'border-horizontal', | ||
424 | + 'border-top', | ||
425 | + 'border-verticle', | ||
426 | + 'border', | ||
427 | + 'branches', | ||
428 | + 'check', | ||
429 | + 'ci', | ||
430 | + 'close', | ||
431 | + 'cloud-download', | ||
432 | + 'cloud-server', | ||
433 | + 'cloud-sync', | ||
434 | + 'cloud-upload', | ||
435 | + 'cluster', | ||
436 | + 'codepen', | ||
437 | + 'code-sandbox', | ||
438 | + 'colum-height', | ||
439 | + 'column-width', | ||
440 | + 'column-height', | ||
441 | + 'coffee', | ||
442 | + 'copyright', | ||
443 | + 'dash', | ||
444 | + 'deployment-unit', | ||
445 | + 'desktop', | ||
446 | + 'dingding', | ||
447 | + 'disconnect', | ||
448 | + 'dollar', | ||
449 | + 'double-left', | ||
450 | + 'dot-chart', | ||
451 | + 'double-right', | ||
452 | + 'down', | ||
453 | + 'drag', | ||
454 | + 'download', | ||
455 | + 'dribbble', | ||
456 | + 'dropbox', | ||
457 | + 'ellipsis', | ||
458 | + 'enter', | ||
459 | + 'euro', | ||
460 | + 'exception', | ||
461 | + 'exclamation', | ||
462 | + 'export', | ||
463 | + 'fall', | ||
464 | + 'file-done', | ||
465 | + 'file-jpg', | ||
466 | + 'file-protect', | ||
467 | + 'file-sync', | ||
468 | + 'file-search', | ||
469 | + 'font-colors', | ||
470 | + 'font-size', | ||
471 | + 'fork', | ||
472 | + 'form', | ||
473 | + 'fullscreen-exit', | ||
474 | + 'fullscreen', | ||
475 | + 'gateway', | ||
476 | + 'global', | ||
477 | + 'google-plus', | ||
478 | + 'gold', | ||
479 | + 'google', | ||
480 | + 'heat-map', | ||
481 | + 'history', | ||
482 | + 'ie', | ||
483 | + 'import', | ||
484 | + 'inbox', | ||
485 | + 'info', | ||
486 | + 'italic', | ||
487 | + 'key', | ||
488 | + 'issues-close', | ||
489 | + 'laptop', | ||
490 | + 'left', | ||
491 | + 'line-chart', | ||
492 | + 'link', | ||
493 | + 'line-height', | ||
494 | + 'line', | ||
495 | + 'loading-3-quarters', | ||
496 | + 'loading', | ||
497 | + 'login', | ||
498 | + 'logout', | ||
499 | + 'man', | ||
500 | + 'medium', | ||
501 | + 'medium-workmark', | ||
502 | + 'menu-unfold', | ||
503 | + 'menu-fold', | ||
504 | + 'menu', | ||
505 | + 'minus', | ||
506 | + 'monitor', | ||
507 | + 'more', | ||
508 | + 'ordered-list', | ||
509 | + 'number', | ||
510 | + 'pause', | ||
511 | + 'percentage', | ||
512 | + 'paper-clip', | ||
513 | + 'pic-center', | ||
514 | + 'pic-left', | ||
515 | + 'pic-right', | ||
516 | + 'plus', | ||
517 | + 'pound', | ||
518 | + 'poweroff', | ||
519 | + 'pull-request', | ||
520 | + 'qq', | ||
521 | + 'question', | ||
522 | + 'radar-chart', | ||
523 | + 'qrcode', | ||
524 | + 'radius-bottomleft', | ||
525 | + 'radius-bottomright', | ||
526 | + 'radius-upleft', | ||
527 | + 'radius-setting', | ||
528 | + 'radius-upright', | ||
529 | + 'reddit', | ||
530 | + 'redo', | ||
531 | + 'reload', | ||
532 | + 'retweet', | ||
533 | + 'right', | ||
534 | + 'rise', | ||
535 | + 'rollback', | ||
536 | + 'safety', | ||
537 | + 'robot', | ||
538 | + 'scan', | ||
539 | + 'search', | ||
540 | + 'scissor', | ||
541 | + 'select', | ||
542 | + 'shake', | ||
543 | + 'share-alt', | ||
544 | + 'shopping-cart', | ||
545 | + 'shrink', | ||
546 | + 'sketch', | ||
547 | + 'slack', | ||
548 | + 'small-dash', | ||
549 | + 'solution', | ||
550 | + 'sort-descending', | ||
551 | + 'sort-ascending', | ||
552 | + 'stock', | ||
553 | + 'swap-left', | ||
554 | + 'swap-right', | ||
555 | + 'strikethrough', | ||
556 | + 'swap', | ||
557 | + 'sync', | ||
558 | + 'table', | ||
559 | + 'team', | ||
560 | + 'taobao', | ||
561 | + 'to-top', | ||
562 | + 'trademark', | ||
563 | + 'transaction', | ||
564 | + 'twitter', | ||
565 | + 'underline', | ||
566 | + 'undo', | ||
567 | + 'unordered-list', | ||
568 | + 'up', | ||
569 | + 'upload', | ||
570 | + 'user-add', | ||
571 | + 'user-delete', | ||
572 | + 'usergroup-add', | ||
573 | + 'user', | ||
574 | + 'usergroup-delete', | ||
575 | + 'vertical-align-bottom', | ||
576 | + 'vertical-align-middle', | ||
577 | + 'vertical-align-top', | ||
578 | + 'vertical-left', | ||
579 | + 'vertical-right', | ||
580 | + 'weibo', | ||
581 | + 'wifi', | ||
582 | + 'zhihu', | ||
583 | + 'woman', | ||
584 | + 'zoom-out', | ||
585 | + 'zoom-in', | ||
586 | + ], | ||
587 | + twoTone: [ | ||
588 | + 'account-book', | ||
589 | + 'alert', | ||
590 | + 'api', | ||
591 | + 'appstore', | ||
592 | + 'audio', | ||
593 | + 'bank', | ||
594 | + 'bell', | ||
595 | + 'book', | ||
596 | + 'box-plot', | ||
597 | + 'bug', | ||
598 | + 'bulb', | ||
599 | + 'calculator', | ||
600 | + 'build', | ||
601 | + 'calendar', | ||
602 | + 'camera', | ||
603 | + 'car', | ||
604 | + 'carry-out', | ||
605 | + 'check-circle', | ||
606 | + 'check-square', | ||
607 | + 'clock-circle', | ||
608 | + 'close-circle', | ||
609 | + 'cloud', | ||
610 | + 'close-square', | ||
611 | + 'code', | ||
612 | + 'compass', | ||
613 | + 'contacts', | ||
614 | + 'container', | ||
615 | + 'control', | ||
616 | + 'copy', | ||
617 | + 'credit-card', | ||
618 | + 'crown', | ||
619 | + 'customer-service', | ||
620 | + 'dashboard', | ||
621 | + 'delete', | ||
622 | + 'diff', | ||
623 | + 'database', | ||
624 | + 'dislike', | ||
625 | + 'down-circle', | ||
626 | + 'down-square', | ||
627 | + 'environment', | ||
628 | + 'edit', | ||
629 | + 'exclamation-circle', | ||
630 | + 'experiment', | ||
631 | + 'eye-invisible', | ||
632 | + 'eye', | ||
633 | + 'file-add', | ||
634 | + 'file-excel', | ||
635 | + 'file-exclamation', | ||
636 | + 'file-image', | ||
637 | + 'file-markdown', | ||
638 | + 'file-pdf', | ||
639 | + 'file-ppt', | ||
640 | + 'file-text', | ||
641 | + 'file-unknown', | ||
642 | + 'file-word', | ||
643 | + 'file-zip', | ||
644 | + 'file', | ||
645 | + 'filter', | ||
646 | + 'fire', | ||
647 | + 'flag', | ||
648 | + 'folder-add', | ||
649 | + 'folder', | ||
650 | + 'folder-open', | ||
651 | + 'frown', | ||
652 | + 'fund', | ||
653 | + 'funnel-plot', | ||
654 | + 'gift', | ||
655 | + 'hdd', | ||
656 | + 'heart', | ||
657 | + 'highlight', | ||
658 | + 'home', | ||
659 | + 'hourglass', | ||
660 | + 'html5', | ||
661 | + 'idcard', | ||
662 | + 'info-circle', | ||
663 | + 'insurance', | ||
664 | + 'interaction', | ||
665 | + 'interation', | ||
666 | + 'layout', | ||
667 | + 'left-circle', | ||
668 | + 'left-square', | ||
669 | + 'like', | ||
670 | + 'lock', | ||
671 | + 'mail', | ||
672 | + 'medicine-box', | ||
673 | + 'meh', | ||
674 | + 'message', | ||
675 | + 'minus-circle', | ||
676 | + 'minus-square', | ||
677 | + 'mobile', | ||
678 | + 'money-collect', | ||
679 | + 'pause-circle', | ||
680 | + 'notification', | ||
681 | + 'phone', | ||
682 | + 'picture', | ||
683 | + 'pie-chart', | ||
684 | + 'play-circle', | ||
685 | + 'play-square', | ||
686 | + 'plus-circle', | ||
687 | + 'plus-square', | ||
688 | + 'pound-circle', | ||
689 | + 'printer', | ||
690 | + 'profile', | ||
691 | + 'project', | ||
692 | + 'pushpin', | ||
693 | + 'property-safety', | ||
694 | + 'question-circle', | ||
695 | + 'reconciliation', | ||
696 | + 'red-envelope', | ||
697 | + 'rest', | ||
698 | + 'right-circle', | ||
699 | + 'rocket', | ||
700 | + 'right-square', | ||
701 | + 'safety-certificate', | ||
702 | + 'save', | ||
703 | + 'schedule', | ||
704 | + 'security-scan', | ||
705 | + 'setting', | ||
706 | + 'shop', | ||
707 | + 'shopping', | ||
708 | + 'skin', | ||
709 | + 'sliders', | ||
710 | + 'smile', | ||
711 | + 'snippets', | ||
712 | + 'sound', | ||
713 | + 'star', | ||
714 | + 'stop', | ||
715 | + 'switcher', | ||
716 | + 'tablet', | ||
717 | + 'tag', | ||
718 | + 'tags', | ||
719 | + 'tool', | ||
720 | + 'thunderbolt', | ||
721 | + 'trademark-circle', | ||
722 | + 'trophy', | ||
723 | + 'unlock', | ||
724 | + 'up-circle', | ||
725 | + 'up-square', | ||
726 | + 'usb', | ||
727 | + 'video-camera', | ||
728 | + 'wallet', | ||
729 | + 'warning', | ||
730 | + 'ci', | ||
731 | + 'copyright', | ||
732 | + 'dollar', | ||
733 | + 'euro', | ||
734 | + 'gold', | ||
735 | + 'canlendar', | ||
736 | + ], | ||
737 | +}; | ||
738 | + | ||
739 | +export default iconConfig; |
src/views/form-design/examples/baseForm.vue
0 → 100644
1 | +<template> | ||
2 | + <BasicForm @register="register" /> | ||
3 | +</template> | ||
4 | +<script lang="ts" setup> | ||
5 | + import { BasicForm, FormSchema, useForm } from '/@/components/Form/index'; | ||
6 | + | ||
7 | + const schemas: FormSchema[] = [ | ||
8 | + { | ||
9 | + field: 'field1', | ||
10 | + component: 'Input', | ||
11 | + label: '字段1', | ||
12 | + span: 8, | ||
13 | + // colProps: { | ||
14 | + // span: 8, | ||
15 | + // }, | ||
16 | + componentProps: { | ||
17 | + placeholder: '自定义placeholder', | ||
18 | + onChange: (e: any) => { | ||
19 | + console.log(e); | ||
20 | + }, | ||
21 | + }, | ||
22 | + }, | ||
23 | + { | ||
24 | + field: 'field2', | ||
25 | + component: 'Input', | ||
26 | + label: '字段2', | ||
27 | + span: 8, | ||
28 | + // colProps: { | ||
29 | + // span: 8, | ||
30 | + // }, | ||
31 | + }, | ||
32 | + ]; | ||
33 | + | ||
34 | + const [register] = useForm({ | ||
35 | + schemas, | ||
36 | + }); | ||
37 | +</script> |
src/views/form-design/hooks/useFormDesignState.ts
0 → 100644
1 | +import { inject, Ref } from 'vue'; | ||
2 | +import { IFormDesignMethods } from '../typings/form-type'; | ||
3 | +import { IFormConfig } from '../typings/v-form-component'; | ||
4 | + | ||
5 | +/** | ||
6 | + * 获取formDesign状态 | ||
7 | + */ | ||
8 | +export function useFormDesignState() { | ||
9 | + const formConfig = inject('formConfig') as Ref<IFormConfig>; | ||
10 | + const formDesignMethods = inject('formDesignMethods') as IFormDesignMethods; | ||
11 | + return { formConfig, formDesignMethods }; | ||
12 | +} | ||
13 | + | ||
14 | +export function useFormModelState() { | ||
15 | + const formModel = inject('formModel') as Ref<{}>; | ||
16 | + const setFormModel = inject('setFormModelMethod') as (key: String, value: any) => void; | ||
17 | + return { formModel, setFormModel }; | ||
18 | +} |
src/views/form-design/hooks/useFormInstanceMethods.ts
0 → 100644
1 | +import { IAnyObject } from '../typings/base-type'; | ||
2 | +import { Ref, SetupContext } from 'vue'; | ||
3 | +import { cloneDeep, forOwn, isFunction } from 'lodash-es'; | ||
4 | +import { AForm, IVFormComponent } from '../typings/v-form-component'; | ||
5 | +import { getCurrentInstance } from 'vue'; | ||
6 | +import { Form } from 'ant-design-vue'; | ||
7 | +import { toRaw } from 'vue'; | ||
8 | + | ||
9 | +export function useFormInstanceMethods( | ||
10 | + props: IAnyObject, | ||
11 | + formdata, | ||
12 | + context: Partial<SetupContext>, | ||
13 | + _formInstance: Ref<AForm | null>, | ||
14 | +) { | ||
15 | + /** | ||
16 | + * 绑定props和on中的上下文为parent | ||
17 | + */ | ||
18 | + const bindContext = () => { | ||
19 | + const instance = getCurrentInstance(); | ||
20 | + const vm = instance?.parent; | ||
21 | + if (!vm) return; | ||
22 | + | ||
23 | + (props.formConfig.schemas as IVFormComponent[]).forEach((item) => { | ||
24 | + // 绑定 props 中的上下文 | ||
25 | + forOwn(item.componentProps, (value: any, key) => { | ||
26 | + if (isFunction(value)) { | ||
27 | + item.componentProps![key] = value.bind(vm); | ||
28 | + } | ||
29 | + }); | ||
30 | + // 绑定事件监听(v-on)的上下文 | ||
31 | + forOwn(item.on, (value: any, key) => { | ||
32 | + if (isFunction(value)) { | ||
33 | + item.componentProps![key] = value.bind(vm); | ||
34 | + } | ||
35 | + }); | ||
36 | + }); | ||
37 | + }; | ||
38 | + bindContext(); | ||
39 | + | ||
40 | + const { emit } = context; | ||
41 | + | ||
42 | + const useForm = Form.useForm; | ||
43 | + | ||
44 | + const { resetFields, validate, clearValidate, validateField } = useForm(formdata, []); | ||
45 | + | ||
46 | + const submit = async () => { | ||
47 | + //const _result = await validate(); | ||
48 | + | ||
49 | + const data = cloneDeep(toRaw(formdata.value)); | ||
50 | + emit?.('submit', data); | ||
51 | + props.formConfig.submit?.(data); | ||
52 | + return data; | ||
53 | + }; | ||
54 | + | ||
55 | + return { | ||
56 | + validate, | ||
57 | + validateField, | ||
58 | + resetFields, | ||
59 | + clearValidate, | ||
60 | + submit, | ||
61 | + }; | ||
62 | +} |
src/views/form-design/hooks/useVFormMethods.ts
0 → 100644
1 | +import { Ref, SetupContext } from 'vue'; | ||
2 | +import { IVFormComponent, IFormConfig, AForm } from '../typings/v-form-component'; | ||
3 | +import { findFormItem, formItemsForEach } from '../utils'; | ||
4 | +import { cloneDeep, isFunction } from 'lodash-es'; | ||
5 | +import { IAnyObject } from '../typings/base-type'; | ||
6 | + | ||
7 | +interface IFormInstanceMethods extends AForm { | ||
8 | + submit: () => Promise<any>; | ||
9 | +} | ||
10 | + | ||
11 | +export interface IProps { | ||
12 | + formConfig: IFormConfig; | ||
13 | + formModel: IAnyObject; | ||
14 | +} | ||
15 | + | ||
16 | +type ISet = <T extends keyof IVFormComponent>( | ||
17 | + field: string, | ||
18 | + key: T, | ||
19 | + value: IVFormComponent[T], | ||
20 | +) => void; | ||
21 | +// 获取当前field绑定的表单项 | ||
22 | +type IGet = (field: string) => IVFormComponent | undefined; | ||
23 | +// 获取field在formData中的值 | ||
24 | +type IGetValue = (field: string) => any; | ||
25 | +// 设置field在formData中的值并且触发校验 | ||
26 | +type ISetValue = (field: string | IAnyObject, value?: any) => void; | ||
27 | +// 隐藏field对应的表单项 | ||
28 | +type IHidden = (field: string) => void; | ||
29 | +// 显示field对应的表单项 | ||
30 | +type IShow = (field: string) => void; | ||
31 | +// 设置field对应的表单项绑定的props属性 | ||
32 | +type ISetProps = (field: string, key: string, value: any) => void; | ||
33 | +// 获取formData中的值 | ||
34 | +type IGetData = () => Promise<IAnyObject>; | ||
35 | +// 禁用表单,如果field为空,则禁用整个表单 | ||
36 | +type IDisable = (field?: string | boolean) => void; | ||
37 | +// 设置表单配置方法 | ||
38 | +type ISetFormConfig = (key: string, value: any) => void; | ||
39 | +interface ILinkOn { | ||
40 | + [key: string]: Set<IVFormComponent>; | ||
41 | +} | ||
42 | + | ||
43 | +export interface IVFormMethods extends Partial<IFormInstanceMethods> { | ||
44 | + set: ISet; | ||
45 | + get: IGet; | ||
46 | + getValue: IGetValue; | ||
47 | + setValue: ISetValue; | ||
48 | + hidden: IHidden; | ||
49 | + show: IShow; | ||
50 | + setProps: ISetProps; | ||
51 | + linkOn: ILinkOn; | ||
52 | + getData: IGetData; | ||
53 | + disable: IDisable; | ||
54 | +} | ||
55 | +export function useVFormMethods( | ||
56 | + props: IProps, | ||
57 | + _context: Partial<SetupContext>, | ||
58 | + formInstance: Ref<AForm | null>, | ||
59 | + formInstanceMethods: Partial<IFormInstanceMethods>, | ||
60 | +): IVFormMethods { | ||
61 | + /** | ||
62 | + * 根据field获取表单项 | ||
63 | + * @param {string} field | ||
64 | + * @return {IVFormComponent | undefined} | ||
65 | + */ | ||
66 | + const get: IGet = (field) => | ||
67 | + findFormItem(props.formConfig.schemas, (item) => item.field === field); | ||
68 | + | ||
69 | + /** | ||
70 | + * 根据表单field设置表单项字段值 | ||
71 | + * @param {string} field | ||
72 | + * @param {keyof IVFormComponent} key | ||
73 | + * @param {never} value | ||
74 | + */ | ||
75 | + const set: ISet = (field, key, value) => { | ||
76 | + const formItem = get(field); | ||
77 | + if (formItem) formItem[key] = value; | ||
78 | + }; | ||
79 | + | ||
80 | + /** | ||
81 | + * 设置表单项的props | ||
82 | + * @param {string} field 需要设置的表单项field | ||
83 | + * @param {string} key 需要设置的key | ||
84 | + * @param value 需要设置的值 | ||
85 | + */ | ||
86 | + const setProps: ISetProps = (field, key, value) => { | ||
87 | + const formItem = get(field); | ||
88 | + if (formItem?.componentProps) { | ||
89 | + ['options', 'treeData'].includes(key) && setValue(field, undefined); | ||
90 | + | ||
91 | + formItem.componentProps[key] = value; | ||
92 | + } | ||
93 | + }; | ||
94 | + /** | ||
95 | + * 设置字段的值,设置后触发校验 | ||
96 | + * @param {string} field 需要设置的字段 | ||
97 | + * @param value 需要设置的值 | ||
98 | + */ | ||
99 | + const setValue: ISetValue = (field, value) => { | ||
100 | + if (typeof field === 'string') { | ||
101 | + // props.formData[field] = value | ||
102 | + props.formModel[field] = value; | ||
103 | + formInstance.value?.validateField(field, value, []); | ||
104 | + } else { | ||
105 | + const keys = Object.keys(field); | ||
106 | + keys.forEach((key) => { | ||
107 | + props.formModel[key] = field[key]; | ||
108 | + formInstance.value?.validateField(key, field[key], []); | ||
109 | + }); | ||
110 | + } | ||
111 | + }; | ||
112 | + /** | ||
113 | + * 设置表单配置方法 | ||
114 | + * @param {string} key | ||
115 | + * @param value | ||
116 | + */ | ||
117 | + const setFormConfig: ISetFormConfig = (key, value) => { | ||
118 | + props.formConfig[key] = value; | ||
119 | + }; | ||
120 | + /** | ||
121 | + * 根据表单项field获取字段值,如果field为空,则 | ||
122 | + * @param {string} field 需要设置的字段 | ||
123 | + */ | ||
124 | + const getValue: IGetValue = (field) => { | ||
125 | + const formData = cloneDeep(props.formModel); | ||
126 | + return formData[field]; | ||
127 | + }; | ||
128 | + | ||
129 | + /** | ||
130 | + * 获取formData中的值 | ||
131 | + * @return {Promise<IAnyObject<any>>} | ||
132 | + */ | ||
133 | + const getData: IGetData = async () => { | ||
134 | + return cloneDeep(props.formModel); | ||
135 | + }; | ||
136 | + /** | ||
137 | + * 隐藏指定表单项 | ||
138 | + * @param {string} field 需要隐藏的表单项的field | ||
139 | + */ | ||
140 | + const hidden: IHidden = (field) => { | ||
141 | + set(field, 'hidden', true); | ||
142 | + }; | ||
143 | + | ||
144 | + /** | ||
145 | + * 禁用表单 | ||
146 | + * @param {string | undefined} field | ||
147 | + */ | ||
148 | + const disable: IDisable = (field) => { | ||
149 | + typeof field === 'string' | ||
150 | + ? setProps(field, 'disabled', true) | ||
151 | + : setFormConfig('disabled', field !== false); | ||
152 | + }; | ||
153 | + | ||
154 | + /** | ||
155 | + * 显示表单项 | ||
156 | + * @param {string} field 需要显示的表单项的field | ||
157 | + */ | ||
158 | + const show: IShow = (field) => { | ||
159 | + set(field, 'hidden', false); | ||
160 | + }; | ||
161 | + | ||
162 | + /** | ||
163 | + * 监听表单字段联动时触发 | ||
164 | + * @type {ILinkOn} | ||
165 | + */ | ||
166 | + const linkOn: ILinkOn = {}; | ||
167 | + const initLink = (schemas: IVFormComponent[]) => { | ||
168 | + // 首次遍历,查找需要关联字段的表单 | ||
169 | + formItemsForEach(schemas, (formItem) => { | ||
170 | + // 如果需要关联,则进行第二层遍历,查找表单中关联的字段,存到Set中 | ||
171 | + formItemsForEach(schemas, (item) => { | ||
172 | + if (!linkOn[item.field!]) linkOn[item.field!] = new Set<IVFormComponent>(); | ||
173 | + if (formItem.link?.includes(item.field!) && isFunction(formItem.update)) { | ||
174 | + linkOn[item.field!].add(formItem); | ||
175 | + } | ||
176 | + }); | ||
177 | + linkOn[formItem.field!].add(formItem); | ||
178 | + }); | ||
179 | + }; | ||
180 | + initLink(props.formConfig.schemas); | ||
181 | + | ||
182 | + return { | ||
183 | + linkOn, | ||
184 | + setValue, | ||
185 | + getValue, | ||
186 | + hidden, | ||
187 | + show, | ||
188 | + set, | ||
189 | + get, | ||
190 | + setProps, | ||
191 | + getData, | ||
192 | + disable, | ||
193 | + ...formInstanceMethods, | ||
194 | + }; | ||
195 | +} |
src/views/form-design/index.vue
0 → 100644
src/views/form-design/tests/import1.json
0 → 100644
1 | +{ | ||
2 | + "schemas": [{ | ||
3 | + "field": "filename", | ||
4 | + "component": "Input", | ||
5 | + "label": "component.excel.fileName", | ||
6 | + "rules": [{ | ||
7 | + "required": true | ||
8 | + }] | ||
9 | + }, | ||
10 | + { | ||
11 | + "field": "bookType", | ||
12 | + "component": "Select", | ||
13 | + "label": "component.excel.fileType", | ||
14 | + "defaultValue": "xlsx", | ||
15 | + "rules": [{ | ||
16 | + "required": true | ||
17 | + }], | ||
18 | + "componentProps": { | ||
19 | + "options": [{ | ||
20 | + "label": "xlsx", | ||
21 | + "value": "xlsx", | ||
22 | + "key": "xlsx" | ||
23 | + }, | ||
24 | + { | ||
25 | + "label": "html", | ||
26 | + "value": "html", | ||
27 | + "key": "html" | ||
28 | + }, | ||
29 | + { | ||
30 | + "label": "csv", | ||
31 | + "value": "csv", | ||
32 | + "key": "csv" | ||
33 | + }, | ||
34 | + { | ||
35 | + "label": "txt", | ||
36 | + "value": "txt", | ||
37 | + "key": "txt" | ||
38 | + } | ||
39 | + ] | ||
40 | + } | ||
41 | + } | ||
42 | + ], | ||
43 | + "layout": "horizontal", | ||
44 | + "labelLayout": "flex", | ||
45 | + "labelWidth": 100, | ||
46 | + "labelCol": {}, | ||
47 | + "wrapperCol": {} | ||
48 | +} | ||
0 | \ No newline at end of file | 49 | \ No newline at end of file |
src/views/form-design/typings/base-type.ts
0 → 100644
src/views/form-design/typings/form-type.ts
0 → 100644
1 | +import { Ref } from 'vue'; | ||
2 | +import { IAnyObject } from './base-type'; | ||
3 | +import { IFormConfig, IVFormComponent } from './v-form-component'; | ||
4 | + | ||
5 | +export interface IToolbarMethods { | ||
6 | + showModal: (jsonData: IAnyObject) => void; | ||
7 | +} | ||
8 | + | ||
9 | +type ChangeTabKey = 1 | 2; | ||
10 | +export interface IPropsPanel { | ||
11 | + changeTab: (key: ChangeTabKey) => void; | ||
12 | +} | ||
13 | +export interface IState { | ||
14 | + // 语言 | ||
15 | + locale: any; | ||
16 | + // 公用组件 | ||
17 | + baseComponents: IVFormComponent[]; | ||
18 | + // 自定义组件 | ||
19 | + customComponents: IVFormComponent[]; | ||
20 | + // 布局组件 | ||
21 | + layoutComponents: IVFormComponent[]; | ||
22 | + // 属性面板实例 | ||
23 | + propsPanel: Ref<null | IPropsPanel>; | ||
24 | + // json模态框实例 | ||
25 | + jsonModal: Ref<null | IToolbarMethods>; | ||
26 | + // 导入json数据模态框 | ||
27 | + importJsonModal: Ref<null | IToolbarMethods>; | ||
28 | + // 代码预览模态框 | ||
29 | + codeModal: Ref<null | IToolbarMethods>; | ||
30 | + // 预览模态框 | ||
31 | + eFormPreview: Ref<null | IToolbarMethods>; | ||
32 | + | ||
33 | + eFormPreview2: Ref<null | IToolbarMethods>; | ||
34 | +} | ||
35 | + | ||
36 | +export interface IFormDesignMethods { | ||
37 | + // 设置当前选中的控件 | ||
38 | + handleSetSelectItem(item: IVFormComponent): void; | ||
39 | + // 添加控件到formConfig.formItems中 | ||
40 | + handleListPush(item: IVFormComponent): void; | ||
41 | + // 复制控件 | ||
42 | + handleCopy(item?: IVFormComponent, isCopy?: boolean): void; | ||
43 | + // 添加控件属性 | ||
44 | + handleAddAttrs(schemas: IVFormComponent[], index: number): void; | ||
45 | + setFormConfig(config: IFormConfig): void; | ||
46 | + // 添加到表单中之前触发 | ||
47 | + handleBeforeColAdd( | ||
48 | + event: { newIndex: string }, | ||
49 | + schemas: IVFormComponent[], | ||
50 | + isCopy?: boolean, | ||
51 | + ): void; | ||
52 | +} |
src/views/form-design/typings/v-form-component.ts
0 → 100644
1 | +import { IAnyObject } from './base-type'; | ||
2 | +// import { ComponentOptions } from 'vue/types/options'; | ||
3 | +import { ComponentOptions } from 'vue'; | ||
4 | +import { IVFormMethods } from '../hooks/useVFormMethods'; | ||
5 | +import { ColEx } from '/@/components/Form/src/types'; | ||
6 | + | ||
7 | +import { SelectValue } from 'ant-design-vue/lib/select'; | ||
8 | +import { validateOptions } from 'ant-design-vue/lib/form/useForm'; | ||
9 | +import { RuleError } from 'ant-design-vue/lib/form/interface'; | ||
10 | +import { FormItem } from '/@/components/Form'; | ||
11 | +type LayoutType = 'horizontal' | 'vertical' | 'inline'; | ||
12 | +type labelLayout = 'flex' | 'Grid'; | ||
13 | +export type PropsTabKey = 1 | 2 | 3; | ||
14 | +type ColSpanType = number | string; | ||
15 | + | ||
16 | +declare type Value = [number, number] | number; | ||
17 | +/** | ||
18 | + * 组件属性 | ||
19 | + */ | ||
20 | +export interface IVFormComponent { | ||
21 | + // extends Omit<FormSchema, 'component' | 'label' | 'field' | 'rules'> { | ||
22 | + // 对应的字段 | ||
23 | + field?: string; | ||
24 | + // 组件类型 | ||
25 | + component: string; | ||
26 | + // 组件label | ||
27 | + label?: string; | ||
28 | + // 自定义组件控件实例 | ||
29 | + componentInstance?: ComponentOptions<any>; | ||
30 | + // 组件icon | ||
31 | + icon?: string; | ||
32 | + // 组件校验规则 | ||
33 | + rules?: Partial<IValidationRule>[]; | ||
34 | + // 是否隐藏 | ||
35 | + hidden?: boolean; | ||
36 | + // 隐藏label | ||
37 | + hiddenLabel?: boolean; | ||
38 | + // 组件宽度 | ||
39 | + width?: string; | ||
40 | + // 是否必选 | ||
41 | + required?: boolean; | ||
42 | + // 必选提示 | ||
43 | + message?: string; | ||
44 | + // 提示信息 | ||
45 | + helpMessage?: string; | ||
46 | + // 传给给组件的属性,默认会吧所有的props都传递给控件 | ||
47 | + componentProps?: IAnyObject; | ||
48 | + // 监听组件事件对象,以v-on方式传递给控件 | ||
49 | + on?: IAnyObject<(...any: []) => void>; | ||
50 | + // 组件选项 | ||
51 | + options?: IAnyObject; | ||
52 | + // 唯一标识 | ||
53 | + key?: string; | ||
54 | + // Reference formModelItem | ||
55 | + itemProps?: Partial<FormItem>; | ||
56 | + | ||
57 | + colProps?: Partial<ColEx>; | ||
58 | + // 联动字段 | ||
59 | + link?: string[]; | ||
60 | + // 联动属性变化的回调 | ||
61 | + update?: (value: any, formItem: IVFormComponent, fApi: IVFormMethods) => void; | ||
62 | + // 控件栅格数 | ||
63 | + // span?: number; | ||
64 | + // 标签布局 | ||
65 | + labelCol?: IAnyObject; | ||
66 | + // 组件布局 | ||
67 | + wrapperCol?: IAnyObject; | ||
68 | + // 子控件 | ||
69 | + columns?: Array<{ span: number; children: any[] }>; | ||
70 | +} | ||
71 | + | ||
72 | +declare type namesType = string | string[]; | ||
73 | + | ||
74 | +/** | ||
75 | + * 表单配置 | ||
76 | + */ | ||
77 | +export interface IFormConfig { | ||
78 | + // 表单项配置列表 | ||
79 | + // schemas: IVFormComponent[]; | ||
80 | + // 表单配置 | ||
81 | + // config: { | ||
82 | + layout?: LayoutType; | ||
83 | + labelLayout?: labelLayout; | ||
84 | + labelWidth?: number; | ||
85 | + labelCol?: Partial<IACol>; | ||
86 | + wrapperCol?: Partial<IACol>; | ||
87 | + hideRequiredMark?: boolean; | ||
88 | + // Whether to disable | ||
89 | + schemas: IVFormComponent[]; | ||
90 | + disabled?: boolean; | ||
91 | + labelAlign?: 'left' | 'right'; | ||
92 | + // Internal component size of the form | ||
93 | + size?: 'default' | 'small' | 'large'; | ||
94 | + // }; | ||
95 | + // 当前选中项 | ||
96 | + currentItem?: IVFormComponent; | ||
97 | + activeKey?: PropsTabKey; | ||
98 | + colon?: boolean; | ||
99 | +} | ||
100 | + | ||
101 | +export interface AForm { | ||
102 | + /** | ||
103 | + * Hide required mark of all form items | ||
104 | + * @default false | ||
105 | + * @type boolean | ||
106 | + */ | ||
107 | + hideRequiredMark: boolean; | ||
108 | + | ||
109 | + /** | ||
110 | + * The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with <Col> | ||
111 | + * @type IACol | ||
112 | + */ | ||
113 | + labelCol: IACol; | ||
114 | + | ||
115 | + /** | ||
116 | + * Define form layout | ||
117 | + * @default 'horizontal' | ||
118 | + * @type string | ||
119 | + */ | ||
120 | + layout: 'horizontal' | 'inline' | 'vertical'; | ||
121 | + | ||
122 | + /** | ||
123 | + * The layout for input controls, same as labelCol | ||
124 | + * @type IACol | ||
125 | + */ | ||
126 | + wrapperCol: IACol; | ||
127 | + | ||
128 | + /** | ||
129 | + * change default props colon value of Form.Item (only effective when prop layout is horizontal) | ||
130 | + * @type boolean | ||
131 | + * @default true | ||
132 | + */ | ||
133 | + colon: boolean; | ||
134 | + | ||
135 | + /** | ||
136 | + * text align of label of all items | ||
137 | + * @type 'left' | 'right' | ||
138 | + * @default 'left' | ||
139 | + */ | ||
140 | + labelAlign: 'left' | 'right'; | ||
141 | + | ||
142 | + /** | ||
143 | + * data of form component | ||
144 | + * @type object | ||
145 | + */ | ||
146 | + model: IAnyObject; | ||
147 | + | ||
148 | + /** | ||
149 | + * validation rules of form | ||
150 | + * @type object | ||
151 | + */ | ||
152 | + rules: IAnyObject; | ||
153 | + | ||
154 | + /** | ||
155 | + * Default validate message. And its format is similar with newMessages's returned value | ||
156 | + * @type any | ||
157 | + */ | ||
158 | + validateMessages?: any; | ||
159 | + | ||
160 | + /** | ||
161 | + * whether to trigger validation when the rules prop is changed | ||
162 | + * @type Boolean | ||
163 | + * @default true | ||
164 | + */ | ||
165 | + validateOnRuleChange: boolean; | ||
166 | + | ||
167 | + /** | ||
168 | + * validate the whole form. Takes a callback as a param. After validation, | ||
169 | + * the callback will be executed with two params: a boolean indicating if the validation has passed, | ||
170 | + * and an object containing all fields that fail the validation. Returns a promise if callback is omitted | ||
171 | + * @type Function | ||
172 | + */ | ||
173 | + validate: <T = any>(names?: namesType, option?: validateOptions) => Promise<T>; | ||
174 | + | ||
175 | + /** | ||
176 | + * validate one or several form items | ||
177 | + * @type Function | ||
178 | + */ | ||
179 | + validateField: ( | ||
180 | + name: string, | ||
181 | + value: any, | ||
182 | + rules: Record<string, unknown>[], | ||
183 | + option?: validateOptions, | ||
184 | + ) => Promise<RuleError[]>; | ||
185 | + /** | ||
186 | + * reset all the fields and remove validation result | ||
187 | + */ | ||
188 | + resetFields: () => void; | ||
189 | + | ||
190 | + /** | ||
191 | + * clear validation message for certain fields. | ||
192 | + * The parameter is prop name or an array of prop names of the form items whose validation messages will be removed. | ||
193 | + * When omitted, all fields' validation messages will be cleared | ||
194 | + * @type string[] | string | ||
195 | + */ | ||
196 | + clearValidate: (props: string[] | string) => void; | ||
197 | +} | ||
198 | + | ||
199 | +interface IACol { | ||
200 | + /** | ||
201 | + * raster number of cells to occupy, 0 corresponds to display: none | ||
202 | + * @default none (0) | ||
203 | + * @type ColSpanType | ||
204 | + */ | ||
205 | + span: Value; | ||
206 | + | ||
207 | + /** | ||
208 | + * raster order, used in flex layout mode | ||
209 | + * @default 0 | ||
210 | + * @type ColSpanType | ||
211 | + */ | ||
212 | + order: ColSpanType; | ||
213 | + | ||
214 | + /** | ||
215 | + * the layout fill of flex | ||
216 | + * @default none | ||
217 | + * @type ColSpanType | ||
218 | + */ | ||
219 | + flex: ColSpanType; | ||
220 | + | ||
221 | + /** | ||
222 | + * the number of cells to offset Col from the left | ||
223 | + * @default 0 | ||
224 | + * @type ColSpanType | ||
225 | + */ | ||
226 | + offset: ColSpanType; | ||
227 | + | ||
228 | + /** | ||
229 | + * the number of cells that raster is moved to the right | ||
230 | + * @default 0 | ||
231 | + * @type ColSpanType | ||
232 | + */ | ||
233 | + push: ColSpanType; | ||
234 | + | ||
235 | + /** | ||
236 | + * the number of cells that raster is moved to the left | ||
237 | + * @default 0 | ||
238 | + * @type ColSpanType | ||
239 | + */ | ||
240 | + pull: ColSpanType; | ||
241 | + | ||
242 | + /** | ||
243 | + * <576px and also default setting, could be a span value or an object containing above props | ||
244 | + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType | ||
245 | + */ | ||
246 | + xs: { span: ColSpanType; offset: ColSpanType } | ColSpanType; | ||
247 | + | ||
248 | + /** | ||
249 | + * ≥576px, could be a span value or an object containing above props | ||
250 | + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType | ||
251 | + */ | ||
252 | + sm: { span: ColSpanType; offset: ColSpanType } | ColSpanType; | ||
253 | + | ||
254 | + /** | ||
255 | + * ≥768px, could be a span value or an object containing above props | ||
256 | + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType | ||
257 | + */ | ||
258 | + md: { span: ColSpanType; offset: ColSpanType } | ColSpanType; | ||
259 | + | ||
260 | + /** | ||
261 | + * ≥992px, could be a span value or an object containing above props | ||
262 | + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType | ||
263 | + */ | ||
264 | + lg: { span: ColSpanType; offset: ColSpanType } | ColSpanType; | ||
265 | + | ||
266 | + /** | ||
267 | + * ≥1200px, could be a span value or an object containing above props | ||
268 | + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType | ||
269 | + */ | ||
270 | + xl: { span: ColSpanType; offset: ColSpanType } | ColSpanType; | ||
271 | + | ||
272 | + /** | ||
273 | + * ≥1600px, could be a span value or an object containing above props | ||
274 | + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType | ||
275 | + */ | ||
276 | + xxl: { span: ColSpanType; offset: ColSpanType } | ColSpanType; | ||
277 | +} | ||
278 | + | ||
279 | +export interface IValidationRule { | ||
280 | + trigger?: 'change' | 'blur' | ['change', 'blur']; | ||
281 | + /** | ||
282 | + * validation error message | ||
283 | + * @type string | Function | ||
284 | + */ | ||
285 | + message?: string | number; | ||
286 | + | ||
287 | + /** | ||
288 | + * built-in validation type, available options: https://github.com/yiminghe/async-validator#type | ||
289 | + * @default 'string' | ||
290 | + * @type string | ||
291 | + */ | ||
292 | + type?: string; | ||
293 | + | ||
294 | + /** | ||
295 | + * indicates whether field is required | ||
296 | + * @default false | ||
297 | + * @type boolean | ||
298 | + */ | ||
299 | + required?: boolean; | ||
300 | + | ||
301 | + /** | ||
302 | + * treat required fields that only contain whitespace as errors | ||
303 | + * @default false | ||
304 | + * @type boolean | ||
305 | + */ | ||
306 | + whitespace?: boolean; | ||
307 | + | ||
308 | + /** | ||
309 | + * validate the exact length of a field | ||
310 | + * @type number | ||
311 | + */ | ||
312 | + len?: number; | ||
313 | + | ||
314 | + /** | ||
315 | + * validate the min length of a field | ||
316 | + * @type number | ||
317 | + */ | ||
318 | + min?: number; | ||
319 | + | ||
320 | + /** | ||
321 | + * validate the max length of a field | ||
322 | + * @type number | ||
323 | + */ | ||
324 | + max?: number; | ||
325 | + | ||
326 | + /** | ||
327 | + * validate the value from a list of possible values | ||
328 | + * @type string | string[] | ||
329 | + */ | ||
330 | + enum?: string | string[]; | ||
331 | + | ||
332 | + /** | ||
333 | + * validate from a regular expression | ||
334 | + * @type boolean | ||
335 | + */ | ||
336 | + pattern?: SelectValue; | ||
337 | + | ||
338 | + /** | ||
339 | + * transform a value before validation | ||
340 | + * @type Function | ||
341 | + */ | ||
342 | + transform?: (value: any) => any; | ||
343 | + | ||
344 | + /** | ||
345 | + * custom validate function (Note: callback must be called) | ||
346 | + * @type Function | ||
347 | + */ | ||
348 | + validator?: (rule: any, value: any, callback: () => void) => any; | ||
349 | +} |
src/views/form-design/utils/index.ts
0 → 100644
1 | +// import { VueConstructor } from 'vue'; | ||
2 | +import { IVFormComponent, IFormConfig, IValidationRule } from '../typings/v-form-component'; | ||
3 | +import { cloneDeep, isArray, isFunction, isNumber, uniqueId } from 'lodash-es'; | ||
4 | +// import { del } from '@vue/composition-api'; | ||
5 | +// import { withInstall } from '/@/utils'; | ||
6 | + | ||
7 | +/** | ||
8 | + * 组件install方法 | ||
9 | + * @param comp 需要挂载install方法的组件 | ||
10 | + */ | ||
11 | +// export function withInstall<T extends { name: string }>(comp: T) { | ||
12 | +// return Object.assign(comp, { | ||
13 | +// install(Vue: VueConstructor) { | ||
14 | +// Vue.component(comp.name, comp); | ||
15 | +// }, | ||
16 | +// }); | ||
17 | +// } | ||
18 | + | ||
19 | +/** | ||
20 | + * 生成key | ||
21 | + * @param [formItem] 需要生成 key 的控件,可选,如果不传,默认返回一个唯一 key | ||
22 | + * @returns {string|boolean} 返回一个唯一 id 或者 false | ||
23 | + */ | ||
24 | +export function generateKey(formItem?: IVFormComponent): string | boolean { | ||
25 | + if (formItem && formItem.component) { | ||
26 | + const key = uniqueId(`${toLine(formItem.component)}_`); | ||
27 | + formItem.key = key; | ||
28 | + formItem.field = key; | ||
29 | + | ||
30 | + return true; | ||
31 | + } | ||
32 | + return uniqueId('key_'); | ||
33 | +} | ||
34 | + | ||
35 | +/** | ||
36 | + * 移除数组中指定元素,value可以是一个数字下标,也可以是一个函数,删除函数第一个返回true的元素 | ||
37 | + * @param array {Array<T>} 需要移除元素的数组 | ||
38 | + * @param value {number | ((item: T, index: number, array: Array<T>) => boolean} | ||
39 | + * @returns {T} 返回删除的数组项 | ||
40 | + */ | ||
41 | +export function remove<T>( | ||
42 | + array: Array<T>, | ||
43 | + value: number | ((item: T, index: number, array: Array<T>) => boolean), | ||
44 | +): T | undefined { | ||
45 | + let removeVal: Array<T | undefined> = []; | ||
46 | + if (!isArray(array)) return undefined; | ||
47 | + if (isNumber(value)) { | ||
48 | + removeVal = array.splice(value, 1); | ||
49 | + } else { | ||
50 | + const index = array.findIndex(value); | ||
51 | + if (index !== -1) { | ||
52 | + removeVal = array.splice(index, 1); | ||
53 | + } | ||
54 | + } | ||
55 | + return removeVal.shift(); | ||
56 | +} | ||
57 | + | ||
58 | +/** | ||
59 | + * 判断数据类型 | ||
60 | + * @param value | ||
61 | + */ | ||
62 | +export function getType(value: any): string { | ||
63 | + return Object.prototype.toString.call(value).slice(8, -1); | ||
64 | +} | ||
65 | + | ||
66 | +/** | ||
67 | + * 生成唯一guid | ||
68 | + * @returns {String} 唯一id标识符 | ||
69 | + */ | ||
70 | +export function randomUUID(): string { | ||
71 | + function S4() { | ||
72 | + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); | ||
73 | + } | ||
74 | + return `${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4() + S4() + S4()}`; | ||
75 | +} | ||
76 | + | ||
77 | +/** | ||
78 | + * 驼峰转下划线 | ||
79 | + * @param str | ||
80 | + */ | ||
81 | +export function toLine(str: string) { | ||
82 | + return str.replace(/([A-Z])/g, '_$1').toLowerCase(); | ||
83 | +} | ||
84 | + | ||
85 | +/** | ||
86 | + * 遍历表单项 | ||
87 | + * @param array | ||
88 | + * @param cb | ||
89 | + */ | ||
90 | +export function formItemsForEach(array: IVFormComponent[], cb: (item: IVFormComponent) => void) { | ||
91 | + if (!isArray(array)) return; | ||
92 | + const traverse = (schemas: IVFormComponent[]) => { | ||
93 | + schemas.forEach((formItem: IVFormComponent) => { | ||
94 | + if (['Grid'].includes(formItem.component)) { | ||
95 | + // 栅格布局 | ||
96 | + formItem.columns?.forEach((item) => traverse(item.children)); | ||
97 | + } else { | ||
98 | + cb(formItem); | ||
99 | + } | ||
100 | + }); | ||
101 | + }; | ||
102 | + traverse(array); | ||
103 | +} | ||
104 | + | ||
105 | +/** | ||
106 | + * 查找表单项 | ||
107 | + */ | ||
108 | +export const findFormItem: ( | ||
109 | + schemas: IVFormComponent[], | ||
110 | + cb: (formItem: IVFormComponent) => boolean, | ||
111 | +) => IVFormComponent | undefined = (schemas, cb) => { | ||
112 | + let res; | ||
113 | + const traverse = (schemas: IVFormComponent[]): boolean => { | ||
114 | + return schemas.some((formItem: IVFormComponent) => { | ||
115 | + const { component: type } = formItem; | ||
116 | + // 处理栅格 | ||
117 | + if (['Grid'].includes(type)) { | ||
118 | + return formItem.columns?.some((item) => traverse(item.children)); | ||
119 | + } | ||
120 | + if (cb(formItem)) res = formItem; | ||
121 | + return cb(formItem); | ||
122 | + }); | ||
123 | + }; | ||
124 | + traverse(schemas); | ||
125 | + return res; | ||
126 | +}; | ||
127 | + | ||
128 | +/** | ||
129 | + * 打开json模态框时删除当前项属性 | ||
130 | + * @param formConfig {IFormConfig} | ||
131 | + * @returns {IFormConfig} | ||
132 | + */ | ||
133 | +export const removeAttrs = (formConfig: IFormConfig): IFormConfig => { | ||
134 | + const copyFormConfig = cloneDeep(formConfig); | ||
135 | + delete copyFormConfig.currentItem; | ||
136 | + delete copyFormConfig.activeKey; | ||
137 | + copyFormConfig.schemas && | ||
138 | + formItemsForEach(copyFormConfig.schemas, (item) => { | ||
139 | + delete item.icon; | ||
140 | + delete item.key; | ||
141 | + }); | ||
142 | + return copyFormConfig; | ||
143 | +}; | ||
144 | + | ||
145 | +/** | ||
146 | + * 处理异步选项属性,如 select treeSelect 等选项属性如果传递为函数并且返回Promise对象,获取异步返回的选项属性 | ||
147 | + * @param {(() => Promise<any[]>) | any[]} options | ||
148 | + * @return {Promise<any[]>} | ||
149 | + */ | ||
150 | +export const handleAsyncOptions = async ( | ||
151 | + options: (() => Promise<any[]>) | any[], | ||
152 | +): Promise<any[]> => { | ||
153 | + try { | ||
154 | + if (isFunction(options)) return await options(); | ||
155 | + return options; | ||
156 | + } catch { | ||
157 | + return []; | ||
158 | + } | ||
159 | +}; | ||
160 | + | ||
161 | +/** | ||
162 | + * 格式化表单项校验规则配置 | ||
163 | + * @param {IVFormComponent[]} schemas | ||
164 | + */ | ||
165 | +export const formatRules = (schemas: IVFormComponent[]) => { | ||
166 | + formItemsForEach(schemas, (item) => { | ||
167 | + if ('required' in item) { | ||
168 | + !isArray(item.rules) && (item.rules = []); | ||
169 | + item.rules.push({ required: true, message: item.message }); | ||
170 | + delete item['required']; | ||
171 | + delete item['message']; | ||
172 | + } | ||
173 | + }); | ||
174 | +}; | ||
175 | + | ||
176 | +/** | ||
177 | + * 将校验规则中的正则字符串转换为正则对象 | ||
178 | + * @param {IValidationRule[]} rules | ||
179 | + * @return {IValidationRule[]} | ||
180 | + */ | ||
181 | +export const strToReg = (rules: IValidationRule[]) => { | ||
182 | + const newRules = cloneDeep(rules); | ||
183 | + return newRules.map((item) => { | ||
184 | + if (item.pattern) item.pattern = runCode(item.pattern); | ||
185 | + return item; | ||
186 | + }); | ||
187 | +}; | ||
188 | + | ||
189 | +/** | ||
190 | + * 执行一段字符串代码,并返回执行结果,如果执行出错,则返回该参数 | ||
191 | + * @param code | ||
192 | + * @return {any} | ||
193 | + */ | ||
194 | +export const runCode = <T>(code: any): T => { | ||
195 | + try { | ||
196 | + return new Function(`return ${code}`)(); | ||
197 | + } catch { | ||
198 | + return code; | ||
199 | + } | ||
200 | +}; |
src/views/form-design/utils/message.ts
0 → 100644
1 | +// import Vue from 'vue'; | ||
2 | + | ||
3 | +const message = Object.assign( | ||
4 | + { | ||
5 | + success: (msg: string) => { | ||
6 | + console.log(msg); | ||
7 | + }, | ||
8 | + error: (msg: string) => { | ||
9 | + console.error(msg); | ||
10 | + }, | ||
11 | + warning: (msg: string) => { | ||
12 | + console.warn(msg); | ||
13 | + }, | ||
14 | + info: (msg: string) => { | ||
15 | + console.info(msg); | ||
16 | + }, | ||
17 | + }, | ||
18 | + // Vue.prototype.$message, | ||
19 | +); | ||
20 | + | ||
21 | +export default message; |
stylelint.config.js
@@ -9,7 +9,7 @@ module.exports = { | @@ -9,7 +9,7 @@ module.exports = { | ||
9 | 'selector-pseudo-class-no-unknown': [ | 9 | 'selector-pseudo-class-no-unknown': [ |
10 | true, | 10 | true, |
11 | { | 11 | { |
12 | - ignorePseudoClasses: ['global'], | 12 | + ignorePseudoClasses: ['global', 'deep'], |
13 | }, | 13 | }, |
14 | ], | 14 | ], |
15 | 'selector-pseudo-element-no-unknown': [ | 15 | 'selector-pseudo-element-no-unknown': [ |