Files
workspace/code/fms/ai-plans/QUERY_REFACTOR_PLAN.md
2026-09-23 16:54:54 +08:00

23 KiB
Raw Permalink Blame History

查询体验重构实施计划

1. 重构目标

本次重构的目标不是增加查询表达力,而是缩短业务人员从“知道要找什么”到“看到结果”的路径。

重构后,列表页应满足:

  • 打开页面即可看到常用工作队列,不需要先打开高级查询;
  • 默认只暴露高频查询条件和必要字段,减少视觉噪声;
  • 状态页签、快捷筛选、精确筛选形成渐进式查询路径;
  • 业务人员使用业务语言,不需要理解 AND、OR、括号或查询表达式;
  • 重复查询可以保存、设为默认并再次使用;
  • 查询条件、分页、排序和当前视图可通过 URL 分享和恢复;
  • 查询权限、字段权限和数据范围始终由后端 SQL 强制执行;
  • 不保留旧查询接口、旧查询配置或旧数据结构的兼容转发层。

2. 核心设计原则

2.1 三层查询模型

工作队列页签     我现在要处理哪一类?
      ↓
快捷筛选         我大概知道是哪几条?
      ↓
更多筛选         我需要精确查找哪一条?

对应到页面:

  1. 工作队列页签:预置常用状态或业务视图,进入页面即可使用。
  2. 快捷筛选:展示 3~5 个最高频的结构化条件。
  3. 更多筛选:按字段类型提供完整但易懂的筛选控件,默认收起。

暂不实现条件树、任意层级括号和查询语言。只有在后续确认存在稳定的分析师角色及真实使用需求后,才单独评估 L3/L4 能力。

2.2 页签和筛选的职责分离

  • 页签代表工作队列或预设查询视图,不等同于数据库状态字段;
  • 快捷筛选用于进一步缩小当前页签结果;
  • 页签条件与用户筛选条件之间固定使用 AND;
  • 有异常、我的订单、即将到期 等跨字段场景也可以是页签;
  • 不把所有生命周期状态平铺到页签,默认显示 4~6 个,其余进入“更多”。

2.3 业务语言优先

控件和文案不暴露技术表达式:

  • 多选枚举:显示“满足任一”或“满足全部”;
  • 日期:提供“今天、 本周、 本月、近三个月、自定义”;
  • 数值:提供“大于、小于、介于”;
  • 文本:默认“包含”,可选“等于、开头是”;
  • 布尔值:提供“是、否、不限”,不强迫用户选择一个值。

3. 目标页面结构

模块标题                                           新增  导出  更多

全部 128 | 待处理 12 | 有异常 5 | 已订舱 31 | 已完成 72 | 更多

[ 搜索订单号、客户、提单号、船名、航次                         ]

[ 客户 ] [ ETD ] [ 业务员 ] [ 目的港 ] [ 更多筛选 ]       清除全部

客户:东方国际 ×    ETD:本月 ×

订单号 | 客户 | 航线/目的港 | ETD | 负责人 | 状态 | 异常 | 操作

3.1 页面区域职责

区域 职责 默认行为
页面头部 模块标题、主操作、导出和更多操作 保持简洁,不能被筛选器挤压
工作队列页签 切换预设查询视图 默认打开模块配置的默认视图
关键词搜索 跨关键字段模糊搜索 回车或防抖后查询,保留输入值
快捷筛选 高频字段筛选 最多展示 3~5 个,可由模块配置
更多筛选 完整字段筛选 抽屉或弹层打开,关闭后保留已选条件
条件回显区 展示当前生效条件 每个条件可单独删除
数据表格 展示精简后的默认列 默认 6~8 列,支持列设置

4. 工作队列页签设计

工作队列页签、关键词搜索和快捷筛选均必须通过模块查询配置生成,不能在页面组件中写死业务字段、状态值或显示顺序。前端只实现通用渲染器和交互,不针对海运订单、报关单等模块增加专用分支。

配置的职责边界如下:

能力 配置决定的内容 前端通用能力
工作队列页签 编码、文案、顺序、默认项、数量开关、预设条件、权限范围 渲染页签、切换视图、展示数量
关键词搜索 占位文案、可搜索字段、匹配方式、触发方式 输入、清除、防抖/回车触发
快捷筛选 字段、标签、顺序、控件类型、选项来源、默认值 根据字段元数据渲染控件、编辑值
更多筛选 字段分组、可用操作符、字段顺序、显示条件 抽屉布局、字段控件和条件回显

模块配置变更后,页面无需重新发布前端代码即可改变查询入口。前端只允许从服务端返回的字段元数据生成查询请求,不接受页面代码中额外注入的可查询字段。

