20260802220544
This commit is contained in:
1 parent
c00329b5a4
commit
4fc9040a8a
60 files changed
+6273
-908
No files matched your search
@@ -0,0 +1,490 @@
|
||||
---
|
||||
name: fms-dev
|
||||
description: >
|
||||
FMS (模块/数据管理平台) 全栈开发技能。本项目后端为 Spring Boot + SqlServer 通用数据接口架构,
|
||||
前端为 Vue 3 + antdv-next + Pinia。本技能描述项目约定、通用组件用法、API 模式、
|
||||
数据库规范及常见开发任务的标准操作。当需要新增页面、新增后端查询/保存逻辑、创建数据库迁移脚本、
|
||||
或修改 FMS 现有功能时使用此技能。
|
||||
agent_created: true
|
||||
---
|
||||
|
||||
# FMS 开发技能
|
||||
|
||||
## 项目概览
|
||||
|
||||
FMS 是一个**配置驱动的低代码数据管理平台**,核心设计理念是:后端不新增业务接口,一切数据读写走少量通用接口;前端用元数据(模块/字段/按钮/多语言)动态拼出页面。
|
||||
|
||||
- `fms-api/` — Spring Boot 3.x + Java 21 + Maven,端口 8088,context-path `/api`
|
||||
- `fms-vue/` — Vue 3 + Vite + Pinia + antdv-next + stk-table-vue,开发端口 5180
|
||||
- 数据库:SQL Server,多机构独立库,按 JWT 中 orgId 路由
|
||||
- 连接池:Alibaba Druid,每机构一个
|
||||
|
||||
## 触发场景
|
||||
|
||||
- 新增/修改前端页面(数据浏览页、CRUD 页、复杂自定义页)
|
||||
- 新增/修改后端通用接口调用逻辑
|
||||
- 创建数据库迁移脚本
|
||||
- 修改模块管理(s_module / s_module_field 等元数据)
|
||||
- 新增文件上传/下载功能
|
||||
- 排查保存或查询问题
|
||||
- 任何需要理解本项目独特约定的开发任务
|
||||
|
||||
## 核心铁律(必须遵守)
|
||||
|
||||
1. **不新增业务专用后端接口**。所有数据操作复用 `DataController` 通用端点。
|
||||
2. **不新增前端 API 地址**。`services/api.js` 封装的函数是唯一入口。
|
||||
3. **改动 scope 到当前任务**,不顺手重构无关代码。
|
||||
4. **改完不要自己跑 build/启动服务**,除非用户明确要求。测试验证(`mvnw test`/`pnpm fmt:check`)可以。
|
||||
5. **bigint 雪花 ID 必须以字符串返回前端**(JavaScript 无法安全表示 64 位整数)。
|
||||
6. **表名/字段名只允许 `[A-Za-z_][A-Za-z0-9_]*`**,后端 `DbUtils` 会强校验。
|
||||
7. **迁移脚本必须可重复执行**(`IF NOT EXISTS`),不修改已有业务表。
|
||||
|
||||
---
|
||||
|
||||
## 后端架构速查
|
||||
|
||||
### 通用数据端点
|
||||
|
||||
| 端点 | 用途 | 参数关键字段 |
|
||||
|------|------|-------------|
|
||||
| `POST /data/loaddata` | 条件查询 | `view_name`, `search_condition`, `order_by`, `search_columns` |
|
||||
| `POST /data/page` | 分页查询 | `view_name`, `order_by`, `page_no`, `page_size`, `search_condition`, `search_columns` |
|
||||
| `POST /data/saveobjt` | 跨表事务保存 | `[{table, key_field, inserts[], updates[], deletes[]}]` |
|
||||
| `GET /data/nextid` | 雪花 ID | `?count=N`(默认1) |
|
||||
| `POST /data/loaddatabysql` | 自定义 SQL | `{sql}` — **仅 SELECT/WITH 开头,禁止 DML** |
|
||||
| `POST /data/describe` | 表结构 | `{table}` |
|
||||
| `GET /data/nextcode` | 自动编号 | `?moduleId=N&count=N` |
|
||||
|
||||
### 保存引擎 (DataSaveService)
|
||||
|
||||
`saveobjt` 在一个**手动 JDBC 事务**中执行,顺序:**delete → update → insert**。任一表失败触发 `connection.rollback()`。
|
||||
|
||||
关键行为:
|
||||
- 主键值 `< 0` 或 `null` → 自动分配雪花 ID
|
||||
- `key_field` 值从 UPDATE SET 中排除(作为 WHERE 条件)
|
||||
- INSERT 只写入 `writable=true` 且在数据中的列
|
||||
- 要求每条 INSERT/UPDATE/DELETE 影响行数恰好为 1
|
||||
|
||||
### 多机构路由
|
||||
|
||||
```
|
||||
JwtAuthFilter → OrgContext.setOrgId() → OrgRoutingDataSource
|
||||
→ OrgDataSourceManager.getConnection() → DruidDataSource[orgId]
|
||||
→ 对应 SQL Server 实例
|
||||
```
|
||||
|
||||
机构数据库配置文件:`config/dbconfigs/{ORG_ID}.properties`(git-ignored)。
|
||||
|
||||
### 异常与响应
|
||||
|
||||
| 异常类型 | code | 用途 |
|
||||
|---------|------|------|
|
||||
| `AuthenticationException` | 401 | 认证失败 |
|
||||
| `BusinessException` | 1000 | 业务校验失败,message 直接展示 |
|
||||
| `SaveObjectException` | 2001 | 保存失败,带 `{table, action, index}` |
|
||||
| `SQLException` / 其他 | 500 | 统一"系统异常" |
|
||||
|
||||
统一响应:`ApiResponse<T>(int code, String message, T data)`,`code == 0` 表示成功。
|
||||
|
||||
### 后端包结构速查
|
||||
|
||||
```
|
||||
cn.g3soft.fmsapi
|
||||
├── config/ JwtAuthFilter, 配置属性
|
||||
├── controller/ DataController, AuthController, FileController
|
||||
├── database/ 多机构路由、OrgContext(ThreadLocal)
|
||||
├── exception/ 异常类 + GlobalExceptionHandler
|
||||
├── service/ DataService, DataSaveService, AuthService, FileService
|
||||
└── utils/ DbUtils(核心), JwtUtils, ApiResponse, IdGenerator(雪花)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端架构速查
|
||||
|
||||
### HTTP 层
|
||||
|
||||
`services/http.js`:axios 实例,baseURL `/api`,自动注入 Bearer Token,拦截 `code !== 0` 转为 Error。认证失效(401/40101)自动跳转登录。
|
||||
|
||||
### API 服务层
|
||||
|
||||
`services/api.js` 封装全部通用接口调用——这是前端与后端交互的**唯一通道**:
|
||||
|
||||
```js
|
||||
import { loadDataApi, pageDataApi, saveObjectApi, nextIdApi, describeApi, loginApi } from '@/services/api';
|
||||
```
|
||||
|
||||
所有 API 返回 `{ success, code, message, data }`,其中 `success` 为前端补充(`code === 0`)。
|
||||
|
||||
### 国际化
|
||||
|
||||
不走 vue-i18n,翻译文本来自数据库 `b_i18n` 表。`app` store 的 `t(key, fallback)` 函数做翻译。多语言键规范:`field.{moduleCode}.{fieldCode}`、`menu.{moduleCode}`、`power.{moduleCode}.{powerCode}`。
|
||||
|
||||
### 权限
|
||||
|
||||
`stores/permissions.js` 从 `b_user_module` / `b_user_power` / `s_module_power` 加载权限。`canAccess(moduleId)` 判断页面权限,`canPower(scopeCode, powerCode)` 判断操作权限。特殊账号 `g3soft` 为超级管理员,跳过所有权限检查。
|
||||
|
||||
### 核心组件
|
||||
|
||||
#### FmsModuleListPage(数据列表页)
|
||||
|
||||
**最常用的页面组件——大多数 CRUD 页面只需配置一个 data-code 即可运行。**
|
||||
|
||||
Props 速查:
|
||||
| Prop | 用途 |
|
||||
|------|------|
|
||||
| `data-code` | 数据模块编码(必填),对应 `s_module.b_code` |
|
||||
| `fixed-search-condition` | 固定查询条件(不含 WHERE 关键字) |
|
||||
| `row-actions` | 行操作按钮 `[{key, label, onClick}]` |
|
||||
| `editable` | 可编辑模式(行内编辑+新增行) |
|
||||
| `row-draggable` | 行拖拽排序 |
|
||||
| `page-size` | 每页条数(默认 20) |
|
||||
| `column-extensions` | 列自定义(formatter, clickable 等) |
|
||||
|
||||
Slots:`#actions`(工具栏按钮区)、`#actions="{ saveChanges, dirtyCount, saving }"`(可编辑模式工具栏)
|
||||
|
||||
暴露方法:`reload()`, `saveChanges()`, `addRow()`, `initialize()`, `resetColumns()`
|
||||
|
||||
#### FmsTree(树形组件)
|
||||
|
||||
Prop `treeData` 接收扁平数组,自动构建树。支持虚拟滚动、拖拽、右键菜单。自动通过 `b_i18n` 做标题翻译。
|
||||
|
||||
### 路由与页面注册
|
||||
|
||||
1. 在 `router/routes.js` 添加路由配置,使用静态 `import()` 注册组件
|
||||
2. `meta.moduleId` 指定权限检查的模块 code
|
||||
3. `meta.title` 指定页面标题
|
||||
|
||||
```js
|
||||
{
|
||||
path: 'my-feature',
|
||||
name: 'my-feature',
|
||||
component: () => import('@/views/my-feature/index.vue'),
|
||||
meta: { title: '我的功能', moduleId: 'my_feature_code' },
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 后端开发模式
|
||||
|
||||
### 模式 1:新增简单查询端点
|
||||
|
||||
在现有 Controller(通常是 `DataController` 或 `FileController` 同级)中添加方法:
|
||||
|
||||
```java
|
||||
@PostMapping("/my/query")
|
||||
public ApiResponse<?> myQuery(@RequestBody Map<String, Object> params) throws SQLException {
|
||||
String viewName = ParamUtils.getRequiredString(params, "view_name");
|
||||
String condition = ParamUtils.getString(params, "search_condition");
|
||||
// 业务逻辑 + 调用 dataService.loadData / dbUtils.loadData
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
```
|
||||
|
||||
### 模式 2:新增保存端点
|
||||
|
||||
封装数据为 `[{table, key_field, inserts[], updates[], deletes[]}]` 格式,调用 `dataSaveService.save(requests)`。
|
||||
|
||||
```java
|
||||
List<Map<String, Object>> requests = new ArrayList<>();
|
||||
Map<String, Object> req = new HashMap<>();
|
||||
req.put("table", "my_table");
|
||||
req.put("key_field", "b_id");
|
||||
req.put("inserts", newList);
|
||||
dataSaveService.save(requests);
|
||||
```
|
||||
|
||||
### 模式 3:自定编号生成
|
||||
|
||||
调用 `dataSaveService.nextCode(moduleId, count)`,返回 `List<String>`。
|
||||
|
||||
---
|
||||
|
||||
## 前端开发模式(按复杂度排序)
|
||||
|
||||
### 模式 A:纯数据列表页(零代码)
|
||||
|
||||
适用于只需展示已有数据模块的页面。
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import FmsModuleListPage from '@/components/fms-module-list/FmsModuleListPage.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-page">
|
||||
<FmsModuleListPage data-code="my_data_code" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.my-page {
|
||||
width: 100%; height: 100%; overflow: hidden;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 6px;
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
.my-page :deep(.fms-module-list-page) { border: 0; border-radius: 0; }
|
||||
</style>
|
||||
```
|
||||
|
||||
### 模式 B:带行操作和自定义工具栏
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import FmsModuleListPage from '@/components/fms-module-list/FmsModuleListPage.vue';
|
||||
|
||||
const listRef = ref(null);
|
||||
|
||||
const rowActions = [
|
||||
{ key: 'edit', label: '编辑', onClick: ({ record }) => handleEdit(record) },
|
||||
{ key: 'delete', label: '删除', danger: true, onClick: ({ record }) => handleDelete(record) },
|
||||
];
|
||||
|
||||
function handleAdd() { /* 打开新增弹窗 */ }
|
||||
function handleEdit(record) { /* 打开编辑弹窗 */ }
|
||||
function handleDelete(record) { /* 确认后调用 saveObjectApi 删除 */ }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-page">
|
||||
<FmsModuleListPage
|
||||
ref="listRef"
|
||||
data-code="my_data_code"
|
||||
:row-actions="rowActions"
|
||||
>
|
||||
<template #actions>
|
||||
<a-button type="primary" @click="handleAdd">新增</a-button>
|
||||
</template>
|
||||
</FmsModuleListPage>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 模式 C:可编辑表格(行内编辑)
|
||||
|
||||
```vue
|
||||
<FmsModuleListPage
|
||||
ref="listRef"
|
||||
data-code="my_data_code"
|
||||
:editable="true"
|
||||
:row-draggable="true"
|
||||
>
|
||||
<template #actions="{ saveChanges, saving }">
|
||||
<a-button type="primary" :loading="saving" @click="saveChanges">保存</a-button>
|
||||
</template>
|
||||
</FmsModuleListPage>
|
||||
```
|
||||
|
||||
### 模式 D:完全自定义页面(复杂场景)
|
||||
|
||||
直接使用 `loadDataApi`、`saveObjectApi`、`nextIdApi` + 原生 antdv-next 组件。需自行管理数据加载、修改追踪(使用 `utils/dataChanges.js` 的 `diffRows`/`cloneData`)、ID 分配(`utils/tempId.js` 的 `createTempId` + `allocateTemporaryIds`)。
|
||||
|
||||
关键工具函数:
|
||||
- `cloneData(obj)` — JSON 深拷贝,存储原始数据快照
|
||||
- `diffRows(current, previous)` — 对比得到 `{inserts, updates, deletes}`
|
||||
- `allocateTemporaryIds(rowGroups)` — 负号临时 ID 替换为真实雪花 ID
|
||||
- `tableChange(table, changes)` — 生成 `{table, key_field, ...changes}` 结构
|
||||
|
||||
---
|
||||
|
||||
## 数据库迁移
|
||||
|
||||
### 脚本命名
|
||||
|
||||
`fms-api/config/migrations/NNN_description.sql`,三位数字递增。
|
||||
|
||||
### 脚本模板
|
||||
|
||||
```sql
|
||||
SET XACT_ABORT ON;
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- 描述:创建 xxx 表
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = 'xxx')
|
||||
BEGIN
|
||||
CREATE TABLE [xxx] (
|
||||
[b_id] BIGINT NOT NULL PRIMARY KEY,
|
||||
[b_name] NVARCHAR(200) NOT NULL,
|
||||
-- ...
|
||||
);
|
||||
END;
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
关键规则:
|
||||
- 使用 `XACT_ABORT ON` + 显式 `BEGIN/COMMIT TRANSACTION`
|
||||
- 所有 DDL 用 `IF NOT EXISTS` 包裹
|
||||
- 不删除或修改已有业务表
|
||||
- 表名/列名使用小写或驼峰,统一
|
||||
- 大字段主键用 `BIGINT`
|
||||
|
||||
### 执行
|
||||
|
||||
使用 PowerShell `.NET SqlClient`(本机 SQL Server 兼容性),参照 `fms-api/tools/migration/` 下的工具脚本。
|
||||
|
||||
---
|
||||
|
||||
## 代码约定细节
|
||||
|
||||
### 后端
|
||||
- Controller 方法声明 `throws SQLException`,返回 `ApiResponse<T>`
|
||||
- Service 注入 `DataSource dataSource`(自动多机构路由)+ `DbUtils dbUtils`
|
||||
- 参数提取用 `ParamUtils.getRequiredString/getString/getList`
|
||||
- ID 生成用 `IdGenerator.nextId()`
|
||||
- 当前用户 ID 用 `OrgContext.getUserId()`,机构 ID 用 `OrgContext.requireOrgId()`
|
||||
- 业务失败抛 `BusinessException("消息")`
|
||||
|
||||
### 前端
|
||||
- SFC 使用 `<script setup>` + Composition API
|
||||
- antdv-next 组件已全局自动导入(通过 `@antdv-next/auto-import-resolver`),**不要在组件中手动 import antdv-next 组件**
|
||||
- 图标使用 Lucide Vue
|
||||
- ID 处理:收到 bigint 一定是字符串,生成临时 ID 用 `createTempId()`(返回 "-1", "-2"...)
|
||||
- 错误处理:API 调用失败自动抛 Error,message 可直接展示;捕获后不要吞异常
|
||||
- 样式使用 scoped SCSS + CSS 变量(`var(--fms-*)`)
|
||||
|
||||
### 多语言
|
||||
- 键格式:`field.{moduleCode}.{fieldCode}`、`power.{moduleCode}.{powerCode}`、`menu.{moduleCode}`、`module.{moduleCode}`
|
||||
- 模块编码校验:`validateModuleCode(code)` — 小写字母开头,2-50 字符
|
||||
- 字段/操作权限重命名后用 `syncModuleItemI18nKeys()` 同步翻译键
|
||||
|
||||
---
|
||||
|
||||
## ERP 前端样式规范
|
||||
|
||||
本项目是企业级数据管理平台,UI 风格必须服从 **数据密度优先、操作效率至上** 的 ERP 设计原则。
|
||||
|
||||
### 三条禁令
|
||||
|
||||
| 禁止项 | 识别特征 | 替代做法 |
|
||||
|--------|----------|----------|
|
||||
| **大卡片** | 圆角 > 6px、带有阴影的大面积色块、Card 组件包裹整行内容、dashboard 风格的数据块 | 表格行、紧凑列表、无边框平铺 |
|
||||
| **大留白** | padding > 16px、margin > 12px、大面积空白区域、内容区居中且两侧大量留白 | padding ≤ 12px,内容撑满可用宽度,用分隔线而不是留白区分区域 |
|
||||
| **营销风格** | 渐变色背景、hero banner、大图标 + 文案宣传区、emoji 装饰、"立即体验"风格按钮、彩色卡片阵列 | 纯功能导向:灰白底、蓝色主操作按钮、无装饰性元素 |
|
||||
|
||||
### 布局规则
|
||||
|
||||
**页面级**:
|
||||
- 内容区 `width: 100%; height: 100%`,撑满可用空间,不设最大宽度
|
||||
- 页面仅一层容器包裹,不要嵌套多层无意义的 `<div>` 外套
|
||||
- 模块页通用外层样式:
|
||||
```scss
|
||||
.my-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 6px;
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
```
|
||||
|
||||
**表单/搜索区**:
|
||||
- 表单网格布局 4 列,`gap: 8px 16px`,标签左对齐,宽度 ≤ 100px
|
||||
- 搜索区和内容区之间用 1px 分割线分隔,间距 ≤ 8px
|
||||
- 表单按钮右对齐,与表单末行同行
|
||||
- 不要将整个搜索区包在一个 Card 组件里
|
||||
|
||||
**表格**:
|
||||
- 表头 `font-size: 13px`,行高 `32-36px`,不对内容行使用大面积背景色
|
||||
- 斑马纹使用极浅灰色(`#fafafa` 级别),不用彩色
|
||||
- 操作列宽度适应内容,不做固定 200px 这样的大宽度
|
||||
- 分页栏始终在表格正下方,`padding: 8px`,背景透明
|
||||
|
||||
### 间距尺度
|
||||
|
||||
```scss
|
||||
// 紧凑型 ERP 间距体系
|
||||
--space-xs: 4px; // 图标与文字间距、表单内紧凑间距
|
||||
--space-sm: 8px; // 组件内部间距、表单字段间距
|
||||
--space-md: 12px; // 组件间间距、页面内边距(默认最大值)
|
||||
--space-lg: 16px; // 仅用于页面级大区块分隔,谨慎使用
|
||||
```
|
||||
|
||||
| 位置 | 推荐 padding |
|
||||
|------|-------------|
|
||||
| 页面容器 | `padding: 0` 或 `padding: 8px 12px` |
|
||||
| 弹窗 body | `padding: 16px 24px` |
|
||||
| 表格单元格 | `padding: 6px 10px` |
|
||||
| 表单字段间距 | `margin-bottom: 12px` |
|
||||
| 按钮组间距 | `gap: 8px` |
|
||||
|
||||
### 颜色语义
|
||||
|
||||
```scss
|
||||
// ERP 语义色 —— 只用功能色,不用装饰色
|
||||
--primary: #1677ff; // 主操作按钮、链接、选中态
|
||||
--success: #52c41a; // 启用状态、已确认、通过
|
||||
--warning: #faad14; // 待审核、警告
|
||||
--danger: #ff4d4f; // 删除、禁用、驳回、错误
|
||||
--inactive: #d9d9d9; // 未启用、禁用态
|
||||
|
||||
// 数据状态色(常用于表格行的标签/圆点)
|
||||
// 正常: #52c41a | 异常: #ff4d4f | 草稿: #faad14 | 归档: #8c8c8c
|
||||
```
|
||||
|
||||
规则:
|
||||
- 表格内数据状态的彩色标签使用 `variant="light"` 浅色变体
|
||||
- 操作按钮组:删除类用 `danger`,主操作用 `primary`,其余用 `default`
|
||||
- 不要在表格行上使用大面积彩色背景
|
||||
- 页面背景统一 `#f5f5f5`(antdv-next 默认),不自定义花哨背景
|
||||
|
||||
### 字体与排版
|
||||
|
||||
```scss
|
||||
// 只使用系统字体栈,不引入 Web Font
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'Microsoft YaHei', sans-serif;
|
||||
|
||||
// 字号体系
|
||||
font-size: 13px; // 表格内容、表单标签
|
||||
font-size: 14px; // 正文、菜单、按钮
|
||||
font-size: 16px; // 页面标题
|
||||
```
|
||||
|
||||
- 不使用图标字体库,统一用 Lucide Vue(已集成)
|
||||
- 数值列右对齐,文本列左对齐,状态/操作列居中
|
||||
- 金额列用等宽数字(`font-variant-numeric: tabular-nums`)
|
||||
|
||||
### 弹窗与抽屉
|
||||
|
||||
- **新增/编辑表单** → Modal 弹窗(`width: 560px-720px`,不要默认 520px 太小)
|
||||
- **查看详情** → Drawer 抽屉(`width: 480px-640px`)
|
||||
- 弹窗 footer 按钮右对齐:[取消] [确定]
|
||||
- 弹窗不要嵌套弹窗
|
||||
|
||||
### 消息与反馈
|
||||
|
||||
- 保存成功不弹窗,用 `message.success("保存成功")` 顶部提示
|
||||
- 删除操作必须用 `Modal.confirm` 二次确认
|
||||
- 错误信息从后端 message 字段获取,直接展示,不做二次包装
|
||||
- 加载状态用 `a-spin` 包裹内容区或骨架屏,不显示大面积全屏 loading
|
||||
|
||||
### 组件选择对照表
|
||||
|
||||
| 场景 | 选这个 | 别选这个 |
|
||||
|------|--------|----------|
|
||||
| 数据展示 | `a-table` / stk-table-vue | `a-card` + `a-list` |
|
||||
| 分组信息 | `a-descriptions` | `a-card` 嵌套 |
|
||||
| 多步骤表单 | `a-tabs` 分 tab | Step 组件引导 |
|
||||
| 多维度筛选 | `a-form` 行内 + `a-select` | Radio Group 平铺 |
|
||||
| 树状导航 | `a-tree` | Menu 手风琴嵌套 |
|
||||
| 只读信息展示 | `a-descriptions` + `a-tag` | 自定义大色块布局 |
|
||||
|
||||
---
|
||||
|
||||
## 常见踩坑点
|
||||
|
||||
1. **新增表后访问不到**:确认当前机构数据库有这张表,检查表名拼写和 schema(默认 `dbo`)。
|
||||
2. **保存 bigint 报错**:确保前端传的是**字符串**而非数字。
|
||||
3. **saveobjt 返回 500**:检查 `key_field` 是否存在于表中,inserts 中是否有非 writable 列(如自增/计算列)。
|
||||
4. **分页返回空**:`order_by` 在 page 中是**必填**的。
|
||||
5. **模块管理保存后权限失效**:需重新调用 `permissionStore.load()`。
|
||||
6. **FmsModuleListPage 不显示数据**:检查 `data-code` 对应的数据模块是否在 `s_module` 中注册且 `b_canuse=1`。
|
||||
7. **拖拽排序失效**:确保 `s_module_field` 中有 `b_xh` 字段且 `orderField` prop 匹配。
|
||||
8. **迁移脚本重复执行报错**:确保所有 DDL 用 `IF NOT EXISTS` 包裹。
|
||||
9. **SQL 注入检测误杀**:`loaddatabysql` 只允许 `SELECT`/`WITH` 开头,字符串中有 `--` 也会拦截——需要时用 `loaddata` + 参数化。
|
||||
10. **多机构数据隔离**:始终通过 `OrgContext` 获取 orgId,不要硬编码机构连接信息。
|
||||
@@ -0,0 +1,148 @@
|
||||
# FMS API 详细参数参考
|
||||
|
||||
## DataController 端点参数
|
||||
|
||||
### POST /data/loaddata
|
||||
|
||||
```
|
||||
Body: {
|
||||
"view_name": "string (required) — 表名或视图名",
|
||||
"search_condition": "string (optional) — WHERE 条件表达式,如 b_id = 1 AND b_name LIKE N'%关键词%'",
|
||||
"order_by": "string (optional) — 排序,如 b_id ASC 或 b_xh ASC, b_id DESC",
|
||||
"search_columns": ["string"] (optional) — 查询列,默认 *"
|
||||
}
|
||||
Response: { code: 0, message: "操作成功", data: [{...row}] }
|
||||
```
|
||||
|
||||
### POST /data/page
|
||||
|
||||
```
|
||||
Body: {
|
||||
"view_name": "string (required)",
|
||||
"order_by": "string (required) — 支持逗号分隔多字段",
|
||||
"page_no": int (optional, default 1),
|
||||
"page_size": int (optional, default 20),
|
||||
"search_condition": "string (optional)",
|
||||
"search_columns": ["string"] (optional)
|
||||
}
|
||||
Response: {
|
||||
code: 0,
|
||||
data: {
|
||||
rows: [{...row}],
|
||||
row_count: int,
|
||||
total: int,
|
||||
page_no: int,
|
||||
page_size: int
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /data/saveobjt
|
||||
|
||||
```
|
||||
Body: [{
|
||||
"table": "string (required) — 目标表名",
|
||||
"key_field": "string (required) — 主键字段名,通常为 b_id",
|
||||
"inserts": [{...row}] (optional) — 新增行,key_field 值 < 0 时自动分配雪花ID",
|
||||
"updates": [{...row}] (optional) — 修改行,必须包含 key_field 值",
|
||||
"deletes": [{...row}] (optional) — 删除行,必须包含 key_field 值",
|
||||
}]
|
||||
Response: { code: 0, message: "操作成功", data: null }
|
||||
```
|
||||
|
||||
执行顺序:deletes → updates → inserts。整体在一个事务中。
|
||||
|
||||
### POST /data/loaddatabysql
|
||||
|
||||
```
|
||||
Body: { "sql": "string (required) — 仅允许 SELECT 或 WITH 开头" }
|
||||
Response: { code: 0, data: [{...row}] }
|
||||
```
|
||||
|
||||
安全检查:
|
||||
- 必须以 SELECT 或 WITH 开头
|
||||
- 禁止 INSERT/UPDATE/DELETE/DROP/ALTER/CREATE/EXEC/GRANT 等 DML/DDL
|
||||
- 禁止 SQL 注释(`--`、`/* */`)
|
||||
- 禁止分号
|
||||
|
||||
### GET /data/nextid
|
||||
|
||||
```
|
||||
Query: ?count=int (optional, default 1)
|
||||
Response: {
|
||||
code: 0,
|
||||
data: "1234567890123456" (count=1) | ["id1", "id2"] (count>1)
|
||||
}
|
||||
```
|
||||
|
||||
### GET /data/nextcode
|
||||
|
||||
```
|
||||
Query: ?moduleId=long&count=int (optional, default 1)
|
||||
Response: {
|
||||
code: 0,
|
||||
data: "PREFIX2025080200001" (count=1) | ["code1", "code2"] (count>1)
|
||||
}
|
||||
```
|
||||
|
||||
### POST /data/describe
|
||||
|
||||
```
|
||||
Body: { "table": "string" }
|
||||
Response: { code: 0, data: [{ name: "b_id", type: "bigint", nullable: false, size: 19 }] }
|
||||
```
|
||||
|
||||
## AuthController
|
||||
|
||||
### POST /auth/login
|
||||
|
||||
```
|
||||
Body: {
|
||||
"orgid": "string (required) — 机构码",
|
||||
"userid": "string (required) — 账号",
|
||||
"password": "string (optional) — 密码"
|
||||
}
|
||||
Response (成功): {
|
||||
code: 0,
|
||||
data: {
|
||||
token: "eyJ...",
|
||||
orgid: "ORG001",
|
||||
user: { id: 1001, account: "admin", name: "管理员" }
|
||||
}
|
||||
}
|
||||
Response (失败): { code: 401, message: "机构码、账号或密码错误" }
|
||||
```
|
||||
|
||||
## FileController
|
||||
|
||||
### GET /file/config
|
||||
|
||||
```
|
||||
Response: { code: 0, data: { storageType: "local" | "aliyun-oss" } }
|
||||
```
|
||||
|
||||
### POST /file/upload (local 模式)
|
||||
|
||||
```
|
||||
Content-Type: multipart/form-data
|
||||
Fields: { files: File[], father: string, moduleId: string, cateId?: string }
|
||||
Response: { code: 0, data: [{...fileRecord}] }
|
||||
```
|
||||
|
||||
### POST /file/upload-credential (OSS 模式)
|
||||
|
||||
```
|
||||
Response: { code: 0, data: { accessId, accessKey, host, policy, signature, expire } }
|
||||
```
|
||||
|
||||
### POST /file/save-record (OSS 回调)
|
||||
|
||||
```
|
||||
Body: { subid: string, father: string, mx_moduleid: string, mx_cate_id?: string }
|
||||
```
|
||||
|
||||
### POST /file/delete
|
||||
|
||||
```
|
||||
Body: { subid: string }
|
||||
```
|
||||
@@ -0,0 +1,136 @@
|
||||
# FMS 数据库 Schema 参考
|
||||
|
||||
## 模块管理核心六表
|
||||
|
||||
### s_module(模块定义)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 不可变雪花 ID |
|
||||
| b_code | VARCHAR(50) UNIQUE | 可变小写编码,如 `m_biz` |
|
||||
| b_parent_id | BIGINT | 父模块 ID(NULL=根) |
|
||||
| b_name | NVARCHAR(200) | 模块名称 |
|
||||
| b_module_type | VARCHAR(20) | `directory` / `page` / `data` |
|
||||
| b_canuse | TINYINT | 1=启用 |
|
||||
| b_canmenu | TINYINT | 1=显示在菜单 |
|
||||
| b_xh | INT | 排序号 |
|
||||
| b_route | VARCHAR(500) | 页面路由 |
|
||||
| b_icon | VARCHAR(200) | 图标 |
|
||||
| b_memo | NVARCHAR(500) | 备注 |
|
||||
|
||||
### s_module_field(字段定义)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 雪花 ID |
|
||||
| b_module_id | BIGINT FK→s_module | 所属模块 |
|
||||
| b_code | VARCHAR(50) | 字段编码 |
|
||||
| b_name | NVARCHAR(200) | 字段名称 |
|
||||
| b_data_type | VARCHAR(50) | 数据类型:text/number/money/date/datetime/checkbox/select |
|
||||
| b_field_type | VARCHAR(50) | 字段类型:normal/key/auto/memo |
|
||||
| b_db_field | VARCHAR(200) | 对应数据库字段名 |
|
||||
| b_db_table | VARCHAR(200) | 对应数据库表名(多表关联时用) |
|
||||
| b_xh | INT | 排序号 |
|
||||
| b_canuse | TINYINT | 1=启用 |
|
||||
| b_memo | NVARCHAR(500) | 备注 |
|
||||
|
||||
### s_module_power(操作权限定义)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 雪花 ID |
|
||||
| b_module_id | BIGINT FK→s_module | 所属模块 |
|
||||
| b_code | VARCHAR(50) | 权限编码:create/update/delete/... |
|
||||
| b_name | NVARCHAR(200) | 权限名称 |
|
||||
| b_canuse | TINYINT | 1=启用 |
|
||||
| b_xh | INT | 排序号 |
|
||||
|
||||
### b_user_module(用户模块授权)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_user_id | BIGINT FK→b_user | 用户 |
|
||||
| b_module_id | BIGINT FK→s_module | 授予的模块 |
|
||||
|
||||
### b_user_power(用户操作权限)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_user_id | BIGINT FK→b_user | 用户 |
|
||||
| b_power_id | BIGINT FK→s_module_power | 授予的操作权限 |
|
||||
|
||||
### b_i18n(多语言翻译)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 雪花 ID |
|
||||
| b_locale | VARCHAR(10) | 语言代码:zh-CN/en-US/... |
|
||||
| b_key | VARCHAR(200) | 翻译键 |
|
||||
| b_value | NVARCHAR(MAX) | 翻译文本 |
|
||||
| b_module_id | BIGINT FK→s_module | 关联模块(可选) |
|
||||
| b_group | VARCHAR(50) | 分组类型 |
|
||||
|
||||
## 扩展表(模块配置)
|
||||
|
||||
### s_module_field_list(列表视图配置)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 雪花 ID |
|
||||
| b_module_id | BIGINT FK→s_module | 所属模块 |
|
||||
| b_field_id | BIGINT FK→s_module_field | 关联字段 |
|
||||
| b_visible | TINYINT | 1=显示在列表 |
|
||||
| b_width | INT | 列宽 |
|
||||
| b_xh | INT | 列顺序 |
|
||||
| b_editor_type | VARCHAR(50) | 编辑控件类型 |
|
||||
|
||||
### s_module_field_edit(编辑视图配置)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 雪花 ID |
|
||||
| b_module_id | BIGINT FK→s_module | 所属模块 |
|
||||
| b_field_id | BIGINT FK→s_module_field | 关联字段 |
|
||||
| b_visible | TINYINT | 1=编辑时可见 |
|
||||
| b_group | VARCHAR(50) | 分组 |
|
||||
| b_xh | INT | 字段排列顺序 |
|
||||
| b_width | INT | 编辑控件宽度 |
|
||||
|
||||
### s_module_field_query(查询条件配置)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 雪花 ID |
|
||||
| b_module_id | BIGINT FK→s_module | 所属模块 |
|
||||
| b_field_id | BIGINT FK→s_module_field | 关联字段 |
|
||||
| b_visible | TINYINT | 1=显示为查询条件 |
|
||||
| b_operator | VARCHAR(20) | 运算符:=/like/in/between |
|
||||
| b_xh | INT | 排列顺序 |
|
||||
| b_option_config | NVARCHAR(500) | 选项配置(select 用) |
|
||||
|
||||
### s_module_auto_code(自动编号规则)
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| b_id | BIGINT PK | 雪花 ID |
|
||||
| b_module_id | BIGINT FK→s_module | 所属模块 |
|
||||
| b_prefix | VARCHAR(50) | 前缀 |
|
||||
| b_separator | VARCHAR(10) | 分隔符 |
|
||||
| b_date_format | VARCHAR(50) | 日期格式:yyyyMMdd |
|
||||
| b_sequence_width | INT | 流水号宽度 |
|
||||
| b_reset_type | VARCHAR(20) | 重置类型:none/year/month/day |
|
||||
| b_start_value | BIGINT | 起始值 |
|
||||
| b_current_period | VARCHAR(50) | 当前周期值 |
|
||||
| b_current_value | BIGINT | 当前流水号 |
|
||||
| b_save_table | VARCHAR(200) | 保存到的表 |
|
||||
| b_field_name | VARCHAR(200) | 保存到的字段 |
|
||||
|
||||
## 业务表命名惯例
|
||||
|
||||
- `s_*` — 系统配置表(static/system)
|
||||
- `b_*` — 业务表/基础表(business/base)
|
||||
- `bf_*` — 业务-文件关联表
|
||||
- `v_*` — 视图
|
||||
- 主键统一:`b_id BIGINT`
|
||||
- 排序字段:`b_xh INT`
|
||||
- 启用字段:`b_canuse TINYINT`(0/1)
|
||||
Reference in new issue
Block a user