chore: remove stale files from v2
This commit is contained in:
@@ -1,322 +0,0 @@
|
||||
const ComplaintDetailPage = {
|
||||
complaintId: null,
|
||||
|
||||
async render(id) {
|
||||
this.complaintId = id;
|
||||
return '<div class="loading"><i class="fas fa-spinner"></i><p>加载中...</p></div>';
|
||||
},
|
||||
|
||||
async mount() {
|
||||
const container = document.getElementById('page-content');
|
||||
const backBtn = '<button class="btn btn-outline btn-sm" onclick="App.navigate(\'complaints\')"><i class="fas fa-arrow-left"></i> 返回</button>';
|
||||
document.getElementById('page-title').textContent = `投诉 #${this.complaintId}`;
|
||||
document.getElementById('page-actions').innerHTML = backBtn;
|
||||
|
||||
try {
|
||||
const complaint = await API.get(`/complaints/${this.complaintId}`);
|
||||
this.renderDetail(container, complaint);
|
||||
} catch (err) {
|
||||
container.innerHTML = `<div class="alert alert-danger">加载失败: ${err.message}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
renderDetail(container, c) {
|
||||
const user = Auth.getUser();
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="detail-grid">
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span>基本信息</span>
|
||||
<div>${Utils.statusBadge(c.status)} ${Utils.priorityBadge(c.priority)}</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-row"><span class="label">标题</span><span class="value font-bold">${Utils.escapeHtml(c.title)}</span></div>
|
||||
<div class="detail-row"><span class="label">描述</span><span class="value">${Utils.escapeHtml(c.description)}</span></div>
|
||||
<div class="detail-row"><span class="label">分类</span><span class="value">${Utils.categoryBadge(c.category)}</span></div>
|
||||
<div class="detail-row"><span class="label">玩家</span><span class="value">${Utils.escapeHtml(c.player_name)}</span></div>
|
||||
${c.player_email ? `<div class="detail-row"><span class="label">邮箱</span><span class="value">${Utils.escapeHtml(c.player_email)}</span></div>` : ''}
|
||||
${c.player_contact ? `<div class="detail-row"><span class="label">联系方式</span><span class="value">${Utils.escapeHtml(c.player_contact)}</span></div>` : ''}
|
||||
<div class="detail-row"><span class="label">创建时间</span><span class="value">${Utils.formatDate(c.created_at)}</span></div>
|
||||
<div class="detail-row"><span class="label">更新时间</span><span class="value">${Utils.formatDate(c.updated_at)}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${c.plan ? this.renderPlanSection(c) : ''}
|
||||
${c.implementation ? this.renderImplementationSection(c) : ''}
|
||||
${c.review ? this.renderReviewSection(c) : ''}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">沟通记录</div>
|
||||
<div class="card-body">
|
||||
${c.responses && c.responses.length > 0 ? `
|
||||
<div class="timeline">
|
||||
${c.responses.map(r => `
|
||||
<div class="timeline-item ${r.response_type === 'system' ? 'system' : ''}">
|
||||
<div class="timeline-header">
|
||||
<span class="name">${Utils.escapeHtml(r.user_name) || '系统'}</span>
|
||||
${r.user_role ? `<span class="badge badge-${r.user_role}">${Utils.roleLabels[r.user_role]}</span>` : ''}
|
||||
<span>${Utils.formatDate(r.created_at)}</span>
|
||||
</div>
|
||||
<div class="timeline-content">${Utils.escapeHtml(r.content)}</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : '<p class="text-muted">暂无记录</p>'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">添加回复</div>
|
||||
<div class="card-body">
|
||||
<form id="add-response-form">
|
||||
<div class="form-group">
|
||||
<textarea id="response-content" rows="3" placeholder="输入回复内容..." required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm"><i class="fas fa-reply"></i> 发送</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-header">人员分配</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-row"><span class="label">规划者</span><span class="value">${c.planner_name || '<span class="text-muted">未分配</span>'}</span></div>
|
||||
<div class="detail-row"><span class="label">实施者</span><span class="value">${c.implementer_name || '<span class="text-muted">未分配</span>'}</span></div>
|
||||
<div class="detail-row"><span class="label">审查者</span><span class="value">${c.reviewer_name || '<span class="text-muted">未分配</span>'}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">操作</div>
|
||||
<div class="card-body" style="display:flex;flex-direction:column;gap:8px">
|
||||
${user && (user.role === 'planner' || user.role === 'admin') && !c.plan ? `
|
||||
<button class="btn btn-primary btn-sm w-full" onclick="ComplaintDetailPage.createPlan(${c.id})">
|
||||
<i class="fas fa-clipboard-list"></i> 创建处理计划
|
||||
</button>
|
||||
` : ''}
|
||||
${user && (user.role === 'planner' || user.role === 'admin') && c.plan && c.plan.status === 'draft' ? `
|
||||
<button class="btn btn-success btn-sm w-full" onclick="ComplaintDetailPage.assignImplementer(${c.id}, ${c.plan.id})">
|
||||
<i class="fas fa-user-tag"></i> 分配实施者
|
||||
</button>
|
||||
` : ''}
|
||||
${user && (user.role === 'implementer' || user.role === 'admin') && c.plan && c.implementer_name === user.display_name && !c.implementation ? `
|
||||
<button class="btn btn-info btn-sm w-full" onclick="ComplaintDetailPage.startImplementation(${c.id}, ${c.plan.id})">
|
||||
<i class="fas fa-code"></i> 开始实施
|
||||
</button>
|
||||
` : ''}
|
||||
${user && (user.role === 'reviewer' || user.role === 'admin') && c.implementation && c.implementation.status === 'completed' && !c.review ? `
|
||||
<button class="btn btn-warning btn-sm w-full" onclick="ComplaintDetailPage.startReview(${c.id}, ${c.implementation.id})">
|
||||
<i class="fas fa-search"></i> 开始审查
|
||||
</button>
|
||||
` : ''}
|
||||
${user && (user.role === 'admin') ? `
|
||||
<button class="btn btn-outline btn-sm w-full" onclick="ComplaintDetailPage.showUpdateStatusModal(${c.id})">
|
||||
<i class="fas fa-edit"></i> 修改状态
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('add-response-form')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const content = document.getElementById('response-content').value.trim();
|
||||
if (!content) return;
|
||||
try {
|
||||
await API.post(`/complaints/${c.id}/response`, { content });
|
||||
document.getElementById('response-content').value = '';
|
||||
this.mount();
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renderPlanSection(c) {
|
||||
const p = c.plan;
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span>处理计划</span>
|
||||
<span>${Utils.statusBadge(p.status)}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-row"><span class="label">标题</span><span class="value font-bold">${Utils.escapeHtml(p.title)}</span></div>
|
||||
<div class="detail-row"><span class="label">描述</span><span class="value">${Utils.escapeHtml(p.description)}</span></div>
|
||||
${p.technical_approach ? `<div class="detail-row"><span class="label">技术方案</span><span class="value" style="white-space:pre-wrap">${Utils.escapeHtml(p.technical_approach)}</span></div>` : ''}
|
||||
${p.expected_effort ? `<div class="detail-row"><span class="label">预计工时</span><span class="value">${p.expected_effort}</span></div>` : ''}
|
||||
${p.reject_reason ? `<div class="detail-row"><span class="label">驳回原因</span><span class="value" style="color:var(--danger)">${Utils.escapeHtml(p.reject_reason)}</span></div>` : ''}
|
||||
<div class="detail-row"><span class="label">规划者</span><span class="value">${p.planner_name || '-'}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
renderImplementationSection(c) {
|
||||
const i = c.implementation;
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span>实施情况</span>
|
||||
<span>${Utils.statusBadge(i.status)}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${i.code_reference ? `<div class="detail-row"><span class="label">代码参考</span><span class="value" style="white-space:pre-wrap">${Utils.escapeHtml(i.code_reference)}</span></div>` : ''}
|
||||
${i.implementation_notes ? `<div class="detail-row"><span class="label">实施说明</span><span class="value" style="white-space:pre-wrap">${Utils.escapeHtml(i.implementation_notes)}</span></div>` : ''}
|
||||
${i.reject_reason ? `<div class="detail-row"><span class="label">驳回原因</span><span class="value" style="color:var(--danger)">${Utils.escapeHtml(i.reject_reason)}</span></div>` : ''}
|
||||
<div class="detail-row"><span class="label">实施者</span><span class="value">${i.implementer_name || '-'}</span></div>
|
||||
${i.status === 'in_progress' ? `
|
||||
<div class="mt-4">
|
||||
<form id="complete-implementation-form">
|
||||
<div class="form-group">
|
||||
<label>代码参考/PR链接</label>
|
||||
<input type="text" id="impl-code" placeholder="Git提交号/PR链接等">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>实施说明</label>
|
||||
<textarea id="impl-notes" rows="3" placeholder="实施过程中的说明..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success btn-sm"><i class="fas fa-check"></i> 标记完成</button>
|
||||
</form>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
renderReviewSection(c) {
|
||||
const r = c.review;
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span>审查结果</span>
|
||||
<span>${Utils.statusBadge(r.status)}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${r.review_notes ? `<div class="detail-row"><span class="label">审查意见</span><span class="value" style="white-space:pre-wrap">${Utils.escapeHtml(r.review_notes)}</span></div>` : ''}
|
||||
${r.issues_found ? `<div class="detail-row"><span class="label">发现的问题</span><span class="value" style="white-space:pre-wrap">${Utils.escapeHtml(r.issues_found)}</span></div>` : ''}
|
||||
${r.reject_reason ? `<div class="detail-row"><span class="label">驳回原因</span><span class="value" style="color:var(--danger)">${Utils.escapeHtml(r.reject_reason)}</span></div>` : ''}
|
||||
<div class="detail-row"><span class="label">审查者</span><span class="value">${r.reviewer_name || '-'}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
async refresh() {
|
||||
await this.mount();
|
||||
},
|
||||
|
||||
async createPlan(complaintId) {
|
||||
Utils.openModal('创建处理计划', `
|
||||
<form id="create-plan-form">
|
||||
<div class="form-group">
|
||||
<label>计划标题 *</label>
|
||||
<input type="text" id="plan-title" required placeholder="例如: 修复闪退问题">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>计划描述 *</label>
|
||||
<textarea id="plan-description" required rows="4" placeholder="详细描述处理方案..."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>技术方案</label>
|
||||
<textarea id="plan-tech" rows="3" placeholder="技术实现方案..."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>预计工时</label>
|
||||
<input type="text" id="plan-effort" placeholder="例如: 2小时">
|
||||
</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-save"></i> 创建</button>
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
document.getElementById('create-plan-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await API.post('/plans', {
|
||||
complaint_id: complaintId,
|
||||
title: document.getElementById('plan-title').value,
|
||||
description: document.getElementById('plan-description').value,
|
||||
technical_approach: document.getElementById('plan-tech').value,
|
||||
expected_effort: document.getElementById('plan-effort').value,
|
||||
});
|
||||
App.closeModal();
|
||||
this.refresh();
|
||||
} catch (err) { alert(err.message); }
|
||||
});
|
||||
},
|
||||
|
||||
async assignImplementer(complaintId, planId) {
|
||||
const users = await API.get('/users/implementers');
|
||||
if (!users.length) { alert('暂无可用实施者'); return; }
|
||||
Utils.openModal('分配实施者', `
|
||||
<form id="assign-impl-form">
|
||||
<div class="form-group">
|
||||
<label>选择实施者</label>
|
||||
<select id="assign-impl-user">
|
||||
${users.map(u => `<option value="${u.id}">${u.display_name} (${u.username})</option>`).join('')}
|
||||
</select>
|
||||
</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-user-tag"></i> 分配</button>
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
document.getElementById('assign-impl-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await API.post(`/plans/${planId}/assign`, { implementer_id: parseInt(document.getElementById('assign-impl-user').value) });
|
||||
App.closeModal();
|
||||
this.refresh();
|
||||
} catch (err) { alert(err.message); }
|
||||
});
|
||||
},
|
||||
|
||||
async startImplementation(complaintId, planId) {
|
||||
try {
|
||||
await API.post('/implementations', { plan_id: planId });
|
||||
this.refresh();
|
||||
} catch (err) { alert(err.message); }
|
||||
},
|
||||
|
||||
async startReview(complaintId, implementationId) {
|
||||
try {
|
||||
await API.post('/reviews', { implementation_id: implementationId });
|
||||
this.refresh();
|
||||
} catch (err) { alert(err.message); }
|
||||
},
|
||||
|
||||
showUpdateStatusModal(complaintId) {
|
||||
Utils.openModal('修改状态', `
|
||||
<form id="update-status-form">
|
||||
<div class="form-group">
|
||||
<label>新状态</label>
|
||||
<select id="new-status">
|
||||
${Object.entries(Utils.statusLabels).map(([k,v]) => `<option value="${k}">${v}</option>`).join('')}
|
||||
</select>
|
||||
</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-save"></i> 保存</button>
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
document.getElementById('update-status-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await API.put(`/complaints/${complaintId}`, { status: document.getElementById('new-status').value });
|
||||
App.closeModal();
|
||||
this.refresh();
|
||||
} catch (err) { alert(err.message); }
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,188 +0,0 @@
|
||||
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> 提交';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
const ImplementationsPage = {
|
||||
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 = '';
|
||||
|
||||
try {
|
||||
const impls = await API.get('/implementations');
|
||||
this.renderList(container, impls);
|
||||
} catch (err) {
|
||||
container.innerHTML = `<div class="alert alert-danger">加载失败: ${err.message}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
renderList(container, impls) {
|
||||
container.innerHTML = `
|
||||
${impls.length === 0 ? `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-code"></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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${impls.map(i => `
|
||||
<tr>
|
||||
<td>${i.id}</td>
|
||||
<td><a href="#" onclick="App.navigate('complaint',${i.complaint_id});return false" style="color:var(--primary)">#${i.complaint_id} ${Utils.escapeHtml(i.complaint_title)}</a></td>
|
||||
<td class="text-sm text-muted">${Utils.escapeHtml(i.plan_title)}</td>
|
||||
<td>${Utils.statusBadge(i.status)}</td>
|
||||
<td>${i.implementer_name || '-'}</td>
|
||||
<td class="date">${Utils.formatDate(i.created_at)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline" onclick="App.navigate('complaint',${i.complaint_id})"><i class="fas fa-eye"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
const PlansPage = {
|
||||
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 = '';
|
||||
|
||||
try {
|
||||
const plans = await API.get('/plans');
|
||||
this.renderList(container, plans);
|
||||
} catch (err) {
|
||||
container.innerHTML = `<div class="alert alert-danger">加载失败: ${err.message}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
renderList(container, plans) {
|
||||
container.innerHTML = `
|
||||
${plans.length === 0 ? `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-clipboard-list"></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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${plans.map(p => `
|
||||
<tr>
|
||||
<td>${p.id}</td>
|
||||
<td><strong>${Utils.escapeHtml(p.title)}</strong></td>
|
||||
<td><a href="#" onclick="App.navigate('complaint',${p.complaint_id});return false" style="color:var(--primary)">#${p.complaint_id} ${Utils.escapeHtml(p.complaint_title)}</a></td>
|
||||
<td>${Utils.statusBadge(p.status)}</td>
|
||||
<td>${p.planner_name || '-'}</td>
|
||||
<td class="date">${Utils.formatDate(p.created_at)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline" onclick="App.navigate('complaint',${p.complaint_id})"><i class="fas fa-eye"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
const ReviewsPage = {
|
||||
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 = '';
|
||||
|
||||
try {
|
||||
const reviews = await API.get('/reviews');
|
||||
this.renderList(container, reviews);
|
||||
} catch (err) {
|
||||
container.innerHTML = `<div class="alert alert-danger">加载失败: ${err.message}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
renderList(container, reviews) {
|
||||
container.innerHTML = `
|
||||
${reviews.length === 0 ? `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-search"></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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${reviews.map(r => `
|
||||
<tr>
|
||||
<td>${r.id}</td>
|
||||
<td><a href="#" onclick="App.navigate('complaint',${r.complaint_id});return false" style="color:var(--primary)">#${r.complaint_id} ${Utils.escapeHtml(r.complaint_title)}</a></td>
|
||||
<td>${Utils.statusBadge(r.status)}</td>
|
||||
<td>${r.reviewer_name || '-'}</td>
|
||||
<td class="text-sm text-muted" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${r.review_notes ? Utils.escapeHtml(r.review_notes.slice(0, 50)) : '-'}</td>
|
||||
<td class="date">${Utils.formatDate(r.created_at)}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline" onclick="App.navigate('complaint',${r.complaint_id})"><i class="fas fa-eye"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user