com.goldenwater:slaj:3.9.2| Layer | Technology | Version |
|---|---|---|
| Java | JDK | 17 |
| Framework | Spring Boot | 4.0.3 |
| ORM | MyBatis Spring Boot | 4.0.1 |
| Database | PostgreSQL (HighGo) | — |
| UI | Vue 3 + Element Plus | 3.5.26 / 2.13.1 |
| Build (FE) | Vite | — |
| Connection Pool | Druid | 1.2.28 |
slaj/
├── gw-admin/ # Admin web module (controllers, composite services) — main entry point
├── gw-common/ # Shared entities, VOs, domain objects
├── gw-framework/ # Framework base classes (BaseController, etc.)
├── gw-quartz/ # Scheduled tasks
├── gw-slaj/ # Slaj business module (mappers, domain, controllers)
├── gw-system/ # System module (common mappers, services, sys_dept, etc.)
├── gw-ui/ # Vue 3 frontend
└── docs/ # DDL scripts, documents
slajmapUnderscoreToCamelCase=true → DB columns org_name auto-map to Java orgNamehelperDialect: postgresql (set in application.yml)39.98.38.2:5866/highgo192.168.0.149:5866/highgoUse resultType (auto-mapping) for JOIN query results. DO NOT use resultMap — PostgreSQL returns lowercase column names, but mapUnderscoreToCamelCase=true handles the conversion automatically. When using resultMap, column aliases must match the resultMap column names exactly, which may conflict with PostgreSQL's lower-case behavior.
find_in_set → ANY(string_to_array(col, ','))INSERT ... ON CONFLICT (guid) DO UPDATE SET ...'%' || #{param} || '%' (not MySQL CONCAT('%', #{param}, '%'))| Type | 中文名 | Tables | Description |
|---|---|---|---|
| Water Org | 水利机构 | sys_dept + att_org_base + att_org_ext |
Government water departments |
| Uwat Org | 参建单位 | sys_dept + att_org_base + att_org_uwat_ext |
Construction units |
| Wsub Org | 分包单位 | sys_dept + att_org_base + att_org_wsub |
Subcontractor units |
Shared UUID pattern: sys_dept.dept_id = att_org_base.guid = extension_table.org_guid
SysDeptServiceImpl.insertDept() internally regenerates UUID (line 230), so:
sys_dept FIRSTdeptId from the returned dept objectdeptId as the shared UUID for att_org_base.guid and extension table's org_guidAll composite org operations (add/update/delete) are in gw-admin/src/main/java/com/goldenwater/web/service/:
OrgCompositeService.java — Water org (水利机构): sys_dept + att_org_base + att_org_extOrgUwatCompositeService.java — Uwat org (参建单位): sys_dept + att_org_base + att_org_uwat_extOrgWsubCompositeService.java — Wsub org (分包单位): sys_dept + att_org_base + att_org_wsubAll operations are @Transactional — all succeed or all roll back.
13 pairs of main/redundant tables. On every INSERT/UPDATE/DELETE to a main ATT* table, the corresponding ObjSyncMapper upserts the OBJ* redundant table via ON CONFLICT DO UPDATE.
| Main Table | Redundant Table |
|---|---|
| ATT_ENG_BASE | OBJ_ENG |
| ATT_ENG_RES | OBJ_RES |
| ATT_ENG_WAGA | OBJ_WAGA |
| ATT_ENG_PUST | OBJ_PUST |
| ATT_ENG_HYST | OBJ_HYST |
| ATT_ENG_DIKE | OBJ_DIKE |
| ATT_ENG_IRR | OBJ_IRR |
| ATT_ENG_WADI | OBJ_WADI |
| ATT_ENG_SD | OBJ_SD |
| ATT_ENG_CWS | OBJ_CWS |
| ATT_ENG_OTHE | OBJ_OTHE |
| ATT_PROJ_BASE | OBJ_PROJ |
| ATT_AD_BASE | OBJ_AD |
项目 (ATT_PROJ_BASE) → 工程 (ATT_ENG_BASE) → 标段 (OBJ_TEND) → 参建单位
REL_PROJ_ORG table (proj_guid + org_guid + from_date + to_date)
fromDate is auto-set by RelProjOrgServiceImpl.insertRelProjOrg()OBJ_TEND.eng_guid → ATT_ENG_BASE.guidREL_TEND_ORG tableALL new pages MUST follow one of the two patterns below. Reference implementations:
| Pattern | Example File |
|---|---|
| Standalone page | gw-ui/src/views/slaj/woas/ObjWoas.vue |
| Tree-sidebar page | gw-ui/src/views/system/orgext/index.vue |
Wrapper: <div class="system-page config-page"> (NOT app-container).
<template>
<div ref="containerRef" class="system-page config-page">
<el-card class="page-card">
<template #header>
<div class="card-header">
<div class="header-left">
<el-icon :size="20" color="#409EFF"><OfficeBuilding /></el-icon>
<span class="page-title">页面标题</span>
</div>
<div class="search-right">
<el-button type="primary" plain @click="handleAdd"><el-icon><Plus /></el-icon> 新增</el-button>
<el-button type="danger" plain :disabled="multiple" @click="handleDelete"><el-icon><Delete /></el-icon> 删除</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" ... />
</div>
</div>
</template>
<div class="search-bar" v-show="showSearch">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<!-- form items use el-icon component syntax for buttons -->
<el-form-item>
<el-button type="primary" @click="handleQuery"><el-icon><Search /></el-icon> 搜索</el-button>
<el-button @click="resetQuery"><el-icon><Refresh /></el-icon> 重置</el-button>
</el-form-item>
</el-form>
</div>
<el-table :height="tableHeight" v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<!-- ... -->
<el-table-column label="操作" align="center" width="160" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-show="total > 0"
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="getList"
@current-change="getList"
/>
</div>
</el-card>
<el-dialog :title="title" v-model="open" width="760px" append-to-body>
<!-- form -->
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
Wrapper: <div class="app-container tree-sidebar-manage-wrap"> with TreePanel on the left.
Key differences from standalone:
content-inner needs style="padding: 0;"el-card needs style="height: 100%;" :body-style="{height: '100%'}"el-table uses height="calc(100% - 100px)" instead of :height="tableHeight"
<template>
<div class="app-container tree-sidebar-manage-wrap">
<tree-panel :key="deptTreeKey" title="组织机构" :tree-data="deptOptions" :tree-props="treeProps"
search-placeholder="请输入机构名称" storage-key="xxx-sidebar-width"
node-key="deptId" lazy :load="loadDeptNode"
@node-click="handleNodeClick" @refresh="getDeptTree" ref="deptTreeRef" />
<div class="tree-sidebar-content">
<div class="content-inner" ref="containerRef" style="padding: 0;">
<el-card class="page-card" style="height: 100%;" :body-style="{height: '100%'}">
<template #header>
<div class="card-header">
<div class="header-left">
<el-icon :size="20" color="#409EFF"><OfficeBuilding /></el-icon>
<span class="page-title">页面标题</span>
</div>
<div class="search-right">
<el-button type="primary" plain @click="handleAdd"><el-icon><Plus /></el-icon> 新增</el-button>
<el-button type="danger" plain :disabled="multiple" @click="handleDelete"><el-icon><Delete /></el-icon> 删除</el-button>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</div>
</div>
</template>
<div class="search-bar" v-show="showSearch">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<!-- ... same as standalone ... -->
</el-form>
</div>
<el-table height="calc(100% - 100px)" v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<!-- ... -->
</el-table>
<div class="pagination">
<el-pagination ... />
</div>
</el-card>
<el-dialog :title="title" v-model="open" width="820px" append-to-body>
<!-- form -->
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</div>
</div>
</template>
All shared page styles are defined in gw-ui/src/assets/styles/goldenwater.scss under the /* Shared Page Styles */ comment block. Pages do NOT need <style scoped> blocks for the following — they inherit globally:
.system-page — wrapper padding, background, min-height, input/textarea border-radius.page-card — card border-radius, shadow, header/body padding overrides.card-header — flex layout, .header-left (icon + title), .search-right (buttons).search-bar — light blue background (#f8faff), flex layout, form item spacing.pagination — margin + right-alignment.el-table — header background (#f5f7fa), link button colorsPages only need a <style scoped> block if they have page-specific overrides (e.g., .projbase-form .el-divider, .tend-form .el-textarea__inner).
<style scoped lang="scss">
// For standalone pages: wrap everything in .system-page { ... }
// For tree-sidebar pages: styles are at root level (no .system-page wrapper needed)
.system-page { // ← standalone pages only
padding: 0px;
background-color: #f5f7fa;
min-height: calc(100vh - 10px);
:deep(.el-input__wrapper) { border-radius: 4px; }
:deep(.el-textarea__inner) { border-radius: 4px; }
} // ← close .system-page
.page-card {
border-radius: 1px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
:deep(.el-card__header) {
padding: 8px 20px !important;
border-bottom: 1px solid #ebeef5;
background-color: #fff;
border-radius: 8px 8px 0 0;
}
:deep(.el-card__body) {
padding: 0px !important;
background-color: #fff;
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.header-left {
display: flex;
align-items: center;
gap: 10px;
.page-title { font-size: 18px; font-weight: 600; color: #303133; }
}
.search-right {
display: flex;
gap: 10px;
}
}
.search-bar {
margin-bottom: 0px;
background-color: #f8faff;
padding: 6px 10px;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
:deep(.el-form) {
display: flex;
flex-wrap: wrap;
gap: 10px;
.el-form-item {
margin-bottom: 0;
margin-right: 15px;
.el-form-item__label { font-weight: 500; }
}
}
}
.pagination {
margin-top: 8px;
margin-bottom: 8px;
display: flex;
justify-content: flex-end;
}
:deep(.el-table) {
border-radius: 4px;
overflow: auto;
th.el-table__cell {
background-color: #f5f7fa !important;
color: #606266;
font-weight: 600;
}
.el-button--primary.is-link { color: #409eff; }
.el-button--danger.is-link { color: #f56c6c; }
}
</style>
| Rule | Do | Don't |
|---|---|---|
| Page wrapper | system-page config-page (standalone) or app-container tree-sidebar-manage-wrap (sidebar) |
app-container alone |
| Card | el-card class="page-card" wraps table+search+pagination |
Table outside card |
| Header | card-header > header-left (icon+title) + search-right (buttons) |
Buttons in a separate toolbar row |
| Search | <div class="search-bar"> with #f8faff bg |
Plain el-form without wrapper |
| Icons | <el-icon><Search /></el-icon> component syntax |
icon="Search" attribute syntax |
| Table header | #f5f7fa (light gray) background, #606266 text |
#d4e7fd (blue) background |
| Pagination | <el-pagination> inside <div class="pagination"> |
Custom <pagination> component |
| Dialog footer | <el-button @click="cancel">取消</el-button> + <el-button type="primary" @click="submitForm">确定</el-button> directly |
Wrapped in extra div.dialog-footer |
| Input size | Default Element Plus height | Compact 26px override |
| Select width | Explicit style="width: 120px" (or appropriate) on each <el-select> |
Default or style="width:100%" |
| Page size | :page-sizes="[10, 20, 50, 100]", default pageSize: 10 |
Other page size arrays |
gw-ui/src/api/ — separate files per module (e.g., slaj/eng.js, system/attOrgWsub.js)@/components/OrgSelect/ — supports scope (all/water/uwat) and showScope props@element-plus/icons-vuegw-ui/src/router/index.js/system/{entity}/index for list pages/system/projbase-eng/index/:projGuid| Package | Module | Contents |
|---|---|---|
com.goldenwater.slaj.hidd |
gw-slaj | Core business mappers/domain/controllers |
com.goldenwater.system |
gw-system | Sys dept, shared mappers, common services |
com.goldenwater.web |
gw-admin | Composite services, system controllers |
com.goldenwater.common.core.domain |
gw-common | Shared entities and VOs |
com.goldenwater.common.core.domain.obj |
gw-common | Redundant OBJ_* entities |
com.goldenwater.common.core.domain.entity |
gw-common | Entity classes (RelProjOrg, SysDept, etc.) |
@/ → gw-ui/src/insertDept() — it will be overwrittenatt_eng_base on eng_guid"0" (enabled), not "1" (disabled)