From ac96225e2d2c8c636bdda3efc2427f1484b440ba Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Tue, 12 May 2026 15:52:56 +0800 Subject: [PATCH] =?UTF-8?q?refactor(X-Pack):=20=E5=AF=B9=E6=9D=83=E9=99=90?= =?UTF-8?q?=E4=BD=93=E7=B3=BB=E8=BF=9B=E8=A1=8C=E9=87=8D=E6=9E=84-2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mcp.json | 14 ++ CLAUDE.md | 143 ++++++++++++++++++ .../io/dataease/menu/manage/MenuManage.java | 3 + .../main/resources/i18n/core_en_US.properties | 3 + .../main/resources/i18n/core_zh_CN.properties | 3 + .../main/resources/i18n/core_zh_TW.properties | 3 + .../src/assets/svg/icon_ban_filled.svg | 3 + .../drawer-filter/src/DrawerTreeFilter.vue | 43 +++++- .../components/drawer-main/src/DrawerMain.vue | 17 ++- .../components/grid-table/src/GridTable.vue | 2 +- core/core-frontend/src/locales/en.ts | 25 ++- core/core-frontend/src/locales/tw.ts | 14 +- core/core-frontend/src/locales/zh-CN.ts | 25 ++- core/core-frontend/src/permission.ts | 1 - .../src/views/system/parameter/index.vue | 13 +- dbhub.toml | 8 + .../api/permissions/org/api/SysOrgApi.java | 51 +++++++ .../api/permissions/org/dto/OrgCreator.java | 3 + .../org/dto/SysOrgMemberAddRequest.java | 25 +++ .../org/dto/SysOrgMemberRequest.java | 27 ++++ .../dto/SysOrgMemberRoleSwitchRequest.java | 25 +++ .../org/dto/SysOrgMemberRowDTO.java | 24 +++ .../org/dto/SysOrgRoleOptionRequest.java | 18 +++ .../permissions/org/vo/SysOrgMemberVO.java | 50 ++++++ .../org/vo/SysOrgRoleOptionVO.java | 24 +++ .../api/permissions/role/dto/RoleRequest.java | 5 + .../api/permissions/user/dto/UserCreator.java | 2 + .../permissions/user/dto/UserGridRequest.java | 2 + .../api/permissions/user/vo/UserFormVO.java | 4 + 29 files changed, 559 insertions(+), 21 deletions(-) create mode 100644 .mcp.json create mode 100644 CLAUDE.md create mode 100644 core/core-frontend/src/assets/svg/icon_ban_filled.svg create mode 100644 dbhub.toml create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/api/SysOrgApi.java create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/dto/SysOrgMemberAddRequest.java create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/dto/SysOrgMemberRequest.java create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/dto/SysOrgMemberRoleSwitchRequest.java create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/dto/SysOrgMemberRowDTO.java create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/dto/SysOrgRoleOptionRequest.java create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/vo/SysOrgMemberVO.java create mode 100644 sdk/api/api-permissions/src/main/java/io/dataease/api/permissions/org/vo/SysOrgRoleOptionVO.java diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000000..c499ff4157 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,14 @@ +{ + "mcpServers": { + "dbhub": { + "type": "stdio", + "command": "npx", + "args": [ + "@bytebase/dbhub@latest", + "--config", + "./dbhub.toml" + ], + "env": {} + } + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..8c00c2fb18 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,143 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +DataEase is an open-source BI tool by FIT2Cloud for data analysis and visualization via drag-and-drop chart creation. Version 3.0.0, GPL v3 license. + +**Branch:** `dev-v3` (current), PRs target `dev-v2` + +## Tech Stack + +- **Backend:** Java 21, Spring Boot 3.3.x, JPA (Hibernate) + QueryDSL, JDK 21 +- **Frontend:** Vue 3, TypeScript, Vite, Element Plus, ECharts, AntV (G2, L7, S2) +- **DB:** MySQL 8 (primary), also PostgreSQL, Oracle, SQL Server, DM, Kingbase, GreatSQL, H2 (desktop) +- **Other:** Knife4j (API docs), EhCache, Quartz + +## Module Structure + +``` +dataease/ +├── core/ +│ ├── core-backend/ # Spring Boot app (main artifact: CoreApplication.jar) +│ └── core-frontend/ # Vue 3 SPA +├── sdk/ # Shared SDK (common, api, extensions, distributed) +├── de-xpack/ # Enterprise/premium features (separate git repo) +├── drivers/ # JDBC drivers +└── installer/ # Docker Compose, deployment scripts +``` + +## Backend Domain Structure + +Each business domain under `core/core-backend/src/main/java/io/dataease//`: + +``` +/ +├── dao/auto/entity/ # JPA @Entity classes +├── auto/repository/ # JpaRepository interfaces +├── dao/ext/po/ # QueryDSL projection POJOs +├── dao/ext/mapper/ # Custom repository interfaces +├── dto/ # Data Transfer Objects +├── bo/ # Business Objects +├── manage/ # Business logic (@Component) +├── server/ # API implementations (implements sdk interfaces) +├── request/ # Request body classes +└── utils/ # Domain utilities +``` + +## Key Commands + +### Build + +```bash +# Full build (all modules) +mvn clean install + +# Build community standalone JAR +cd core && mvn clean package -Pstandalone -U -Dmaven.test.skip=true +# Output: core/core-backend/target/CoreApplication.jar + +# Build desktop edition +cd core && mvn clean package -Pdesktop -U -Dmaven.test.skip=true + +# Build enterprise/distributed edition +cd core && mvn clean package -Pdistributed -U -Dmaven.test.skip=true +``` + +### Run + +```bash +# Direct JAR execution +java -jar core/core-backend/target/CoreApplication.jar + +# Docker (recommended for testing) +curl -sSL https://dataease.oss-cn-hangzhou.aliyuncs.com/quick_start_v2.sh | bash +# Default credentials: admin / DataEase@123456 + +# Local installer +cd installer && /bin/bash install.sh +``` + +### Frontend Dev + +```bash +cd core/core-frontend +npm run dev # Dev server +npm run build:distributed # Production build +``` + +### QueryDSL Regeneration + +```bash +mvn compile # Regenerates Q-classes in target/generated-sources/java/ +``` + +## Build Profiles + +| Profile | Database | Description | +|---------|----------|-------------| +| `standalone` (default) | MySQL | Community/single-server | +| `desktop` | H2 | Desktop/lightweight | +| `distributed` | MySQL | Enterprise/multi-node | + +Database-specific profiles: `standalone-oracle`, `standalone-pg`, `standalone-dm`, `standalone-kingbase`, `standalone-sqlserver`, `standalone-GreatSQL` + +## Critical Dev Conventions + +### JPA > MyBatis-Plus + +MyBatis-Plus has been replaced by JPA. **Do not use MyBatis-Plus patterns.** + +- Use `jakarta.persistence.*` (not `javax.persistence.*`) +- Entities: `@Getter`/`@Setter` via Lombok (not `@Data`), `@Comment` on table and every field +- IDs: `Long`, no `@GeneratedValue` — IDs generated manually via `SnowflakeUtil` +- Repositories: `JpaRepository`, `JpaSpecificationExecutor` + +### JpaUpdateNonNullAspect + +Intercepts all `save`/`saveAndFlush` calls: +- If entity exists (non-null ID), **only non-null fields are copied** before saving — mimics MyBatis-Plus `updateById` +- To intentionally set a field to null: use QueryDSL `@Modifying` + JPQL, `save()` cannot do this + +### Multi-DB Compatibility + +All QueryDSL/JPQL queries **must work across all supported databases** (MySQL, Oracle, PostgreSQL, SQL Server, DM, Kingbase, GreatSQL). Avoid database-specific dialect functions; branch per-dialect when necessary (see `DynamicCaseNamingStrategy`). + +### Layering + +- **server/** = API endpoint implementation, implements interfaces from `sdk` module +- **manage/** = business logic, `@Component`, called by server +- **dao/auto/** = JPA entities + repositories +- **dao/ext/** = custom query POJOs and non-standard repositories + +### ID Generation + +Use `SnowflakeUtil` for all new entity IDs. + +## Architecture Notes + +- **Profile-based feature toggling:** Three Maven profiles control which modules are included (community vs enterprise) +- **Frontend bundled into JAR:** `maven-antrun-plugin` copies frontend dist into backend `static/` +- **Schema management:** Flyway disabled, `ddl-auto: update` manages schema in dev and production +- **xpack independence:** de-xpack is a separate git repo; core-backend optionally depends on it for debugging diff --git a/core/core-backend/src/main/java/io/dataease/menu/manage/MenuManage.java b/core/core-backend/src/main/java/io/dataease/menu/manage/MenuManage.java index 5d84720999..18992b4a31 100644 --- a/core/core-backend/src/main/java/io/dataease/menu/manage/MenuManage.java +++ b/core/core-backend/src/main/java/io/dataease/menu/manage/MenuManage.java @@ -97,6 +97,9 @@ public class MenuManage { if (coreMenu.getId().equals(21L)) return false; return coreMenu.getId().equals(7L) || coreMenu.getPid().equals(7L) + || coreMenu.getId().equals(9L) + || coreMenu.getId().equals(111L) + || coreMenu.getId().equals(112L) || coreMenu.getId().equals(14L) || coreMenu.getId().equals(17L) || coreMenu.getId().equals(18L) diff --git a/core/core-backend/src/main/resources/i18n/core_en_US.properties b/core/core-backend/src/main/resources/i18n/core_en_US.properties index 0525da3ced..1e20a43563 100644 --- a/core/core-backend/src/main/resources/i18n/core_en_US.properties +++ b/core/core-backend/src/main/resources/i18n/core_en_US.properties @@ -18,6 +18,8 @@ i18n_menu.screen=Data Screen i18n_menu.dataset=Dataset i18n_menu.datasource=Data Source i18n_menu.user=User Management +i18n_menu.sysuser=User Management +i18n_menu.sysauth=Permission Configuration i18n_menu.org=Organization Management i18n_menu.auth=Permission Configuration i18n_menu.report=Scheduled Report @@ -149,6 +151,7 @@ i18n_template_recent=Recently Used i18n_default_org=Default Organization i18n_org_admin=Organization Admin i18n_ordinary_role=Ordinary User +i18n_org_analyst=Data Analyst i18n_sys_admin=System Admin i18n_threshold_logic_eq=Equal to diff --git a/core/core-backend/src/main/resources/i18n/core_zh_CN.properties b/core/core-backend/src/main/resources/i18n/core_zh_CN.properties index 15bdb31aef..d5a3e4bbc0 100644 --- a/core/core-backend/src/main/resources/i18n/core_zh_CN.properties +++ b/core/core-backend/src/main/resources/i18n/core_zh_CN.properties @@ -17,6 +17,8 @@ i18n_menu.screen=\u6570\u636E\u5927\u5C4F i18n_menu.dataset=\u6570\u636E\u96C6 i18n_menu.datasource=\u6570\u636E\u6E90 i18n_menu.user=\u7528\u6237\u7BA1\u7406 +i18n_menu.sysuser=\u7528\u6237\u7BA1\u7406 +i18n_menu.sysauth=\u6743\u9650\u914D\u7F6E i18n_menu.org=\u7EC4\u7EC7\u7BA1\u7406 i18n_menu.auth=\u6743\u9650\u914D\u7F6E i18n_menu.report=\u5B9A\u65F6\u62A5\u544A @@ -148,6 +150,7 @@ i18n_template_recent=\u6700\u8FD1\u4F7F\u7528 i18n_default_org=\u9ED8\u8BA4\u7EC4\u7EC7 i18n_org_admin=\u7EC4\u7EC7\u7BA1\u7406\u5458 i18n_ordinary_role=\u666E\u901A\u7528\u6237 +i18n_org_analyst=\u6570\u636E\u5206\u6790\u5E08 i18n_sys_admin=\u7CFB\u7EDF\u7BA1\u7406\u5458 i18n_threshold_logic_eq=\u7B49\u4E8E diff --git a/core/core-backend/src/main/resources/i18n/core_zh_TW.properties b/core/core-backend/src/main/resources/i18n/core_zh_TW.properties index 5da107df42..38df290978 100644 --- a/core/core-backend/src/main/resources/i18n/core_zh_TW.properties +++ b/core/core-backend/src/main/resources/i18n/core_zh_TW.properties @@ -17,6 +17,8 @@ i18n_menu.screen=\u6578\u64DA\u5927\u5C4F i18n_menu.dataset=\u6578\u64DA\u96C6 i18n_menu.datasource=\u6578\u64DA\u6E90 i18n_menu.user=\u7528\u6236\u7BA1\u7406 +i18n_menu.sysuser=\u7528\u6236\u7BA1\u7406 +i18n_menu.sysauth=\u6B0A\u9650\u914D\u7F6E i18n_menu.org=\u7D44\u7E54\u7BA1\u7406 i18n_menu.auth=\u6B0A\u9650\u914D\u7F6E i18n_menu.report=\u5B9A\u6642\u5831\u544A @@ -148,6 +150,7 @@ i18n_template_recent=\u6700\u8FD1\u4F7F\u7528 i18n_default_org=\u9ED8\u8A8D\u7D44\u7E54 i18n_org_admin=\u7D44\u7E54\u7BA1\u7406\u54E1 i18n_ordinary_role=\u666E\u901A\u7528\u6236 +i18n_org_analyst=\u6578\u64DA\u5206\u6790\u5E2B i18n_sys_admin=\u7CFB\u7D71\u7BA1\u7406\u54E1 i18n_threshold_logic_eq=\u7B49\u65BC diff --git a/core/core-frontend/src/assets/svg/icon_ban_filled.svg b/core/core-frontend/src/assets/svg/icon_ban_filled.svg new file mode 100644 index 0000000000..fa22f12aae --- /dev/null +++ b/core/core-frontend/src/assets/svg/icon_ban_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/core-frontend/src/components/drawer-filter/src/DrawerTreeFilter.vue b/core/core-frontend/src/components/drawer-filter/src/DrawerTreeFilter.vue index 2de5c5499b..af50b49fc4 100644 --- a/core/core-frontend/src/components/drawer-filter/src/DrawerTreeFilter.vue +++ b/core/core-frontend/src/components/drawer-filter/src/DrawerTreeFilter.vue @@ -9,6 +9,8 @@ interface TreeConfig { showCheckbox: boolean checkOnClickNode: boolean placeholder: string + multiple: boolean + clearable: boolean } const props = defineProps({ optionList: propTypes.arrayOf( @@ -20,7 +22,11 @@ const props = defineProps({ }) ), title: propTypes.string, - property: Object as PropType + property: Object as PropType, + disabled: { + type: Boolean, + default: false + } }) const { property } = toRefs(props) @@ -30,7 +36,9 @@ const treeConfig = computed(() => { checkStrictly: false, showCheckbox: true, checkOnClickNode: true, - placeholder: t('user.role') + placeholder: t('user.role'), + multiple: true, + clearable: false }, property.value ) @@ -39,12 +47,17 @@ const treeConfig = computed(() => { const state = reactive({ currentStatus: [], + currentStatusSingle: null as any, activeStatus: [] }) const emits = defineEmits(['filter-change']) const filterTree = ref() const treeChange = () => { + if (!treeConfig.value.multiple) { + emits('filter-change', state.currentStatusSingle ? [state.currentStatusSingle] : []) + return + } const nodes = state.currentStatus.map(id => { return filterTree.value?.getNode(id).data }) @@ -59,6 +72,7 @@ const optionListNotSelect = computed(() => { }) const clear = () => { state.currentStatus = [] + state.currentStatusSingle = null } watch( () => state.currentStatus, @@ -69,6 +83,12 @@ watch( immediate: true } ) +watch( + () => state.currentStatusSingle, + () => { + treeChange() + } +) defineExpose({ clear }) @@ -79,6 +99,7 @@ defineExpose({ {{ title }}
+
diff --git a/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue b/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue index f1123fa5c3..ebbb88392f 100644 --- a/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue +++ b/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue @@ -44,6 +44,13 @@ const cleanrInnerValue = (index: number) => { state.conditions[i].value = [] } } + // For tree-select in single-select mode, also clear single value + if (componentList.value[index]?.type === 'tree-select') { + const prop = componentList.value[index]?.property + if (prop && prop.multiple === false) { + state.conditions = state.conditions.filter(c => c.field !== field) + } + } } const clearInnerTag = (index?: number) => { if (isNaN(index)) { @@ -79,11 +86,16 @@ const filterChange = (value, field, operator) => { exits = true condition['value'] = value } - if (!condition?.value?.length) { + const val = condition?.value + const isEmpty = Array.isArray(val) + ? !val.length + : val === null || val === undefined || val === '' + if (isEmpty) { state.conditions.splice(len, 1) } } - if (!exits && value?.length) { + const hasValue = Array.isArray(value) ? value.length : value != null && value !== '' + if (!exits && hasValue) { state.conditions.push({ field, value, operator }) } treeFilterChange(value, field, operator) @@ -129,6 +141,7 @@ defineExpose({ :option-list="component.option" :title="component.title" :property="component.property" + :disabled="component.disabled" @filter-change="v => filterChange(v, component.field, 'in')" />