feat: 🚀 表单设计器和审批流本地化

This commit is contained in:
2025-09-09 18:03:00 +08:00
parent 45ec52eb77
commit cd268db2a5
212 changed files with 18082 additions and 22183 deletions

View File

@@ -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 ==> 打开的规则作为警告(不影响代码执行)

View File

@@ -2,4 +2,5 @@
/public/*
public/*
stats.html
.less
src/components/EpicDesigner/theme/var.less

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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";
// }
// }

View File

@@ -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");
};

View File

@@ -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-"],

File diff suppressed because it is too large Load Diff

View File

@@ -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";
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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"];
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,7 @@
<template>
<div class="e-loading">
<div></div>
<div></div>
<div></div>
</div>
</template>

View File

@@ -0,0 +1,3 @@
import EBuilder from "./src/builder.vue";
export default EBuilder;

View 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>

View File

@@ -0,0 +1,4 @@
export interface DesignerProps {
disabledZoom?: boolean;
hiddenHeader?: boolean;
}

View File

@@ -0,0 +1,3 @@
import EDesigner from "./src/designer.vue";
export default EDesigner;

View 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>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;
// }
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
.epic-outline {
box-sizing: border-box;
height: 100%;
overflow: auto;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -0,0 +1,6 @@
.epic-sound-code {
height: 100%;
}
.epic-editor {
height: 100%;
}

View File

@@ -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>

View File

@@ -0,0 +1,9 @@
import { PageSchema } from "../../../types/epic-designer";
export interface DesignerProps {
disabledZoom?: boolean;
hiddenHeader?: boolean;
lockDefaultSchemaEdit?: boolean;
title?: string;
defaultSchema?: PageSchema;
}

View File

@@ -0,0 +1,2 @@
import EIcon from "./src/icon.vue";
export default EIcon;

View 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>

View File

@@ -0,0 +1,2 @@
import ENode from "./src/node.vue";
export default ENode;

View 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>

View File

@@ -0,0 +1,2 @@
import ETree from "./src/tree.vue";
export default ETree;

View 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;
}
}
}
}
}

View 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>

View 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>

View 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>

View 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;
}

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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;
}

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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>

View 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"];

View File

@@ -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;
}
}

View 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>

View 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;
}

View 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;

View 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>

View 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;

View 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>

View 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);
});
}

View 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";

View 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;

View File

@@ -0,0 +1,3 @@
import EDesigner from "../EpicDesigner/components/designer";
export default EDesigner;

View 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;
}

File diff suppressed because it is too large Load Diff

View 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";
}

File diff suppressed because one or more lines are too long

View 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
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View 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

View 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;
}

View 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])
});
};
}
});

View 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;

View 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")
});
};
}
});

View 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