feat: 🚀 业务组件

This commit is contained in:
2025-09-12 17:55:21 +08:00
parent 94089a549d
commit 92ae61ba1c
13 changed files with 455 additions and 10 deletions

View File

@@ -53,3 +53,35 @@ dev(开发分支,用于开环境、解决冲突,来源于其他开发分支)
分支拉取请从 pro 分支进行拉取!
冲突请在 dev 分支解决,解决冲突后再合并测试分支
# EpicDesigner
├── index.ts # 入口文件,导出核心组件并初始化
├── index.less # 全局样式
├── components/ # 核心组件目录
│ ├── builder/ # 构建器组件EBuilder
│ ├── designer/ # 设计器主组件EDesigner
│ │ └── src/
│ │ ├── modules/ # 设计器模块(组件面板、属性面板等)
│ ├── node/ # 节点组件ENode用于渲染设计元素
│ └── tree/ # 树形组件ETree用于展示结构大纲
├── extensions/ # 扩展组件目录
│ ├── EInputSize/ # 输入尺寸组件
│ ├── MonacoEditor/ # 代码编辑器组件
│ ├── Page/ # 页面组件
│ └── index.ts # 扩展组件注册逻辑
├── ui/ # 基础 UI 组件目录
│ ├── dept/ # 部门/人员选择组件
│ ├── form/ # 表单组件
│ ├── input/ # 输入框组件
│ ├── row/ # 栅格布局组件
│ ├── upload-file/ # 文件上传组件
│ └── upload-image/ # 图片上传组件
├── utils/ # 工具函数目录
│ ├── manager/ # 管理器(插件、页面状态等)
│ ├── common/ # 通用工具数据处理、DOM 操作等)
│ └── shareStore/ # 共享状态管理
├── types/ # 类型定义目录
│ └── epic-designer.ts # 核心类型(组件结构、设计器状态等)
├── theme/ # 主题相关(推测)
└── static/ # 静态资源(推测)

4
src/components.d.ts vendored
View File