4.1 页签配置模型

页签使用统一的预设视图模型,不限定只能绑定一个状态字段。

{
  "moduleCode": "sea_order",
  "code": "pending",
  "label": "待处理",
  "type": "preset",
  "order": 10,
  "showCount": true,
  "isDefault": true,
  "query": {
    "conditions": [
      {
        "field": "status",
        "operator": "in",
        "value": ["pending_audit", "pending_booking"]
      }
    ]
  }
}

一个模块的完整查询入口配置示例:

{
  "moduleCode": "sea_order",
  "defaultViewCode": "pending",
  "views": [
    {
      "code": "all",
      "label": "全部",
      "type": "all",
      "order": 1,
      "showCount": true
    },
    {
      "code": "pending",
      "label": "待处理",
      "type": "preset",
      "order": 2,
      "showCount": true,
      "isDefault": true,
      "query": {
        "conditions": [
          {
            "field": "status",
            "operator": "in",
            "value": ["pending_audit", "pending_booking"]
          }
        ]
      }
    },
    {
      "code": "exception",
      "label": "有异常",
      "type": "preset",
      "order": 3,
      "showCount": true,
      "query": {
        "conditions": [
          {
            "field": "has_exception",
            "operator": "=",
            "value": true
          }
        ]
      }
    }
  ],
  "keywordSearch": {
    "placeholder": "搜索订单号、客户、提单号、船名、航次",
    "trigger": "enter_or_debounce",
    "fields": [
      { "field": "order_no", "operator": "contains" },
      { "field": "customer_name", "operator": "contains" },
      { "field": "bill_of_lading_no", "operator": "contains" },
      { "field": "vessel_name", "operator": "contains" },
      { "field": "voyage_no", "operator": "contains" }
    ]
  },
  "quickFilters": [
    {
      "field": "customer_id",
      "label": "客户",
      "control": "select",
      "multiple": true,
      "optionSource": "customer",
      "matchMode": "any",
      "order": 1
    },
    {
      "field": "etd",
      "label": "ETD",
      "control": "date_range",
      "presets": ["today", "this_week", "this_month", "next_month"],
      "order": 2
    },
    {
      "field": "owner_id",
      "label": "业务员",
      "control": "select",
      "multiple": true,
      "optionSource": "current_user_scope",
      "matchMode": "any",
      "order": 3
    }
  ],
  "filterGroups": [
    {
      "code": "basic",
      "label": "基础信息",
      "order": 1,
      "fields": ["customer_id", "owner_id", "status", "carrier_id"]
    },
    {
      "code": "schedule",
      "label": "船期与港口",
      "order": 2,
      "fields": ["etd", "eta", "pol", "pod"]
    }
  ]
}

配置中的 field 必须引用模块字段元数据中的合法字段,control、operator 和 optionSource 必须由后端字段定义校验。前端不得仅因为配置返回了一个字段就绕过字段权限。

支持的页签类型:

  • all:全部数据;
  • preset:模块配置的固定预设查询;
  • personal:用户保存的查询方案;
  • shared:角色或团队共享的查询方案。

4.2 页签规则

  • 默认展示 4~6 个页签;
  • 超出数量的视图进入“更多”;
  • 页签顺序、显示/隐藏、默认页签由模块配置和用户偏好共同决定;
  • 用户偏好只能调整布局和默认视图,不能扩大权限范围;
  • 页签统计数量使用与列表相同的权限、数据范围和过滤规则;
  • 统计请求失败不阻塞列表,显示无数量状态并记录错误日志;
  • 切换页签时保留用户已有的快捷筛选,若发生明显冲突则移除冲突条件并提示;
  • 页签条件和快捷/更多筛选条件始终按 AND 组合。

4.3 数量统计策略

优先为待处理、有异常等具有行动价值的页签显示数量。数量统计接口应支持批量返回当前模块所有页签的数量,避免一个页签发起一次请求。

建议接口返回:

{
  "counts": {
    "all": 128,
    "pending": 12,
    "exception": 5,
    "completed": 72
  },
  "generatedAt": "2026-09-23T08:30:00+08:00"
}

数量不是强一致业务数据。允许短时间缓存,但必须在用户切换页签或刷新列表时提供可预测的刷新行为。

5. 查询条件设计

5.1 关键词搜索

每个模块配置一组关键词字段,关键词默认对这些字段执行跨字段模糊匹配。关键词框本身是通用组件,模块只提供占位文案、字段清单和触发策略。

示例:

