diff --git a/packages/view-design/public/assets/svg/er-artifacts.svg b/packages/view-design/public/assets/svg/er-artifacts.svg
new file mode 100644
index 0000000000000000000000000000000000000000..73d6f9482b6c017f262c13568f07f81b4777b4e4
--- /dev/null
+++ b/packages/view-design/public/assets/svg/er-artifacts.svg
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/packages/view-design/public/assets/svg/er-entity-list.svg b/packages/view-design/public/assets/svg/er-entity-list.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0794b8cb37cee95ca0ec82a1cc708f7d669e7452
--- /dev/null
+++ b/packages/view-design/public/assets/svg/er-entity-list.svg
@@ -0,0 +1,24 @@
+
+
\ No newline at end of file
diff --git a/packages/view-design/public/assets/svg/tab-container.svg b/packages/view-design/public/assets/svg/tab-container.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8bcf50e8e162ee43efeeff3629d1b1915d7e91e0
--- /dev/null
+++ b/packages/view-design/public/assets/svg/tab-container.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/packages/view-design/src/components/index.ts b/packages/view-design/src/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8d9bb06a9e32c65455b82b1ad04ed9a73d8e54f7
--- /dev/null
+++ b/packages/view-design/src/components/index.ts
@@ -0,0 +1,17 @@
+import { App } from 'vue';
+import IBizViewDesignMaterialPage from './view-design-material-page/view-design-material-page';
+import IBizViewDesignStructureTree from './view-design-structure-tree/view-design-structure-tree';
+import IBizViewDesignCollapseList from './view-design-collapse-list/view-design-collapse-list';
+import IBizViewDesignCollapseControl from './view-design-collapse-control/view-design-collapse-control';
+
+export default {
+ install(app: App): void {
+ app.component('IBizViewDesignMaterialPage', IBizViewDesignMaterialPage);
+ app.component('IBizViewDesignStructureTree', IBizViewDesignStructureTree);
+ app.component('IBizViewDesignCollapseList', IBizViewDesignCollapseList);
+ app.component(
+ 'IBizViewDesignCollapseControl',
+ IBizViewDesignCollapseControl,
+ );
+ },
+};
diff --git a/packages/view-design/src/components/view-design-collapse-control/view-design-collapse-control.scss b/packages/view-design/src/components/view-design-collapse-control/view-design-collapse-control.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d4253077aec2d2ce665e1af89fba0a9c86e8f6f0
--- /dev/null
+++ b/packages/view-design/src/components/view-design-collapse-control/view-design-collapse-control.scss
@@ -0,0 +1,30 @@
+/* stylelint-disable no-descending-specificity */
+/* stylelint-disable selector-class-pattern */
+@include b(view-design-collapse-control) {
+ &:not(:last-child) {
+ .el-collapse {
+ border-bottom: none;
+ }
+ }
+
+ .el-collapse {
+ --el-collapse-header-height: 40px;
+ --el-collapse-header-bg-color: transparent;
+ --el-collapse-content-bg-color: #{getCssVar(color, bg, 1)};
+ --el-collapse-header-text-color: #{getCssVar(color, text, 1)};
+ --el-collapse-header-font-size: 12px;
+
+ .el-collapse-item__header {
+ padding: 0 15px;
+ border: none;
+ }
+
+ .el-collapse-item__wrap {
+ border: none;
+ }
+
+ .el-collapse-item__content {
+ padding: 0;
+ }
+ }
+}
diff --git a/packages/view-design/src/components/view-design-collapse-control/view-design-collapse-control.tsx b/packages/view-design/src/components/view-design-collapse-control/view-design-collapse-control.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fef6d801d671b580ab3d8fa363ba88e7484b33f1
--- /dev/null
+++ b/packages/view-design/src/components/view-design-collapse-control/view-design-collapse-control.tsx
@@ -0,0 +1,42 @@
+import { defineComponent } from 'vue';
+import { useNamespace } from '@ibiz-template/vue3-util';
+import './view-design-collapse-control.scss';
+
+export default defineComponent({
+ name: 'IBizViewDesignCollapseControl',
+ props: {
+ title: {
+ type: String,
+ default: '',
+ },
+ },
+ setup() {
+ const ns = useNamespace('view-design-collapse-control');
+
+ return {
+ ns,
+ };
+ },
+ render() {
+ return (
+
+
+
+ {{
+ title: () => {
+ return (
+
+ );
+ },
+ }}
+
+
+
+ );
+ },
+});
diff --git a/packages/view-design/src/components/view-design-collapse-list/view-design-collapse-list.scss b/packages/view-design/src/components/view-design-collapse-list/view-design-collapse-list.scss
new file mode 100644
index 0000000000000000000000000000000000000000..633c44e5681f0cc33c94aa862c807b9c0b8563f5
--- /dev/null
+++ b/packages/view-design/src/components/view-design-collapse-list/view-design-collapse-list.scss
@@ -0,0 +1,30 @@
+/* stylelint-disable no-descending-specificity */
+/* stylelint-disable selector-class-pattern */
+@include b(view-design-collapse-list) {
+ &:not(:last-child) {
+ .el-collapse {
+ border-bottom: none;
+ }
+ }
+
+ .el-collapse {
+ --el-collapse-header-height: 40px;
+ --el-collapse-header-bg-color: transparent;
+ --el-collapse-content-bg-color: #{getCssVar(color, bg, 1)};
+ --el-collapse-header-text-color: #{getCssVar(color, text, 1)};
+ --el-collapse-header-font-size: 12px;
+
+ .el-collapse-item__header {
+ padding: 0 15px;
+ border: none;
+ }
+
+ .el-collapse-item__wrap {
+ border: none;
+ }
+
+ .el-collapse-item__content {
+ padding: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/view-design/src/components/view-design-collapse-list/view-design-collapse-list.tsx b/packages/view-design/src/components/view-design-collapse-list/view-design-collapse-list.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..34cf9476e38e61d8ddedd5b4c7afc48f14bcc14b
--- /dev/null
+++ b/packages/view-design/src/components/view-design-collapse-list/view-design-collapse-list.tsx
@@ -0,0 +1,42 @@
+import { defineComponent } from 'vue';
+import { useNamespace } from '@ibiz-template/vue3-util';
+import './view-design-collapse-list.scss';
+
+export default defineComponent({
+ name: 'IBizViewDesignCollapseList',
+ props: {
+ title: {
+ type: String,
+ default: '',
+ },
+ },
+ setup() {
+ const ns = useNamespace('view-design-collapse-list');
+
+ return {
+ ns,
+ };
+ },
+ render() {
+ return (
+
+
+
+ {{
+ title: () => {
+ return (
+
+ );
+ },
+ }}
+
+
+
+ );
+ },
+});
diff --git a/packages/view-design/src/components/view-design-material-page/view-design-material-page.scss b/packages/view-design/src/components/view-design-material-page/view-design-material-page.scss
new file mode 100644
index 0000000000000000000000000000000000000000..46c1e28bca3e35ab7aaa3bf0028cdc9e7c64c77b
--- /dev/null
+++ b/packages/view-design/src/components/view-design-material-page/view-design-material-page.scss
@@ -0,0 +1,148 @@
+/* stylelint-disable selector-class-pattern */
+@include b(view-design-material-page) {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background: rgba(var(#{getCssVarName(grey, 0)}), 1);
+
+ @include m(single) {
+ @include b(view-design-material-page-group-item) {
+ width: 100%;
+ }
+ }
+}
+
+@include b(view-design-material-page-header) {
+ display: flex;
+ flex: 0 0 auto;
+ align-items: center;
+ margin: 8px 0;
+}
+
+@include b(view-design-material-page-header-btn-group) {
+ display: flex;
+ flex: 0 0 auto;
+ align-items: center;
+ margin-right: 8px;
+ margin-left: 16px;
+}
+
+@include b(view-design-material-page-header-btn) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 2px;
+ font-size: 14px;
+ color: getCssVar(color, text, 3);
+ cursor: pointer;
+
+ > svg {
+ width: 1em;
+ height: 1em;
+ fill: currentcolor;
+ }
+
+ @include when(active) {
+ color: getCssVar(color, text, 1);
+ }
+}
+
+@include b(view-design-material-page-header-search-btn) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 26px;
+ color: getCssVar(color, text, 3);
+ cursor: pointer;
+}
+
+@include b(view-design-material-page-header-search) {
+ flex: 1 1 0;
+ padding-right: 10px;
+
+ > .el-input {
+ --el-input-height: 26px;
+
+ .el-input__inner::placeholder {
+ font-size: 12px;
+ }
+ }
+}
+
+@include b(view-design-material-page-content) {
+ flex: 1 1 0;
+ overflow: auto;
+
+ .el-collapse {
+ --el-collapse-header-height: 28px;
+ --el-collapse-header-bg-color: transparent;
+ --el-collapse-content-bg-color: #{getCssVar(color, bg, 1)};
+ --el-collapse-header-text-color: #{getCssVar(color, text, 1)};
+ --el-collapse-header-font-size: 12px;
+
+ border: none;
+
+ .el-collapse-item__header {
+ border: none;
+ }
+
+ .el-collapse-item__wrap {
+ border: none;
+ }
+
+ .el-collapse-item__content {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0;
+ }
+ }
+}
+
+@include b(view-design-material-page-group-header) {
+ padding-left: 6px;
+ overflow: hidden;
+ font-size: 12px;
+ color: getCssVar(color, text, 1);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+@include b(view-design-material-page-group-item) {
+ width: calc(100% / 3);
+ height: 80px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ @include e(icon) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 56px;
+ height: 56px;
+ margin: 0 auto;
+
+ ion-icon {
+ font-size: 56px;
+ }
+
+ img {
+ max-width: 100%;
+ }
+ }
+
+ @include e(text) {
+ padding: 0 4px;
+ overflow: hidden;
+ line-height: 20px;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+}
+
+@include b(view-design-material-page-group-item-content) {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+}
diff --git a/packages/view-design/src/components/view-design-material-page/view-design-material-page.tsx b/packages/view-design/src/components/view-design-material-page/view-design-material-page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a6ce3b59594042e1d324ddb1dddd76ee49243858
--- /dev/null
+++ b/packages/view-design/src/components/view-design-material-page/view-design-material-page.tsx
@@ -0,0 +1,287 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable no-return-assign */
+import { PropType, defineComponent, onMounted, ref } from 'vue';
+import { useClickOutside, useNamespace } from '@ibiz-template/vue3-util';
+import { IDEFormGroupPanel } from '@ibiz/model-core';
+import { resource } from '../../global';
+import './view-design-material-page.scss';
+
+export default defineComponent({
+ name: 'IBizViewDesignMaterialPage',
+ props: {
+ groups: {
+ type: Array as PropType,
+ default: () => [],
+ },
+ columnMode: {
+ type: String as PropType<'multiple' | 'single'>,
+ default: 'multiple',
+ },
+ activeGroups: {
+ type: Array as PropType,
+ default: () => [],
+ },
+ },
+ setup(props) {
+ const ns = useNamespace('view-design-material-page');
+
+ // 收缩面板值
+ const collapseValue = ref(props.groups.map(group => group.codeName));
+
+ // 搜索值
+ const searchValue = ref('');
+
+ // 搜索框容器元素
+ const search = ref();
+
+ // 是否展示搜索框
+ const isShowSearch = ref(false);
+
+ onMounted(() => {
+ if (search.value) {
+ useClickOutside(search as any, (): void => {
+ isShowSearch.value = false;
+ });
+ }
+ });
+
+ // 收缩分组
+ const collapse = (): void => {
+ collapseValue.value = [];
+ };
+
+ // 展开分组
+ const expand = (): void => {
+ collapseValue.value = props.groups.map(group => group.codeName);
+ };
+
+ // 渲染图标
+ const renderIcon = (icon?: string): any => {
+ if (icon) {
+ if (icon.startsWith('http')) {
+ return
;
+ }
+ if (icon.endsWith('')) {
+ const svg = URL.createObjectURL(
+ new Blob([icon], { type: 'image/svg+xml;charset=utf-8' }),
+ );
+ return
;
+ }
+ if (icon.startsWith('data:image')) {
+ return
;
+ }
+ }
+ return (
+
+ );
+ };
+
+ return {
+ ns,
+ collapseValue,
+ searchValue,
+ isShowSearch,
+ search,
+ collapse,
+ expand,
+ renderIcon,
+ };
+ },
+ render() {
+ const isExpandAll = this.groups.every(group =>
+ this.collapseValue.includes(group.codeName),
+ );
+ return (
+
+
+
+
+ {this.groups.map(group => {
+ if (!this.activeGroups.includes(group.codeName!)) {
+ return;
+ }
+ return (
+
+ {{
+ title: () => {
+ return (
+
+ );
+ },
+ default: () => {
+ return group.deformDetails?.map(item => {
+ if (this.searchValue) {
+ const regExp = this.searchValue.trim().toLowerCase();
+ const text = item.caption?.toLowerCase();
+ if (text && text.indexOf(regExp) === -1) {
+ return;
+ }
+ }
+ return (
+
+
+
+ {this.renderIcon(item.sysImage?.imagePath)}
+
+
+ {item.caption || ''}
+
+
+
+ );
+ });
+ },
+ }}
+
+ );
+ })}
+
+
+
+ );
+ },
+});
diff --git a/packages/view-design/src/components/view-design-structure-tree/view-design-structure-tree.scss b/packages/view-design/src/components/view-design-structure-tree/view-design-structure-tree.scss
new file mode 100644
index 0000000000000000000000000000000000000000..02e0dd6d1e7b113d691db2f7e9442e985d3a19c9
--- /dev/null
+++ b/packages/view-design/src/components/view-design-structure-tree/view-design-structure-tree.scss
@@ -0,0 +1,35 @@
+/* stylelint-disable selector-class-pattern */
+@include b(view-design-structure-tree) {
+ width: 100%;
+ height: 100%;
+ background: rgba(var(#{getCssVarName(grey, 0)}), 1);
+}
+
+@include b(view-design-structure-tree-search) {
+ padding: 2px 10px 2px 0;
+ margin-left: 8px;
+
+ > .el-input {
+ --el-input-height: 26px;
+
+ .el-input__inner::placeholder {
+ font-size: 12px;
+ }
+ }
+}
+
+@include b(view-design-structure-tree-header) {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 12px 0 28px;
+ font-size: 12px;
+ line-height: 28px;
+
+ @include e(action) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ }
+}
diff --git a/packages/view-design/src/components/view-design-structure-tree/view-design-structure-tree.tsx b/packages/view-design/src/components/view-design-structure-tree/view-design-structure-tree.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e3e70bb1a70c063cfd4edfaab163bb5771300a10
--- /dev/null
+++ b/packages/view-design/src/components/view-design-structure-tree/view-design-structure-tree.tsx
@@ -0,0 +1,87 @@
+import { defineComponent, ref } from 'vue';
+import { useNamespace } from '@ibiz-template/vue3-util';
+import './view-design-structure-tree.scss';
+
+export default defineComponent({
+ name: 'IBizViewDesignStructureTree',
+ setup() {
+ const ns = useNamespace('view-design-structure-tree');
+
+ // 搜索值
+ const searchValue = ref('');
+
+ return {
+ ns,
+ searchValue,
+ };
+ },
+ render() {
+ return (
+
+
+
+ {{
+ prefix: () => {
+ return ;
+ },
+ }}
+
+
+
+
+
+
+
+ );
+ },
+});
diff --git a/packages/view-design/src/global.ts b/packages/view-design/src/global.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0fa10431b9670ddb5003e3e2fafbf06ce1dfd368
--- /dev/null
+++ b/packages/view-design/src/global.ts
@@ -0,0 +1,7 @@
+// 文件路径: [插件项目的根目录]/src/global.ts
+
+import { PluginStaticResource } from '@ibiz-template/runtime';
+
+const resource = new PluginStaticResource(import.meta.url);
+
+export { resource };
diff --git a/packages/view-design/src/index.ts b/packages/view-design/src/index.ts
index b17714e6dce268bfc55bd5ae098115ddabdba84a..a35de14952cc3d530f28b20c54751850f5ad9f76 100644
--- a/packages/view-design/src/index.ts
+++ b/packages/view-design/src/index.ts
@@ -3,10 +3,14 @@
import { App } from 'vue';
import Views from './views';
import Services from './service';
+import PanelItems from './panel-items';
+import Components from './components';
export default {
install(app: App) {
app.use(Views);
app.use(Services);
+ app.use(PanelItems);
+ app.use(Components);
},
};
diff --git a/packages/view-design/src/panel-items/index.ts b/packages/view-design/src/panel-items/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bd679f7c165d9d98dd4d565acb4a3925590fe61a
--- /dev/null
+++ b/packages/view-design/src/panel-items/index.ts
@@ -0,0 +1,10 @@
+import { App } from 'vue';
+import ViewDesignMaterialTree from './view-design-material-tree';
+import ViewDesignMaterialTab from './view-design-material-tab';
+
+export default {
+ install(app: App): void {
+ app.use(ViewDesignMaterialTree);
+ app.use(ViewDesignMaterialTab);
+ },
+};
diff --git a/packages/view-design/src/panel-items/view-design-material-tab/index.ts b/packages/view-design/src/panel-items/view-design-material-tab/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a02be406e0a834ba669b56506f8035a68b9f0e85
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tab/index.ts
@@ -0,0 +1,17 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { App } from 'vue';
+import { registerPanelItemProvider } from '@ibiz-template/runtime';
+import ViewDesignMaterialTab from './view-design-material-tab';
+import { ViewDesignMaterialTabProvider } from './view-design-material-tab.provider';
+
+export default {
+ install(app: App) {
+ // 注册组件
+ app.component('IBizViewDesignMaterialTab', ViewDesignMaterialTab);
+ registerPanelItemProvider(
+ 'RAWITEM_VIEW_DESIGN_MATERIAL_TAB',
+ () => new ViewDesignMaterialTabProvider(),
+ );
+ },
+};
diff --git a/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.controller.ts b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc3da886c8daa9e73a59b2d623549e7363523fb8
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.controller.ts
@@ -0,0 +1,174 @@
+import {
+ PanelItemController,
+ calcLayoutHeightWidth,
+ getControl,
+} from '@ibiz-template/runtime';
+import {
+ IDEEditForm,
+ IDEFormGroupPanel,
+ IDEFormPage,
+ IDEFormTabPanel,
+} from '@ibiz/model-core';
+import { ViewDesignMaterialTabState } from './view-design-material-tab.state';
+import { ViewDesignMaterialTreeController } from '../view-design-material-tree/view-design-material-tree.controller';
+
+export class ViewDesignMaterialTabController extends PanelItemController {
+ declare state: ViewDesignMaterialTabState;
+
+ protected createState(): ViewDesignMaterialTabState {
+ return new ViewDesignMaterialTabState(this.parent?.state);
+ }
+
+ get materialForm(): IDEEditForm {
+ return getControl(this.panel.view.model, 'material') as IDEEditForm;
+ }
+
+ get viewDesignMaterialTree(): ViewDesignMaterialTreeController | undefined {
+ return this.panel.panelItems
+ .view_design_material_tree as ViewDesignMaterialTreeController;
+ }
+
+ protected async onInit(): Promise {
+ await super.onInit();
+ this.initItems();
+ }
+
+ /**
+ * 初始化项数据
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:21
+ * @return {*} {void}
+ */
+ initItems(): void {
+ const formPages = this.materialForm.deformPages;
+ if (!formPages) {
+ return;
+ }
+ const pages: IDEFormPage[] = [];
+ formPages.forEach(formPage => {
+ pages.push(formPage);
+ const details = formPage.deformDetails;
+ details?.forEach(detail => {
+ if (detail.detailType === 'TABPANEL') {
+ const tabPages = (detail as IDEFormTabPanel).deformTabPages;
+ if (tabPages) {
+ pages.push(...tabPages);
+ }
+ }
+ });
+ });
+ const materialPage = pages.find(page => page.codeName === 'controls');
+ if (materialPage) {
+ const details = materialPage.deformDetails;
+ const groups: IDEFormGroupPanel[] = [];
+ details?.forEach(detail => {
+ if (
+ detail.detailType === 'GROUPPANEL' &&
+ detail.codeName?.toUpperCase() !== 'HIDDENGROUP'
+ ) {
+ groups.push(detail);
+ }
+ });
+ this.state.pageItems = formPages?.map(page => {
+ return {
+ id: page.codeName === 'toolkit' ? 'controls' : page.codeName || '',
+ text: page.caption || '',
+ imagePath: page.sysImage?.imagePath,
+ };
+ });
+ this.state.menuItems.push(
+ ...groups.map(group => {
+ return {
+ id: group.codeName || '',
+ text: group.caption || '',
+ };
+ }),
+ );
+ this.state.activeItems.push(...groups.map(group => group.codeName || ''));
+ }
+ }
+
+ /**
+ * 处理选中改变
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:47
+ * @param {string} value
+ * @return {*} {void}
+ */
+ onSelect(value: string): void {
+ if (!this.viewDesignMaterialTree) {
+ return;
+ }
+ if (this.state.pageItems.some(page => page.id === value)) {
+ const parent = this.viewDesignMaterialTree.parent;
+ if (!parent) {
+ return;
+ }
+ const { layoutPos } = parent.model;
+ if (!layoutPos) {
+ return;
+ }
+ const { width } = calcLayoutHeightWidth(layoutPos);
+ parent.state.layout.width = `${width}`;
+ this.state.columnMode = 'multiple';
+ this.viewDesignMaterialTree.state.columnMode = this.state.columnMode;
+ this.viewDesignMaterialTree.state.activePage = value;
+ return;
+ }
+ this.viewDesignMaterialTree.state.activePage = 'controls';
+ if (value === '$multiple' || value === '$single') {
+ const parent = this.viewDesignMaterialTree.parent;
+ if (!parent) {
+ return;
+ }
+ const { layoutPos } = parent.model;
+ if (!layoutPos) {
+ return;
+ }
+ const { width } = calcLayoutHeightWidth(layoutPos);
+ if (value === '$multiple') {
+ parent.state.layout.width = `${width}`;
+ }
+ if (value === '$single') {
+ parent.state.layout.width = `calc(${width} / 3)`;
+ }
+ this.state.columnMode = value.slice(1) as 'multiple' | 'single';
+ this.viewDesignMaterialTree.state.columnMode = this.state.columnMode;
+ return;
+ }
+ if (value === '$component') {
+ this.state.activeItems = [];
+ this.viewDesignMaterialTree.state.activeGroups = [
+ ...this.state.activeItems,
+ ];
+ return;
+ }
+ if (value === '$all') {
+ const result = this.state.menuItems.every(item =>
+ this.state.activeItems.includes(item.id),
+ );
+ if (!result) {
+ this.state.activeItems = this.state.menuItems.map(item => item.id);
+ } else {
+ this.state.activeItems = this.state.menuItems[0]?.id
+ ? [this.state.menuItems[0].id]
+ : [];
+ }
+ this.viewDesignMaterialTree.state.activeGroups = [
+ ...this.state.activeItems,
+ ];
+ return;
+ }
+ const index = this.state.activeItems.findIndex(v => v === value);
+ if (index !== -1) {
+ this.state.activeItems.splice(index, 1);
+ } else {
+ this.state.activeItems.push(value);
+ }
+ this.viewDesignMaterialTree.state.activeGroups = [
+ ...this.state.activeItems,
+ ];
+ }
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.provider.ts b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.provider.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fff0daa78197a35db6801351841fb71fd2f7fe8c
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.provider.ts
@@ -0,0 +1,21 @@
+import {
+ IPanelItemController,
+ IPanelItemProvider,
+ IPanelController,
+} from '@ibiz-template/runtime';
+import { IPanelItem } from '@ibiz/model-core';
+import { ViewDesignMaterialTabController } from './view-design-material-tab.controller';
+
+export class ViewDesignMaterialTabProvider implements IPanelItemProvider {
+ component = 'IBizViewDesignMaterialTab';
+
+ async createController(
+ panelItem: IPanelItem,
+ panel: IPanelController,
+ parent?: IPanelItemController,
+ ): Promise {
+ const c = new ViewDesignMaterialTabController(panelItem, panel, parent);
+ await c.init();
+ return c;
+ }
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.scss b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.scss
new file mode 100644
index 0000000000000000000000000000000000000000..8b2a0e1063b21c4e44b7107aa7f38c56fdf30fd4
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.scss
@@ -0,0 +1,159 @@
+/* stylelint-disable selector-class-pattern */
+@include b(view-design-material-tab) {
+ display: flex;
+ align-items: center;
+ padding: 0 16px 0 10px;
+
+ .el-menu {
+ --el-menu-hover-text-color: #{getCssVar(color, primary, hover)};
+ --el-menu-hover-bg-color: #{getCssVar(color, primary, light, default)};
+ --el-menu-text-color: #{getCssVar(color, text, 2)};
+
+ &.el-menu--horizontal.el-menu {
+ --el-menu-horizontal-height: 24px;
+ --el-menu-item-height: 24px;
+ --el-menu-icon-width: 20px;
+ --el-menu-base-level-padding: #{getCssVar('spacing', 'tight')};
+
+ border: none;
+
+ @include b(view-design-material-tab-menu-item) {
+ @include m(node) {
+ margin-left: getCssVar('spacing', 'base-loose');
+ border-radius: getCssVar(border, radius, small);
+ }
+ }
+ }
+
+ &.el-menu--horizontal > .el-menu-item {
+ border-bottom: none;
+ }
+
+ &.el-menu--horizontal > .el-menu-item.is-active {
+ border-bottom: none;
+ }
+
+ &.el-menu--horizontal .el-menu-item:not(:hover):focus {
+ background-color: transparent;
+ }
+
+ &.el-menu--horizontal > .el-sub-menu {
+ margin-right: 20px;
+ }
+
+ &.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
+ color: var(--el-menu-text-color);
+ border: none;
+ border-radius: 4px;
+
+ &:hover {
+ background-color: var(--el-menu-hover-bg-color);
+ }
+ }
+ }
+}
+
+@include b(view-design-material-tab-menu-header) {
+ display: flex;
+ align-items: center;
+
+ @include e(icon) {
+ display: flex;
+ flex: 0 0 auto;
+ align-items: center;
+ justify-content: center;
+ }
+
+ @include e(text) {
+ margin-left: 6px;
+ }
+}
+
+@include b(view-design-material-tab-page-item) {
+ border-radius: 4px;
+
+ @include e(icon) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 6px;
+ color: var(--el-menu-text-color);
+ }
+
+ @include e(text) {
+ margin-right: 6px;
+ color: var(--el-menu-text-color);
+ }
+}
+
+@include b(view-design-material-tab-sub-menu-header) {
+ display: flex;
+ align-items: center;
+
+ @include e(text) {
+ color: var(--el-menu-text-color);
+ }
+}
+
+@include b(view-design-material-tab-menu-item) {
+ @include e(icon) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 4px;
+ color: var(--el-menu-text-color);
+ }
+
+ @include e(text) {
+ margin-right: 4px;
+ color: var(--el-menu-text-color);
+ }
+
+ @include e(select-icon) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: auto;
+ visibility: hidden;
+ }
+
+ @include m(active) {
+ color: var(--el-menu-hover-text-color);
+ background-color: var(--el-menu-hover-bg-color);
+
+ @include e(select-icon) {
+ visibility: visible;
+ }
+ }
+}
+
+@include b(view-design-material-tab-popper) {
+ --el-menu-horizontal-sub-item-height: 32px;
+ --el-menu-base-level-padding: #{getCssVar('spacing', 'base-tight')};
+ --el-menu-hover-text-color: #{getCssVar(color, primary, hover)};
+ --el-menu-hover-bg-color: #{getCssVar(color, primary, light, default)};
+ --el-menu-text-color: #{getCssVar(color, text, 2)};
+
+ .el-menu--popup {
+ min-width: 120px;
+ }
+
+ &.el-menu--horizontal .el-menu {
+ @include b(view-design-material-tab-menu-item) {
+ @include m(active) {
+ color: var(--el-menu-hover-text-color);
+ background-color: var(--el-menu-hover-bg-color);
+
+ @include e(select-icon) {
+ visibility: visible;
+ }
+ }
+ }
+ }
+
+ &.el-menu--horizontal .el-menu .el-sub-menu__title {
+ &:hover {
+ background-color: var(--el-menu-hover-bg-color);
+ }
+ }
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.state.ts b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.state.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7f41c98dc8640f86371ea0ad479e755987a236ea
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.state.ts
@@ -0,0 +1,39 @@
+import { PanelItemState } from '@ibiz-template/runtime';
+
+export class ViewDesignMaterialTabState extends PanelItemState {
+ /**
+ * 分页项
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:24
+ * @type {{ id: string; text: string; imagePath?: string }[]}
+ */
+ pageItems: { id: string; text: string; imagePath?: string }[] = [];
+
+ /**
+ * 菜单项
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:35
+ * @type {{ id: string; text: string }[]}
+ */
+ menuItems: { id: string; text: string }[] = [];
+
+ /**
+ * 激活菜单项
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:46
+ * @type {string[]}
+ */
+ activeItems: string[] = [];
+
+ /**
+ * 布局模式
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:57
+ * @type {('multiple' | 'single')}
+ */
+ columnMode: 'multiple' | 'single' = 'multiple';
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.tsx b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bb5b8a2d568a304f0d41f720ef7667fa4df626ab
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tab/view-design-material-tab.tsx
@@ -0,0 +1,242 @@
+import { PropType, defineComponent } from 'vue';
+import { useNamespace } from '@ibiz-template/vue3-util';
+import { IPanelItem } from '@ibiz/model-core';
+import { ViewDesignMaterialTabController } from './view-design-material-tab.controller';
+import { resource } from '../../global';
+import './view-design-material-tab.scss';
+
+export default defineComponent({
+ name: 'IBizViewDesignMaterialTab',
+ props: {
+ modelData: {
+ type: Object as PropType,
+ required: true,
+ },
+ controller: {
+ type: ViewDesignMaterialTabController,
+ required: true,
+ },
+ },
+ setup() {
+ const ns = useNamespace('view-design-material-tab');
+
+ return {
+ ns,
+ };
+ },
+ render() {
+ const renderSelectIcon = () => (
+
+ );
+ return (
+
+
this.controller.onSelect(value)}
+ >
+ {this.controller.state.pageItems.map(page => {
+ if (page.id === 'controls') {
+ return (
+ {
+ this.controller.onSelect('$multiple');
+ const result = this.controller.state.menuItems.every(item =>
+ this.controller.state.activeItems.includes(item.id),
+ );
+ if (!result) {
+ this.controller.onSelect('$all');
+ }
+ }}
+ >
+ {{
+ title: () => {
+ return (
+
+ );
+ },
+ default: () => {
+ return [
+
+ {{
+ title: () => {
+ return (
+
+ );
+ },
+ default: () => {
+ return [
+
+
+
+
+ {renderSelectIcon()}
+
+ ,
+
+
+
+
+ {renderSelectIcon()}
+
+ ,
+ ];
+ },
+ }}
+ ,
+
+ this.controller.state.activeItems.includes(
+ item.id,
+ ),
+ ) && this.ns.bm('menu-item', 'active'),
+ ]}
+ >
+
+
+ ,
+ this.controller.state.menuItems.map(item => {
+ return (
+
+
+
+
+ );
+ }),
+
+
+
+ ,
+ ];
+ },
+ }}
+
+ );
+ }
+
+ return (
+
+
+
})
+
+ {page.text}
+
+ );
+ })}
+
+
+ );
+ },
+});
diff --git a/packages/view-design/src/panel-items/view-design-material-tree/index.ts b/packages/view-design/src/panel-items/view-design-material-tree/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..002c2a8fbcbee1e4331d4441da5aeee452b04507
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tree/index.ts
@@ -0,0 +1,15 @@
+import { App } from 'vue';
+import { registerPanelItemProvider } from '@ibiz-template/runtime';
+import ViewDesignMaterialTree from './view-design-material-tree';
+import { ViewDesignMaterialTreeProvider } from './view-design-material-tree.provider';
+
+export default {
+ install(app: App): void {
+ // 注册组件
+ app.component('IBizViewDesignMaterialTree', ViewDesignMaterialTree);
+ registerPanelItemProvider(
+ 'RAWITEM_VIEW_DESIGN_MATERIAL_TREE',
+ () => new ViewDesignMaterialTreeProvider(),
+ );
+ },
+};
diff --git a/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.controller.state.ts b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.controller.state.ts
new file mode 100644
index 0000000000000000000000000000000000000000..61973f5a8719011e25103e5f519c157a7974a45d
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.controller.state.ts
@@ -0,0 +1,30 @@
+import { PanelItemState } from '@ibiz-template/runtime';
+
+export class ViewDesignMaterialTreeState extends PanelItemState {
+ /**
+ * 布局模式
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:52
+ * @type {('multiple' | 'single')}
+ */
+ columnMode: 'multiple' | 'single' = 'multiple';
+
+ /**
+ * 激活分组
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:02
+ * @type {string[]}
+ */
+ activeGroups: string[] = [];
+
+ /**
+ * 激活分页
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:15
+ * @type {string}
+ */
+ activePage: string = '';
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.controller.ts b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e748fde8f0d9795049f944b9e8e85618cd70327b
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.controller.ts
@@ -0,0 +1,73 @@
+import { PanelItemController, getControl } from '@ibiz-template/runtime';
+import {
+ IDEEditForm,
+ IDEFormGroupPanel,
+ IDEFormPage,
+ IDEFormTabPanel,
+} from '@ibiz/model-core';
+import { clone } from 'ramda';
+import { ViewDesignMaterialTreeState } from './view-design-material-tree.controller.state';
+
+export class ViewDesignMaterialTreeController extends PanelItemController {
+ declare state: ViewDesignMaterialTreeState;
+
+ protected createState(): ViewDesignMaterialTreeState {
+ return new ViewDesignMaterialTreeState(this.parent?.state);
+ }
+
+ get materialForm(): IDEEditForm {
+ return getControl(this.panel.view.model, 'material') as IDEEditForm;
+ }
+
+ /**
+ * 素材分组
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 20:09:44
+ * @type {IDEFormGroupPanel[]}
+ */
+ materialGroups: IDEFormGroupPanel[] = [];
+
+ protected async onInit(): Promise {
+ await super.onInit();
+ this.initMaterialGroups();
+ this.state.activeGroups = this.materialGroups.map(group => group.codeName!);
+ }
+
+ /**
+ * 初始化素材分组
+ *
+ * @author zhanghengfeng
+ * @date 2024-09-25 21:09:04
+ */
+ initMaterialGroups(): void {
+ const formPages = this.materialForm.deformPages;
+ const pages: IDEFormPage[] = [];
+ formPages?.forEach(formPage => {
+ pages.push(formPage);
+ const details = formPage.deformDetails;
+ details?.forEach(detail => {
+ if (detail.detailType === 'TABPANEL') {
+ const tabPages = (detail as IDEFormTabPanel).deformTabPages;
+ if (tabPages) {
+ pages.push(...tabPages);
+ }
+ }
+ });
+ });
+ const materialPage = pages.find(page => page.codeName === 'controls');
+ if (materialPage) {
+ const details = materialPage.deformDetails;
+ const groups: IDEFormGroupPanel[] = [];
+ details?.forEach(detail => {
+ if (
+ detail.detailType === 'GROUPPANEL' &&
+ detail.codeName?.toUpperCase() !== 'HIDDENGROUP'
+ ) {
+ groups.push(detail);
+ }
+ });
+ this.materialGroups = clone(groups);
+ }
+ }
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.provider.ts b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.provider.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fc8396542f71b560c7c36a1646704cd49f488a52
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.provider.ts
@@ -0,0 +1,21 @@
+import {
+ IPanelItemController,
+ IPanelItemProvider,
+ IPanelController,
+} from '@ibiz-template/runtime';
+import { IPanelItem } from '@ibiz/model-core';
+import { ViewDesignMaterialTreeController } from './view-design-material-tree.controller';
+
+export class ViewDesignMaterialTreeProvider implements IPanelItemProvider {
+ component = 'IBizViewDesignMaterialTree';
+
+ async createController(
+ panelItem: IPanelItem,
+ panel: IPanelController,
+ parent?: IPanelItemController,
+ ): Promise {
+ const c = new ViewDesignMaterialTreeController(panelItem, panel, parent);
+ await c.init();
+ return c;
+ }
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.scss b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.scss
new file mode 100644
index 0000000000000000000000000000000000000000..fbe6416b0f7ccbc095aa5c1afe388f5aaa1fa2df
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.scss
@@ -0,0 +1,12 @@
+@include b(view-design-material-tree) {
+ width: 100%;
+ height: 100%;
+ border: 1px solid getCssVar(color, border);
+}
+
+@include b(view-design-material-tree-caption) {
+ padding: 0 10px;
+ font-size: 18px;
+ line-height: 40px;
+ cursor: pointer;
+}
diff --git a/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.tsx b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c1234fd4f6b064edc7d3f5a8fcd0008d4a260055
--- /dev/null
+++ b/packages/view-design/src/panel-items/view-design-material-tree/view-design-material-tree.tsx
@@ -0,0 +1,73 @@
+import { PropType, defineComponent, ref } from 'vue';
+import { useNamespace } from '@ibiz-template/vue3-util';
+import { IPanelItem } from '@ibiz/model-core';
+import { ViewDesignMaterialTreeController } from './view-design-material-tree.controller';
+import './view-design-material-tree.scss';
+
+export default defineComponent({
+ name: 'IBizViewDesignMaterialTree',
+ props: {
+ modelData: {
+ type: Object as PropType,
+ required: true,
+ },
+ controller: {
+ type: ViewDesignMaterialTreeController,
+ required: true,
+ },
+ },
+ setup() {
+ const ns = useNamespace('view-design-material-tree');
+
+ // 分割值
+ const splitValue = ref(0.5);
+
+ return {
+ ns,
+ splitValue,
+ };
+ },
+ render() {
+ if (this.controller.state.activePage === 'viewdesign') {
+ return (
+
+
+ {{
+ top: () => {
+ return (
+
+ );
+ },
+ bottom: () => {
+ return (
+
+
+ {this.controller.panel.view?.state?.caption || ''}
+
+
+
+
+
+ );
+ },
+ }}
+
+
+ );
+ }
+ return (
+
+
+
+ );
+ },
+});