如何用PHP项目实现用户自定义审批看板?

wen java案例 1

如何用PHP项目实现用户自定义审批看板?一篇从零到实战的完整指南

目录导读

  1. 为什么需要自定义审批看板?
  2. 系统架构设计要点
  3. 数据库表结构设计(含核心字段)
  4. PHP后端核心实现逻辑
  5. 前端动态看板渲染方案
  6. 常见问题问答(FAQ)
  7. 性能优化与安全建议

为什么需要自定义审批看板?

传统OA系统中的审批看板往往是固定布局,用户只能被动查看“待办”、“已办”、“抄送”等预设分类,但在实际业务中,不同部门、不同角色的审批关注点差异巨大——

如何用PHP项目实现用户自定义审批看板?

财务总监希望一眼看到“金额大于5万的待审批报销单”,而项目经理则想汇总“本周所有待审批的采购申请”。固定看板无法满足这种差异化需求

通过PHP实现用户自定义审批看板,核心价值在于:

  • 用户可自由组合筛选条件(审批类型、时间范围、金额阈值、申请人等)
  • 支持拖拽调整看板卡片布局
  • 看板配置自动保存,跨设备同步

本文将从数据库设计、PHP业务逻辑编码、前端交互三个维度,给出可落地的完整实现方案。


系统架构设计要点

采用 PHP(ThinkPHP/Laravel)+ MySQL + Redis + Vue.js 的组合:

层级 技术选择 职责
数据层 MySQL(InnoDB) 存储用户配置、审批单据数据
缓存层 Redis 加速看板配置读取,存储临时筛选结果
业务层 PHP 8.x 解析用户配置、动态构建SQL查询
展示层 Vue 3 + SortableJS 拖拽式看板布局管理与实时数据渲染

关键设计原则

  • 看板配置以JSON格式存储,而非固化字段,便于扩展
  • 所有筛选条件由后端动态解析,防止SQL注入
  • 使用队列异步更新看板数据(如新审批单出现时自动推送)

数据库表结构设计(含核心字段)

表1:审批单据主表 approval_records

