query('SELECT * FROM ' . tn('ticket_departments') . ' ORDER BY sort, id')->fetchAll(); $deptMap = []; foreach ($depts as $d) { $deptMap[$d['id']] = $d['name']; } $validStatus = ['open', 'pending', 'resolved', 'closed']; // 重新开启已关闭工单(快捷按钮) if (isset($_POST['reopen'])) { verifyCsrf(); $id = (int) ($_POST['id'] ?? 0); $stmt = $db->prepare('SELECT id FROM ' . tn('tickets') . ' WHERE id = ?'); $stmt->execute([$id]); if ($stmt->fetch()) { $db->prepare('UPDATE ' . tn('tickets') . ' SET status=?, updated_at=NOW(), closed_at=NULL WHERE id=?') ->execute(['open', $id]); $msg = '工单已重新开启。'; } else { $err = '工单不存在'; } } // 分配负责人(仅管理员可操作;客服仅能看自己负责的工单) if (isset($_POST['assign']) && $isAdmin) { verifyCsrf(); $id = (int) ($_POST['id'] ?? 0); $toUid = (int) ($_POST['assignee_id'] ?? 0); if ($id <= 0) { $err = '工单不存在'; } else { $staffCheck = $db->prepare('SELECT id FROM ' . tn('users') . ' WHERE id = ? AND role IN (\'admin\',\'cs\') AND status = 1'); $staffCheck->execute([$toUid]); $realUid = $toUid > 0 && $staffCheck->fetch() ? $toUid : null; $db->prepare('UPDATE ' . tn('tickets') . ' SET assignee_id = ? WHERE id = ?') ->execute([$realUid, $id]); $msg = $realUid ? '已分配负责人。' : '已取消分配(工单转为未分配,仅管理员可见)。'; } } // 处理提交(追加回复 / 状态变更) if (isset($_POST['reply'])) { verifyCsrf(); $id = (int) ($_POST['id'] ?? 0); $status = trim($_POST['status'] ?? 'open'); $reply = trim($_POST['admin_reply'] ?? ''); $notify = !empty($_POST['notify']); // 客服仅能处理分配给自己的工单 if (!$isAdmin) { $chk = $db->prepare('SELECT assignee_id FROM ' . tn('tickets') . ' WHERE id = ?'); $chk->execute([$id]); $ar = $chk->fetch(); if (!$ar || (int) $ar['assignee_id'] !== (int) $me['id']) { $err = '该工单未分配给你,无权操作。'; $id = 0; } } if (!in_array($status, $validStatus, true)) { $err = '状态不合法'; } else { $stmt = $db->prepare('SELECT t.*, u.email AS user_email, u.username AS user_name FROM ' . tn('tickets') . ' t LEFT JOIN ' . tn('users') . ' u ON u.id = t.user_id WHERE t.id = ?'); $stmt->execute([$id]); $t = $stmt->fetch(); if (!$t) { $err = '工单不存在'; } else { $adminId = $me['id']; $closedAt = $status === 'closed' ? date('Y-m-d H:i:s') : null; if ($reply !== '') { $db->prepare('INSERT INTO ' . tn('ticket_replies') . ' (ticket_id, user_id, is_admin, message, created_at) VALUES (?,?,?,?,NOW())') ->execute([$id, $adminId, 1, $reply]); } $db->prepare('UPDATE ' . tn('tickets') . ' SET status=?, admin_id=?, updated_at=NOW(), closed_at=? WHERE id=?') ->execute([$status, $adminId, $closedAt, $id]); if ($notify && $t['user_email'] !== '') { $base = siteBaseUrl(); $url = rtrim($base, '/') . '/mytickets.php?view=' . $id; $subject = '[' . SITE_NAME . '] 您的工单 #' . $id . ' 有新回复'; $body = '
' . '

工单处理通知

' . '

您好 ' . h($t['user_name']) . ',您提交的工单已更新:

' . '

工单标题:' . h($t['subject']) . '

' . '

当前状态:' . ticketStatusLabel($status) . '

' . ($reply !== '' ? '
' . nl2br(h($reply)) . '
' : '') . '

查看工单详情

