23 KiB
查询体验重构实施计划
1. 重构目标
本次重构的目标不是增加查询表达力,而是缩短业务人员从“知道要找什么”到“看到结果”的路径。
重构后,列表页应满足:
- 打开页面即可看到常用工作队列,不需要先打开高级查询;
- 默认只暴露高频查询条件和必要字段,减少视觉噪声;
- 状态页签、快捷筛选、精确筛选形成渐进式查询路径;
- 业务人员使用业务语言,不需要理解
AND、OR、括号或查询表达式; - 重复查询可以保存、设为默认并再次使用;
- 查询条件、分页、排序和当前视图可通过 URL 分享和恢复;
- 查询权限、字段权限和数据范围始终由后端 SQL 强制执行;
- 不保留旧查询接口、旧查询配置或旧数据结构的兼容转发层。
2. 核心设计原则
2.1 三层查询模型
工作队列页签 我现在要处理哪一类?
↓
快捷筛选 我大概知道是哪几条?
↓
更多筛选 我需要精确查找哪一条?
对应到页面:
- 工作队列页签:预置常用状态或业务视图,进入页面即可使用。
- 快捷筛选:展示 3~5 个最高频的结构化条件。
- 更多筛选:按字段类型提供完整但易懂的筛选控件,默认收起。
暂不实现条件树、任意层级括号和查询语言。只有在后续确认存在稳定的分析师角色及真实使用需求后,才单独评估 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
}
后端处理顺序:
- 加载模块、字段、页签和视图配置;
- 校验字段可查询权限和操作符;
- 合并页签预设条件和用户筛选条件;
- 注入用户数据范围和权限条件;
- 生成参数化 SQL 或执行模块配置中的合法
SELECT; - 返回分页数据、总数和必要的展示元数据。
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 验收场景
至少用真实业务流程验证:
- 业务员进入海运订单,直接点击“待处理”处理自己的工作队列;
- 在“待处理”下筛选某客户和本月 ETD;
- 通过“更多筛选”查找同时包含多个服务项的订单;
- 将“本月未订舱”保存为个人默认查询;
- 复制查询 URL 给另一位有权限的同事;
- 无权限用户打开同一 URL 时看不到越权数据;
- 查询无结果时能明确看到条件并一键放宽或清除。
13. 观测指标
上线后按模块记录以下指标,用于持续收敛默认配置:
- 从进入列表到首次有效查询的耗时;
- 页签点击占比及各页签使用频率;
- 快捷筛选使用频率;
- 更多筛选打开率和应用成功率;
- 空结果率;
- 查询后再次修改条件的次数;
- 保存查询方案数量和复用次数;
- 默认列设置被修改的比例;
- 列表查询接口耗时、数量统计接口耗时和错误率。
判断标准不是“可配置项是否足够多”,而是业务是否更少打开复杂筛选、更少遇到空结果、更快找到需要处理的数据。
14. 明确不做的事项
- 不在第一版实现任意层级的 AND/OR 条件树;
- 不实现查询表达式语言;
- 不把所有字段默认展示为筛选项;
- 不把所有生命周期状态都平铺为页签;
- 不把最终 SQL 保存到用户查询方案;
- 不在前端执行权限过滤代替后端 SQL;
- 不保留旧查询接口、旧查询配置、旧字段映射或旧数据兼容逻辑;
- 不为每个业务模块复制一套专用查询接口;
- 本期不做:URL 状态同步与查询分享(见 §8 实施状态);保存查询方案与列预设视图亦下期再做。
15. 完成标准
- 新查询模型和新接口在目标模块生效,旧查询入口已移除;
- 工作队列页签、关键词搜索和快捷筛选均由模块配置生成,前端没有模块专用分支;
- 新增或调整模块查询入口只需要修改模块配置和字段元数据,不需要修改前端组件代码;
- 列表页默认只展示必要的工作队列、筛选条件和表格列;
- 业务人员可以不打开“更多筛选”完成主要日常工作;
- 复杂查询通过字段类型匹配的业务控件完成,不需要理解技术表达式;
- 查询条件可回显、单独修改、清除和通过 URL 恢复;
- 常用查询可以保存、设为默认并再次使用;
- 页签数量与列表结果遵守相同的权限和数据范围;
- 关键前后端测试通过,真实业务验收场景可完成;
- 查询日志和性能指标已接入,能够根据使用数据继续调整页签、快捷字段和默认列。