CREATE TABLE `approval_records` (
  `id` int(11) NOT NULL AUTO_INCREMENT, varchar(200) DEFAULT NULL COMMENT '审批标题',
  `type` varchar(50) DEFAULT NULL COMMENT '审批类型(报销/请假/采购等)',
  `applicant` varchar(50) DEFAULT NULL COMMENT '申请人',
  `amount` decimal(10,2) DEFAULT '0.00' COMMENT '涉及金额',
  `status` tinyint(4) DEFAULT '0' COMMENT '0待审 1通过 2驳回',
  `create_time` datetime DEFAULT NULL,
  `finish_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_status` (`status`),
  KEY `idx_type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

表2:用户看板配置表 user_dashboard

CREATE TABLE `user_dashboard` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `board_config` json DEFAULT NULL COMMENT '看板配置JSON',
  `is_default` tinyint(1) DEFAULT '0' COMMENT '是否默认看板',
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_default` (`user_id`, `is_default`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

JSON配置示例(board_config字段)

[
  {
    "name": "高额待审单",
    "position": 1,
    "filters": { 
      "status": 0, 
      "amount_min": 50000 
    },
    "columns": ["title", "applicant", "amount", "create_time"],
    "sort": { "field": "amount", "order": "DESC" }
  }
]

PHP后端核心实现逻辑

1 保存看板配置(伪代码核心片段)

// 路径:/api/dashboard/save
public function saveConfig(Request $request)
{
    $userId = auth()->id();
    $config = $request->input('board_config'); // 前端传来的JSON
    // 校验JSON格式合法性
    if (!$this->isValidConfig($config)) {
        return response()->json(['code' => 400, 'msg' => '配置格式错误']);
    }
    // 存入数据库,使用upsert避免重复
    Dashboard::updateOrCreate(
        ['user_id' => $userId, 'is_default' => 1],
        ['board_config' => json_encode($config), 'updated_at' => now()]
    );
    // 清理用户看板缓存
    Cache::forget("dashboard:user:{$userId}");
    return success('保存成功');
}

2 动态生成看板数据(核心难点)

// 路径:/api/dashboard/render
public function render(Request $request)
{
    $userId = auth()->id();
    $cacheKey = "dashboard:user:{$userId}";
    // 优先从Redis缓存读取
    $cached = Cache::remember($cacheKey, 300, function () use ($userId) {
        $dashboard = Dashboard::where('user_id', $userId)
            ->where('is_default', 1)
            ->first();
        if (!$dashboard) return [];
        $cards = json_decode($dashboard->board_config, true);
        $results = [];
        foreach ($cards as $card) {
            // 动态拼接查询条件
            $query = ApprovalRecord::query();
            if (!empty($card['filters']['status'])) {
                $query->where('status', $card['filters']['status']);
            }
            if (!empty($card['filters']['amount_min'])) {
                $query->where('amount', '>=', $card['filters']['amount_min']);
            }
            // 此处可扩展更多动态条件...
            $results[] = [
                'name' => $card['name'],
                'count' => $query->count(),
                'list' => $query->limit(20)->get(),
                'columns' => $card['columns']
            ];
        }
        return $results;
    });
    return response()->json($cached);
}

安全性关键点$card['filters']中的字段名必须做白名单校验,防止用户传入delete from user等危险字符。


前端动态看板渲染方案

使用 Vue 3 + SortableJS 实现拖拽排序,核心步骤:

// 拖拽结束事件
const onDragEnd = (evt) => {
    const newOrder = [...cards.value];
    const [movedItem] = newOrder.splice(evt.oldIndex, 1);
    newOrder.splice(evt.newIndex, 0, movedItem);
    // 更新排序字段
    newOrder.forEach((item, index) => item.position = index + 1);
    cards.value = newOrder;
    // 自动保存
    saveConfigToServer(newOrder);
};
// 过滤条件实时切换
const handleFilterChange = (cardIndex) => {
    // 调用后端API重新获取该卡片数据
    axios.get('/api/dashboard/card-detail', {
        params: { filters: cards.value[cardIndex].filters }
    }).then((res) => {
        cards.value[cardIndex].list = res.data;
    });
};

前端展示效果:

  • 每个看板卡片可独立拖动调整位置
  • 卡片右上角提供“编辑过滤条件”功能弹窗
  • 支持展开/折叠卡片内容

常见问题问答(FAQ)

Q1:看板配置JSON太复杂,普通用户不会配置怎么办?
A:提供可视化筛选器组件——用户点击“添加过滤条件”,选择字段、操作符(大于/等于/包含等)、输入值,系统自动生成JSON,相当于用UI操作替代手写JSON。

Q2:每次渲染都要查所有卡片数据,性能扛不住怎么办?
A:采取“懒加载+分页”策略:

  • 页面首次只加载每个卡片的数量(count)
  • 点击展开卡片时,再请求具体列表数据(limit 20)
  • 对于超大数据量,提前在Redis中存储各过滤条件的统计结果

Q3:如何保证不同用户看到的看板数据权限正确?
A:查询时强制加上 where('create_user_id', auth()->id()) 或根据角色过滤,且后端不做“全局查询”,每个SQL都绑定了当前用户上下文。


性能优化与安全建议

优化方向 具体措施
数据库 typestatuscreate_time建立联合索引;对于金额查询使用BETWEEN代替>=提高命中率
缓存 看板配置缓存1200秒,卡片统计缓存300秒;使用Redis的HASH结构存储,手动处理缓存雪崩
防注入 所有用户传入的字段名必须通过whiteList校验,值使用参数绑定
防误操作 同一用户最多保存5个自定义看板;删除看板前二次确认

扩展建议

  • 如果公司有AI能力,可以接入“智能推荐看板”——分析用户上次查看记录,自动生成建议看板
  • 支持看板模板市场:管理员上传标准看板模板,用户一键套用

通过以上方案,一个完全由用户定义、满足个性化需求的审批看板即可在PHP项目中顺利落地,关键不在于框架本身,而在于灵活的配置存储结构安全的动态查询解析引擎

抱歉,评论功能暂时关闭!