{
  "keyword": {
    "placeholder": "搜索订单号、客户、提单号、船名、航次",
    "searchableFields": [
      "orderNo",
      "customerName",
      "billOfLadingNo",
      "vesselName",
      "voyageNo"
    ]
  }
}

关键词搜索不要求用户先选择字段。字段可见性和可查询权限仍由后端校验。

5.2 快捷筛选

每个模块配置 3~5 个快捷条件。选择依据:使用频率、业务价值、结果收敛效果和字段稳定性。页面不根据模块名称或路由写 if/else 决定快捷条件。

推荐字段:

  • 客户;
  • 负责人/业务员;
  • 日期范围;
  • 目的港或业务区域;
  • 运输方式、船公司等高频枚举。

快捷筛选控件由字段类型决定,不允许所有字段统一使用普通文本框。

5.3 更多筛选

“更多筛选”以抽屉形式打开,按业务分组展示字段。默认只加载当前模块允许查询的字段,按需展开低频分组。

字段控件规则:

字段类型 控件 默认语义
枚举 多选下拉/标签选择 满足任一
布尔 三态选择 不限
日期 预置区间 + 自定义范围 区间包含起止日期
数值 操作符 + 数值框 大于、小于、介于
文本 匹配方式 + 输入框 包含
多值字段 任一/全部切换 + 多选 满足任一

筛选抽屉底部固定提供:

[重置]                                      [取消] [应用筛选]

点击“取消”不改变当前已应用查询,点击“应用筛选”后统一刷新列表并回到第一页。

6. 表格和视图设计

6.1 默认列

每个模块必须定义一套精简的默认列,原则上控制在 6~8 列。默认列优先展示:

  • 用户识别记录所需的主标识;
  • 判断是否需要处理所需的状态;
  • 判断时效所需的日期;
  • 处理责任人;
  • 异常或风险提示;
  • 最常用的业务维度。

不在默认列表展示低频、技术性或详情级字段。低频字段通过详情页、列设置和导出字段获取。

6.2 列设置和预设视图

提供以下视图能力:

  • 日常处理:面向操作员的精简列;
  • 运营跟踪:包含船期、港口、节点等字段;
  • 财务核对:包含金额、费用、开票等字段;
  • 自定义:用户调整后的列和顺序。

列可见性、列顺序和列宽属于用户布局偏好,不得影响后端权限判断。

7. 保存查询方案

保存查询方案是本次重构的核心留存能力之一,优先级高于复杂逻辑组合。

支持:

  • 保存当前页签、关键词、筛选条件、排序和列视图;
  • 自定义名称;
  • 设为默认;
  • 最近使用;
  • 删除和重新命名;
  • 按用户、角色或团队共享;
  • 将高频方案固定为页签或快捷入口。

查询方案保存的内容应是结构化查询状态,不保存最终 SQL。

{
  "name": "本月未订舱",
  "moduleCode": "sea_order",
  "viewCode": "pending",
  "keyword": "",
  "filters": {
    "etd": { "preset": "this_month" },
    "bookingStatus": ["unbooked"]
  },
  "sort": { "field": "etd", "direction": "asc" },
  "columnPreset": "daily"
}

8. URL 状态和查询行为

实施状态(2026-09):URL 状态同步与查询分享本期不做(下期评估)。 原因:布局层的页签 key 与 keep-alive 缓存 key 都是 route.fullPath,直接把查询状态写进 URL 会派生出新页签、破坏页面缓存;做 URL 同步需要先给列表路由做 key 归一化,属独立改动。 本节以下内容保留为下期目标。

以下状态必须可序列化到 URL:

  • 当前页签/查询方案;
  • 关键词;
  • 结构化筛选条件;
  • 排序;
  • 当前页码和每页数量;
  • 列视图(如需要)。

要求:

  • 刷新页面后可恢复查询;
  • 复制 URL 给有权限的同事后可打开相同查询;
  • 无权限字段或无效条件由后端拒绝并由前端清晰提示;
  • URL 不包含最终 SQL,不把权限判断交给前端;
  • 条件变化后重置到第一页;修改单个条件不清空其他条件。

9. 后端和接口设计

9.1 统一查询请求

前端提交结构化查询状态,后端负责将配置、页签条件、用户条件、权限和数据范围组合成 SQL。

{
  "moduleCode": "sea_order",
  "viewCode": "pending",
  "keyword": "东方",
  "filters": {
    "customerId": [1001],
    "etd": {
      "from": "2026-09-01",
      "to": "2026-09-30"
    }
  },
  "sort": {
    "field": "etd",
    "direction": "asc"
  },
  "page": 1,
  "pageSize": 20
}

