20260802220544

This commit is contained in:
oneao committed 2026-08-02 22:05:44 +08:00
1 parent c00329b5a4
commit 4fc9040a8a
60 files changed
+6273 -908

No files matched your search

+490
View File
@@ -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)