一、概述
FE BLL(Frontend Business Logic Layer) 是一套面向前端复杂业务的逻辑治理架构,核心是落地领域驱动设计(DDD) 理念 ------ 通过标准化目录结构与事件驱动机制,将业务逻辑与视图逻辑彻底解耦,解决大型前端应用中 "逻辑散、耦合重、维护难" 的问题
二、架构设计
2.1 目录规范
FE BLL 采用 "分层 + 领域划分" 的目录结构,核心分为业务逻辑层(BLL )和视图层(View),示例如下:
typescript
src/
├── bll-business/ # BLL层:按业务域封装逻辑
│ ├── main-flow/ # 主流程业务域
│ │ ├── handover/ # 交接记录子域
│ │ │ ├── actions.ts # 业务逻辑执行单元
│ │ │ ├── index.ts # 业务上下文容器
│ │ │ ├── schema.ts # UI配置单一来源
│ │ │ └── types.ts # 业务类型定义
├── views/ # View层:仅负责UI展示
│ ├── main-flow/
│ │ ├── handover-record-list.vue # 交接记录页面
该结构确保每个业务域的逻辑、配置、类型独立封装,便于团队协作与模块复用
2.2 BLL 层核心文件
BLL 层由 3 个职责明确的核心文件组成,实现 "业务逻辑的内聚与隔离":
2.2.1 actions.ts - 业务逻辑执行单元
actions.ts 是 BLL 层的 "逻辑核心",负责封装所有业务行为(数据请求、参数校验、状态变更、事件触发等)
核心特点
- 支持异步操作处理,适配接口调用等场景
- 逻辑内聚:同一业务的所有操作(查询 / 创建 / 批量处理)集中管理
- 解耦视图 :通过
$emit触发事件,不直接依赖任何 UI 代码
主要功能
- 数据获取:处理列表查询、详情获取等接口请求
- 业务操作 :处理 "创建 / 编辑 / 删除" 等核心业务行为
- 状态管理 :通过
updateStore更新业务数据 - 事件触发 :通过
$emit向视图层传递业务结果
2.2.2 index.ts - 业务上下文容器
index.ts 是 View 层与 BLL 层的 "交互入口",核心职责如下
- 初始化业务基础数据(如下拉选择列表)
- 整合
actions.ts与schema.ts,构建完整业务上下文 - 统一管理业务状态(Store)的初始值
2.2.3 schema.ts - UI 配置单一来源
schema.ts 是 "UI 配置的集中管理中心",主要功能如下
- 表格配置:定义列名、字段、宽度、插槽等
- 筛选器配置:定义输入框、下拉选择等筛选组件
- 表单配置:定义字段类型、验证规则、交互属性等
三、代码示例
3.1 actions.ts:交接记录的业务逻辑实现
typescript
export const actions = {
// 获取交接记录列表
async getHandoverRecordList({ updateStore, store }, res: Function, pagination: Pagination) {
let result: { data: HandoverTaskSearchResponse[], total: number } = {
data: [],
total: 0,
};
try {
// 从Store读取搜索参数
const params = { ...store.searchParams };
// 接口请求
const { data } = await api.handoverTaskSearch(params);
// 处理数据并更新Store
const list = data?.list ?? [];
updateStore(list, 'listData.list');
result = { data: list, total: data?.total ?? 0 };
} catch (e) {
console.error('get handover record list error: ', e);
} finally {
// 同步返回结果
res(result);
}
},
// 创建交接任务
async createHandoverTask({ $options, $emit }) {
try {
const { data: { handover_task_number }} = await api.handoverTaskCreate();
// 触发视图层事件(不直接处理UI)
$emit(eventTypes.onCreateSuccess, handover_task_number);
} catch (e) {
console.error('create handover task error: ', e);
}
},
// 批量打印交接单(含二次确认逻辑)
async batchPrintHandover({ store, $options }) {
const { selectedRows } = store;
// 校验选中项
if (!selectedRows.length) {
$options.$message.error(i18n('Please select task!'));
return;
}
// 二次确认:含进行中任务的风险提示
const hasOngoing = selectedRows.some(item => item.status === 1);
if (hasOngoing) {
const confirm = await $options.$confirm(
i18n('Ongoing tasks will be marked as completed if printed'),
i18n('Tips'),
{ confirmButtonText: i18n('Print') }
);
if (!confirm) return;
}
try {
const { data: { print_data, email_rule_not_matched_list: notMatchedList }} =
await api.handoverBatchPrintSheetRecordCreate({
task_number_list: selectedRows.map(v => v.task_number)
});
// 执行打印逻辑
print_data?.forEach(v => v.html && print_transit_handover(v.html));
// 提示未匹配规则的任务
if (notMatchedList?.length) {
$options.$message.error(i18n('Task %s has no matching rule', [notMatchedList.join(',')]));
}
} catch (e) {
console.error('batch print handover error: ', e);
}
},
// 其他逻辑:筛选变更、表单处理等...
onFilterChange({ updateStore }, key: string, _: string, value: string[]) {
if (key === 'destination_list') {
const newValue = value?.includes(ALL_VALUE)
? [ALL_VALUE]
: value?.filter(v => v !== ALL_VALUE) ?? [];
updateStore(newValue, 'searchParams.destination_list');
}
}
};
3.2 index.ts:交接记录的上下文容器
typescript
export default class HandoverRecordListContext {
schema: any = {};
async init() {
// 并行初始化业务基础数据
const [ilhRes, serviceCodeRes, changeFlRes, tpTagRes] = await Promise.all([
api.getIhList(),
api.getServiceCodeList(),
api.getChangeFlList(),
api.getTpTagList()
]);
// 转换为下拉选择格式
const ilhList = (ilhRes?.data ?? []).map((item: string) => ({ value: item, label: item }));
const serviceCodeList = (serviceCodeRes?.data?.service_code_list ?? []).map((item: string) => ({
value: item, label: item
}));
const changeFlList = (changeFlRes?.data?.list ?? []).map((value: string) => ({
value, label: i18n(value)
}));
const tpOptions = Object.keys(tpMap).map(key => ({ label: key, value: tpMap[key] }));
// 组装Schema(关联表格/筛选器/表单配置)
this.schema = {
tableListSchema: getHandoverRecordListTableColumns(),
filterSchema: getHandoverRecordListFilter(
serviceCodeRes?.data?.service_code_of_destination_map ?? {},
serviceCodeList, ilhList, tpOptions
),
changeFormSchema: getChangeSchema(changeFlList, tpTagRes?.data ?? [])
};
}
}
3.3 schema.ts:交接记录的 UI 配置
typescript
// 表格列配置
export const getHandoverRecordListTableColumns = (): JSCSchema.ExtendSchema[] => {
return [
{
label: 'Task ID',
field: 'task_number',
xData: { fixed: 'left', width: 170, slots: { task_number: '' } }
},
{ label: 'Operator', field: 'operator', xData: { width: 100 } },
{
label: 'Action',
field: 'action',
xData: { fixed: 'right', width: 150, slots: { action: '' } }
}
];
};
// 筛选器配置
export const getHandoverRecordListFilter = (
serviceCodeMap: Record<string, any> = {},
serviceCodeList: Option[] = [],
ilhList: LHLineItem[] = [],
tpOptions: Option[] = [],
): JSCSchema.ExtendSchema[] => {
const countryOptions = generateEnums([ALL_VALUE, ...Object.keys(serviceCodeMap).sort()]);
return [
{ type: 'input', field: 'task_number', label: 'Task ID' },
{
type: 'select',
field: 'service_code',
label: 'Service Code',
xProps: {
'use-virtual': true,
'options': [ALL_OPTIONS, ...serviceCodeList]
}
}
];
};
3.4 View 层:交接记录页面(仅负责 UI)
typescript
import { promisify } from 'bll-cerberus/dist/bll-core';
import { HandoverRecordListContext } from '@/bll-business/main-flow/handover/handover-record-list';
export default class HandoverRecordList extends Vue {
// 初始化BLL上下文
bll = new HandoverRecordListContext();
// 关联BLL的事件、Schema、状态
get bllEventTypes() { return this.bll.eventTypes; }
get bllSchema() { return this.bll.schema ?? {}; }
get bllStore() { return this.bll.store ?? {}; }
// 转换Schema为UI组件可用的配置
get listTableSchema() {
return getProTableColumns(this.bllSchema.tableListSchema ?? []).map(item => ({
...item, label: this.$i18n(item.label)
}));
}
// 注册事件监听(组件创建时)
created() {
this.bll.$on(this.bllEventTypes.fetchData, this.reloadTable);
this.bll.$on(this.bllEventTypes.onCreateSuccess, this.handleCreateSuccess);
}
// 触发BLL的查询逻辑
async getList(params: Record<string, any>) {
return await promisify(this.bll, this.bllEventTypes.onListSearch, params);
}
// 视图交互:创建任务(仅触发事件,不处理业务)
handleCreateClick() {
this.bll.$emit(this.bllEventTypes.onCreate);
}
// 视图响应:创建成功后跳转页面
handleCreateSuccess(_: any, task_number: string) {
this.$router.push({ name: 'main.flow.handover.task', query: { task_number } });
}
}
四、实践分析