@@ -7,6 +7,7 @@ export {}
declare module "vue" {
export interface GlobalComponents {
Dept: typeof import("./components/EpicDesigner/extensions/Dept/index.vue")["default"];
ElAside: typeof import("element-plus/es")["ElAside"];
ElAutocomplete: typeof import("element-plus/es")["ElAutocomplete"];
ElAvatar: typeof import("element-plus/es")["ElAvatar"];
@@ -65,5 +66,8 @@ declare module "vue" {
IEpSwitchButton: typeof import("~icons/ep/switch-button")["default"];
RouterLink: typeof import("vue-router")["RouterLink"];
RouterView: typeof import("vue-router")["RouterView"];
TagInput: typeof import("./components/EpicDesigner/extensions/TagInput/index.vue")["default"];
Test: typeof import("./components/EpicDesigner/businessUi/test/index.vue")["default"];
User: typeof import("./components/EpicDesigner/ui/dept/user.vue")["default"];
}
}

View File

@@ -0,0 +1,56 @@
import $Bus from "@/utils/mittBus";
export default {
component: () => import("./index.vue"),
groupName: "扩展组件", //分组
icon: "epic-icon-dept", //图标
defaultSchema: {
label: "部门", //组件名称
type: "epic-dept-select", //组件类型
field: "epic-dept-select",
input: true,
componentProps: {
placeholder: "请选择人员"
}
},
config: {
attribute: [
{
label: "字段名",
type: "input",
field: "field"
},
{
label: "标签文字",
type: "input",
field: "label"
},
{
label: "占位内容",
type: "input",
field: "componentProps.placeholder"
},
{
label: "弹窗择值",
type: "button",
field: "componentProps.openModal",
componentProps: {
onClick: () => {
$Bus.emit("trigger-dept-select-modal");
}
}
}
]
// event: [
// {
// type: "change",
// describe: "选择值变化时"
// },
// // 添加按钮点击事件定义
// {
// type: "buttonClick", // 自定义事件名,避免冲突
// describe: "点击选择人员按钮时"
// }
// ]
}
} as any;

View File

@@ -0,0 +1,345 @@
<template>
<div class="user-select-component">
<el-input
v-model:value="displayValue"
placeholder="请选择组织"
:disabled="componentSchema?.disabled"
readonly
:class="{ selected: isSelected }"
/>
<!-- 用户选择弹窗 -->
<el-dialog v-model="isModalVisible" title="选择组织" width="800px" @close="handleModalClose">
<div class="user-select-container">
<!-- 部门树带复选框 -->
<div class="dept-tree">
<el-input
v-model="searchQuery"
placeholder="输入关键字按enter键查询"
class="search-input"
@keyup.enter="handleSearch"
>
<template #append>
<el-button @click="handleSearch">
<el-icon><Search /></el-icon>
</el-button>
</template>
</el-input>
<el-tree
ref="deptTree"
:data="deptData"
:props="deptProps"
:default-expanded-keys="defaultExpandedKeys"
:default-checked-keys="selectedDeptIds"
show-checkbox
node-key="id"
@check="handleDeptCheck"
class="tree"
>
<template #default="{ node }">
<span class="custom-tree-node">
<el-icon v-if="node.children && node.children.length">
<Fold v-if="node.expanded" />
<Expand v-else />
</el-icon>
{{ node.label }}
</span>
</template>
</el-tree>
</div>
<!-- 已选用户列表 -->
<div class="user-transfer">
<div
style="display: flex; align-items: center; justify-content: space-between; padding: 8px 0"
v-for="item in selectDeptData"
:key="item.id"
>
<div>{{ item.label }}</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleConfirm">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
import { inject } from "vue";
import { Designer } from "epic-designer";
import $Bus from "@/utils/mittBus";
import { ElMessage } from "element-plus";
import { Search, Fold, Expand } from "@element-plus/icons-vue";
// import { useMsg } from "@/hooks/useMsg";
// 接收props
const props = defineProps({
componentSchema: {
type: Object,
default: () => ({
placeholder: "请选择组织",
value: null,
disabled: false,
componentProps: {}
})
},
modelValue: {
type: String,
default: null
}
});
// 事件发射补充v-model更新事件
const emit = defineEmits(["update:modelValue"]);
// 状态管理
const isModalVisible = ref(false);
const selectedDept = ref<any>(null);
const isSelected = ref(false);
const defaultExpandedKeys = ref<number[]>([]); // 用于默认展开部门节点
// 设计器实例
const designer = inject("designer") as Designer;
// 部门数据
const deptData = ref([
{
id: 1,
label: "销售部",
type: "deptP", // 明确标记为部门
children: [
{ id: 2151111, label: "销售部1组", type: "dept" },
{ id: 2141111, label: "销售部2组", type: "dept" },
{ id: 2131111, label: "销售部3组", type: "dept" },
{ id: 2121111, label: "销售部4组", type: "dept" },
{ id: 2111111, label: "销售部5组", type: "dept" }
]
},
{
id: 2,
label: "市场部",
type: "deptP",
children: [
{ id: 2151111, label: "市场部1组", type: "dept" },
{ id: 2141111, label: "市场部2组", type: "dept" },
{ id: 2131111, label: "市场部3组", type: "dept" },
{ id: 2121111, label: "市场部4组", type: "dept" },
{ id: 2111111, label: "市场部5组", type: "dept" }
]
},
{
id: 3,
label: "开发部",
type: "deptP",
children: [
{ id: 215111, label: "开发部1组", type: "dept" },
{ id: 214111, label: "开发部2组", type: "dept" },
{ id: 213111, label: "开发部3组", type: "dept" },
{ id: 212111, label: "开发部4组", type: "dept" },
{ id: 211111, label: "开发部5组", type: "dept" }
]
},
{
id: 4,
label: "运维部",
type: "deptP",
children: [
{ id: 21511, label: "运维部1组", type: "dept" },
{ id: 21411, label: "运维部2组", type: "dept" },
{ id: 21311, label: "运维部3组", type: "dept" },
{ id: 21211, label: "运维部4组", type: "dept" },
{ id: 21111, label: "运维部5组", type: "dept" }
]
},
{
id: 5,
label: "财务部",
type: "deptP",
children: [
{ id: 2151, label: "财务部1组", type: "dept" },
{ id: 2141, label: "财务部2组", type: "dept" },
{ id: 2131, label: "财务部3组", type: "dept" },
{ id: 2121, label: "财务部4组", type: "dept" },
{ id: 2111, label: "财务部5组", type: "dept" }
]
},
{
id: 14,
label: "IT部",
type: "deptP",
children: [
{ id: 215, label: "IT部1组", type: "dept" },
{ id: 214, label: "IT部2组", type: "dept" },
{ id: 213, label: "IT部3组", type: "dept" },
{ id: 212, label: "IT部4组", type: "dept" },
{ id: 211, label: "IT部5组", type: "dept" }
]
}
]);
// 部门树配置
const deptProps = {
children: "children",
label: "label"
};
// 搜索查询
const searchQuery = ref("");
const deptTree = ref<any>(null);
// 处理搜索
const handleSearch = () => {
deptTree.value?.filter(searchQuery.value);
};
// 关闭弹窗回调(清空临时数据)
const handleModalClose = () => {
selectDeptData.value = [];
selectedDeptIds.value = [];
};
// 部门树复选事件(核心修改)
const handleDeptCheck = (data: any, checked: any) => {
selectDeptData.value = checked.checkedNodes.filter((item: any) => {
return item.type === "dept";
});
selectDeptData.value.forEach((item: any) => {
selectedDeptIds.value.push(item.id);
});
};
//选择的部门
const selectDeptData = ref<any[]>([]);
const selectedDeptIds = ref<number[]>([]);
// 确认选择
const handleConfirm = () => {
if (selectedDeptIds.value.length === 0) {
ElMessage.warning("请选择至少一个部门");
return;
}
// 处理选择的组织
const selectedDepts: any = selectDeptData.value;
// 拼接用户名
const deptNames: any = selectedDepts.map((item: any) => item.label).join(",");
selectedDept.value = deptNames;
// 更新设计器数据(双向同步)
if (designer?.state?.checkedNode) {
designer.state.checkedNode.componentProps.defaultValue = deptNames;
designer.state.checkedNode.componentProps.defaultValueClone = selectedDepts;
}
// 触发v-model更新确保表单能获取到值
emit("update:modelValue", deptNames);
isModalVisible.value = false;
// 清空临时数据,避免下次打开残留
selectDeptData.value = [];
selectedDeptIds.value = [];
};
// 关闭弹窗
const handleClose = () => {
isModalVisible.value = false;
selectDeptData.value = [];
selectedDeptIds.value = [];
};
// 显示在输入框的文本
const displayValue = computed(() => {
return selectedDept.value || "";
});
console.log(designer?.state, "= designer?.state=");
// 监听设计器选中状态变化
watch(
() => designer?.state.checkedNode?.field,
newField => {
isSelected.value = newField === props.componentSchema.field;
},
{ immediate: true }
);
// 监听modelValue变化外部更新时同步
watch(
() => props.modelValue,
newValue => {
selectedDept.value = newValue;
},
{ immediate: true }
);
// 监听按钮触发的全局事件
const handleGlobalTrigger = () => {
if (isSelected.value && !props.componentSchema.disabled) {
isModalVisible.value = true;
// 初始化已选数据(从设计器中读取)
const savedUsers = designer?.state?.checkedNode?.componentProps?.defaultValueClone || [];
if (savedUsers.length) {
selectDeptData.value = savedUsers;
selectedDeptIds.value = savedUsers.map((item: any) => item.id);
// 默认展开包含已选用户的部门(优化体验)
const deptIds = selectDeptData.value.map((dept: any) => dept.id);
defaultExpandedKeys.value = deptIds;
}
}
};
// 事件监听
onMounted(() => {
$Bus.on("trigger-dept-select-modal", handleGlobalTrigger);
});
// 清理事件监听
onUnmounted(() => {
$Bus.off("trigger-dept-select-modal", handleGlobalTrigger);
});
</script>
<style scoped lang="scss">
.user-select-component {
width: 100%;
}
// .el-input__inner {
// cursor: pointer;
// }
/* 选中状态样式 */
.selected {
// .el-input__inner {
// border-color: red;
// box-shadow: 0 0 0 2px rgb(64 158 255 / 20%);
// }
}
/* 弹窗样式 */
.user-select-container {
display: flex;
height: 400px;
padding: 10px;
margin-top: 20px;
border: 1px solid #eeeeee;
}
.dept-tree {
width: 300px;
padding-right: 20px;
overflow-y: auto;
border-right: 1px solid #eeeeeeee;
}
.search-input {
margin-bottom: 10px;
}
.user-transfer {
flex: 1;
padding-left: 20px;
overflow-y: auto; /* 增加滚动,避免用户过多溢出 */
}
</style>

View File

@@ -42,7 +42,6 @@ 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);
});

