feat: MC Report System - MySQL + Express + Vanilla JS SPA
This commit is contained in:
188
public/js/pages/complaints.js
Normal file
188
public/js/pages/complaints.js
Normal file
@@ -0,0 +1,188 @@
|
||||
const ComplaintsPage = {
|
||||
async render() {
|
||||
return '<div class="loading"><i class="fas fa-spinner"></i><p>加载中...</p></div>';
|
||||
},
|
||||
|
||||
async mount() {
|
||||
const container = document.getElementById('page-content');
|
||||
document.getElementById('page-title').textContent = '投诉列表';
|
||||
document.getElementById('page-actions').innerHTML = '<button class="btn btn-primary" onclick="ComplaintsPage.showCreateModal()"><i class="fas fa-plus"></i> 提交投诉</button>';
|
||||
|
||||
await this.loadList(container);
|
||||
},
|
||||
|
||||
async loadList(container, filters = {}) {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.status) params.set('status', filters.status);
|
||||
if (filters.priority) params.set('priority', filters.priority);
|
||||
if (filters.category) params.set('category', filters.category);
|
||||
if (filters.search) params.set('search', filters.search);
|
||||
const qs = params.toString();
|
||||
|
||||
container.innerHTML = '<div class="loading"><i class="fas fa-spinner"></i><p>加载中...</p></div>';
|
||||
|
||||
try {
|
||||
const complaints = await API.get('/complaints' + (qs ? '?' + qs : ''));
|
||||
this.renderList(container, complaints, filters);
|
||||
} catch (err) {
|
||||
container.innerHTML = `<div class="alert alert-danger">加载失败: ${err.message}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
renderList(container, complaints, filters = {}) {
|
||||
container.innerHTML = `
|
||||
<div class="filter-bar">
|
||||
<select id="filter-status" onchange="ComplaintsPage.applyFilters()">
|
||||
<option value="">全部状态</option>
|
||||
${Object.entries(Utils.statusLabels).map(([k,v]) => `<option value="${k}" ${filters.status === k ? 'selected' : ''}>${v}</option>`).join('')}
|
||||
</select>
|
||||
<select id="filter-priority" onchange="ComplaintsPage.applyFilters()">
|
||||
<option value="">全部优先级</option>
|
||||
${Object.entries(Utils.priorityLabels).map(([k,v]) => `<option value="${k}" ${filters.priority === k ? 'selected' : ''}>${v}</option>`).join('')}
|
||||
</select>
|
||||
<select id="filter-category" onchange="ComplaintsPage.applyFilters()">
|
||||
<option value="">全部分类</option>
|
||||
${Object.entries(Utils.categoryLabels).map(([k,v]) => `<option value="${k}" ${filters.category === k ? 'selected' : ''}>${v}</option>`).join('')}
|
||||
</select>
|
||||
<input id="filter-search" placeholder="搜索标题/描述/玩家..." value="${filters.search || ''}" onkeyup="if(event.key==='Enter')ComplaintsPage.applyFilters()">
|
||||
<button class="btn btn-outline btn-sm" onclick="ComplaintsPage.applyFilters()"><i class="fas fa-search"></i> 搜索</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="ComplaintsPage.resetFilters()"><i class="fas fa-undo"></i> 重置</button>
|
||||
</div>
|
||||
|
||||
${complaints.length === 0 ? `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<p>暂无投诉记录</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="card">
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>标题</th>
|
||||
<th>玩家</th>
|
||||
<th>分类</th>
|
||||
<th>优先级</th>
|
||||
<th>状态</th>
|
||||
<th>规划者</th>
|
||||
<th>实施者</th>
|
||||
<th>审查者</th>
|
||||
<th>更新时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${complaints.map(c => `
|
||||
<tr>
|
||||
<td>${c.id}</td>
|
||||
<td><strong><a href="#" onclick="App.navigate('complaint',${c.id});return false" style="color:var(--primary);text-decoration:none">${Utils.escapeHtml(c.title)}</a></strong></td>
|
||||
<td>${Utils.escapeHtml(c.player_name)}</td>
|
||||
<td>${Utils.categoryBadge(c.category)}</td>
|
||||
<td>${Utils.priorityBadge(c.priority)}</td>
|
||||
<td>${Utils.statusBadge(c.status)}</td>
|
||||
<td>${c.planner_name || '-'}</td>
|
||||
<td>${c.implementer_name || '-'}</td>
|
||||
<td>${c.reviewer_name || '-'}</td>
|
||||
<td class="date">${Utils.formatDate(c.updated_at)}</td>
|
||||
<td><button class="btn btn-sm btn-outline" onclick="App.navigate('complaint',${c.id})"><i class="fas fa-eye"></i></button></td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
},
|
||||
|
||||
applyFilters() {
|
||||
const filters = {
|
||||
status: document.getElementById('filter-status').value,
|
||||
priority: document.getElementById('filter-priority').value,
|
||||
category: document.getElementById('filter-category').value,
|
||||
search: document.getElementById('filter-search').value,
|
||||
};
|
||||
const container = document.getElementById('page-content');
|
||||
this.loadList(container, filters);
|
||||
},
|
||||
|
||||
resetFilters() {
|
||||
const container = document.getElementById('page-content');
|
||||
this.loadList(container, {});
|
||||
},
|
||||
|
||||
showCreateModal() {
|
||||
Utils.openModal('提交投诉', `
|
||||
<form id="create-complaint-form">
|
||||
<div class="form-group">
|
||||
<label>标题 *</label>
|
||||
<input type="text" id="c-title" required placeholder="投诉标题">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>描述 *</label>
|
||||
<textarea id="c-description" required placeholder="请详细描述问题..." rows="4"></textarea>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>分类</label>
|
||||
<select id="c-category">
|
||||
${Object.entries(Utils.categoryLabels).map(([k,v]) => `<option value="${k}">${v}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>优先级</label>
|
||||
<select id="c-priority">
|
||||
<option value="low">低</option>
|
||||
<option value="medium" selected>中</option>
|
||||
<option value="high">高</option>
|
||||
<option value="critical">紧急</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>玩家名称 *</label>
|
||||
<input type="text" id="c-player_name" required placeholder="游戏内昵称">
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>邮箱</label>
|
||||
<input type="email" id="c-player_email" placeholder="选填">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>联系方式</label>
|
||||
<input type="text" id="c-player_contact" placeholder="手机/微信/QQ">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="padding:16px 0 0;border-top:1px solid var(--gray-200)">
|
||||
<button type="button" class="btn btn-outline" onclick="App.closeModal()">取消</button>
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> 提交</button>
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
document.getElementById('create-complaint-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = e.target.querySelector('button[type="submit"]');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 提交中...';
|
||||
try {
|
||||
await API.post('/complaints', {
|
||||
title: document.getElementById('c-title').value,
|
||||
description: document.getElementById('c-description').value,
|
||||
category: document.getElementById('c-category').value,
|
||||
priority: document.getElementById('c-priority').value,
|
||||
player_name: document.getElementById('c-player_name').value,
|
||||
player_email: document.getElementById('c-player_email').value,
|
||||
player_contact: document.getElementById('c-player_contact').value,
|
||||
});
|
||||
App.closeModal();
|
||||
this.mount();
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane"></i> 提交';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user