后端处理顺序:

  1. 加载模块、字段、页签和视图配置;
  2. 校验字段可查询权限和操作符;
  3. 合并页签预设条件和用户筛选条件;
  4. 注入用户数据范围和权限条件;
  5. 生成参数化 SQL 或执行模块配置中的合法 SELECT;
  6. 返回分页数据、总数和必要的展示元数据。

9.2 查询配置接口

页面首次加载时获取模块查询配置,配置接口应一次返回当前用户可用的视图、关键词搜索、快捷筛选、更多筛选分组、字段元数据和默认列。

{
  "moduleCode": "sea_order",
  "version": 3,
  "defaultViewCode": "pending",
  "views": [],
  "keywordSearch": {},
  "quickFilters": [],
  "filterGroups": [],
  "fields": [],
  "columnPresets": []
}

配置接口必须完成以下处理:

  • 过滤当前用户无权查看的页签、字段和选项来源;
  • 过滤已停用或不存在的字段,记录配置错误;
  • 校验页签预设条件和快捷筛选字段的合法性;
  • 返回可直接渲染的字段类型、标签、操作符和选项元数据;
  • 返回配置版本,便于前端缓存失效和问题定位。

9.3 建议接口

接口名称可按现有 API 风格落地,职责保持以下边界:

GET/POST  查询模块配置
POST      查询列表
POST      查询数量统计
GET       查询方案列表
POST      保存查询方案
PUT       修改查询方案
DELETE    删除查询方案

列表查询和数量统计必须复用相同的过滤、权限和数据范围构建逻辑,避免页签数量与列表结果不一致。

9.4 数据库配置模型

新模型至少需要表达:

  • 模块可查询字段及字段类型;
  • 字段权限和可选操作符;
  • 关键词搜索字段;
  • 快捷筛选字段;
  • 更多筛选字段分组;
  • 默认表格列和列预设;
  • 工作队列页签;
  • 默认视图;
  • 查询方案的所属用户、角色或团队。

具体表名应复用现有模块、字段、权限和用户配置体系。若现有结构无法表达上述关系,直接调整为新结构,不增加旧结构兼容读取逻辑。

10. 前端实现拆分

建议按页面职责拆分,具体目录以现有列表模块目录为准:

list-page/
├── index.vue                 页面编排、查询状态和数据加载
├── WorkQueueTabs.vue         工作队列页签和数量
├── KeywordSearch.vue         关键词搜索
├── QuickFilters.vue          快捷筛选
├── FilterDrawer.vue          更多筛选
├── ActiveFilterPills.vue     已应用条件回显
├── ResultTable.vue            表格和列设置入口
├── SavedQueryMenu.vue         保存查询方案和最近使用
└── query-utils.js             查询状态序列化、规范化和纯转换函数

职责要求:

  • index.vue 持有唯一查询状态源,负责加载配置、拼接请求、分页和刷新;
  • index.vue 不包含模块编码判断、业务状态常量、固定快捷字段或固定关键词字段;
  • 各筛选组件只负责展示和编辑条件,通过 v-model 或语义明确的事件通信;
  • WorkQueueTabs.vue、KeywordSearch.vue 和 QuickFilters.vue 均只消费配置,不内置模块专用选项;
  • 不在多个组件复制查询条件;
  • 不在子组件中自行请求同一列表或配置数据;
  • 查询状态规范化、URL 编解码和条件标签生成等纯逻辑集中维护并可单测;
  • 组件不直接拼接 SQL,也不承担权限判断。

11. 实施阶段

阶段一:查询模型和接口

  • 确认新查询请求、响应和配置结构;
  • 建立字段类型、操作符和筛选值的统一定义;
  • 建立页签/预设视图模型;
  • 建立模块查询配置接口,统一返回工作队列、关键词搜索、快捷筛选和字段元数据;
  • 为首批模块录入查询配置,不在前端页面写入模块专用查询定义;
  • 建立统一列表查询和数量统计服务;
  • 将权限、字段权限和数据范围接入查询构建流程;
  • 直接切换到新接口,不保留旧查询兼容入口。

阶段二:列表页基础体验

  • 实现工作队列页签;
  • 实现批量数量统计;
  • 实现关键词搜索;
  • 实现快捷筛选;
  • 实现条件回显、单条件删除和全部清除;
  • 实现查询变更后的分页重置;
  • 精简默认表格列。

阶段三:更多筛选

  • 实现字段类型到控件的映射;
  • 实现日期预设区间和自定义范围;
  • 实现数值比较和文本匹配方式;
  • 实现多值字段“任一/全部”;
  • 实现筛选抽屉的应用、取消和重置行为;
  • 实现空结果提示和条件放宽入口。

