20260913231607
This commit is contained in:
1 parent
f3ad727f1c
commit
c31329d387
179 files changed
+10846
-2684
No files matched your search
@@ -0,0 +1,238 @@
|
||||
---
|
||||
name: fms-module-list 查询区升级为常用条件网格+可用的高级查询
|
||||
overview: 把 `fms-module-list` 的查询区改造成 dashboard 的布局(常用条件 4 列网格 + 更多条件展开 + 左侧高级查询面板),并让高级查询真正生效(AST → SQL → `/data/page`)。常用与高级复用同一套查询构造器:常用条件按字段类型取默认操作符(文本一律 like),高级条件由用户选操作符并支持嵌套条件组。全程只改前端,后端零改动。
|
||||
design:
|
||||
architecture:
|
||||
framework: vue
|
||||
styleKeywords:
|
||||
- 企业级简约
|
||||
- 白卡分区
|
||||
- 定宽网格
|
||||
- 轻阴影
|
||||
- 微交互反馈
|
||||
fontSystem:
|
||||
fontFamily: PingFang SC
|
||||
heading:
|
||||
size: 15px
|
||||
weight: 600
|
||||
subheading:
|
||||
size: 13px
|
||||
weight: 600
|
||||
body:
|
||||
size: 13px
|
||||
weight: 400
|
||||
colorSystem:
|
||||
primary:
|
||||
- "#1677FF"
|
||||
- "#EDF5FF"
|
||||
background:
|
||||
- "#F5F6F8"
|
||||
- "#FFFFFF"
|
||||
- "#FAFBFC"
|
||||
text:
|
||||
- "#1F2329"
|
||||
- "#646A73"
|
||||
- "#A3ACB8"
|
||||
functional:
|
||||
- "#F53F3F"
|
||||
- "#FF7D00"
|
||||
- "#D8E5F7"
|
||||
todos:
|
||||
- id: query-ast-builder
|
||||
content: 在 queryUtils.js 实现统一 AST 构造器(OPERATORS、defaultOperator、buildConditionSql、buildGroupSql、astToSql、buildQuickAst、buildAdvancedAst),并让 buildQueryCondition 内部转发保持兼容
|
||||
status: completed
|
||||
- id: toolbar-quick-layout
|
||||
content: 改造 FmsQueryToolbar 为 dashboard 布局:常用条件 4 列网格 + 更多条件展开 + 生效条件 chips 条,emit 改为 { mode, ast }
|
||||
status: completed
|
||||
dependencies:
|
||||
- query-ast-builder
|
||||
- id: toolbar-advanced-panel
|
||||
content: 新增高级查询面板:条件行(复用 FmsQueryControl 按操作符渲染)、条件组与嵌套逻辑、根逻辑、条件计数与应用查询
|
||||
status: completed
|
||||
dependencies:
|
||||
- toolbar-quick-layout
|
||||
- id: page-ast-wiring
|
||||
content: FmsModuleListPage 接入 AST:fetchData 用 astToSql 生成条件,resetSearch 回到默认条件,保留 fixedSearchCondition 组合
|
||||
status: completed
|
||||
dependencies:
|
||||
- toolbar-advanced-panel
|
||||
- id: user-pref
|
||||
content: 新增 useModulePref 实现偏好读写与合并(query 分区顺序、view 列显隐/顺序/宽度),按默认→偏好→权限裁剪顺序合并并落库 s_user_module_pref
|
||||
status: completed
|
||||
dependencies:
|
||||
- page-ast-wiring
|
||||
- id: query-settings-drawer
|
||||
content: 实现查询设置抽屉:常用/高级/隐藏分区、拖拽排序、恢复默认、无查询权限字段灰显,改动在关闭时一次性保存
|
||||
status: completed
|
||||
dependencies:
|
||||
- user-pref
|
||||
- id: dashboard-cleanup-and-tests
|
||||
content: 删除 dashboard 中重复的 AST computed,补充 query-ast / module-pref 单测并跑通 fms-vue 全量测试
|
||||
status: completed
|
||||
dependencies:
|
||||
- query-settings-drawer
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
把 `fms-module-list`(生产通用列表组件)的查询区改造为 dashboard 原型已验证的形态,并让高级查询**真正生效**。改造后:常用条件以 4 列网格平铺、超出部分「更多条件」展开;高级查询提供可选操作符、可嵌套条件组、条件摘要 chips;两种模式共用同一套查询构造逻辑,最终都生成同一份 `search_condition` 交给 `/data/page` 执行。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- **常用条件区**:4 列定宽网格(容器查询在窄容器降为 3 列),默认展示 4 个条件,其余收进「更多条件」并带生效条件数角标;支持查询、重置。
|
||||
- **高级查询**:根逻辑(满足全部/任一)+ 条件行(字段 + 操作符 + 值)+ 条件组(组内 and/or,可嵌套、可增删)+ 「已配置 N 个条件」+ 应用查询,点击后真正请求数据。
|
||||
- **统一构造**:常用条件用默认操作符(文本 `like`、日期/数值区间 `between`、枚举 `eq`),高级条件用用户选择的操作符,二者生成同一种 AST,经同一个构造器产出 SQL。
|
||||
- **生效条件 chips 条**:展示当前生效条件,可单个移除、可全部清除;树/页面固定条件也以 chip 呈现(保持现有 `fixedSearchCondition` 语义)。
|
||||
- **查询设置(用户个性化)**:每个字段可设为「常用/仅高级/隐藏」并拖拽排序,只影响当前用户;无查询权限的字段灰显且不可调整。
|
||||
- **个性化持久化**:只记布局(查询字段分区与顺序、列显隐/顺序/宽度),不记条件值;语言、主题保持本地存储,不做表格密度。
|
||||
- **后端零改动**:沿用通用 `/data/loaddata`、`/data/page`、`/data/saveobjt`。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 前端:Vue 3 `<script setup>` + 现有自研 `fms-ui`(`@/components/ui` 的 Button / Input / Select / RangePicker / Dropdown / FormItem)
|
||||
- 图标:`@lucide/vue`(项目既有用法)
|
||||
- 数据接口:通用 `loadDataApi` / `pageDataApi` / `saveObjectApi`(`fms-vue/src/services/api.js`)
|
||||
- 测试:Vitest(`fms-vue/tests`)
|
||||
- 后端:Spring Boot `fms-api` —— **本次零改动**
|
||||
|
||||
## 实现思路
|
||||
|
||||
### 1. 一套 AST,一个 SQL 构造器(核心)
|
||||
|
||||
把「常用条件」和「高级条件」的差异收敛为**操作符从哪来**:常用条件取 `defaultOperator(field)`,高级条件取用户选择,之后共用同一条链路。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[常用条件表单值] -->|operator = defaultOperator| C[统一 AST]
|
||||
B[高级条件节点/分组] -->|operator = 用户选择 + 分组 logic| C
|
||||
C --> D[astToSql]
|
||||
D --> E[search_condition]
|
||||
E --> F[/data/page]
|
||||
G[fixedSearchCondition] --> E
|
||||
```
|
||||
|
||||
- `OPERATORS`:按 `b_type` 分组的操作符枚举,唯一定义处(文本 `like/not_like/eq/ne/is_null/is_not_null`;数值/金钱 `eq/ne/gt/ge/lt/le/between`;日期 `between/ge/le/is_null/is_not_null`;枚举/下拉/复选 `eq/ne/in/not_in`)。
|
||||
- `defaultOperator(field)`:文本 → `like`(即「几乎都是 like」),日期/数值区间 → `between`,其余 → `eq`。
|
||||
- `buildConditionSql(node, fieldMap)`:单条件 → SQL,由现有 `buildQueryCondition` 抽出来,`sqlLiteral` / `escapeSqlText` / `isRangeQuery` 等既有逻辑全部复用。
|
||||
- `buildGroupSql(group, fieldMap)`:递归;组内按 `logic` 连接,子节点多于 1 个时整体加括号;空值条件跳过。
|
||||
- `astToSql(ast, fieldMap)`:唯一出口,根节点为空时返回 `''`。
|
||||
- `buildQuickAst(items, values)` / `buildAdvancedAst({ nodes, groups, logic })`:两个入口。
|
||||
- `buildQueryCondition(item, value)` **保留签名并内部转发**,现有三个调用方(`FmsModuleListPage`、`FmsQueryToolbar`、`FileListPanel`)不动。
|
||||
|
||||
### 2. 工具栏改造(`FmsQueryToolbar.vue`)
|
||||
|
||||
- 移除「单字段快搜框 + 顶部摊平的高级面板」,改为:
|
||||
- 常用条件区:`.list-search` 网格 `repeat(4, 264px)`,`@container (max-width: 1130px)` 降 3 列;`QUICK_VISIBLE_LIMIT = 4`,其余「更多条件」展开并带角标。
|
||||
- 生效条件 chips 条:单项可移除、全部清除。
|
||||
- 「高级查询」入口 → 切换 `activeQueryMode`。
|
||||
- 高级面板:左栏常驻 360px(dashboard 已验证:条件行两行堆叠、条件组左侧粗竖线表达嵌套、底部「已配置 N 个条件 / 取消 / 应用查询」)。
|
||||
- **值控件复用 `FmsQueryControl`**:高级条件行按节点操作符构造临时 item(`{ config: { ...item.config, b_operator: node.operator }, field, options }`)传入,不重写一套输入控件;改变字段/操作符时按 `emptyQueryValue` 重置值。
|
||||
- emit 契约由 `{ mode, entries }` 改为 `{ mode, ast }`,父页面用 `astToSql` 落地。
|
||||
|
||||
### 3. 页面接入(`FmsModuleListPage.vue`)
|
||||
|
||||
- `searchConditions`(SQL 片段数组)替换为 `queryAst`(shallowRef);`fetchData()` 用 `astToSql(queryAst, fieldMap)` 生成条件,再与 `fixedSearchCondition` 以 AND 组合。
|
||||
- `resetSearch()` 改为**重建默认 AST**(依据模块配置的 `defaultValue`),修掉现有「重置会丢掉默认条件」的问题。
|
||||
- 初始化时读取用户偏好,把 `query.area/order` 下发给工具栏、把 `view.hidden/order/width` 应用到 `listConfig`(在 `applyColumnSettings` 结果之上再覆盖)。
|
||||
- 工具栏设置提交时调用 pref 保存。
|
||||
|
||||
### 4. 用户个性化(`fms-module-list/useModulePref.js`,新增)
|
||||
|
||||
- 读取:`loadDataApi('s_user_module_pref', "b_user_id = N'..' AND b_module_id = N'..'")`。
|
||||
- 保存:`saveObjectApi([{ table: 's_user_module_pref', key_field: 'b_user_id,b_module_id', updates | inserts: [row] }])`;先查后写,捕获主键冲突时改走 `updates` 兜底(后端无 MERGE,属已知非原子点,影响可忽略)。
|
||||
- 数据按规范成对维护 `pref` / `pref_org`,关闭设置抽屉时 diff 一次保存,不随每次拖拽写库。
|
||||
- 合并顺序严格为 **模块默认 → 用户偏好 → 权限裁剪**,权限放最后,确保「偏好只能调布局、不得放宽权限」(`开发规范.md` 通用原则 10)。
|
||||
- 用户编码取 `useAuthStore().userInfo.account`(缺失时回退 `loginInfo.user?.b_id`);`b_updated_by` / `b_updated_at` 前端赋值。
|
||||
|
||||
### 5. 后端改动清单
|
||||
|
||||
**零改动**。`search_condition` 仍由前端拼装(通用原则 5 明确不为此增加安全防护层);数据范围与字段查询权限现阶段在前端拼装(后端尚无权限引擎,属过渡实现),代码注释中写明约束,不写 TODO / 占位(通用原则 1)。
|
||||
|
||||
## 实现要点(防回归)
|
||||
|
||||
- `buildQueryCondition` 是既有公共函数,必须保留兼容,`FileListPanel.vue` 不能受影响。
|
||||
- `FmsQueryControl` 的区间/多选判定来自 `item.config.b_operator`(`isRangeQuery` / `isMultipleSelect`),高级条件行复用它时必须同步 `b_operator`,否则控件形态错误。
|
||||
- 现有「保存栏位设置」写的是 `s_module_schema`(模块级默认,管理员行为),与用户级 pref 是两条通道:渲染时 pref 覆盖在模块默认之上,二者不互相写回。
|
||||
- 高级模式下不重复渲染常用条件区(dashboard 已验证:搜索卡隐藏、表格占满剩余宽高)。
|
||||
- 只在模块初始化时读一次 pref;设置变更在抽屉关闭/完成时写一次,避免高频写库。
|
||||
- AST → SQL 为纯函数、条件数量级在数十以内,每次查询构建一次即可,无需缓存。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
fms-vue/src/components/fms-module-common/
|
||||
└── queryUtils.js # [MODIFY] 新增 OPERATORS / defaultOperator / buildConditionSql /
|
||||
# buildGroupSql / astToSql / buildQuickAst / buildAdvancedAst /
|
||||
# hasConditionValue;buildQueryCondition 转为内部转发,签名不变
|
||||
|
||||
fms-vue/src/components/fms-module-list/
|
||||
├── FmsQueryToolbar.vue # [MODIFY] 改 dashboard 形态:常用条件 4 列网格 + 更多条件展开 +
|
||||
# 生效条件 chips + 高级查询左栏常驻面板(条件行/条件组/操作符可选/
|
||||
# 应用查询)+ 查询设置抽屉(常用/高级/隐藏分区 + 拖拽排序 + 恢复默认);
|
||||
# emit('search', { mode, ast })
|
||||
├── FmsModuleListPage.vue # [MODIFY] searchConditions → queryAst;fetchData 用 astToSql;
|
||||
# resetSearch 回到默认条件;加载并下发 pref;保存 pref
|
||||
└── useModulePref.js # [NEW] 偏好读写与合并:loadModulePref / mergeQueryLayout /
|
||||
# mergeViewLayout / saveModulePref,含权限裁剪与 pref_org 基线
|
||||
|
||||
fms-vue/src/views/dashboard/
|
||||
└── index.vue # [MODIFY] 仅删除与公共构造器重复的 quickQueryAst / advancedQueryAst
|
||||
# (当前无调用点),其余保持原型不变
|
||||
|
||||
fms-vue/tests/unit/
|
||||
├── query-ast.spec.js # [NEW] AST 构造与 SQL 生成用例:默认操作符、空值跳过、between 单端、
|
||||
# 嵌套括号、转义、buildQueryCondition 兼容
|
||||
└── module-pref.spec.js # [NEW] 偏好合并顺序、权限裁剪优先、diff 保存请求体
|
||||
```
|
||||
|
||||
## 关键结构
|
||||
|
||||
```js
|
||||
// 统一 AST(两种模式共用)
|
||||
// { type: 'group', logic: 'and' | 'or', children: [Condition | Group] }
|
||||
// { type: 'condition', field: 'b_no', operator: 'like', value: 'FMS' }
|
||||
|
||||
// queryUtils.js 新增签名(均为纯函数)
|
||||
function defaultOperator(field: object): string
|
||||
function hasConditionValue(value: unknown): boolean
|
||||
function buildConditionSql(node: object, fieldMap: Map<string, object>): string
|
||||
function buildGroupSql(group: object, fieldMap: Map<string, object>): string
|
||||
function astToSql(ast: object | null, fieldMap: Map<string, object>): string
|
||||
function buildQuickAst(items: object[], values: object): object
|
||||
function buildAdvancedAst(state: { nodes: object[], groups: object[], logic: string }): object
|
||||
```
|
||||
|
||||
```
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"view": { "default": { "hidden": ["b_bz"], "order": ["b_no", "b_name"], "width": { "b_no": 160 } } },
|
||||
"query": { "default": { "area": { "b_remark": "quick", "b_score": "hidden" }, "order": ["b_no", "b_status"] } }
|
||||
}
|
||||
```
|
||||
|
||||
## 设计风格
|
||||
|
||||
沿用项目现有设计语言(`--fms-card` 白卡 + 6px 圆角 + 轻阴影 + 8px 灰缝),与 dashboard 布局原型保持一致:浅灰画布上「搜索卡」与「表格卡」两张白卡纵向堆叠,搜索卡高度随内容、表格卡撑满剩余高度。查询区采用顶部标签 + 定宽网格,保证字段位置稳定、英文标签变长也不漂移。整体为克制的现代企业级风格(Enterprise Minimal),交互反馈靠主色 hover / 淡主色底,不使用重投影或渐变。
|
||||
|
||||
## 页面区块
|
||||
|
||||
1. **常用条件区(搜索卡)**:4 列 × 264px 定宽网格,标签在上控件在下;超过 4 个的条件收进「更多条件」展开,按钮上带生效条件数角标;操作区分「查询/重置」「更多条件/高级查询」两组,配置组钉在行尾;右上角「查询设置」入口绝对定位不占流内高度。
|
||||
2. **高级查询面板**:左栏常驻 360px 白卡(与树形页左栏同一槽位),头部「高级查询 + 设置 + 关闭」,body 内根逻辑选择、条件行(字段 + 关联徽标 / 操作符 / 值,两行堆叠)、条件组(浅蓝左边框 + 组内逻辑 + 删除组)、底部「已配置 N 个条件 / 取消 / 应用查询」;无遮罩,表格保持可交互。
|
||||
3. **生效条件 chips 条**:搜索卡底部细分隔线下方一行,浅蓝底圆角 chip 展示每个生效条件文本,可单个 × 移除,右侧「清除全部」;未应用查询时保留条位避免跳动。
|
||||
4. **查询设置抽屉**:右侧 420px 窄抽屉(浅遮罩),按「页面常用条件 / 仅高级条件 / 已隐藏字段 / 不可用字段」四组罗列,行内拖拽手柄排序 + 「设为第一个 / 移到高级 / 隐藏 / 恢复」文字操作,底部「恢复默认 / 完成设置」;无查询权限字段整行灰显并标注「无查询权限」。
|
||||
|
||||
## 交互
|
||||
|
||||
条件编辑即时影响本地状态,「应用查询」才固化生效态;切换高级模式时常用条件以 chip 形式保留可见可清除;拖拽排序以源行半透明 + 目标行顶部插入指示线反馈。
|
||||
|
||||
## 响应式
|
||||
|
||||
搜索卡使用容器查询(`@container`):内容宽度不足 4 列时降为 3 列;高级模式下取消页面最小宽度限制,表格自行横向滚动。
|
||||
|
||||
## Agent Extensions
|
||||
|
||||
### Skill
|
||||
|
||||
- **ui-ux-pro-max**
|
||||
- 用途:在改造 `FmsQueryToolbar` 的查询区(4 列网格、chips 条、高级条件面板、设置抽屉)时,核对布局节奏、间距层级、拖拽反馈与可访问性细节。
|
||||
- 预期结果:产出与 dashboard 原型一致、且在企业级密集信息场景下可读可用的查询区视觉与交互规范,避免列宽、角标、chip 溢出等细节返工。
|
||||
@@ -0,0 +1,265 @@
|
||||
---
|
||||
name: 组件库动效体系落地
|
||||
overview: 按动效规范为 fms-vue 组件库补齐动画体系:9 个 motion token 落进 tokens.css,浮层组件(popover/modal/drawer)补离场状态机,纯 CSS 过渡组件批量补课,修复 Spin 组件不旋转的缺陷并收敛业务页手写 keyframes,全站适配 prefers-reduced-motion。
|
||||
todos:
|
||||
- id: motion-tokens
|
||||
content: 用 [subagent:code-explorer] 盘点组件样式入口,并把 9 个动效 token 与 reduced-motion 归零块写入 tokens.css
|
||||
status: completed
|
||||
- id: popover-motion
|
||||
content: 给 popover 补进出场过渡,覆盖 dropdown / tooltip / select 并确认 select 是否继承
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: modal-drawer-motion
|
||||
content: modal 与 drawer 分层进出场,Drawer 用组件级变量承载 240/200ms
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: button-input-motion
|
||||
content: button 补颜色过渡与按下缩放,input 补聚焦过渡
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: form-control-motion
|
||||
content: switch、checkbox、radio、segmented、tabs 补控件微交互过渡
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: collapse-motion
|
||||
content: FormGroup 与树节点用 grid 1fr 到 0fr 实现折叠过渡
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: feedback-motion
|
||||
content: message 与 notification 用 TransitionGroup 补进出场,scrollbar 补淡出
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: spin-motion
|
||||
content: 修复 Spin 旋转并收敛业务页面两处手写 keyframes
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: motion-verify
|
||||
content: 用 [skill:ui-ux-pro-max] 核对动效与无障碍,跑 oxlint 与编译确认无裸写时长
|
||||
status: completed
|
||||
dependencies:
|
||||
- popover-motion
|
||||
- modal-drawer-motion
|
||||
- button-input-motion
|
||||
- form-control-motion
|
||||
- collapse-motion
|
||||
- feedback-motion
|
||||
- spin-motion
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
为 `fms-vue` 内置组件库(`src/components/ui`,26 个组件)建立统一的动效体系:此前组件库里已有的动画代码被移除过,现按用户给定的动效规范全部恢复并补齐。目标是在高频后台操作场景下,让状态变化"可被感知但不拖慢操作"。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- **动效契约**:9 个动效 token(3 档时长、3 档缓动、2 档位移幅度)写入 `src/theme/tokens.css`,沿用 `--fms-` 命名空间
|
||||
- **浮层进出场**:popover、modal、drawer、下拉菜单、tooltip、select 具备进场与退场动画(遮罩与内容分层、进场错峰、退场同时走)
|
||||
- **控件微交互**:按钮 hover 变色 + 按下缩放、输入框聚焦、开关滑块位移、分段控制器与标签页指示器滑动、勾选态变化
|
||||
- **折叠展开**:表单分组与树节点用网格行高过渡实现展开收起
|
||||
- **反馈提示**:message / notification 进出场、滚动条淡出
|
||||
- **加载指示器**:修复 Spin 组件不旋转的缺陷,并将业务页面中各自手写的旋转动画收敛回组件
|
||||
- **无障碍**:系统开启"减少动态效果"时时长归零(保留状态变化、去掉过渡),浮层动画期间不移动焦点
|
||||
|
||||
## 视觉与体验效果
|
||||
|
||||
按钮按下时有极轻微的收缩反馈;浮层从遮罩淡入到内容浮起有清晰的先后层次,关闭时干脆利落;开关拨动、指示器切换跟随手指;折叠区域平滑展开。整体节奏偏快(100~200ms),不出现回弹、位移、页面转场等干扰性效果。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 框架与样式:Vue 3(`script setup`)+ SCSS(沿用 `@use './index.scss'` 拆分约定)
|
||||
- 变量体系:CSS 自定义属性,复用现有 `--fms-` 命名空间与 `src/theme/tokens.css` 单源
|
||||
- 过渡机制:CSS `transition` + Vue 内置 Transition / TransitionGroup 组件
|
||||
- 校验:`pnpm exec oxlint` + Vite 编译检查(dev server 端口 5082)
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 分层策略
|
||||
|
||||
1. **契约层**:9 个 token 落到 `tokens.css` 的 `:root`,并追加 `prefers-reduced-motion` 归零块。所有组件只引用变量、不写裸时长,保证"改一处、全站一致"。
|
||||
2. **行为层**:浮层组件用 Vue 内置 Transition 承载进出场 —— 这是本次工作量最大、也最容易出 bug 的部分,先做 popover(dropdown / tooltip / select 的共同基座)。选择:全局开关只做系统级自动适配,不新建 ConfigProvider、不引入手动开关状态。
|
||||
3. **表现层**:纯 CSS 补控件微交互,无卸载时序风险,可并行推进。
|
||||
|
||||
### 关键决策与理由
|
||||
|
||||
**决策 1:浮层离场用 Vue 内置 Transition,不手写 `isLeaving` 状态类 + `setTimeout`**
|
||||
规范给出的 `.is-leaving` 写法是框架无关方案;本项目是 Vue 3,用内置过渡组件更省代码。更关键的理由是:**reduced-motion 下 token 归零会让 `transition-duration` 变成 `0ms`,此时 `transitionend` 事件不会触发** —— 若卸载靠该事件驱动,浮层会卡在页面上关不掉。Vue 的 Transition 会读取计算样式,时长为 0 时直接结束过渡,天然不会卡死。代价是过渡类名变成 `.fms-xxx-enter-active` 形态,但时长与缓动参数完全按规范取值。
|
||||
|
||||
**决策 2:Drawer 的 240ms / 200ms 用组件级 CSS 变量承载,不裸写数字**
|
||||
规范建议这两个值写死在组件里,但那样会逃出 reduced-motion 的变量归零覆盖,无障碍要求落空。改为组件级变量(默认值保持 240/200 的视觉意图),并在组件级补一条 reduced-motion 覆盖,兼顾"不进全局 token"与"可被关闭"。
|
||||
|
||||
**决策 3:只动 opacity / transform / color 系**
|
||||
所有新增过渡严格限定这三类属性,走合成层,不触发布局与重绘。表格行、数字滚动、页面转场、`transition: all` 一律不做。
|
||||
|
||||
**决策 4:折叠展开用网格 `1fr → 0fr`**
|
||||
`height: auto` 无法过渡。FormGroup 当前已是 `v-show` + grid 结构,直接可用;树节点同步采用同一手法,避免 JS 测高。
|
||||
|
||||
**决策 5:补齐规范对照表遗漏的场景**
|
||||
Switch 滑块、Segmented 滑块、Scrollbar 淡出、Spin 循环旋转(为"禁止无意义图标旋转"开显式豁免)、Tooltip 用更快的档位(高频鼠标扫过场景,避免拖残影);同时明确位移幅度取值用途:下拉 / 提示用 4px、Modal 用 8px、Drawer 用整屏位移。
|
||||
|
||||
**决策 6:按钮 hover 采用规范原值(base 档同时管进出)**
|
||||
此前工具栏 ghost 按钮的"背景色闪烁"真因是性能浮层压住按钮命中区(已单独修复),与过渡时长无关。此处不设"离开即时"之类的例外规则,保持全站一致。
|
||||
|
||||
### 性能与可靠性
|
||||
|
||||
- 只改合成层属性,无布局抖动;无新增 DOM 节点(Transition 不额外渲染)
|
||||
- 每个组件改动互相独立,浮层与纯 CSS 组分批推进,避免一次性全量改动难以定位问题
|
||||
- 退场时序统一由 Transition 管理,不引入自定义定时器,杜绝"浮层关不掉 / 提前卸载闪烁"两类经典 bug
|
||||
|
||||
### 验证方式
|
||||
|
||||
用户明确要求不自动启动浏览器实测。本次以静态检查为主:`oxlint` + Vite 编译 + 逐文件核对"无裸写时长数字、无禁用属性、reduced-motion 覆盖完整"。
|
||||
|
||||
## 实现注意
|
||||
|
||||
- 严格沿用存量风格:SCSS 独立文件用 `@use './index.scss'` 引入;注释用中文说明"为什么这样做",与现有代码一致
|
||||
- 除 Transition 包裹层外不改动组件 DOM 结构与既有 props / emits,保证向后兼容
|
||||
- popover 是 dropdown / tooltip / select 的基座,优先改造收益最大;select 是否完全复用 popover 需在实施时确认,若为独立实现在其内部补同样处理
|
||||
- 控件类动画不影响键盘操作与焦点顺序;浮层禁止在动画期间抢焦点
|
||||
- 不为动画而动画:无状态变化的组件(栅格、分割器等拖拽即时类)不强行加过渡
|
||||
|
||||
## 架构设计
|
||||
|
||||
本次改动是"在既有组件库上加一层动效契约",不引入新的架构模式:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["tokens.css<br/>9 个动效 token + reduced-motion 归零"] --> B["浮层组件<br/>popover / modal / drawer"]
|
||||
A --> C["表单控件<br/>button / input / switch / checkbox / radio"]
|
||||
A --> D["导航与容器<br/>tabs / segmented / form-group / tree / scrollbar"]
|
||||
A --> E["反馈与加载<br/>message / notification / spin"]
|
||||
B --> B1["dropdown / tooltip / select<br/>经 popover 继承"]
|
||||
F["业务页面手写旋转动画"] --> E
|
||||
```
|
||||
|
||||
- **契约层**:`src/theme/tokens.css` 提供唯一动效参数来源
|
||||
- **行为层**:浮层组件内置过渡状态,dropdown / tooltip / select 通过基座继承
|
||||
- **表现层**:各组件 SCSS 引用 token,声明自己用哪几档
|
||||
|
||||
## 目录结构
|
||||
|
||||
本次改动为既有项目补动效,涉及文件如下(未列出的文件不动):
|
||||
|
||||
```
|
||||
fms-vue/
|
||||
├── src/
|
||||
│ ├── theme/
|
||||
│ │ └── tokens.css # [MODIFY] 追加 9 个动效 token(3 档时长 / 3 档缓动 / 2 档位移)到 :root,
|
||||
│ │ # 并追加 @media (prefers-reduced-motion: reduce) 块把三档时长归零。
|
||||
│ │ # 深色 .dark 块不需要覆盖(动效参数与色彩无关)。
|
||||
│ ├── components/ui/
|
||||
│ │ ├── popover/
|
||||
│ │ │ ├── popover.vue # [MODIFY] 浮层基座:把 v-if 内容包进 Transition,补进退场。
|
||||
│ │ │ │ # 进场 base + ease-out,退场 base + ease-in;定位逻辑不动。
|
||||
│ │ │ └── index.scss # [MODIFY] 补 enter/leave 过渡类(opacity + 4px 位移)
|
||||
│ │ ├── modal/
|
||||
│ │ │ ├── modal.vue # [MODIFY] 遮罩与内容分层过渡,进场遮罩先铺、内容延迟 20ms 落
|
||||
│ │ │ └── index.scss # [MODIFY] 进场 slow + ease-out(scale 0.96→1),退场 base + ease-in;
|
||||
│ │ │ # 遮罩进场 base + ease-out、退场 base + ease-in
|
||||
│ │ ├── drawer/
|
||||
│ │ │ ├── drawer.vue # [MODIFY] 补进场/退场过渡,复用与 modal 相同的分层写法
|
||||
│ │ │ └── index.scss # [MODIFY] 用组件级变量承载 240ms 进 / 200ms 出(不裸写),
|
||||
│ │ │ # 并在组件级加 reduced-motion 覆盖
|
||||
│ │ ├── select/
|
||||
│ │ │ ├── select.vue # [MODIFY] 确认是否复用 popover;若独立实现则补下拉进出场
|
||||
│ │ │ └── index.scss # [MODIFY] 同上,补选项面板过渡类
|
||||
│ │ ├── dropdown/index.scss # [MODIFY] 下拉项 hover 底色过渡(菜单容器过渡由 popover 提供)
|
||||
│ │ ├── tooltip/index.scss # [MODIFY] 用更快档位(100ms),高频鼠标扫过场景避免拖残影
|
||||
│ │ ├── button/index.scss # [MODIFY] 补 background-color / border-color / box-shadow(base + standard)
|
||||
│ │ │ # 与 transform(fast + standard),补 :active 的 scale 0.97
|
||||
│ │ ├── input/
|
||||
│ │ │ ├── input.vue # [MODIFY] 核对聚焦/校验态切换所依赖的 class 是否齐备
|
||||
│ │ │ └── index.scss # [MODIFY] 补边框色与阴影的聚焦过渡(base + standard)
|
||||
│ │ ├── switch/index.scss # [MODIFY] 滑块 translateX 过渡(fast + standard),轨道颜色同步
|
||||
│ │ ├── checkbox/index.scss # [MODIFY] 勾选态颜色/描边过渡
|
||||
│ │ ├── radio/index.scss # [MODIFY] 选中态过渡,与 checkbox 同参数
|
||||
│ │ ├── segmented/index.scss # [MODIFY] 滑块位移过渡(base + standard),与 tabs 指示器同类
|
||||
│ │ ├── tabs/
|
||||
│ │ │ ├── tabs.vue # [MODIFY] 若已有滑动指示器元素,补其位移态;无则仅补 pane 淡入
|
||||
│ │ │ └── index.scss # [MODIFY] 指示器位移过渡(base + standard)
|
||||
│ │ ├── form/
|
||||
│ │ │ ├── FormGroup.vue # [MODIFY] 折叠容器改为 grid 1fr↔0fr 过渡(分组图标同步旋转)
|
||||
│ │ │ └── index.scss # [MODIFY] 折叠过渡类(base + standard),禁止动 height
|
||||
│ │ ├── tree/
|
||||
│ │ │ ├── tree-node.vue # [MODIFY] 子节点容器套用 grid 1fr↔0fr 手法
|
||||
│ │ │ └── index.scss # [MODIFY] 展开收起过渡 + 展开箭头旋转(fast + standard)
|
||||
│ │ ├── message/
|
||||
│ │ │ ├── message-container.vue # [MODIFY] 列表渲染改为 TransitionGroup,使项进出有过渡
|
||||
│ │ │ └── index.scss # [MODIFY] 进场 base + ease-out、退场 base + ease-in
|
||||
│ │ ├── notification/
|
||||
│ │ │ ├── notification-container.vue # [MODIFY] 同 message 的 TransitionGroup 处理
|
||||
│ │ │ └── index.scss # [MODIFY] 同 message 参数
|
||||
│ │ ├── scrollbar/scrollbar.vue # [MODIFY] leave 模式下滚动条淡出(opacity 过渡,样式为组件内联)
|
||||
│ │ ├── spin/index.scss # [MODIFY] 补 @keyframes 旋转动画到 .spin-indicator,
|
||||
│ │ │ # 时长用组件级变量承载以便 reduced-motion 归零
|
||||
│ │ ├── tag/index.scss # [MODIFY] 关闭/删除类交互的轻量过渡(若存在)
|
||||
│ │ └── pagination/index.scss # [MODIFY] 页码切换态过渡(若存在)
|
||||
│ └── views/module/
|
||||
│ ├── module-management/index.vue # [MODIFY] 移除手写 .mm-config-state__spinner 与 @keyframes mm-config-spin,
|
||||
│ │ # 改用 Spin 组件,复用统一旋转参数
|
||||
│ └── menu-management/index.vue # [MODIFY] 同上,移除 @keyframes mm-spin
|
||||
```
|
||||
|
||||
## 关键代码结构
|
||||
|
||||
动效契约(`src/theme/tokens.css`)是全部组件依赖的唯一来源,必须精确定义:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 时长:跟手操作 / 同层小元素 / 占满屏幕 三档封顶 */
|
||||
--fms-duration-fast: 100ms;
|
||||
--fms-duration-base: 150ms;
|
||||
--fms-duration-slow: 200ms;
|
||||
|
||||
/* 缓动:进场用 out、退场用 in、纯颜色变化用 standard */
|
||||
--fms-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--fms-ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--fms-ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
|
||||
/* 位移幅度:下拉 / 提示用 sm,Modal 用 md */
|
||||
--fms-offset-sm: 4px;
|
||||
--fms-offset-md: 8px;
|
||||
}
|
||||
|
||||
/* 无障碍:时长归零保留状态变化,去掉过渡过程 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:root {
|
||||
--fms-duration-fast: 0ms;
|
||||
--fms-duration-base: 0ms;
|
||||
--fms-duration-slow: 0ms;
|
||||
}
|
||||
}
|
||||
|
||||
/* 组件级变量示例(Drawer):不进全局 token,但同样可被上面的覆盖命中 */
|
||||
.fms-drawer {
|
||||
--fms-drawer-duration-in: 240ms;
|
||||
--fms-drawer-duration-out: 200ms;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fms-drawer {
|
||||
--fms-drawer-duration-in: 0ms;
|
||||
--fms-drawer-duration-out: 0ms;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Extensions
|
||||
|
||||
### SubAgent
|
||||
|
||||
- **code-explorer**
|
||||
- Purpose: 在动工前一次性盘点 `src/components/ui` 下 26 个组件的样式入口、可动画属性与浮层显隐结构,确认"哪些组件走 Transition、哪些纯 CSS 补课、select 是否复用 popover",避免逐文件试探。
|
||||
- Expected outcome: 输出一份带路径的改动清单与每类组件的处理方式,作为后续实现的任务依据。
|
||||
|
||||
### Skill
|
||||
|
||||
- **ui-ux-pro-max**
|
||||
- Purpose: 核对本次动效参数与无障碍设计的合理性(时长是否落在后台高频场景的舒适区间、缓动方向是否正确、reduced-motion 与焦点处理是否到位),并检查是否有遗漏的交互态。
|
||||
- Expected outcome: 一份针对动效参数与无障碍的核对结论,确认无系统性偏差后再收尾。
|
||||
+31
-31
@@ -116,7 +116,7 @@ s_module
|
||||
| 表单布局 | JSON | 支持分组、栅格、选项卡和条件显示 |
|
||||
| 查询配置 | JSON | 支持同字段多条件、条件组和嵌套逻辑 |
|
||||
| 字段分组 | 布局 JSON 节点 | 不再建立独立字段分组表 |
|
||||
| 用户偏好 | JSON 偏离量 | 只保存用户相对默认配置的修改 |
|
||||
| 用户偏好 | JSON 偏离量 | 只保存用户相对系统默认的修改 |
|
||||
| 多语言 | 关系表 | 支持按语言查询、导入、导出和回退 |
|
||||
| 用户与部门 | 关系表 | 用户归属部门,部门支持层级树 |
|
||||
| 权限 | 关系表 | 见第 14 节,不与界面布局混合 |
|
||||
@@ -341,6 +341,21 @@ query
|
||||
|
||||
当前阶段不维护同一类型的多套命名配置。若未来确实需要多套列表、表单或查询方案,再增加配置编码字段,或在 JSON 内增加方案节点。
|
||||
|
||||
### 6.0 配置两层继承(系统默认 → 个人)
|
||||
|
||||
模块 UI 配置采用两层继承,由两张字符结构一致的表(`s_module_schema` 系统默认 + `s_user_module_pref` 个人覆盖)表达。配置层级由**表本身**体现,不在 JSON 内部用 `default`、`system`、`user` 等节点表示层级。
|
||||
|
||||
```text
|
||||
系统默认(s_module_schema) 产品/开发者提供的标准 Schema
|
||||
↓ 覆盖
|
||||
个人配置(s_user_module_pref) 用户在系统默认之上的个人调整
|
||||
```
|
||||
|
||||
- `view`、`edit`、`query` 是三种一致的 `b_schema_type`,两层共用同一命名,不拆成独立表,也不引入部门/角色/租户配置层。
|
||||
- 优先级:个人 > 系统默认。个人层无记录时即系统默认。
|
||||
- **保存为通用**:把当前查询布局回写为新的系统默认——直接更新 `s_module_schema.query` 的 quick 顺序并标记隐藏字段,同时清空该用户的个人覆盖,所有用户默认即在改后配置。
|
||||
- 覆盖方式:`s_module_schema` 保存完整默认 Schema;`s_user_module_pref` 只保存相对系统默认的**增量覆盖**,不全量复制,因此系统默认修改后能自然继承。
|
||||
|
||||
### 6.1 列表配置示例
|
||||
|
||||
```json
|
||||
@@ -427,57 +442,42 @@ query
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 用户个性化
|
||||
## 7. 用户个性化(个人层)
|
||||
|
||||
原有列表字段偏好和查询字段偏好合并为一张表。用户偏好只保存相对模块默认配置的偏离量,不复制完整模块配置。
|
||||
个人配置是两层继承的最上层,只保存相对系统默认(`s_module_schema`)的偏离量,不复制完整模块配置。与 `s_module_schema` 统一使用 `b_schema_type` + `b_schema_json`,按 schema 类型分别存一条。
|
||||
|
||||
```sql
|
||||
-- 用户模块偏好表
|
||||
-- 用户模块偏好表(个人层)
|
||||
create table dbo.s_user_module_pref (
|
||||
b_user_id varchar(50) not null, -- 用户编码
|
||||
b_user_id varchar(50) not null, -- 用户编码(关联 b_user.b_id)
|
||||
b_module_id varchar(50) not null, -- 模块编码
|
||||
b_pref_json nvarchar(max) not null, -- 用户偏离配置 JSON
|
||||
b_schema_type varchar(20) not null, -- view / edit / query
|
||||
b_schema_json nvarchar(max) not null, -- 个人覆盖 JSON(相对系统默认)
|
||||
b_updated_by varchar(50) null, -- 最后修改人
|
||||
b_updated_at datetime2 null, -- 最后修改时间
|
||||
primary key (b_user_id, b_module_id)
|
||||
primary key (b_user_id, b_module_id, b_schema_type)
|
||||
);
|
||||
|
||||
create index ix_s_user_module_pref_module
|
||||
on dbo.s_user_module_pref (b_module_id, b_user_id);
|
||||
```
|
||||
|
||||
偏好示例:
|
||||
查询布局偏好示例(平铺、增量,不使用 `view.default` / `query.default` 节点):
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"view": {
|
||||
"default": {
|
||||
"hidden": ["b_bz"],
|
||||
"order": ["b_no", "b_name", "b_status"],
|
||||
"width": {
|
||||
"b_no": 160,
|
||||
"b_name": 240
|
||||
}
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"default": {
|
||||
"quick": ["b_no", "b_status"]
|
||||
}
|
||||
}
|
||||
"area": { "b_no": "quick", "b_bz": "hidden" },
|
||||
"order": { "b_no": 10, "b_name": 20 }
|
||||
}
|
||||
```
|
||||
|
||||
偏好 JSON 中的 `view.default`、`query.default` 是当前唯一方案的占位,与第 6 节“当前阶段不维护同一类型的多套命名配置”一致;将来出现多套方案时才用该层级区分。
|
||||
合并规则(两层依次应用):
|
||||
|
||||
合并规则:
|
||||
|
||||
1. 读取 `s_module_schema` 中的模块默认配置;
|
||||
2. 读取当前用户的 `s_user_module_pref.b_pref_json`;
|
||||
3. 按配置类型应用偏离量;
|
||||
4. 已停用、已删除或无权限的字段不得因用户偏好重新出现;
|
||||
5. 恢复默认时删除当前模块偏好记录,或删除 JSON 中对应配置片段。
|
||||
1. 读取 `s_module_schema` 系统默认(query 布局由 `quick` 列表 + 条件推导);
|
||||
2. 读取当前用户的 `s_user_module_pref.b_schema_json`,按 schema 类型叠加个人偏离量,得到最终配置;
|
||||
3. 已停用、已删除或无权限的字段(`s_user_field_power`)不得因配置重新出现;权限过滤在配置合并之后执行,永远优先于配置;
|
||||
4. 恢复默认(恢复通用)= 删除 `s_user_module_pref` 对应行,回落到系统默认;「恢复默认」是删除覆盖,不是新增配置层。
|
||||
|
||||
## 8. 自动编码
|
||||
|
||||
|
||||
+3599
-1353
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,138 @@
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 导出旧系统库(FMS)的用户表结构为 Markdown 文档。
|
||||
*
|
||||
* 用法:
|
||||
* javac -encoding UTF-8 -cp "mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown.java
|
||||
* java -cp ".;mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown <输出文件绝对路径> [数据库名,默认 G3HY2025]
|
||||
*/
|
||||
public class SchemaDumpMarkdown {
|
||||
|
||||
static final String BASE_URL = "jdbc:sqlserver://118.89.70.199:1433;encrypt=true;trustServerCertificate=true";
|
||||
static final String USER = "sa";
|
||||
static final String PASSWORD = "zhang520..";
|
||||
|
||||
static class Col {
|
||||
String name, type, len, precScale, nullable, def;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String out = args.length > 0 ? args[0] : "Fms旧系统表结构.md";
|
||||
String db = args.length > 1 ? args[1] : "G3HY2025";
|
||||
String url = BASE_URL + ";databaseName=" + db;
|
||||
|
||||
Map<String, List<Col>> cols = new LinkedHashMap<>();
|
||||
Map<String, String> pk = new LinkedHashMap<>();
|
||||
Map<String, List<String>> fk = new LinkedHashMap<>();
|
||||
|
||||
try (Connection conn = DriverManager.getConnection(url, USER, PASSWORD)) {
|
||||
// 表清单
|
||||
List<String> tables = new ArrayList<>();
|
||||
try (Statement st = conn.createStatement();
|
||||
ResultSet rs = st.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES "
|
||||
+ "WHERE TABLE_TYPE='BASE TABLE' ORDER BY TABLE_NAME")) {
|
||||
while (rs.next()) tables.add(rs.getString(1));
|
||||
}
|
||||
|
||||
// 字段
|
||||
try (Statement st = conn.createStatement();
|
||||
ResultSet rs = st.executeQuery("SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, "
|
||||
+ "CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE, COLUMN_DEFAULT "
|
||||
+ "FROM INFORMATION_SCHEMA.COLUMNS ORDER BY TABLE_NAME, ORDINAL_POSITION")) {
|
||||
while (rs.next()) {
|
||||
Col c = new Col();
|
||||
c.name = rs.getString(2);
|
||||
c.type = rs.getString(3);
|
||||
int maxLen = rs.getInt(4);
|
||||
c.len = rs.wasNull() ? "" : (maxLen == -1 ? "MAX" : String.valueOf(maxLen));
|
||||
Object p = rs.getObject(5);
|
||||
if (p != null) c.precScale = p + "," + rs.getObject(6);
|
||||
else c.precScale = "";
|
||||
c.nullable = rs.getString(7);
|
||||
c.def = rs.getString(8);
|
||||
cols.computeIfAbsent(rs.getString(1), k -> new ArrayList<>()).add(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 主键
|
||||
try (Statement st = conn.createStatement();
|
||||
ResultSet rs = st.executeQuery("SELECT OBJECT_NAME(k.parent_object_id), c.name "
|
||||
+ "FROM sys.key_constraints k "
|
||||
+ "JOIN sys.index_columns ic ON ic.object_id = k.parent_object_id AND ic.index_id = k.unique_index_id "
|
||||
+ "JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id "
|
||||
+ "WHERE k.type = 'PK' "
|
||||
+ "ORDER BY 1, ic.key_ordinal")) {
|
||||
while (rs.next()) {
|
||||
String t = rs.getString(1);
|
||||
pk.merge(t, rs.getString(2), (a, b) -> a + ", " + b);
|
||||
}
|
||||
}
|
||||
|
||||
// 外键
|
||||
try (Statement st = conn.createStatement();
|
||||
ResultSet rs = st.executeQuery("SELECT OBJECT_NAME(f.parent_object_id), "
|
||||
+ "COL_NAME(fc.parent_object_id, fc.parent_column_id), "
|
||||
+ "OBJECT_NAME(f.referenced_object_id), "
|
||||
+ "COL_NAME(fc.referenced_object_id, fc.referenced_column_id) "
|
||||
+ "FROM sys.foreign_keys f JOIN sys.foreign_key_columns fc ON f.object_id = fc.constraint_object_id "
|
||||
+ "ORDER BY 1, 2")) {
|
||||
while (rs.next()) {
|
||||
fk.computeIfAbsent(rs.getString(1), k -> new ArrayList<>())
|
||||
.add(rs.getString(2) + " -> " + rs.getString(3) + "(" + rs.getString(4) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("# Fms 旧系统表结构(G3HY2025 库)\n\n");
|
||||
sb.append("> 来源库:`").append(db).append("`(jdbc:sqlserver://118.89.70.199:1433;databaseName=").append(db).append(")\n");
|
||||
sb.append("> 仅包含用户表(BASE TABLE),不含视图。字段「备注」列留空,供后续补充含义。\n");
|
||||
sb.append("> 拉取时间:").append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())).append("\n");
|
||||
sb.append("> 约束情况:该库未定义外键约束(`sys.foreign_keys` = 0);")
|
||||
.append(pk.size()).append(" 张表定义了主键,").append(tables.size() - pk.size())
|
||||
.append(" 张表无主键。\n\n");
|
||||
sb.append("共 **").append(tables.size()).append("** 张表。\n\n");
|
||||
|
||||
sb.append("## 表清单(索引)\n\n");
|
||||
sb.append("| # | 表名 | 字段数 | 主键 |\n| --- | --- | --- | --- |\n");
|
||||
int i = 1;
|
||||
for (String t : tables) {
|
||||
List<Col> cs = cols.getOrDefault(t, Collections.emptyList());
|
||||
sb.append("| ").append(i++).append(" | `").append(t).append("` | ").append(cs.size())
|
||||
.append(" | ").append(esc(pk.getOrDefault(t, ""))).append(" |\n");
|
||||
}
|
||||
|
||||
sb.append("\n## 各表字段结构\n");
|
||||
for (String t : tables) {
|
||||
List<Col> cs = cols.getOrDefault(t, Collections.emptyList());
|
||||
sb.append("\n### ").append(t).append("\n\n");
|
||||
if (pk.containsKey(t)) sb.append("**主键:** ").append(esc(pk.get(t))).append("\n\n");
|
||||
List<String> fs = fk.get(t);
|
||||
if (fs != null && !fs.isEmpty()) {
|
||||
sb.append("**外键:**\n");
|
||||
for (String f : fs) sb.append("- ").append(esc(f)).append("\n");
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append("| 字段名 | 类型 | 长度 | 精度,小数位 | 可空 | 默认值 | 备注 |\n");
|
||||
sb.append("| --- | --- | --- | --- | --- | --- | --- |\n");
|
||||
for (Col c : cs) {
|
||||
sb.append("| `").append(c.name).append("` | ").append(c.type).append(" | ").append(c.len)
|
||||
.append(" | ").append(c.precScale).append(" | ").append(c.nullable)
|
||||
.append(" | ").append(c.def == null ? "" : esc(c.def)).append(" | |\n");
|
||||
}
|
||||
}
|
||||
|
||||
try (Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8))) {
|
||||
w.write(sb.toString());
|
||||
}
|
||||
System.out.println("已写出 " + tables.size() + " 张表 -> " + out);
|
||||
}
|
||||
}
|
||||
|
||||
static String esc(String s) {
|
||||
return s == null ? "" : s.replace("|", "\\|");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,393 @@
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e2]:
|
||||
- generic [ref=e139]:
|
||||
- generic [ref=e141]:
|
||||
- button "金地集团 企业版" [ref=e143] [cursor=pointer]:
|
||||
- generic [ref=e147]:
|
||||
- generic [ref=e148]: 金地集团
|
||||
- generic [ref=e149]: 企业版
|
||||
- navigation "主导航" [ref=e154]:
|
||||
- link "工作台" [ref=e155] [cursor=pointer]:
|
||||
- /url: /dashboard
|
||||
- link "组件库" [ref=e163] [cursor=pointer]:
|
||||
- /url: /demo
|
||||
- button "模块中心" [ref=e172] [cursor=pointer]
|
||||
- separator
|
||||
- generic [ref=e181]:
|
||||
- banner [ref=e182]:
|
||||
- button "折叠侧栏" [ref=e184] [cursor=pointer]
|
||||
- tablist [ref=e190]:
|
||||
- tab "工作台" [selected] [ref=e191] [cursor=pointer]
|
||||
- generic [ref=e193]:
|
||||
- button "English" [ref=e194] [cursor=pointer]
|
||||
- button "主题色切换" [ref=e199] [cursor=pointer]
|
||||
- button "切换到深色模式" [ref=e206] [cursor=pointer]
|
||||
- button "全屏" [ref=e213] [cursor=pointer]
|
||||
- button "通知" [ref=e219] [cursor=pointer]
|
||||
- button "超 超级管理员" [ref=e224] [cursor=pointer]:
|
||||
- generic [ref=e225]: 超
|
||||
- generic [ref=e226]: 超级管理员
|
||||
- generic [ref=e230]:
|
||||
- generic [ref=e231]:
|
||||
- button "纯列表" [ref=e232] [cursor=pointer]
|
||||
- button "树+表格" [ref=e233] [cursor=pointer]
|
||||
- generic [ref=e234]:
|
||||
- generic [ref=e235]:
|
||||
- button "查询设置" [ref=e236] [cursor=pointer]
|
||||
- generic [ref=e240]:
|
||||
- generic [ref=e241]:
|
||||
- generic [ref=e242]: 编号
|
||||
- textbox [ref=e244]
|
||||
- generic [ref=e245]:
|
||||
- generic [ref=e246]: 名称
|
||||
- textbox [ref=e248]
|
||||
- generic [ref=e249]:
|
||||
- generic [ref=e250]: 类型
|
||||
- combobox [ref=e253]
|
||||
- generic [ref=e256]:
|
||||
- generic [ref=e257]: 状态
|
||||
- combobox [ref=e260]
|
||||
- generic [ref=e263]:
|
||||
- generic [ref=e264]:
|
||||
- button "查询" [ref=e265] [cursor=pointer]
|
||||
- button "重置" [ref=e266] [cursor=pointer]
|
||||
- generic [ref=e267]:
|
||||
- button "更多条件" [ref=e268] [cursor=pointer]
|
||||
- button "高级查询" [ref=e271] [cursor=pointer]
|
||||
- generic [ref=e273]:
|
||||
- generic [ref=e274]:
|
||||
- generic [ref=e275]:
|
||||
- button "新增" [ref=e276] [cursor=pointer]
|
||||
- button "导入" [ref=e278] [cursor=pointer]
|
||||
- button "删除" [disabled] [ref=e282]
|
||||
- generic [ref=e286]:
|
||||
- button "EN" [ref=e287] [cursor=pointer]
|
||||
- button "刷新数据" [ref=e288] [cursor=pointer]
|
||||
- button "导出" [ref=e294] [cursor=pointer]
|
||||
- button "列设置" [ref=e298] [cursor=pointer]
|
||||
- button "更多" [ref=e301] [cursor=pointer]
|
||||
- table [ref=e310]:
|
||||
- rowgroup [ref=e311]:
|
||||
- row [ref=e312]:
|
||||
- columnheader
|
||||
- columnheader [ref=e313]:
|
||||
- generic [ref=e315] [cursor=pointer]:
|
||||
- checkbox
|
||||
- columnheader "序号" [ref=e317]
|
||||
- columnheader "ID" [ref=e320]
|
||||
- columnheader "名称" [ref=e323]
|
||||
- columnheader "类型" [ref=e326]
|
||||
- columnheader "状态" [ref=e329]
|
||||
- columnheader "启用" [ref=e332]
|
||||
- columnheader "评分" [ref=e335]
|
||||
- columnheader "字段 1" [ref=e338]
|
||||
- columnheader "字段 2" [ref=e341]
|
||||
- columnheader "字段 3" [ref=e344]
|
||||
- columnheader "字段 4" [ref=e347]
|
||||
- columnheader "字段 5" [ref=e350]
|
||||
- columnheader "字段 6" [ref=e353]
|
||||
- columnheader [ref=e356]
|
||||
- rowgroup [ref=e357]:
|
||||
- row
|
||||
- row [ref=e358]:
|
||||
- cell
|
||||
- cell [ref=e359]:
|
||||
- generic [ref=e360] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "1" [ref=e362]
|
||||
- cell "1" [ref=e364]
|
||||
- cell "任务-00001" [ref=e366]
|
||||
- cell "任务" [ref=e368]
|
||||
- cell "启用" [ref=e370]
|
||||
- cell [ref=e372]:
|
||||
- generic [ref=e373] [cursor=pointer]:
|
||||
- checkbox "启用"
|
||||
- cell "85" [ref=e375]
|
||||
- cell "1-1" [ref=e377]
|
||||
- cell "1-2" [ref=e379]
|
||||
- cell "1-3" [ref=e381]
|
||||
- cell "1-4" [ref=e383]
|
||||
- cell "1-5" [ref=e385]
|
||||
- cell "1-6" [ref=e387]
|
||||
- cell [ref=e389]
|
||||
- row [ref=e390]:
|
||||
- cell
|
||||
- cell [ref=e391]:
|
||||
- generic [ref=e392] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "2" [ref=e394]
|
||||
- cell "2" [ref=e396]
|
||||
- cell "任务-00002" [ref=e398]
|
||||
- cell "作业" [ref=e400]
|
||||
- cell "禁用" [ref=e402]
|
||||
- cell "启用" [ref=e404]:
|
||||
- generic [ref=e405] [cursor=pointer]:
|
||||
- checkbox "启用" [checked]
|
||||
- cell "56.4" [ref=e407]
|
||||
- cell "2-1" [ref=e409]
|
||||
- cell "2-2" [ref=e411]
|
||||
- cell "2-3" [ref=e413]
|
||||
- cell "2-4" [ref=e415]
|
||||
- cell "2-5" [ref=e417]
|
||||
- cell "2-6" [ref=e419]
|
||||
- cell [ref=e421]
|
||||
- row [ref=e422]:
|
||||
- cell
|
||||
- cell [ref=e423]:
|
||||
- generic [ref=e424] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "3" [ref=e426]
|
||||
- cell "3" [ref=e428]
|
||||
- cell "任务-00003" [ref=e430]
|
||||
- cell "流程" [ref=e432]
|
||||
- cell "待审" [ref=e434]
|
||||
- cell [ref=e436]:
|
||||
- generic [ref=e437] [cursor=pointer]:
|
||||
- checkbox "启用"
|
||||
- cell "48" [ref=e439]
|
||||
- cell "3-1" [ref=e441]
|
||||
- cell "3-2" [ref=e443]
|
||||
- cell "3-3" [ref=e445]
|
||||
- cell "3-4" [ref=e447]
|
||||
- cell "3-5" [ref=e449]
|
||||
- cell "3-6" [ref=e451]
|
||||
- cell [ref=e453]
|
||||
- row [ref=e454]:
|
||||
- cell
|
||||
- cell [ref=e455]:
|
||||
- generic [ref=e456] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "4" [ref=e458]
|
||||
- cell "4" [ref=e460]
|
||||
- cell "任务-00004" [ref=e462]
|
||||
- cell "告警" [ref=e464]
|
||||
- cell "启用" [ref=e466]
|
||||
- cell "启用" [ref=e468]:
|
||||
- generic [ref=e469] [cursor=pointer]:
|
||||
- checkbox "启用" [checked]
|
||||
- cell "64.9" [ref=e471]
|
||||
- cell "4-1" [ref=e473]
|
||||
- cell "4-2" [ref=e475]
|
||||
- cell "4-3" [ref=e477]
|
||||
- cell "4-4" [ref=e479]
|
||||
- cell "4-5" [ref=e481]
|
||||
- cell "4-6" [ref=e483]
|
||||
- cell [ref=e485]
|
||||
- row [ref=e486]:
|
||||
- cell
|
||||
- cell [ref=e487]:
|
||||
- generic [ref=e488] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "5" [ref=e490]
|
||||
- cell "5" [ref=e492]
|
||||
- cell "任务-00005" [ref=e494]
|
||||
- cell "任务" [ref=e496]
|
||||
- cell "禁用" [ref=e498]
|
||||
- cell [ref=e500]:
|
||||
- generic [ref=e501] [cursor=pointer]:
|
||||
- checkbox "启用"
|
||||
- cell "43.7" [ref=e503]
|
||||
- cell "5-1" [ref=e505]
|
||||
- cell "5-2" [ref=e507]
|
||||
- cell "5-3" [ref=e509]
|
||||
- cell "5-4" [ref=e511]
|
||||
- cell "5-5" [ref=e513]
|
||||
- cell "5-6" [ref=e515]
|
||||
- cell [ref=e517]
|
||||
- row [ref=e518]:
|
||||
- cell
|
||||
- cell [ref=e519]:
|
||||
- generic [ref=e520] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "6" [ref=e522]
|
||||
- cell "6" [ref=e524]
|
||||
- cell "任务-00006" [ref=e526]
|
||||
- cell "作业" [ref=e528]
|
||||
- cell "待审" [ref=e530]
|
||||
- cell "启用" [ref=e532]:
|
||||
- generic [ref=e533] [cursor=pointer]:
|
||||
- checkbox "启用" [checked]
|
||||
- cell "70.6" [ref=e535]
|
||||
- cell "6-1" [ref=e537]
|
||||
- cell "6-2" [ref=e539]
|
||||
- cell "6-3" [ref=e541]
|
||||
- cell "6-4" [ref=e543]
|
||||
- cell "6-5" [ref=e545]
|
||||
- cell "6-6" [ref=e547]
|
||||
- cell [ref=e549]
|
||||
- row [ref=e550]:
|
||||
- cell
|
||||
- cell [ref=e551]:
|
||||
- generic [ref=e552] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "7" [ref=e554]
|
||||
- cell "7" [ref=e556]
|
||||
- cell "任务-00007" [ref=e558]
|
||||
- cell "流程" [ref=e560]
|
||||
- cell "启用" [ref=e562]
|
||||
- cell [ref=e564]:
|
||||
- generic [ref=e565] [cursor=pointer]:
|
||||
- checkbox "启用"
|
||||
- cell "54.5" [ref=e567]
|
||||
- cell "7-1" [ref=e569]
|
||||
- cell "7-2" [ref=e571]
|
||||
- cell "7-3" [ref=e573]
|
||||
- cell "7-4" [ref=e575]
|
||||
- cell "7-5" [ref=e577]
|
||||
- cell "7-6" [ref=e579]
|
||||
- cell [ref=e581]
|
||||
- row [ref=e582]:
|
||||
- cell
|
||||
- cell [ref=e583]:
|
||||
- generic [ref=e584] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "8" [ref=e586]
|
||||
- cell "8" [ref=e588]
|
||||
- cell "任务-00008" [ref=e590]
|
||||
- cell "告警" [ref=e592]
|
||||
- cell "禁用" [ref=e594]
|
||||
- cell "启用" [ref=e596]:
|
||||
- generic [ref=e597] [cursor=pointer]:
|
||||
- checkbox "启用" [checked]
|
||||
- cell "67.4" [ref=e599]
|
||||
- cell "8-1" [ref=e601]
|
||||
- cell "8-2" [ref=e603]
|
||||
- cell "8-3" [ref=e605]
|
||||
- cell "8-4" [ref=e607]
|
||||
- cell "8-5" [ref=e609]
|
||||
- cell "8-6" [ref=e611]
|
||||
- cell [ref=e613]
|
||||
- row [ref=e614]:
|
||||
- cell
|
||||
- cell [ref=e615]:
|
||||
- generic [ref=e616] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "9" [ref=e618]
|
||||
- cell "9" [ref=e620]
|
||||
- cell "任务-00009" [ref=e622]
|
||||
- cell "任务" [ref=e624]
|
||||
- cell "待审" [ref=e626]
|
||||
- cell [ref=e628]:
|
||||
- generic [ref=e629] [cursor=pointer]:
|
||||
- checkbox "启用"
|
||||
- cell "71.1" [ref=e631]
|
||||
- cell "9-1" [ref=e633]
|
||||
- cell "9-2" [ref=e635]
|
||||
- cell "9-3" [ref=e637]
|
||||
- cell "9-4" [ref=e639]
|
||||
- cell "9-5" [ref=e641]
|
||||
- cell "9-6" [ref=e643]
|
||||
- cell [ref=e645]
|
||||
- row [ref=e646]:
|
||||
- cell
|
||||
- cell [ref=e647]:
|
||||
- generic [ref=e648] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "10" [ref=e650]
|
||||
- cell "10" [ref=e652]
|
||||
- cell "任务-00010" [ref=e654]
|
||||
- cell "作业" [ref=e656]
|
||||
- cell "启用" [ref=e658]
|
||||
- cell "启用" [ref=e660]:
|
||||
- generic [ref=e661] [cursor=pointer]:
|
||||
- checkbox "启用" [checked]
|
||||
- cell "5.4" [ref=e663]
|
||||
- cell "10-1" [ref=e665]
|
||||
- cell "10-2" [ref=e667]
|
||||
- cell "10-3" [ref=e669]
|
||||
- cell "10-4" [ref=e671]
|
||||
- cell "10-5" [ref=e673]
|
||||
- cell "10-6" [ref=e675]
|
||||
- cell [ref=e677]
|
||||
- row [ref=e678]:
|
||||
- cell
|
||||
- cell [ref=e679]:
|
||||
- generic [ref=e680] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "11" [ref=e682]
|
||||
- cell "11" [ref=e684]
|
||||
- cell "任务-00011" [ref=e686]
|
||||
- cell "流程" [ref=e688]
|
||||
- cell "禁用" [ref=e690]
|
||||
- cell [ref=e692]:
|
||||
- generic [ref=e693] [cursor=pointer]:
|
||||
- checkbox "启用"
|
||||
- cell "25" [ref=e695]
|
||||
- cell "11-1" [ref=e697]
|
||||
- cell "11-2" [ref=e699]
|
||||
- cell "11-3" [ref=e701]
|
||||
- cell "11-4" [ref=e703]
|
||||
- cell "11-5" [ref=e705]
|
||||
- cell "11-6" [ref=e707]
|
||||
- cell [ref=e709]
|
||||
- row [ref=e710]:
|
||||
- cell
|
||||
- cell [ref=e711]:
|
||||
- generic [ref=e712] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "12" [ref=e714]
|
||||
- cell "12" [ref=e716]
|
||||
- cell "任务-00012" [ref=e718]
|
||||
- cell "告警" [ref=e720]
|
||||
- cell "待审" [ref=e722]
|
||||
- cell "启用" [ref=e724]:
|
||||
- generic [ref=e725] [cursor=pointer]:
|
||||
- checkbox "启用" [checked]
|
||||
- cell "89.2" [ref=e727]
|
||||
- cell "12-1" [ref=e729]
|
||||
- cell "12-2" [ref=e731]
|
||||
- cell "12-3" [ref=e733]
|
||||
- cell "12-4" [ref=e735]
|
||||
- cell "12-5" [ref=e737]
|
||||
- cell "12-6" [ref=e739]
|
||||
- cell [ref=e741]
|
||||
- row [ref=e742]:
|
||||
- cell
|
||||
- cell [ref=e743]:
|
||||
- generic [ref=e744] [cursor=pointer]:
|
||||
- checkbox
|
||||
- cell "13" [ref=e746]
|
||||
- cell "13" [ref=e748]
|
||||
- cell "任务-00013" [ref=e750]
|
||||
- cell "任务" [ref=e752]
|
||||
- cell "启用" [ref=e754]
|
||||
- cell [ref=e756]:
|
||||
- generic [ref=e757] [cursor=pointer]:
|
||||
- checkbox "启用"
|
||||
- cell "57.5" [ref=e759]
|
||||
- cell "13-1" [ref=e761]
|
||||
- cell "13-2" [ref=e763]
|
||||
- cell "13-3" [ref=e765]
|
||||
- cell "13-4" [ref=e767]
|
||||
- cell "13-5" [ref=e769]
|
||||
- cell "13-6" [ref=e771]
|
||||
- cell [ref=e773]
|
||||
- row [ref=e774]
|
||||
- generic [ref=e775]:
|
||||
- generic [ref=e776]: 共 10000 条
|
||||
- navigation "分页" [ref=e777]:
|
||||
- list [ref=e778]:
|
||||
- listitem [ref=e779]:
|
||||
- button "上一页" [disabled] [ref=e780]
|
||||
- listitem [ref=e783]:
|
||||
- button "1" [ref=e784] [cursor=pointer]
|
||||
- listitem [ref=e785]:
|
||||
- button "2" [ref=e786] [cursor=pointer]
|
||||
- listitem [ref=e787]:
|
||||
- button "向后跳 5 页" [ref=e788] [cursor=pointer]
|
||||
- listitem [ref=e793]:
|
||||
- button "1000" [ref=e794] [cursor=pointer]
|
||||
- listitem [ref=e795]:
|
||||
- button "下一页" [ref=e796] [cursor=pointer]
|
||||
- generic "每页条数" [ref=e800]:
|
||||
- combobox [ref=e802]: "10"
|
||||
- complementary "FPS 性能监控" [ref=e76]:
|
||||
- generic [ref=e77]:
|
||||
- generic [ref=e78]: FPS
|
||||
- strong [ref=e79]: "60"
|
||||
- button "重置 FPS 统计" [ref=e80] [cursor=pointer]
|
||||
- generic [ref=e106]:
|
||||
- generic [ref=e107]: avg 60
|
||||
- generic [ref=e108]: min 53
|
||||
- alert [ref=e805]:
|
||||
- generic [ref=e809]: 登录成功
|
||||
@@ -0,0 +1,6 @@
|
||||
- generic [active] [ref=f1e1]:
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,6 @@
|
||||
- generic [active] [ref=f7e1]:
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=f1e1]:
|
||||
- generic [ref=f1e3]:
|
||||
- generic [ref=f1e4]:
|
||||
- img "FMS" [ref=f1e5]
|
||||
- generic [ref=f1e6]: FMS 企业管理系统
|
||||
- generic [ref=f1e7]:
|
||||
- button "中" [ref=f1e8] [cursor=pointer]
|
||||
- button [ref=f1e9] [cursor=pointer]
|
||||
- generic [ref=f1e12]:
|
||||
- paragraph [ref=f1e22]: 企业管理系统
|
||||
- generic [ref=f1e24]:
|
||||
- heading "登录" [level=1] [ref=f1e25]
|
||||
- paragraph [ref=f1e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=f1e27]:
|
||||
- generic [ref=f1e28]:
|
||||
- generic [ref=f1e29]:
|
||||
- text: "*"
|
||||
- generic [ref=f1e30]: 机构码
|
||||
- textbox [active] [ref=f1e39]: g3hd
|
||||
- generic [ref=f1e40]:
|
||||
- generic [ref=f1e41]:
|
||||
- text: "*"
|
||||
- generic [ref=f1e42]: 账号
|
||||
- textbox [ref=f1e50]: g3soft
|
||||
- generic [ref=f1e51]:
|
||||
- generic [ref=f1e52]: 密码
|
||||
- generic [ref=f1e56]:
|
||||
- textbox [ref=f1e61]
|
||||
- button "显示密码" [ref=f1e62] [cursor=pointer]
|
||||
- generic [ref=f1e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=f1e74] [cursor=pointer]
|
||||
- paragraph [ref=f1e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
@@ -0,0 +1,37 @@
|
||||
- generic [ref=e1]:
|
||||
- generic [ref=e3]:
|
||||
- generic [ref=e4]:
|
||||
- img "FMS" [ref=e5]
|
||||
- generic [ref=e6]: FMS 企业管理系统
|
||||
- generic [ref=e7]:
|
||||
- button "中" [ref=e8] [cursor=pointer]
|
||||
- button [ref=e9] [cursor=pointer]
|
||||
- generic [ref=e12]:
|
||||
- paragraph [ref=e22]: 企业管理系统
|
||||
- generic [ref=e24]:
|
||||
- heading "登录" [level=1] [ref=e25]
|
||||
- paragraph [ref=e26]: 欢迎回来,请输入您的账号信息
|
||||
- generic [ref=e27]:
|
||||
- generic [ref=e28]:
|
||||
- generic [ref=e29]:
|
||||
- text: "*"
|
||||
- generic [ref=e30]: 机构码
|
||||
- textbox [active] [ref=e39]
|
||||
- generic [ref=e40]:
|
||||
- generic [ref=e41]:
|
||||
- text: "*"
|
||||
- generic [ref=e42]: 账号
|
||||
- textbox [ref=e50]
|
||||
- generic [ref=e51]:
|
||||
- generic [ref=e52]: 密码
|
||||
- generic [ref=e56]:
|
||||
- textbox [ref=e61]
|
||||
- button "显示密码" [ref=e62] [cursor=pointer]
|
||||
- generic [ref=e66]: 请按住滑块拖动
|
||||
- button "登录" [ref=e74] [cursor=pointer]
|
||||
- paragraph [ref=e75]: © 2026 FMS · 企业管理系统
|
||||
- status:
|
||||
- generic:
|
||||
- generic: FMS
|
||||
- generic: 数字化管理系统
|
||||
- generic: 正在加载,请稍候…
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"cookies": [],
|
||||
"origins": [
|
||||
{
|
||||
"origin": "http://localhost:5082",
|
||||
"localStorage": [
|
||||
{
|
||||
"name": "i18n",
|
||||
"value": "{\"locale\":\"zh-CN\"}"
|
||||
},
|
||||
{
|
||||
"name": "app",
|
||||
"value": "{\"darkMode\":false,\"primaryColor\":\"#0f172b\"}"
|
||||
},
|
||||
{
|
||||
"name": "__probe_marker",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"name": "fms-user",
|
||||
"value": "{\"userInfo\":{\"orgId\":\"g3hd\",\"account\":\"g3soft\"}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -30,12 +30,12 @@
|
||||
}
|
||||
|
||||
/* ===== 开屏加载屏 =====
|
||||
配色对齐 src/theme/tokens.css 浅色主题(默认):背景 #f5f6f8 / 文字 #1f2329 /
|
||||
次级文字 #86909c / 主色 #0f172b / 边框 #e5e6eb;
|
||||
配色对齐 src/theme/tokens.css 浅色主题(默认):背景 #eef1f6 / 文字 #1f2329 /
|
||||
次级文字 #6f7784 / 主色 #0f172b / 边框 #e3e8ee;
|
||||
深色(html.boot-dark,由上方脚本按持久化偏好写入)对齐 .dark 覆盖值。 */
|
||||
.app-boot {
|
||||
display: flex;
|
||||
background-color: #f5f6f8;
|
||||
background-color: #eef1f6;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
@@ -114,7 +114,7 @@
|
||||
.app-boot__sub-title {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
color: #86909c;
|
||||
color: #6f7784;
|
||||
}
|
||||
|
||||
.boot-dark .app-boot__sub-title,
|
||||
@@ -153,7 +153,7 @@
|
||||
.app-boot__hint {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: #86909c;
|
||||
color: #6f7784;
|
||||
}
|
||||
|
||||
/* 内容入场:轻微上浮 + 淡入,逐项错开,避免生硬 */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { RotateCcw } from '@lucide/vue'
|
||||
import { ChevronUp, RotateCcw } from '@lucide/vue'
|
||||
import { Button } from '@/components/ui'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
@@ -21,6 +21,20 @@ const averageFps = ref(0)
|
||||
const minimumFps = ref(null)
|
||||
const history = ref([])
|
||||
|
||||
/* 折叠态:只留一颗迷你胶囊(状态色 + 当前 FPS),点击可展开完整面板。
|
||||
*
|
||||
* 为什么默认折叠、且折叠态整颗胶囊 pointer-events: none:
|
||||
* 浮层 fixed 在页面右上角,只要参与鼠标命中,就会压住页面右上角的操作区
|
||||
* (顶栏用户菜单、列表页查询工具栏的 ghost 按钮等),把这些控件的 hover 命中区
|
||||
* 吞掉;指针在「被遮挡 / 未遮挡」边界来回时 hover 反复开关,表现为背景色闪烁。
|
||||
* 因此折叠态整体不参与命中,展开入口改在窗口捕获阶段用坐标判断:
|
||||
* - 未位移抬指 → 展开完整面板
|
||||
* - 拖 → 移动胶囊位置(与展开态拖动一致) */
|
||||
const collapsed = ref(true)
|
||||
const pillEl = ref(null)
|
||||
let pillDrag = null // 折叠态按下:{ pointerX, pointerY, originX, originY, width, height, moved }
|
||||
const PILL_DRAG_THRESHOLD = 4
|
||||
|
||||
/* 非法入参归一化:采样间隔/帧数/基准值过低时取最小值,避免除零或空图 */
|
||||
const normalizedSampleInterval = computed(() => Math.max(100, props.sampleInterval))
|
||||
const normalizedHistorySize = computed(() => Math.max(1, Math.floor(props.historySize)))
|
||||
@@ -94,10 +108,12 @@ const barStyles = computed(() =>
|
||||
}),
|
||||
)
|
||||
|
||||
/* ---------- 拖拽移动(固定浮层可能遮挡顶栏操作,允许用户拖开) ---------- */
|
||||
/* ---------- 拖拽移动(固定浮层可能遮挡页面操作区,允许用户拖开) ---------- */
|
||||
const rootEl = ref(null)
|
||||
// 拖动后的 left/top 坐标;null 表示未拖过,使用默认定位(右上角)
|
||||
const pos = ref(null)
|
||||
// 拖动中标记:模板用普通变量不具响应性,独立 ref 保证拖动态样式即时生效
|
||||
const isDragging = ref(false)
|
||||
let dragStart = null // { pointerX, pointerY, originX, originY, width, height }
|
||||
|
||||
/* ---------- 双击隐藏(也可由父级通过 ref 调 show/hide 控制显隐) ---------- */
|
||||
@@ -118,8 +134,9 @@ function hide() {
|
||||
defineExpose({ show, hide })
|
||||
|
||||
function onPointerDown(e) {
|
||||
// 重置按钮的点击不触发拖拽
|
||||
if (e.target.closest('.fps-monitor__reset')) return
|
||||
// 面板内按钮(收起 / 重置)标记了 data-fps-no-drag:
|
||||
// 拖拽会在 pointerdown 上 preventDefault,从而吞掉这些按钮的原生 click
|
||||
if (e.target.closest('[data-fps-no-drag]')) return
|
||||
const rect = rootEl.value.getBoundingClientRect()
|
||||
dragStart = {
|
||||
pointerX: e.clientX,
|
||||
@@ -129,6 +146,7 @@ function onPointerDown(e) {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
isDragging.value = true
|
||||
window.addEventListener('pointermove', onPointerMove)
|
||||
window.addEventListener('pointerup', onPointerUp)
|
||||
e.preventDefault()
|
||||
@@ -146,20 +164,78 @@ function onPointerMove(e) {
|
||||
|
||||
function onPointerUp() {
|
||||
dragStart = null
|
||||
isDragging.value = false
|
||||
window.removeEventListener('pointermove', onPointerMove)
|
||||
window.removeEventListener('pointerup', onPointerUp)
|
||||
}
|
||||
|
||||
/* ---------- 折叠态交互:捕获阶段判断是否点在胶囊上 ----------
|
||||
* 胶囊自身 pointer-events: none(不参与页面命中),若不拦截,
|
||||
* 这次交互会「隔空」落到胶囊下方的页面元素上,所以命中胶囊时需要拦下它。 */
|
||||
function onPillPointerDownCapture(e) {
|
||||
if (!collapsed.value || !visible.value || !pillEl.value) return
|
||||
const rect = pillEl.value.getBoundingClientRect()
|
||||
if (
|
||||
e.clientX < rect.left ||
|
||||
e.clientX > rect.right ||
|
||||
e.clientY < rect.top ||
|
||||
e.clientY > rect.bottom
|
||||
) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
pillDrag = {
|
||||
pointerX: e.clientX,
|
||||
pointerY: e.clientY,
|
||||
originX: rect.left,
|
||||
originY: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
moved: false,
|
||||
}
|
||||
window.addEventListener('pointermove', onPillPointerMove)
|
||||
window.addEventListener('pointerup', onPillPointerUp)
|
||||
}
|
||||
|
||||
function onPillPointerMove(e) {
|
||||
if (!pillDrag) return
|
||||
const dx = e.clientX - pillDrag.pointerX
|
||||
const dy = e.clientY - pillDrag.pointerY
|
||||
// 阈值内的轻微抖动不算拖动,避免「点击展开」被误判
|
||||
if (!pillDrag.moved && Math.abs(dx) < PILL_DRAG_THRESHOLD && Math.abs(dy) < PILL_DRAG_THRESHOLD) {
|
||||
return
|
||||
}
|
||||
pillDrag.moved = true
|
||||
isDragging.value = true
|
||||
// 边界限制:不拖出视口
|
||||
const x = Math.max(0, Math.min(pillDrag.originX + dx, window.innerWidth - pillDrag.width))
|
||||
const y = Math.max(0, Math.min(pillDrag.originY + dy, window.innerHeight - pillDrag.height))
|
||||
pos.value = { x, y }
|
||||
}
|
||||
|
||||
function onPillPointerUp() {
|
||||
const wasClick = Boolean(pillDrag && !pillDrag.moved)
|
||||
pillDrag = null
|
||||
isDragging.value = false
|
||||
window.removeEventListener('pointermove', onPillPointerMove)
|
||||
window.removeEventListener('pointerup', onPillPointerUp)
|
||||
// 原地点击 → 展开完整面板
|
||||
if (wasClick) collapsed.value = false
|
||||
}
|
||||
|
||||
/* ---------- 生命周期 ---------- */
|
||||
onMounted(() => {
|
||||
resetSampleWindow()
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
window.addEventListener('pointerdown', onPillPointerDownCapture, true)
|
||||
animationFrameId = requestAnimationFrame(tick)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cancelAnimationFrame(animationFrameId)
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
window.removeEventListener('pointerdown', onPillPointerDownCapture, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -168,41 +244,73 @@ onBeforeUnmount(() => {
|
||||
v-show="visible"
|
||||
ref="rootEl"
|
||||
class="fps-monitor"
|
||||
:class="{ 'is-dragging': dragStart }"
|
||||
:class="{ 'is-dragging': isDragging, 'is-collapsed': collapsed }"
|
||||
:style="pos ? { left: pos.x + 'px', top: pos.y + 'px' } : undefined"
|
||||
aria-label="FPS 性能监控"
|
||||
v-bind="$attrs"
|
||||
@pointerdown="onPointerDown"
|
||||
@dblclick="onDblClick"
|
||||
>
|
||||
<div class="fps-monitor__header">
|
||||
<span class="fps-monitor__label">FPS</span>
|
||||
<strong class="fps-monitor__current" :style="{ color: currentFpsColor }">
|
||||
{{ currentFps || '--' }}
|
||||
</strong>
|
||||
<Button
|
||||
type="ghost"
|
||||
class="fps-monitor__reset"
|
||||
title="重置统计"
|
||||
aria-label="重置 FPS 统计"
|
||||
@click="reset"
|
||||
>
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
<!-- 折叠态:迷你胶囊(状态色 + 当前 FPS)。
|
||||
碰撞由捕获阶段处理:胶囊整体 pointer-events: none,不参与页面命中,
|
||||
因此不会吞掉页面按钮的 hover;点击 / 拖动见 onPillPointerDownCapture。 -->
|
||||
<button
|
||||
v-if="collapsed"
|
||||
ref="pillEl"
|
||||
type="button"
|
||||
class="fps-monitor__pill"
|
||||
title="点击展开 FPS 面板"
|
||||
aria-label="展开 FPS 性能监控面板"
|
||||
>
|
||||
<i
|
||||
class="fps-monitor__pill-dot"
|
||||
:style="{ backgroundColor: currentFpsColor }"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<b class="fps-monitor__pill-value">{{ currentFps || '--' }}</b>
|
||||
</button>
|
||||
|
||||
<div class="fps-monitor__chart" aria-hidden="true">
|
||||
<span v-for="(bar, index) in barStyles" :key="index" class="fps-monitor__bar" :style="bar" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="fps-monitor__header">
|
||||
<span class="fps-monitor__label">FPS</span>
|
||||
<strong class="fps-monitor__current" :style="{ color: currentFpsColor }">
|
||||
{{ currentFps || '--' }}
|
||||
</strong>
|
||||
<Button
|
||||
type="ghost"
|
||||
class="fps-monitor__icon-button"
|
||||
data-fps-no-drag
|
||||
title="收起面板"
|
||||
aria-label="收起 FPS 面板"
|
||||
@click="collapsed = true"
|
||||
>
|
||||
<ChevronUp />
|
||||
</Button>
|
||||
<Button
|
||||
type="ghost"
|
||||
class="fps-monitor__icon-button fps-monitor__reset"
|
||||
data-fps-no-drag
|
||||
title="重置统计"
|
||||
aria-label="重置 FPS 统计"
|
||||
@click="reset"
|
||||
>
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="fps-monitor__summary">
|
||||
<span
|
||||
>avg <b>{{ averageFps || '--' }}</b></span
|
||||
>
|
||||
<span
|
||||
>min <b>{{ minimumFps ?? '--' }}</b></span
|
||||
>
|
||||
</div>
|
||||
<div class="fps-monitor__chart" aria-hidden="true">
|
||||
<span v-for="(bar, index) in barStyles" :key="index" class="fps-monitor__bar" :style="bar" />
|
||||
</div>
|
||||
|
||||
<div class="fps-monitor__summary">
|
||||
<span
|
||||
>avg <b>{{ averageFps || '--' }}</b></span
|
||||
>
|
||||
<span
|
||||
>min <b>{{ minimumFps ?? '--' }}</b></span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -228,7 +336,8 @@ onBeforeUnmount(() => {
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: var(--fms-control-radius);
|
||||
box-shadow: var(--fms-popover-shadow);
|
||||
transition: box-shadow 150ms ease;
|
||||
/* 拖起时阴影加深,走 token 保证 reduced-motion 下同步降级 */
|
||||
transition: box-shadow var(--fms-duration-base) var(--fms-ease-standard);
|
||||
|
||||
&.is-dragging {
|
||||
cursor: grabbing;
|
||||
@@ -236,6 +345,55 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 折叠态:容器收缩到胶囊尺寸(width:auto + 去内边距 + 去卡片外观),
|
||||
并整体退出鼠标命中(pointer-events: none)。
|
||||
容器盒子同样参与命中判定,只缩小内容而留下原尺寸的盒子仍会遮挡页面。 */
|
||||
.fps-monitor.is-collapsed {
|
||||
width: auto;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 折叠态拖动:容器此时无阴影,反馈补到胶囊自身 */
|
||||
.fps-monitor.is-collapsed.is-dragging .fps-monitor__pill {
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
/* 折叠胶囊:状态色圆点 + 当前 FPS,点击展开完整面板 */
|
||||
.fps-monitor__pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
height: 18px;
|
||||
padding: 0 7px;
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
color: var(--fms-text-secondary);
|
||||
cursor: pointer;
|
||||
background: var(--fms-card);
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 9px;
|
||||
box-shadow: var(--fms-shadow);
|
||||
}
|
||||
|
||||
.fps-monitor__pill-dot {
|
||||
flex: none;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.fps-monitor__pill-value {
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--fms-text);
|
||||
}
|
||||
|
||||
.fps-monitor__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -250,7 +408,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
.fps-monitor__current {
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -280,8 +438,8 @@ onBeforeUnmount(() => {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* 重置按钮:组件库 Button 无 size 枚举,在 ghost 变体基础上压缩为 20×20 小方钮 */
|
||||
.fps-monitor .fps-monitor__reset {
|
||||
/* 面板内图标按钮(收起 / 重置):组件库 Button 无 size 枚举,压缩为 20×20 小方钮 */
|
||||
.fps-monitor .fps-monitor__icon-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<script setup>
|
||||
/**
|
||||
* FmsField —— 多态控件渲染器(不含 label)。
|
||||
*
|
||||
* 职责单一:给一个 type + 一组属性,渲染出对应的、已接好 v-model 的控件。
|
||||
* label / 必填 / 校验 / 栅格 交给 FormItem,本组件只做"类型 → 控件"的多态映射,
|
||||
* 把散落在各处的类型默认值(日期格式、数字输入、checkbox 0/1 归一等)收敛到一处。
|
||||
*
|
||||
* 留出的扩展口子:
|
||||
* - 默认插槽:整体接管控件渲染(忽略 type),最大逃生舱;
|
||||
* - control-props:一个对象直接喂给内部控件(如 select 的 options、selectId 的 view-name);
|
||||
* - types:自定义 { [type]: Component } 注册表,新增类型无需改本组件模板;
|
||||
* - 透传:inheritAttrs=false,class/style/原生事件(如 @keydown)等 fallthrough 属性
|
||||
* 一律转发到内部控件;
|
||||
* - v-model:默认转发;selectId 额外支持 v-model:label(id + 显示名 双绑)。
|
||||
*
|
||||
* 内置 type:input | textarea | number | money | date | datetime | checkbox | select | selectId
|
||||
* 未识别的 type 兜底为 Input。
|
||||
*/
|
||||
import { computed, useAttrs } from 'vue'
|
||||
import { Checkbox, DatePicker, Input, Select } from '@/components/ui'
|
||||
import FmsSelect from '@/components/fms-select/FmsSelect.vue'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = defineProps({
|
||||
/** 控件类型;见文件头内置类型清单,可用 types 扩展 */
|
||||
type: { type: String, default: 'input' },
|
||||
/** 双向绑定的值,转发给内部控件 */
|
||||
modelValue: { type: [String, Number, Boolean, Array, Object], default: undefined },
|
||||
/** 显示名(仅 selectId 用):v-model:label,与 modelValue(id) 成对 */
|
||||
label: { type: String, default: '' },
|
||||
/** 禁用态(所有控件通用) */
|
||||
disabled: { type: Boolean, default: false },
|
||||
/** 只读态(仅 Input 类控件生效) */
|
||||
readonly: { type: Boolean, default: false },
|
||||
/** 校验状态 '' | error | warning(仅 Input 类控件生效) */
|
||||
status: { type: String, default: '' },
|
||||
/** 直接喂给内部控件的额外属性(优先级高于 fallthrough 属性) */
|
||||
controlProps: { type: Object, default: () => ({}) },
|
||||
/** 自定义类型注册表 { [type]: Component },命中时优先于内置类型 */
|
||||
types: { type: Object, default: () => ({}) },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'update:label', 'change'])
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
// 非 Input 的内置类型 → 组件映射;其余(input/textarea/number/money/未知)统一落到 Input
|
||||
const BUILTIN_COMPONENTS = {
|
||||
select: Select,
|
||||
selectId: FmsSelect,
|
||||
checkbox: Checkbox,
|
||||
date: DatePicker,
|
||||
datetime: DatePicker,
|
||||
}
|
||||
|
||||
const resolvedComponent = computed(
|
||||
() => props.types[props.type] || BUILTIN_COMPONENTS[props.type] || Input,
|
||||
)
|
||||
|
||||
const isCheckbox = computed(() => props.type === 'checkbox')
|
||||
const isInputLike = computed(() => resolvedComponent.value === Input)
|
||||
const isLookup = computed(() => resolvedComponent.value === FmsSelect)
|
||||
|
||||
// 各类型的默认控件属性(可被 controlProps 覆盖)
|
||||
const typeDefaults = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'textarea':
|
||||
return { type: 'textarea' }
|
||||
case 'number':
|
||||
case 'money':
|
||||
return { type: 'number' }
|
||||
case 'date':
|
||||
return { valueFormat: 'YYYY-MM-DD' }
|
||||
case 'datetime':
|
||||
return { format: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
})
|
||||
|
||||
// 汇总喂给内部控件的全部 props/事件:类型默认 < fallthrough 属性 < controlProps,
|
||||
// 再叠加值绑定、双绑、通用状态与 change 转发。
|
||||
const bindProps = computed(() => {
|
||||
const merged = { ...typeDefaults.value, ...attrs, ...props.controlProps }
|
||||
|
||||
// 值绑定:checkbox 在 0/1(存储)与 boolean(控件)之间转换,其余类型直通
|
||||
merged.modelValue = isCheckbox.value ? Number(props.modelValue) === 1 : props.modelValue
|
||||
merged['onUpdate:modelValue'] = (value) =>
|
||||
emit('update:modelValue', isCheckbox.value ? (value ? 1 : 0) : value)
|
||||
|
||||
// selectId:额外的显示名双绑(v-model:label)
|
||||
if (isLookup.value) {
|
||||
merged.label = props.label
|
||||
merged['onUpdate:label'] = (value) => emit('update:label', value)
|
||||
}
|
||||
|
||||
// 通用状态:disabled 所有控件都吃;readonly / status 仅 Input 支持,避免污染其它控件
|
||||
merged.disabled = props.disabled
|
||||
if (isInputLike.value) {
|
||||
if (props.readonly) merged.readonly = true
|
||||
if (props.status) merged.status = props.status
|
||||
}
|
||||
|
||||
// 内部控件的 change 原样转发(各控件签名不同,不做强行归一)
|
||||
merged.onChange = (...args) => emit('change', ...args)
|
||||
|
||||
return merged
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 默认插槽:整体接管控件渲染;未提供时按 type 渲染内置/注册控件 -->
|
||||
<slot>
|
||||
<component :is="resolvedComponent" v-bind="bindProps" />
|
||||
</slot>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
// 模块数据驱动列表/编辑的「字段展示」工具:解析下拉选项、格式化日期等。
|
||||
// 供 fms-module-list / fms-module-edit 等按模块元数据渲染的组件共用;
|
||||
// 查询条件构建(buildQueryCondition 等)见 queryUtils.js。
|
||||
// 查询条件构建(buildConditionSql / astToSql 等)见 queryUtils.js。
|
||||
import { isEmptyValue } from './queryUtils'
|
||||
|
||||
const padDatePart = (number) => String(number).padStart(2, '0')
|
||||
|
||||
Loaded 100 of 179 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user