PHP项目怎么实现用户自定义审批大屏?

wen java案例 1

本文目录导读:

PHP项目怎么实现用户自定义审批大屏?

  1. 第一层:核心数据模型设计(数据库层面)
  2. 第二层:核心数据模型设计(PHP 与 JSON 结构)
  3. 第三层:PHP 实现逻辑
  4. 第四层:前端封装(用户交互关键)
  5. 安全性考量(极其重要)

这是一个非常典型且复杂的企业级需求,所谓的“用户自定义审批大屏”,核心在于低代码/无代码的配置化思想,允许用户(通常是非技术管理员)通过拖拽或表单配置,来定义审批流程的各个节点、条件以及最终展示在屏幕上的图表样式。

要实现这个功能,不能只靠简单的代码逻辑,需要一套完整的数据模型和前端渲染引擎。

以下是一套可行的、分层次的 PHP 实现架构方案:

第一层:核心数据模型设计(数据库层面)

这是最基础、最关键的,你需要几张核心表来存储用户“自定义”的结果,而不是硬编码。

流程定义表 approval_flows

CREATE TABLE `approval_flows` (
    `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(100) NOT NULL COMMENT '流程名称',
    `icon` VARCHAR(50) DEFAULT NULL COMMENT '大屏显示的图标',
    `description` TEXT COMMENT '描述',
    `status` TINYINT DEFAULT 1 COMMENT '1启用 0停用',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

节点定义表 approval_nodes

核心字段:typeconfig_json

CREATE TABLE `approval_nodes` (
    `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `flow_id` INT UNSIGNED NOT NULL COMMENT '所属流程ID',
    `node_order` INT UNSIGNED NOT NULL COMMENT '节点顺序,如1,2,3',
    `type` VARCHAR(50) NOT NULL COMMENT '节点类型: start, approve, condition, notify, end',
    `name` VARCHAR(100) NOT NULL COMMENT '节点名称(如: 部门经理审批)',
    -- 关键配置字段:存储该节点的所有自定义属性(JSON格式)
    `config_json` JSON DEFAULT NULL COMMENT '节点配置,具体见下方详解',
    `parent_node_id` INT DEFAULT NULL COMMENT '条件分支时指向父节点',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

大屏配置表 approval_dashboards

记录用户对于“大屏”本身的可视化配置。

CREATE TABLE `approval_dashboards` (
    `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `flow_id` INT UNSIGNED NOT NULL,
    `name` VARCHAR(100) NOT NULL COMMENT '大屏名称',
    `layout_config` JSON NOT NULL COMMENT '布局配置,如各组件的位置、大小',
    `theme` VARCHAR(50) DEFAULT 'default' COMMENT '主题',
    `refresh_interval` INT DEFAULT 30 COMMENT '自动刷新秒数',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

大屏组件配置表 dashboard_components

存储每一个图表/卡片/指标的配置。

CREATE TABLE `dashboard_components` (
    `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `dashboard_id` INT UNSIGNED NOT NULL,
    `type` VARCHAR(50) NOT NULL COMMENT '组件类型: pie_chart, bar_chart, number_card, list_table, status_timeline',
    `x` INT DEFAULT 0 COMMENT '网格布局X坐标',
    `y` INT DEFAULT 0 COMMENT '网格布局Y坐标',
    `w` INT DEFAULT 4 COMMENT '网格宽度',
    `h` INT DEFAULT 4 COMMENT '网格高度',
    `source_config` JSON NOT NULL COMMENT '数据源配置,详见下方',
    `style_config` JSON DEFAULT NULL COMMENT '样式配置,如颜色、标题、单位',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

第二层:核心数据模型设计(PHP 与 JSON 结构)

用户自定义的核心就在于 JSON 字段,你需要定义好这些 JSON 的内部结构。

节点 config_json 示例:

{
  "assignee_type": "role", 
  "assignee_value": 5, 
  "timeout_hours": 48,
  "sign_type": "single", 
  "condition": null
}

或对于条件节点:

{
  "condition_logic": "AND",
  "rules": [
    {"field": "amount", "operator": ">", "value": 10000},
    {"field": "department", "operator": "==", "value": "finance"}
  ]
}

组件 source_config 示例:

{
  "data_source": "sql",
  "sql": "SELECT status, COUNT(*) as count FROM approval_requests WHERE flow_id = ? GROUP BY status",
  "params": ["flow_id"],
  "mapping": {
    "name": "status",
    "value": "count"
  }
}

或引用预定义的查询(推荐,更安全):

{
  "data_source": "predefined_query",
  "query_key": "approval_status_distribution",
  "filter_params": {
    "start_date": "-30",
    "end_date": "today"
  }
}

第三层:PHP 实现逻辑

配置视图层(供管理员使用)

  • 路由: /admin/flow/{id}/design
  • PHP 控制器: FlowDesignController@index
    • 从数据库加载 approval_flowsapproval_nodes
    • 返回给前端一个 JSON 树状结构(前端使用 Vue/React 的 antv/g6 或类似流程图库进行拖拽渲染)。
    • 关键点: PHP 只负责提供原始数据(flow + nodes),前端的交互逻辑(拖拽添加节点、修改配置、保存JSON)完全由前端完成。
  • 保存操作: 控制器 FlowDesignController@save
    • 接收前端传来的完整 nodes 数组。
    • 开启事务,先删除该流程下旧节点,再逐条插入新节点。
    • 注意防注入: 尽管是 JSON,但在 SQL 查询中务必使用参数绑定。

运行时解析层(分析引擎)

这是最复杂的一环,当一个人发起一个审批请求时,系统需要根据配置走的路径。

  • 核心服务类: ApprovalEngineService
// 伪代码
class ApprovalEngineService {
    public function startFlow($flowId, $formData) {
        $flow = ApprovalFlows::find($flowId);
        $firstNode = ApprovalNodes::where('flow_id', $flowId)
                        ->where('node_order', 1)->first();
        // 创建审批请求记录
        $request = new ApprovalRequest();
        $request->flow_id = $flowId;
        $request->form_data = json_encode($formData); // 用户提交的表单数据
        $request->current_node_id = $firstNode->id;
        $request->status = 'pending';
        $request->save();
        // 根据 $firstNode->config_json 动态生成待办任务
        $this->createTaskForNode($request, $firstNode);
    }
    public function approve($requestId, $action, $userId) {
        $request = ApprovalRequest::find($requestId);
        $node = ApprovalNodes::find($request->current_node_id);
        // 根据 $node->type 执行不同逻辑        
        switch ($node->type) {
            case 'approve':
                $this->handleApproveNode($request, $node, $action, $userId);
                break;
            case 'condition':
                $this->handleConditionNode($request, $node, $formData);
                break;
        }
        // 移动到下一个节点
        $nextNode = $this->determineNextNode($request, $node);
        $request->current_node_id = $nextNode->id;
        $request->save();
    }
    private function determineNextNode($request, $currentNode) {
        // 核心:根据当前节点 type 和 config 解析下一步
        if ($currentNode->type == 'condition') {
            $rules = json_decode($currentNode->config_json, true)['rules'];
            $formData = json_decode($request->form_data, true);
            // 解析条件逻辑,判断走哪个分支(例如走true分支还是false分支)
            // 通过 parent_node_id 或分支字段来找到对应子节点
            $nextOrder = $this->evaluateCondition($rules, $formData) 
                        ? $currentNode->true_node_id 
                        : $currentNode->false_node_id;
            return ApprovalNodes::find($nextOrder);
        } else {
            // 正常顺序流
            return ApprovalNodes::where('flow_id', $currentNode->flow_id)
                ->where('node_order', $currentNode->node_order + 1)
                ->first();
        }
    }
}

大屏渲染层(后端数据提供)

  • 路由: /api/dashboard/{dashboardId}/data (支持轮询或WebSocket)
  • 控制器: DashboardDataController
class DashboardDataController {
    public function getData($dashboardId) {
        $components = DashboardComponents::where('dashboard_id', $dashboardId)->get();
        $result = [];
        foreach ($components as $component) {
            // 根据 component->source_config 的不同 type 执行不同查询
            if ($component->source_config['data_source'] == 'predefined_query') {
                $queryKey = $component->source_config['query_key'];
                $filterParams = $component->source_config['filter_params'];
                $data = $this->executePredefinedQuery($queryKey, $filterParams);
            } elseif ($component->source_config['data_source'] == 'sql') {
                // 注意极其危险!必须严格过滤用户输入的SQL片段,最好只允许选择预定义字段
                $sql = $component->source_config['sql'];
                // 使用参数绑定执行
                $data = DB::select($sql, [$dashboardId]); 
            }
            $result[] = [
                'component_id' => $component->id,
                'type' => $component->type,
                'style' => $component->style_config,
                'data' => $data
            ];
        }
        return response()->json($result);
    }
    // 安全的预定义查询示例
    private function executePredefinedQuery($key, $params) {
        switch ($key) {
            case 'approval_status_distribution':
                $start = now()->subDays($params['start_date'] ?? 30);
                $end = now()->addDays(1);
                return DB::table('approval_requests')
                    ->select('status', DB::raw('count(*) as count'))
                    ->where('created_at', '>=', $start)
                    ->where('created_at', '<=', $end)
                    ->groupBy('status')
                    ->get();
            // ...
        }
    }
}

第四层:前端封装(用户交互关键)

用户自定义的核心体验在于前端,后端 PHP 只需提供配置数据。

  1. 工作流设计器: 使用 vue-flow / react-flow / LogicFlow 类似的开源库,配合 approval_nodes 的 type 和 config_json,让用户能:
    • 拖出一个“审批节点”。
    • 点击节点,弹出一个PHP渲染的表单(或纯前端Vue组件)配置 config_json(比如选择审批人角色、超时时间)。
  2. 大屏配置器: 使用 GridStack / React-Grid-Layout 实现拖拽布局。
    • 用户添加“饼图组件”。
    • 选择数据源(如:选择预定义的 按状态统计审批单数 查询,并从 status 字段映射到 name)。
    • 保存时,前端将 x,y,w,hsource_config 传给 PHP 存入 dashboard_components 表。
  3. 大屏展示页: 静态页面,使用 ECharts / AntV 等图表库。
    • 启动时调用 GET /api/dashboard/{id}/data
    • 根据返回的 typedata,实例化对应图表。
    • 通过 setInterval( refreshInterval ) 定期重新请求数据,实现动态效果。

安全性考量(极其重要)

当涉及“用户自定义SQL”或“用户自定义条件”时,安全风险极高。

  1. 绝对禁止直接执行用户输入的SQL。 只能执行预定义的、硬编码在PHP中的查询方法(如 executePredefinedQuery 方法中的 switch 语句),用户只能选择“用哪个查询”和“映射哪些字段”。
  2. 条件规则必须在前端和后端双重校验。 用户在 config_json 里保存的 {"rules": [{"field": "amount", "operator": ">", "value": 10000}]},在运行时,PHP 必须有一个白名单,只允许 field 是预设的安全字段(如 amount,department_id),operator 只允许 >, <, , in 等,防止 SQL 注入。
  3. 配置数据缓存: 这类配置通常变化不频繁,将用户的配置(approval_nodes, dashboard_components)缓存到 Redis 或 Memcached 中,减少数据库查询,提升大屏加载速度。

实现 PHP 用户自定义审批大屏,核心思路是:

用数据结构(表 + JSON)定义规则,用引擎(PHP 类)解析规则,用组件(前端库)渲染配置。

  • 后端(PHP): 负责存储配置、解析流程走向(引擎)、提供图表数据(安全预定义查询)、维护用户权限。
  • 前端(JS): 负责将配置以图形化的形式可视化给用户拖拽配置,以及最终将数据渲染成图表。

这种架构下,PHP 更多扮演配置管理器安全的数据查询网关的角色,而非硬编码逻辑。

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

下一篇当前分类已是最新一篇

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