feat: 🚀 表单设计器和审批流本地化
This commit is contained in:
@@ -21,6 +21,7 @@ module.exports = {
|
||||
},
|
||||
// 继承某些已有的规则
|
||||
extends: ["plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
|
||||
ignorePatterns: ["src/components/EpicDesigner/static/icons/iconfont.js"],
|
||||
/**
|
||||
* "off" 或 0 ==> 关闭规则
|
||||
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
/public/*
|
||||
public/*
|
||||
stats.html
|
||||
|
||||
.less
|
||||
src/components/EpicDesigner/theme/var.less
|
||||
|
||||
@@ -16,6 +16,11 @@ module.exports = {
|
||||
files: ["**/*.{vue,html}"],
|
||||
customSyntax: "postcss-html"
|
||||
}
|
||||
// 新增:对 Less 文件单独配置解析器
|
||||
// {
|
||||
// files: ["**/*.less"],
|
||||
// customSyntax: "postcss-less" // 用 postcss-less 解析 Less 语法
|
||||
// }
|
||||
],
|
||||
rules: {
|
||||
"function-url-quotes": "always", // URL 的引号 "always(必须加上引号)"|"never(没有引号)"
|
||||
@@ -29,6 +34,7 @@ module.exports = {
|
||||
"value-no-vendor-prefix": null, // 关闭 vendor-prefix (为了解决多行省略 -webkit-box)
|
||||
"no-descending-specificity": null, // 不允许较低特异性的选择器出现在覆盖较高特异性的选择器
|
||||
"value-keyword-case": null, // 解决在 scss 中使用 v-bind 大写单词报错
|
||||
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
@@ -36,5 +42,5 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts"]
|
||||
ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts", "**/*.less"]
|
||||
};
|
||||
|
||||
18532
package-lock.json
generated
18532
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -20,8 +20,7 @@
|
||||
"commit": "git add -A && czg && git push"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@types/decimal.js": "^7.4.0",
|
||||
"@element-plus/icons-vue": "2.1.0",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
@@ -30,6 +29,7 @@
|
||||
"bwip-js": "^4.3.2",
|
||||
"dayjs": "^1.11.9",
|
||||
"decimal": "^0.0.2",
|
||||
"decimal.js": "^10.6.0",
|
||||
"default-passive-events": "^2.0.0",
|
||||
"driver.js": "^0.9.8",
|
||||
"element-plus": "^2.7.3",
|
||||
@@ -59,17 +59,18 @@
|
||||
"@commitlint/cli": "^17.6.3",
|
||||
"@commitlint/config-conventional": "^17.6.3",
|
||||
"@iconify-json/ep": "^1.1.10",
|
||||
"@types/decimal.js": "^7.4.0",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/js-md5": "^0.7.0",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/postcss-import": "^14.0.3",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"@unocss/vite": "^66.5.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "~3.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cz-git": "^1.6.1",
|
||||
"czg": "^1.6.1",
|
||||
@@ -79,10 +80,8 @@
|
||||
"eslint-plugin-vue": "^9.14.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.2",
|
||||
"mockjs": "^1.1.0",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-import": "^16.1.1",
|
||||
"prettier": "^2.8.8",
|
||||
"qrcode": "^1.5.3",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
@@ -95,8 +94,8 @@
|
||||
"stylelint-config-recommended-vue": "^1.4.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint-config-standard-scss": "^9.0.0",
|
||||
"typescript": "~5.0.2",
|
||||
"unocss": "^66.3.3",
|
||||
"typescript": "5.0.2",
|
||||
"unocss": "^66.5.1",
|
||||
"unplugin-auto-import": "^0.16.4",
|
||||
"unplugin-icons": "^0.16.3",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
@@ -105,11 +104,11 @@
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-mock": "^3.0.2",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-pwa": "^0.15.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "~1.6.5"
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "1.8.27"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
|
||||
2156
pnpm-lock.yaml
generated
2156
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -16,10 +16,11 @@ export namespace Global {
|
||||
number: number;
|
||||
disable: boolean;
|
||||
}
|
||||
interface ResDropDownAllItem {
|
||||
id: 15756737;
|
||||
level: 2;
|
||||
name: "jx_cs_15_01";
|
||||
number: "123456";
|
||||
}
|
||||
}
|
||||
// interface ResDropDownAllItem {
|
||||
// id: 15756737;
|
||||
// level: 2;
|
||||
// name: "jx_cs_15_01";
|
||||
// number: "123456";
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import http from "@/api";
|
||||
import FileSaver from "file-saver";
|
||||
|
||||
export const downloadXml = async (data: object) => {
|
||||
const res = await http.download("https://demo.lowflow.vip/api/model/download", data);
|
||||
FileSaver.saveAs(new Blob([res], { type: "application/octet-stream;charset=utf-8" }), "测试流程.bpmn20.xml");
|
||||
};
|
||||
@@ -143,6 +143,7 @@
|
||||
}
|
||||
.markdown h1 {
|
||||
margin-bottom: 24px;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
color: #404040;
|
||||
@@ -157,9 +158,6 @@
|
||||
font-weight: 500;
|
||||
color: #404040;
|
||||
}
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
@@ -234,6 +232,7 @@
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
white-space: nowrap;
|
||||
background: #F7F7F7;
|
||||
}
|
||||
.markdown>table th,
|
||||
.markdown>table td {
|
||||
@@ -241,9 +240,6 @@
|
||||
text-align: left;
|
||||
border: 1px solid #e9e9e9;
|
||||
}
|
||||
.markdown>table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
.markdown blockquote {
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
@@ -364,13 +360,7 @@ code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
|
||||
1612
src/assets/fonto/demo_index.html
Normal file
1612
src/assets/fonto/demo_index.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,263 +1,200 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2863944 */
|
||||
font-family: iconfont; /* Project id 2863944 */
|
||||
src: url('iconfont.woff2?t=1700032659797') format('woff2'),
|
||||
url('iconfont.woff?t=1700032659797') format('woff'),
|
||||
url('iconfont.ttf?t=1700032659797') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-family: iconfont !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-fahuochuku:before {
|
||||
.icon-fahuochuku::before {
|
||||
content: "\e670";
|
||||
}
|
||||
|
||||
.icon-shouhuoruku:before {
|
||||
.icon-shouhuoruku::before {
|
||||
content: "\e671";
|
||||
}
|
||||
|
||||
.icon-qitakuneicaozuo:before {
|
||||
.icon-qitakuneicaozuo::before {
|
||||
content: "\e672";
|
||||
}
|
||||
|
||||
.icon-pandian:before {
|
||||
.icon-pandian::before {
|
||||
content: "\e673";
|
||||
}
|
||||
|
||||
.icon-baobiao1:before {
|
||||
.icon-baobiao1::before {
|
||||
content: "\e674";
|
||||
}
|
||||
|
||||
.icon-shouqiicon:before {
|
||||
.icon-shouqiicon::before {
|
||||
content: "\e66e";
|
||||
}
|
||||
|
||||
.icon-zhankaiicon:before {
|
||||
.icon-zhankaiicon::before {
|
||||
content: "\e66a";
|
||||
}
|
||||
|
||||
.icon-kelong:before {
|
||||
.icon-kelong::before {
|
||||
content: "\e66b";
|
||||
}
|
||||
|
||||
.icon-baocun1:before {
|
||||
.icon-baocun1::before {
|
||||
content: "\e66c";
|
||||
}
|
||||
|
||||
.icon-riliicon:before {
|
||||
.icon-riliicon::before {
|
||||
content: "\e66d";
|
||||
}
|
||||
|
||||
.icon-tianjia1:before {
|
||||
.icon-tianjia1::before {
|
||||
content: "\e65e";
|
||||
}
|
||||
|
||||
.icon-fuzhi:before {
|
||||
.icon-fuzhi::before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.icon-guanbi1:before {
|
||||
.icon-guanbi1::before {
|
||||
content: "\e65b";
|
||||
}
|
||||
|
||||
.icon-bianji1:before {
|
||||
.icon-bianji1::before {
|
||||
content: "\e65a";
|
||||
}
|
||||
|
||||
.icon-shuiwuguanli:before {
|
||||
.icon-shuiwuguanli::before {
|
||||
content: "\e649";
|
||||
}
|
||||
|
||||
.icon-chengbenguanli:before {
|
||||
.icon-chengbenguanli::before {
|
||||
content: "\e645";
|
||||
}
|
||||
|
||||
.icon-yingshoukuanguanli:before {
|
||||
.icon-yingshoukuanguanli::before {
|
||||
content: "\e642";
|
||||
}
|
||||
|
||||
.icon-zijinguanli:before {
|
||||
.icon-zijinguanli::before {
|
||||
content: "\e643";
|
||||
}
|
||||
|
||||
.icon-feiyongguanli:before {
|
||||
.icon-feiyongguanli::before {
|
||||
content: "\e644";
|
||||
}
|
||||
|
||||
.icon-chunaguanli:before {
|
||||
.icon-chunaguanli::before {
|
||||
content: "\e646";
|
||||
}
|
||||
|
||||
.icon-zichanguanli:before {
|
||||
.icon-zichanguanli::before {
|
||||
content: "\e647";
|
||||
}
|
||||
|
||||
.icon-zongzhang:before {
|
||||
.icon-zongzhang::before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.icon-yingfukuanguanli:before {
|
||||
.icon-yingfukuanguanli::before {
|
||||
content: "\e64a";
|
||||
}
|
||||
|
||||
.icon-caiwu:before {
|
||||
.icon-caiwu::before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
.icon-daochu1:before {
|
||||
.icon-daochu1::before {
|
||||
content: "\e63f";
|
||||
}
|
||||
|
||||
.icon-renminbi:before {
|
||||
.icon-renminbi::before {
|
||||
content: "\e63b";
|
||||
}
|
||||
|
||||
.icon-xiazaiweikong:before {
|
||||
.icon-xiazaiweikong::before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
.icon-shanchu:before {
|
||||
.icon-shanchu::before {
|
||||
content: "\e639";
|
||||
}
|
||||
|
||||
.icon-daochu:before {
|
||||
.icon-daochu::before {
|
||||
content: "\e636";
|
||||
}
|
||||
|
||||
.icon-xiadan:before {
|
||||
.icon-xiadan::before {
|
||||
content: "\e637";
|
||||
}
|
||||
|
||||
.icon-piliang:before {
|
||||
.icon-piliang::before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.icon-zhexiantu:before {
|
||||
.icon-zhexiantu::before {
|
||||
content: "\e635";
|
||||
}
|
||||
|
||||
.icon-gengduo:before {
|
||||
.icon-gengduo::before {
|
||||
content: "\e631";
|
||||
}
|
||||
|
||||
.icon-chenggong:before {
|
||||
.icon-chenggong::before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
||||
.icon-shijing:before {
|
||||
.icon-shijing::before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.icon-xinxi:before {
|
||||
.icon-xinxi::before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.icon-renwuweikong:before {
|
||||
.icon-renwuweikong::before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.icon-liulanjiluweikong:before {
|
||||
.icon-liulanjiluweikong::before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.icon-qingchu:before {
|
||||
.icon-qingchu::before {
|
||||
content: "\e62b";
|
||||
}
|
||||
|
||||
.icon-zuobian:before {
|
||||
.icon-zuobian::before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||
.icon-youbian:before {
|
||||
.icon-youbian::before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.icon-xuanzhong:before {
|
||||
.icon-xuanzhong::before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.icon-shuangjiantoushouqi-shang:before {
|
||||
.icon-shuangjiantoushouqi-shang::before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.icon-shuangjiantouzhankai-xia:before {
|
||||
.icon-shuangjiantouzhankai-xia::before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.icon-shuaxin:before {
|
||||
.icon-shuaxin::before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.icon-tianjia:before {
|
||||
.icon-tianjia::before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-baocun:before {
|
||||
.icon-baocun::before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.icon-you-zhankaigengduo:before {
|
||||
.icon-you-zhankaigengduo::before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.icon-zuo-zhankaigengduo:before {
|
||||
.icon-zuo-zhankaigengduo::before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.icon-xia-zhankai:before {
|
||||
.icon-xia-zhankai::before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.icon-guanbi:before {
|
||||
.icon-guanbi::before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.icon-baobiao:before {
|
||||
.icon-baobiao::before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.icon-caigou:before {
|
||||
.icon-caigou::before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.icon-cangku:before {
|
||||
.icon-cangku::before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.icon-shengchan:before {
|
||||
.icon-shengchan::before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.icon-xiaoshou:before {
|
||||
.icon-xiaoshou::before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.icon-tiaoma:before {
|
||||
.icon-tiaoma::before {
|
||||
content: "\e617";
|
||||
}
|
||||
|
||||
.icon-wuliao:before {
|
||||
.icon-wuliao::before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.icon-weiwai:before {
|
||||
.icon-weiwai::before {
|
||||
content: "\e619";
|
||||
}
|
||||
|
||||
.icon-gongyingshang:before {
|
||||
.icon-gongyingshang::before {
|
||||
content: "\e61a";
|
||||
}
|
||||
|
||||
.icon-shezhi:before {
|
||||
.icon-shezhi::before {
|
||||
content: "\e61b";
|
||||
}
|
||||
|
||||
.icon-a-211:before {
|
||||
.icon-a-211::before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1707920696842" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3610" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M918.8 770.9H804.7V656.8c0-13.8-11.2-25-25-25s-25 11.2-25 25v114.1h-114c-13.8 0-25 11.2-25 25s11.2 25 25 25h114.1V935c0 13.8 11.2 25 25 25s25-11.2 25-25V820.9h114.1c13.8 0 25-11.2 25-25s-11.3-25-25.1-25zM487.4 483.1c116.5 0 211.4-94.3 211.4-210.1S603.9 62.8 487.4 62.8C370.8 62.8 276 157.1 276 273s94.8 210.1 211.4 210.1z m0-370.3c89 0 161.4 71.8 161.4 160.1S576.4 433 487.4 433 326 361.2 326 272.9s72.4-160.1 161.4-160.1z" fill="currentColor" p-id="3611"></path><path d="M560.2 909.6c-25.7-0.5-47.1-0.7-67.5-0.7-40.6 0-79 0.9-116.2 1.9-106.7 2.6-207.5 5.1-236.9-23.6-4.1-4-8.8-10.4-8.8-23.8 0-14.6 5.4-56.4 17.1-99.6 14.2-52.4 29.7-78.2 39.6-85.1 187.9-114.6 188-114.6 308.7-114.6 92.8 0 112.1 0 181.2 39.9 12 6.9 27.2 2.8 34.1-9.2 6.9-12 2.8-27.2-9.2-34.1-80.7-46.6-109.8-46.6-206.2-46.6-64.6 0-100.2 0-144.6 16.4-42.7 15.5-92.1 45.7-190.5 105.7-0.3 0.2-0.5 0.3-0.8 0.5-24.1 15.9-44.5 54.3-60.7 114-12.1 45.3-18.7 91.8-18.7 112.6 0 24.1 8 44.1 23.9 59.6 33.3 32.5 95.6 39.3 180.6 39.3 28.4 0 59.3-0.8 92.4-1.6 36.9-0.9 75-1.8 114.9-1.8 20 0 41.2 0.2 66.5 0.7h0.5c13.6 0 24.7-10.9 25-24.5 0.4-13.7-10.6-25.1-24.4-25.4z" fill="currentColor" p-id="3612"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1707920525706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3101" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M917.5 819.5H639.3c-13.8 0-25-11.2-25-25s11.2-25 25-25h278.1c13.8 0 25 11.2 25 25s-11.1 25-24.9 25zM486 481.7c-116.5 0-211.4-94.3-211.4-210.1 0-115.9 94.8-210.1 211.4-210.1 116.5 0 211.4 94.3 211.4 210.1S602.6 481.7 486 481.7z m0-370.2c-89 0-161.4 71.8-161.4 160.1S397 431.7 486 431.7s161.4-71.8 161.4-160.1S575 111.5 486 111.5z" fill="currentColor" p-id="3102"></path><path d="M284 960.9c-85 0-147.3-6.8-180.6-39.3-15.9-15.6-24-35.6-24-59.7 0-20.8 6.6-67.4 18.9-112.7 16.1-59.7 36.5-98.1 60.7-114 0.2-0.2 0.5-0.3 0.8-0.5 98.4-60 147.8-90.1 190.6-105.9 44.5-16.4 80.1-16.4 144.6-16.4 96.4 0 125.4 0 206.2 46.6 12 6.9 16.1 22.2 9.2 34.1-6.9 12-22.2 16.1-34.1 9.2-69.1-39.9-88.4-39.9-181.2-39.9-120.7 0-120.8 0-308.7 114.6-9.9 6.9-25.5 32.7-39.6 85.1-11.7 43.2-17.1 85-17.1 99.6 0 13.5 4.7 19.8 8.8 23.8 29.4 28.7 130.2 26.2 236.9 23.6 37.2-0.9 75.6-1.9 116.2-1.9 20.4 0 41.9 0.2 67.5 0.7 13.8 0.3 24.8 11.7 24.5 25.5-0.3 13.6-11.4 24.5-25 24.5h-0.5c-25.3-0.5-46.4-0.7-66.5-0.7-39.9 0-78.1 0.9-114.9 1.8-33.5 1.1-64.4 1.9-92.7 1.9z" fill="currentColor" p-id="3103"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||
|
Before Width: | Height: | Size: 276 B |
145
src/auto-import.d.ts
vendored
145
src/auto-import.d.ts
vendored
@@ -5,72 +5,87 @@
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
const EffectScope: typeof import("vue")["EffectScope"];
|
||||
const ElDivider: typeof import("element-plus/es")["ElDivider"];
|
||||
const computed: typeof import("vue")["computed"];
|
||||
const createApp: typeof import("vue")["createApp"];
|
||||
const customRef: typeof import("vue")["customRef"];
|
||||
const defineAsyncComponent: typeof import("vue")["defineAsyncComponent"];
|
||||
const defineComponent: typeof import("vue")["defineComponent"];
|
||||
const effectScope: typeof import("vue")["effectScope"];
|
||||
const getCurrentInstance: typeof import("vue")["getCurrentInstance"];
|
||||
const getCurrentScope: typeof import("vue")["getCurrentScope"];
|
||||
const h: typeof import("vue")["h"];
|
||||
const inject: typeof import("vue")["inject"];
|
||||
const isProxy: typeof import("vue")["isProxy"];
|
||||
const isReactive: typeof import("vue")["isReactive"];
|
||||
const isReadonly: typeof import("vue")["isReadonly"];
|
||||
const isRef: typeof import("vue")["isRef"];
|
||||
const markRaw: typeof import("vue")["markRaw"];
|
||||
const nextTick: typeof import("vue")["nextTick"];
|
||||
const onActivated: typeof import("vue")["onActivated"];
|
||||
const onBeforeMount: typeof import("vue")["onBeforeMount"];
|
||||
const onBeforeRouteLeave: typeof import("vue-router")["onBeforeRouteLeave"];
|
||||
const onBeforeRouteUpdate: typeof import("vue-router")["onBeforeRouteUpdate"];
|
||||
const onBeforeUnmount: typeof import("vue")["onBeforeUnmount"];
|
||||
const onBeforeUpdate: typeof import("vue")["onBeforeUpdate"];
|
||||
const onDeactivated: typeof import("vue")["onDeactivated"];
|
||||
const onErrorCaptured: typeof import("vue")["onErrorCaptured"];
|
||||
const onMounted: typeof import("vue")["onMounted"];
|
||||
const onRenderTracked: typeof import("vue")["onRenderTracked"];
|
||||
const onRenderTriggered: typeof import("vue")["onRenderTriggered"];
|
||||
const onScopeDispose: typeof import("vue")["onScopeDispose"];
|
||||
const onServerPrefetch: typeof import("vue")["onServerPrefetch"];
|
||||
const onUnmounted: typeof import("vue")["onUnmounted"];
|
||||
const onUpdated: typeof import("vue")["onUpdated"];
|
||||
const onWatcherCleanup: typeof import("vue")["onWatcherCleanup"];
|
||||
const provide: typeof import("vue")["provide"];
|
||||
const reactive: typeof import("vue")["reactive"];
|
||||
const readonly: typeof import("vue")["readonly"];
|
||||
const ref: typeof import("vue")["ref"];
|
||||
const resolveComponent: typeof import("vue")["resolveComponent"];
|
||||
const shallowReactive: typeof import("vue")["shallowReactive"];
|
||||
const shallowReadonly: typeof import("vue")["shallowReadonly"];
|
||||
const shallowRef: typeof import("vue")["shallowRef"];
|
||||
const toRaw: typeof import("vue")["toRaw"];
|
||||
const toRef: typeof import("vue")["toRef"];
|
||||
const toRefs: typeof import("vue")["toRefs"];
|
||||
const toValue: typeof import("vue")["toValue"];
|
||||
const triggerRef: typeof import("vue")["triggerRef"];
|
||||
const unref: typeof import("vue")["unref"];
|
||||
const useAttrs: typeof import("vue")["useAttrs"];
|
||||
const useCssModule: typeof import("vue")["useCssModule"];
|
||||
const useCssVars: typeof import("vue")["useCssVars"];
|
||||
const useId: typeof import("vue")["useId"];
|
||||
const useLink: typeof import("vue-router")["useLink"];
|
||||
const useModel: typeof import("vue")["useModel"];
|
||||
const useRoute: typeof import("vue-router")["useRoute"];
|
||||
const useRouter: typeof import("vue-router")["useRouter"];
|
||||
const useSlots: typeof import("vue")["useSlots"];
|
||||
const useTemplateRef: typeof import("vue")["useTemplateRef"];
|
||||
const watch: typeof import("vue")["watch"];
|
||||
const watchEffect: typeof import("vue")["watchEffect"];
|
||||
const watchPostEffect: typeof import("vue")["watchPostEffect"];
|
||||
const watchSyncEffect: typeof import("vue")["watchSyncEffect"];
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
// @ts-ignore
|
||||
export type {
|
||||
Component,
|
||||
ComponentPublicInstance,
|
||||
ComputedRef,
|
||||
DirectiveBinding,
|
||||
ExtractDefaultPropTypes,
|
||||
ExtractPropTypes,
|
||||
ExtractPublicPropTypes,
|
||||
InjectionKey,
|
||||
PropType,
|
||||
Ref,
|
||||
MaybeRef,
|
||||
MaybeRefOrGetter,
|
||||
VNode,
|
||||
WritableComputedRef
|
||||
} from "vue";
|
||||
import("vue");
|
||||
}
|
||||
|
||||
239
src/components.d.ts
vendored
239
src/components.d.ts
vendored
@@ -5,107 +5,140 @@
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
403: typeof import('./components/ErrorMessage/403.vue')['default']
|
||||
404: typeof import('./components/ErrorMessage/404.vue')['default']
|
||||
500: typeof import('./components/ErrorMessage/500.vue')['default']
|
||||
Add: typeof import('./components/ListBtns/components/Add.vue')['default']
|
||||
AdvancedFilter: typeof import('./components/AdvancedFilter/index.vue')['default']
|
||||
ColSetting: typeof import('./components/ProTable/components/ColSetting.vue')['default']
|
||||
Del: typeof import('./components/ListBtns/components/Del.vue')['default']
|
||||
Download: typeof import('./components/ListBtns/components/download.vue')['default']
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElText: typeof import('element-plus/es')['ElText']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
Empty: typeof import('./components/ProTable/components/Empty.vue')['default']
|
||||
Form: typeof import('./components/Form/index.vue')['default']
|
||||
FormTable: typeof import('./components/FormTable/index.vue')['default']
|
||||
Generate: typeof import('./components/ListBtns/components/Generate.vue')['default']
|
||||
GenerateBox: typeof import('./components/ListBtns/components/GenerateBox.vue')['default']
|
||||
Generates: typeof import('./components/ListBtns/components/Generates.vue')['default']
|
||||
Grid: typeof import('./components/Grid/index.vue')['default']
|
||||
GridItem: typeof import('./components/Grid/components/GridItem.vue')['default']
|
||||
IEpArrowDown: typeof import('~icons/ep/arrow-down')['default']
|
||||
IEpCircleClose: typeof import('~icons/ep/circle-close')['default']
|
||||
IEpFolderDelete: typeof import('~icons/ep/folder-delete')['default']
|
||||
IEpFullScreen: typeof import('~icons/ep/full-screen')['default']
|
||||
IEpRefresh: typeof import('~icons/ep/refresh')['default']
|
||||
IEpRemove: typeof import('~icons/ep/remove')['default']
|
||||
IEpSearch: typeof import('~icons/ep/search')['default']
|
||||
IEpSwitchButton: typeof import('~icons/ep/switch-button')['default']
|
||||
ImportExcel: typeof import('./components/ImportExcel/index.vue')['default']
|
||||
ListBtns: typeof import('./components/ListBtns/index.vue')['default']
|
||||
Loading: typeof import('./components/Loading/index.vue')['default']
|
||||
Operator: typeof import('./components/AdvancedFilter/Operator.vue')['default']
|
||||
PackingBox: typeof import('./components/ListBtns/components/PackingBox.vue')['default']
|
||||
Pagination: typeof import('./components/ProTable/components/Pagination.vue')['default']
|
||||
Print: typeof import('./components/ListBtns/components/Print.vue')['default']
|
||||
PrintBox: typeof import('./components/ListBtns/components/PrintBox.vue')['default']
|
||||
ProTable: typeof import('./components/ProTable/index.vue')['default']
|
||||
Refresh: typeof import('./components/ListBtns/components/Refresh.vue')['default']
|
||||
RefreshBox: typeof import('./components/ListBtns/components/RefreshBox.vue')['default']
|
||||
RolePicker: typeof import('./components/RoleSelector/RolePicker.vue')['default']
|
||||
RoleSelector: typeof import('./components/RoleSelector/index.vue')['default']
|
||||
RoleTag: typeof import('./components/RoleSelector/RoleTag.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SearchForm: typeof import('./components/SearchForm/index.vue')['default']
|
||||
SearchFormItem: typeof import('./components/SearchForm/components/SearchFormItem.vue')['default']
|
||||
Switch: typeof import('./components/ListBtns/components/Switch.vue')['default']
|
||||
TableColumn: typeof import('./components/ProTable/components/TableColumn.vue')['default']
|
||||
Trigger: typeof import('./components/AdvancedFilter/Trigger.vue')['default']
|
||||
UserPicker: typeof import('./components/UserSelector/UserPicker.vue')['default']
|
||||
UserSelector: typeof import('./components/UserSelector/index.vue')['default']
|
||||
UserTag: typeof import('./components/UserSelector/UserTag.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
declare module "vue" {
|
||||
export interface GlobalComponents {
|
||||
ActionBar: typeof import("./components/EpicDesigner/components/designer/src/modules/actionBar/index.vue")["default"];
|
||||
Add: typeof import("./components/FlowDesign/nodes/Add.vue")["default"];
|
||||
ApprovalNode: typeof import("./components/FlowDesign/nodes/ApprovalNode.vue")["default"];
|
||||
ApprovalNodeClone: typeof import("./components/FlowDesign/nodes/ApprovalNodeClone.vue")["default"];
|
||||
ApprovalPanel: typeof import("./components/FlowDesign/panels/ApprovalPanel.vue")["default"];
|
||||
ApprovalPanel1: typeof import("./components/FlowDesign/panels/ApprovalPanel1.vue")["default"];
|
||||
AssigneePanel: typeof import("./components/FlowDesign/panels/AssigneePanel.vue")["default"];
|
||||
AssigneePanelClone: typeof import("./components/FlowDesign/panels/AssigneePanelClone.vue")["default"];
|
||||
AsyncLoader: typeof import("./components/EpicDesigner/components/asyncLoader/index.vue")["default"];
|
||||
AttributeView: typeof import("./components/EpicDesigner/components/designer/src/modules/attributeView/attributeView.vue")["default"];
|
||||
Breadcrumb: typeof import("./components/EpicDesigner/components/designer/src/modules/rightSidebar/breadcrumb.vue")["default"];
|
||||
Builder: typeof import("./components/EpicDesigner/components/builder/src/builder.vue")["default"];
|
||||
CcNode: typeof import("./components/FlowDesign/nodes/CcNode.vue")["default"];
|
||||
CcNodeClone: typeof import("./components/FlowDesign/nodes/CcNodeClone.vue")["default"];
|
||||
CcPanel: typeof import("./components/FlowDesign/panels/CcPanel.vue")["default"];
|
||||
ComponentView: typeof import("./components/EpicDesigner/components/designer/src/modules/componentView/index.vue")["default"];
|
||||
ConditionNode: typeof import("./components/FlowDesign/nodes/ConditionNode.vue")["default"];
|
||||
ConditionPanel: typeof import("./components/FlowDesign/panels/ConditionPanel.vue")["default"];
|
||||
DataView: typeof import("./components/EpicDesigner/components/designer/src/modules/attributeView/dataView.vue")["default"];
|
||||
Designer: typeof import("./components/EpicDesigner/components/designer/src/designer.vue")["default"];
|
||||
EActionEditor: typeof import("./components/EpicDesigner/extensions/EActionEditor/index.vue")["default"];
|
||||
EActionEditorItem: typeof import("./components/EpicDesigner/extensions/EActionEditor/src/EActionEditorItem.vue")["default"];
|
||||
EActionEditorItem1: typeof import("./components/EpicDesigner/extensions/EActionEditor/src/EActionEditorItem1.vue")["default"];
|
||||
EActionModal: typeof import("./components/EpicDesigner/extensions/EActionEditor/src/EActionModal.vue")["default"];
|
||||
EArgsEditor: typeof import("./components/EpicDesigner/extensions/EActionEditor/src/EArgsEditor.vue")["default"];
|
||||
EColEditor: typeof import("./components/EpicDesigner/extensions/EColEditor/index.vue")["default"];
|
||||
EditContainer: typeof import("./components/EpicDesigner/components/designer/src/modules/editContainer/index.vue")["default"];
|
||||
EditNodeItem: typeof import("./components/EpicDesigner/components/designer/src/modules/editContainer/editNodeItem.vue")["default"];
|
||||
EditScreenContainer: typeof import("./components/EpicDesigner/components/designer/src/modules/editContainer/editScreenContainer.vue")["default"];
|
||||
EInputSize: typeof import("./components/EpicDesigner/extensions/EInputSize/index.vue")["default"];
|
||||
ElAside: typeof import("element-plus/es")["ElAside"];
|
||||
ElAutocomplete: typeof import("element-plus/es")["ElAutocomplete"];
|
||||
ElAvatar: typeof import("element-plus/es")["ElAvatar"];
|
||||
ElBadge: typeof import("element-plus/es")["ElBadge"];
|
||||
ElBreadcrumb: typeof import("element-plus/es")["ElBreadcrumb"];
|
||||
ElBreadcrumbItem: typeof import("element-plus/es")["ElBreadcrumbItem"];
|
||||
ElButton: typeof import("element-plus/es")["ElButton"];
|
||||
ElCard: typeof import("element-plus/es")["ElCard"];
|
||||
ElCheckbox: typeof import("element-plus/es")["ElCheckbox"];
|
||||
ElCheckboxGroup: typeof import("element-plus/es")["ElCheckboxGroup"];
|
||||
ElCol: typeof import("element-plus/es")["ElCol"];
|
||||
ElContainer: typeof import("element-plus/es")["ElContainer"];
|
||||
ElDatePicker: typeof import("element-plus/es")["ElDatePicker"];
|
||||
ElDialog: typeof import("element-plus/es")["ElDialog"];
|
||||
ElDrawer: typeof import("element-plus/es")["ElDrawer"];
|
||||
ElDropdown: typeof import("element-plus/es")["ElDropdown"];
|
||||
ElDropdownItem: typeof import("element-plus/es")["ElDropdownItem"];
|
||||
ElDropdownMenu: typeof import("element-plus/es")["ElDropdownMenu"];
|
||||
ElForm: typeof import("element-plus/es")["ElForm"];
|
||||
ElFormItem: typeof import("element-plus/es")["ElFormItem"];
|
||||
ElHeader: typeof import("element-plus/es")["ElHeader"];
|
||||
ElIcon: typeof import("element-plus/es")["ElIcon"];
|
||||
ElInput: typeof import("element-plus/es")["ElInput"];
|
||||
ElInputNumber: typeof import("element-plus/es")["ElInputNumber"];
|
||||
ElLink: typeof import("element-plus/es")["ElLink"];
|
||||
ElMain: typeof import("element-plus/es")["ElMain"];
|
||||
ElMenu: typeof import("element-plus/es")["ElMenu"];
|
||||
ElMenuItem: typeof import("element-plus/es")["ElMenuItem"];
|
||||
ElOption: typeof import("element-plus/es")["ElOption"];
|
||||
ElPopconfirm: typeof import("element-plus/es")["ElPopconfirm"];
|
||||
ElPopover: typeof import("element-plus/es")["ElPopover"];
|
||||
ElRadio: typeof import("element-plus/es")["ElRadio"];
|
||||
ElRadioButton: typeof import("element-plus/es")["ElRadioButton"];
|
||||
ElRadioGroup: typeof import("element-plus/es")["ElRadioGroup"];
|
||||
ElRow: typeof import("element-plus/es")["ElRow"];
|
||||
ElScrollbar: typeof import("element-plus/es")["ElScrollbar"];
|
||||
ElSelect: typeof import("element-plus/es")["ElSelect"];
|
||||
ElSpace: typeof import("element-plus/es")["ElSpace"];
|
||||
ElSubMenu: typeof import("element-plus/es")["ElSubMenu"];
|
||||
ElSwitch: typeof import("element-plus/es")["ElSwitch"];
|
||||
ElTable: typeof import("element-plus/es")["ElTable"];
|
||||
ElTableColumn: typeof import("element-plus/es")["ElTableColumn"];
|
||||
ElTabPane: typeof import("element-plus/es")["ElTabPane"];
|
||||
ElTabs: typeof import("element-plus/es")["ElTabs"];
|
||||
ElTag: typeof import("element-plus/es")["ElTag"];
|
||||
ElText: typeof import("element-plus/es")["ElText"];
|
||||
ElTooltip: typeof import("element-plus/es")["ElTooltip"];
|
||||
ElTree: typeof import("element-plus/es")["ElTree"];
|
||||
EndNode: typeof import("./components/FlowDesign/nodes/EndNode.vue")["default"];
|
||||
EndPanel: typeof import("./components/FlowDesign/panels/EndPanel.vue")["default"];
|
||||
EOptionsEditor: typeof import("./components/EpicDesigner/extensions/EOptionsEditor/index.vue")["default"];
|
||||
ERuleEditor: typeof import("./components/EpicDesigner/extensions/ERuleEditor/index.vue")["default"];
|
||||
ERuleItem: typeof import("./components/EpicDesigner/extensions/ERuleEditor/ERuleItem.vue")["default"];
|
||||
EScriptEdit: typeof import("./components/EpicDesigner/extensions/EActionEditor/src/EScriptEdit.vue")["default"];
|
||||
EventView: typeof import("./components/EpicDesigner/components/designer/src/modules/attributeView/eventView.vue")["default"];
|
||||
ExclusiveNode: typeof import("./components/FlowDesign/nodes/ExclusiveNode.vue")["default"];
|
||||
ExecutionListeners: typeof import("./components/FlowDesign/panels/ExecutionListeners.vue")["default"];
|
||||
FlowDesign: typeof import("./components/FlowDesign/index.vue")["default"];
|
||||
Form: typeof import("./components/EpicDesigner/ui/form/form.vue")["default"];
|
||||
FormItem: typeof import("./components/EpicDesigner/ui/formItem/formItem.vue")["default"];
|
||||
GatewayNode: typeof import("./components/FlowDesign/nodes/GatewayNode.vue")["default"];
|
||||
Header: typeof import("./components/EpicDesigner/components/designer/src/modules/header/index.vue")["default"];
|
||||
Icon: typeof import("./components/EpicDesigner/components/icon/src/icon.vue")["default"];
|
||||
IEpArrowDown: typeof import("~icons/ep/arrow-down")["default"];
|
||||
IEpCircleClose: typeof import("~icons/ep/circle-close")["default"];
|
||||
IEpFolderDelete: typeof import("~icons/ep/folder-delete")["default"];
|
||||
IEpFullScreen: typeof import("~icons/ep/full-screen")["default"];
|
||||
IEpRefresh: typeof import("~icons/ep/refresh")["default"];
|
||||
IEpRemove: typeof import("~icons/ep/remove")["default"];
|
||||
IEpSearch: typeof import("~icons/ep/search")["default"];
|
||||
IEpSwitchButton: typeof import("~icons/ep/switch-button")["default"];
|
||||
MonacoEditor: typeof import("./components/EpicDesigner/extensions/MonacoEditor/index.vue")["default"];
|
||||
Node: typeof import("./components/FlowDesign/nodes/Node.vue")["default"];
|
||||
NodeItem: typeof import("./components/EpicDesigner/components/designer/src/modules/editContainer/nodeItem.vue")["default"];
|
||||
NotifyNode: typeof import("./components/FlowDesign/nodes/NotifyNode.vue")["default"];
|
||||
NotifyNodeC: typeof import("./components/FlowDesign/nodes/NotifyNodeC.vue")["default"];
|
||||
NotifyNodeClone: typeof import("./components/FlowDesign/nodes/NotifyNodeClone.vue")["default"];
|
||||
NotifyPanel: typeof import("./components/FlowDesign/panels/NotifyPanel.vue")["default"];
|
||||
OptionsCol: typeof import("./components/EpicDesigner/extensions/EOptionsEditor/optionsCol.vue")["default"];
|
||||
Outline: typeof import("./components/EpicDesigner/components/designer/src/modules/outline/outline.vue")["default"];
|
||||
Page: typeof import("./components/EpicDesigner/extensions/Page/index.vue")["default"];
|
||||
Panels: typeof import("./components/FlowDesign/panels/index.vue")["default"];
|
||||
ParallelNode: typeof import("./components/FlowDesign/nodes/ParallelNode.vue")["default"];
|
||||
Preview: typeof import("./components/EpicDesigner/components/designer/src/modules/preview/index.vue")["default"];
|
||||
PreviewJson: typeof import("./components/EpicDesigner/components/designer/src/modules/editContainer/previewJson.vue")["default"];
|
||||
PreviewWidgets: typeof import("./components/EpicDesigner/components/designer/src/modules/editContainer/previewWidgets.vue")["default"];
|
||||
RightSidebar: typeof import("./components/EpicDesigner/components/designer/src/modules/rightSidebar/index.vue")["default"];
|
||||
RouterLink: typeof import("vue-router")["RouterLink"];
|
||||
RouterView: typeof import("vue-router")["RouterView"];
|
||||
ServiceNode: typeof import("./components/FlowDesign/nodes/ServiceNode.vue")["default"];
|
||||
ServicePanel: typeof import("./components/FlowDesign/panels/ServicePanel.vue")["default"];
|
||||
SoundCode: typeof import("./components/EpicDesigner/components/designer/src/modules/soundCode/index.vue")["default"];
|
||||
StartNode: typeof import("./components/FlowDesign/nodes/StartNode.vue")["default"];
|
||||
StartPanel: typeof import("./components/FlowDesign/panels/StartPanel.vue")["default"];
|
||||
StyleView: typeof import("./components/EpicDesigner/components/designer/src/modules/attributeView/styleView.vue")["default"];
|
||||
TaskListeners: typeof import("./components/FlowDesign/panels/TaskListeners.vue")["default"];
|
||||
TimerNode: typeof import("./components/FlowDesign/nodes/TimerNode.vue")["default"];
|
||||
TimerPanel: typeof import("./components/FlowDesign/panels/TimerPanel.vue")["default"];
|
||||
Toolbar: typeof import("./components/EpicDesigner/components/designer/src/modules/editContainer/toolbar.vue")["default"];
|
||||
Tree: typeof import("./components/EpicDesigner/components/tree/src/tree.vue")["default"];
|
||||
TreeNode: typeof import("./components/FlowDesign/nodes/TreeNode.vue")["default"];
|
||||
TreeNodeItem: typeof import("./components/EpicDesigner/components/tree/src/treeNodeItem.vue")["default"];
|
||||
TreeNodes: typeof import("./components/EpicDesigner/components/tree/src/treeNodes.vue")["default"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// 基础选择器合并(解决重复定义问题)
|
||||
.e-loading {
|
||||
// 共享基础样式
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
// 显示与颜色样式
|
||||
display: block;
|
||||
|
||||
// 基础尺寸样式
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
font-size: 0;
|
||||
color: var(--epic-primary-color);
|
||||
}
|
||||
.e-loading > div {
|
||||
// 共享基础样式(position 会被后续定义覆盖,保持原逻辑)
|
||||
position: relative;
|
||||
|
||||
// 定位与动画样式(覆盖上方的 position: relative)
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
// 显示与背景样式
|
||||
display: inline-block;
|
||||
float: none;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-color: currentColor;
|
||||
border: 0 solid currentColor;
|
||||
border-radius: 100%;
|
||||
opacity: 0;
|
||||
animation: ball-scale-multiple 1s 0s linear infinite;
|
||||
}
|
||||
|
||||
// 衍生样式(无重复,保持原样)
|
||||
.e-loading.la-dark {
|
||||
color: #333333;
|
||||
}
|
||||
.e-loading > div:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.e-loading > div:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
// 尺寸变体样式
|
||||
.e-loading.la-sm {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.e-loading.la-sm > div {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.e-loading.la-2x {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
.e-loading.la-2x > div {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
.e-loading.la-3x {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
.e-loading.la-3x > div {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
// 动画定义
|
||||
@keyframes ball-scale-multiple {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
5% {
|
||||
opacity: 0.75;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="e-loading">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</template>
|
||||
3
src/components/EpicDesigner/components/builder/index.ts
Normal file
3
src/components/EpicDesigner/components/builder/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import EBuilder from "./src/builder.vue";
|
||||
|
||||
export default EBuilder;
|
||||
234
src/components/EpicDesigner/components/builder/src/builder.vue
Normal file
234
src/components/EpicDesigner/components/builder/src/builder.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<Suspense @resolve="handleReady">
|
||||
<template #default>
|
||||
<div class="epic-builder-main">
|
||||
<ENode v-for="(item, index) in pageSchemaReactive.schemas" :key="index" :componentSchema="item" />
|
||||
</div>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="epic-loading-box">
|
||||
<EAsyncLoader />
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ENode from "../../node";
|
||||
import {
|
||||
reactive,
|
||||
provide,
|
||||
computed,
|
||||
ref,
|
||||
watch,
|
||||
useSlots,
|
||||
nextTick,
|
||||
getCurrentInstance,
|
||||
type ComponentInternalInstance
|
||||
} from "vue";
|
||||
import { PageSchema, FormDataModel } from "../../../types/epic-designer";
|
||||
import {
|
||||
loadAsyncComponent,
|
||||
// pluginManager,
|
||||
deepCompareAndModify,
|
||||
deepClone,
|
||||
usePageManager
|
||||
} from "@/components/EpicDesigner/utils";
|
||||
const EAsyncLoader = loadAsyncComponent(() => import("../../asyncLoader/index.vue"));
|
||||
|
||||
const pageManager = usePageManager();
|
||||
const emit = defineEmits<{
|
||||
ready: any;
|
||||
}>();
|
||||
const slots = useSlots();
|
||||
const forms = ref<any>({});
|
||||
const ready = ref<boolean>(false);
|
||||
const props = defineProps<{
|
||||
pageSchema: PageSchema;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
const pageSchemaReactive = reactive<PageSchema>({
|
||||
schemas: []
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.pageSchema,
|
||||
e => {
|
||||
deepCompareAndModify(pageSchemaReactive, e);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => pageSchemaReactive.script,
|
||||
e => {
|
||||
if (e && e !== "") {
|
||||
pageManager.setMethods(e, true);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
provide("slots", slots);
|
||||
provide("pageManager", pageManager);
|
||||
provide("forms", forms);
|
||||
provide("pageSchema", pageSchemaReactive);
|
||||
provide(
|
||||
"disabled",
|
||||
computed(() => props.disabled)
|
||||
);
|
||||
|
||||
/**
|
||||
* 跳过验证直接获取表单数据
|
||||
* @param formName 表单name
|
||||
*/
|
||||
function getData(formName = "default"): Promise<FormDataModel | boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 判断表单是否已经初始化
|
||||
if (!ready.value) {
|
||||
// 监听表单初始化状态
|
||||
const unwatch = watch(ready, async () => {
|
||||
// 注销监听
|
||||
unwatch();
|
||||
resolve(await getData(formName));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const form = forms.value?.[formName];
|
||||
// 通过表单查询不到表单实例
|
||||
if (!form) {
|
||||
// console.error(`表单 [name=${formName}] 不存在`)
|
||||
reject(`表单 [name=${formName}] 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const formData = deepClone(await form.getData());
|
||||
resolve(formData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并获取数据
|
||||
* @param formName 表单name
|
||||
*/
|
||||
function validate(formName = "default"): Promise<FormDataModel | boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 判断表单是否已经初始化
|
||||
if (!ready.value) {
|
||||
// 监听表单初始化状态
|
||||
const unwatch = watch(ready, async () => {
|
||||
// 注销监听
|
||||
unwatch();
|
||||
resolve(await validate(formName));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const form = forms.value?.[formName];
|
||||
// 通过表单查询不到表单实例
|
||||
if (!form) {
|
||||
// console.error(`表单 [name=${formName}] 不存在`)
|
||||
reject(`表单 [name=${formName}] 不存在`);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await form.validate();
|
||||
const formData = await form.getData();
|
||||
resolve(formData);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单数据
|
||||
* @param data
|
||||
*/
|
||||
function setData(data: FormDataModel, formName = "default") {
|
||||
// 判断表单是否已经初始化
|
||||
if (!ready.value) {
|
||||
// 监听表单初始化状态
|
||||
const unwatch = watch(ready, () => {
|
||||
// 注销监听
|
||||
unwatch();
|
||||
setData(data, formName);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const form = forms.value?.[formName];
|
||||
// 通过表单查询不到表单实例
|
||||
if (!form) {
|
||||
console.error(`表单 [name=${formName}] 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
form.setData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单实例的异步函数
|
||||
* @param {string} formName - 表单名称,默认为 'default'
|
||||
* @returns {Promise<any | boolean>} - 返回一个 Promise 对象,可能是表单实例或布尔值
|
||||
*/
|
||||
function getFormInstance(formName = "default"): Promise<any | boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 判断表单是否已经初始化
|
||||
if (!ready.value) {
|
||||
// 监听表单初始化状态
|
||||
const unwatch = watch(ready, async () => {
|
||||
// 注销监听
|
||||
unwatch();
|
||||
// 重新调用 getFormInstance,直到表单初始化完成
|
||||
resolve(await getFormInstance(formName));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取指定名称的表单实例
|
||||
const form = forms.value?.[formName];
|
||||
// 通过表单查询不到表单实例
|
||||
if (!form) {
|
||||
// 输出错误信息并拒绝 Promise
|
||||
// console.error(`表单 [name=${formName}] 不存在`);
|
||||
reject(`表单 [name=${formName}] 不存在`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 成功获取表单实例,解析 Promise
|
||||
resolve(form);
|
||||
});
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
/**
|
||||
* 组件(包含异步组件)加载完成后
|
||||
*/
|
||||
function handleReady() {
|
||||
// 等待DOM更新循环结束后
|
||||
nextTick(() => {
|
||||
ready.value = true;
|
||||
|
||||
emit("ready", pageManager);
|
||||
|
||||
// 注入builder对象
|
||||
proxy && pageManager.addComponentInstance("builder", proxy);
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
ready,
|
||||
getData,
|
||||
setData,
|
||||
validate,
|
||||
getFormInstance
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DesignerProps {
|
||||
disabledZoom?: boolean;
|
||||
hiddenHeader?: boolean;
|
||||
}
|
||||
3
src/components/EpicDesigner/components/designer/index.ts
Normal file
3
src/components/EpicDesigner/components/designer/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import EDesigner from "./src/designer.vue";
|
||||
|
||||
export default EDesigner;
|
||||
288
src/components/EpicDesigner/components/designer/src/designer.vue
Normal file
288
src/components/EpicDesigner/components/designer/src/designer.vue
Normal file
@@ -0,0 +1,288 @@
|
||||
<template>
|
||||
<Suspense @resolve="handleReady">
|
||||
<template #default>
|
||||
<div class="epic-designer-main">
|
||||
<div class="epic-header-container">
|
||||
<slot name="header">
|
||||
<EHeader v-if="!props.hiddenHeader" @save="handleSave">
|
||||
<template #header>
|
||||
<slot name="header-prefix"></slot>
|
||||
</template>
|
||||
|
||||
<template #prefix>
|
||||
<slot name="header-prefix"></slot>
|
||||
</template>
|
||||
<template #title>
|
||||
<slot name="header-title"></slot>
|
||||
</template>
|
||||
<template #right-prefix>
|
||||
<slot name="header-right-prefix"></slot>
|
||||
</template>
|
||||
<template #right-action>
|
||||
<slot name="header-right-action"></slot>
|
||||
</template>
|
||||
<template #right-suffix>
|
||||
<slot name="header-right-suffix"></slot>
|
||||
</template>
|
||||
</EHeader>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="epic-split-view-container" :class="{ 'hidden-header': hiddenHeader }">
|
||||
<EActionBar />
|
||||
<EEditContainer />
|
||||
<ERightSidebar />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="epic-loading-box">
|
||||
<EAsyncLoader />
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { provide, reactive, toRaw, watch, nextTick, computed } from "vue";
|
||||
import { ComponentSchema, PageSchema } from "@/components/EpicDesigner/types/epic-designer";
|
||||
import {
|
||||
getMatchedById,
|
||||
loadAsyncComponent,
|
||||
revoke,
|
||||
usePageManager,
|
||||
// pluginManager,
|
||||
deepCompareAndModify,
|
||||
deepEqual,
|
||||
deepClone
|
||||
} from "@/components/EpicDesigner/utils";
|
||||
import { DesignerProps } from "./types";
|
||||
import { useShareStore } from "@/components/EpicDesigner/utils/shareStore";
|
||||
|
||||
const EHeader = loadAsyncComponent(() => import("./modules/header/index.vue"));
|
||||
const EActionBar = loadAsyncComponent(() => import("./modules/actionBar/index.vue"));
|
||||
const EEditContainer = loadAsyncComponent(() => import("./modules/editContainer/index.vue"));
|
||||
const ERightSidebar = loadAsyncComponent(() => import("./modules/rightSidebar/index.vue"));
|
||||
const EAsyncLoader = loadAsyncComponent(() => import("../../asyncLoader/index.vue"));
|
||||
const pageManager = usePageManager();
|
||||
|
||||
const props = withDefaults(defineProps<DesignerProps>(), {
|
||||
disabledZoom: false,
|
||||
hiddenHeader: false,
|
||||
lockDefaultSchemaEdit: false,
|
||||
title: "EDesigner默认项目",
|
||||
defaultSchema: () => ({
|
||||
schemas: [
|
||||
{
|
||||
type: "page",
|
||||
id: "root",
|
||||
label: "页面",
|
||||
children: [],
|
||||
componentProps: {
|
||||
style: {
|
||||
padding: "16px"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
script: `const { defineExpose, find } = epic;
|
||||
|
||||
function test (){
|
||||
console.log('test')
|
||||
}
|
||||
|
||||
// 通过defineExpose暴露的函数或者属性
|
||||
defineExpose({
|
||||
test
|
||||
})`
|
||||
})
|
||||
});
|
||||
|
||||
// 设置为设计模式
|
||||
pageManager.setDesignMode();
|
||||
pageManager.setDefaultComponentIds(props.defaultSchema.schemas);
|
||||
|
||||
const emits = defineEmits(["ready", "save", "reset", "toggleDeviceMode"]);
|
||||
|
||||
const state = reactive<any>({
|
||||
checkedNode: null,
|
||||
hoverNode: null,
|
||||
disableHover: false,
|
||||
matched: [],
|
||||
dataSource: [],
|
||||
status: false
|
||||
});
|
||||
|
||||
const pageSchema = reactive<PageSchema>({
|
||||
schemas: [],
|
||||
script: props.defaultSchema.script
|
||||
});
|
||||
|
||||
// 记录缩放状态 start
|
||||
const { disabledZoom } = useShareStore();
|
||||
watch(
|
||||
() => props.disabledZoom,
|
||||
newVal => {
|
||||
disabledZoom.value = newVal;
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
// 记录缩放状态 end
|
||||
|
||||
watch(
|
||||
() => pageSchema.script,
|
||||
e => {
|
||||
if (e && e !== "") {
|
||||
pageManager.setMethods(e);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
provide("pageSchema", pageSchema);
|
||||
provide("pageManager", pageManager);
|
||||
provide(
|
||||
"designerProps",
|
||||
computed(() => props)
|
||||
);
|
||||
|
||||
provide("designer", {
|
||||
setCheckedNode,
|
||||
setHoverNode,
|
||||
setDisableHover,
|
||||
handleToggleDeviceMode,
|
||||
reset,
|
||||
state
|
||||
});
|
||||
|
||||
function init() {
|
||||
// 初始化默认节点
|
||||
pageSchema.schemas = deepClone(props.defaultSchema.schemas);
|
||||
|
||||
// 选中根节点
|
||||
setCheckedNode(pageSchema.schemas[0]);
|
||||
revoke.push(pageSchema.schemas, "初始化撤销功能");
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中节点
|
||||
* @param schema
|
||||
*/
|
||||
async function setCheckedNode(schema: ComponentSchema = pageSchema.schemas[0]) {
|
||||
state.checkedNode = schema;
|
||||
state.matched = getMatchedById(pageSchema.schemas, schema.id!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置悬停节点
|
||||
* @param schema
|
||||
*/
|
||||
async function setHoverNode(schema: ComponentSchema | null = null) {
|
||||
if (!schema || state.disableHover) {
|
||||
state.hoverNode = null;
|
||||
return false;
|
||||
}
|
||||
if (schema?.id === state.hoverNode?.id) {
|
||||
return false;
|
||||
}
|
||||
// console.log(schema?.id)
|
||||
state.hoverNode = schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件(包含异步组件)加载完成后
|
||||
*/
|
||||
function handleReady() {
|
||||
// 等待DOM更新循环结束后
|
||||
nextTick(() => {
|
||||
emits("ready", { pageManager });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置hover状态
|
||||
* @param disableHover
|
||||
*/
|
||||
async function setDisableHover(disableHover = false) {
|
||||
state.disableHover = disableHover;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接受一个PageSchema对象作为参数。根据传入的schemas和script属性,更新页面对应的数据
|
||||
* @param pageSchema
|
||||
*/
|
||||
function setData(schema: PageSchema) {
|
||||
// 调用 deepCompareAndModify 函数比较 pageSchema 和传入的 schema,进行修改
|
||||
deepCompareAndModify(pageSchema, schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前页面数据的 PageSchema 对象,包含页面当前的 schemas 和 script 数据。
|
||||
*/
|
||||
function getData(): PageSchema {
|
||||
// 返回一个对象,包含当前 schemas 对象的普通对象表示和当前 script 的值
|
||||
return toRaw(pageSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前页面数据的 state.dataSource 对象,
|
||||
*/
|
||||
function getDataSource() {
|
||||
// 返回一个对象,包含当前 state.dataSource 对象的普通对象
|
||||
// console.log(state.dataSource, "getdataSource")
|
||||
state.status = false;
|
||||
return toRaw(state.dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置state.dataSource 的初始化值
|
||||
*/
|
||||
function setDataSource(obj: any) {
|
||||
let arr = JSON.parse(obj);
|
||||
//参数:当前表单设计器的数据库字段
|
||||
state.dataSource = arr;
|
||||
state.status = true;
|
||||
// console.log(obj, "getdataSource")
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置页面数据为默认数据。
|
||||
*/
|
||||
function reset() {
|
||||
// 判断数据是否已修改,如果未修改,则取消重置操作
|
||||
if (deepEqual(pageSchema.schemas, props.defaultSchema.schemas) && pageSchema.script === props.defaultSchema.script) return;
|
||||
|
||||
// 调用 deepCompareAndModify 函数比较 pageSchema.schemas 和 props.defaultSchema.schemas,进行修改
|
||||
deepCompareAndModify(pageSchema.schemas, props.defaultSchema.schemas);
|
||||
// 更新 script.value
|
||||
pageSchema.script = props.defaultSchema.script;
|
||||
// 选中根节点
|
||||
setCheckedNode(pageSchema.schemas[0]);
|
||||
revoke.push(pageSchema.schemas, "重置操作");
|
||||
|
||||
emits("reset", pageSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据
|
||||
*/
|
||||
function handleSave() {
|
||||
emits("save", toRaw(pageSchema));
|
||||
}
|
||||
|
||||
function handleToggleDeviceMode(mode: string) {
|
||||
emits("toggleDeviceMode", mode);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
defineExpose({
|
||||
setData,
|
||||
getData,
|
||||
reset,
|
||||
getDataSource,
|
||||
setDataSource
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
@import "./modules/rightSidebar/index.less";
|
||||
@import "./modules/outline/index.less";
|
||||
@import "./modules/header/index.less";
|
||||
@import "./modules/actionBar/index.less";
|
||||
@import "./modules/componentView/index.less";
|
||||
@import "./modules/editContainer/index.less";
|
||||
@import "./modules/attributeView/index.less";
|
||||
@import "./modules/soundCode/index.less";
|
||||
.epic-loading-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
// 覆盖iconfont样式
|
||||
.iconfont {
|
||||
font-size: 1em;
|
||||
}
|
||||
.epic-designer-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 500px;
|
||||
height: calc(100% - 150px);
|
||||
max-height: 100vh;
|
||||
font-size: var(--epic-text-md);
|
||||
color: var(--epic-text-main);
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
background: var(--epic-designer-color);
|
||||
div,
|
||||
aside {
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #0000002d;
|
||||
border-radius: 6px;
|
||||
box-shadow: none;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.epic-split-view-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
border-top: 1px solid var(--epic-border-color);
|
||||
border-bottom: 1px solid var(--epic-border-color);
|
||||
}
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
}
|
||||
.epic-modal-main {
|
||||
height: calc(100% - 56px);
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid var(--epic-border-color);
|
||||
}
|
||||
.epic-modal-footer {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
.ant-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 活动视图切换器
|
||||
.epic-action-bar {
|
||||
width: 56px;
|
||||
background-color: var(--epic-action-bar-color);
|
||||
border-right: 1px solid var(--epic-border-color);
|
||||
border-left: 1px solid var(--epic-border-color);
|
||||
.epic-action-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 72px;
|
||||
color: var(--epic-text-main);
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
> span {
|
||||
font-size: 20px;
|
||||
}
|
||||
&:hover {
|
||||
color: var(--epic-primary-color);
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
content: "";
|
||||
background-color: var(--epic-primary-hover-color);
|
||||
border-radius: 6px;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
&.checked {
|
||||
color: var(--epic-primary-color);
|
||||
|
||||
// &::after {
|
||||
// content: "";
|
||||
// height: 100%;
|
||||
// width: 2px;
|
||||
// background-color: var(--epic-primary-color);
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// }
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧栏
|
||||
.epic-left-sidebar {
|
||||
width: 320px;
|
||||
overflow: hidden auto;
|
||||
background-color: var(--epic-view-color);
|
||||
border-right: 1px solid var(--epic-border-color);
|
||||
transition: all 0.3s;
|
||||
.epic-sidebar-container {
|
||||
min-width: 280px;
|
||||
height: 100%;
|
||||
}
|
||||
&.hide {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="flex relative">
|
||||
<div class="epic-action-bar">
|
||||
<ul class="epic-actions-container">
|
||||
<!-- 只有 activitybars 有数据时才渲染 -->
|
||||
<li
|
||||
v-for="(item, index) in activitybars"
|
||||
:key="item.id || index"
|
||||
class="epic-action-item"
|
||||
:title="item.title"
|
||||
:class="{ checked: actionBarCheckedIndex === index }"
|
||||
@click="handleClick(item, index)"
|
||||
>
|
||||
<EIcon prefix="" class="iconfont relative" :name="item.icon" />
|
||||
<div class="text-14px">{{ item.title }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 只有选中索引有效且有侧边栏组件时才显示 -->
|
||||
<div class="epic-left-sidebar" :class="{ hide: !actionBarCheckedIndex && actionBarCheckedIndex !== 0 }">
|
||||
<div class="epic-sidebar-container">
|
||||
<component :is="sidebarComponent" v-if="sidebarComponent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, shallowRef, watchEffect } from "vue";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { ActivitybarModel } from "@/components/EpicDesigner/utils";
|
||||
import EIcon from "../../../../icon";
|
||||
|
||||
defineOptions({
|
||||
name: "EActionBar"
|
||||
});
|
||||
|
||||
// 1. 计算属性:过滤可见的 activitybar 项
|
||||
const activitybars = computed<ActivitybarModel[]>(() => {
|
||||
// 安全处理:确保 pluginManager.viewsContainers.activitybars.value 是数组
|
||||
const rawActivitybars = pluginManager.viewsContainers.activitybars.value || [];
|
||||
return rawActivitybars.filter(item => item?.visible !== false); // 避免 item 为 undefined 导致的过滤错误
|
||||
});
|
||||
|
||||
// 2. 选中索引:初始化为 null(避免空数组时默认 0 导致错误)
|
||||
const actionBarCheckedIndex = ref<number | null>(null);
|
||||
|
||||
// 3. 侧边栏组件:用 shallowRef 存储,初始为 null
|
||||
const sidebarComponent = shallowRef<ActivitybarModel["component"] | null>(null);
|
||||
|
||||
// 4. 响应式初始化:当 activitybars 变化时,安全设置默认选中项
|
||||
watchEffect(() => {
|
||||
const validActivitybars = activitybars.value;
|
||||
// 如果有有效项,且当前未选中任何项,则选中第一个
|
||||
if (validActivitybars.length > 0 && actionBarCheckedIndex.value === null) {
|
||||
actionBarCheckedIndex.value = 0;
|
||||
sidebarComponent.value = validActivitybars[0].component; // 此时 validActivitybars[0] 一定存在
|
||||
}
|
||||
// 如果没有有效项,重置状态
|
||||
else if (validActivitybars.length === 0) {
|
||||
actionBarCheckedIndex.value = null;
|
||||
sidebarComponent.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 点击事件:增加空值判断,避免无效操作
|
||||
function handleClick(item: ActivitybarModel, index: number) {
|
||||
// 安全校验:确保 item 有 component(避免无效项点击)
|
||||
if (!item?.component) return false;
|
||||
|
||||
// 切换选中状态(点击已选中项则取消)
|
||||
if (actionBarCheckedIndex.value === index) {
|
||||
actionBarCheckedIndex.value = null;
|
||||
sidebarComponent.value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置新选中项和侧边栏组件
|
||||
sidebarComponent.value = item.component;
|
||||
actionBarCheckedIndex.value = index;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="epic-attribute-view" :key="checkedNode?.id">
|
||||
<div v-for="item in componentAttributes" :key="item.field">
|
||||
<div v-if="isShow(item)" class="epic-attr-item" :class="item.layout">
|
||||
<div class="epic-attr-label" :title="item.label">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div class="epic-attr-input">
|
||||
<ENode
|
||||
:componentSchema="{
|
||||
...item,
|
||||
componentProps: {
|
||||
...item.componentProps,
|
||||
...(item.field === 'componentProps.defaultValue' ? checkedNode?.componentProps : {}),
|
||||
input: false,
|
||||
field: undefined,
|
||||
hidden: false
|
||||
},
|
||||
show: true,
|
||||
noFormItem: true
|
||||
}"
|
||||
:model-value="getAttributeValue(item.field!, item.editData ?? checkedNode!)"
|
||||
@update:model-value="handleSetValue($event, item.field!, item, item.editData)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ENode from "../../../../node/index";
|
||||
import { Designer, ComponentSchema, PageSchema } from "../../../../../types/epic-designer";
|
||||
import { pluginManager, revoke, getAttributeValue, setAttributeValue } from "@/components/EpicDesigner/utils";
|
||||
import { inject, computed, ref, watch, nextTick } from "vue";
|
||||
const designer = inject("designer") as Designer;
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
|
||||
const componentConfings = pluginManager.getComponentConfings();
|
||||
const checkedNode = computed(() => {
|
||||
return designer.state.checkedNode;
|
||||
});
|
||||
|
||||
function isShow(item: ComponentSchema) {
|
||||
// show属性为boolean类型则直接返回
|
||||
if (typeof item.show === "boolean") {
|
||||
return item.show;
|
||||
}
|
||||
|
||||
// show属性为function类型则执行
|
||||
if (typeof item.show === "function") {
|
||||
return item.show?.({ values: checkedNode.value! });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const componentAttributes = ref<ComponentSchema[]>([]);
|
||||
watch(
|
||||
() => designer.state.checkedNode?.type,
|
||||
() => {
|
||||
const type = designer.state.checkedNode?.type;
|
||||
if (!type) {
|
||||
return [];
|
||||
}
|
||||
const attribute = componentConfings[type]?.config.attribute ?? [];
|
||||
|
||||
componentAttributes.value = [
|
||||
{
|
||||
label: "组件ID",
|
||||
type: "input",
|
||||
field: "id",
|
||||
componentProps: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
...attribute
|
||||
];
|
||||
|
||||
if (type === "page") {
|
||||
componentAttributes.value.push(
|
||||
...[
|
||||
{
|
||||
label: "画布宽度",
|
||||
type: "EInputSize",
|
||||
field: "canvas.width",
|
||||
editData: pageSchema
|
||||
},
|
||||
{
|
||||
label: "画布高度",
|
||||
type: "EInputSize",
|
||||
field: "canvas.height",
|
||||
editData: pageSchema
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 设置属性值
|
||||
*/
|
||||
function handleSetValue(value: any, field: string, item: ComponentSchema, editData = checkedNode.value) {
|
||||
if (typeof item.onChange === "function") {
|
||||
item.onChange({ value, values: editData!, componentAttributes });
|
||||
}
|
||||
console.log(value, field, editData!, "改变属性");
|
||||
// 判断是否同步修改属性值
|
||||
if (item.changeSync) {
|
||||
setAttributeValue(value, field, editData!);
|
||||
} else {
|
||||
nextTick(() => {
|
||||
setAttributeValue(value, field, editData!);
|
||||
});
|
||||
}
|
||||
// 将修改过的组件属性推入撤销操作的栈中
|
||||
revoke.push(pageSchema.schemas, "编辑组件属性");
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div class="dataView_box" :style="isDark ? 'color: #fff;' : 'color: #333;'">
|
||||
<p>当前组件:</p>
|
||||
<div
|
||||
class="card_box"
|
||||
:style="
|
||||
isDark
|
||||
? ' border: 1px solid #a3a2a25e;background-color: #d1d1d115;'
|
||||
: ' border: 1px solid #d6d5d5;background-color: #c4c3c31a;'
|
||||
"
|
||||
>
|
||||
<p>组件id:{{ templateID }}</p>
|
||||
<p>组件类型:{{ templateType }}</p>
|
||||
</div>
|
||||
<p>字段设置:</p>
|
||||
<div
|
||||
class="card_box"
|
||||
:style="
|
||||
isDark
|
||||
? ' border: 1px solid #a3a2a25e;background-color: #d1d1d115;'
|
||||
: ' border: 1px solid #d6d5d5;background-color: #c4c3c31a;'
|
||||
"
|
||||
>
|
||||
<p>
|
||||
字段名:
|
||||
<el-input style="width: 60%" v-model="dataSQL.name" @change="changeName($event, 'field')" />
|
||||
</p>
|
||||
<p>
|
||||
注释:
|
||||
<el-input style="width: 60%" v-model="dataSQL.info" @change="changeName($event, 'label')" />
|
||||
</p>
|
||||
<p>
|
||||
字段类型:
|
||||
<el-select
|
||||
style="width: 60%"
|
||||
placeholder="请选择字段类型"
|
||||
v-model="dataSQL.type"
|
||||
@change="changeSQL"
|
||||
:disabled="edit"
|
||||
>
|
||||
<el-option v-for="option in selectOptions" :key="option.value" :label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</p>
|
||||
<p>
|
||||
字段长度:
|
||||
<el-input-number
|
||||
style="width: 60%"
|
||||
:min="edit ? dataSQL.length : 1"
|
||||
:max="255"
|
||||
placeholder="请输入字段长度"
|
||||
v-model="dataSQL.length"
|
||||
@change="changeSQL"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
能否为NULL:
|
||||
<el-switch v-model="dataSQL.isNull" @change="changeSQL" />
|
||||
</p>
|
||||
<p>
|
||||
是否主键:
|
||||
<el-switch v-model="dataSQL.isKey" @change="changeSQL" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Designer } from "../../../../../types/epic-designer";
|
||||
import { inject, reactive, ref, watch, onMounted, computed } from "vue";
|
||||
import { ElInput, ElSelect, ElOption, ElInputNumber, ElSwitch } from "element-plus";
|
||||
import { setAttributeValue, setdataSource } from "@/components/EpicDesigner/utils";
|
||||
import { useTheme } from "@/components/EpicDesigner/utils";
|
||||
const { isDark } = useTheme();
|
||||
|
||||
/**
|
||||
* 页面data
|
||||
* */
|
||||
const designer = inject("designer") as Designer;
|
||||
let templateID = ref("");
|
||||
let templateType = ref("");
|
||||
let dataSQL = reactive({
|
||||
name: "",
|
||||
type: "",
|
||||
length: 50,
|
||||
isNull: true,
|
||||
isKey: false,
|
||||
info: ""
|
||||
});
|
||||
|
||||
const selectOptions = ref([
|
||||
{ value: "int", label: "int" },
|
||||
{ value: "bigint", label: "bigint" },
|
||||
{ value: "float", label: "float" },
|
||||
{ value: "double", label: "double" },
|
||||
{ value: "char", label: "char" },
|
||||
{ value: "varchar", label: "varchar" },
|
||||
{ value: "text", label: "text" },
|
||||
{ value: "longtext", label: "longtext" }
|
||||
]);
|
||||
|
||||
const checkedNode = computed(() => {
|
||||
return designer.state.checkedNode;
|
||||
});
|
||||
|
||||
// const domID = computed(() => {
|
||||
// return designer.state.checkedNode?.id;
|
||||
// });
|
||||
|
||||
const dataSource = computed(() => {
|
||||
return designer.state.dataSource;
|
||||
});
|
||||
|
||||
let edit = computed(() => {
|
||||
return designer.state.status;
|
||||
});
|
||||
|
||||
/**
|
||||
* 监听数据变化
|
||||
* */
|
||||
watch(
|
||||
() => designer.state.checkedNode?.id,
|
||||
() => {
|
||||
setdataSourceData();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
/**
|
||||
* 数据初始化
|
||||
* 先赋值默认值,然后判断该组件是否已经有自己的字段,有的话就赋值
|
||||
* */
|
||||
setdataSourceData();
|
||||
});
|
||||
|
||||
/**
|
||||
* 数据绑定
|
||||
* */
|
||||
const setdataSourceData = () => {
|
||||
templateID.value = designer.state.checkedNode?.id;
|
||||
templateType.value = designer.state.checkedNode?.type;
|
||||
|
||||
// 判断当前组件是否已经赋值
|
||||
let dataArr = dataSource.value;
|
||||
// 判断当前的设计器的dataSource数据,看看当前组件是否已经存在
|
||||
let flag = dataArr.some(item => {
|
||||
return item.id === templateID.value;
|
||||
});
|
||||
|
||||
if (flag) {
|
||||
dataArr.forEach(element => {
|
||||
if (element.id === templateID.value) {
|
||||
for (const key in dataSQL) {
|
||||
dataSQL[key] = element[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 重置数据
|
||||
for (const key in dataSQL) {
|
||||
if (key === "isNull") {
|
||||
dataSQL[key] = true;
|
||||
} else if (key === "isKey") {
|
||||
dataSQL[key] = false;
|
||||
} else if (key === "length") {
|
||||
dataSQL[key] = 50;
|
||||
} else {
|
||||
dataSQL[key] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataSQL.name = designer.state.checkedNode?.field;
|
||||
dataSQL.info = designer.state.checkedNode?.label;
|
||||
};
|
||||
|
||||
/**
|
||||
* 改变“数据库”模块的字段名和注释的时候,同时修改“属性”模块中的字段名和文字
|
||||
* */
|
||||
const changeName = (value, field) => {
|
||||
_setAttributeValue(value, field);
|
||||
_databaseData();
|
||||
};
|
||||
|
||||
const _setAttributeValue = (value: any, field: string, editData = checkedNode.value) => {
|
||||
setAttributeValue(value, field, editData!);
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存当前组件的字段属性到整个设计器的字段属性
|
||||
* */
|
||||
const _databaseData = () => {
|
||||
setdataSource(dataSQL, templateID.value, dataSource.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 深拷贝
|
||||
* 参数:obj
|
||||
* */
|
||||
// function deepCopy(obj) {
|
||||
// if (typeof obj !== "object" || obj === null) {
|
||||
// return obj;
|
||||
// }
|
||||
// let copy = Array.isArray(obj) ? [] : {};
|
||||
// for (const key in obj) {
|
||||
// if (obj.hasOwnProperty(key)) {
|
||||
// copy[key] = deepCopy(obj[key]);
|
||||
// }
|
||||
// }
|
||||
// return copy;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 改变了dataSQL
|
||||
* */
|
||||
const changeSQL = () => {
|
||||
_databaseData();
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.dataView_box {
|
||||
padding: 2px;
|
||||
}
|
||||
.dataView_box p {
|
||||
padding: 0;
|
||||
margin: 17px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.dataView_box .card_box {
|
||||
padding: 5px 15px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="epic-event-view">
|
||||
<div v-if="checkedNode">
|
||||
<EActionEditor
|
||||
:key="checkedNode.id"
|
||||
:event-list="eventList"
|
||||
:model-value="getAttributeValue(`on`, checkedNode!)"
|
||||
@update:model-value="handleSetValue($event, `on`)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Designer, PageSchema } from "../../../../../types/epic-designer";
|
||||
import { pluginManager, revoke, getAttributeValue, setAttributeValue } from "@/components/EpicDesigner/utils";
|
||||
import { inject, computed } from "vue";
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
const designer = inject("designer") as Designer;
|
||||
const EActionEditor = pluginManager.getComponent("EActionEditor");
|
||||
|
||||
const componentConfings = pluginManager.getComponentConfings();
|
||||
const checkedNode = computed(() => {
|
||||
return designer.state.checkedNode;
|
||||
});
|
||||
|
||||
const eventList = computed(() => {
|
||||
const eventList: any = [
|
||||
{
|
||||
title: "生命周期",
|
||||
events: [
|
||||
{
|
||||
type: "vnodeBeforeMount",
|
||||
describe: "beforeMount"
|
||||
},
|
||||
{
|
||||
type: "vnodeMounted",
|
||||
describe: "mounted"
|
||||
},
|
||||
{
|
||||
type: "vnodeBeforeUpdate",
|
||||
describe: "beforeUpdate"
|
||||
},
|
||||
{
|
||||
type: "vnodeUpdated",
|
||||
describe: "updated"
|
||||
},
|
||||
{
|
||||
type: "vnodeBeforeUnmount",
|
||||
describe: "beforeUnmount"
|
||||
},
|
||||
{
|
||||
type: "vnodeUnmounted",
|
||||
describe: "unmounted"
|
||||
},
|
||||
{
|
||||
type: "vnodeErrorCaptured",
|
||||
describe: "errorCaptured"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
const events = componentConfings[designer.state.checkedNode?.type ?? ""]?.config.event ?? [];
|
||||
eventList.unshift({
|
||||
title: "组件事件",
|
||||
events
|
||||
});
|
||||
return eventList;
|
||||
});
|
||||
|
||||
/**
|
||||
* 设置属性值
|
||||
*/
|
||||
function handleSetValue(value: any, field: string) {
|
||||
setAttributeValue(value, field, checkedNode.value!);
|
||||
// 将修改过的组件属性推入撤销操作的栈中
|
||||
revoke.push(pageSchema.schemas, "编辑组件属性");
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
// 属性编辑
|
||||
.epic-attribute-view,
|
||||
.epic-style-view {
|
||||
padding: 16px;
|
||||
.epic-attr-item {
|
||||
display: flex;
|
||||
|
||||
// align-items : center;
|
||||
margin-bottom: 12px;
|
||||
&.vertical {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// &:hover{
|
||||
// background-color: var(--epic-primary-color);
|
||||
// }
|
||||
.epic-attr-label {
|
||||
width: 80px;
|
||||
overflow: hidden;
|
||||
font-size: var(--epic-text-sm);
|
||||
line-height: 32px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.epic-attr-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-designer-main .el-collapse .el-collapse-item__header {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="epic-style-view" :key="checkedNode?.id">
|
||||
<div v-for="item in componentStyles" :key="item.field">
|
||||
<div v-if="isShow(item)" class="epic-attr-item" :class="item.layout">
|
||||
<div class="epic-attr-label" :title="item.label">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div class="epic-attr-input">
|
||||
<ENode
|
||||
:componentSchema="{
|
||||
...item,
|
||||
componentProps: {
|
||||
...item.componentProps,
|
||||
...(item.field === 'componentProps.defaultValue' ? checkedNode?.componentProps : {}),
|
||||
input: false,
|
||||
field: undefined,
|
||||
hidden: false
|
||||
},
|
||||
show: true,
|
||||
noFormItem: true
|
||||
}"
|
||||
:model-value="getAttributeValue(item.field!, item.editData ?? checkedNode!)"
|
||||
@update:model-value="handleSetValue($event, item.field!, item, item.editData)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ENode from "../../../../node/index";
|
||||
import { Designer, ComponentSchema, PageSchema } from "../../../../../types/epic-designer";
|
||||
import { revoke, getAttributeValue, setAttributeValue } from "@/components/EpicDesigner/utils";
|
||||
|
||||
import { inject, computed, nextTick } from "vue";
|
||||
const designer = inject("designer") as Designer;
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
|
||||
const componentStyles: ComponentSchema[] = [
|
||||
{
|
||||
label: "宽度",
|
||||
type: "EInputSize",
|
||||
field: "componentProps.style.width"
|
||||
},
|
||||
{
|
||||
label: "高度",
|
||||
type: "EInputSize",
|
||||
field: "componentProps.style.height"
|
||||
},
|
||||
{
|
||||
label: "内边距",
|
||||
type: "EInputSize",
|
||||
field: "componentProps.style.padding"
|
||||
},
|
||||
{
|
||||
label: "外边距",
|
||||
type: "EInputSize",
|
||||
field: "componentProps.style.margin"
|
||||
},
|
||||
{
|
||||
label: "背景色",
|
||||
type: "color-picker",
|
||||
field: "componentProps.style.backgroundColor",
|
||||
componentProps: {
|
||||
type: "color",
|
||||
style: {
|
||||
// width: '60px'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "字体颜色",
|
||||
type: "color-picker",
|
||||
field: "componentProps.style.color",
|
||||
componentProps: {
|
||||
type: "color",
|
||||
style: {
|
||||
// width: '60px'
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const checkedNode = computed(() => {
|
||||
return designer.state.checkedNode;
|
||||
});
|
||||
|
||||
function isShow(item: ComponentSchema) {
|
||||
// show属性为boolean类型则直接返回
|
||||
if (typeof item.show === "boolean") {
|
||||
return item.show;
|
||||
}
|
||||
return item.show?.({ values: checkedNode.value! }) ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性值
|
||||
*/
|
||||
function handleSetValue(value: any, field: string, item: ComponentSchema, editData = checkedNode.value) {
|
||||
if (typeof item.onChange === "function") {
|
||||
item.onChange({ value, values: editData!, componentStyles });
|
||||
}
|
||||
// 判断是否同步修改属性值
|
||||
if (item.changeSync) {
|
||||
setAttributeValue(value, field, editData!);
|
||||
} else {
|
||||
nextTick(() => {
|
||||
setAttributeValue(value, field, editData!);
|
||||
});
|
||||
}
|
||||
// 将修改过的组件属性推入撤销操作的栈中
|
||||
revoke.push(pageSchema.schemas, "编辑组件属性");
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,48 @@
|
||||
// 组件选择面板
|
||||
.epic-component-view {
|
||||
height: 100%;
|
||||
.epic-componet-item {
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--epic-border-color);
|
||||
border-radius: 4px;
|
||||
.iconfont {
|
||||
margin-right: 5px;
|
||||
}
|
||||
&:hover {
|
||||
color: var(--epic-primary-color);
|
||||
background-color: var(--epic-compoent-hover-color);
|
||||
border-color: var(--epic-primary-color);
|
||||
}
|
||||
}
|
||||
.epic-tabs-box {
|
||||
box-sizing: border-box;
|
||||
width: 62px;
|
||||
height: 100%;
|
||||
padding: 12px 6px;
|
||||
background: var(--epic-compoent-tabs-color);
|
||||
.epic-tab {
|
||||
height: 28px;
|
||||
margin-bottom: 10px;
|
||||
font-size: var(--epic-text-sm);
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
color: var(--epic-text-main);
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
border-radius: 6px;
|
||||
&.checked {
|
||||
color: var(--epic-primary-color);
|
||||
background-color: var(--epic-primary-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-search-box {
|
||||
border-bottom: 1px solid var(--epic-border-color);
|
||||
|
||||
// element 搜素框样式
|
||||
// .el-input__wrapper {
|
||||
// background-color: #f7f7f7;
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="epic-component-view flex flex-col">
|
||||
<!-- 搜素框 start -->
|
||||
<div class="epic-search-box px-10px py-6px">
|
||||
<Input placeholder="搜索组件" v-model="keyword" clearable allowClear v-model:value="keyword">
|
||||
<template #prefix>
|
||||
<EIcon name="icon-chaxun" />
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
<!-- 搜素框 end -->
|
||||
<div class="flex flex-1 overflow-auto">
|
||||
<!-- 分类选项 start -->
|
||||
<div class="epic-tabs-box">
|
||||
<div
|
||||
class="epic-tab cursor-pointer truncate"
|
||||
:class="{ checked: activeItem.title === item.title }"
|
||||
v-for="(item, index) in getSchemaTypeList"
|
||||
:key="index"
|
||||
:title="item.title"
|
||||
@click="handelChecked(item)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类选项 end -->
|
||||
|
||||
<div class="h-full flex-1 overflow-auto py-2 box-border">
|
||||
<draggable
|
||||
v-model="getSourceSchemaList"
|
||||
v-bind="{
|
||||
group: { name: 'edit-draggable', pull: 'clone', put: false },
|
||||
sort: false,
|
||||
animation: 180,
|
||||
ghostClass: 'moving'
|
||||
}"
|
||||
:clone="generateNewSchema"
|
||||
item-key="id"
|
||||
class="grid grid-cols-[auto_auto] px-10px gap-2"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="epic-componet-item flex items-center truncate" @click="handleClick(element)">
|
||||
<EIcon prefix="" :name="pluginManager.getComponentConfingByType(element.type).icon ?? ''" />
|
||||
<div>{{ element.label }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-show="!getSourceSchemaList.length" class="text-center pt-42px text-gray-400">没有查询到的组件</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import draggable from "vuedraggable";
|
||||
import { ref, computed, inject } from "vue";
|
||||
import { generateNewSchema, findSchemaInfoById, pluginManager, revoke } from "@/components/EpicDesigner/utils";
|
||||
import { ComponentSchema, PageSchema, Designer } from "../../../../../types/epic-designer";
|
||||
import EIcon from "../../../../icon";
|
||||
const Input = pluginManager.getComponent("input");
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
const designer = inject("designer") as Designer;
|
||||
const sourceSchema = pluginManager.getComponentSchemaGroups();
|
||||
const keyword = ref("");
|
||||
const allSchema = {
|
||||
title: "全部",
|
||||
list: []
|
||||
};
|
||||
const activeItem = ref(allSchema);
|
||||
|
||||
/**
|
||||
* 计算组件分类列表
|
||||
*/
|
||||
const getSchemaTypeList = computed(() => {
|
||||
return [allSchema, ...sourceSchema.value];
|
||||
});
|
||||
|
||||
/**
|
||||
* 计算当前需要展示的组件列表
|
||||
*/
|
||||
const getSourceSchemaList = computed(() => {
|
||||
let sourceSchemaList: ComponentSchema[] = activeItem.value.list;
|
||||
if (activeItem.value.title === "全部") {
|
||||
const sourceSchemaAllList = sourceSchema.value.map(item => {
|
||||
return item.list;
|
||||
});
|
||||
sourceSchemaList = ([] as ComponentSchema[]).concat(...sourceSchemaAllList);
|
||||
}
|
||||
|
||||
if (keyword.value) {
|
||||
return sourceSchemaList.filter(item => item.label?.includes(keyword.value));
|
||||
}
|
||||
|
||||
return sourceSchemaList;
|
||||
});
|
||||
|
||||
function handelChecked(item: any) {
|
||||
activeItem.value = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击添加节点
|
||||
* @param e
|
||||
*/
|
||||
function handleClick(schema: ComponentSchema) {
|
||||
const data = findSchemaInfoById(pageSchema.schemas, designer.state.checkedNode?.id ?? "root");
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
let { list, schema: checkedSchema, index } = data;
|
||||
|
||||
// 如果选中元素存在children字段,则添加到children中
|
||||
if (checkedSchema.children && !pluginManager.getComponentConfingByType(checkedSchema.type).childImmovable) {
|
||||
list = checkedSchema.children;
|
||||
index = checkedSchema.children.length - 1;
|
||||
}
|
||||
|
||||
const newSchema = generateNewSchema(schema);
|
||||
|
||||
list.splice(index + 1, 0, newSchema);
|
||||
designer.setCheckedNode(newSchema);
|
||||
revoke.push(pageSchema.schemas, "插入组件");
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<draggable
|
||||
v-model="modelSchemas"
|
||||
item-key="id"
|
||||
:component-data="{
|
||||
type: 'transition-group'
|
||||
}"
|
||||
class="epic-draggable-range"
|
||||
v-bind="{
|
||||
animation: 200,
|
||||
group: 'edit-draggable',
|
||||
handle: '.draggable-item',
|
||||
ghostClass: 'moveing'
|
||||
}"
|
||||
@start="handleSelect($event.oldIndex)"
|
||||
@end="handleEnd()"
|
||||
@add="
|
||||
handleSelect($event.newIndex);
|
||||
handleAdd();
|
||||
"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="widget-box" :class="isDraggable(element)" :key="index">
|
||||
<ENodeItem :schema="element" />
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import draggable from "vuedraggable";
|
||||
import { computed, inject } from "vue";
|
||||
import { revoke, pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { ComponentSchema, PageSchema, Designer } from "../../../../../types/epic-designer";
|
||||
import ENodeItem from "./nodeItem.vue";
|
||||
|
||||
const designer = inject("designer") as Designer;
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
defineOptions({
|
||||
name: "EditNodeItem"
|
||||
});
|
||||
const props = defineProps<{
|
||||
schemas: ComponentSchema[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["update:schemas"]);
|
||||
const modelSchemas = computed({
|
||||
get() {
|
||||
// 判断props.schemas是否存在值
|
||||
return props.schemas;
|
||||
},
|
||||
set(e) {
|
||||
emit("update:schemas", e);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 选中点击节点元素
|
||||
* @param index
|
||||
*/
|
||||
function handleSelect(index: number) {
|
||||
designer.setCheckedNode(modelSchemas.value![index]);
|
||||
designer.setDisableHover(true);
|
||||
}
|
||||
|
||||
function handleEnd() {
|
||||
designer.setDisableHover();
|
||||
revoke.push(pageSchema.schemas, "拖拽组件");
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
revoke.push(pageSchema.schemas, "插入组件");
|
||||
}
|
||||
|
||||
function isDraggable(schemas: ComponentSchema) {
|
||||
// 判断当前节点类型是否允许拖拽
|
||||
if (schemas.type === "page" || pluginManager.getComponentConfingByType(schemas.type).immovable) {
|
||||
// 禁止拖拽
|
||||
return "unmover-item";
|
||||
}
|
||||
|
||||
return "draggable-item";
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="h-full flex flex-col relative">
|
||||
<!-- 工具条 start -->
|
||||
<Toolbar />
|
||||
<!-- 工具条 end -->
|
||||
|
||||
<div
|
||||
ref="editScreenContainerRef"
|
||||
class="flex-1 overflow-auto overflow-y-hidden epic-edit-screen-container"
|
||||
:class="{ 'cursor-grab': pressSpace }"
|
||||
:draggable="pressSpace"
|
||||
@wheel="handleZoom"
|
||||
@dragstart="handleElementDragStart"
|
||||
@dragend="handleElementDragEnd"
|
||||
@drag="handleElementDrag"
|
||||
>
|
||||
<div id="canvasContainer" class="flex items-center justify-center" :style="scrollBoxStyle">
|
||||
<div ref="draggableElRef" class="transition-all">
|
||||
<div :class="{ 'pointer-events-none': pressSpace }" :style="canvasBoxStyle">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="sizeBoxRef" class="absolute op-0 pointer-events-none" :style="sizeBoxStyle" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { watchOnce, useElementSize, useResizeObserver } from "@vueuse/core";
|
||||
import type { PageSchema } from "../../../../../types/epic-designer";
|
||||
|
||||
import { useShareStore, useElementDrag, useElementZoom, debounce } from "@/components/EpicDesigner/utils";
|
||||
import { ref, nextTick, inject, watch, shallowRef, unref, onMounted, UnwrapRef } from "vue";
|
||||
import Toolbar from "./toolbar.vue";
|
||||
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
|
||||
const editScreenContainerRef = ref<HTMLDivElement | null>(null);
|
||||
const draggableElRef = ref<HTMLDivElement | null>(null);
|
||||
const sizeBoxRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
const { pressSpace, disabledZoom } = useShareStore();
|
||||
const { handleElementDragStart, handleElementDrag, handleElementDragEnd } = useElementDrag(editScreenContainerRef);
|
||||
const { width, height } = useElementSize(editScreenContainerRef);
|
||||
const { canvasScale, handleZoom } = useElementZoom(draggableElRef);
|
||||
|
||||
let contentRectWidth = 0;
|
||||
let contentRectHeight = 0;
|
||||
|
||||
const scrollBoxStyle = ref<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
}>({});
|
||||
const canvasBoxStyle = ref<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
transform?: string;
|
||||
}>({});
|
||||
const sizeBoxStyle = ref<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
}>({});
|
||||
|
||||
const getCanvasAttribute = shallowRef<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>({ width: 0, height: 0 });
|
||||
watch(() => ({ width: pageSchema.canvas?.width, height: pageSchema.canvas?.height }), updateSizeBoxStyle);
|
||||
watch(sizeBoxStyle, () => {
|
||||
nextTick(updateCanvasAttribute);
|
||||
});
|
||||
onMounted(() => {
|
||||
updateSizeBoxStyle({ width: pageSchema.canvas?.width, height: pageSchema.canvas?.height });
|
||||
});
|
||||
function updateCanvasAttribute() {
|
||||
const sizeBox = unref(sizeBoxRef);
|
||||
if (!sizeBox) return;
|
||||
getCanvasAttribute.value = {
|
||||
width: sizeBox.clientWidth,
|
||||
height: sizeBox.clientHeight
|
||||
};
|
||||
}
|
||||
function updateSizeBoxStyle({ width, height }: UnwrapRef<typeof sizeBoxStyle>) {
|
||||
sizeBoxStyle.value = {
|
||||
width: width ?? "0",
|
||||
height: height ?? "0"
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化页面样式
|
||||
watchOnce(width, updateScrollBoxStyle);
|
||||
// 动态适配设计区域宽度
|
||||
watch(getCanvasAttribute, updateScrollBoxStyle);
|
||||
|
||||
function updateScrollBoxStyle() {
|
||||
if (disabledZoom.value) {
|
||||
// 禁用画布缩放功能
|
||||
canvasBoxStyle.value = {
|
||||
width: contentRectWidth + "px",
|
||||
height: contentRectHeight + "px",
|
||||
transform: "translate(0px, 20px)"
|
||||
};
|
||||
return;
|
||||
}
|
||||
let canvasWidth = getCanvasAttribute.value.width || contentRectWidth;
|
||||
let canvasHeight = getCanvasAttribute.value.height || contentRectHeight;
|
||||
scrollBoxStyle.value = { width: width.value + canvasWidth + "px", height: height.value + canvasHeight + "px" };
|
||||
canvasBoxStyle.value = { width: canvasWidth + "px", height: canvasHeight + "px" };
|
||||
|
||||
// 调整位置
|
||||
setScroll();
|
||||
}
|
||||
|
||||
function setScroll() {
|
||||
nextTick(() => {
|
||||
let canvasWidth = getCanvasAttribute.value.width || contentRectWidth;
|
||||
let canvasHeight = getCanvasAttribute.value.height || contentRectHeight;
|
||||
|
||||
const scrollTop = canvasHeight / 2;
|
||||
const scrollLeft = canvasWidth / 2;
|
||||
editScreenContainerRef.value!.scrollTo(scrollLeft, scrollTop);
|
||||
});
|
||||
}
|
||||
|
||||
// 使用 ResizeObserver 监听editScreenContainerRef编辑屏幕容器尺寸变化
|
||||
useResizeObserver(editScreenContainerRef, ([{ contentRect }]) => {
|
||||
// 计算编辑屏幕容器的宽度和高度,减去固定的边距值
|
||||
contentRectWidth = contentRect.width - 60;
|
||||
contentRectHeight = contentRect.height - 80;
|
||||
|
||||
// 如果画布缩放未被禁用
|
||||
if (!disabledZoom.value) {
|
||||
// 如果画布的宽度为0,即没有内容
|
||||
if (getCanvasAttribute.value.width === 0) {
|
||||
// 设置画布缩放为默认值1
|
||||
canvasScale.value = 1;
|
||||
} else {
|
||||
// 计算画布应该缩放的比例,使其适应容器的宽度
|
||||
const scale = (contentRectWidth - 20) / getCanvasAttribute.value.width;
|
||||
|
||||
// 如果计算的缩放比例小于1.2,则更新画布缩放
|
||||
canvasScale.value = scale < 1.2 ? scale : canvasScale.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新滚动区域的样式,根据新的容器尺寸和画布缩放
|
||||
|
||||
debounce(updateScrollBoxStyle, 50)();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,108 @@
|
||||
// 编辑画布
|
||||
.epic-edit-canvas {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background-image: linear-gradient(var(--epic-designer-color) 14px, transparent 0),
|
||||
linear-gradient(90deg, transparent 14px, var(--epic-designer-dot-color) 0);
|
||||
background-size: 15px 15px;
|
||||
.epic-edit-range {
|
||||
overflow-y: auto;
|
||||
cursor: pointer;
|
||||
background-color: var(--epic-edit-range-color);
|
||||
box-shadow: rgb(0 0 0 / 10%) 0 20px 25px -5px, rgb(0 0 0 / 4%) 0 10px 10px -5px;
|
||||
.epic-draggable-range {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 60px;
|
||||
border: 1px var(--epic-border-color) dashed;
|
||||
}
|
||||
> div > .epic-draggable-range {
|
||||
border: transparent;
|
||||
|
||||
// 屏蔽antd element-plus label点击事件
|
||||
.ant-form-item-label > label,
|
||||
.el-form-item > .el-form-item__label {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.root-node > .form-main > form > .epic-draggable-range {
|
||||
border: transparent;
|
||||
}
|
||||
}
|
||||
.epic-hover-widget {
|
||||
box-sizing: border-box;
|
||||
border: 1px var(--epic-primary-color) dashed;
|
||||
}
|
||||
.epic-checked-widget {
|
||||
background: var(--epic-widget-shade-color);
|
||||
border: 1px var(--epic-primary-color) dashed;
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
content: "";
|
||||
background: var(--epic-primary-color);
|
||||
}
|
||||
&.top,
|
||||
&.center {
|
||||
&::before {
|
||||
top: -5px;
|
||||
}
|
||||
}
|
||||
&.bottom {
|
||||
&::before {
|
||||
bottom: -5px;
|
||||
}
|
||||
}
|
||||
.epic-widget-action-box {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
padding: 0 4px;
|
||||
background: var(--epic-primary-color);
|
||||
border-radius: 4px 4px 0 0;
|
||||
.epic-widget-action-item {
|
||||
height: 30px;
|
||||
padding: 0 4px;
|
||||
line-height: 25px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.form-main {
|
||||
padding: 6px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏设计区域滚动条
|
||||
.epic-edit-screen-container {
|
||||
&::-webkit-scrollbar {
|
||||
// display: none;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
}
|
||||
.epic-edit-toolbar {
|
||||
box-sizing: border-box;
|
||||
height: 40px;
|
||||
background: var(--epic-view-color);
|
||||
border-bottom: 1px solid var(--epic-border-color);
|
||||
.epic-action-item.disabled {
|
||||
color: var(--epic-text-disabled);
|
||||
cursor: no-drop;
|
||||
}
|
||||
.epic-device-item.checked {
|
||||
color: var(--epic-primary-color);
|
||||
}
|
||||
.epic-device-item:hover,
|
||||
.epic-action-item:hover {
|
||||
background: var(--epic-action-hover-color);
|
||||
}
|
||||
}
|
||||
.widget-box {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<section class="epic-edit-canvas">
|
||||
<KEditScreenContainer>
|
||||
<div ref="epicEditRangeRef" class="epic-edit-range rounded-md overflow-auto relative" :style="getEditRangestyle">
|
||||
<ENodeItem :schema="rootSchema" />
|
||||
<EPreviewWidgets ref="ePreviewWidgetsRef" />
|
||||
</div>
|
||||
</KEditScreenContainer>
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { PageSchema } from "../../../../../types/epic-designer";
|
||||
import EPreviewWidgets from "./previewWidgets.vue";
|
||||
import ENodeItem from "./nodeItem.vue";
|
||||
import KEditScreenContainer from "./editScreenContainer.vue";
|
||||
import { ref, inject, computed, onMounted } from "vue";
|
||||
|
||||
const epicEditRangeRef = ref<HTMLDivElement | null>(null);
|
||||
const ePreviewWidgetsRef = ref<typeof EPreviewWidgets | null>(null);
|
||||
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
const rootSchema = computed(() => {
|
||||
return pageSchema.schemas[0];
|
||||
});
|
||||
|
||||
const getEditRangestyle = computed(() => {
|
||||
return {
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
};
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
ePreviewWidgetsRef.value?.handleInit(epicEditRangeRef.value);
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<ENode ref="nodeRef" :componentSchema="props.schema">
|
||||
<!-- childImmovable不可拖拽设计 start -->
|
||||
<template v-if="pluginManager.getComponentConfingByType(props.schema.type)?.childImmovable" #edit-node>
|
||||
<ENodeItem v-for="item in props.schema.children" :key="item.id" :schema="item" />
|
||||
</template>
|
||||
<!-- childImmovable不可拖拽设计 end -->
|
||||
|
||||
<template v-else #edit-node>
|
||||
<!-- 使用本地变量而非直接绑定props -->
|
||||
<KEditNodeItem v-if="localChildren" :schemas="localChildren" @update:schemas="handleChildrenUpdate" />
|
||||
</template>
|
||||
</ENode>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ENode from "../../../../node";
|
||||
import { ComponentSchema, Designer } from "../../../../../types/epic-designer";
|
||||
import KEditNodeItem from "./editNodeItem.vue";
|
||||
import { useAttrs, provide, watch, ref, onUnmounted, computed, inject } from "vue";
|
||||
import { pluginManager, type PageManager } from "@/components/EpicDesigner/utils";
|
||||
|
||||
const attrs = useAttrs();
|
||||
const designer = inject("designer") as Designer;
|
||||
const pageManager = inject("pageManager", {}) as PageManager;
|
||||
|
||||
const nodeRef = ref<HTMLBaseElement | null>(null);
|
||||
|
||||
provide("nodeAttrs", attrs);
|
||||
defineOptions({
|
||||
name: "ENodeItem"
|
||||
});
|
||||
const props = defineProps<{
|
||||
schema: ComponentSchema;
|
||||
name?: string;
|
||||
}>();
|
||||
|
||||
// 定义事件用于通知父组件更新
|
||||
const emit = defineEmits<{
|
||||
(e: "update:schema", value: ComponentSchema): void;
|
||||
}>();
|
||||
|
||||
// 创建本地副本存储children
|
||||
const localChildren = ref<ComponentSchema[]>([]);
|
||||
|
||||
// 初始化本地副本
|
||||
watch(
|
||||
() => props.schema.children,
|
||||
newChildren => {
|
||||
localChildren.value = newChildren ? [...newChildren] : [];
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
// 处理子组件更新
|
||||
const handleChildrenUpdate = (newChildren: ComponentSchema[]) => {
|
||||
localChildren.value = [...newChildren];
|
||||
// 通知父组件更新整个schema
|
||||
emit("update:schema", {
|
||||
...props.schema,
|
||||
children: newChildren
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前组件dom元素
|
||||
*/
|
||||
const getComponentElement = computed<HTMLBaseElement | null>(() => {
|
||||
const componentInstances = pageManager.componentInstances.value;
|
||||
const id = props.schema?.id;
|
||||
const componentConfing = pluginManager.getComponentConfingByType(props.schema?.type) ?? null;
|
||||
if (!id || !componentInstances?.[id]) {
|
||||
return null;
|
||||
}
|
||||
if (componentConfing?.defaultSchema.input && props.schema?.noFormItem !== true) {
|
||||
return componentInstances[id + "formItem"]?.$el;
|
||||
}
|
||||
const componentInstance = componentInstances[id];
|
||||
if (componentInstance?.$el?.nodeName === "#text") {
|
||||
return null;
|
||||
}
|
||||
return componentInstance?.$el;
|
||||
});
|
||||
|
||||
// 监听选中dom元素变化
|
||||
watch(
|
||||
() => getComponentElement.value,
|
||||
componentElement => {
|
||||
componentElement?.addEventListener("click", setCheckedNode, false);
|
||||
componentElement?.addEventListener("mouseover", setHoverNode, false);
|
||||
componentElement?.addEventListener("mouseout", clearHoverNode, false);
|
||||
}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
getComponentElement.value?.removeEventListener("click", setCheckedNode, false);
|
||||
getComponentElement.value?.removeEventListener("mouseover", setHoverNode, false);
|
||||
getComponentElement.value?.removeEventListener("mouseout", clearHoverNode, false);
|
||||
});
|
||||
|
||||
function setCheckedNode(event: Event) {
|
||||
event.stopPropagation();
|
||||
designer.setCheckedNode(props.schema);
|
||||
}
|
||||
|
||||
function setHoverNode(event: Event) {
|
||||
if (props.schema.type === "page") return;
|
||||
event.stopPropagation();
|
||||
designer.setHoverNode(props.schema);
|
||||
}
|
||||
|
||||
function clearHoverNode(event: Event) {
|
||||
event.stopPropagation();
|
||||
designer.setHoverNode(null);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<Modal
|
||||
v-model="visible"
|
||||
title="查看数据"
|
||||
class="w-900px"
|
||||
width="900px"
|
||||
@close="handleClose"
|
||||
@ok="handleExportData"
|
||||
okText="导出数据"
|
||||
>
|
||||
<div class="min-w-750px rounded h-full">
|
||||
<MonacoEditor
|
||||
ref="monacoEditorRef"
|
||||
class="editor h-full"
|
||||
autoToggleTheme
|
||||
:config="MonacoEditorConfig"
|
||||
language="json"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { ref, inject } from "vue";
|
||||
import { PageSchema } from "../../../../../types/epic-designer";
|
||||
|
||||
const Modal = pluginManager.getComponent("modal");
|
||||
const MonacoEditor = pluginManager.getComponent("monacoEditor");
|
||||
const MonacoEditorConfig = {
|
||||
theme: "vs-light",
|
||||
selectOnLineNumbers: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
readOnly: true
|
||||
};
|
||||
const monacoEditorRef = ref<any>(null);
|
||||
const visible = ref(false);
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
function handleClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 打开预览页面
|
||||
function handleOpen() {
|
||||
visible.value = true;
|
||||
if (monacoEditorRef.value) {
|
||||
monacoEditorRef.value.setValue(JSON.stringify(pageSchema, null, 2));
|
||||
} else {
|
||||
// 编辑器组件未加载,延时重新调用函数
|
||||
setTimeout(() => {
|
||||
handleOpen();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出数据
|
||||
*/
|
||||
function handleExportData(fileName = `epic-data.json`) {
|
||||
let content = "data:text/json;charset=utf-8,";
|
||||
content += JSON.stringify(pageSchema, null, 2);
|
||||
let encodedUri = encodeURI(content);
|
||||
let actions = document.createElement("a");
|
||||
actions.setAttribute("href", encodedUri);
|
||||
actions.setAttribute("download", fileName);
|
||||
actions.click();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleOpen
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<!-- 选中高亮 start -->
|
||||
<div
|
||||
v-show="showSelector && designer.state.checkedNode?.id !== 'root'"
|
||||
ref="selectorRef"
|
||||
class="epic-checked-widget absolute pointer-events-none z-20"
|
||||
:class="selectorPosition + ' ' + (selectorTransition ? 'transition-all' : '')"
|
||||
>
|
||||
<div class="epic-widget-action-box" ref="actionBoxRef">
|
||||
<div class="epic-widget-action-item whitespace-nowrap">
|
||||
<!-- {{ designer.state.checkedNode?.type }} -->
|
||||
{{ pluginManager.getComponentConfingByType(designer.state.checkedNode?.type ?? "")?.defaultSchema.label }}
|
||||
</div>
|
||||
<!-- 操作按钮 start -->
|
||||
<div class="flex" v-if="isRemovableAndDraggable">
|
||||
<div title="复制" class="epic-widget-action-item pointer-events-auto" @click="handleCopy">
|
||||
<EIcon name="icon-fuzhi3" />
|
||||
</div>
|
||||
<div title="删除" class="epic-widget-action-item pointer-events-auto" @click="handleDelete">
|
||||
<EIcon name="icon-shanchu1" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 操作按钮 end -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 选中高亮 end -->
|
||||
<!-- 悬停效果 start -->
|
||||
<div
|
||||
v-show="showHover && designer.state.checkedNode?.id !== designer.state.hoverNode?.id"
|
||||
ref="hoverWidgetRef"
|
||||
class="epic-hover-widget absolute transition-all pointer-events-none z-998"
|
||||
/>
|
||||
<!-- 悬停效果 end -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DesignerProps } from "../../types";
|
||||
import { PageSchema, Designer } from "../../../../../types/epic-designer";
|
||||
import { inject, computed, ref, watch, type Ref } from "vue";
|
||||
import {
|
||||
pluginManager,
|
||||
generateNewSchema,
|
||||
revoke,
|
||||
findSchemaInfoById,
|
||||
useShareStore,
|
||||
useTimedQuery,
|
||||
type PageManager
|
||||
} from "@/components/EpicDesigner/utils";
|
||||
import { useResizeObserver } from "@vueuse/core";
|
||||
import EIcon from "../../../../icon";
|
||||
|
||||
const pageManager = inject("pageManager", {}) as PageManager;
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
const designer = inject("designer") as Designer;
|
||||
const designerProps = inject("designerProps") as Ref<DesignerProps>;
|
||||
|
||||
const selectorRef = ref<HTMLDivElement | null>(null);
|
||||
const hoverWidgetRef = ref<HTMLDivElement | null>(null);
|
||||
const actionBoxRef = ref<any>(null);
|
||||
|
||||
const showSelector = ref(false);
|
||||
const showHover = ref(false);
|
||||
const selectorTransition = ref(true);
|
||||
const selectorPosition = ref<"top" | "center" | "bottom">("top");
|
||||
|
||||
const { canvasScale, disabledZoom } = useShareStore();
|
||||
let kEditRange: HTMLDivElement | null = null;
|
||||
|
||||
/**
|
||||
* 判断组件是否可移动和可拖拽删除
|
||||
*/
|
||||
const isRemovableAndDraggable = computed(() => {
|
||||
const schemas = designer.state.checkedNode;
|
||||
// 没有id不可编辑
|
||||
if (!schemas?.id) return false;
|
||||
// 判断当前节点类型是否允许拖拽删除
|
||||
if (designerProps.value.lockDefaultSchemaEdit && pageManager.defaultComponentIds.value.includes(schemas.id)) {
|
||||
// 禁止拖拽删除
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取选中组件dom元素
|
||||
*/
|
||||
const getSelectComponentElement = computed<HTMLBaseElement | null>(() => {
|
||||
const componentInstances = pageManager.componentInstances.value;
|
||||
const checkedNode = designer.state.checkedNode;
|
||||
const id = checkedNode?.id;
|
||||
|
||||
// 组件隐藏状态:提前判断 checkedNode 是否存在,避免可选链后接非空断言
|
||||
if (checkedNode?.componentProps?.hidden) {
|
||||
return null;
|
||||
}
|
||||
// 修复错误1:移除 `designer.state.checkedNode?.type!` 的非空断言,用可选链+默认值处理
|
||||
const componentConfing = pluginManager.getComponentConfingByType(checkedNode?.type ?? "") ?? null;
|
||||
if (!id || !componentInstances?.[id]) {
|
||||
return null;
|
||||
}
|
||||
// 修复错误:checkedNode 已提前判断,直接用 `.` 访问 noFormItem
|
||||
if (componentConfing?.defaultSchema.input && !checkedNode.noFormItem) {
|
||||
return componentInstances[id + "formItem"]?.$el;
|
||||
}
|
||||
const componentInstance = componentInstances[id];
|
||||
// 补充安全判断:避免访问不存在的 $el 或 getBoundingClientRect
|
||||
if (
|
||||
!componentInstance ||
|
||||
!componentInstance.$el ||
|
||||
componentInstance.$el.nodeName === "#text" ||
|
||||
!componentInstance.$el.getBoundingClientRect
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return componentInstance.$el;
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取悬停组件dom元素
|
||||
*/
|
||||
const getHoverComponentElement = computed<HTMLBaseElement | null>(() => {
|
||||
const componentInstances = pageManager.componentInstances.value;
|
||||
const hoverNode = designer.state.hoverNode;
|
||||
const id = hoverNode?.id;
|
||||
|
||||
// 修复错误2:移除 `designer.state.hoverNode?.type!` 的非空断言,用可选链+默认值处理
|
||||
const componentConfing = pluginManager.getComponentConfingByType(hoverNode?.type ?? "") ?? null;
|
||||
if (!id || !componentInstances?.[id]) {
|
||||
return null;
|
||||
}
|
||||
// 修复错误:hoverNode 已提前判断,直接用 `.` 访问 noFormItem
|
||||
if (componentConfing?.defaultSchema.input && !hoverNode.noFormItem) {
|
||||
return componentInstances[id + "formItem"]?.$el;
|
||||
}
|
||||
const componentInstance = componentInstances[id];
|
||||
if (!componentInstance || componentInstance.$el.nodeName === "#text") {
|
||||
return null;
|
||||
}
|
||||
return componentInstance.$el;
|
||||
});
|
||||
|
||||
const { mutationObserver, observerConfig: DocumentObserverConfig } = initObserve(setSeletorStyle);
|
||||
const { startTimedQuery, stopTimedQuery } = useTimedQuery(setSeletorStyle);
|
||||
|
||||
// 监听选中dom元素变化
|
||||
watch(
|
||||
() => getSelectComponentElement.value,
|
||||
selectComponentElement => {
|
||||
if (selectComponentElement) {
|
||||
showSelector.value = true;
|
||||
// 监听dom元素及子元素的变化
|
||||
mutationObserver.observe(selectComponentElement, DocumentObserverConfig);
|
||||
|
||||
const parentNode = selectComponentElement.parentNode as HTMLBaseElement;
|
||||
if (parentNode) {
|
||||
parentNode.ondragstart = () => {
|
||||
selectorTransition.value = false;
|
||||
startTimedQuery();
|
||||
};
|
||||
parentNode.ondragend = () => {
|
||||
selectorTransition.value = true;
|
||||
stopTimedQuery();
|
||||
};
|
||||
}
|
||||
setSeletorStyle();
|
||||
} else {
|
||||
showSelector.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { mutationObserver: hoverMutationObserver, observerConfig: hoverObserverConfig } = initObserve(setHoverStyle);
|
||||
|
||||
// 监听悬停dom元素变化
|
||||
watch(
|
||||
() => getHoverComponentElement.value,
|
||||
hoverComponentElement => {
|
||||
if (hoverComponentElement) {
|
||||
// 监听dom元素及子元素的变化
|
||||
hoverMutationObserver.observe(hoverComponentElement, hoverObserverConfig);
|
||||
setHoverStyle();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 添加悬停节点监听,当悬停节点消失超过300ms,则隐藏悬停部件
|
||||
let hideTimer: NodeJS.Timeout | number = 0;
|
||||
watch(
|
||||
() => designer.state.hoverNode?.id,
|
||||
e => {
|
||||
if (e) {
|
||||
showHover.value = true;
|
||||
clearTimeout(hideTimer);
|
||||
return;
|
||||
}
|
||||
hideTimer = setTimeout(() => {
|
||||
showHover.value = false;
|
||||
}, 300);
|
||||
}
|
||||
);
|
||||
|
||||
let oldScrollTop = 0;
|
||||
let oldScrollLeft = 0;
|
||||
/**
|
||||
* 设置选择部件 样式 定位 宽高
|
||||
*/
|
||||
function setSeletorStyle() {
|
||||
const element = getSelectComponentElement.value;
|
||||
if (!element || !kEditRange) return;
|
||||
|
||||
const { top: offsetY, left: offsetX } = kEditRange.getBoundingClientRect();
|
||||
const { top, left, width, height } = element.getBoundingClientRect();
|
||||
const scale = !disabledZoom.value ? canvasScale.value : 1;
|
||||
|
||||
// 计算选择器部件位置
|
||||
const selectorTop = top - offsetY + (kEditRange.scrollTop ?? 0) * scale;
|
||||
const selectorLeft = left - offsetX + (kEditRange.scrollLeft ?? 0) * scale;
|
||||
const selectorRefHeight = height / scale;
|
||||
|
||||
if (selectorRef.value) {
|
||||
selectorRef.value.style.width = `${width / scale}px`;
|
||||
selectorRef.value.style.height = `${selectorRefHeight}px`;
|
||||
selectorRef.value.style.top = `${selectorTop / scale}px`;
|
||||
selectorRef.value.style.left = `${selectorLeft / scale}px`;
|
||||
scrollIntoView(selectorTop, selectorLeft);
|
||||
}
|
||||
|
||||
// 调整操作调位置 start
|
||||
if (!actionBoxRef.value) return;
|
||||
|
||||
// 判断actionBoxRef位置是否应该置于底部 距离顶部45px 高度100px
|
||||
if (selectorTop < 45 && selectorRefHeight < 100) {
|
||||
actionBoxRef.value.style.top = "";
|
||||
actionBoxRef.value.style.bottom = "-30px";
|
||||
actionBoxRef.value.style["border-radius"] = "0px 0px 4px 4px";
|
||||
selectorPosition.value = "bottom";
|
||||
} else if (selectorTop < 45) {
|
||||
// 置于中间
|
||||
actionBoxRef.value.style.top = "0px";
|
||||
actionBoxRef.value.style["border-radius"] = "0px 0px 4px 0";
|
||||
selectorPosition.value = "center";
|
||||
} else {
|
||||
// 置于顶部
|
||||
actionBoxRef.value.style.top = "-30px";
|
||||
actionBoxRef.value.style["border-radius"] = "4px 4px 0px 0px";
|
||||
selectorPosition.value = "top";
|
||||
}
|
||||
// 调整操作调位置 end
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动进入可视区
|
||||
*/
|
||||
function scrollIntoView(selectorTop: number, selectorLeft: number) {
|
||||
const element = getSelectComponentElement.value;
|
||||
if (!kEditRange || !element) return;
|
||||
|
||||
const rect2 = kEditRange.getBoundingClientRect();
|
||||
const { width } = element.getBoundingClientRect();
|
||||
const scale = !disabledZoom.value ? canvasScale.value : 1;
|
||||
|
||||
// 计算滚动位置
|
||||
const newScrollTop = selectorTop / scale - rect2.top;
|
||||
let newScrollLeft = selectorLeft / scale - rect2.left + width / scale;
|
||||
newScrollLeft < rect2.width && (newScrollLeft = 0);
|
||||
|
||||
const yMin = kEditRange.scrollTop - rect2.height / 3 + 60;
|
||||
const yMax = kEditRange.scrollTop + (rect2.height / 3) * 2;
|
||||
const xMin = kEditRange.scrollLeft - rect2.width + 200;
|
||||
const xMax = kEditRange.scrollLeft + rect2.width - 200;
|
||||
|
||||
// 避免频繁滚动
|
||||
if (Math.abs(newScrollTop - oldScrollTop) < 10 && Math.abs(newScrollLeft - oldScrollLeft) < 10) return;
|
||||
oldScrollTop = newScrollTop;
|
||||
oldScrollLeft = newScrollLeft;
|
||||
|
||||
if (newScrollTop > yMin && newScrollTop < yMax && newScrollLeft > xMin && newScrollLeft < xMax) return;
|
||||
kEditRange.scrollTop = newScrollTop;
|
||||
kEditRange.scrollLeft = newScrollLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置悬停部件 样式 定位 宽高
|
||||
*/
|
||||
function setHoverStyle() {
|
||||
const element = getHoverComponentElement.value;
|
||||
if (!element || !kEditRange) return;
|
||||
|
||||
const { top: offsetY, left: offsetX } = kEditRange.getBoundingClientRect();
|
||||
// 修复:用可选链处理 getBoundingClientRect 可能不存在的情况
|
||||
const elementRect = element.getBoundingClientRect?.() ??
|
||||
element.nextElementSibling?.getBoundingClientRect?.() ?? { top: 0, left: 0, width: 0, height: 0 };
|
||||
const { top, left, width, height } = elementRect;
|
||||
const scale = !disabledZoom.value ? canvasScale.value : 1;
|
||||
|
||||
// 计算悬停部件位置
|
||||
const hoverTop = top - offsetY + (kEditRange.scrollTop ?? 0) * scale;
|
||||
const hoverLeft = left - offsetX + (kEditRange.scrollLeft ?? 0) * scale;
|
||||
|
||||
if (hoverWidgetRef.value) {
|
||||
hoverWidgetRef.value.style.width = `${width / scale}px`;
|
||||
hoverWidgetRef.value.style.height = `${height / scale}px`;
|
||||
hoverWidgetRef.value.style.top = `${hoverTop / scale}px`;
|
||||
hoverWidgetRef.value.style.left = `${hoverLeft / scale}px`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化观察者对象
|
||||
*/
|
||||
function initObserve(func: () => void) {
|
||||
const MutationObserver = window.MutationObserver;
|
||||
const observerConfig = {
|
||||
childList: true,
|
||||
attributes: true,
|
||||
subtree: true
|
||||
};
|
||||
const mutationObserver = new MutationObserver(func);
|
||||
return { mutationObserver, observerConfig };
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制选中节点元素
|
||||
*/
|
||||
function handleCopy() {
|
||||
const checkedNodeId = designer.state.checkedNode?.id ?? "root";
|
||||
const data = findSchemaInfoById(pageSchema.schemas, checkedNodeId);
|
||||
if (!data) return false;
|
||||
|
||||
// 修复错误3:移除未使用的 `any` 类型,显式指定类型为 ComponentSchema
|
||||
const { list, schema, index } = data;
|
||||
const node = generateNewSchema(schema);
|
||||
list.splice(index + 1, 0, node);
|
||||
designer.setCheckedNode(node);
|
||||
revoke.push(pageSchema.schemas, "复制组件");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除元素
|
||||
*/
|
||||
function handleDelete() {
|
||||
const checkedNodeId = designer.state.checkedNode?.id ?? "root";
|
||||
const data = findSchemaInfoById(pageSchema.schemas, checkedNodeId);
|
||||
if (!data) return false;
|
||||
|
||||
// 修复错误3:移除未使用的 `any` 类型,显式指定类型为 ComponentSchema
|
||||
const { list, schema, index } = data;
|
||||
console.log(schema);
|
||||
list.splice(index, 1);
|
||||
|
||||
// 选中下一个/上一个节点
|
||||
const newIndex = index === list.length ? index - 1 : index;
|
||||
designer.setCheckedNode(list[newIndex]);
|
||||
revoke.push(pageSchema.schemas, "删除组件");
|
||||
}
|
||||
|
||||
// 初始化函数,传入一个指向 Epic 编辑范围的引用
|
||||
function handleInit(epicEditRangeRef: HTMLDivElement | null) {
|
||||
kEditRange = epicEditRangeRef;
|
||||
kEditRange?.addEventListener("scroll", setSeletorStyle);
|
||||
|
||||
// 监听选中元素视窗变化
|
||||
useResizeObserver(getSelectComponentElement, setSeletorStyle);
|
||||
// 监听悬停元素视窗变化
|
||||
useResizeObserver(getHoverComponentElement, setHoverStyle);
|
||||
}
|
||||
|
||||
defineExpose({ handleInit });
|
||||
</script>
|
||||
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<!-- 工具条 start -->
|
||||
<div class="epic-edit-toolbar flex items-center justify-between px-4">
|
||||
<div class="flex-1 h-full flex items-center">
|
||||
<div
|
||||
:title="item.title"
|
||||
class="epic-action-item h-90% px-10px flex items-center cursor-pointer"
|
||||
v-for="(item, index) in actionOptions"
|
||||
:class="{ disabled: item.disabled }"
|
||||
:key="index"
|
||||
@click="item.on"
|
||||
>
|
||||
<EIcon :name="item.icon"></EIcon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="file" ref="fileRef" accept=".json,.txt" v-show="false" @change="handleFileSelected" />
|
||||
|
||||
<div class="flex-1 h-full flex items-center justify-center">
|
||||
<div
|
||||
:title="item.title"
|
||||
class="epic-device-item h-90% px-10px flex items-center cursor-pointer"
|
||||
:class="{ checked: item.key === checkedKey }"
|
||||
v-for="item in deviceOptions"
|
||||
:key="item.key"
|
||||
@click="handleSetCanvas(item.key)"
|
||||
>
|
||||
<EIcon :name="item.icon"></EIcon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 h-full flex items-center justify-end">
|
||||
<!-- 缩放操作 start -->
|
||||
<div v-if="!disabledZoom" class="flex items-center ml-12px">
|
||||
<div class="pr-8px w-82px cursor-pointer">
|
||||
<Select
|
||||
v-model:value="canvasScaleComuted"
|
||||
v-model="canvasScaleComuted"
|
||||
:options="canvasScaleOptions"
|
||||
size="small"
|
||||
></Select>
|
||||
</div>
|
||||
<div class="w-90px cursor-pointer">
|
||||
<Slider
|
||||
:min="0.6"
|
||||
:max="1.4"
|
||||
:step="0.01"
|
||||
:tooltip="false"
|
||||
v-model:value="canvasScale"
|
||||
v-model="canvasScale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 缩放操作 end -->
|
||||
</div>
|
||||
</div>
|
||||
<EPreviewJson ref="previewJson" />
|
||||
<!-- 工具条 end -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useShareStore, pluginManager, revoke, deepCompareAndModify, convertKFormData } from "@/components/EpicDesigner/utils";
|
||||
import type { PageSchema, Designer } from "../../../../../types/epic-designer";
|
||||
import { computed, inject, ref } from "vue";
|
||||
import EIcon from "../../../../icon";
|
||||
import EPreviewJson from "./previewJson.vue";
|
||||
|
||||
const Slider = pluginManager.getComponent("slider");
|
||||
const Select = pluginManager.getComponent("select");
|
||||
|
||||
const { canvasScale, disabledZoom } = useShareStore();
|
||||
const checkedKey = ref("pc");
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
const designer = inject("designer") as Designer;
|
||||
const previewJson = ref<InstanceType<typeof EPreviewJson> | null>(null);
|
||||
|
||||
const deviceOptions = [
|
||||
{
|
||||
icon: "icon-a-diannaotoubu",
|
||||
title: "pc",
|
||||
key: "pc"
|
||||
},
|
||||
{
|
||||
icon: "icon-a-pingbantoubu",
|
||||
title: "平板",
|
||||
key: "pad"
|
||||
},
|
||||
{
|
||||
icon: "icon-a-shoujitoubu",
|
||||
title: "手机",
|
||||
key: "mobile"
|
||||
}
|
||||
];
|
||||
|
||||
const recordList = revoke.recordList;
|
||||
const undoList = revoke.undoList;
|
||||
|
||||
const actionOptions = computed(() => {
|
||||
return [
|
||||
{
|
||||
icon: "icon-daima1",
|
||||
title: "查看数据",
|
||||
on: () => handlePreview()
|
||||
},
|
||||
{
|
||||
icon: "icon-shangchuan1",
|
||||
title: "导入数据",
|
||||
on: handleOpenFileSelector
|
||||
},
|
||||
{
|
||||
icon: "icon-shanchu1",
|
||||
title: "清空",
|
||||
on: handleReset
|
||||
},
|
||||
{
|
||||
icon: "icon-chexiao2x",
|
||||
title: "撤销",
|
||||
on: handleUndo,
|
||||
disabled: !recordList.value.length
|
||||
},
|
||||
{
|
||||
icon: "icon-fanhui2x",
|
||||
title: "重做",
|
||||
on: handleRedo,
|
||||
disabled: !undoList.value.length
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
const fileRef = ref<HTMLInputElement | null>(null);
|
||||
const canvasScaleComuted = computed({
|
||||
get() {
|
||||
return `${(canvasScale.value * 100).toFixed(0)}%`;
|
||||
},
|
||||
set(value) {
|
||||
canvasScale.value = Number(value);
|
||||
}
|
||||
});
|
||||
|
||||
const canvasScaleOptions = [
|
||||
{
|
||||
label: "60%",
|
||||
value: "0.6"
|
||||
},
|
||||
{
|
||||
label: "80%",
|
||||
value: "0.8"
|
||||
},
|
||||
{
|
||||
label: "100%",
|
||||
value: "1.0"
|
||||
},
|
||||
{
|
||||
label: "120%",
|
||||
value: "1.2"
|
||||
},
|
||||
{
|
||||
label: "140%",
|
||||
value: "1.4"
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 撤销操作
|
||||
*/
|
||||
function handleUndo() {
|
||||
const componentSchema = revoke.undo();
|
||||
if (!componentSchema) return;
|
||||
deepCompareAndModify(pageSchema.schemas, componentSchema);
|
||||
designer.setCheckedNode(pageSchema.schemas[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重做操作
|
||||
*/
|
||||
function handleRedo() {
|
||||
const componentSchema = revoke.redo();
|
||||
if (!componentSchema) return;
|
||||
deepCompareAndModify(pageSchema.schemas, componentSchema);
|
||||
designer.setCheckedNode(pageSchema.schemas[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置页面数据
|
||||
*/
|
||||
function handleReset() {
|
||||
designer.reset();
|
||||
}
|
||||
|
||||
// 预览数据
|
||||
function handlePreview() {
|
||||
previewJson.value!.handleOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开文件选择器
|
||||
*/
|
||||
function handleOpenFileSelector() {
|
||||
fileRef.value?.click();
|
||||
}
|
||||
|
||||
// 选择文件
|
||||
function handleFileSelected(e: any) {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
e.target.value = null;
|
||||
|
||||
// 通过json文件导入
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onload = res => {
|
||||
handleImporttData(res.target?.result as string);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入数据
|
||||
*/
|
||||
function handleImporttData(content?: string) {
|
||||
// 导入JSON
|
||||
try {
|
||||
let schema = JSON.parse(content ?? "");
|
||||
if (!schema.schemas) {
|
||||
schema = convertKFormData(schema);
|
||||
}
|
||||
|
||||
// 调用 deepCompareAndModify 函数比较 pageSchema 和传入的 schema,进行修改
|
||||
deepCompareAndModify(pageSchema, schema);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置画布宽高
|
||||
* @param type
|
||||
*/
|
||||
function handleSetCanvas(type: string) {
|
||||
designer.handleToggleDeviceMode(type);
|
||||
checkedKey.value = type;
|
||||
const canvasConfigs: any = {
|
||||
pc: {},
|
||||
pad: {
|
||||
width: "800px",
|
||||
mode: type
|
||||
},
|
||||
mobile: {
|
||||
width: "420px",
|
||||
mode: type
|
||||
}
|
||||
};
|
||||
|
||||
pageSchema.canvas = canvasConfigs[type];
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
// 头部
|
||||
.epic-header-container {
|
||||
border: 1px solid var(--epic-border-color);
|
||||
border-bottom: 0;
|
||||
}
|
||||
.epic-header,
|
||||
.epic-header a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 42px;
|
||||
padding: 0 12px;
|
||||
color: var(--epic-text-main);
|
||||
background-color: var(--epic-header-color);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<header class="epic-header relative">
|
||||
<div class="epic-header-item flex-1 items-center flex">
|
||||
<slot name="prefix">
|
||||
<a class="decoration-none items-center flex" href="https://kcz66.gitee.io/epic-designer" target="_blank">
|
||||
<img src="../../../../../static/logo.png" class="w-18px h-18px" alt="" srcset="" />
|
||||
<span class="ml-3">EDesigner</span>
|
||||
</a>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="epic-header-item flex-1 flex justify-center text-12px">
|
||||
<slot name="title">
|
||||
{{ designerProps.title }}
|
||||
</slot>
|
||||
</div>
|
||||
<div class="epic-header-item flex-1 flex justify-end items-center">
|
||||
<slot name="right-prefix"></slot>
|
||||
<slot name="right-action">
|
||||
<div>
|
||||
<Button size="small" @click="handlePreview">
|
||||
<EIcon name="icon-yulan" style="margin-right: 6px" />
|
||||
预览
|
||||
</Button>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<Button size="small" @click="handleSave">
|
||||
<EIcon name="icon-baocun" style="margin-right: 6px" />
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</slot>
|
||||
<slot name="right-suffix"></slot>
|
||||
</div>
|
||||
<EPreview ref="preview" />
|
||||
</header>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, inject, type Ref } from "vue";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import EIcon from "../../../../icon";
|
||||
import EPreview from "./../preview/index.vue";
|
||||
import { DesignerProps } from "../../types";
|
||||
const emits = defineEmits(["save", "reset"]);
|
||||
const Button = pluginManager.getComponent("button");
|
||||
const preview = ref<InstanceType<typeof EPreview> | null>(null);
|
||||
const designerProps = inject("designerProps") as Ref<DesignerProps>;
|
||||
|
||||
/**
|
||||
* 预览
|
||||
*/
|
||||
function handlePreview() {
|
||||
preview.value!.handleOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发保存操作
|
||||
*/
|
||||
function handleSave() {
|
||||
emits("save");
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,5 @@
|
||||
.epic-outline {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="epic-outline">
|
||||
<ETree
|
||||
:options="pageSchema.schemas"
|
||||
draggable
|
||||
:selected-keys="selectedKeys"
|
||||
:hover-key="designer.state.hoverNode?.id"
|
||||
@nodeClick="handleNodeClick"
|
||||
>
|
||||
<template #tree-node="{ schema }">
|
||||
<div
|
||||
class="epic-text-padding"
|
||||
@mouseenter.stop="designer.setHoverNode(schema)"
|
||||
@mouseleave.stop="designer.setHoverNode(null)"
|
||||
>
|
||||
{{ schema.label ?? pluginManager.getComponentConfingByType(schema.type)?.defaultSchema.label }}
|
||||
<span class="epic-node-type-text">
|
||||
{{ schema.type }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</ETree>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ETree from "../../../../tree";
|
||||
import { inject, computed } from "vue";
|
||||
import { PageSchema, Designer } from "../../../../../types/epic-designer";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
|
||||
const designer = inject("designer") as Designer;
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
|
||||
// 计算选中节点值
|
||||
const selectedKeys = computed(() => {
|
||||
const id = designer.state.checkedNode?.id;
|
||||
return id ? [id] : [];
|
||||
});
|
||||
|
||||
// 设置选中节点
|
||||
function handleNodeClick(e: any) {
|
||||
designer.setCheckedNode(e.componentSchema);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<Modal v-model="visible" title="预览" class="w-900px" width="900px" @close="handleClose" @ok="handleOk" okText="输出结果">
|
||||
<div class="min-w-750px rounded">
|
||||
<EBuilder :key="EBuilderKey" ref="kb" :page-schema="pageSchema" />
|
||||
|
||||
<!-- 输出结果 start -->
|
||||
<Modal
|
||||
v-model="dataVisible"
|
||||
title="表单数据"
|
||||
class="w-860px"
|
||||
width="860px"
|
||||
@close="handleCloseData"
|
||||
@ok="handleCloseData"
|
||||
>
|
||||
<div class="h-full rounded">
|
||||
<MonacoEditor ref="monacoEditorRef" autoToggleTheme class="h-full editor" :model-value="formValues" />
|
||||
</div>
|
||||
</Modal>
|
||||
<!-- 输出结果 end -->
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import EBuilder from "../../../../builder";
|
||||
import { pluginManager, getUUID } from "@/components/EpicDesigner/utils";
|
||||
import { ref, inject, nextTick } from "vue";
|
||||
import { PageSchema } from "../../../../../types/epic-designer";
|
||||
const MonacoEditor = pluginManager.getComponent("monacoEditor");
|
||||
const Modal = pluginManager.getComponent("modal");
|
||||
const monacoEditorRef = ref<any>(null);
|
||||
|
||||
const visible = ref(false);
|
||||
const dataVisible = ref(false);
|
||||
const formValues = ref({});
|
||||
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
const kb = ref<any>(null);
|
||||
const EBuilderKey = ref("");
|
||||
|
||||
function handleCloseData() {
|
||||
dataVisible.value = false;
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleOpen() {
|
||||
visible.value = true;
|
||||
// 通过修改key重新渲染组件
|
||||
EBuilderKey.value = getUUID();
|
||||
}
|
||||
|
||||
async function handleOk() {
|
||||
try {
|
||||
const values = await kb.value.validate();
|
||||
console.log("表单结果为:", values);
|
||||
formValues.value = JSON.stringify(values, null, 2);
|
||||
nextTick(() => {
|
||||
monacoEditorRef.value?.setValue(formValues.value);
|
||||
});
|
||||
dataVisible.value = true;
|
||||
} catch (err) {
|
||||
if (typeof err === "string") {
|
||||
alert(err + "\r\n请添加表单组件后再尝试!");
|
||||
}
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleOpen
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="epic-breadcrumb pl-4 py-2 truncate">
|
||||
<span v-for="(item, index) in designer.state.matched" :key="index">
|
||||
<span v-if="index > designer.state.matched.length - 4">
|
||||
<span v-if="designer.state.matched.length > 3 && index === designer.state.matched.length - 3">...</span>
|
||||
<EIcon v-if="index !== 0" class="m-1" name="icon-zhankai" />
|
||||
<span
|
||||
class="node-item cursor-pointer"
|
||||
@click="handleSelect(item)"
|
||||
@mouseenter.stop="designer.setHoverNode(item)"
|
||||
@mouseleave.stop="designer.setHoverNode(null)"
|
||||
>
|
||||
{{ item.label ?? pluginManager.getComponentConfingByType(item.type)?.defaultSchema.label }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { inject } from "vue";
|
||||
import { Designer, ComponentSchema } from "../../../../../types/epic-designer";
|
||||
import EIcon from "../../../../icon";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
const designer = inject("designer") as Designer;
|
||||
/**
|
||||
* 选中点击节点元素
|
||||
* @param schema
|
||||
*/
|
||||
function handleSelect(schema: ComponentSchema) {
|
||||
designer.setCheckedNode(schema);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,68 @@
|
||||
.epic-right-sidebar-container {
|
||||
background-color: var(--epic-view-color);
|
||||
}
|
||||
.epic-right-sidebar-hide-btn {
|
||||
color: var(--epic-text-helper);
|
||||
background-color: var(--epic-view-color);
|
||||
|
||||
// box-shadow: 2px 5px 6px 3px #ffffff36;
|
||||
box-shadow: var(--epic-shadow-color) 0 1px 4px;
|
||||
}
|
||||
.epic-right-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden auto;
|
||||
background-color: var(--epic-view-color);
|
||||
border-right: 1px solid var(--epic-border-color);
|
||||
border-left: 1px solid var(--epic-border-color);
|
||||
transition: width 0.3s;
|
||||
&.hide {
|
||||
width: 0;
|
||||
border: 0;
|
||||
}
|
||||
.epic-breadcrumb {
|
||||
color: var(--epic-text-main);
|
||||
border-bottom: 1px solid var(--epic-border-color);
|
||||
.iconfont {
|
||||
font-size: var(--epic-text-sm);
|
||||
color: var(--epic-text-secondary);
|
||||
}
|
||||
}
|
||||
.epic-actions-container {
|
||||
display: flex;
|
||||
|
||||
// border : 1px solid #f89;
|
||||
.epic-action-item {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--epic-border-color);
|
||||
&::after {
|
||||
left: 50%;
|
||||
width: 0%;
|
||||
content: "";
|
||||
}
|
||||
&.checked {
|
||||
position: relative;
|
||||
color: var(--epic-primary-color);
|
||||
transition: 0.3s all;
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
content: "";
|
||||
background-color: var(--epic-primary-color);
|
||||
transition: 0.3s all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-sidebar-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div v-if="sidebarComponent" class="epic-right-sidebar-container relative">
|
||||
<!-- 折叠按钮 start -->
|
||||
<div
|
||||
class="epic-right-sidebar-hide-btn absolute left--18px top-80px cursor-pointer rounded-full flex justify-center items-center w-28px h-28px z-9"
|
||||
@click="handleHideRight"
|
||||
>
|
||||
<EIcon prefix="" class="iconfont transition-all" :class="{ 'rotate-180': hideRightMain }" name="epic-icon-zhankai" />
|
||||
</div>
|
||||
<div class="w-10px"></div>
|
||||
<!-- 折叠按钮 end -->
|
||||
|
||||
<div class="epic-right-sidebar w-280px" :class="{ hide: hideRightMain }">
|
||||
<div class="w-280px">
|
||||
<EBreadcrumb />
|
||||
<ul class="epic-actions-container">
|
||||
<li
|
||||
v-for="(item, index) in rightSidebars"
|
||||
:key="index"
|
||||
class="epic-action-item"
|
||||
:title="item.title"
|
||||
:class="{ checked: actionBarCheckedIndex === index }"
|
||||
@click="handleClick(item, index)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="epic-sidebar-content">
|
||||
<component :is="sidebarComponent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, shallowRef } from "vue";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { RightSidebarModel } from "@/components/EpicDesigner/utils";
|
||||
import EBreadcrumb from "./breadcrumb.vue";
|
||||
import EIcon from "../../../../icon";
|
||||
|
||||
const hideRightMain = ref(false);
|
||||
|
||||
const rightSidebars = computed(() => {
|
||||
console.log(pluginManager.viewsContainers.rightSidebars.value);
|
||||
return pluginManager.viewsContainers.rightSidebars.value.filter(item => item.visible);
|
||||
});
|
||||
|
||||
const actionBarCheckedIndex = ref<number | null>(0);
|
||||
const sidebarComponent = shallowRef<any>(null);
|
||||
sidebarComponent.value = rightSidebars.value[0]?.component;
|
||||
|
||||
function handleHideRight() {
|
||||
hideRightMain.value = !hideRightMain.value;
|
||||
}
|
||||
|
||||
function handleClick(item: RightSidebarModel, index: number) {
|
||||
if (actionBarCheckedIndex.value === index) {
|
||||
return false;
|
||||
}
|
||||
sidebarComponent.value = item.component;
|
||||
actionBarCheckedIndex.value = index;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
.epic-sound-code {
|
||||
height: 100%;
|
||||
}
|
||||
.epic-editor {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="epic-sound-code">
|
||||
<MonacoEditor
|
||||
ref="monacoEditorRef"
|
||||
autoToggleTheme
|
||||
class="epic-editor"
|
||||
:model-value="initModelValue"
|
||||
lineNumbers="off"
|
||||
@update:model-value="setSchemas"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { inject, ref, toRaw, watch } from "vue";
|
||||
import { pluginManager, deepEqual, deepCompareAndModify } from "@/components/EpicDesigner/utils";
|
||||
import { Designer } from "../../../../../types/epic-designer";
|
||||
const MonacoEditor = pluginManager.getComponent("monacoEditor");
|
||||
const monacoEditorRef = ref<any>(null);
|
||||
const designer = inject("designer") as Designer;
|
||||
let oldVal: any = {};
|
||||
watch(
|
||||
() => designer.state.checkedNode,
|
||||
(newVal: any) => {
|
||||
if (!deepEqual(oldVal, toRaw(newVal))) {
|
||||
monacoEditorRef.value?.setValue(JSON.stringify(newVal, null, 2));
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
const initModelValue = JSON.stringify(designer.state.checkedNode, null, 2);
|
||||
|
||||
function setSchemas(e: string) {
|
||||
try {
|
||||
if (!designer.state.checkedNode) {
|
||||
return false;
|
||||
}
|
||||
oldVal = JSON.parse(e);
|
||||
deepCompareAndModify(designer.state.checkedNode, oldVal);
|
||||
} catch (error) {
|
||||
console.warn("[epic-desinger:源码]异常:", error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { PageSchema } from "../../../types/epic-designer";
|
||||
|
||||
export interface DesignerProps {
|
||||
disabledZoom?: boolean;
|
||||
hiddenHeader?: boolean;
|
||||
lockDefaultSchemaEdit?: boolean;
|
||||
title?: string;
|
||||
defaultSchema?: PageSchema;
|
||||
}
|
||||
2
src/components/EpicDesigner/components/icon/index.ts
Normal file
2
src/components/EpicDesigner/components/icon/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import EIcon from "./src/icon.vue";
|
||||
export default EIcon;
|
||||
17
src/components/EpicDesigner/components/icon/src/icon.vue
Normal file
17
src/components/EpicDesigner/components/icon/src/icon.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<span class="iconfont" :class="props.prefix + props.name" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: "EIcon"
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name: string;
|
||||
prefix?: string;
|
||||
}>(),
|
||||
{
|
||||
prefix: "epic-"
|
||||
}
|
||||
);
|
||||
</script>
|
||||
2
src/components/EpicDesigner/components/node/index.ts
Normal file
2
src/components/EpicDesigner/components/node/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ENode from "./src/node.vue";
|
||||
export default ENode;
|
||||
368
src/components/EpicDesigner/components/node/src/node.vue
Normal file
368
src/components/EpicDesigner/components/node/src/node.vue
Normal file
@@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<FormItem
|
||||
v-if="innerSchema.noFormItem !== true && getComponentConfing?.defaultSchema.input && component && show"
|
||||
ref="formItemRef"
|
||||
v-bind="getFormItemProps"
|
||||
>
|
||||
<component
|
||||
:is="component"
|
||||
ref="componentInstance"
|
||||
@vue:mounted="handleAddComponentInstance"
|
||||
v-bind="{ ...getComponentProps, ...dataSource, [getComponentProps.bindModel]: getBindValue() }"
|
||||
>
|
||||
<!-- 嵌套组件递归 start -->
|
||||
<!-- 渲染子组件 start -->
|
||||
<template #node="data">
|
||||
<ENode v-bind="data" />
|
||||
</template>
|
||||
<!-- 渲染子组件 end -->
|
||||
<!-- 渲染布局设计子组件列表 start -->
|
||||
<template #edit-node>
|
||||
<slot name="edit-node" />
|
||||
</template>
|
||||
<!-- 渲染布局设计子组件列表 end -->
|
||||
</component>
|
||||
</FormItem>
|
||||
<!-- 无需FormItem start -->
|
||||
<component
|
||||
:is="component"
|
||||
v-else-if="component && show"
|
||||
@vue:mounted="handleAddComponentInstance"
|
||||
ref="componentInstance"
|
||||
:model="formData"
|
||||
v-bind="{ ...getComponentProps, ...dataSource, [getComponentProps.bindModel]: getBindValue() }"
|
||||
>
|
||||
<!-- 嵌套组件递归 start -->
|
||||
<!-- 渲染子组件 start -->
|
||||
<template #node="data">
|
||||
<ENode v-bind="data" />
|
||||
</template>
|
||||
<!-- 渲染子组件 end -->
|
||||
<!-- 渲染布局设计子组件列表 start -->
|
||||
<template #edit-node>
|
||||
<slot name="edit-node" />
|
||||
</template>
|
||||
<!-- 渲染布局设计子组件列表 end -->
|
||||
</component>
|
||||
<!-- 无需FormItem end -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
shallowRef,
|
||||
ref,
|
||||
Ref,
|
||||
inject,
|
||||
computed,
|
||||
reactive,
|
||||
useAttrs,
|
||||
onUnmounted,
|
||||
provide,
|
||||
Slots,
|
||||
renderSlot,
|
||||
defineComponent,
|
||||
watch,
|
||||
// h,
|
||||
type ComponentPublicInstance,
|
||||
type ComponentInternalInstance,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import {
|
||||
pluginManager,
|
||||
capitalizeFirstLetter,
|
||||
PageManager,
|
||||
deepClone,
|
||||
deepCompareAndModify,
|
||||
deepEqual
|
||||
} from "@/components/EpicDesigner/utils";
|
||||
import { FormDataModel, ComponentSchema } from "../../../types/epic-designer";
|
||||
|
||||
export interface ComponentNodeInstance extends ComponentPublicInstance {
|
||||
setValue?: (value: any) => void;
|
||||
getValue?: () => any;
|
||||
setAttr?: (key: string, value: any) => any;
|
||||
getAttr?: (key: string) => any;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
defineOptions({
|
||||
name: "ENode"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
componentSchema: ComponentSchema;
|
||||
modelValue?: any;
|
||||
ruleField?: string[];
|
||||
resetFormData?: Boolean;
|
||||
name?: string;
|
||||
}>();
|
||||
|
||||
// 内部schema数据
|
||||
let innerSchema = reactive<ComponentSchema>(deepClone(props.componentSchema));
|
||||
|
||||
// 监听 props.componentSchema 的变化,并在变化时调用 deepCompareAndModify 方法更新内部schema数据
|
||||
watch(
|
||||
() => props.componentSchema,
|
||||
componentSchema => {
|
||||
// 深度比较对象属性值是否变更, 忽略 children 节点
|
||||
if (deepEqual(innerSchema, componentSchema, ["children"])) {
|
||||
return;
|
||||
}
|
||||
deepCompareAndModify(innerSchema, deepClone(componentSchema));
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
// 表单formData数据
|
||||
let formData = inject("formData", {}) as FormDataModel;
|
||||
|
||||
const slots = inject("slots", {}) as Slots;
|
||||
// 接收页面管理对象
|
||||
const pageManager = inject("pageManager", {}) as PageManager;
|
||||
// 上级组件注入的disabled状态
|
||||
const disabled = inject<Ref<boolean> | { value: false }>("disabled", { value: false });
|
||||
// 校验前缀字段
|
||||
const ruleFieldPrefix = inject<any[] | null>("ruleFieldPrefix", null);
|
||||
// 重置表单数据,不设置到表单formData数据
|
||||
const resetFormDataInject = inject<boolean>("resetFormData", false);
|
||||
|
||||
// 重置表单数据,移除表单数据引用
|
||||
if (props.resetFormData || resetFormDataInject) {
|
||||
formData = {};
|
||||
}
|
||||
|
||||
// 定义组件的事件
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
// 获取插件管理器中的表单项组件
|
||||
const FormItem = pluginManager.getComponent("form-item");
|
||||
// 组件实例的引用
|
||||
const componentInstance = ref<ComponentNodeInstance>();
|
||||
// 表单项的引用
|
||||
const formItemRef = ref<ComponentPublicInstance>();
|
||||
|
||||
// 传递额外的attrs
|
||||
const attrs = useAttrs();
|
||||
if (Object.keys(attrs).length) {
|
||||
provide("nodeAttrs", attrs);
|
||||
}
|
||||
|
||||
// 定义组件及组件props字段
|
||||
const component = shallowRef<any>(null);
|
||||
// const componentProps = shallowRef<any>({})
|
||||
const dataSource = reactive<any>({});
|
||||
|
||||
const show = computed(() => {
|
||||
// hidden 属性优先级最高
|
||||
if (innerSchema.componentProps?.hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// show属性为boolean类型则直接返回
|
||||
if (typeof innerSchema.show === "boolean") {
|
||||
return innerSchema.show;
|
||||
}
|
||||
|
||||
return innerSchema.show?.({ values: formData }) ?? true;
|
||||
});
|
||||
|
||||
// 获取FormItemProps
|
||||
const getFormItemProps = computed(() => {
|
||||
const rules =
|
||||
show.value &&
|
||||
innerSchema.rules?.map(item => ({
|
||||
...item,
|
||||
validator: item.validator && pageManager.funcs.value[item.validator] // 自定义校验函数
|
||||
}));
|
||||
|
||||
// 获取校验字段
|
||||
let model: string | string[] | undefined = innerSchema.field;
|
||||
|
||||
if (props.ruleField) {
|
||||
// 设置为父级传入的校验字段
|
||||
model = props.ruleField;
|
||||
} else if (ruleFieldPrefix && innerSchema.field) {
|
||||
// 添加校验字段前缀
|
||||
model = deepClone(ruleFieldPrefix) as [];
|
||||
model.push(innerSchema.field);
|
||||
}
|
||||
const formItemProps = {
|
||||
...innerSchema,
|
||||
rules,
|
||||
rule: rules,
|
||||
field: model
|
||||
};
|
||||
|
||||
// 移除元素只读属性 children
|
||||
if (formItemProps["children"]) {
|
||||
delete formItemProps["children"];
|
||||
}
|
||||
return formItemProps;
|
||||
});
|
||||
|
||||
// 获取组件props数据
|
||||
const getComponentProps = computed(() => {
|
||||
const bindModel = getComponentConfing.value?.bindModel ?? "modelValue";
|
||||
|
||||
const onEvent: { [type: string]: Function } = {};
|
||||
innerSchema.on &&
|
||||
Object.keys(innerSchema.on).forEach(item => {
|
||||
onEvent[`on${capitalizeFirstLetter(item)}`] = (...args: any) => pageManager.doActions(innerSchema.on[item], ...args);
|
||||
});
|
||||
|
||||
return {
|
||||
...props,
|
||||
...innerSchema.componentProps,
|
||||
disabled: disabled?.value || innerSchema.componentProps?.disabled,
|
||||
bindModel,
|
||||
[`onUpdate:${bindModel}`]: handleUpdate,
|
||||
...onEvent
|
||||
};
|
||||
});
|
||||
|
||||
// 获取组件原配置
|
||||
const getComponentConfing = computed(() => {
|
||||
return pluginManager.getComponentConfingByType(innerSchema.type) ?? null;
|
||||
});
|
||||
|
||||
// 计算绑定值
|
||||
const getBindValue = () => {
|
||||
return formData[innerSchema.field ?? ""] ?? props.modelValue;
|
||||
};
|
||||
|
||||
// 监听组件实例是否初始化
|
||||
watch(
|
||||
() => componentInstance.value,
|
||||
() => {
|
||||
handleAddComponentInstance();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 添加组件实例
|
||||
function handleAddComponentInstance() {
|
||||
const instance = (componentInstance.value || proxy) as ComponentNodeInstance;
|
||||
if (innerSchema.id && instance) {
|
||||
// 输入组件则添加setValue方法
|
||||
if (innerSchema.input) {
|
||||
instance.setValue = handleUpdate;
|
||||
instance.getValue = () => {
|
||||
return formData[innerSchema.field!] || props.modelValue;
|
||||
};
|
||||
}
|
||||
|
||||
// 添加属性设置方法
|
||||
instance.setAttr = (key: string, value: any) => {
|
||||
return (innerSchema.componentProps[key] = value);
|
||||
};
|
||||
|
||||
// 添加获取设置方法
|
||||
instance.getAttr = (key: string) => {
|
||||
return innerSchema.componentProps[key];
|
||||
};
|
||||
|
||||
pageManager.addComponentInstance(innerSchema.id, instance);
|
||||
// 添加实例 及 formItem实例
|
||||
if (getComponentConfing.value?.defaultSchema.input && innerSchema.noFormItem !== true && formItemRef.value) {
|
||||
pageManager.addComponentInstance(innerSchema.id + "formItem", formItemRef.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除组件实例
|
||||
*/
|
||||
function handleVnodeUnmounted() {
|
||||
if (innerSchema.id) {
|
||||
// 移除实例 及 formItem实例
|
||||
pageManager.removeComponentInstance(innerSchema.id);
|
||||
if (getComponentConfing.value?.defaultSchema.input && innerSchema.noFormItem !== true) {
|
||||
pageManager.removeComponentInstance(innerSchema.id + "formItem");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化组件
|
||||
*/
|
||||
async function initComponent() {
|
||||
// 如果存在默认值,则会在初始化之后赋值
|
||||
if (typeof innerSchema.componentProps?.defaultValue !== "undefined") {
|
||||
const defaultValue = pageManager.isDesignMode.value
|
||||
? innerSchema.componentProps?.defaultValue
|
||||
: formData[innerSchema.field!] ?? innerSchema.componentProps?.defaultValue;
|
||||
|
||||
handleUpdate(deepClone(defaultValue));
|
||||
}
|
||||
|
||||
// 组件为slot类型时
|
||||
if (innerSchema.type === "slot") {
|
||||
const slotName = innerSchema.slotName;
|
||||
if (!slotName) return;
|
||||
|
||||
component.value = defineComponent({
|
||||
setup() {
|
||||
return () =>
|
||||
renderSlot(slots, slotName, {
|
||||
componentSchema: innerSchema,
|
||||
model: formData
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
console.log(innerSchema.type, "=innerSchema.type=");
|
||||
// 内置组件
|
||||
const cmp = pluginManager.getComponent(innerSchema.type);
|
||||
// 内部不存在组件
|
||||
if (!cmp) {
|
||||
console.error(`组件${innerSchema.type}未注册`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果数据项为函数,则判定为懒加载组件
|
||||
if (typeof cmp === "function") {
|
||||
const res = await cmp();
|
||||
component.value = res.default ?? res;
|
||||
} else {
|
||||
// 否则为预加载组件
|
||||
component.value = cmp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过函数更新值
|
||||
* @param v value值
|
||||
*/
|
||||
function handleUpdate(v: any) {
|
||||
emit("update:modelValue", v);
|
||||
emit("change", v);
|
||||
if (innerSchema.field) {
|
||||
formData[innerSchema.field!] = v;
|
||||
}
|
||||
}
|
||||
|
||||
let oldData: string | null = null;
|
||||
// 需要监听值变化,重新渲染组件
|
||||
watch(
|
||||
() => innerSchema,
|
||||
newVal => {
|
||||
// 过滤所有子节点
|
||||
const newData = JSON.stringify({ ...newVal, children: undefined });
|
||||
if (newData === oldData) {
|
||||
return false;
|
||||
}
|
||||
oldData = newData;
|
||||
initComponent();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
// 组件卸载时移除组件实例
|
||||
onUnmounted(handleVnodeUnmounted);
|
||||
</script>
|
||||
2
src/components/EpicDesigner/components/tree/index.ts
Normal file
2
src/components/EpicDesigner/components/tree/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ETree from "./src/tree.vue";
|
||||
export default ETree;
|
||||
94
src/components/EpicDesigner/components/tree/src/index.less
Normal file
94
src/components/EpicDesigner/components/tree/src/index.less
Normal file
@@ -0,0 +1,94 @@
|
||||
.epic-tree {
|
||||
.epic-tree-main {
|
||||
padding-right: 10px;
|
||||
}
|
||||
ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
color: var(--epic-text-main);
|
||||
&:hover {
|
||||
color: var(--epic-text-main);
|
||||
}
|
||||
.text {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
&.hover {
|
||||
background-color: var(--epic-widget-hover-color);
|
||||
}
|
||||
&.checked {
|
||||
color: var(--epic-primary-color);
|
||||
background: var(--epic-primary-hover-color);
|
||||
}
|
||||
.epic-text-padding {
|
||||
padding: 0 6px;
|
||||
color: var(--epic-text-main);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-node-type-text {
|
||||
margin-left: 6px;
|
||||
font-size: var(--epic-text-sm);
|
||||
color: var(--epic-text-disabled);
|
||||
}
|
||||
.epic-tree-node {
|
||||
&.level-1 {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
min-height: 30px;
|
||||
padding-left: 16px;
|
||||
line-height: 30px;
|
||||
cursor: pointer;
|
||||
.icon-expanded {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
font-size: var(--epic-text-sm);
|
||||
color: var(--epic-border-color);
|
||||
transition: transform 0.3s;
|
||||
&.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-tree-sublist {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
&.expanded {
|
||||
height: auto;
|
||||
}
|
||||
.epic-tree-node {
|
||||
position: relative;
|
||||
&::after,
|
||||
&::before {
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
content: "";
|
||||
background: var(--epic-border-color);
|
||||
}
|
||||
&::after {
|
||||
top: 0;
|
||||
left: 4px;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
&::before {
|
||||
top: 13px;
|
||||
left: 4px;
|
||||
width: 8px;
|
||||
height: 1px;
|
||||
}
|
||||
&.expanded::before {
|
||||
display: none;
|
||||
}
|
||||
&:last-child {
|
||||
&::after {
|
||||
height: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
src/components/EpicDesigner/components/tree/src/tree.vue
Normal file
110
src/components/EpicDesigner/components/tree/src/tree.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="epic-tree h-full flex flex-col">
|
||||
<!-- 搜素框 start -->
|
||||
<div class="epic-search-box px-10px py-6px">
|
||||
<Input placeholder="搜索节点" clearable allowClear v-model="keyword" v-model:value="keyword">
|
||||
<template #prefix>
|
||||
<EIcon name="icon-chaxun" />
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
<!-- 搜素框 end -->
|
||||
<div class="epic-tree-main flex-1 overflow-auto h-0">
|
||||
<ul>
|
||||
<ETreeNodes v-model:schemas="getTreeData" />
|
||||
</ul>
|
||||
<div v-show="!getTreeData.length" class="text-center pt-42px text-gray-400">没有查询到的数据</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ComponentSchema } from "../../../types/epic-designer";
|
||||
import type { PropType } from "vue";
|
||||
import { ref, provide, computed, useSlots } from "vue";
|
||||
import ETreeNodes from "./treeNodes.vue";
|
||||
import EIcon from "../../icon";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "ETree"
|
||||
});
|
||||
const slots = useSlots();
|
||||
provide("slots", slots);
|
||||
|
||||
const Input = pluginManager.getComponent("input");
|
||||
|
||||
const keyword = ref("");
|
||||
const expandedKeys = ref([]);
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array as PropType<ComponentSchema[]>,
|
||||
default: () => []
|
||||
},
|
||||
hoverKey: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
selectedKeys: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:selectedKeys", "nodeClick"]);
|
||||
const selectedKeysComputed = computed({
|
||||
get() {
|
||||
return props.selectedKeys;
|
||||
},
|
||||
set(value) {
|
||||
emits("update:selectedKeys", value);
|
||||
}
|
||||
});
|
||||
|
||||
const getTreeData = computed({
|
||||
get() {
|
||||
return filterTreeByLabel(props.options, keyword.value);
|
||||
},
|
||||
set(e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 通过label 过滤节点
|
||||
* @param tree 节点树
|
||||
* @param labelToFilter 过滤关键字
|
||||
*/
|
||||
function filterTreeByLabel(tree: any, labelToFilter: any) {
|
||||
const filteredTree: ComponentSchema[] = [];
|
||||
|
||||
tree.forEach((item: ComponentSchema) => {
|
||||
if (item.label?.includes(labelToFilter)) {
|
||||
filteredTree.push(item);
|
||||
} else if (item.children) {
|
||||
const filteredChildren = filterTreeByLabel(item.children, labelToFilter);
|
||||
if (filteredChildren.length > 0) {
|
||||
// Clone the item and replace its children
|
||||
const clonedItem = { ...item };
|
||||
clonedItem.children = filteredChildren;
|
||||
filteredTree.push(clonedItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return filteredTree;
|
||||
}
|
||||
|
||||
function handleSelect(id: string, componentSchema: ComponentSchema) {
|
||||
selectedKeysComputed.value = [id];
|
||||
emits("nodeClick", { id, componentSchema });
|
||||
}
|
||||
|
||||
provide("expandedKeys", expandedKeys);
|
||||
provide("selectedKeys", selectedKeysComputed);
|
||||
provide("treeProps", props);
|
||||
provide("handleSelect", handleSelect);
|
||||
</script>
|
||||
150
src/components/EpicDesigner/components/tree/src/treeNodeItem.vue
Normal file
150
src/components/EpicDesigner/components/tree/src/treeNodeItem.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<li
|
||||
class="epic-tree-node"
|
||||
:class="{
|
||||
expanded: hasChildren,
|
||||
'level-1': props.schema.type === 'page'
|
||||
}"
|
||||
>
|
||||
<a>
|
||||
<span
|
||||
v-if="hasChildren && props.schema.type !== 'page'"
|
||||
class="icon-expanded"
|
||||
:class="{ expanded }"
|
||||
@click="handleExpanded"
|
||||
>
|
||||
<EIcon name="icon-youjiantou" />
|
||||
</span>
|
||||
<TreeNodeText />
|
||||
</a>
|
||||
<!-- 关键修改:绑定内部响应式数据 innerChildren,而非直接绑定 props.schema.children -->
|
||||
<ETreeNodes
|
||||
v-if="hasChildren"
|
||||
class="epic-tree-sublist"
|
||||
:class="{ expanded }"
|
||||
v-model:schemas="innerChildren"
|
||||
:parentSchema="props.schema"
|
||||
@update:schemas="handleChildrenUpdate"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ComponentSchema } from "../../../types/epic-designer";
|
||||
import EIcon from "../../icon";
|
||||
import { inject, computed, ref, Ref, h, defineComponent, Slots, watch } from "vue";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import ETreeNodes from "./treeNodes.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "ETreeNodeItem"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
schema: ComponentSchema;
|
||||
}>();
|
||||
|
||||
// 关键步骤1:创建内部响应式数据,复制 props.schema.children 的初始值
|
||||
// 避免直接操作 props 的嵌套属性,通过内部数据做中间层
|
||||
const innerChildren = ref<ComponentSchema[]>(props.schema.children || []);
|
||||
|
||||
// 关键步骤2:监听 props.schema.children 的变化,同步到内部响应式数据
|
||||
// 确保父组件更新 children 时,子组件能同步更新(遵循单向数据流)
|
||||
watch(
|
||||
() => props.schema.children,
|
||||
newChildren => {
|
||||
innerChildren.value = newChildren || [];
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
// 计算属性:判断是否有子节点(基于内部响应式数据,而非直接读 props)
|
||||
const hasChildren = computed(() => {
|
||||
return innerChildren.value.length > 0;
|
||||
});
|
||||
|
||||
// 注入上层依赖(保持不变)
|
||||
const slots = inject("slots", {}) as Slots;
|
||||
const expandedKeys = inject("expandedKeys") as Ref<string[]>;
|
||||
const treeProps = inject("treeProps") as any;
|
||||
const selectedKeys = inject("selectedKeys") as Ref<string[]>;
|
||||
const handleSelect = inject("handleSelect") as (id: string, componentSchema: ComponentSchema) => void;
|
||||
|
||||
// 计算属性:判断当前节点是否展开(保持不变)
|
||||
const expanded = computed(() => {
|
||||
return expandedKeys.value.includes(props.schema.id ?? "");
|
||||
});
|
||||
|
||||
// 节点文本组件(保持不变,仅读取 props.schema,无修改)
|
||||
const TreeNodeText = defineComponent({
|
||||
setup() {
|
||||
const getComponentLabel = () => {
|
||||
return (
|
||||
props.schema.label ||
|
||||
pluginManager.getComponentConfingByType(props.schema.type)?.defaultSchema.label ||
|
||||
"未命名组件"
|
||||
);
|
||||
};
|
||||
|
||||
return () =>
|
||||
h(
|
||||
"span",
|
||||
{
|
||||
class: {
|
||||
text: true,
|
||||
hover: treeProps.hoverKey === props.schema.id,
|
||||
checked: selectedKeys.value.includes(props.schema.id!)
|
||||
},
|
||||
onClick: () => {
|
||||
if (props.schema.id) {
|
||||
// 传递 schema 副本,避免父组件可能的意外修改
|
||||
handleSelect(props.schema.id, { ...props.schema });
|
||||
}
|
||||
}
|
||||
},
|
||||
slots["tree-node"]?.(props) ??
|
||||
h(
|
||||
"span",
|
||||
{ class: "epic-text-padding" },
|
||||
{
|
||||
default: () => [getComponentLabel(), h("span", { class: "epic-node-type-text" }, props.schema.type)]
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 展开/折叠节点(保持不变,仅修改注入的 expandedKeys,不碰 props)
|
||||
function handleExpanded() {
|
||||
const id = props.schema.id;
|
||||
if (!id) return;
|
||||
|
||||
expandedKeys.value = expandedKeys.value.includes(id)
|
||||
? expandedKeys.value.filter(item => item !== id)
|
||||
: [...expandedKeys.value, id];
|
||||
}
|
||||
|
||||
// 关键步骤3:子节点更新时,通过父组件暴露的方法同步(而非直接修改 props)
|
||||
// 需在父组件中处理 children 的更新逻辑,子组件仅触发事件
|
||||
function handleChildrenUpdate(newChildren: ComponentSchema[]) {
|
||||
// 触发自定义事件,通知父组件更新 children
|
||||
// 父组件需通过 @update:children 接收并更新自身的 schema.children
|
||||
emit("update:children", newChildren);
|
||||
}
|
||||
|
||||
// 初始化展开节点(保持不变,仅修改注入的 expandedKeys,不碰 props)
|
||||
function init() {
|
||||
const id = props.schema.id;
|
||||
if (!id || !hasChildren.value) return;
|
||||
|
||||
if (!expandedKeys.value.includes(id)) {
|
||||
expandedKeys.value = [...expandedKeys.value, id];
|
||||
}
|
||||
}
|
||||
|
||||
// 注册自定义事件,用于通知父组件更新 children
|
||||
const emit = defineEmits<{
|
||||
(e: "update:children", newChildren: ComponentSchema[]): void;
|
||||
}>();
|
||||
|
||||
init();
|
||||
</script>
|
||||
125
src/components/EpicDesigner/components/tree/src/treeNodes.vue
Normal file
125
src/components/EpicDesigner/components/tree/src/treeNodes.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<!-- 可拖拽模式:子节点允许拖拽排序 -->
|
||||
<draggable
|
||||
v-if="!pluginManager.getComponentConfingByType(props.parentSchema?.type || '')?.childImmovable"
|
||||
v-model="modelSchemas"
|
||||
item-key="id"
|
||||
:component-data="{}"
|
||||
class="epic-draggable-range"
|
||||
v-bind="{
|
||||
animation: 200,
|
||||
tag: 'ul',
|
||||
group: 'tree-draggable',
|
||||
ghostClass: 'moveing',
|
||||
draggable: '.draggable-item',
|
||||
disabled: !treeProps.draggable || modelSchemas[0]?.type === 'page'
|
||||
}"
|
||||
@start="handleSelect($event.oldIndex)"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<!-- 绑定子组件的update:children事件,接收子节点更新 -->
|
||||
<ETreeNodeItem
|
||||
:class="isDraggable(element)"
|
||||
:key="element.id"
|
||||
:schema="element"
|
||||
@update:children="newChildren => handleChildrenUpdate(element.id, newChildren)"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- 不可拖拽模式:仅静态渲染节点 -->
|
||||
<ul v-else class="epic-static-list">
|
||||
<ETreeNodeItem
|
||||
v-for="element in modelSchemas"
|
||||
:key="element.id"
|
||||
:schema="element"
|
||||
:class="isDraggable(element)"
|
||||
@update:children="newChildren => handleChildrenUpdate(element.id, newChildren)"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject } from "vue";
|
||||
import { ComponentSchema, Designer } from "../../../types/epic-designer";
|
||||
import ETreeNodeItem from "./treeNodeItem.vue";
|
||||
import draggable from "vuedraggable";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
|
||||
defineOptions({ name: "ETreeNodes" });
|
||||
|
||||
// 注入上层依赖(设计器实例、树配置)
|
||||
const designer = inject("designer") as Designer;
|
||||
const treeProps = inject("treeProps") as any;
|
||||
|
||||
// 父组件接收的props(从上层组件传递的节点列表)
|
||||
const props = defineProps<{
|
||||
schemas: ComponentSchema[]; // 子节点列表
|
||||
parentSchema?: ComponentSchema; // 父节点配置(用于判断是否允许子节点拖拽)
|
||||
}>();
|
||||
|
||||
// 父组件对外暴露的事件(向上层传递节点更新)
|
||||
const emit = defineEmits(["update:schemas"]);
|
||||
|
||||
// 核心:响应式数据代理,避免直接修改props
|
||||
const modelSchemas = computed({
|
||||
get() {
|
||||
return props.schemas || []; // 空值兜底,避免undefined
|
||||
},
|
||||
set(newSchemas) {
|
||||
emit("update:schemas", newSchemas); // 向上层同步更新
|
||||
}
|
||||
});
|
||||
|
||||
// 拖拽开始时选中节点(设计器联动逻辑)
|
||||
function handleSelect(index: number) {
|
||||
if (index >= 0 && index < modelSchemas.value.length) {
|
||||
designer.setDisableHover(true);
|
||||
designer.setCheckedNode(modelSchemas.value[index]);
|
||||
}
|
||||
}
|
||||
|
||||
// 关键:处理子组件传递的“子节点更新”,同步到父组件响应式数据
|
||||
function handleChildrenUpdate(targetId: any, newChildren: any[]) {
|
||||
// 1. 找到要更新的节点索引
|
||||
const targetIndex = modelSchemas.value.findIndex(item => item.id === targetId);
|
||||
if (targetIndex === -1) return;
|
||||
|
||||
// 2. 复制数组+节点对象(避免直接修改响应式引用,确保Vue触发更新)
|
||||
const updatedSchemas = [...modelSchemas.value];
|
||||
updatedSchemas[targetIndex] = {
|
||||
...updatedSchemas[targetIndex], // 保留原节点其他属性
|
||||
children: newChildren // 替换为子组件传递的新子节点
|
||||
};
|
||||
|
||||
// 3. 触发响应式更新
|
||||
modelSchemas.value = updatedSchemas;
|
||||
}
|
||||
|
||||
// 判断节点是否允许拖拽(禁止页面节点、配置为不可拖拽的节点)
|
||||
function isDraggable(schema: ComponentSchema) {
|
||||
const componentConfig = pluginManager.getComponentConfingByType(schema.type);
|
||||
return schema.type === "page" || (componentConfig && componentConfig.immovable)
|
||||
? "unmover-item" // 禁止拖拽的类
|
||||
: "draggable-item"; // 允许拖拽的类
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.epic-draggable-range,
|
||||
.epic-static-list {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.draggable-item {
|
||||
cursor: move;
|
||||
}
|
||||
.unmover-item {
|
||||
cursor: default;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.moveing {
|
||||
background-color: #f0f7ff;
|
||||
border: 1px dashed #1890ff;
|
||||
}
|
||||
</style>
|
||||
147
src/components/EpicDesigner/extensions/EActionEditor/index.less
Normal file
147
src/components/EpicDesigner/extensions/EActionEditor/index.less
Normal file
@@ -0,0 +1,147 @@
|
||||
.epic-event-item {
|
||||
.epic-event-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
margin: 0 -1px -1px 0;
|
||||
color: var(--epic-text-main);
|
||||
background-color: var(--epic-event-item-color);
|
||||
border: 1px solid var(--epic-border-color);
|
||||
}
|
||||
.epic-event-btn > span {
|
||||
cursor: pointer;
|
||||
}
|
||||
.epic-action-editor-main {
|
||||
.epic-editor-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
margin: 8px 0;
|
||||
color: var(--epic-text-main);
|
||||
background-color: var(--epic-event-item-color);
|
||||
border: 1px solid var(--epic-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-modal-action-main {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
.epic-modal-left-panel,
|
||||
.epic-script-left-panel {
|
||||
box-sizing: border-box;
|
||||
width: 240px;
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--epic-border-color);
|
||||
.fun-btn {
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 6px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--epic-border-color);
|
||||
border-radius: 3px;
|
||||
&.checked {
|
||||
color: var(--epic-primary-color);
|
||||
background-color: var(--epic-primary-hover-color);
|
||||
border-color: var(--epic-primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-modal-right-panel,
|
||||
.epic-script-right-panel {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
margin-left: 8px;
|
||||
border: 1px solid var(--epic-border-color);
|
||||
.action-select {
|
||||
min-width: 175px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.epic-script-edit {
|
||||
display: flex;
|
||||
.epic-script-left-panel {
|
||||
.epic-tree-select {
|
||||
height: 240px;
|
||||
overflow: auto;
|
||||
}
|
||||
.tree-node-item,
|
||||
.epic-action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8px;
|
||||
.action {
|
||||
color: var(--epic-primary-color);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
&:hover .action {
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-script-right-panel {
|
||||
// width: 240px;
|
||||
// flex: none;
|
||||
}
|
||||
}
|
||||
.epic-action-select {
|
||||
padding: 8px;
|
||||
padding-right: 0;
|
||||
margin-top: 12px;
|
||||
overflow: auto;
|
||||
border-top: 1px solid var(--epic-border-color);
|
||||
.epic-action-item {
|
||||
height: 32px;
|
||||
padding-left: 12px;
|
||||
line-height: 32px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
&:hover {
|
||||
color: var(--epic-primary-color);
|
||||
background-color: var(--epic-primary-hover-color);
|
||||
}
|
||||
&.checked {
|
||||
color: var(--epic-primary-color);
|
||||
background-color: var(--epic-primary-hover-color);
|
||||
border-color: var(--epic-primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.epic-editor-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 6px;
|
||||
.epic-action-box {
|
||||
display: flex;
|
||||
.epic-del-btn {
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #ff2222;
|
||||
}
|
||||
}
|
||||
.epic-edit-btn {
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--epic-primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.add-btn {
|
||||
margin-top: 6px;
|
||||
color: var(--epic-primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
164
src/components/EpicDesigner/extensions/EActionEditor/index.vue
Normal file
164
src/components/EpicDesigner/extensions/EActionEditor/index.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<Collapse v-model="activeNames" v-model:activeKey="activeNames" v-model:expanded-names="activeNames">
|
||||
<CollapseItem
|
||||
v-for="item in filterEventList"
|
||||
:key="item.title"
|
||||
:title="item.title"
|
||||
:header="item.title"
|
||||
:name="item.title"
|
||||
>
|
||||
<EActionEditorItem
|
||||
v-model="modelValueComputed"
|
||||
:item-events="item.events"
|
||||
:all-events="allEvents"
|
||||
:events="events"
|
||||
@add="handleOpen"
|
||||
@edit="handleOpenEdit"
|
||||
/>
|
||||
</CollapseItem>
|
||||
</Collapse>
|
||||
<EActionModal ref="EActionModalRef" @add="handleAdd" @edit="handleEdit" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType, computed, ref, toRaw, watch } from "vue";
|
||||
import EActionEditorItem from "./src/EActionEditorItem.vue";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import EActionModal from "./src/EActionModal.vue";
|
||||
const Collapse = pluginManager.getComponent("Collapse");
|
||||
const CollapseItem = pluginManager.getComponent("CollapseItem");
|
||||
|
||||
const EActionModalRef = ref<any>(null);
|
||||
let editIndex = 0;
|
||||
const props = defineProps({
|
||||
eventList: {
|
||||
type: Array as PropType<any>,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as PropType<any>
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const modelValueComputed = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
});
|
||||
const activeNames = ref<string[]>([]);
|
||||
|
||||
// 过滤无事件
|
||||
const filterEventList = computed(() => {
|
||||
return props.eventList.filter((item: { events: string | any[] }) => item.events.length);
|
||||
});
|
||||
|
||||
const allEvents = computed(() => {
|
||||
return props.eventList.map((item: { events: any }) => item.events).flat();
|
||||
});
|
||||
|
||||
const events = ref<any>({});
|
||||
|
||||
allEvents.value.forEach((item: any) => {
|
||||
events.value[item.type] = computed({
|
||||
get() {
|
||||
return modelValueComputed.value?.[item.type] ?? [];
|
||||
},
|
||||
set(e) {
|
||||
if (e && e.length) {
|
||||
modelValueComputed.value[item.type] = e.map((item: any) => toRaw(item));
|
||||
} else {
|
||||
// 事件动作为空时,则清除该事件列表
|
||||
delete modelValueComputed.value[item.type];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 监视 filterEventList.value 的变化
|
||||
watch(
|
||||
() => filterEventList.value,
|
||||
(e: any) => {
|
||||
// 当 filterEventList.value 变化时执行回调函数
|
||||
if (e.length) {
|
||||
// 如果 filterEventList.value 不为空数组
|
||||
// 过滤出满足条件的事件,并将它们的标题存储在 activeNames.value 中
|
||||
activeNames.value = e
|
||||
.filter((item: any) => {
|
||||
// 对于每个事件项,检查其包含的事件类型
|
||||
for (let i = 0; i < item.events.length; i++) {
|
||||
const type = item.events[i].type;
|
||||
// 如果 events.value[type] 不为空数组,则返回 true
|
||||
if (events.value[type].length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 如果事件项中没有任何一个事件类型满足条件,则返回 false
|
||||
return false;
|
||||
})
|
||||
.map((item: any) => item.title); // 将满足条件的事件项的标题映射成一个新的数组
|
||||
}
|
||||
},
|
||||
{
|
||||
// 配置选项
|
||||
immediate: true // 立即执行一次回调函数
|
||||
}
|
||||
);
|
||||
|
||||
let currentType: string = "";
|
||||
/**
|
||||
* 打开动作配置窗口
|
||||
* @param type
|
||||
*/
|
||||
function handleOpen(type: string) {
|
||||
EActionModalRef.value?.handleOpen();
|
||||
currentType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开动作配置窗口-编辑
|
||||
* @param {number} index - 要编辑事件的索引
|
||||
* @param {string} type - 事件类型
|
||||
* @param {any} action - 要执行的动作
|
||||
*/
|
||||
function handleOpenEdit(index: number, type: string, action: any) {
|
||||
// 如果 EActionModalRef.value 不为 null 或 undefined,则调用其 handleOpenEdit 方法
|
||||
EActionModalRef.value?.handleOpenEdit(action);
|
||||
|
||||
// 将要编辑的事件的索引赋值给 editIndex
|
||||
editIndex = index;
|
||||
|
||||
// 将事件的类型赋值给 currentType
|
||||
currentType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件的函数
|
||||
* @param {any} action - 动作数据
|
||||
*/
|
||||
function handleEdit(action: any) {
|
||||
// 将编辑后的动作替换掉 events.value[currentType][editIndex]
|
||||
events.value[currentType][editIndex] = action;
|
||||
|
||||
// 更新 modelValueComputed.value[currentType],使用新的数组代替原始数组
|
||||
modelValueComputed.value[currentType] = [...(events.value[currentType] ?? [])];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加组件事件
|
||||
* @param {any} action - 要执行的动作
|
||||
*/
|
||||
function handleAdd(action: any) {
|
||||
// 如果 modelValueComputed.value 为 null 或 undefined,则创建一个新的对象
|
||||
if (!modelValueComputed.value) {
|
||||
modelValueComputed.value = { [currentType]: [...(events.value[currentType] ?? []), action] };
|
||||
return;
|
||||
}
|
||||
|
||||
// 否则,直接在 modelValueComputed.value[currentType] 中添加新的动作
|
||||
modelValueComputed.value[currentType] = [...(events.value[currentType] ?? []), action];
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div v-for="item in itemEvents" :key="item.type" class="epic-event-item">
|
||||
<div class="epic-event-info">
|
||||
<div class="epic-event-label" :title="item.describe">
|
||||
{{ item.describe }}
|
||||
</div>
|
||||
<div class="epic-event-btn">
|
||||
<EIcon name="icon-tianjia1" @click="handleOpen(item.type)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="epic-action-editor-main">
|
||||
<draggable
|
||||
v-model="internalEvents[item.type]"
|
||||
item-key="id"
|
||||
:component-data="{
|
||||
type: 'transition-group'
|
||||
}"
|
||||
group="option-list"
|
||||
handle=".handle"
|
||||
:animation="200"
|
||||
@update:modelValue="handleEventsUpdate(item.type, $event)"
|
||||
>
|
||||
<template #item="{ element: action, index }">
|
||||
<div class="epic-editor-item">
|
||||
<div class="w-36px text-lg">
|
||||
<EIcon class="mr-2 text-lg cursor-move handle" name="icon-tuozhuai" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div v-if="action.type === 'component'">
|
||||
{{ getLabel(action.componentId) }}
|
||||
</div>
|
||||
<div v-else-if="action.type === 'custom'">自定义函数</div>
|
||||
<div v-else-if="action.type === 'public'">公共函数</div>
|
||||
{{ action.methodName }}
|
||||
</div>
|
||||
<div class="epic-action-box">
|
||||
<div class="epic-edit-btn" @click="handleEdit(index, item.type, action)">
|
||||
<EIcon name="icon-tiaozheng" />
|
||||
</div>
|
||||
<div class="epic-del-btn" @click="handleDelete(index, item.type)">
|
||||
<EIcon name="icon-shanchu1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { type PropType, inject, ref, watch } from "vue";
|
||||
import { findSchemaById } from "@/components/EpicDesigner/utils";
|
||||
import { PageSchema } from "../../../types/epic-designer";
|
||||
import EIcon from "../../../components/icon";
|
||||
import draggable from "vuedraggable";
|
||||
|
||||
const props = defineProps({
|
||||
itemEvents: {
|
||||
type: Array as PropType<any>,
|
||||
default: () => []
|
||||
},
|
||||
allEvents: {
|
||||
type: Array as PropType<any>,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}) // 修复默认值类型,与类型声明一致
|
||||
},
|
||||
events: {
|
||||
type: Object as PropType<Record<string, any[]>>,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(["add", "edit", "update:modelValue"]);
|
||||
|
||||
// 核心修复:创建内部响应式数据,避免直接修改props
|
||||
const internalEvents = ref<Record<string, any[]>>({});
|
||||
|
||||
// 监听props变化,同步到内部数据
|
||||
watch(
|
||||
() => props.events,
|
||||
newEvents => {
|
||||
internalEvents.value = { ...newEvents };
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
|
||||
function handleOpen(type: string) {
|
||||
emit("add", type);
|
||||
}
|
||||
|
||||
function getLabel(id: string) {
|
||||
const schema = findSchemaById(pageSchema.schemas, id);
|
||||
return schema?.label || ""; // 增加空值处理
|
||||
}
|
||||
|
||||
function handleDelete(index: number, type: string) {
|
||||
// 基于内部数据操作
|
||||
const newEvents = getNewEvents(type);
|
||||
newEvents[type] = internalEvents.value[type]?.filter((_item: any, i: number) => index !== i) || [];
|
||||
|
||||
if (!newEvents[type]?.length) {
|
||||
delete newEvents[type];
|
||||
}
|
||||
|
||||
// 更新内部数据并通知父组件
|
||||
internalEvents.value = newEvents;
|
||||
emit("update:modelValue", newEvents);
|
||||
}
|
||||
|
||||
function handleEdit(index: number, type: string, action: any) {
|
||||
emit("edit", index, type, action);
|
||||
}
|
||||
|
||||
function getNewEvents(type: string) {
|
||||
const newEvents: { [type: string]: any } = {};
|
||||
props.allEvents.forEach((item: any) => {
|
||||
// 增加空值检查
|
||||
if (!internalEvents.value[item.type]?.length) {
|
||||
return;
|
||||
}
|
||||
if (item.type === type) {
|
||||
return;
|
||||
}
|
||||
newEvents[item.type] = internalEvents.value[item.type];
|
||||
});
|
||||
return newEvents;
|
||||
}
|
||||
|
||||
// 处理拖拽导致的事件顺序变化
|
||||
function handleEventsUpdate(type: string, newValue: any[]) {
|
||||
internalEvents.value[type] = newValue;
|
||||
|
||||
// 构建完整的事件对象并通知父组件
|
||||
const updatedEvents = { ...internalEvents.value };
|
||||
emit("update:modelValue", updatedEvents);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<Modal v-model="visible" class="w-1200px" width="1200px" @close="handleClose" @ok="handleSave" title="动作配置">
|
||||
<div class="rounded epic-modal-action-main">
|
||||
<div class="epic-modal-left-panel h-full flex flex-col">
|
||||
<!-- 动作所属对象 start -->
|
||||
<div class="flex flex-1 h-0 flex-col">
|
||||
<div class="fun-btn" :class="{ checked: state.actionItem.type === 'custom' }" @click="toggleMethod('custom')">
|
||||
自定义函数
|
||||
</div>
|
||||
<div class="fun-btn" :class="{ checked: state.actionItem.type === 'public' }" @click="toggleMethod('public')">
|
||||
公共函数
|
||||
</div>
|
||||
组件
|
||||
<div class="flex-1 h-0">
|
||||
<ETree v-model:selectedKeys="selectedKeys" :options="pageSchema.schemas" @nodeClick="handleNodeClick" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 动作选择 start -->
|
||||
<div class="epic-action-select h-30/100 flex flex-col">
|
||||
<div class="mb-2">动作选择</div>
|
||||
<div class="flex-1 overflow-auto pr-8px">
|
||||
<div
|
||||
v-for="item in methodOptions"
|
||||
:class="{ checked: item.value === state.actionItem.methodName }"
|
||||
@click="handleCheckedMethod(item.value)"
|
||||
:key="item.value"
|
||||
class="epic-action-item"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
<div v-show="!methodOptions?.length" class="text-center pt-42px text-gray-400">当前组件暂无动作</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 动作所属对象 end -->
|
||||
<!-- 动作选择 end -->
|
||||
</div>
|
||||
<!-- 动作配置 start -->
|
||||
<div class="epic-modal-right-panel">
|
||||
<EScriptEdit v-if="state.actionItem.type === 'custom'" />
|
||||
<div v-else-if="actionArgsConfigs.length === 0" class="text-center pt-42px text-gray-400">暂无配置</div>
|
||||
<EArgsEditor v-else v-model="state.actionItem.args" :actionArgsConfigs="actionArgsConfigs" />
|
||||
</div>
|
||||
<!-- 动作配置 end -->
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { pluginManager, PageManager, deepClone, findSchemaById, getUUID } from "@/components/EpicDesigner/utils";
|
||||
import { ref, inject, toRaw, reactive, computed, nextTick } from "vue";
|
||||
import ETree from "../../../components/tree";
|
||||
import { ComponentSchema, PageSchema, FormDataModel } from "../../../types/epic-designer";
|
||||
import EScriptEdit from "./EScriptEdit.vue";
|
||||
import EArgsEditor from "./EArgsEditor.vue";
|
||||
|
||||
const Modal = pluginManager.getComponent("modal");
|
||||
const isAdd = ref(true);
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
const pageManager = inject("pageManager", {}) as PageManager;
|
||||
const visible = ref(false);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const componentSchema = ref<any>(null);
|
||||
|
||||
const emit = defineEmits(["add", "edit"]);
|
||||
|
||||
const methodOptions = computed(() => {
|
||||
// 组件动作列表
|
||||
if (state.actionItem.type === "component") {
|
||||
if (componentSchema.value) {
|
||||
return pluginManager.getComponentConfings()[componentSchema.value.type].config.action?.map(item => ({
|
||||
label: item.describe,
|
||||
value: item.type
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// 自定义函数列表
|
||||
if (state.actionItem.type === "custom") {
|
||||
return Object.entries(pageManager.funcs.value)
|
||||
.filter(([, value]) => typeof value === "function")
|
||||
.map(([label]) => ({ label, value: label }));
|
||||
}
|
||||
|
||||
// 公共函数列表
|
||||
if (state.actionItem.type === "public") {
|
||||
return Object.entries(pluginManager.publicMethods).map(([label]) => ({
|
||||
label,
|
||||
value: label
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
const actionArgsConfigs = computed(() => {
|
||||
if (state.actionItem.type === "component") {
|
||||
if (componentSchema.value) {
|
||||
const action = pluginManager.getComponentConfings()[componentSchema.value.type].config.action;
|
||||
const actionItem = action?.find(item => item.type === state.actionItem.methodName);
|
||||
|
||||
if (actionItem?.argsConfigs) {
|
||||
const index = actionItem.argsConfigs.findIndex(item => item.label === "设置数据");
|
||||
index !== -1 &&
|
||||
(actionItem.argsConfigs[index] = {
|
||||
...componentSchema.value,
|
||||
label: "设置数据",
|
||||
field: "0",
|
||||
id: getUUID()
|
||||
} as ComponentSchema);
|
||||
}
|
||||
|
||||
return actionItem?.argsConfigs ?? [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const state = reactive<any>({
|
||||
actionItem: {
|
||||
type: "custom",
|
||||
methodName: "test",
|
||||
componentId: null
|
||||
} as FormDataModel,
|
||||
cacheData: {}
|
||||
});
|
||||
|
||||
function handleOpen() {
|
||||
visible.value = true;
|
||||
isAdd.value = true;
|
||||
state.actionItem.type = "custom";
|
||||
state.actionItem.componentId = null;
|
||||
if (methodOptions.value?.length) {
|
||||
handleCheckedMethod(methodOptions.value[0].value);
|
||||
}
|
||||
}
|
||||
function handleOpenEdit(action: any) {
|
||||
visible.value = true;
|
||||
isAdd.value = false;
|
||||
componentSchema.value = null;
|
||||
|
||||
if (action.componentId) {
|
||||
const schema = findSchemaById(pageSchema.schemas, action.componentId);
|
||||
|
||||
componentSchema.value = schema;
|
||||
selectedKeys.value = [action.componentId];
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
state.actionItem.componentId = action.componentId;
|
||||
state.actionItem.methodName = action.methodName;
|
||||
state.actionItem.type = action.type;
|
||||
state.actionItem.args = action.args;
|
||||
});
|
||||
}
|
||||
function handleSave() {
|
||||
if (!state.actionItem.methodName) {
|
||||
alert("请先选择动作方法");
|
||||
return;
|
||||
}
|
||||
emit(isAdd.value ? "add" : "edit", deepClone(toRaw(state.actionItem)));
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
visible.value = false;
|
||||
selectedKeys.value = [];
|
||||
// 清空缓存数据
|
||||
state.cacheData = {};
|
||||
}
|
||||
|
||||
function toggleMethod(type: string) {
|
||||
state.actionItem.componentId = null;
|
||||
state.actionItem.type = type;
|
||||
componentSchema.value = null;
|
||||
state.actionItem.methodName = null;
|
||||
|
||||
selectedKeys.value = [];
|
||||
if (methodOptions.value?.length) {
|
||||
handleCheckedMethod(methodOptions.value[0].value);
|
||||
}
|
||||
}
|
||||
function handleNodeClick(e: any) {
|
||||
if (state.actionItem.args) {
|
||||
// 存在参数配置,缓存参数配置数据
|
||||
state.cacheData[state.actionItem.componentId + state.actionItem.methodName] = state.actionItem.args;
|
||||
}
|
||||
|
||||
state.actionItem.componentId = e.id;
|
||||
state.actionItem.type = "component";
|
||||
state.actionItem.methodName = null;
|
||||
componentSchema.value = e.componentSchema;
|
||||
|
||||
if (methodOptions.value?.length) {
|
||||
handleCheckedMethod(methodOptions.value[0].value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCheckedMethod(value: string) {
|
||||
// activeTab.value = '脚本编辑'
|
||||
state.actionItem.methodName = value;
|
||||
|
||||
// 获取缓存参数配置
|
||||
state.actionItem.args = state.cacheData[state.actionItem.componentId + state.actionItem.methodName];
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleOpen,
|
||||
handleOpenEdit
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="epic-attribute-view">
|
||||
<div v-for="item in props.actionArgsConfigs" :key="item.field + item.type">
|
||||
<div v-show="isShow(item)" class="epic-attr-item" :class="item.layout">
|
||||
<div class="epic-attr-label" :title="item.label">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
<div class="epic-attr-input">
|
||||
<ENode
|
||||
:componentSchema="{
|
||||
...item,
|
||||
componentProps: { ...item.componentProps, input: false, field: undefined, hidden: false },
|
||||
show: true,
|
||||
noFormItem: true
|
||||
}"
|
||||
:model-value="valueRef[item.field!]"
|
||||
@update:model-value="handleSetValue($event, item.field!)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ENode from "../../../components/node";
|
||||
import { ComponentSchema } from "../../../types/epic-designer";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | null | undefined;
|
||||
actionArgsConfigs: ComponentSchema[];
|
||||
}>();
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const valueRef = computed<any>(() => {
|
||||
if (props.modelValue) {
|
||||
return JSON.parse(props.modelValue);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
function isShow(item: ComponentSchema) {
|
||||
// show属性为boolean类型则直接返回
|
||||
if (typeof item.show === "boolean") {
|
||||
return item.show;
|
||||
}
|
||||
|
||||
// show属性为function类型则执行
|
||||
if (typeof item.show === "function") {
|
||||
return item.show?.({ values: valueRef.value! });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性值
|
||||
*/
|
||||
function handleSetValue(value: any, field: string) {
|
||||
const values: any = [...JSON.parse(props.modelValue ?? "[]")];
|
||||
values[field] = value;
|
||||
emits("update:modelValue", JSON.stringify(values));
|
||||
// 将修改过的组件属性推入撤销操作的栈中
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="pb-2">自定义函数编辑</div>
|
||||
<MonacoEditor
|
||||
ref="monacoEditorRef"
|
||||
autoToggleTheme
|
||||
v-model="pageSchema.script"
|
||||
class="epic-editor flex-1"
|
||||
:config="MonacoEditorConfig"
|
||||
language="javascript"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { ref, inject } from "vue";
|
||||
import { PageSchema } from "../../../types/epic-designer";
|
||||
const MonacoEditor = pluginManager.getComponent("monacoEditor");
|
||||
const MonacoEditorConfig = {
|
||||
theme: "vs-light",
|
||||
selectOnLineNumbers: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
const monacoEditorRef = ref<any>(null);
|
||||
const pageSchema = inject("pageSchema") as PageSchema;
|
||||
</script>
|
||||
19
src/components/EpicDesigner/extensions/EColEditor/index.less
Normal file
19
src/components/EpicDesigner/extensions/EColEditor/index.less
Normal file
@@ -0,0 +1,19 @@
|
||||
.EColEditor-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 6px;
|
||||
.epic-del-btn {
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #ff2222;
|
||||
}
|
||||
}
|
||||
}
|
||||
.add-btn {
|
||||
margin-top: 6px;
|
||||
color: var(--epic-primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
66
src/components/EpicDesigner/extensions/EColEditor/index.vue
Normal file
66
src/components/EpicDesigner/extensions/EColEditor/index.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in colList" :key="index" class="EColEditor-item">
|
||||
<Number
|
||||
v-model:value="item.componentProps.span"
|
||||
v-model="item.componentProps.span"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="24"
|
||||
/>
|
||||
<div v-if="colList.length > 1" class="epic-del-btn">
|
||||
<span @click="handleDelete(index)">
|
||||
<EIcon name="icon-shanchu1" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-btn" @click="handleAdd">添加</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { pluginManager, getUUID } from "@/components/EpicDesigner/utils";
|
||||
import { ComponentSchema } from "../../types/epic-designer";
|
||||
import { computed, PropType } from "vue";
|
||||
import EIcon from "../../components/icon";
|
||||
|
||||
const Number = pluginManager.getComponent("number");
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<ComponentSchema[]>,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const colList = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(e) {
|
||||
emit("update:modelValue", e);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增栅格Col
|
||||
*/
|
||||
function handleAdd() {
|
||||
const colItem = {
|
||||
type: "col",
|
||||
children: [],
|
||||
componentProps: {
|
||||
span: 12
|
||||
},
|
||||
id: getUUID()
|
||||
};
|
||||
colList.value.push(colItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除栅格Col
|
||||
* @param index
|
||||
*/
|
||||
function handleDelete(index: number) {
|
||||
colList.value = colList.value.filter((item, i) => index !== i);
|
||||
}
|
||||
</script>
|
||||
60
src/components/EpicDesigner/extensions/EInputSize/index.vue
Normal file
60
src/components/EpicDesigner/extensions/EInputSize/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<Input class="epic-input-size" v-model="size" v-model:value="size" type="number" min="0" placeholder="请输入">
|
||||
<template #suffix>
|
||||
<Select v-model:value="unit" v-model="unit" style="width: 68px" :options="unitArray" />
|
||||
</template>
|
||||
</Input>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { ref, watch, nextTick } from "vue";
|
||||
const Input = pluginManager.getComponent("input");
|
||||
const Select = pluginManager.getComponent("select");
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const size = ref<string | null>(null);
|
||||
const unit = ref("px");
|
||||
const unitArray = [
|
||||
{ label: "px", value: "px" },
|
||||
{ label: "%", value: "%" },
|
||||
{ label: "vw", value: "vw" },
|
||||
{ label: "vh", value: "vh" },
|
||||
{ label: "rem", value: "rem" },
|
||||
{ label: "em", value: "em" },
|
||||
{ label: "pt", value: "pt" }
|
||||
];
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
e => {
|
||||
const num = parseFloat(e);
|
||||
// 传入值为空或不正常时
|
||||
if (isNaN(num)) {
|
||||
size.value = null;
|
||||
return false;
|
||||
}
|
||||
const regex = /^(\d+(\.\d+)?)(px|%|vw|vh|rem|em|pt){1}$/;
|
||||
const match = e.trim().match(regex);
|
||||
size.value = match?.[1] ?? null;
|
||||
unit.value = match?.[3] ?? "";
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => unit.value + size.value,
|
||||
() => {
|
||||
handleUpdate();
|
||||
}
|
||||
);
|
||||
|
||||
function handleUpdate() {
|
||||
nextTick(() => emit("update:modelValue", size.value ? size.value + unit.value : undefined));
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="py-4 my-2 text-center text-gray-400 bg-white" v-show="!modelValueComputed?.length">暂无选项</div>
|
||||
<!-- <div>
|
||||
<div :class="tree ? 'grid-cols-[16px_auto_auto_16px_16px]' : 'grid-cols-[16px_auto_auto_16px]'"
|
||||
class="option-item grid gap-2 items-center mb-2">
|
||||
<span></span>
|
||||
<div class="text-center">label</div>
|
||||
<div class="text-center">value</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<KOptionsCol v-model="modelValueComputed" />
|
||||
<Button @click="handleAdd">添加选项</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import KOptionsCol from "./optionsCol.vue";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { provide, computed } from "vue";
|
||||
const Button = pluginManager.getComponent("button");
|
||||
const props = defineProps<{
|
||||
tree?: boolean;
|
||||
modelValue: Option[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const modelValueComputed = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
});
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
value: string;
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
provide("tree", props.tree);
|
||||
|
||||
/**
|
||||
* 添加选项
|
||||
*/
|
||||
function handleAdd() {
|
||||
const option: Option = {
|
||||
label: "",
|
||||
value: ""
|
||||
};
|
||||
modelValueComputed.value?.push(option);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<draggable
|
||||
v-model="modelValueComputed"
|
||||
item-key="id"
|
||||
:component-data="{
|
||||
type: 'transition-group'
|
||||
}"
|
||||
group="option-list"
|
||||
handle=".handle"
|
||||
:animation="200"
|
||||
>
|
||||
<template #item="{ element: option, index }">
|
||||
<div>
|
||||
<div
|
||||
:class="tree ? 'grid-cols-[16px_auto_auto_16px_16px]' : 'grid-cols-[16px_auto_auto_16px]'"
|
||||
class="option-item grid gap-2 items-center mb-2"
|
||||
>
|
||||
<EIcon class="mr-2 text-lg cursor-move handle" name="icon-tuozhuai" />
|
||||
<Input v-model="option.label" v-model:value="option.label" placeholder="label"></Input>
|
||||
<Input v-model="option.value" v-model:value="option.value" placeholder="value"></Input>
|
||||
<EIcon v-if="tree" class="text-lg" name="icon-tianjia1" @click="handleAddChildren(option)" />
|
||||
<EIcon class="text-lg hover:text-red cursor-pointer" name="icon-shanchu1" @click="handleRemove(index)" />
|
||||
</div>
|
||||
<div class="pl-4" v-if="option.children">
|
||||
<KOptionsCol v-model="option.children" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import draggable from "vuedraggable";
|
||||
import { pluginManager } from "@/components/EpicDesigner/utils";
|
||||
import { inject, computed } from "vue";
|
||||
import EIcon from "../../components/icon";
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
value: string;
|
||||
children?: Option[];
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: "KOptionsCol"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Option[];
|
||||
}>();
|
||||
const Input = pluginManager.getComponent("input");
|
||||
const tree = inject("tree", false);
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const modelValueComputed = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 添加选项子选项
|
||||
*/
|
||||
function handleAddChildren(option: Option) {
|
||||
const childrenOption: Option = {
|
||||
label: "",
|
||||
value: ""
|
||||
};
|
||||
|
||||
if (option.children) {
|
||||
option.children.push(childrenOption);
|
||||
} else {
|
||||
option.children = [childrenOption];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除选项
|
||||
* @param index
|
||||
*/
|
||||
function handleRemove(index: number) {
|
||||
modelValueComputed.value?.splice(index, 1);
|
||||
}
|
||||
</script>
|
||||
159
src/components/EpicDesigner/extensions/ERuleEditor/ERuleItem.vue
Normal file
159
src/components/EpicDesigner/extensions/ERuleEditor/ERuleItem.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div
|
||||
class="rule-item-main bg-white m-t-2 p-2 rounded border border-solid border-gray-200 hover:border-primary transition-all relative"
|
||||
>
|
||||
<template v-for="(componentSchema, index) in ruleItemSchemas" :key="index">
|
||||
<div v-if="componentSchema.show ? componentSchema.show() : true" class="flex m-t-2 first:m-0">
|
||||
<div class="epic-attr-label" title="校验时机">
|
||||
{{ componentSchema.label }}
|
||||
</div>
|
||||
<div class="epic-attr-input">
|
||||
<ENode
|
||||
v-model="modelRule[componentSchema.model]"
|
||||
:componentSchema="{ ...componentSchema, noFormItem: true }"
|
||||
@change="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
class="rule-btn-delete absolute top-0 right-0 transition-all w-24px h-24px cursor-pointer rounded-bl-2 flex justify-center items-center color-white"
|
||||
@click="handleDelete"
|
||||
>
|
||||
<EIcon name="icon-shanchu1" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { FormItemRule } from "./types";
|
||||
import { computed, inject } from "vue";
|
||||
import { PageManager } from "@/components/EpicDesigner/utils";
|
||||
import ENode from "../../components/node/index";
|
||||
import EIcon from "../../components/icon";
|
||||
import { typeOptions, triggerOptions, lenTypeOptions } from "./data";
|
||||
const emit = defineEmits(["change", "delete", "update:rule"]);
|
||||
const props = defineProps<{
|
||||
rule: FormItemRule;
|
||||
}>();
|
||||
|
||||
const modelRule = computed({
|
||||
get() {
|
||||
return props.rule;
|
||||
},
|
||||
set(value) {
|
||||
emit("update:rule", value);
|
||||
}
|
||||
});
|
||||
|
||||
const pageManager = inject("pageManager", {}) as PageManager;
|
||||
|
||||
const methodOptions = computed(() => {
|
||||
// 使用对象值迭代,再通过Object.keys获取对应键名
|
||||
return Object.values(pageManager.funcs.value)
|
||||
.filter(value => typeof value === "function")
|
||||
.map((key, index) => {
|
||||
// 获取对应索引的键名作为label和value
|
||||
console.log(key);
|
||||
const label = Object.keys(pageManager.funcs.value)[index];
|
||||
return { label, value: label };
|
||||
});
|
||||
});
|
||||
const ruleItemSchemas = [
|
||||
{
|
||||
type: "select",
|
||||
label: "校验时机",
|
||||
model: "trigger",
|
||||
componentProps: { options: triggerOptions, placeholder: "校验时机", multiple: true, mode: "multiple" }
|
||||
},
|
||||
{
|
||||
type: "switch",
|
||||
label: "自定义规则",
|
||||
model: "isValidator"
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
label: "校验函数",
|
||||
model: "validator",
|
||||
show() {
|
||||
return Boolean(modelRule.value.isValidator);
|
||||
},
|
||||
componentProps: { options: methodOptions.value, placeholder: "校验函数" }
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
label: "类型",
|
||||
model: "type",
|
||||
show() {
|
||||
return !modelRule.value.isValidator;
|
||||
},
|
||||
componentProps: { options: typeOptions, placeholder: "类型" }
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
label: "正则校验",
|
||||
model: "pattern",
|
||||
show() {
|
||||
return !modelRule.value.isValidator;
|
||||
},
|
||||
componentProps: { placeholder: "正则校验" }
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
label: "字段长度",
|
||||
model: "len",
|
||||
show() {
|
||||
return lenTypeOptions.includes(modelRule.value.type ?? "");
|
||||
},
|
||||
componentProps: { min: 0, placeholder: "字段长度" }
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
label: "最小长度",
|
||||
model: "min",
|
||||
show() {
|
||||
return lenTypeOptions.includes(modelRule.value.type ?? "");
|
||||
},
|
||||
componentProps: { min: 0, placeholder: "最小长度" }
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
label: "最大长度",
|
||||
model: "max",
|
||||
show() {
|
||||
return lenTypeOptions.includes(modelRule.value.type ?? "");
|
||||
},
|
||||
componentProps: { min: 0, placeholder: "最大长度" }
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
label: "校验信息",
|
||||
model: "message",
|
||||
componentProps: { placeholder: "校验信息" }
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 更新校验规则
|
||||
*/
|
||||
function handleUpdate() {
|
||||
const v = modelRule.value;
|
||||
if (v.isValidator) {
|
||||
delete v.type;
|
||||
delete v.pattern;
|
||||
delete v.len;
|
||||
delete v.min;
|
||||
delete v.max;
|
||||
} else {
|
||||
delete v.validator;
|
||||
}
|
||||
// emit('update:rule', v)
|
||||
emit("change", v);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除校验规则
|
||||
*/
|
||||
function handleDelete() {
|
||||
emit("delete");
|
||||
}
|
||||
</script>
|
||||
24
src/components/EpicDesigner/extensions/ERuleEditor/data.ts
Normal file
24
src/components/EpicDesigner/extensions/ERuleEditor/data.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const typeOptions = [
|
||||
{ label: "string", value: "string" },
|
||||
{ label: "number", value: "number" },
|
||||
{ label: "boolean", value: "boolean" },
|
||||
{ label: "method", value: "method" },
|
||||
{ label: "regexp", value: "regexp" },
|
||||
{ label: "integer", value: "integer" },
|
||||
{ label: "float", value: "float" },
|
||||
{ label: "array", value: "array" },
|
||||
{ label: "object", value: "object" },
|
||||
// { label: 'enum', value: 'enum' },
|
||||
{ label: "date", value: "date" },
|
||||
{ label: "url", value: "url" },
|
||||
{ label: "hex", value: "hex" },
|
||||
{ label: "email", value: "email" },
|
||||
{ label: "any", value: "any" }
|
||||
];
|
||||
|
||||
export const triggerOptions = [
|
||||
{ label: "change", value: "change" },
|
||||
{ label: "blur", value: "blur" }
|
||||
];
|
||||
|
||||
export const lenTypeOptions = ["string", "number", "url", "array", "email"];
|
||||
@@ -0,0 +1,16 @@
|
||||
.rule-item-main {
|
||||
background-color: var(--epic-view-color);
|
||||
border-color: var(--epic-border-color);
|
||||
&:hover {
|
||||
border-color: var(--epic-primary-color);
|
||||
}
|
||||
.rule-btn-delete {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
&:hover > .rule-btn-delete {
|
||||
pointer-events: all;
|
||||
background-color: var(--epic-primary-color);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
159
src/components/EpicDesigner/extensions/ERuleEditor/index.vue
Normal file
159
src/components/EpicDesigner/extensions/ERuleEditor/index.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="rule-item-main m-t-2 p-2 rounded border border-solid transition-all relative">
|
||||
<template v-for="(componentSchema, index) in requiredRuleSchemas" :key="index">
|
||||
<div v-if="componentSchema.show ? componentSchema.show() : true" class="flex m-t-2 first:m-0">
|
||||
<div class="epic-attr-label">
|
||||
{{ componentSchema.label }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<ENode
|
||||
v-model="requiredRule[componentSchema.model]"
|
||||
:componentSchema="{ ...componentSchema, noFormItem: true }"
|
||||
@change="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<ERuleItem
|
||||
v-for="(item, index) in rules"
|
||||
:key="index"
|
||||
v-model:rule="rules[index]"
|
||||
@delete="handleDelete(index)"
|
||||
@change="handleUpdate"
|
||||
/>
|
||||
<Button class="m-t-2" @click="handleAdd"> 添加规则 </Button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { pluginManager, deepClone } from "@/components/EpicDesigner/utils";
|
||||
import { ref, watch, PropType } from "vue";
|
||||
import { FormItemRule } from "./types";
|
||||
import ERuleItem from "./ERuleItem.vue";
|
||||
import ENode from "../../components/node/index";
|
||||
import { triggerOptions, typeOptions } from "./data";
|
||||
const Button = pluginManager.getComponent("button");
|
||||
const props = defineProps({
|
||||
ruleType: {
|
||||
type: String,
|
||||
default: "string"
|
||||
},
|
||||
modelValue: {
|
||||
type: Array as PropType<FormItemRule[] | undefined>,
|
||||
default: undefined
|
||||
}
|
||||
});
|
||||
|
||||
const requiredRule = ref<FormItemRule>({
|
||||
required: false,
|
||||
message: "必填项",
|
||||
type: props.ruleType,
|
||||
trigger: ["change"]
|
||||
});
|
||||
|
||||
const requiredRuleSchemas = [
|
||||
{
|
||||
type: "switch",
|
||||
label: "必填项",
|
||||
model: "required"
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
label: "校验时机",
|
||||
model: "trigger",
|
||||
show() {
|
||||
return Boolean(requiredRule.value.required);
|
||||
},
|
||||
componentProps: {
|
||||
options: triggerOptions,
|
||||
placeholder: "校验时机",
|
||||
multiple: true,
|
||||
mode: "multiple"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
label: "类型",
|
||||
model: "type",
|
||||
show() {
|
||||
return Boolean(requiredRule.value.required);
|
||||
},
|
||||
componentProps: { options: typeOptions, placeholder: "类型" }
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
label: "校验信息",
|
||||
model: "message",
|
||||
show() {
|
||||
return Boolean(requiredRule.value.required);
|
||||
},
|
||||
componentProps: { placeholder: "校验信息" }
|
||||
}
|
||||
];
|
||||
|
||||
const rules = ref<FormItemRule[]>([]);
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
e => {
|
||||
if (e) {
|
||||
if (!e) return;
|
||||
rules.value = [];
|
||||
e.forEach(item => {
|
||||
// 必填项单独存储
|
||||
if (typeof item.required !== "undefined") {
|
||||
requiredRule.value = item;
|
||||
} else {
|
||||
rules.value.push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 新增检验规则
|
||||
*/
|
||||
function handleAdd() {
|
||||
rules.value.push({
|
||||
message: "",
|
||||
type: props.ruleType,
|
||||
trigger: ["change"]
|
||||
});
|
||||
handleUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新校验规则
|
||||
*/
|
||||
function handleUpdate() {
|
||||
// 存在必填项时,合并其他规则
|
||||
if (requiredRule.value.required) {
|
||||
emit("update:modelValue", deepClone([...rules.value, requiredRule.value]));
|
||||
return;
|
||||
}
|
||||
|
||||
// 存在其他规则
|
||||
if (rules.value.length) {
|
||||
emit("update:modelValue", deepClone(rules.value));
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有任何校验规则
|
||||
emit("update:modelValue", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过下标删除校验规则项
|
||||
* @param index
|
||||
*/
|
||||
function handleDelete(index: number) {
|
||||
rules.value.splice(index, 1);
|
||||
handleUpdate();
|
||||
}
|
||||
</script>
|
||||
17
src/components/EpicDesigner/extensions/ERuleEditor/types.ts
Normal file
17
src/components/EpicDesigner/extensions/ERuleEditor/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface RuleItem {
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
pattern?: RegExp | string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
len?: number;
|
||||
enum?: Array<string | number | boolean | null | undefined>;
|
||||
whitespace?: boolean;
|
||||
validator?: string;
|
||||
isValidator?: boolean;
|
||||
message?: string | ((a?: string) => string);
|
||||
}
|
||||
export interface FormItemRule extends RuleItem {
|
||||
trigger?: string | string[];
|
||||
[model: string]: any;
|
||||
}
|
||||
31
src/components/EpicDesigner/extensions/MonacoEditor/index.ts
Normal file
31
src/components/EpicDesigner/extensions/MonacoEditor/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { type ComponentConfigModel } from "@/components/EpicDesigner/utils";
|
||||
export default {
|
||||
component: async () => await import("./index.vue"),
|
||||
defaultSchema: {
|
||||
label: "代码编辑器",
|
||||
type: "monacoEditor",
|
||||
field: "monacoEditor",
|
||||
icon: "epic-icon-write",
|
||||
input: true
|
||||
},
|
||||
config: {
|
||||
attribute: [
|
||||
{
|
||||
label: "字段名",
|
||||
type: "input",
|
||||
field: "field"
|
||||
},
|
||||
{
|
||||
label: "标题",
|
||||
type: "input",
|
||||
field: "label"
|
||||
},
|
||||
{
|
||||
label: "默认值",
|
||||
type: "monacoEditor",
|
||||
field: "componentProps.defaultValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
bindModel: "model-value"
|
||||
} as ComponentConfigModel;
|
||||
145
src/components/EpicDesigner/extensions/MonacoEditor/index.vue
Normal file
145
src/components/EpicDesigner/extensions/MonacoEditor/index.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div ref="editContainer" class="epic-code-editor" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick, onMounted } from "vue";
|
||||
import * as monaco from "monaco-editor";
|
||||
import type { editor } from "monaco-editor";
|
||||
import { useTheme } from "@/components/EpicDesigner/utils";
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
language?: string;
|
||||
readOnly?: boolean;
|
||||
valueFormat?: string;
|
||||
modelValue?: any;
|
||||
config?: editor.IStandaloneEditorConstructionOptions;
|
||||
lineNumbers?: "on" | "off";
|
||||
autoToggleTheme?: boolean;
|
||||
theme?: "vs-light" | "vs-dark" | "hc-black";
|
||||
}>(),
|
||||
{
|
||||
language: "json",
|
||||
readOnly: false,
|
||||
valueFormat: "string",
|
||||
lineNumbers: "on",
|
||||
theme: "vs-light",
|
||||
config: () => ({
|
||||
selectOnLineNumbers: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const editContainer = ref<HTMLElement | null>(null);
|
||||
|
||||
let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null;
|
||||
const { isDark } = useTheme();
|
||||
|
||||
function handleToggleTheme() {
|
||||
if (isDark.value) {
|
||||
monaco.editor.setTheme("vs-dark");
|
||||
} else {
|
||||
monaco.editor.setTheme("vs-light");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文本
|
||||
* @param text
|
||||
*/
|
||||
function setValue(text: string) {
|
||||
monacoEditor?.setValue(text || "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 光标处插入文本
|
||||
* @param text
|
||||
*/
|
||||
function insertText(text: string) {
|
||||
// 获取光标位置
|
||||
const position = monacoEditor?.getPosition();
|
||||
// 未获取到光标位置信息
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
// 插入
|
||||
monacoEditor?.executeEdits("", [
|
||||
{
|
||||
range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
||||
text
|
||||
}
|
||||
]);
|
||||
// 设置新的光标位置
|
||||
monacoEditor?.setPosition({ ...position, column: position.column + text.length });
|
||||
// 重新聚焦
|
||||
monacoEditor?.focus();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
monacoEditor = monaco.editor.create(editContainer.value as HTMLElement, {
|
||||
value: getValue(),
|
||||
...props.config,
|
||||
language: props.language,
|
||||
readOnly: props.readOnly,
|
||||
lineNumbers: props.lineNumbers,
|
||||
theme: props.theme,
|
||||
automaticLayout: true
|
||||
});
|
||||
|
||||
// 自动切换主题
|
||||
if (props.autoToggleTheme) {
|
||||
watch(
|
||||
() => isDark.value,
|
||||
() => {
|
||||
nextTick(() => handleToggleTheme());
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 获取值
|
||||
function getValue() {
|
||||
// valueFormat 为json 格式,需要转换处理
|
||||
if (props.valueFormat === "json") {
|
||||
if (props.modelValue) {
|
||||
return JSON.stringify(props.modelValue, null, 2);
|
||||
}
|
||||
}
|
||||
return props.modelValue ?? "";
|
||||
}
|
||||
|
||||
// 监听值变化
|
||||
monacoEditor.onDidChangeModelContent(() => {
|
||||
const currenValue = monacoEditor?.getValue();
|
||||
|
||||
// valueFormat 为json 格式,需要转换处理
|
||||
if (props.valueFormat === "json" && currenValue) {
|
||||
emit("update:modelValue", JSON.parse(currenValue));
|
||||
return;
|
||||
}
|
||||
|
||||
emit("update:modelValue", currenValue ?? "");
|
||||
});
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
setValue,
|
||||
insertText
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.epic-code-editor {
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
|
||||
:deep(.monaco-editor) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
23
src/components/EpicDesigner/extensions/Page/index.ts
Normal file
23
src/components/EpicDesigner/extensions/Page/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { type ComponentConfigModel } from "@/components/EpicDesigner/utils";
|
||||
|
||||
export default {
|
||||
component: async () => await import("./index.vue"),
|
||||
defaultSchema: {
|
||||
label: "页面",
|
||||
type: "page",
|
||||
componentProps: {},
|
||||
children: []
|
||||
},
|
||||
config: {
|
||||
attribute: [
|
||||
{
|
||||
label: "页面名称",
|
||||
type: "input",
|
||||
componentProps: {
|
||||
placeholder: "请输入"
|
||||
},
|
||||
field: "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
} as ComponentConfigModel;
|
||||
19
src/components/EpicDesigner/extensions/Page/index.vue
Normal file
19
src/components/EpicDesigner/extensions/Page/index.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="h-full box-border wh-full!">
|
||||
<slot name="edit-node">
|
||||
<slot v-for="item in children" name="node" :componentSchema="item" />
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { ComponentSchema } from "@/components/EpicDesigner/types/epic-designer";
|
||||
|
||||
const props = defineProps<{
|
||||
componentSchema: ComponentSchema;
|
||||
}>();
|
||||
|
||||
const children = computed(() => {
|
||||
return props.componentSchema.children ?? [];
|
||||
});
|
||||
</script>
|
||||
62
src/components/EpicDesigner/extensions/index.ts
Normal file
62
src/components/EpicDesigner/extensions/index.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { type PluginManager } from "@/components/EpicDesigner/utils";
|
||||
import MonacoEditor from "./MonacoEditor";
|
||||
import Page from "./Page";
|
||||
|
||||
export function setupComponent(pluginManager: PluginManager): void {
|
||||
pluginManager.component("EInputSize", async () => await import("./EInputSize/index.vue"));
|
||||
pluginManager.component("EColEditor", async () => await import("./EColEditor/index.vue"));
|
||||
pluginManager.component("EActionEditor", async () => await import("./EActionEditor/index.vue"));
|
||||
pluginManager.component("ERuleEditor", async () => await import("./ERuleEditor/index.vue"));
|
||||
pluginManager.component("EOptionsEditor", async () => await import("./EOptionsEditor/index.vue"));
|
||||
pluginManager.component("ENode", async () => await import("../components/node"));
|
||||
|
||||
// 左侧菜单初始化
|
||||
pluginManager.registerActivitybar({
|
||||
id: "component_view",
|
||||
title: "组件",
|
||||
icon: "epic-icon-mokuai_1",
|
||||
component: async () => await import("../components/designer/src/modules/componentView/index.vue")
|
||||
});
|
||||
|
||||
pluginManager.registerActivitybar({
|
||||
id: "sound_code_view",
|
||||
title: "源码",
|
||||
icon: "epic-icon-daima1",
|
||||
component: async () => await import("../components/designer/src/modules/soundCode/index.vue")
|
||||
});
|
||||
|
||||
// pluginManager.registerActivitybar({
|
||||
// id: "outline_view",
|
||||
// title: "大纲",
|
||||
// icon: "epic-icon-juxingkaobei",
|
||||
// component: async () => await import("../components/designer/src/modules/outline/outline.vue")
|
||||
// });
|
||||
|
||||
pluginManager.registerRightSidebar({
|
||||
id: "attribute_view",
|
||||
title: "属性",
|
||||
component: async () => await import("../components/designer/src/modules/attributeView/attributeView.vue")
|
||||
});
|
||||
|
||||
pluginManager.registerRightSidebar({
|
||||
id: "style_view",
|
||||
title: "样式",
|
||||
component: async () => await import("../components/designer/src/modules/attributeView/styleView.vue")
|
||||
});
|
||||
// pluginManager.registerRightSidebar({
|
||||
// id: "event_view",
|
||||
// title: "事件",
|
||||
// component: async () => await import("../components/designer/src/modules/attributeView/eventView.vue")
|
||||
// });
|
||||
// pluginManager.registerRightSidebar({
|
||||
// id: "data_view",
|
||||
// title: "数据库",
|
||||
// component: async () => await import("../components/designer/src/modules/attributeView/dataView.vue")
|
||||
// });
|
||||
|
||||
const componentArray = [MonacoEditor, Page];
|
||||
console.log(Page, "=============>");
|
||||
componentArray.forEach(item => {
|
||||
pluginManager.registerComponent(item);
|
||||
});
|
||||
}
|
||||
12
src/components/EpicDesigner/index.less
Normal file
12
src/components/EpicDesigner/index.less
Normal file
@@ -0,0 +1,12 @@
|
||||
@import "./static/icons/iconfont.css";
|
||||
@import "./theme/var.less";
|
||||
@import "./components/tree/src/index.less";
|
||||
@import "./components/designer/src/index.less";
|
||||
@import "./extensions/EActionEditor/index.less";
|
||||
@import "./extensions/ERuleEditor/index.less";
|
||||
@import "./extensions/EColEditor/index.less";
|
||||
@import "./components/asyncLoader/index.less";
|
||||
|
||||
// 基础组件适配样式
|
||||
|
||||
@import "./ui/index.less";
|
||||
27
src/components/EpicDesigner/index.ts
Normal file
27
src/components/EpicDesigner/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import "virtual:uno.css";
|
||||
import EBuilder from "./components/builder/index";
|
||||
import EDesigner from "./components/designer/index";
|
||||
import ENode from "./components/node/index";
|
||||
//type PluginManager, type PageManager, usePageManager
|
||||
import { pluginManager } from "./utils";
|
||||
import { setupComponent } from "./extensions";
|
||||
import "./index.less";
|
||||
// export type * from "./types/epic-designer";
|
||||
// 初始化设计器
|
||||
setupComponent(pluginManager);
|
||||
|
||||
// const components = [EBuilder, EDesigner]
|
||||
|
||||
// 注册全局组件
|
||||
// const EDesignr = {
|
||||
// install (app: App) {
|
||||
// components.forEach((comp) => {
|
||||
// app.component(comp.__name ?? '', comp)
|
||||
// })
|
||||
// },
|
||||
// pluginManager,
|
||||
// usePageManager
|
||||
// }
|
||||
//, pluginManager, usePageManager, type PluginManager, type PageManager
|
||||
export { EBuilder, EDesigner, ENode };
|
||||
// export default EDesignr;
|
||||
3
src/components/EpicDesigner/index1.ts
Normal file
3
src/components/EpicDesigner/index1.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import EDesigner from "../EpicDesigner/components/designer";
|
||||
|
||||
export default EDesigner;
|
||||
450
src/components/EpicDesigner/static/icons/demo.css
Normal file
450
src/components/EpicDesigner/static/icons/demo.css
Normal file
@@ -0,0 +1,450 @@
|
||||
/* Logo 字体 */
|
||||
@font-face {
|
||||
font-family: "iconfont logo";
|
||||
src: url("https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834");
|
||||
src: url("https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix") format("embedded-opentype"),
|
||||
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834") format("woff"),
|
||||
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834") format("truetype"),
|
||||
url("https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont") format("svg");
|
||||
}
|
||||
.logo {
|
||||
font-family: "iconfont logo";
|
||||
font-size: 160px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.nav-tabs {
|
||||
position: relative;
|
||||
}
|
||||
.nav-tabs .nav-more {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
color: #666666;
|
||||
}
|
||||
#tabs {
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
#tabs li {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
margin-bottom: -1px;
|
||||
font-size: 16px;
|
||||
line-height: 40px;
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
#tabs .active {
|
||||
color: #222222;
|
||||
border-bottom-color: #ff0000;
|
||||
}
|
||||
.tab-container .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 页面布局 */
|
||||
.main {
|
||||
width: 960px;
|
||||
padding: 30px 100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.main .logo {
|
||||
height: 110px;
|
||||
margin-top: -50px;
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
*zoom: 1;
|
||||
}
|
||||
.main .logo a {
|
||||
font-size: 160px;
|
||||
color: #333333;
|
||||
}
|
||||
.helps {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.helps pre {
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
overflow: auto;
|
||||
background-color: #fffdef;
|
||||
border: solid 1px #e7e1cd;
|
||||
}
|
||||
.icon_lists {
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
.icon_lists li {
|
||||
width: 100px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
cursor: default;
|
||||
}
|
||||
.icon_lists li .code-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
.icon_lists .icon {
|
||||
display: block;
|
||||
height: 100px;
|
||||
margin: 10px auto;
|
||||
font-size: 42px;
|
||||
line-height: 100px;
|
||||
color: #333333;
|
||||
transition: font-size 0.25s linear, width 0.25s linear;
|
||||
}
|
||||
.icon_lists .icon:hover {
|
||||
font-size: 100px;
|
||||
}
|
||||
.icon_lists .svg-icon {
|
||||
/* 通过设置 font-size 来改变图标大小 */
|
||||
width: 1em;
|
||||
|
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||
normalize.css 中也包含这行 */
|
||||
overflow: hidden;
|
||||
|
||||
/* 图标和文字相邻时,垂直对齐 */
|
||||
vertical-align: -0.15em;
|
||||
|
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||
fill: currentColor;
|
||||
}
|
||||
.icon_lists li .name,
|
||||
.icon_lists li .code-name {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* markdown 样式 */
|
||||
.markdown {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: #666666;
|
||||
}
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
.markdown img {
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
margin: 1.6em 0 0.6em;
|
||||
clear: both;
|
||||
font-weight: 500;
|
||||
color: #404040;
|
||||
}
|
||||
.markdown h1 {
|
||||
margin-bottom: 24px;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
color: #404040;
|
||||
}
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
background: #e9e9e9;
|
||||
border: 0;
|
||||
}
|
||||
.markdown p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
.markdown > p,
|
||||
.markdown > blockquote,
|
||||
.markdown > .highlight,
|
||||
.markdown > ol,
|
||||
.markdown > ul {
|
||||
width: 80%;
|
||||
}
|
||||
.markdown ul > li {
|
||||
list-style: circle;
|
||||
}
|
||||
.markdown > ul li,
|
||||
.markdown blockquote ul > li {
|
||||
padding-left: 4px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.markdown > ul li p,
|
||||
.markdown > ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
.markdown ol > li {
|
||||
list-style: decimal;
|
||||
}
|
||||
.markdown > ol li,
|
||||
.markdown blockquote ol > li {
|
||||
padding-left: 4px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.markdown code {
|
||||
padding: 0 5px;
|
||||
margin: 0 3px;
|
||||
background: #eeeeee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
.markdown > table {
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
empty-cells: show;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #e9e9e9;
|
||||
}
|
||||
.markdown > table th {
|
||||
padding: 8px 16px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
background: #f7f7f7;
|
||||
border: 1px solid #e9e9e9;
|
||||
}
|
||||
.markdown > table td {
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
border: 1px solid #e9e9e9;
|
||||
}
|
||||
.markdown blockquote {
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
font-size: 90%;
|
||||
color: #999999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
}
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
.markdown .anchor {
|
||||
margin-left: 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.markdown .waiting {
|
||||
color: #cccccc;
|
||||
}
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
}
|
||||
.markdown > br,
|
||||
.markdown > p > br {
|
||||
clear: both;
|
||||
}
|
||||
.hljs {
|
||||
display: block;
|
||||
padding: 0.5em;
|
||||
overflow-x: auto;
|
||||
color: #333333;
|
||||
background: white;
|
||||
}
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 代码高亮 */
|
||||
|
||||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
hyphens: none;
|
||||
line-height: 1.5;
|
||||
color: black;
|
||||
text-align: left;
|
||||
text-shadow: 0 1px white;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
tab-size: 4;
|
||||
white-space: pre;
|
||||
background: none;
|
||||
word-spacing: normal;
|
||||
}
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: 0.1em;
|
||||
white-space: normal;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
.token.punctuation {
|
||||
color: #999999;
|
||||
}
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #990055;
|
||||
}
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #669900;
|
||||
}
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
background: hsl(0deg 0% 100% / 50%);
|
||||
}
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #0077aa;
|
||||
}
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #dd4a68;
|
||||
}
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #ee9900;
|
||||
}
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
2021
src/components/EpicDesigner/static/icons/demo_index.html
Normal file
2021
src/components/EpicDesigner/static/icons/demo_index.html
Normal file
File diff suppressed because it is too large
Load Diff
285
src/components/EpicDesigner/static/icons/iconfont.css
Normal file
285
src/components/EpicDesigner/static/icons/iconfont.css
Normal file
@@ -0,0 +1,285 @@
|
||||
@font-face {
|
||||
font-family: iconfont; /* Project id 3853801 */
|
||||
src: url("iconfont.woff2?t=1699374961981") format("woff2"), url("iconfont.woff?t=1699374961981") format("woff"),
|
||||
url("iconfont.ttf?t=1699374961981") format("truetype");
|
||||
}
|
||||
.iconfont {
|
||||
font-family: iconfont !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.epic-icon-fangda::before {
|
||||
content: "\e632";
|
||||
}
|
||||
.epic-icon-shangchuan::before {
|
||||
content: "\e6c7";
|
||||
}
|
||||
.epic-icon-zaixianjiance::before {
|
||||
content: "\e616";
|
||||
}
|
||||
.epic-icon-gaojingguanli::before {
|
||||
content: "\e61c";
|
||||
}
|
||||
.epic-icon-gaojing::before {
|
||||
content: "\e61d";
|
||||
}
|
||||
.epic-icon-dayin::before {
|
||||
content: "\e621";
|
||||
}
|
||||
.epic-icon-qiandaoxinxi::before {
|
||||
content: "\e625";
|
||||
}
|
||||
.epic-icon-shezhi::before {
|
||||
content: "\e626";
|
||||
}
|
||||
.epic-icon-neirongshezhi::before {
|
||||
content: "\e61e";
|
||||
}
|
||||
.epic-icon-duihua::before {
|
||||
content: "\e622";
|
||||
}
|
||||
.epic-icon-dabaoxiazai::before {
|
||||
content: "\e624";
|
||||
}
|
||||
.epic-icon-zhengque::before {
|
||||
content: "\e628";
|
||||
}
|
||||
.epic-icon-chuangkou::before {
|
||||
content: "\e629";
|
||||
}
|
||||
.epic-icon-shezhi1::before {
|
||||
content: "\e62a";
|
||||
}
|
||||
.epic-icon-fuzhi::before {
|
||||
content: "\e62f";
|
||||
}
|
||||
.epic-icon-cunchu::before {
|
||||
content: "\e630";
|
||||
}
|
||||
.epic-icon-wangge::before {
|
||||
content: "\e631";
|
||||
}
|
||||
.epic-icon-bofang::before {
|
||||
content: "\e63a";
|
||||
}
|
||||
.epic-icon-diyinliang::before {
|
||||
content: "\e63c";
|
||||
}
|
||||
.epic-icon-tiaozheng::before {
|
||||
content: "\e63d";
|
||||
}
|
||||
.epic-icon-shangjiantou::before {
|
||||
content: "\e617";
|
||||
}
|
||||
.epic-icon-xiajiantou::before {
|
||||
content: "\e618";
|
||||
}
|
||||
.epic-icon-youjiantou::before {
|
||||
content: "\e619";
|
||||
}
|
||||
.epic-icon-zuojiantou::before {
|
||||
content: "\e61b";
|
||||
}
|
||||
.epic-icon-daliebiao::before {
|
||||
content: "\e600";
|
||||
}
|
||||
.epic-icon-dasuolvetuliebiao::before {
|
||||
content: "\e601";
|
||||
}
|
||||
.epic-icon-liebiao::before {
|
||||
content: "\e602";
|
||||
}
|
||||
.epic-icon-tuwenxiangqing::before {
|
||||
content: "\e603";
|
||||
}
|
||||
.epic-icon-a-pingbantoubu::before {
|
||||
content: "\e656";
|
||||
}
|
||||
.epic-icon-a-shoujitoubu::before {
|
||||
content: "\e657";
|
||||
}
|
||||
.epic-icon-a-diannaotoubu::before {
|
||||
content: "\e655";
|
||||
}
|
||||
.epic-icon-daibanshixiang::before {
|
||||
content: "\ec4e";
|
||||
}
|
||||
.epic-icon-mima::before {
|
||||
content: "\e669";
|
||||
}
|
||||
.epic-icon-baocun::before {
|
||||
content: "\e6a3";
|
||||
}
|
||||
.epic-icon-baocun1::before {
|
||||
content: "\e6a2";
|
||||
}
|
||||
.epic-icon-baocun2::before {
|
||||
content: "\e79e";
|
||||
}
|
||||
.epic-icon-yanse::before {
|
||||
content: "\e678";
|
||||
}
|
||||
.epic-icon-tuozhuai::before {
|
||||
content: "\e614";
|
||||
}
|
||||
.epic-icon-chaxun::before {
|
||||
content: "\e660";
|
||||
}
|
||||
.epic-icon-tianjia::before {
|
||||
content: "\e620";
|
||||
}
|
||||
.epic-icon-tianjia1::before {
|
||||
content: "\e740";
|
||||
}
|
||||
.epic-icon-yulan::before {
|
||||
content: "\e668";
|
||||
}
|
||||
.epic-icon-fanhui2x::before {
|
||||
content: "\e62c";
|
||||
}
|
||||
.epic-icon-chexiao2x::before {
|
||||
content: "\e62e";
|
||||
}
|
||||
.epic-icon-shangchuan1::before {
|
||||
content: "\e62b";
|
||||
}
|
||||
.epic-icon-zhankai::before {
|
||||
content: "\e68b";
|
||||
}
|
||||
.epic-icon-fuzhi3::before {
|
||||
content: "\e643";
|
||||
}
|
||||
.epic-icon-shanchu1::before {
|
||||
content: "\e627";
|
||||
}
|
||||
.epic-icon-circular::before {
|
||||
content: "\e667";
|
||||
}
|
||||
.epic-icon-gengduo::before {
|
||||
content: "\e73a";
|
||||
}
|
||||
.epic-icon-daima1::before {
|
||||
content: "\e761";
|
||||
}
|
||||
.epic-icon-mokuai_1::before {
|
||||
content: "\e63b";
|
||||
}
|
||||
.epic-icon-LC_icon_edit_line_1::before {
|
||||
content: "\e65b";
|
||||
}
|
||||
.epic-icon-chaifen::before {
|
||||
content: "\e653";
|
||||
}
|
||||
.epic-icon-danxuanxuanzhong::before {
|
||||
content: "\e62d";
|
||||
}
|
||||
.epic-icon-ai-code::before {
|
||||
content: "\e606";
|
||||
}
|
||||
.epic-icon-duoxuan1::before {
|
||||
content: "\e66d";
|
||||
}
|
||||
.epic-icon-kaiguan3::before {
|
||||
content: "\e6da";
|
||||
}
|
||||
.epic-icon-biaoge::before {
|
||||
content: "\e63e";
|
||||
}
|
||||
.epic-icon-html::before {
|
||||
content: "\e693";
|
||||
}
|
||||
.epic-icon-menu::before {
|
||||
content: "\e605";
|
||||
}
|
||||
.epic-icon-edit::before {
|
||||
content: "\e607";
|
||||
}
|
||||
.epic-icon-calendar::before {
|
||||
content: "\e60a";
|
||||
}
|
||||
.epic-icon-download::before {
|
||||
content: "\e60b";
|
||||
}
|
||||
.epic-icon-upload::before {
|
||||
content: "\e60c";
|
||||
}
|
||||
.epic-icon-folder::before {
|
||||
content: "\e611";
|
||||
}
|
||||
.epic-icon-write::before {
|
||||
content: "\e612";
|
||||
}
|
||||
.epic-icon-gallery::before {
|
||||
content: "\e613";
|
||||
}
|
||||
.epic-icon-image::before {
|
||||
content: "\e61a";
|
||||
}
|
||||
.epic-icon-time::before {
|
||||
content: "\e633";
|
||||
}
|
||||
.epic-icon-button-add::before {
|
||||
content: "\e637";
|
||||
}
|
||||
.epic-icon-button-remove::before {
|
||||
content: "\e638";
|
||||
}
|
||||
.epic-icon-qiapian::before {
|
||||
content: "\e674";
|
||||
}
|
||||
.epic-icon-zhage::before {
|
||||
content: "\e63f";
|
||||
}
|
||||
.epic-icon-danxuan-cuxiantiao::before {
|
||||
content: "\e676";
|
||||
}
|
||||
.epic-icon-xiala::before {
|
||||
content: "\e640";
|
||||
}
|
||||
.epic-icon-fengexian::before {
|
||||
content: "\ec7f";
|
||||
}
|
||||
.epic-icon-zihao::before {
|
||||
content: "\e623";
|
||||
}
|
||||
.epic-icon-juxingkaobei::before {
|
||||
content: "\e7a5";
|
||||
}
|
||||
.epic-icon-edit1::before {
|
||||
content: "\e72a";
|
||||
}
|
||||
.epic-icon-pingfen_moren::before {
|
||||
content: "\e877";
|
||||
}
|
||||
.epic-icon-edit-::before {
|
||||
content: "\e83b";
|
||||
}
|
||||
.epic-icon-number::before {
|
||||
content: "\e639";
|
||||
}
|
||||
.epic-icon-zu::before {
|
||||
content: "\e615";
|
||||
}
|
||||
.epic-icon-guanlian::before {
|
||||
content: "\e61f";
|
||||
}
|
||||
.epic-icon-tabs::before {
|
||||
content: "\e8cd";
|
||||
}
|
||||
.epic-icon-chexiao::before {
|
||||
content: "\e609";
|
||||
}
|
||||
.epic-icon-quanping::before {
|
||||
content: "\e60d";
|
||||
}
|
||||
.epic-icon-zhongzuo::before {
|
||||
content: "\e60e";
|
||||
}
|
||||
.epic-icon-zujian::before {
|
||||
content: "\e60f";
|
||||
}
|
||||
.epic-icon-bianji2::before {
|
||||
content: "\e610";
|
||||
}
|
||||
66
src/components/EpicDesigner/static/icons/iconfont.js
Normal file
66
src/components/EpicDesigner/static/icons/iconfont.js
Normal file
File diff suppressed because one or more lines are too long
646
src/components/EpicDesigner/static/icons/iconfont.json
Normal file
646
src/components/EpicDesigner/static/icons/iconfont.json
Normal file
@@ -0,0 +1,646 @@
|
||||
{
|
||||
"id": "3853801",
|
||||
"name": "k-designer",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "epic-epic-icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "3278362",
|
||||
"name": "放大",
|
||||
"font_class": "fangda",
|
||||
"unicode": "e632",
|
||||
"unicode_decimal": 58930
|
||||
},
|
||||
{
|
||||
"icon_id": "34047654",
|
||||
"name": "上传",
|
||||
"font_class": "shangchuan",
|
||||
"unicode": "e6c7",
|
||||
"unicode_decimal": 59079
|
||||
},
|
||||
{
|
||||
"icon_id": "3101153",
|
||||
"name": "1在线监测",
|
||||
"font_class": "zaixianjiance",
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "3101155",
|
||||
"name": "1告警管理",
|
||||
"font_class": "gaojingguanli",
|
||||
"unicode": "e61c",
|
||||
"unicode_decimal": 58908
|
||||
},
|
||||
{
|
||||
"icon_id": "3101158",
|
||||
"name": "告警",
|
||||
"font_class": "gaojing",
|
||||
"unicode": "e61d",
|
||||
"unicode_decimal": 58909
|
||||
},
|
||||
{
|
||||
"icon_id": "3278356",
|
||||
"name": "打印",
|
||||
"font_class": "dayin",
|
||||
"unicode": "e621",
|
||||
"unicode_decimal": 58913
|
||||
},
|
||||
{
|
||||
"icon_id": "3278372",
|
||||
"name": "签到信息",
|
||||
"font_class": "qiandaoxinxi",
|
||||
"unicode": "e625",
|
||||
"unicode_decimal": 58917
|
||||
},
|
||||
{
|
||||
"icon_id": "3278374",
|
||||
"name": "设置",
|
||||
"font_class": "shezhi",
|
||||
"unicode": "e626",
|
||||
"unicode_decimal": 58918
|
||||
},
|
||||
{
|
||||
"icon_id": "25290665",
|
||||
"name": "内容设置",
|
||||
"font_class": "neirongshezhi",
|
||||
"unicode": "e61e",
|
||||
"unicode_decimal": 58910
|
||||
},
|
||||
{
|
||||
"icon_id": "25290677",
|
||||
"name": "对话",
|
||||
"font_class": "duihua",
|
||||
"unicode": "e622",
|
||||
"unicode_decimal": 58914
|
||||
},
|
||||
{
|
||||
"icon_id": "25290689",
|
||||
"name": "打包下载",
|
||||
"font_class": "dabaoxiazai",
|
||||
"unicode": "e624",
|
||||
"unicode_decimal": 58916
|
||||
},
|
||||
{
|
||||
"icon_id": "25290702",
|
||||
"name": "正确",
|
||||
"font_class": "zhengque",
|
||||
"unicode": "e628",
|
||||
"unicode_decimal": 58920
|
||||
},
|
||||
{
|
||||
"icon_id": "25290704",
|
||||
"name": "窗口",
|
||||
"font_class": "chuangkou",
|
||||
"unicode": "e629",
|
||||
"unicode_decimal": 58921
|
||||
},
|
||||
{
|
||||
"icon_id": "25290707",
|
||||
"name": "设置",
|
||||
"font_class": "shezhi1",
|
||||
"unicode": "e62a",
|
||||
"unicode_decimal": 58922
|
||||
},
|
||||
{
|
||||
"icon_id": "25290715",
|
||||
"name": "复制",
|
||||
"font_class": "fuzhi",
|
||||
"unicode": "e62f",
|
||||
"unicode_decimal": 58927
|
||||
},
|
||||
{
|
||||
"icon_id": "25426237",
|
||||
"name": "存储",
|
||||
"font_class": "cunchu",
|
||||
"unicode": "e630",
|
||||
"unicode_decimal": 58928
|
||||
},
|
||||
{
|
||||
"icon_id": "25426239",
|
||||
"name": "网格",
|
||||
"font_class": "wangge",
|
||||
"unicode": "e631",
|
||||
"unicode_decimal": 58929
|
||||
},
|
||||
{
|
||||
"icon_id": "25468355",
|
||||
"name": "播放",
|
||||
"font_class": "bofang",
|
||||
"unicode": "e63a",
|
||||
"unicode_decimal": 58938
|
||||
},
|
||||
{
|
||||
"icon_id": "25468358",
|
||||
"name": "低音量",
|
||||
"font_class": "diyinliang",
|
||||
"unicode": "e63c",
|
||||
"unicode_decimal": 58940
|
||||
},
|
||||
{
|
||||
"icon_id": "25468359",
|
||||
"name": "调整",
|
||||
"font_class": "tiaozheng",
|
||||
"unicode": "e63d",
|
||||
"unicode_decimal": 58941
|
||||
},
|
||||
{
|
||||
"icon_id": "1718351",
|
||||
"name": "上箭头",
|
||||
"font_class": "shangjiantou",
|
||||
"unicode": "e617",
|
||||
"unicode_decimal": 58903
|
||||
},
|
||||
{
|
||||
"icon_id": "1718353",
|
||||
"name": "下箭头",
|
||||
"font_class": "xiajiantou",
|
||||
"unicode": "e618",
|
||||
"unicode_decimal": 58904
|
||||
},
|
||||
{
|
||||
"icon_id": "1718355",
|
||||
"name": "右箭头",
|
||||
"font_class": "youjiantou",
|
||||
"unicode": "e619",
|
||||
"unicode_decimal": 58905
|
||||
},
|
||||
{
|
||||
"icon_id": "1718358",
|
||||
"name": "左箭头",
|
||||
"font_class": "zuojiantou",
|
||||
"unicode": "e61b",
|
||||
"unicode_decimal": 58907
|
||||
},
|
||||
{
|
||||
"icon_id": "1279",
|
||||
"name": "大列表",
|
||||
"font_class": "daliebiao",
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "1280",
|
||||
"name": "大缩略图列表",
|
||||
"font_class": "dasuolvetuliebiao",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "1282",
|
||||
"name": "列表",
|
||||
"font_class": "liebiao",
|
||||
"unicode": "e602",
|
||||
"unicode_decimal": 58882
|
||||
},
|
||||
{
|
||||
"icon_id": "1337",
|
||||
"name": "图文详情",
|
||||
"font_class": "tuwenxiangqing",
|
||||
"unicode": "e603",
|
||||
"unicode_decimal": 58883
|
||||
},
|
||||
{
|
||||
"icon_id": "24072346",
|
||||
"name": "平板(头部)",
|
||||
"font_class": "a-pingbantoubu",
|
||||
"unicode": "e656",
|
||||
"unicode_decimal": 58966
|
||||
},
|
||||
{
|
||||
"icon_id": "24072347",
|
||||
"name": "手机(头部)",
|
||||
"font_class": "a-shoujitoubu",
|
||||
"unicode": "e657",
|
||||
"unicode_decimal": 58967
|
||||
},
|
||||
{
|
||||
"icon_id": "24072345",
|
||||
"name": "电脑(头部)",
|
||||
"font_class": "a-diannaotoubu",
|
||||
"unicode": "e655",
|
||||
"unicode_decimal": 58965
|
||||
},
|
||||
{
|
||||
"icon_id": "5961300",
|
||||
"name": "待办事项",
|
||||
"font_class": "daibanshixiang",
|
||||
"unicode": "ec4e",
|
||||
"unicode_decimal": 60494
|
||||
},
|
||||
{
|
||||
"icon_id": "9447888",
|
||||
"name": "密码",
|
||||
"font_class": "mima",
|
||||
"unicode": "e669",
|
||||
"unicode_decimal": 58985
|
||||
},
|
||||
{
|
||||
"icon_id": "17606297",
|
||||
"name": "保存",
|
||||
"font_class": "baocun",
|
||||
"unicode": "e6a3",
|
||||
"unicode_decimal": 59043
|
||||
},
|
||||
{
|
||||
"icon_id": "20853322",
|
||||
"name": "保存",
|
||||
"font_class": "baocun1",
|
||||
"unicode": "e6a2",
|
||||
"unicode_decimal": 59042
|
||||
},
|
||||
{
|
||||
"icon_id": "25931979",
|
||||
"name": "保存",
|
||||
"font_class": "baocun2",
|
||||
"unicode": "e79e",
|
||||
"unicode_decimal": 59294
|
||||
},
|
||||
{
|
||||
"icon_id": "5886128",
|
||||
"name": "颜色",
|
||||
"font_class": "yanse",
|
||||
"unicode": "e678",
|
||||
"unicode_decimal": 59000
|
||||
},
|
||||
{
|
||||
"icon_id": "35348744",
|
||||
"name": "拖拽",
|
||||
"font_class": "tuozhuai",
|
||||
"unicode": "e614",
|
||||
"unicode_decimal": 58900
|
||||
},
|
||||
{
|
||||
"icon_id": "8039243",
|
||||
"name": "查询",
|
||||
"font_class": "chaxun",
|
||||
"unicode": "e660",
|
||||
"unicode_decimal": 58976
|
||||
},
|
||||
{
|
||||
"icon_id": "4942665",
|
||||
"name": "添加",
|
||||
"font_class": "tianjia",
|
||||
"unicode": "e620",
|
||||
"unicode_decimal": 58912
|
||||
},
|
||||
{
|
||||
"icon_id": "32414825",
|
||||
"name": "添加",
|
||||
"font_class": "tianjia1",
|
||||
"unicode": "e740",
|
||||
"unicode_decimal": 59200
|
||||
},
|
||||
{
|
||||
"icon_id": "7450656",
|
||||
"name": "预览",
|
||||
"font_class": "yulan",
|
||||
"unicode": "e668",
|
||||
"unicode_decimal": 58984
|
||||
},
|
||||
{
|
||||
"icon_id": "20286810",
|
||||
"name": "返回@2x",
|
||||
"font_class": "fanhui2x",
|
||||
"unicode": "e62c",
|
||||
"unicode_decimal": 58924
|
||||
},
|
||||
{
|
||||
"icon_id": "20286828",
|
||||
"name": "撤销@2x",
|
||||
"font_class": "chexiao2x",
|
||||
"unicode": "e62e",
|
||||
"unicode_decimal": 58926
|
||||
},
|
||||
{
|
||||
"icon_id": "12974853",
|
||||
"name": "上传",
|
||||
"font_class": "shangchuan1",
|
||||
"unicode": "e62b",
|
||||
"unicode_decimal": 58923
|
||||
},
|
||||
{
|
||||
"icon_id": "20320554",
|
||||
"name": "展开",
|
||||
"font_class": "zhankai",
|
||||
"unicode": "e68b",
|
||||
"unicode_decimal": 59019
|
||||
},
|
||||
{
|
||||
"icon_id": "18863163",
|
||||
"name": "复制",
|
||||
"font_class": "fuzhi3",
|
||||
"unicode": "e643",
|
||||
"unicode_decimal": 58947
|
||||
},
|
||||
{
|
||||
"icon_id": "3278375",
|
||||
"name": "删除",
|
||||
"font_class": "shanchu1",
|
||||
"unicode": "e627",
|
||||
"unicode_decimal": 58919
|
||||
},
|
||||
{
|
||||
"icon_id": "11891761",
|
||||
"name": "circular",
|
||||
"font_class": "circular",
|
||||
"unicode": "e667",
|
||||
"unicode_decimal": 58983
|
||||
},
|
||||
{
|
||||
"icon_id": "577338",
|
||||
"name": "更多",
|
||||
"font_class": "gengduo",
|
||||
"unicode": "e73a",
|
||||
"unicode_decimal": 59194
|
||||
},
|
||||
{
|
||||
"icon_id": "23897237",
|
||||
"name": "代码",
|
||||
"font_class": "daima1",
|
||||
"unicode": "e761",
|
||||
"unicode_decimal": 59233
|
||||
},
|
||||
{
|
||||
"icon_id": "25468367",
|
||||
"name": "模块_1",
|
||||
"font_class": "mokuai_1",
|
||||
"unicode": "e63b",
|
||||
"unicode_decimal": 58939
|
||||
},
|
||||
{
|
||||
"icon_id": "648145",
|
||||
"name": "LC_icon_edit_line_1",
|
||||
"font_class": "LC_icon_edit_line_1",
|
||||
"unicode": "e65b",
|
||||
"unicode_decimal": 58971
|
||||
},
|
||||
{
|
||||
"icon_id": "657585",
|
||||
"name": "拆分",
|
||||
"font_class": "chaifen",
|
||||
"unicode": "e653",
|
||||
"unicode_decimal": 58963
|
||||
},
|
||||
{
|
||||
"icon_id": "695129",
|
||||
"name": "单选 选中",
|
||||
"font_class": "danxuanxuanzhong",
|
||||
"unicode": "e62d",
|
||||
"unicode_decimal": 58925
|
||||
},
|
||||
{
|
||||
"icon_id": "721659",
|
||||
"name": "代码",
|
||||
"font_class": "ai-code",
|
||||
"unicode": "e606",
|
||||
"unicode_decimal": 58886
|
||||
},
|
||||
{
|
||||
"icon_id": "731574",
|
||||
"name": "多选",
|
||||
"font_class": "duoxuan1",
|
||||
"unicode": "e66d",
|
||||
"unicode_decimal": 58989
|
||||
},
|
||||
{
|
||||
"icon_id": "784217",
|
||||
"name": "开关3",
|
||||
"font_class": "kaiguan3",
|
||||
"unicode": "e6da",
|
||||
"unicode_decimal": 59098
|
||||
},
|
||||
{
|
||||
"icon_id": "1305461",
|
||||
"name": "表格",
|
||||
"font_class": "biaoge",
|
||||
"unicode": "e63e",
|
||||
"unicode_decimal": 58942
|
||||
},
|
||||
{
|
||||
"icon_id": "1467357",
|
||||
"name": "html5",
|
||||
"font_class": "html",
|
||||
"unicode": "e693",
|
||||
"unicode_decimal": 59027
|
||||
},
|
||||
{
|
||||
"icon_id": "1812689",
|
||||
"name": "menu-菜单",
|
||||
"font_class": "menu",
|
||||
"unicode": "e605",
|
||||
"unicode_decimal": 58885
|
||||
},
|
||||
{
|
||||
"icon_id": "1812699",
|
||||
"name": "edit-编辑",
|
||||
"font_class": "edit",
|
||||
"unicode": "e607",
|
||||
"unicode_decimal": 58887
|
||||
},
|
||||
{
|
||||
"icon_id": "1812735",
|
||||
"name": "calendar-日历",
|
||||
"font_class": "calendar",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "1812743",
|
||||
"name": "download-下载",
|
||||
"font_class": "download",
|
||||
"unicode": "e60b",
|
||||
"unicode_decimal": 58891
|
||||
},
|
||||
{
|
||||
"icon_id": "1812751",
|
||||
"name": "upload-上传",
|
||||
"font_class": "upload",
|
||||
"unicode": "e60c",
|
||||
"unicode_decimal": 58892
|
||||
},
|
||||
{
|
||||
"icon_id": "1813029",
|
||||
"name": "folder-文件夹",
|
||||
"font_class": "folder",
|
||||
"unicode": "e611",
|
||||
"unicode_decimal": 58897
|
||||
},
|
||||
{
|
||||
"icon_id": "1813034",
|
||||
"name": "write-写",
|
||||
"font_class": "write",
|
||||
"unicode": "e612",
|
||||
"unicode_decimal": 58898
|
||||
},
|
||||
{
|
||||
"icon_id": "1813038",
|
||||
"name": "gallery-画廊",
|
||||
"font_class": "gallery",
|
||||
"unicode": "e613",
|
||||
"unicode_decimal": 58899
|
||||
},
|
||||
{
|
||||
"icon_id": "1813190",
|
||||
"name": "image-图像",
|
||||
"font_class": "image",
|
||||
"unicode": "e61a",
|
||||
"unicode_decimal": 58906
|
||||
},
|
||||
{
|
||||
"icon_id": "1813719",
|
||||
"name": "time-时间",
|
||||
"font_class": "time",
|
||||
"unicode": "e633",
|
||||
"unicode_decimal": 58931
|
||||
},
|
||||
{
|
||||
"icon_id": "1813731",
|
||||
"name": "button-add-按钮添加",
|
||||
"font_class": "button-add",
|
||||
"unicode": "e637",
|
||||
"unicode_decimal": 58935
|
||||
},
|
||||
{
|
||||
"icon_id": "1813733",
|
||||
"name": "button-remove-按钮删除",
|
||||
"font_class": "button-remove",
|
||||
"unicode": "e638",
|
||||
"unicode_decimal": 58936
|
||||
},
|
||||
{
|
||||
"icon_id": "2471370",
|
||||
"name": "卡片",
|
||||
"font_class": "qiapian",
|
||||
"unicode": "e674",
|
||||
"unicode_decimal": 58996
|
||||
},
|
||||
{
|
||||
"icon_id": "5203323",
|
||||
"name": "栅格",
|
||||
"font_class": "zhage",
|
||||
"unicode": "e63f",
|
||||
"unicode_decimal": 58943
|
||||
},
|
||||
{
|
||||
"icon_id": "5755487",
|
||||
"name": "单选-粗线条",
|
||||
"font_class": "danxuan-cuxiantiao",
|
||||
"unicode": "e676",
|
||||
"unicode_decimal": 58998
|
||||
},
|
||||
{
|
||||
"icon_id": "6022670",
|
||||
"name": "下拉2",
|
||||
"font_class": "xiala",
|
||||
"unicode": "e640",
|
||||
"unicode_decimal": 58944
|
||||
},
|
||||
{
|
||||
"icon_id": "6337454",
|
||||
"name": "分割线",
|
||||
"font_class": "fengexian",
|
||||
"unicode": "ec7f",
|
||||
"unicode_decimal": 60543
|
||||
},
|
||||
{
|
||||
"icon_id": "7274115",
|
||||
"name": "字号",
|
||||
"font_class": "zihao",
|
||||
"unicode": "e623",
|
||||
"unicode_decimal": 58915
|
||||
},
|
||||
{
|
||||
"icon_id": "7544797",
|
||||
"name": "树型",
|
||||
"font_class": "juxingkaobei",
|
||||
"unicode": "e7a5",
|
||||
"unicode_decimal": 59301
|
||||
},
|
||||
{
|
||||
"icon_id": "7712657",
|
||||
"name": "edit",
|
||||
"font_class": "edit1",
|
||||
"unicode": "e72a",
|
||||
"unicode_decimal": 59178
|
||||
},
|
||||
{
|
||||
"icon_id": "9626907",
|
||||
"name": "评分_默认",
|
||||
"font_class": "pingfen_moren",
|
||||
"unicode": "e877",
|
||||
"unicode_decimal": 59511
|
||||
},
|
||||
{
|
||||
"icon_id": "11249706",
|
||||
"name": "edit-2",
|
||||
"font_class": "edit-",
|
||||
"unicode": "e83b",
|
||||
"unicode_decimal": 59451
|
||||
},
|
||||
{
|
||||
"icon_id": "11737013",
|
||||
"name": "number",
|
||||
"font_class": "number",
|
||||
"unicode": "e639",
|
||||
"unicode_decimal": 58937
|
||||
},
|
||||
{
|
||||
"icon_id": "11976079",
|
||||
"name": "提示",
|
||||
"font_class": "zu",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "12694622",
|
||||
"name": "关联",
|
||||
"font_class": "guanlian",
|
||||
"unicode": "e61f",
|
||||
"unicode_decimal": 58911
|
||||
},
|
||||
{
|
||||
"icon_id": "15617359",
|
||||
"name": "tabs",
|
||||
"font_class": "tabs",
|
||||
"unicode": "e8cd",
|
||||
"unicode_decimal": 59597
|
||||
},
|
||||
{
|
||||
"icon_id": "16194540",
|
||||
"name": "撤销",
|
||||
"font_class": "chexiao",
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "16194544",
|
||||
"name": "全屏",
|
||||
"font_class": "quanping",
|
||||
"unicode": "e60d",
|
||||
"unicode_decimal": 58893
|
||||
},
|
||||
{
|
||||
"icon_id": "16194545",
|
||||
"name": "重做",
|
||||
"font_class": "zhongzuo",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "16194678",
|
||||
"name": "组件",
|
||||
"font_class": "zujian",
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "16198616",
|
||||
"name": "编辑2",
|
||||
"font_class": "bianji2",
|
||||
"unicode": "e610",
|
||||
"unicode_decimal": 58896
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
src/components/EpicDesigner/static/icons/iconfont.ttf
Normal file
BIN
src/components/EpicDesigner/static/icons/iconfont.ttf
Normal file
Binary file not shown.
BIN
src/components/EpicDesigner/static/icons/iconfont.woff
Normal file
BIN
src/components/EpicDesigner/static/icons/iconfont.woff
Normal file
Binary file not shown.
BIN
src/components/EpicDesigner/static/icons/iconfont.woff2
Normal file
BIN
src/components/EpicDesigner/static/icons/iconfont.woff2
Normal file
Binary file not shown.
BIN
src/components/EpicDesigner/static/logo.png
Normal file
BIN
src/components/EpicDesigner/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
176
src/components/EpicDesigner/theme/var.less
Normal file
176
src/components/EpicDesigner/theme/var.less
Normal file
@@ -0,0 +1,176 @@
|
||||
// Light theme
|
||||
@epic-primary-color: #3e8bf2;
|
||||
@epic-designer-color: #fafafc;
|
||||
@epic-designer-dot-color: #373739;
|
||||
@epic-edit-range-color: #fff;
|
||||
@epic-view-color: #fff;
|
||||
@epic-edit-color: #ccc;
|
||||
@epic-action-bar-color: #fff;
|
||||
@epic-header-color: white;
|
||||
@epic-action-hover-color: #f8f8f8;
|
||||
@epic-widget-hover-color: #f8f8f8;
|
||||
@epic-event-item-color: #f8f8f8;
|
||||
@epic-shadow-color: #0000003d;
|
||||
@epic-text-main: #000000de;
|
||||
@epic-text-secondary: #00000099;
|
||||
@epic-text-helper: #00000066;
|
||||
@epic-text-disabled: #00000042;
|
||||
@epic-border-color: #d9d9d9;
|
||||
@epic-compoent-tabs-color: #f8f8f8;
|
||||
@epic-text-sm: 12px;
|
||||
@epic-text-md: 14px;
|
||||
@epic-text-lg: 16px;
|
||||
|
||||
// Dark theme
|
||||
@epic-dark-primary-color: #3e8bf2;
|
||||
@epic-dark-designer-color: #18181c;
|
||||
@epic-dark-designer-dot-color: #86909c;
|
||||
@epic-dark-edit-range-color: #232324;
|
||||
@epic-dark-view-color: #252526;
|
||||
@epic-dark-edit-color: #ccc;
|
||||
@epic-dark-action-bar-color: #252526;
|
||||
@epic-dark-header-color: #252526;
|
||||
@epic-dark-action-hover-color: #000;
|
||||
@epic-dark-widget-hover-color: #111;
|
||||
@epic-dark-event-item-color: #111;
|
||||
@epic-dark-shadow-color: #000000;
|
||||
@epic-dark-text-main: #ffffffde;
|
||||
@epic-dark-text-secondary: #ffffff99;
|
||||
@epic-dark-text-helper: #ffffff66;
|
||||
@epic-dark-text-disabled: #666;
|
||||
@epic-dark-border-color: #ffffff20;
|
||||
@epic-dark-compoent-tabs-color: #2c2c2c;
|
||||
:root {
|
||||
// 主色
|
||||
--epic-primary-color: @epic-primary-color;
|
||||
|
||||
// hover颜色
|
||||
--epic-primary-hover-color: rgb(@epic-primary-color 8%);
|
||||
|
||||
// 设计器背景色
|
||||
--epic-designer-color: @epic-designer-color;
|
||||
|
||||
// 设计器点颜色
|
||||
--epic-designer-dot-color: @epic-designer-dot-color;
|
||||
|
||||
// 画布背景
|
||||
--epic-edit-range-color: @epic-edit-range-color;
|
||||
|
||||
// 属性视图背景色
|
||||
--epic-view-color: @epic-view-color;
|
||||
|
||||
// 拖拽区域背景色
|
||||
--epic-edit-color: @epic-edit-color;
|
||||
|
||||
// 活动视图切换器背景色
|
||||
--epic-action-bar-color: @epic-action-bar-color;
|
||||
|
||||
// 顶部颜色
|
||||
--epic-header-color: @epic-header-color;
|
||||
|
||||
// 动作栏 hover
|
||||
--epic-action-hover-color: @epic-action-hover-color;
|
||||
|
||||
// 窗口部件 hover
|
||||
--epic-widget-hover-color: @epic-widget-hover-color;
|
||||
|
||||
// 窗口部件遮罩颜色
|
||||
--epic-widget-shade-color: rgb(@epic-primary-color 6%);
|
||||
|
||||
// 事件项背景色
|
||||
--epic-event-item-color: @epic-event-item-color;
|
||||
|
||||
// 阴影颜色
|
||||
--epic-shadow-color: @epic-shadow-color;
|
||||
|
||||
// 主要文本颜色
|
||||
--epic-text-main: @epic-text-main;
|
||||
|
||||
// 二级文本颜色(介于正文和标题之间)
|
||||
--epic-text-secondary: @epic-text-secondary;
|
||||
|
||||
// 辅助文本颜色(更浅)
|
||||
--epic-text-helper: @epic-text-helper;
|
||||
|
||||
// 禁用文本颜色
|
||||
--epic-text-disabled: @epic-text-disabled;
|
||||
|
||||
// 边框颜色
|
||||
--epic-border-color: @epic-border-color;
|
||||
|
||||
// 组件tabs颜色
|
||||
--epic-compoent-tabs-color: @epic-compoent-tabs-color;
|
||||
|
||||
// 组件hover颜色
|
||||
--epic-compoent-hover-color: rgb(@epic-primary-color 6%);
|
||||
|
||||
// 字体大小
|
||||
--epic-text-sm: @epic-text-sm;
|
||||
--epic-text-md: @epic-text-md;
|
||||
--epic-text-lg: @epic-text-lg;
|
||||
}
|
||||
|
||||
// 黑暗主题 start
|
||||
.dark {
|
||||
// 主色
|
||||
--epic-primary-color: @epic-dark-primary-color;
|
||||
--epic-primary-hover-color: rgb(@epic-dark-primary-color 8%);
|
||||
--epic-primary-hover-color: rgb(@epic-dark-primary-color 8%);
|
||||
|
||||
// 设计器背景色
|
||||
--epic-designer-color: @epic-dark-designer-color;
|
||||
--epic-designer-dot-color: @epic-dark-designer-dot-color;
|
||||
|
||||
// 画布背景
|
||||
--epic-edit-range-color: @epic-dark-edit-range-color;
|
||||
|
||||
// 属性视图背景色
|
||||
--epic-view-color: @epic-dark-view-color;
|
||||
|
||||
// 拖拽区域背景色
|
||||
--epic-edit-color: @epic-dark-edit-color;
|
||||
|
||||
// 活动视图切换器背景色
|
||||
--epic-action-bar-color: @epic-dark-action-bar-color;
|
||||
|
||||
// 顶部颜色
|
||||
--epic-header-color: @epic-dark-header-color;
|
||||
|
||||
// 动作栏 hover
|
||||
--epic-action-hover-color: @epic-dark-action-hover-color;
|
||||
|
||||
// 窗口部件 hover
|
||||
--epic-widget-hover-color: @epic-dark-widget-hover-color;
|
||||
|
||||
// 窗口部件遮罩颜色
|
||||
--epic-widget-shade-color: rgb(@epic-dark-primary-color 6%);
|
||||
|
||||
// 事件项背景色
|
||||
--epic-event-item-color: @epic-dark-event-item-color;
|
||||
|
||||
// 阴影颜色
|
||||
--epic-shadow-color: @epic-dark-shadow-color;
|
||||
|
||||
// 主要文本颜色
|
||||
--epic-text-main: @epic-dark-text-main;
|
||||
|
||||
// 二级文本颜色(介于正文和标题之间)
|
||||
--epic-text-secondary: @epic-dark-text-secondary;
|
||||
|
||||
// 辅助文本颜色(更浅)
|
||||
--epic-text-helper: @epic-dark-text-helper;
|
||||
|
||||
// 禁用文本颜色
|
||||
--epic-text-disabled: @epic-dark-text-disabled;
|
||||
|
||||
// 边框颜色
|
||||
--epic-border-color: @epic-dark-border-color;
|
||||
|
||||
// 组件tabs颜色
|
||||
--epic-compoent-tabs-color: @epic-dark-compoent-tabs-color;
|
||||
|
||||
// 组件hover颜色
|
||||
--epic-compoent-hover-color: rgb(@epic-dark-primary-color 6%);
|
||||
}
|
||||
|
||||
// 黑暗主题 end
|
||||
83
src/components/EpicDesigner/types/epic-designer.ts
Normal file
83
src/components/EpicDesigner/types/epic-designer.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { type FormItemRule } from "../extensions/ERuleEditor/types";
|
||||
export interface RenderCallbackParams {
|
||||
values: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ComponentSchema {
|
||||
// 节点ID,可选
|
||||
id?: string;
|
||||
// 节点类型,必选
|
||||
type: string;
|
||||
// 节点标签,可选
|
||||
label?: string;
|
||||
// 节点字段,可选
|
||||
field?: string;
|
||||
// 组件属性,可选
|
||||
componentProps?: any;
|
||||
// 插槽名称(组件为插槽类型时,需要设置插槽name),可选
|
||||
slotName?: string;
|
||||
// 表单验证规则,可选
|
||||
rules?: FormItemRule[];
|
||||
// 是否无需表单项,可选
|
||||
noFormItem?: boolean;
|
||||
// 是否为表单输入组件,可选
|
||||
input?: boolean;
|
||||
// 子节点列表,可选
|
||||
children?: ComponentSchema[];
|
||||
// 插槽列表,可选
|
||||
slots?: { [slotName: string]: ComponentSchema[] };
|
||||
// 是否显示(属性编辑组件可以添加函数动态显示隐藏),可选
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
// 编辑组件数据,可选(属性编辑另外绑定编辑的数据,默认则为当前选中组件数据)
|
||||
editData?: any;
|
||||
// 其他未明确指定的属性
|
||||
[fieldName: string]: any;
|
||||
}
|
||||
|
||||
export interface FormConfig {
|
||||
layout?: string;
|
||||
labelWidth?: string;
|
||||
labelLayout: any;
|
||||
labelCol: any;
|
||||
wrapperCol: any;
|
||||
hideRequiredMark?: boolean;
|
||||
customStyle?: string;
|
||||
}
|
||||
|
||||
export type FormDataModel = Record<string, any>;
|
||||
|
||||
export interface Designer {
|
||||
setCheckedNode: (schema?: ComponentSchema) => void;
|
||||
setHoverNode: (schema: ComponentSchema | null) => void;
|
||||
setDisableHover: (disableHover?: boolean) => void;
|
||||
handleToggleDeviceMode: (mode: string) => void;
|
||||
reset: () => void;
|
||||
state: DesignerState;
|
||||
// schemas: ComponentSchema[];
|
||||
}
|
||||
|
||||
export interface DesignerState {
|
||||
checkedNode: ComponentSchema | null;
|
||||
hoverNode: ComponentSchema | null;
|
||||
disableHover: boolean;
|
||||
matched: ComponentSchema[];
|
||||
dataSource: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
length: string;
|
||||
isNull: boolean;
|
||||
isKey: boolean;
|
||||
info: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PageSchema {
|
||||
schemas: ComponentSchema[];
|
||||
canvas?: {
|
||||
width?: string;
|
||||
height?: string;
|
||||
mode?: string;
|
||||
};
|
||||
script?: string;
|
||||
}
|
||||
25
src/components/EpicDesigner/ui/button/button.ts
Normal file
25
src/components/EpicDesigner/ui/button/button.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineComponent, h, renderSlot, type PropType } from "vue";
|
||||
import { ElButton } from "element-plus";
|
||||
import { type ComponentSchema } from "@/components/EpicDesigner/types/epic-designer";
|
||||
|
||||
// 二次封装组件
|
||||
export default defineComponent({
|
||||
props: {
|
||||
componentSchema: {
|
||||
type: Object as PropType<ComponentSchema>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { emit, slots }) {
|
||||
return () => {
|
||||
console.log(emit);
|
||||
const componentProps: Record<string, any> = {
|
||||
...props.componentSchema?.componentProps
|
||||
};
|
||||
|
||||
return h(ElButton, componentProps, {
|
||||
default: () => renderSlot(slots, "default", {}, () => [props.componentSchema?.label])
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
92
src/components/EpicDesigner/ui/button/index.ts
Normal file
92
src/components/EpicDesigner/ui/button/index.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { type ComponentConfigModel } from "@/components/EpicDesigner/utils";
|
||||
export default {
|
||||
component: () => import("./button"),
|
||||
groupName: "表单",
|
||||
icon: "epic-icon-button-remove",
|
||||
defaultSchema: {
|
||||
label: "按钮",
|
||||
type: "button",
|
||||
field: "input",
|
||||
input: false
|
||||
},
|
||||
config: {
|
||||
attribute: [
|
||||
{
|
||||
label: "标题",
|
||||
type: "input",
|
||||
field: "label"
|
||||
},
|
||||
{
|
||||
label: "类型",
|
||||
type: "select",
|
||||
componentProps: {
|
||||
placeholder: "请选择",
|
||||
clearable: true,
|
||||
options: [
|
||||
{
|
||||
label: "primary",
|
||||
value: "primary"
|
||||
},
|
||||
{
|
||||
label: "success",
|
||||
value: "success"
|
||||
},
|
||||
{
|
||||
label: "info",
|
||||
value: "info"
|
||||
},
|
||||
{
|
||||
label: "warning",
|
||||
value: "warning"
|
||||
},
|
||||
{
|
||||
label: "danger",
|
||||
value: "danger"
|
||||
}
|
||||
]
|
||||
},
|
||||
field: "componentProps.type"
|
||||
},
|
||||
{
|
||||
label: "朴素按钮",
|
||||
type: "switch",
|
||||
field: "componentProps.plain"
|
||||
},
|
||||
{
|
||||
label: "圆角按钮",
|
||||
type: "switch",
|
||||
field: "componentProps.round"
|
||||
},
|
||||
{
|
||||
label: "圆形按钮",
|
||||
type: "switch",
|
||||
field: "componentProps.circle"
|
||||
},
|
||||
{
|
||||
label: "文字按钮",
|
||||
type: "switch",
|
||||
field: "componentProps.text"
|
||||
},
|
||||
{
|
||||
label: "禁用",
|
||||
type: "switch",
|
||||
field: "componentProps.disabled"
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
type: "switch",
|
||||
field: "componentProps.hidden"
|
||||
}
|
||||
],
|
||||
event: [
|
||||
{
|
||||
type: "click",
|
||||
describe: "点击按钮时"
|
||||
},
|
||||
{
|
||||
type: "dblclick",
|
||||
describe: "双击按钮时"
|
||||
}
|
||||
]
|
||||
}
|
||||
} as ComponentConfigModel;
|
||||
35
src/components/EpicDesigner/ui/card/card.ts
Normal file
35
src/components/EpicDesigner/ui/card/card.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { defineComponent, h, renderSlot, type PropType } from "vue";
|
||||
import { ElCard } from "element-plus";
|
||||
import { type ComponentSchema } from "@/components/EpicDesigner/types/epic-designer";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
componentSchema: {
|
||||
type: Object as PropType<ComponentSchema>,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { attrs, slots }) {
|
||||
return () => {
|
||||
const componentSchema = {
|
||||
...props.componentSchema,
|
||||
header: props.componentSchema?.label ?? ""
|
||||
} as ComponentSchema;
|
||||
console.log(attrs);
|
||||
const children = componentSchema.children ?? [];
|
||||
delete componentSchema.children;
|
||||
|
||||
let vNodeClildren: any = null;
|
||||
if (children.length) {
|
||||
vNodeClildren = () =>
|
||||
children.map((node: ComponentSchema) => renderSlot(slots, "node", { componentSchema: node }));
|
||||
} else {
|
||||
vNodeClildren = () => [renderSlot(slots, "default")];
|
||||
}
|
||||
return h(ElCard, componentSchema, {
|
||||
default: () => renderSlot(slots, "edit-node", {}, vNodeClildren),
|
||||
header: () => renderSlot(slots, "header")
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
51
src/components/EpicDesigner/ui/card/index.ts
Normal file
51
src/components/EpicDesigner/ui/card/index.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { type ComponentConfigModel } from "@/components/EpicDesigner/utils";
|
||||
export default {
|
||||
component: () => import("./card"),
|
||||
groupName: "布局",
|
||||
icon: "epic-icon-qiapian",
|
||||
defaultSchema: {
|
||||
label: "卡片布局",
|
||||
type: "card",
|
||||
children: [],
|
||||
componentProps: {
|
||||
shadow: "hover"
|
||||
}
|
||||
},
|
||||
config: {
|
||||
attribute: [
|
||||
{
|
||||
label: "标题",
|
||||
type: "input",
|
||||
field: "label"
|
||||
},
|
||||
{
|
||||
label: "阴影时机",
|
||||
type: "select",
|
||||
defaultValue: "always",
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: "always",
|
||||
value: "always"
|
||||
},
|
||||
{
|
||||
label: "hover",
|
||||
value: "hover"
|
||||
},
|
||||
{
|
||||
label: "never",
|
||||
value: "never"
|
||||
}
|
||||
],
|
||||
placeholder: "请选择"
|
||||
},
|
||||
field: "componentProps.shadow"
|
||||
},
|
||||
{
|
||||
label: "隐藏",
|
||||
type: "switch",
|
||||
field: "componentProps.hidden"
|
||||
}
|
||||
]
|
||||
}
|
||||
} as ComponentConfigModel;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user