0%

EspoCRM定制第6篇Kanban增强——从阶段计数到可控重定制

适用版本:EspoCRM 9.2.2+

别小看”显示个数字”,做错了会变成:不准、卡、泄露权限范围。

TL;DR

  • 计数必须来自后端聚合,而不是前端猜测
  • API 必须走 ACL/过滤条件,计数和可见数据保持一致
  • 能扩展默认看板就别重写;重写要把代价锁进模块边界
  • 列独立滚动/分页要做到”列内状态独立”
  • 多币种宁可分组展示,也不要混算误导用户

1. 轻量增强 —— 阶段计数

1.1 场景:看板要”阶段一眼看懂”

看板的核心诉求很朴素:

  • 每个阶段有多少条记录?
  • 拖拽卡片后,数字要跟着变

这看似小功能,但最容易被写成”堆前端临时逻辑”:页面上数 DOM、或者从列表数据里猜数量——最后一定不准。

1.2 目标:准确、实时、权限正确

  • 准确:基于数据库聚合
  • 实时:拖拽后刷新计数
  • 权限正确:用户只能看到自己有权限看到的记录计数

1.3 数据流:前端视图扩展 + 后端统计 API

sequenceDiagram
  participant UI as Kanban View
  participant API as Entity API
  participant DB as Database

  UI->>API: GET /api/v1//kanbanStageCount
  API->>DB: SELECT stage, COUNT(*) ... WHERE ACL条件
  DB-->>API: aggregated counts
  API-->>UI: {stage: count}
  UI->>UI: render badge near column title

  UI->>UI: onDragDrop()
  UI->>API: refresh counts
  API-->>UI: updated counts

1.4 后端:为什么一定要聚合查询

你想要的是”真实数据”的计数,而不是”当前前端加载到内存里的一页数据”。

聚合查询的本质是:

  • 只返回计数,不返回详情,响应体小
  • 可加索引字段(如 stage)提升性能
  • 可复用 ACL/过滤条件,保证权限一致

SQL 思路(示意):

1
2
3
4
5
SELECT stage, COUNT(id) AS cnt
FROM <entity_table>
WHERE deleted = 0
AND -- ACL 条件自动应用
GROUP BY stage;

PHP 代码骨架(公开版、严格 ACL):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Select\SearchParams;
use Espo\Core\Select\SelectBuilderFactory;
use Espo\Core\Utils\Log;
use Espo\Core\Api\Request;
use Espo\ORM\EntityManager;

class Entity
{
public function __construct(
private SelectBuilderFactory $selectBuilderFactory,
private EntityManager $entityManager,
private Log $log
) {}

/**
* @return array<string, int>
* @throws BadRequest
* @throws Forbidden
*/
public function getKanbanStageCountAction(Request $request): array
{
$searchParams = SearchParams::fromRaw($request->getQueryParams());

$queryBuilder = $this->selectBuilderFactory
->create()
->from('<ENTITY_TYPE>')
->withStrictAccessControl()
->withSearchParams($searchParams)
->buildQueryBuilder()
->select('stage')
->select('COUNT:(id)', 'cnt')
->group('stage');

$query = $queryBuilder->build();

$this->log->debug('Kanban stage count query built', [
'entityType' => '<ENTITY_TYPE>',
]);

$sth = $this->entityManager->getQueryExecutor()->execute($query);

$counts = [];

foreach ($sth->fetchAll(\PDO::FETCH_ASSOC) as $row) {
if (!isset($row['stage'])) {
continue;
}

$counts[$row['stage']] = (int) ($row['cnt'] ?? 0);
}

return $counts;
}
}

1.5 前端:只做展示与刷新,不做”算数”

