PHP项目反馈工单如何流转跟进状态

wen PHP项目 27

本文目录导读:

PHP项目反馈工单如何流转跟进状态

  1. 核心:工单状态机设计
  2. 数据库表设计
  3. PHP 后端逻辑实现(状态机)
  4. 前端交互设计
  5. 进阶优化建议

在 PHP 项目中,实现反馈工单的流转与跟进状态,通常需要结合数据库设计后端状态机逻辑以及前端交互,下面是一个标准的实现方案,涵盖了从提交到关闭的完整生命周期。

核心:工单状态机设计

需要定义清晰的状态流转规则,常见的工单状态如下:

状态 含义 下一步可能状态
待处理 用户提交,管理员未查看 处理中、已关闭
处理中 管理员已接单,正在处理 待回复、已解决、已关闭
待回复 管理员回复或询问,等待用户反馈 处理中、已关闭
已解决 管理员认为已解决,等待用户确认 待回复、已关闭
已关闭 最终状态,不可再流转 -

状态流转图

[用户提交] --> 待处理
               |
               v
            处理中 <---> 待回复
               |
               v
            已解决 <---> 待回复 (用户不满意)
               |
               v
            已关闭

数据库表设计

建议使用 MySQLPostgreSQL

-- 工单主表
CREATE TABLE tickets (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,           -- 提交用户IDVARCHAR(255) NOT NULL,    -- 工单标题
    description TEXT,               -- 问题描述
    status ENUM('待处理', '处理中', '待回复', '已解决', '已关闭') DEFAULT '待处理',
    priority ENUM('低', '中', '高', '紧急') DEFAULT '中',
    assignee_id INT DEFAULT NULL,   -- 当前处理人(管理员ID)
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    closed_at TIMESTAMP NULL,       -- 关闭时间
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (assignee_id) REFERENCES admins(id)
);
-- 跟进记录表(记录所有操作日志)
CREATE TABLE ticket_logs (
    id INT PRIMARY KEY AUTO_INCREMENT,
    ticket_id INT NOT NULL,
    operator_type ENUM('user', 'admin') NOT NULL, -- 操作者类型
    operator_id INT NOT NULL,                     -- 操作者ID
    action ENUM('created', 'assigned', 'status_changed', 'replied', 'closed'),
    old_status VARCHAR(20),                       -- 变更前状态
    new_status VARCHAR(20),                       -- 变更后状态
    content TEXT,                                 -- 备注或回复内容
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (ticket_id) REFERENCES tickets(id)
);

PHP 后端逻辑实现(状态机)

在 PHP 中,建议使用 状态模式简单的条件判断 来控制流转,下面是一个基于 switch 的示例:

<?php
class TicketService
{
    /**
     * 更新工单状态(核心流转逻辑)
     * @param int $ticketId
     * @param string $newStatus   目标状态
     * @param int $operatorId     操作人ID
     * @param string $operatorType 操作人类型 (user/admin)
     * @return bool|string 成功返回true,失败返回错误信息
     */
    public function changeStatus(int $ticketId, string $newStatus, int $operatorId, string $operatorType): bool|string
    {
        // 1. 获取当前工单
        $ticket = Ticket::find($ticketId);
        if (!$ticket) {
            return '工单不存在';
        }
        $currentStatus = $ticket->status;
        $allowedTransitions = $this->getAllowedTransitions($currentStatus, $operatorType);
        // 2. 验证状态是否允许
        if (!in_array($newStatus, $allowedTransitions)) {
            return "不允许从 [$currentStatus] 流转到 [$newStatus]";
        }
        // 3. 权限检查(用户不能直接改为“已解决”)
        if (!$this->checkPermission($operatorType, $newStatus, $ticket)) {
            return '无权限执行此操作';
        }
        // 4. 执行状态变更(数据库事务)
        DB::beginTransaction();
        try {
            // 更新主表
            $ticket->status = $newStatus;
            $ticket->updated_at = now();
            if ($newStatus == '已关闭') {
                $ticket->closed_at = now();
            }
            $ticket->save();
            // 记录日志
            $log = new TicketLog();
            $log->ticket_id = $ticketId;
            $log->operator_type = $operatorType;
            $log->operator_id = $operatorId;
            $log->action = 'status_changed';
            $log->old_status = $currentStatus;
            $log->new_status = $newStatus;
            $log->content = "状态由 [$currentStatus] 变更为 [$newStatus]";
            $log->save();
            // 可选:发送通知
            $this->sendStatusChangeNotification($ticket, $newStatus);
            DB::commit();
            return true;
        } catch (\Exception $e) {
            DB::rollBack();
            return '状态更新失败:' . $e->getMessage();
        }
    }
    /**
     * 定义允许的状态转换矩阵
     */
    private function getAllowedTransitions(string $currentStatus, string $operatorType): array
    {
        // 基础转换规则
        $transitions = [
            '待处理' => ['处理中', '已关闭'],
            '处理中' => ['待回复', '已解决', '已关闭'],
            '待回复' => ['处理中', '已关闭'],   // 用户回复后管理员可再次进入处理中
            '已解决' => ['待回复', '已关闭'],   // 用户不满意可回到待回复
            '已关闭' => [],                    // 最终态,不可变更
        ];
        // 管理员可以执行所有允许的转换
        if ($operatorType === 'admin') {
            return $transitions[$currentStatus] ?? [];
        }
        // 用户只能执行部分转换(提交时,回复时)
        $userTransitions = [
            '待处理' => [],                      // 用户提交后不能再改
            '处理中' => ['待回复'],              // 用户可回复,但不改变状态
            '待回复' => ['处理中'],              // 用户回复后,状态变回处理中(等待管理员)
            '已解决' => ['待回复'],              // 用户不满意,重新开启
            '已关闭' => [],                     // 用户不能重开,需要管理员处理
        ];
        // 特殊处理:用户只能将待回复 -> 处理中, 已解决 -> 待回复
        // 用户回复 不等于 状态变更,需要单独处理。
        // 这里简化:用户主动点击 "重新开启" 才改变状态。
        return $userTransitions[$currentStatus] ?? [];
    }
    /**
     * 简单权限检查
     */
    private function checkPermission(string $operatorType, string $newStatus, $ticket): bool
    {
        // 禁止用户关闭自己的工单(应由管理员关闭)
        if ($operatorType === 'user' && in_array($newStatus, ['已关闭', '已解决'])) {
            return false;
        }
        // 管理员不能将状态回退到“待处理”(用户自己提交的才算待处理)
        if ($operatorType === 'admin' && $newStatus === '待处理') {
            return false;
        }
        return true;
    }
    /**
     * 发送通知(邮件/钉钉/内部消息)
     */
    private function sendStatusChangeNotification($ticket, string $newStatus): void
    {
        // 可以发送给提交者或处理人
        // $ticket->user->notify(new TicketStatusUpdated($ticket, $newStatus));
    }
}