' . '
'; $r = fnwSendMail($t['user_email'], $subject, $body); if (!$r['ok']) { $msg = '工单已保存,但邮件通知发送失败:' . $r['error']; } else { $msg = '工单已保存,并已邮件通知用户。'; } } else { $msg = '工单已保存。'; } } } } // 批准续期(续期工单专用) if (isset($_POST['approve_renew'])) { verifyCsrf(); if (!$isAdmin) { $err = '仅管理员可批准续期。'; } else { $id = (int) ($_POST['id'] ?? 0); $reply = trim($_POST['admin_reply'] ?? ''); $stmt = $db->prepare('SELECT t.*, o.expires_at, o.period_days, u.email AS user_email, u.username AS user_name FROM ' . tn('tickets') . ' t LEFT JOIN ' . tn('orders') . ' o ON o.id = t.order_id LEFT JOIN ' . tn('users') . ' u ON u.id = t.user_id WHERE t.id = ?'); $stmt->execute([$id]); $t = $stmt->fetch(); if (!$t || $t['type'] !== 'renewal' || !$t['order_id']) { $err = '该工单不是有效的续期申请'; } elseif (empty($t['period_days']) || $t['period_days'] <= 0) { $err = '关联订单无有效周期,无法续期'; } else { // 从当前到期(未过期则顺延)或现在起,延长一个周期 $base = ($t['expires_at'] && !isExpired($t['expires_at'])) ? $t['expires_at'] : date('Y-m-d H:i:s'); $newExpiry = date('Y-m-d H:i:s', strtotime($base . ' +' . (int)$t['period_days'] . ' days')); $db->prepare('UPDATE ' . tn('orders') . ' SET expires_at = ? WHERE id = ?')->execute([$newExpiry, $t['order_id']]); $reply = $reply !== '' ? $reply : ('已为你续期,新到期时间:' . $newExpiry); $adminId = $me['id']; $db->prepare('INSERT INTO ' . tn('ticket_replies') . ' (ticket_id, user_id, is_admin, message, created_at) VALUES (?,?,?,?,NOW())') ->execute([$id, $adminId, 1, $reply]); $db->prepare('UPDATE ' . tn('tickets') . ' SET admin_reply=?, status=?, admin_id=?, updated_at=NOW(), closed_at=NULL WHERE id=?') ->execute([$reply, 'resolved', $adminId, $id]); $msg = '已批准续期,关联订单 #' . $t['order_id'] . ' 到期时间已延长至 ' . $newExpiry; if (!empty($t['user_email'])) { $baseUrl = siteBaseUrl(); $url = rtrim($baseUrl, '/') . '/mytickets.php?view=' . $id; $subject = '[' . SITE_NAME . '] 你的续期申请已通过(订单 #' . $t['order_id'] . ')'; $body = '
' . '

续期成功

' . '

你好 ' . h($t['user_name']) . ',你的续期申请已通过:

' . '

关联订单:#' . (int)$t['order_id'] . '

' . '

新到期时间:' . h($newExpiry) . '

' . '
' . nl2br(h($reply)) . '
' . '

查看工单

' . '

可在「我的订单 → 详情」中查看完整凭据。

'; fnwSendMail($t['user_email'], $subject, $body); } } } } // 列表筛选 $filterStatus = trim($_GET['status'] ?? ''); $filterDept = isset($_GET['dept']) ? (int) $_GET['dept'] : 0; $where = []; $params = []; if ($filterStatus !== '') { $where[] = 't.status = ?'; $params[] = $filterStatus; } if ($filterDept > 0) { $where[] = 't.dept_id = ?'; $params[] = $filterDept; } // 客服仅能看到分配给自己的工单 if (!$isAdmin) { $where[] = 't.assignee_id = ?'; $params[] = $me['id']; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $totalStmt = $db->prepare('SELECT COUNT(*) FROM ' . tn('tickets') . ' t ' . $whereSql); $totalStmt->execute($params); $total = $totalStmt->fetchColumn(); $stmt = $db->prepare( 'SELECT t.*, u.username AS user_name, u.email AS user_email, d.name AS dept_name, a.username AS assignee_name FROM ' . tn('tickets') . ' t LEFT JOIN ' . tn('users') . ' u ON u.id = t.user_id LEFT JOIN ' . tn('ticket_departments') . ' d ON d.id = t.dept_id LEFT JOIN ' . tn('users') . ' a ON a.id = t.assignee_id ' . $whereSql . ' ORDER BY t.updated_at DESC' ); $stmt->execute($params); $list = $stmt->fetchAll(); // 查看单条 $view = isset($_GET['view']) ? (int) $_GET['view'] : 0; $ticket = null; $replies = []; if ($view > 0) { $stmt = $db->prepare( 'SELECT t.*, u.username AS user_name, u.email AS user_email, d.name AS dept_name, a.username AS assignee_name FROM ' . tn('tickets') . ' t LEFT JOIN ' . tn('users') . ' u ON u.id = t.user_id LEFT JOIN ' . tn('ticket_departments') . ' d ON d.id = t.dept_id LEFT JOIN ' . tn('users') . ' a ON a.id = t.assignee_id WHERE t.id = ?' ); $stmt->execute([$view]); $ticket = $stmt->fetch(); // 客服仅能查看分配给自己的工单 if ($ticket && !$isAdmin && (int) $ticket['assignee_id'] !== (int) $me['id']) { $ticket = null; $err = '该工单未分配给你,无权查看。'; } if ($ticket) { $rstmt = $db->prepare( 'SELECT r.*, u.username FROM ' . tn('ticket_replies') . ' r LEFT JOIN ' . tn('users') . ' u ON u.id = r.user_id WHERE r.ticket_id = ? ORDER BY r.created_at ASC' ); $rstmt->execute([$view]); $replies = $rstmt->fetchAll(); } } // 负责人候选(管理员/客服),仅管理员可在表单中分配 $assignees = []; if ($isAdmin) { $assignees = $db->query("SELECT id, username, role FROM " . tn('users') . " WHERE role IN ('admin','cs') AND status = 1 ORDER BY role DESC, username")->fetchAll(); } adminHeader('工单管理', 'tickets'); ?>

工单管理(共

类型: 提交人: 部门: 优先级: 负责人:未分配' ?> 提交:
prepare('SELECT order_no, period_days, expires_at, status FROM ' . tn('orders') . ' WHERE id = ?'); $ord->execute([$ticket['order_id']]); $ord = $ord->fetch(); ?>
续期申请 · 关联订单 # · 周期 · 当前到期
·

处理人员() ·
·

提示:历史回复以上方时间线为准,此处内容仅用于新增回复。

返回列表
重置

暂无工单。

编号标题提交人部门类型优先级状态负责人更新
# 未分配' ?> 处理