Commit c5b39f2c16f9aa56431844e9c01600a2d2f3848c

Authored by wwsheng009
Committed by GitHub
1 parent 4c0f2038

feat: 增加表单设计器 (#2533)

Showing 26 changed files with 4579 additions and 1 deletions

Too many changes to show.

To preserve performance only 26 of 47 files are displayed.

package.json
... ... @@ -73,7 +73,8 @@
73 73 "vxe-table": "^4.3.9",
74 74 "vxe-table-plugin-export-xlsx": "^3.0.4",
75 75 "xe-utils": "^3.5.7",
76   - "xlsx": "^0.18.5"
  76 + "xlsx": "^0.18.5",
  77 + "vuedraggable": "^4.1.0"
77 78 },
78 79 "devDependencies": {
79 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 +}
... ...