前端交互设计

前端需要根据当前用户角色(用户/管理员)显示不同的操作按钮。

用户视角:

  • 待处理:只能查看,不能操作
  • 处理中:可以追加回复(此时状态不变,但管理员会收到通知)
  • 待回复:可以(回复后,状态变为“处理中”)
  • 已解决:可以确认关闭(或重新开启,状态变为“待回复”)

管理员视角:

  • 待处理:可接单(状态变为处理中)
  • 处理中:可回复用户(回复后自动变为“待回复”或保持“处理中”),或标记为已解决
  • 待回复:查看用户回复后,可继续处理(状态变为处理中),或关闭工单
  • 已解决:可最终关闭,或重新开启(如果用户不满意但未操作)

界面按钮示例:

<!-- 管理员操作区 -->
<button class="btn btn-primary" data-action="接单" data-status="处理中">接单</button>
<button class="btn btn-success" data-action="解决" data-status="已解决">标记已解决</button>
<button class="btn btn-danger" data-action="关闭" data-status="已关闭">关闭工单</button>
<!-- 用户操作区 -->
<button class="btn btn-info" data-action="回复" data-status="处理中">回复</button>
<button class="btn btn-warning" data-action="重新开启" data-status="待回复">重新开启</button>

JavaScript 示例:

// 使用 Ajax 调用后端 API
$('.status-change-btn').click(function() {
    const ticketId = $(this).data('ticket-id');
    const newStatus = $(this).data('status');
    const action = $(this).data('action');
    if (!confirm(`确定要${action}吗?`)) return;
    $.ajax({
        url: '/api/ticket/change-status',
        method: 'POST',
        data: {
            ticket_id: ticketId,
            new_status: newStatus,
            _token: csrfToken
        },
        success: function(response) {
            if (response.success) {
                alert('操作成功');
                location.reload();
            } else {
                alert('操作失败:' + response.message);
            }
        },
        error: function() {
            alert('网络错误');
        }
    });
});

进阶优化建议

  1. 使用状态机库: 推荐使用 sebastian/environment 或更强大的 Finiteyohang/Finite)库来管理复杂的状态流转,避免硬编码。

  2. 引入工作流引擎: 对于非常复杂的工单(如跨部门审批、SLA 监控),可以考虑集成 Symfony WorkflowPetri网 概念。

  3. 通知与提醒

    • 状态变更时,通过邮件、短信、WebSocket 或钉钉/企业微信机器人通知相关方。
    • SLA 超时自动升级(如:待处理超过 24 小时自动升级为紧急)。
  4. 权限与角色

    • 普通用户:只能查看自己的工单,不能关闭。
    • 普通管理员:可以处理所有工单,但不能删除。
    • 超级管理员:可以关闭、删除、重新分配。
  5. 搜索与筛选: 提供按状态、优先级、处理人、时间范围进行高级搜索。

  6. 前端框架: 对于复杂工单,建议使用 Vue.js 或 React 构建单页应用 (SPA) 来实时显示状态变化,配合 WebSocket 实现即时通讯。

在 PHP 项目中实现工单流转,核心是定义好状态机记录操作日志规范权限,前端根据当前状态和用户角色动态显示操作按钮,通过这样一套设计,可以确保工单在不同角色之间有序流转,同时为后续的统计分析和问题追溯提供完整的数据支持。

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