Files
aler_dashboard_git/alert_dashboard/www/work_order.php
T

1011 lines
47 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
header("Expires: Thu, 01 Jan 1970 00:00:00 GMT");
require_once 'auth.php';
// 兼容低PHP版本 str_contains
if (!function_exists('str_contains')) {
function str_contains($haystack, $needle) {
return $needle === '' || strpos($haystack, $needle) !== false;
}
}
$expire = 1800;
if (empty($_SESSION['user_id']) || (time() - $_SESSION['login_time'] > $expire)) {
session_destroy();
header("Location: login.php");
exit;
}
$_SESSION['login_time'] = time();
$userName = htmlspecialchars($_SESSION['username']);
$userRoleId = $_SESSION['role_id'];
$loginUid = $_SESSION['user_id'];
// 修复:auth.php 必须输出 $roleRealName,若无则临时赋值避免报错
$roleRealName = $_SESSION['roleRealName'] ?? '';
// 全局配置库连接
$dbConfigHost = "10.150.117.190";
$dbConfigUser = "root";
$dbConfigPwd = "hp93000";
$dbConfigName = "alert_mail_stat";
$connConfig = mysqli_connect($dbConfigHost, $dbConfigUser, $dbConfigPwd, $dbConfigName);
if(!$connConfig){
die("配置库连接失败");
}
mysqli_set_charset($connConfig, "utf8mb4");
$configSql = "SELECT k,v FROM sys_config";
$configRes = mysqli_query($connConfig, $configSql);
$sysConfig = [];
while ($row = mysqli_fetch_assoc($configRes)) {
$sysConfig[$row['k']] = $row['v'];
}
// 修复SMTP查询警告
$smtpRow = [];
$sqlSmtp = "SELECT * FROM sys_smtp_config WHERE id=1";
if (!empty(trim($sqlSmtp))) {
$smtpQuery = mysqli_query($connConfig, $sqlSmtp);
if ($smtpQuery !== false && $smtpQuery instanceof mysqli_result) {
$smtpRow = mysqli_fetch_assoc($smtpQuery) ?: [];
}
}
$smtpConfig = $smtpRow ?? [];
$pageTitle = htmlspecialchars($sysConfig['page_title'] ?? "告警监控系统");
$currentLang = $sysConfig['lang'] ?? "zh";
$sidebarRawLinks = $sysConfig['sidebar_links'] ?? "";
mysqli_close($connConfig);
// 多语言词典(新增未读、待处理文案)
$langMap = [
'zh' => [
'html_lang' => 'zh-CN',
'page_subtitle' => '爱德万测试 · 运维告警监控平台',
'monitor_panel' => '监控面板',
'alert_overview' => '告警总览',
'quick_link' => '快捷外链',
'system_manage' => '系统管理',
'system_config' => '系统参数',
'notify_config' => '工单消息通知配置',
'current_user' => '当前用户:',
'sys_time' => '系统时间:',
'refresh_all' => '刷新页面',
'export_csv' => '导出CSV',
'logout' => '退出登录',
'lang_text' => '语言:',
'work_order_mgr' => '工单系统',
'work_create' => '新建工单',
'work_edit' => '编辑工单',
'work_assign' => '分发工单',
'work_status' => '工单状态',
'work_creator' => '创建人',
'work_handler' => '处理人',
'work_content' => '工单内容/备注',
'work_title' => '工单标题',
'create_time' => '创建时间',
'update_time' => '更新时间',
'all_work_order' => '全部工单列表',
'no_work_data' => '暂无工单数据',
'save' => '保存',
'cancel' => '取消',
'title_placeholder' => '填写故障/需求标题',
'content_placeholder' => '详细描述故障现象、操作步骤、处理方案',
'select_handler' => '选择处理人(留空暂不分配)',
'status_1' => '待处理',
'status_2' => '处理中',
'status_3' => '已完成',
'status_4' => '已关闭',
'no_alert_option' => '不关联任何告警',
'relate_alert' => '关联告警(可选)',
'relate_alert_id' => '关联告警ID',
'notify_title' => '通知配置',
'notify_user' => '通知接收人',
'notify_channel' => '通知渠道',
'channel_mail' => '邮箱',
'channel_ding' => '钉钉',
'channel_wecom' => '企业微信',
'smtp_host' => 'SMTP服务器',
'smtp_port' => '端口',
'smtp_ssl' => '开启SSL/TLS',
'smtp_account' => '发件账号',
'smtp_pwd' => '授权密码',
'smtp_from' => '发件邮箱',
'save_smtp' => '保存通知配置',
'alert_loading' => '加载活跃告警...',
'no_alert_data' => '当前无正在触发的告警',
'user_loading' => '加载用户列表',
'no_user_data' => '无可用用户',
'perm_deny' => '权限不足,禁止访问',
'unread_ticket' => '未读工单',
'mark_all_read' => '全部标记已读',
'claim_ticket' => '认领工单',
'claim_man' => '认领人'
],
'en' => [
'html_lang' => 'en',
'page_subtitle' => 'Advantest · O&M Alert Monitor Platform',
'monitor_panel' => 'Monitor Panel',
'alert_overview' => 'Alert Overview',
'quick_link' => 'Quick Links',
'system_manage' => 'System Manage',
'system_config' => 'System Config',
'notify_config' => 'Work Order Notification Config',
'current_user' => 'User: ',
'sys_time' => 'System Time: ',
'refresh_all' => 'Refresh',
'export_csv' => 'Export CSV',
'logout' => 'Logout',
'lang_text' => 'Lang: ',
'work_order_mgr' => 'Work Order System',
'work_create' => 'Create Order',
'work_edit' => 'Edit Order',
'work_assign' => 'Assign Order',
'work_status' => 'Status',
'work_creator' => 'Creator',
'work_handler' => 'Handler',
'work_content' => 'Content / Remark',
'work_title' => 'Order Title',
'create_time' => 'Create Time',
'update_time' => 'Update Time',
'all_work_order' => 'All Work Orders',
'no_work_data' => 'No work orders',
'save' => 'Save',
'cancel' => 'Cancel',
'title_placeholder' => 'Fill fault or demand title',
'content_placeholder' => 'Describe fault, steps and handling plan',
'select_handler' => 'Select handler (empty for unassigned)',
'status_1' => 'Pending',
'status_2' => 'Processing',
'status_3' => 'Finished',
'status_4' => 'Closed',
'no_alert_option' => 'No related alert',
'relate_alert' => 'Relate Alert (Optional)',
'relate_alert_id' => 'Relate Alert ID',
'notify_title' => 'Notification Config',
'notify_user' => 'Notify Users',
'notify_channel' => 'Notify Channel',
'channel_mail' => 'Email',
'channel_ding' => 'DingTalk',
'channel_wecom' => 'WeCom',
'smtp_host' => 'SMTP Host',
'smtp_port' => 'Port',
'smtp_ssl' => 'Enable SSL/TLS',
'smtp_account' => 'SMTP Account',
'smtp_pwd' => 'Auth Password',
'smtp_from' => 'Sender Email',
'save_smtp' => 'Save Notification Config',
'alert_loading' => 'Loading active alerts...',
'no_alert_data' => 'No active firing alerts',
'user_loading' => 'Loading user list...',
'no_user_data' => 'No available users',
'perm_deny' => 'Permission denied',
'unread_ticket' => 'Unread Tickets',
'mark_all_read' => 'Mark All Read',
'claim_ticket' => 'Claim Ticket',
'claim_man' => 'Claimer'
]
];
$t = $langMap[$currentLang];
// 解析外链菜单
$sidebarLinkList = [];
if (!empty($sidebarRawLinks)) {
$lines = explode("\n", $sidebarRawLinks);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
$item = explode("|", $line, 2);
if (count($item) === 2) {
$sidebarLinkList[] = ["name" => trim($item[0]), "url" => trim($item[1])];
}
}
}
// 工单业务库 monitor
$dbWorkHost = "10.150.117.190";
$dbWorkUser = "root";
$dbWorkPwd = "hp93000";
$dbWorkName = "monitor";
$connWork = mysqli_connect($dbWorkHost, $dbWorkUser, $dbWorkPwd, $dbWorkName);
if (!$connWork) {
die("工单数据库 monitor 连接失败");
}
mysqli_set_charset($connWork, "utf8mb4");
// 获取用户角色权限
$isAdmin = 0;
$permList = "";
$roleId = 0;
$allowPages = [];
$roleSql = "SELECT r.is_admin, r.perm_list, u.role_id FROM sys_user u LEFT JOIN sys_role r ON u.role_id=r.id WHERE u.uid = ?";
$stmtRole = mysqli_prepare($connWork, $roleSql);
if($stmtRole){
mysqli_stmt_bind_param($stmtRole, 's', $loginUid);
mysqli_stmt_execute($stmtRole);
$roleRes = mysqli_stmt_get_result($stmtRole);
if ($roleRes instanceof mysqli_result) {
$roleRow = mysqli_fetch_assoc($roleRes);
if ($roleRow) {
$isAdmin = intval($roleRow['is_admin']);
$permList = $roleRow['perm_list'];
$roleId = intval($roleRow['role_id'] ?? 0);
}
}
}
// 读取当前角色页面权限列表(修复分发下拉判断缺失变量)
if ($roleId > 0) {
$permSql = "SELECT page_key FROM sys_role_perm WHERE role_id = ?";
$stmtPerm = mysqli_prepare($connWork, $permSql);
mysqli_stmt_bind_param($stmtPerm, 'i', $roleId);
mysqli_stmt_execute($stmtPerm);
$pRes = mysqli_stmt_get_result($stmtPerm);
while ($p = mysqli_fetch_assoc($pRes)) {
$allowPages[] = $p['page_key'];
}
}
// 权限校验临时注释,如需开启取消注释
//$hasWorkPerm = str_contains($permList, "work_order");
//if (!$hasWorkPerm) {
// echo "<script>alert('".$t['perm_deny']."');location.href='dashboard.php';</script>";
// exit;
//}
// 加载全部系统用户下拉
$userOptions = "";
$userAll = mysqli_query($connWork, "SELECT uid, real_name, username FROM sys_user WHERE enable = 1 ORDER BY real_name ASC");
if ($userAll instanceof mysqli_result) {
while ($u = mysqli_fetch_assoc($userAll)) {
$userOptions .= '<option value="' . $u['uid'] . '">' . $u['real_name'] . '(' . $u['username'] . ')</option>';
}
}
// ========== 修复1:统计当前用户未读待处理工单(右上角角标数字) ==========
$unreadCount = 0;
$unreadSql = "SELECT COUNT(*) cnt FROM work_order WHERE assign_uid = ? AND status = 1 AND is_read = 0";
$stmtUnread = mysqli_prepare($connWork, $unreadSql);
mysqli_stmt_bind_param($stmtUnread, 's', $loginUid);
mysqli_stmt_execute($stmtUnread);
$unreadRes = mysqli_stmt_get_result($stmtUnread);
if($unreadRes){
$unreadRow = mysqli_fetch_assoc($unreadRes);
$unreadCount = intval($unreadRow['cnt']);
}
// 分页参数
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$pageSize = intval($sysConfig['page_size'] ?? 20);
if ($pageSize < 1) $pageSize = 20;
$offset = ($page - 1) * $pageSize;
// 所有人查看全部工单,不再按uid过滤
$whereSql = " 1=1 ";
$bindType = "ii";
$bindData = [$offset, $pageSize];
// 统计工单总数
$cntSql = "SELECT COUNT(*) total FROM work_order WHERE " . $whereSql;
$stmtCnt = mysqli_prepare($connWork, $cntSql);
$total = 0;
if($stmtCnt){
mysqli_stmt_execute($stmtCnt);
$cntRes = mysqli_stmt_get_result($stmtCnt);
if ($cntRes instanceof mysqli_result) {
$cntRow = mysqli_fetch_assoc($cntRes);
$total = intval($cntRow['total'] ?? 0);
}
}
$totalPage = $pageSize <= 0 ? 1 : ceil($total / $pageSize);
// 查询工单列表,带出认领人信息
$listSql = "SELECT wo.*,
uc.real_name create_name,
ua.real_name assign_name,
uu.real_name claim_name
FROM work_order wo
LEFT JOIN sys_user uc ON wo.create_uid = uc.uid
LEFT JOIN sys_user ua ON wo.assign_uid = ua.uid
LEFT JOIN sys_user uu ON wo.claim_uid = uu.uid
WHERE " . $whereSql . " ORDER BY create_time DESC LIMIT ?, ?";
$stmtList = mysqli_prepare($connWork, $listSql);
$orderList = [];
if($stmtList){
mysqli_stmt_bind_param($stmtList, $bindType, ...$bindData);
mysqli_stmt_execute($stmtList);
$listResult = mysqli_stmt_get_result($stmtList);
if ($listResult instanceof mysqli_result) {
while ($row = mysqli_fetch_assoc($listResult)) {
$orderList[] = $row;
}
}
}
mysqli_close($connWork);
?>
<!DOCTYPE html>
<html lang="<?php echo $t['html_lang']; ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $pageTitle; ?> - Advantest <?php echo $t['work_order_mgr']; ?></title>
<link rel="stylesheet" href="/static/css/font-awesome.min.css">
<script src="/static/js/echarts.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#f9fafc;font-family:"Inter","PingFang SC","Microsoft YaHei";color:#1d2939;line-height:1.6;display:flex;flex-direction:column;height:100vh;overflow:hidden;}
.wrap-main{display:flex;flex:1;overflow:hidden;}
.sidebar{width:240px;background:#152c5b;color:#e2e8f0;flex-shrink:0;overflow-y:auto;}
.sidebar-brand{padding:26px 24px;border-bottom:1px solid rgba(255,255,255,0.1);}
.sidebar-brand-en{font-size:20px;font-weight:700;letter-spacing:1px;color:#fff;}
.sidebar-brand-cn{font-size:13px;color:#a5b4fc;margin-top:4px;}
.sidebar-menu{padding:16px 0;}
.menu-title{padding:10px 24px;font-size:12px;color:#94a3b8;letter-spacing:1px;}
.menu-item{display:flex;align-items:center;gap:12px;padding:14px 24px;cursor:pointer;font-size:14px;transition:0.2s;color:#cbd5e1;}
.menu-item i{font-size:16px;width:20px;text-align:center;}
.menu-item.active{background:rgba(255,255,255,0.12);color:#fff;border-left:4px solid #3b82f6;}
.menu-item:hover:not(.active){background:rgba(255,255,255,0.06);color:#fff;}
.main-content{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
.header{background:#ffffff;border-bottom:1px solid #eef2fb;color:#152c5b;padding:20px 40px;display:flex;justify-content:space-between;align-items:center;box-shadow:0 2px 12px rgba(21,44,91,0.05);flex-shrink:0;}
.header-left{display:flex;align-items:center;gap:14px;}
.header-brand-en{font-size:24px;font-weight:700;letter-spacing:2px;color:#152c5b;}
.header-brand-cn{font-size:15px;color:#64748b;}
.header-right{display:flex;align-items:center;gap:18px;font-size:14px;flex-wrap:wrap;}
.user-info,.time-info,.lang-box{color:#475569;display:flex;align-items:center;gap:6px;}
.lang-box select{padding:4px 8px;border:1px solid #cbd5e1;border-radius:6px;}
/* 消息铃铛角标样式,与dashboard统一 */
.msg-badge{position:relative;cursor:pointer;}
.msg-badge span{position:absolute;top:-5px;right:-5px;background:#ef4444;color:#fff;font-size:10px;border-radius:99px;width:14px;height:14px;text-align:center;line-height:14px;}
.btn-base{border:none;padding:9px 20px;border-radius:8px;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:6px;transition:all 0.2s ease;height:38px;}
.btn-sm{padding:6px 10px;height:32px;font-size:13px;}
.btn-outline{background:#ffffff;color:#152c5b;border:1px solid #cbd5e1;}
.btn-outline:hover{background:#f7f8fc;border-color:#152c5b;}
.btn-primary{background:#152c5b;color:#fff;}
.btn-primary:hover{background:#0f2347;}
.btn-success{background:#10b981;color:#fff;}
.btn-success:hover{background:#059669;}
.mask{position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(255,255,255,0.85);display:none;z-index:1;align-items:center;justify-content:center;}
.mask-box{background:#fff;padding:30px 40px;border-radius:14px;font-size:15px;display:flex;gap:14px;align-items:center;box-shadow:0 8px 30px rgba(21,44,91,0.12);border:1px solid #eef2fb;}
.loading-spin{display:inline-block;width:20px;height:20px;border:2px solid #e2e8f0;border-top-color:#152c5b;border-radius:50%;animation:spin 0.8s linear infinite;}
@keyframes spin{to{transform:rotate(360deg)}}
.container{padding:36px 40px;max-width:100%;flex:1;}
.panel-card{background:#fff;border-radius:16px;padding:32px;box-shadow:0 3px 18px rgba(21,44,91,0.06);border:1px solid #eef2fb;margin-bottom:36px;}
.panel-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:26px;padding-bottom:16px;border-bottom:1px solid #eef2fb;}
.panel-title{display:flex;align-items:center;gap:12px;font-size:19px;font-weight:600;color:#152c5b;}
.panel-title i{color:#3b82f6;font-size:21px}
.table-wrap{overflow-x:auto}
table{width:100%;border-collapse:collapse;font-size:14px}
th{background:#f9fafc;color:#475569;font-weight:600;padding:16px 18px;text-align:left;border-bottom:2px solid #e2e8f0;white-space:nowrap;}
th i{margin-right:6px;font-size:13px;color:#94a3b8}
td{padding:18px;border-bottom:1px solid #f1f5f9;color:#334155;vertical-align:middle;}
tbody tr{transition:background 0.2s}
tbody tr:hover{background:#f7f8fc}
tbody tr:nth-child(even){background:#fbfcfe}
/* 未读行高亮 */
tr.unread-row{background:#fff3f3 !important;}
.empty-tip{text-align:center;padding:80px 20px;color:#94a3b8;font-size:15px;}
.empty-tip i{font-size:56px;margin-bottom:18px;opacity:0.35;}
.modal-mask{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.4);display:none;align-items:center;justify-content:center;z-index:999;pointer-events:none;}
.modal-box{width:720px;background:#fff;border-radius:16px;padding:32px;box-shadow:0 8px 30px rgba(21,44,91,0.12);max-height:90vh;overflow-y:auto;pointer-events:auto;}
.modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:26px;padding-bottom:16px;border-bottom:1px solid #eef2fb;}
.modal-close{cursor:pointer;font-size:22px;color:#94a3b8;}
.form-row{margin-bottom:20px;}
.form-row label{display:block;margin-bottom:8px;color:#475569;font-weight:500;}
.form-row input,.form-row select,.form-row textarea{width:100%;padding:10px 14px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;height:40px;}
.form-row textarea{height:140px;resize:none;}
.btn-group{display:flex;gap:14px;margin-top:10px;}
.select-sm{padding:6px 8px;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;height:32px;}
.status-tag{display:inline-flex;align-items:center;gap:5px;padding:6px 14px;border-radius:24px;font-size:13px;font-weight:500;}
.tag-pending{background:#ffedd5;color:#ea580c}
.tag-processing{background:#dbeafe;color:#2563eb}
.tag-finished{background:#dcfce7;color:#16a34a}
.tag-closed{background:#f1f5f9;color:#64748b}
.pagination{display:flex;justify-content:space-between;align-items:center;margin-top:20px;flex-wrap:wrap;gap:16px;}
.page-btn{padding:8px 16px;border:1px solid #cbd5e1;background:#fff;border-radius:8px;cursor:pointer;font-size:14px;min-width:40px;text-align:center;transition:0.2s;}
.page-btn:hover{background:#f7f8fc;border-color:#152c5b;}
.page-btn.active{background:#152c5b;color:#fff;border-color:#152c5b;}
.page-btn:disabled{opacity:0.5;cursor:not-allowed;}
.checkbox-wrap{display:flex;gap:16px;flex-wrap:wrap;padding:8px 0;}
.checkbox-item{display:flex;align-items:center;gap:6px;padding:8px 12px;border:1px solid #e5e7eb;border-radius:8px;background:#fafafa;min-width:130px;}
.checkbox-item input{width:auto;height:auto;}
.input-readonly{background:#f7f8fc;color:#64748b;}
.divider-top{margin:24px 0 8px;padding-top:16px;border-top:1px solid #eef2fb;}
@media (max-width:768px){
.wrap-main{flex-direction:column;}
.sidebar{width:100%;height:auto}
.header{padding:16px 24px;flex-direction:column;gap:16px;align-items:flex-start}
.header-right{gap:8px;flex-wrap:wrap;}
.container{padding:12px 14px;}
.panel-card{padding:24px}
.pagination{flex-direction:column;align-items:flex-start;}
}
</style>
</head>
<body>
<div class="mask" id="loadingMask">
<div class="mask-box"><span class="loading-spin"></span><span></span></div>
</div>
<div class="wrap-main">
<div class="sidebar">
<div class="sidebar-brand">
<div class="sidebar-brand-en">Advantest</div>
<div class="sidebar-brand-cn"><?php echo $t['page_subtitle']; ?></div>
</div>
<div class="sidebar-menu">
<div class="menu-title"><?php echo $t['monitor_panel']; ?></div>
<div class="menu-item" onclick="location.href='dashboard.php'">
<i class="fa fa-line-chart"></i><span><?php echo $t['alert_overview']; ?></span>
</div>
<div class="menu-item active">
<i class="fa fa-ticket"></i><span><?php echo $t['work_order_mgr']; ?></span>
</div>
<?php if (!empty($sidebarLinkList)): ?>
<div class="menu-title"><?php echo $t['quick_link']; ?></div>
<?php foreach ($sidebarLinkList as $link): ?>
<div class="menu-item" onclick="window.open('<?php echo htmlspecialchars($link['url']); ?>','_blank')">
<i class="fa fa-external-link"></i><span><?php echo htmlspecialchars($link['name']); ?></span>
</div>
<?php endforeach; ?>
<?php endif; ?>
<div class="menu-title"><?php echo $t['system_manage']; ?></div>
<div class="menu-item" onclick="location.href='smtp_config.php'">
<i class="fa fa-bell"></i><span><?php echo $t['notify_config']; ?></span>
</div>
</div>
</div>
<div class="main-content">
<div class="header">
<div class="header-left">
<div>
<div class="header-brand-en">Advantest</div>
<div class="header-brand-cn"><?php echo $t['page_subtitle']; ?></div>
</div>
</div>
<div class="header-right">
<!-- 右上角未读工单铃铛角标,与dashboard统一 -->
<span class="msg-badge" onclick="location.href='work_order.php'">
<i class="fa fa-bell-o"></i>
<?php if($unreadCount > 0):?><span><?php echo $unreadCount; ?></span><?php endif;?>
</span>
<span class="user-info"><i class="fa fa-user-circle"></i><?php echo $t['current_user']; ?><?php echo $userName; ?><?php echo $roleRealName; ?></span>
<span class="time-info"><i class="fa fa-clock-o"></i><span id="nowTime">--</span></span>
<div class="lang-box">
<select id="langSwitch">
<option value="zh" <?php echo $currentLang==='zh'?'selected':''; ?>>中文</option>
<option value="en" <?php echo $currentLang==='en'?'selected':''; ?>>EN</option>
</select>
</div>
<button class="btn-base btn-outline" onclick="location.reload()"><i class="fa fa-refresh"></i><?php echo $t['refresh_all']; ?></button>
<button class="btn-base btn-primary" onclick="location.href='logout.php'"><i class="fa fa-sign-out"></i><?php echo $t['logout']; ?></button>
</div>
</div>
<div class="container">
<div class="panel-card">
<div class="panel-head">
<div class="panel-title"><i class="fa fa-ticket"></i><?php echo $t['all_work_order']; ?></div>
<div style="display:flex;gap:12px;align-items:center;">
<button class="btn-base btn-outline btn-sm" id="markAllRead"><i class="fa fa-check"></i><?php echo $t['mark_all_read']; ?></button>
<button class="btn-base btn-success" id="openCreateModal"><i class="fa fa-plus"></i><?php echo $t['work_create']; ?></button>
</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>ID</th>
<th><i class="fa fa-link"></i><?php echo $t['relate_alert_id']; ?></th>
<th><i class="fa fa-file-text-o"></i><?php echo $t['work_title']; ?></th>
<th><i class="fa fa-user"></i><?php echo $t['work_creator']; ?></th>
<th><i class="fa fa-user-o"></i><?php echo $t['work_handler']; ?></th>
<th><i class="fa fa-tag"></i><?php echo $t['work_status']; ?></th>
<th><i class="fa fa-calendar-o"></i><?php echo $t['create_time']; ?></th>
<th><i class="fa fa-clock-o"></i><?php echo $t['update_time']; ?></th>
<th><i class="fa fa-user-circle-o"></i><?php echo $t['claim_man']; ?></th>
<th>OP</th>
</tr>
</thead>
<tbody>
<?php if (count($orderList) === 0): ?>
<tr>
<td colspan="10">
<div class="empty-tip">
<i class="fa fa-ticket"></i>
<div><?php echo $t['no_work_data']; ?></div>
</div>
</td>
</tr>
<?php else: ?>
<?php foreach ($orderList as $item): ?>
<?php
$statusText = ''; $statusClass = '';
switch ($item['status']) {
case 1:$statusText=$t['status_1'];$statusClass='tag-pending';break;
case 2:$statusText=$t['status_2'];$statusClass='tag-processing';break;
case 3:$statusText=$t['status_3'];$statusClass='tag-finished';break;
case 4:$statusText=$t['status_4'];$statusClass='tag-closed';break;
}
$rowClass = $item['is_read'] == 0 && $item['assign_uid'] == $loginUid ? 'unread-row' : '';
// 操作权限:管理员 或 当前登录用户是认领人
$canOperate = ($isAdmin === 1 || $item['claim_uid'] === $loginUid);
?>
<tr class="<?php echo $rowClass; ?>"
data-id="<?php echo $item['id']; ?>"
data-title="<?php echo htmlspecialchars($item['title']); ?>"
data-content="<?php echo htmlspecialchars($item['content']); ?>"
data-assign="<?php echo $item['assign_uid']; ?>"
data-status="<?php echo $item['status']; ?>"
data-relate="<?php echo htmlspecialchars($item['relate_alert_id'] ?? ''); ?>"
data-notifyuser="<?php echo htmlspecialchars($item['notify_user'] ?? ''); ?>"
data-notifychannel="<?php echo htmlspecialchars($item['notify_channel'] ?? ''); ?>"
data-isread="<?php echo $item['is_read']; ?>">
<td><?php echo $item['id']; ?></td>
<td><?php echo htmlspecialchars($item['relate_alert_id'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($item['title']); ?></td>
<td><?php echo $item['create_name'] ?? '-'; ?></td>
<td><?php echo $item['assign_name'] ?? '-'; ?></td>
<td><span class="status-tag <?php echo $statusClass; ?>"><?php echo $statusText; ?></span></td>
<td><?php echo $item['create_time']; ?></td>
<td><?php echo $item['update_time'] ?? '-'; ?></td>
<!-- 认领人列 -->
<td><?php echo $item['claim_name'] ?? '-'; ?></td>
<!-- 操作栏 -->
<td style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">
<?php if ($isAdmin === 1 || in_array("system_manage", $allowPages)): ?>
<select class="assign-user select-sm">
<option value="">--</option>
<?php echo $userOptions; ?>
</select>
<?php endif; ?>
<?php if (empty($item['claim_uid'])): ?>
<button class="btn-base btn-sm btn-success claim-order" data-id="<?php echo $item['id']; ?>">
<i class="fa fa-hand-paper-o"></i><?php echo $t['claim_ticket']; ?>
</button>
<?php endif; ?>
<select class="status-select select-sm" <?php if(!$canOperate) echo "disabled"; ?>>
<option value="1" <?php echo $item['status']==1?'selected':''; ?>><?php echo $t['status_1']; ?></option>
<option value="2" <?php echo $item['status']==2?'selected':''; ?>><?php echo $t['status_2']; ?></option>
<option value="3" <?php echo $item['status']==3?'selected':''; ?>><?php echo $t['status_3']; ?></option>
<option value="4" <?php echo $item['status']==4?'selected':''; ?>><?php echo $t['status_4']; ?></option>
</select>
<button class="btn-base btn-outline btn-sm edit-order" <?php if(!$canOperate) echo "disabled"; ?>>
<i class="fa fa-edit"></i><?php echo $t['work_edit']; ?>
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="pagination">
<div><?php echo $currentLang==='zh'?'共 ':'Total '; ?><?php echo $total; ?> <?php echo $currentLang==='zh'?'条':'records'; ?></div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
<?php if ($page > 1): ?>
<button class="page-btn" onclick="location.href='work_order.php?page=1'"><?php echo $currentLang==='zh'?'首页':'First' ?></button>
<button class="page-btn" onclick="location.href='work_order.php?page=<?php echo $page-1; ?>'"><?php echo $currentLang==='zh'?'上一页':'Prev' ?></button>
<?php else: ?>
<button class="page-btn" disabled><?php echo $currentLang==='zh'?'首页':'First' ?></button>
<button class="page-btn" disabled><?php echo $currentLang==='zh'?'上一页':'Prev' ?></button>
<?php endif; ?>
<?php
$start = max(1, $page - 2);
$end = min($totalPage, $page + 2);
for ($i = $start; $i <= $end; $i++):
?>
<button class="page-btn <?php echo $i == $page ? 'active' : ''; ?>" onclick="location.href='work_order.php?page=<?php echo $i; ?>'"><?php echo $i; ?></button>
<?php endfor; ?>
<?php if ($page < $totalPage): ?>
<button class="page-btn" onclick="location.href='work_order.php?page=<?php echo $page+1; ?>'"><?php echo $currentLang==='zh'?'下一页':'Next' ?></button>
<button class="page-btn" onclick="location.href='work_order.php?page=<?php echo $totalPage; ?>'"><?php echo $currentLang==='zh'?'末页':'Last' ?></button>
<?php else: ?>
<button class="page-btn" disabled><?php echo $currentLang==='zh'?'下一页':'Next' ?></button>
<button class="page-btn" disabled><?php echo $currentLang==='zh'?'末页':'Last' ?></button>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 新建工单弹窗 -->
<div class="modal-mask" id="createModal">
<div class="modal-box">
<div class="modal-head">
<div class="panel-title"><i class="fa fa-plus"></i><?php echo $t['work_create']; ?></div>
<span class="modal-close" id="closeCreateModal">×</span>
</div>
<div class="form-row">
<label><?php echo $t['relate_alert']; ?></label>
<select id="relateAlertSelect">
<option value="">-- <?php echo $t['no_alert_option']; ?> --</option>
</select>
<div class="form-desc"><?php echo $currentLang==='zh'?'选择后自动填充工单标题与告警基础信息':'Auto fill title and alert info after select'; ?></div>
</div>
<div class="form-row">
<label><?php echo $t['work_title']; ?></label>
<input type="text" id="orderTitle" placeholder="<?php echo $t['title_placeholder']; ?>">
</div>
<div class="form-row">
<label><?php echo $t['work_content']; ?></label>
<textarea id="orderContent" placeholder="<?php echo $t['content_placeholder']; ?>"></textarea>
</div>
<div class="form-row">
<label><?php echo $t['work_handler']; ?></label>
<select id="orderAssign">
<option value=""><?php echo $t['select_handler']; ?></option>
<?php echo $userOptions; ?>
</select>
</div>
<div class="divider-top"></div>
<div class="panel-title" style="font-size:16px;margin-bottom:12px;"><i class="fa fa-bell"></i><?php echo $t['notify_title']; ?></div>
<div class="form-row">
<label><?php echo $t['notify_user']; ?></label>
<select id="notifyUserSelect" multiple style="height:120px;">
<?php echo $userOptions; ?>
</select>
<div class="form-desc"><?php echo $currentLang==='zh'?'按住Ctrl多选通知人员':'Hold Ctrl multi-select users'; ?></div>
</div>
<div class="form-row">
<label><?php echo $t['notify_channel']; ?></label>
<div class="checkbox-wrap">
<div class="checkbox-item">
<input type="checkbox" id="chMail" value="mail">
<label for="chMail"><i class="fa fa-envelope-o"></i> <?php echo $t['channel_mail']; ?></label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="chDing" value="dingtalk">
<label for="chDing"><i class="fa fa-comment"></i> <?php echo $t['channel_ding']; ?></label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="chWecom" value="wecom">
<label for="chWecom"><i class="fa fa-wechat"></i> <?php echo $t['channel_wecom']; ?></label>
</div>
</div>
</div>
<div class="btn-group">
<button class="btn-base btn-primary" id="submitOrder"><i class="fa fa-save"></i><?php echo $t['save']; ?></button>
<button class="btn-base btn-outline" id="resetForm"><i class="fa fa-refresh"></i><?php echo $t['cancel']; ?></button>
</div>
</div>
</div>
<!-- 编辑工单弹窗 -->
<div class="modal-mask" id="editModal">
<div class="modal-box">
<div class="modal-head">
<div class="panel-title"><i class="fa fa-edit"></i><?php echo $t['work_edit']; ?></div>
<span class="modal-close" id="closeEditModal">×</span>
</div>
<input type="hidden" id="editOrderId">
<input type="hidden" id="editRelateAlertId">
<div class="form-row">
<label><?php echo $t['relate_alert']; ?></label>
<select id="editRelateAlertSelect" style="width:100%;">
<option value="">-- <?php echo $t['no_alert_option']; ?> --</option>
</select>
</div>
<div class="form-row">
<label>当前绑定告警ID(只读)</label>
<input type="text" id="editRelateAlertShow" readonly class="input-readonly">
</div>
<div class="form-row">
<label><?php echo $t['work_title']; ?></label>
<input type="text" id="editTitle">
</div>
<div class="form-row">
<label><?php echo $t['work_content']; ?></label>
<textarea id="editContent"></textarea>
</div>
<div class="form-row">
<label><?php echo $t['work_handler']; ?></label>
<select id="editAssign">
<option value="">--</option>
<?php echo $userOptions; ?>
</select>
<script>$("select[name=role_id]").val(<?php echo $editUser['role_id']; ?>)</script>
</div>
<div class="divider-top"></div>
<div class="panel-title" style="font-size:16px;margin-bottom:12px;"><i class="fa fa-bell"></i><?php echo $t['notify_title']; ?></div>
<div class="form-row">
<label><?php echo $t['notify_user']; ?></label>
<select id="editNotifyUser" multiple style="height:120px;">
<?php echo $userOptions; ?>
</select>
</div>
<div class="form-row">
<label><?php echo $t['notify_channel']; ?></label>
<div class="checkbox-wrap">
<div class="checkbox-item"><input type="checkbox" id="editChMail" value="mail"><label for="editChMail"><i class="fa fa-envelope-o"></i> <?php echo $t['channel_mail']; ?></label></div>
<div class="checkbox-item"><input type="checkbox" id="editChDing" value="dingtalk"><label for="editChDing"><i class="fa fa-comment"></i> <?php echo $t['channel_ding']; ?></label></div>
<div class="checkbox-item"><input type="checkbox" id="editChWecom" value="wecom"><label for="editChWecom"><i class="fa fa-wechat"></i> <?php echo $t['channel_wecom']; ?></label></div>
</div>
</div>
<div class="btn-group">
<button class="btn-base btn-primary" id="saveEdit"><i class="fa fa-save"></i><?php echo $t['save']; ?></button>
<button class="btn-base btn-outline" id="cancelEdit"><?php echo $t['cancel']; ?></button>
</div>
</div>
</div>
<script>
const apiAlert = "./api/index.php";
const apiOrder = "./api/work_order_api.php";
const loginUid = "<?php echo $loginUid; ?>";
const isAdmin = <?php echo $isAdmin; ?>;
const currentLang = "<?php echo $currentLang; ?>";
let activeAlertList = [];
$("#loadingMask").hide();
function updateNowTime() {
const d = new Date();
const Y = d.getFullYear();
const M = String(d.getMonth()+1).padStart(2,'0');
const D = String(d.getDate()).padStart(2,'0');
const h = String(d.getHours()).padStart(2,'0');
const m = String(d.getMinutes()).padStart(2,'0');
const s = String(d.getSeconds()).padStart(2,'0');
$("#nowTime").text(`${Y}-${M}-${D} ${h}:${m}:${s}`);
}
setInterval(updateNowTime,1000);
updateNowTime();
$("#langSwitch").change(function(){
const lang = $(this).val();
$.post(apiOrder, {action:"save_lang",lang:lang}, ()=>location.reload());
})
function loadFiringAlert(selSelector, bindId=""){
$.getJSON(apiAlert+"?act=active_firing", function(list){
activeAlertList = list;
const sel = $(selSelector);
sel.html(`<option value="">-- <?php echo $t['no_alert_option']; ?> --</option>`);
list.forEach(item=>{
const aid = `${item.alert_name}_${item.instance}`;
sel.append(`<option value="${aid}" data-name="${item.alert_name}" data-ip="${item.instance}">
[${item.instance}] ${item.alert_name}
</option>`);
})
if(bindId) sel.val(bindId);
}).fail(()=>{
$(selSelector).html(`<option value=""><?php echo $t['no_alert_data']; ?></option>`);
})
}
$("#relateAlertSelect").change(function(){
const opt = $(this).find("option:selected");
const aid = opt.val();
const aname = opt.data("name");
const ip = opt.data("ip");
if(!aid){
$("#orderTitle").val("");
$("#orderContent").val("");
return;
}
const tip = currentLang === "zh"
? `关联告警ID${aid}\n实例IP${ip}\n告警名称:${aname}\n=====故障处理备注=====\n`
: `Relate Alert ID: ${aid}\nInstance IP: ${ip}\nAlert Name: ${aname}\n=====Handling Remark=====\n`;
$("#orderTitle").val(aname);
$("#orderContent").val(tip);
});
$("#openCreateModal").on("click", function(){
$("#createModal").css("display","flex");
loadFiringAlert("#relateAlertSelect");
});
$("#closeCreateModal,#resetForm").click(function(){
$("#relateAlertSelect").val("");
$("#orderTitle").val("");
$("#orderContent").val("");
$("#orderAssign").val("");
$("#notifyUserSelect").val([]);
$("#chMail,#chDing,#chWecom").prop("checked",false);
$("#createModal").hide();
});
$("#closeEditModal,#cancelEdit").click(function(){
$("#editModal").hide();
});
// 编辑工单弹窗填充
$(document).on("click", ".edit-order", function(){
const tr = $(this).closest("tr");
$("#editOrderId").val(tr.data("id"));
$("#editTitle").val(tr.data("title"));
$("#editContent").val(tr.data("content"));
$("#editAssign").val(tr.data("assign"));
const relateAid = tr.data("relate") || "";
$("#editRelateAlertId").val(relateAid);
$("#editRelateAlertShow").val(relateAid || (currentLang==="zh"?"无关联告警":"No related alert"));
const userRaw = tr.data("notifyuser") || "";
const userArr = userRaw.split(",").filter(x=>x);
$("#editNotifyUser").val(userArr);
const chRaw = tr.data("notifychannel") || "";
const chArr = chRaw.split(",").filter(x=>x);
$("#editChMail").prop("checked", chArr.includes("mail"));
$("#editChDing").prop("checked", chArr.includes("dingtalk"));
$("#editChWecom").prop("checked", chArr.includes("wecom"));
loadFiringAlert("#editRelateAlertSelect", relateAid);
$("#editModal").css("display","flex");
});
// 新建工单提交
$("#submitOrder").click(function(){
const title = $("#orderTitle").val().trim();
const content = $("#orderContent").val().trim();
const assign = $("#orderAssign").val();
const relateAlertId = $("#relateAlertSelect").val();
const notifyUserArr = $("#notifyUserSelect").val() || [];
const notifyUser = notifyUserArr.join(",");
let channelArr = [];
if($("#chMail").prop("checked")) channelArr.push("mail");
if($("#chDing").prop("checked")) channelArr.push("dingtalk");
if($("#chWecom").prop("checked")) channelArr.push("wecom");
const notifyChannel = channelArr.join(",");
if(!title) return alert(currentLang==='zh'?"请填写工单标题":"Fill order title");
if(!content) return alert(currentLang==='zh'?"请填写工单内容":"Fill order content");
$.ajax({
url: apiOrder + "?action=create",
method: "POST",
contentType: "application/json",
data: JSON.stringify({
title,
content,
assign_uid: assign,
relate_alert_id: relateAlertId,
notify_user: notifyUser,
notify_channel: notifyChannel
}),
dataType: "json",
success(res){
alert(res.msg);
if(res.code === 0) location.reload();
},
error(){
alert(currentLang==='zh'?"提交失败,接口异常":"Submit failed, api error")
}
})
});
// 编辑保存
$("#saveEdit").click(function(){
const id = $("#editOrderId").val();
const title = $("#editTitle").val().trim();
const content = $("#editContent").val().trim();
const assign = $("#editAssign").val();
const newRelateAid = $("#editRelateAlertSelect").val();
const notifyUserArr = $("#editNotifyUser").val() || [];
const notifyUser = notifyUserArr.join(",");
let channelArr = [];
if($("#editChMail").prop("checked")) channelArr.push("mail");
if($("#editChDing").prop("checked")) channelArr.push("dingtalk");
if($("#editChWecom").prop("checked")) channelArr.push("wecom");
const notifyChannel = channelArr.join(",");
if(!title || !content) return alert(currentLang==='zh'?"标题和内容不能为空":"Title and content required");
$.ajax({
url: apiOrder + "?action=edit",
method: "POST",
contentType: "application/json",
data: JSON.stringify({
id: id,
title: title,
content: content,
assign_uid: assign,
relate_alert_id: newRelateAid,
notify_user: notifyUser,
notify_channel: notifyChannel
}),
dataType: "json",
success(res){
alert(res.msg);
if(res.code === 0) location.reload();
},
error(){
alert(currentLang==='zh'?"保存失败,接口请求异常":"Save failed, api request error")
}
});
});
// 修改工单状态
$(document).on("change", ".status-select", function(){
const tr = $(this).closest("tr");
const orderId = tr.data("id");
const status = $(this).val();
$.ajax({
url: apiOrder + "?action=update_status",
method: "POST",
contentType: "application/json",
data: JSON.stringify({id:orderId,status:status}),
dataType: "json",
success(res){
alert(res.msg);
if(res.code === 0) location.reload();
},
error(){
alert(currentLang==='zh'?"状态更新失败":"Status update failed")
}
});
});
// 管理员分发工单
$(document).on("change", ".assign-user", function(){
const tr = $(this).closest("tr");
const orderId = tr.data("id");
const assignUid = $(this).val();
if(!assignUid) return;
$.ajax({
url: apiOrder + "?action=assign",
method: "POST",
contentType: "application/json",
data: JSON.stringify({id:orderId,assign_uid:assignUid}),
dataType: "json",
success(res){
alert(res.msg);
if(res.code === 0) location.reload();
},
error(){
alert(currentLang==='zh'?"工单分发失败":"Assign order failed")
}
});
});
// 工单认领
$(document).on("click", ".claim-order", function(){
const orderId = $(this).data("id");
if(!confirm(currentLang==="zh"?"确认认领该工单?":"Confirm claim this order?")) return;
$.ajax({
url: apiOrder + "?action=claim_work",
method: "POST",
contentType: "application/json",
data: JSON.stringify({
id: orderId,
uid: loginUid
}),
dataType: "json",
success(res){
alert(res.msg);
if(res.code === 0) location.reload();
},
error(){
alert(currentLang==='zh'?"认领请求失败":"Claim failed");
}
});
});
// 一键标记全部当前用户待处理工单为已读
$("#markAllRead").click(function(){
$.ajax({
url: apiOrder + "?action=mark_all_read",
method: "POST",
contentType: "application/json",
data: JSON.stringify({uid:loginUid}),
dataType: "json",
success(res){
alert(res.msg);
if(res.code === 0) location.reload();
},
error(){
alert(currentLang==='zh'?"标记已读失败":"Mark read failed")
}
})
});
// 点击弹窗空白区域关闭弹窗
$(".modal-mask").click(function(e){
if($(e.target).is(".modal-mask")) $(this).hide();
});
// 页面初始化加载告警下拉数据
$(function(){
loadFiringAlert("#relateAlertSelect");
})
</script>
</body>
</html>