阶段四:保存和个性化

  • 实现保存查询方案;
  • 实现默认视图、最近使用和方案删除;
  • 实现列预设和用户列布局;
  • 实现方案共享;
  • 实现 URL 状态同步和查询分享。

阶段五:按模块推广

  • 选择海运订单、报关单、费用单等状态清晰且使用频繁的模块作为首批模块;
  • 为每个模块定义 4~6 个工作队列页签;
  • 根据实际查询日志调整快捷字段和默认列;
  • 再推广到客户档案等没有明显生命周期的模块,此类模块可以只启用关键词、快捷筛选和更多筛选,不强制配置页签。

12. 测试计划

12.1 后端测试

  • 页签条件与用户筛选条件按 AND 合并;
  • 多选字段“任一/全部”生成正确 SQL;
  • 日期边界、时区和包含起止日期行为正确;
  • 数值比较符行为正确;
  • 关键词跨字段搜索行为正确;
  • 字段权限、模块权限和数据范围始终生效;
  • 列表数量与页签统计使用相同条件;
  • 非法字段、非法操作符和越权条件被拒绝;
  • 分页、排序和筛选参数不会产生非查询 SQL;
  • 查询方案只能被授权用户读取或修改。

12.2 前端测试

  • 默认页签和默认条件正确加载;
  • 切换页签保留或清理筛选条件的规则正确;
  • 条件标签可单独删除;
  • 更多筛选的取消不影响已应用条件;
  • 应用筛选后回到第一页;
  • 刷新或打开分享 URL 可恢复查询;
  • 空结果状态能够定位和清除条件;
  • 数量加载失败不阻塞列表;
  • 表格列视图切换不改变查询结果;
  • 保存、修改、删除和设为默认查询方案正常。

12.3 验收场景

至少用真实业务流程验证:

  1. 业务员进入海运订单,直接点击“待处理”处理自己的工作队列;
  2. 在“待处理”下筛选某客户和本月 ETD;
  3. 通过“更多筛选”查找同时包含多个服务项的订单;
  4. 将“本月未订舱”保存为个人默认查询;
  5. 复制查询 URL 给另一位有权限的同事;
  6. 无权限用户打开同一 URL 时看不到越权数据;
  7. 查询无结果时能明确看到条件并一键放宽或清除。

13. 观测指标

上线后按模块记录以下指标,用于持续收敛默认配置:

  • 从进入列表到首次有效查询的耗时;
  • 页签点击占比及各页签使用频率;
  • 快捷筛选使用频率;
  • 更多筛选打开率和应用成功率;
  • 空结果率;
  • 查询后再次修改条件的次数;
  • 保存查询方案数量和复用次数;
  • 默认列设置被修改的比例;
  • 列表查询接口耗时、数量统计接口耗时和错误率。

判断标准不是“可配置项是否足够多”,而是业务是否更少打开复杂筛选、更少遇到空结果、更快找到需要处理的数据。

14. 明确不做的事项

  • 不在第一版实现任意层级的 AND/OR 条件树;
  • 不实现查询表达式语言;
  • 不把所有字段默认展示为筛选项;
  • 不把所有生命周期状态都平铺为页签;
  • 不把最终 SQL 保存到用户查询方案;
  • 不在前端执行权限过滤代替后端 SQL;
  • 不保留旧查询接口、旧查询配置、旧字段映射或旧数据兼容逻辑;
  • 不为每个业务模块复制一套专用查询接口;
  • 本期不做:URL 状态同步与查询分享(见 §8 实施状态);保存查询方案与列预设视图亦下期再做。

15. 完成标准

  • 新查询模型和新接口在目标模块生效,旧查询入口已移除;
  • 工作队列页签、关键词搜索和快捷筛选均由模块配置生成,前端没有模块专用分支;
  • 新增或调整模块查询入口只需要修改模块配置和字段元数据,不需要修改前端组件代码;
  • 列表页默认只展示必要的工作队列、筛选条件和表格列;
  • 业务人员可以不打开“更多筛选”完成主要日常工作;
  • 复杂查询通过字段类型匹配的业务控件完成,不需要理解技术表达式;
  • 查询条件可回显、单独修改、清除和通过 URL 恢复;
  • 常用查询可以保存、设为默认并再次使用;
  • 页签数量与列表结果遵守相同的权限和数据范围;
  • 关键前后端测试通过,真实业务验收场景可完成;
  • 查询日志和性能指标已接入,能够根据使用数据继续调整页签、快捷字段和默认列。