View File

@@ -313,7 +313,6 @@ async function initComponent() {
return;
}
console.log(innerSchema.type, "=innerSchema.type=");
// 内置组件
const cmp = pluginManager.getComponent(innerSchema.type);
// 内部不存在组件

View File

@@ -72,7 +72,7 @@ const getTreeData = computed({
console.log(e);
}
});
console.log(getTreeData, "=getTreeData=");
/**
* 通过label 过滤节点
* @param tree 节点树

View File

@@ -50,6 +50,7 @@ function handleAdd() {
label: "",
value: ""
};
console.log("1232323");
modelValueComputed.value?.push(option);
}
</script>

View File

@@ -2,6 +2,10 @@ import { type PluginManager } from "@/components/EpicDesigner/utils";
import MonacoEditor from "./MonacoEditor";
import Page from "./Page";
import Test from "../businessUi/Test";
// import TagInput from "./TagInput/index.vue";
// 1. 注册【配置类组件】:用于属性面板,不渲染到画布
export function setupComponent(pluginManager: PluginManager): void {
pluginManager.component("EInputSize", async () => await import("./EInputSize/index.vue"));
pluginManager.component("EColEditor", async () => await import("./EColEditor/index.vue"));
@@ -9,7 +13,6 @@ export function setupComponent(pluginManager: PluginManager): void {
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",
@@ -54,8 +57,7 @@ export function setupComponent(pluginManager: PluginManager): void {
// component: async () => await import("../components/designer/src/modules/attributeView/dataView.vue")
// });
const componentArray = [MonacoEditor, Page];
console.log(Page, "=============>");
const componentArray = [MonacoEditor, Page, Test];
componentArray.forEach(item => {
pluginManager.registerComponent(item);
});

View File

@@ -16,7 +16,7 @@ export default defineComponent({
const componentProps: Record<string, any> = {
...props.componentSchema?.componentProps
};
console.log(componentProps, "=========componentProps============");
return h(ElButton, componentProps, {
default: () => renderSlot(slots, "default", {}, () => [props.componentSchema?.label])
});

View File

@@ -16,6 +16,7 @@ export default defineComponent({
header: props.componentSchema?.label ?? ""
} as ComponentSchema;
console.log(attrs);
const children = componentSchema.children ?? [];
delete componentSchema.children;

View File

@@ -26,8 +26,6 @@ import Modal from "./modal";
import { ElFormItem, ElTabs, ElTabPane, ElCollapse, ElCollapseItem } from "element-plus";
export function setupElementPlus(pluginManager: PluginManager = pManager): void {
// console.log(pluginManager, "=pluginManager=");
console.log(1232323);
pluginManager.component("FormItem", ElFormItem);
pluginManager.component("Tabs", ElTabs);
pluginManager.component("TabPane", ElTabPane);
@@ -63,7 +61,6 @@ export function setupElementPlus(pluginManager: PluginManager = pManager): void
pluginManager.registerComponent(item);
pluginManager.addBaseComponentTypes(item.defaultSchema.type);
});
console.log(1232323);
// ui初始化完成。
pluginManager.setInitialized(true);
}

View File

@@ -39,6 +39,15 @@ const pageSchema = ref({
label: "输入框",
type: "input",
id: "input_4wujh0i3"
},
{
id: "epic-dept-select_qwefi7l4",
type: "input",
label: "人员选择11",
componentProps: {
placeholder: "请输入"
},
field: "epic-dept-select_qwefi7l4"
}
]
}