396 lines
20 KiB
HTML
396 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>消息中心 - OA系统</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
|
||
<!-- 统一布局样式 -->
|
||
<link rel="stylesheet" href="unified-layout.css">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
|
||
background: #f0f2f5;
|
||
overflow: hidden;
|
||
}
|
||
.layout-container { height: 100vh; display: flex; flex-direction: column; }
|
||
.navbar {
|
||
height: 50px;
|
||
background: #fff;
|
||
border-bottom: 1px solid #e6e6e6;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 20px;
|
||
box-shadow: 0 1px 4px rgba(0,21,41,.08);
|
||
}
|
||
.navbar-left { display: flex; align-items: center; }
|
||
.logo { font-size: 20px; font-weight: bold; color: #409EFF; cursor: pointer; }
|
||
.nav-menu { display: flex; gap: 8px; margin-left: 16px; }
|
||
.nav-item { padding: 8px 16px; cursor: pointer; border-radius: 4px; color: #303133; }
|
||
.nav-item:hover { background: #ecf5ff; color: #409EFF; }
|
||
.nav-item.active { background: #409EFF; color: #fff; }
|
||
.navbar-right { display: flex; align-items: center; gap: 20px; }
|
||
.content { flex: 1; padding: 20px; overflow-y: auto; }
|
||
.grid { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; }
|
||
.card {
|
||
background: #fff;
|
||
border: 1px solid #e6e6e6;
|
||
border-radius: 4px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||
padding: 20px;
|
||
}
|
||
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||
.card-title { font-size: 16px; font-weight: 500; color: #303133; }
|
||
.filters { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
|
||
.form-input, .form-select {
|
||
height: 36px;
|
||
padding: 0 10px;
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
min-width: 150px;
|
||
}
|
||
.btn { padding: 8px 14px; border: none; border-radius: 4px; cursor: pointer; }
|
||
.btn-primary { background: #409EFF; color: #fff; }
|
||
.btn-default { background: #fff; color: #303133; border: 1px solid #dcdfe6; }
|
||
.tabs { display: flex; gap: 10px; margin-bottom: 10px; }
|
||
.tab { padding: 10px 14px; background: #f5f7fa; border: 1px solid #e4e7ed; border-radius: 4px; cursor: pointer; color: #606266; }
|
||
.tab.active { background: #409EFF; color: #fff; border-color: #409EFF; }
|
||
.table { width: 100%; border-collapse: collapse; }
|
||
.table th, .table td { border: 1px solid #ebeef5; padding: 10px 12px; color: #606266; font-size: 14px; }
|
||
.table th { background: #f5f7fa; text-align: left; }
|
||
.status-tag { padding: 2px 8px; border-radius: 10px; font-size: 12px; color: #fff; }
|
||
.status-unread { background: #f56c6c; }
|
||
.status-read { background: #67c23a; }
|
||
.tag { display: inline-block; padding: 2px 8px; border-radius: 10px; background: #ecf5ff; color: #409EFF; font-size: 12px; margin-right: 6px; }
|
||
.setting-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #f0f0f0; }
|
||
.setting-row:last-child { border-bottom: none; }
|
||
.switch { width: 46px; height: 24px; border-radius: 12px; background: #dcdfe6; position: relative; cursor: pointer; transition: all 0.2s; }
|
||
.switch.on { background: #409EFF; }
|
||
.switch-circle { width: 20px; height: 20px; background: #fff; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: all 0.2s; }
|
||
.switch.on .switch-circle { left: 24px; }
|
||
.channel-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
|
||
.pill { padding: 4px 10px; border-radius: 12px; border: 1px solid #dcdfe6; color: #606266; cursor: pointer; }
|
||
.pill.active { background: #409EFF; color: #fff; border-color: #409EFF; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<div class="layout-container">
|
||
<div class="navbar">
|
||
<div class="navbar-left">
|
||
<div class="logo" onclick="goHome()">OA系统</div>
|
||
<div class="nav-menu">
|
||
<div class="nav-item" onclick="window.location.href='dashboard.html'">首页</div>
|
||
<div class="nav-item" onclick="window.location.href='project-initiation.html'">商机管理</div>
|
||
<div class="nav-item" onclick="window.location.href='project-start.html'">过程管理</div>
|
||
<div class="nav-item" onclick="window.location.href='finance-invoice.html'">财务管理</div>
|
||
<div class="nav-item" onclick="window.location.href='report-project-detail.html'">报表管理</div>
|
||
<div class="nav-item active">消息中心</div>
|
||
</div>
|
||
</div>
|
||
<div class="navbar-right">
|
||
<span style="color: #303133;">张三</span><span style="color: #909399; margin: 0 8px;">|</span>
|
||
<a href="profile.html" style="color: #409EFF; text-decoration: none;">个人中心</a>
|
||
<span style="color: #909399;">|</span>
|
||
<a href="login.html" style="color: #409EFF; text-decoration: none;">退出</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<div class="grid">
|
||
<!-- 消息列表 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<div class="card-title">消息中心</div>
|
||
<div style="display:flex;gap:8px;">
|
||
<button class="btn btn-default" onclick="markAllRead()">全部设为已读</button>
|
||
<button class="btn btn-default" onclick="exportMsg()">导出</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<div class="tab active" id="tab-all" onclick="switchTab('all')">全部</div>
|
||
<div class="tab" id="tab-approval" onclick="switchTab('approval')">审批提醒</div>
|
||
<div class="tab" id="tab-announcement" onclick="switchTab('announcement')">公告</div>
|
||
<div class="tab" id="tab-system" onclick="switchTab('system')">系统通知</div>
|
||
</div>
|
||
|
||
<div class="filters">
|
||
<select id="filter-status" class="form-select">
|
||
<option value="">阅读状态</option>
|
||
<option value="unread">未读</option>
|
||
<option value="read">已读</option>
|
||
</select>
|
||
<input type="date" id="filter-start" class="form-input">
|
||
<span style="align-self:center;color:#909399;">至</span>
|
||
<input type="date" id="filter-end" class="form-input">
|
||
<input type="text" id="filter-keyword" class="form-input" placeholder="标题/内容/来源">
|
||
<button class="btn btn-primary" onclick="applyFilters()">查询</button>
|
||
<button class="btn btn-default" onclick="resetFilters()">重置</button>
|
||
</div>
|
||
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:40px;"><input type="checkbox" id="select-all" onchange="toggleAll(this)"></th>
|
||
<th>标题</th>
|
||
<th>类型</th>
|
||
<th>状态</th>
|
||
<th>时间</th>
|
||
<th style="width:120px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="msg-body"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 提醒策略 -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<div class="card-title">提醒策略</div>
|
||
<button class="btn btn-primary" onclick="saveStrategy()">保存策略</button>
|
||
</div>
|
||
<div style="font-size:13px;color:#909399;margin-bottom:8px;">设置渠道、时间窗和频率,避免打扰</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div style="color:#303133;">审批提醒</div>
|
||
<div style="color:#909399;font-size:13px;">待办、驳回、超时提醒</div>
|
||
</div>
|
||
<div class="channel-row">
|
||
<div class="pill active" data-key="approval" data-channel="site" onclick="toggleChannel(this)">站内信</div>
|
||
<div class="pill" data-key="approval" data-channel="email" onclick="toggleChannel(this)">邮件</div>
|
||
<div class="pill" data-key="approval" data-channel="wechat" onclick="toggleChannel(this)">企业微信</div>
|
||
</div>
|
||
<div class="switch on" data-key="approval" onclick="toggleSwitch(this)">
|
||
<div class="switch-circle"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div style="color:#303133;">公告推送</div>
|
||
<div style="color:#909399;font-size:13px;">紧急公告可外发,普通公告仅站内</div>
|
||
</div>
|
||
<div class="channel-row">
|
||
<div class="pill active" data-key="announcement" data-channel="site" onclick="toggleChannel(this)">站内信</div>
|
||
<div class="pill" data-key="announcement" data-channel="email" onclick="toggleChannel(this)">邮件</div>
|
||
</div>
|
||
<div class="switch on" data-key="announcement" onclick="toggleSwitch(this)">
|
||
<div class="switch-circle"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div style="color:#303133;">系统通知</div>
|
||
<div style="color:#909399;font-size:13px;">安全提示、导出完成、批处理结果</div>
|
||
</div>
|
||
<div class="channel-row">
|
||
<div class="pill active" data-key="system" data-channel="site" onclick="toggleChannel(this)">站内信</div>
|
||
<div class="pill" data-key="system" data-channel="email" onclick="toggleChannel(this)">邮件</div>
|
||
</div>
|
||
<div class="switch on" data-key="system" onclick="toggleSwitch(this)">
|
||
<div class="switch-circle"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div style="color:#303133;">时间窗</div>
|
||
<div style="color:#909399;font-size:13px;">避免夜间打扰,紧急消息除外</div>
|
||
</div>
|
||
<div style="display:flex;gap:6px;align-items:center;">
|
||
<input type="time" id="quiet-start" class="form-input" value="22:00">
|
||
<span style="color:#909399;">至</span>
|
||
<input type="time" id="quiet-end" class="form-input" value="08:00">
|
||
</div>
|
||
<div class="switch on" data-key="quiet" onclick="toggleSwitch(this)">
|
||
<div class="switch-circle"></div>
|
||
</div>
|
||
</div>
|
||
<div class="setting-row">
|
||
<div>
|
||
<div style="color:#303133;">频率限制</div>
|
||
<div style="color:#909399;font-size:13px;">同一事项在30分钟内只提醒一次</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:6px;">
|
||
<input type="number" id="freq-minute" class="form-input" style="width:90px;" value="30" min="5">
|
||
<span style="color:#909399;">分钟</span>
|
||
</div>
|
||
<div class="switch on" data-key="freq" onclick="toggleSwitch(this)">
|
||
<div class="switch-circle"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script src="common.js"></script>
|
||
<script src="unified-layout.js"></script>
|
||
<script>
|
||
const messages = [
|
||
{ id: 'MSG-001', title: '您有1条待办:立项审批', type: 'approval', content: '项目 25-1 XX工程进入部门审核', status: 'unread', time: '2024-12-02 10:20' },
|
||
{ id: 'MSG-002', title: '公告:年终安全检查', type: 'announcement', content: '12月安全巡检安排', status: 'unread', time: '2024-12-01 09:00' },
|
||
{ id: 'MSG-003', title: '系统通知:导出完成', type: 'system', content: '审批中心列表导出完成', status: 'read', time: '2024-11-30 18:10' }
|
||
];
|
||
let activeTab = 'all';
|
||
let filtered = [...messages];
|
||
|
||
function switchTab(tab) {
|
||
activeTab = tab;
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
document.getElementById(`tab-${tab}`).classList.add('active');
|
||
applyFilters();
|
||
}
|
||
|
||
function applyFilters() {
|
||
const status = document.getElementById('filter-status').value;
|
||
const start = document.getElementById('filter-start').value;
|
||
const end = document.getElementById('filter-end').value;
|
||
const kw = document.getElementById('filter-keyword').value.trim();
|
||
filtered = messages.filter(m => {
|
||
if (activeTab !== 'all' && m.type !== activeTab) return false;
|
||
if (status && m.status !== status) return false;
|
||
if (start && new Date(m.time) < new Date(start)) return false;
|
||
if (end && new Date(m.time) > new Date(end + ' 23:59:59')) return false;
|
||
if (kw && !(m.title.includes(kw) || m.content.includes(kw))) return false;
|
||
return true;
|
||
});
|
||
renderTable();
|
||
}
|
||
|
||
function resetFilters() {
|
||
document.getElementById('filter-status').value = '';
|
||
document.getElementById('filter-start').value = '';
|
||
document.getElementById('filter-end').value = '';
|
||
document.getElementById('filter-keyword').value = '';
|
||
applyFilters();
|
||
}
|
||
|
||
function toggleAll(checkbox) {
|
||
document.querySelectorAll('.row-check').forEach(cb => cb.checked = checkbox.checked);
|
||
}
|
||
|
||
function statusTag(status) {
|
||
return status === 'unread'
|
||
? '<span class="status-tag status-unread">未读</span>'
|
||
: '<span class="status-tag status-read">已读</span>';
|
||
}
|
||
|
||
function renderTable() {
|
||
const tbody = document.getElementById('msg-body');
|
||
tbody.innerHTML = '';
|
||
filtered.forEach(m => {
|
||
const tr = document.createElement('tr');
|
||
tr.innerHTML = `
|
||
<td><input type="checkbox" class="row-check" value="${m.id}"></td>
|
||
<td>
|
||
<div style="font-weight: ${m.status === 'unread' ? '600' : '400'}; color:#303133;">${m.title}</div>
|
||
<div style="color:#909399;font-size:12px;margin-top:4px;">${m.content}</div>
|
||
</td>
|
||
<td>
|
||
${m.type === 'approval' ? '<span class="tag">审批</span>' : ''}
|
||
${m.type === 'announcement' ? '<span class="tag">公告</span>' : ''}
|
||
${m.type === 'system' ? '<span class="tag">系统</span>' : ''}
|
||
</td>
|
||
<td>${statusTag(m.status)}</td>
|
||
<td>${m.time}</td>
|
||
<td>
|
||
${m.status === 'unread' ? `<span class="btn btn-default" style="padding:6px 10px;" onclick="markRead('${m.id}')">标为已读</span>` : ''}
|
||
<span class="btn btn-default" style="padding:6px 10px;" onclick="viewMsg('${m.id}')">查看</span>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(tr);
|
||
});
|
||
document.getElementById('select-all').checked = false;
|
||
}
|
||
|
||
function getSelectedIds() {
|
||
return Array.from(document.querySelectorAll('.row-check'))
|
||
.filter(cb => cb.checked)
|
||
.map(cb => cb.value);
|
||
}
|
||
|
||
function markRead(id) {
|
||
const item = messages.find(m => m.id === id);
|
||
if (item) item.status = 'read';
|
||
CommonUtils.showMessage('已标记为已读');
|
||
applyFilters();
|
||
}
|
||
|
||
function markAllRead() {
|
||
messages.forEach(m => m.status = 'read');
|
||
CommonUtils.showMessage('全部已读');
|
||
applyFilters();
|
||
}
|
||
|
||
function viewMsg(id) {
|
||
const item = messages.find(m => m.id === id);
|
||
if (!item) return;
|
||
const tag = item.type === 'approval' ? '审批' : item.type === 'announcement' ? '公告' : '系统';
|
||
CommonUtils.createModal('消息详情', `
|
||
<div style="margin-bottom:8px;font-weight:600;">${item.title}</div>
|
||
<div style="color:#909399;font-size:13px;margin-bottom:12px;">类型:${tag} | 时间:${item.time}</div>
|
||
<div style="line-height:1.6;color:#303133;">${item.content}</div>
|
||
`, () => true);
|
||
item.status = 'read';
|
||
renderTable();
|
||
}
|
||
|
||
function exportMsg() {
|
||
CommonUtils.exportExcel('消息中心.xlsx');
|
||
}
|
||
|
||
// 提醒策略交互
|
||
const channelState = {
|
||
approval: new Set(['site']),
|
||
announcement: new Set(['site']),
|
||
system: new Set(['site'])
|
||
};
|
||
const switchState = {
|
||
approval: true,
|
||
announcement: true,
|
||
system: true,
|
||
quiet: true,
|
||
freq: true
|
||
};
|
||
|
||
function toggleSwitch(el) {
|
||
const key = el.getAttribute('data-key');
|
||
switchState[key] = !switchState[key];
|
||
el.classList.toggle('on', switchState[key]);
|
||
}
|
||
|
||
function toggleChannel(el) {
|
||
const key = el.getAttribute('data-key');
|
||
const channel = el.getAttribute('data-channel');
|
||
const set = channelState[key];
|
||
if (set.has(channel)) {
|
||
set.delete(channel);
|
||
el.classList.remove('active');
|
||
} else {
|
||
set.add(channel);
|
||
el.classList.add('active');
|
||
}
|
||
}
|
||
|
||
function saveStrategy() {
|
||
const quietStart = document.getElementById('quiet-start').value;
|
||
const quietEnd = document.getElementById('quiet-end').value;
|
||
const freq = document.getElementById('freq-minute').value;
|
||
if (freq && Number(freq) < 5) {
|
||
CommonUtils.showMessage('频率限制不得低于5分钟', 'error');
|
||
return;
|
||
}
|
||
CommonUtils.showMessage('提醒策略已保存');
|
||
}
|
||
|
||
function goHome() { window.location.href = 'dashboard.html'; }
|
||
initUnifiedLayout('message');
|
||
// 初始化
|
||
renderTable();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|