前端的职责应该非常克制:

  • 请求计数
  • 在阶段标题旁渲染徽章(badge)
  • 在拖拽成功后触发刷新
  • 当 API 失败时做回退(例如隐藏徽章或显示 --

失败回退(统一英文提示):

1
2
3
4
5
6
7
try {
const counts = await this.fetchKanbanCounts();
this.renderBadges(counts);
} catch (error) {
this.$log.error('Failed to load kanban counts');
this.hideBadges(); // 或显示 '--'
}

1.6 性能与一致性

常见错误:每个阶段单独请求一次(N 个阶段 = N 次 API)

更稳妥的做法:一次 API 返回全部阶段的计数(1 次请求)


2. 深度定制 —— 可控重写

2.1 什么时候应该”重定制”,什么时候”扩展一下就行”

先说结论:能扩展默认看板就别重写

重写意味着你要长期对抗:

  • EspoCRM 前端内部结构变化
  • 过滤/排序/权限/拖拽的细节
  • 多浏览器兼容与样式适配

但有些需求默认看板确实做不到

  • 每列独立滚动(避免整页超长)
  • 每列独立分页(列内”Show More”懒加载)
  • 每列统计区(汇总数值/加权数值/比例)
  • 多币种显示避免误读

2.2 目标:功能增强不破坏原有体验

你重定制的底线应该是:

  • 不修改核心文件(可升级)
  • 不破坏默认过滤/排序/分组能力
  • 不破坏 ACL 行为(权限永远正确)
  • 统计逻辑可复用、可测试、可观测(日志英文)

2.3 推荐架构:后端统计服务 + 前端自定义视图

flowchart LR
  subgraph Backend[Backend - custom module]
    S[KanbanStatisticsService]
    C[Entity Controller]
    R[routes.json]
  end

  subgraph Frontend[Frontend - custom module]
    V[Custom Kanban View]
    T[Handlebars Template]
    CSS[Styles]
  end

  V -->|AJAX| C
  C --> S
  S -->|aggregate| DB[(Database)]
  V --> T
  V --> CSS

2.4 关键实现点

2.4.1 列独立滚动

1
2
3
4
5
6
7
.column-container {
height: 600px;
}

.column-content {
overflow-y: auto;
}

2.4.2 列级分页(Show More)

每列维护自己的 offset/limit:

1
2
3
4
5
6
7
8
9
10
11
12
// 每列独立状态
this.columnState = {
'stage1': { offset: 0, limit: 20, hasMore: true },
'stage2': { offset: 0, limit: 20, hasMore: true },
// ...
};

onShowMore(stage) {
const state = this.columnState[stage];
// 加载本列更多数据
this.loadColumnData(stage, state.offset + state.limit);
}

2.4.3 统计区:汇总数值与加权数值

推荐后端统一计算(避免前端重复、避免精度与货币问题):

1
2
3
4
5
6
{
"totalValue": 150000.00,
"weightedValue": 75000.00,
"weightedPercentage": 50.0,
"currency": "USD"
}

2.4.4 多币种显示:避免用户误读

最常见事故:

列底部写了 USD,但数据里混入了 HKD,用户以为总额都是 USD。

策略

  • 展示时明确币种
  • 统计时按币种分组或统一换算(取决于规则定义)
  • 没有明确换算规则就不要混算(宁可多行展示)

2.5 模块边界:把重写代价锁住

1
2
3
4
5
6
7
8
9
10
11
12
custom/Espo/Modules/MyKanban/
├── Controllers/
│ └── <Entity>.php # 统计 API
├── Services/
│ └── KanbanStatisticsService.php
├── Resources/
│ └── metadata/
│ └── routes.json # API 路由
└── client/modules/my-kanban/
└── src/views/
└── <entity>/
└── kanban.js # 自定义看板视图

关键:所有改动都在模块内,不触碰 application/ 目录。


本篇总结

  • 阶段计数:后端聚合 + ACL 复用,前端只渲染刷新
  • 深度定制:必须重写时,用模块边界锁住代价
  • 统一后端计算统计,前端不做”算数”
  • 多币种宁可分组展示,也不要混算误导用户