以上图为例,分析 FE BLL 架构中 "视图层 - 事件中心 - 业务逻辑层 - API 层" 的协作链路与各层的职责边界
4.1 架构优势
1. 职责边界绝对清晰
- BLL 层(业务逻辑层):负责封装所有业务逻辑,为 View 层提供完整的业务支持。该层专注于定义 "业务应该做什么",严格避免包含任何 UI 相关代码
- View 层(视图层):仅负责 "页面应该怎么展示与交互触发",不包含任何业务逻辑
2. 事件驱动解耦复杂逻辑
- 通过事件订阅 / 发布 ,实现 "业务行为" 与 "视图响应" 的解耦(如创建成功后 View 层跳转,BLL 层无需关心)
- 支持异步操作的同步化调用,适配多步骤、联动型业务场景
3. 团队协作效率提升
- 标准化目录让新人快速定位代码(eg:找业务逻辑 → actions.ts)
- 同一业务域的代码集中管理,避免跨文件协作冲突
4. 低改造成本适配现有项目
- 无需升级框架版本,可在现有 Vue / React 项目中 "局部试点、逐步推广"
- 不依赖特定状态管理库,适配 Vuex、Pinia、Redux 等方案
4.2 实践要点
1. Store 数据的 "业务域划分"
- 仅将 "业务核心数据" (列表、搜索参数)放入 Store
- 按业务域拆分 Store (如交接记录 Store 、转运规则 Store),避免全局臃肿
2. 事件的 "精准订阅"
- 用 TypeScript 枚举定义事件类型(如
HandoverEventTypes),避免拼写错误 - 仅订阅 "与当前视图相关的事件",减少不必要的监听。
3. 视图与逻辑的 "严格边界"
- BLL 层禁止调用
$message / $router等 UI 工具 - View 层禁止写 "参数校验、接口调用" 等业务逻辑
4.3 架构思考:BLL vs 传统 Mixins
疑问
把业务逻辑放到 Mixins 中,用同步方法调用,是否也能解耦,而不需要使用 "事件驱动" 这种异步机制来通信?
结合场景判断
- Mixins 的局限 :同步调用会导致 "视图层直接依赖业务逻辑实现",耦合度更高;且无法实现跨组件无耦合通信
- 事件机制的价值:对于复杂异步业务(如批量操作需二次确认),事件能解耦逻辑;但对于简单同步逻辑(如表单字段变更),事件会增加复杂度
结论
FE BLL 的 "事件驱动" 是 "复杂业务的解耦工具" ,而非 "通用方案"
4.4 潜在问题与优化方向
1. 事件类型的类型安全问题
问题 :字符串类型的事件 易出错且无提示
优化 :用 TypeScript 枚举定义事件类型,配合类型守卫强化检查
2. UI 与业务逻辑的混淆风险
问题 :BLL 层调用 UI 工具会导致依赖耦合
优化 :BLL 层仅触发事件,由 View 层订阅后处理 UI 交互(如提示弹窗)
五、总结
FE BLL 是前端复杂业务的 "逻辑治理工具" ,核心价值是通过 DDD 理念,将散落在视图中的业务逻辑集中封装,实现 "业务逻辑可复用、视图代码可维护"
5.1 应用场景
- 适合场景:多步骤、异步联动、多人协作的复杂业务(如订单流程、转运规则)
- 不适合场景 :简单 CRUD 操作(会显得过度设计)
5.2 落地建议
- 先在核心复杂业务模块试点,再逐步推广
- 团队统一目录结构与编码规范
- 结合业务复杂度,灵活选择 "事件驱动" 或 "同步调用"