fix: backend+frontend full audit - 15 bugs fixed across all files

This commit is contained in:
2026-07-13 04:07:18 +08:00
parent 532efb962f
commit 4f4df7e479
6 changed files with 13 additions and 11 deletions

View File

@@ -4,7 +4,7 @@ const Auth = {
token() { return localStorage.getItem(this.TK); }, token() { return localStorage.getItem(this.TK); },
user() { try { return JSON.parse(localStorage.getItem(this.US)); } catch { return null; } }, user() { try { return JSON.parse(localStorage.getItem(this.US)); } catch { return null; } },
logged() { return !!this.token(); }, logged() { return !!(this.token() && this.user()); },
async login(username, password) { async login(username, password) {
const d = await API.post('/auth/login', { username, password }); const d = await API.post('/auth/login', { username, password });

View File

@@ -50,7 +50,7 @@ const HomePage = {
</tr></thead><tbody>${tickets.map(tick => ` </tr></thead><tbody>${tickets.map(tick => `
<tr><td>${tick.id}</td><td>${U.badge(tick.type,'type')}</td><td>${U.esc(tick.title)}</td> <tr><td>${tick.id}</td><td>${U.badge(tick.type,'type')}</td><td>${U.esc(tick.title)}</td>
<td>${U.badge(tick.status,'status')}</td><td>${U.date(tick.created_at)}</td> <td>${U.badge(tick.status,'status')}</td><td>${U.date(tick.created_at)}</td>
<td><button class="btn btn-o btn-sm" onclick="App.navigate('home','tracking');HomePage.showTrackDetail(${tick.id},'${token}')">查看</button></td></tr> <td><button class="btn btn-o btn-sm" onclick="App.navigate('home','tracking');HomePage.showTrackDetail(${tick.id},'${U.escJs(token)}')">查看</button></td></tr>
`).join('')}</tbody></table></div>` : '<div class="empty"><i class="fas fa-inbox"></i><p>未找到工单</p></div>'; `).join('')}</tbody></table></div>` : '<div class="empty"><i class="fas fa-inbox"></i><p>未找到工单</p></div>';
} catch (ex) { ct.innerHTML = `<div class="alert alert-e">${ex.message}</div>`; } } catch (ex) { ct.innerHTML = `<div class="alert alert-e">${ex.message}</div>`; }
}; };

View File

@@ -17,7 +17,7 @@ const NotificationsPage = {
${c.webhook_url?`<div class="detail-row"><span class="lbl">地址</span><span class="val ts" style="word-break:break-all">${U.esc(c.webhook_url)}</span></div>`:''} ${c.webhook_url?`<div class="detail-row"><span class="lbl">地址</span><span class="val ts" style="word-break:break-all">${U.esc(c.webhook_url)}</span></div>`:''}
<div class="detail-row"><span class="lbl">事件</span><span class="val">${EventsLabel(c.events)}</span></div> <div class="detail-row"><span class="lbl">事件</span><span class="val">${EventsLabel(c.events)}</span></div>
<div class="mt-2 flex g2"> <div class="mt-2 flex g2">
<button class="btn btn-o btn-sm" onclick="NotificationsPage.showEdit(${c.id},'${U.escJs(c.name)}','${c.type}','${U.escJs(c.webhook_url)}','${c.events}',${c.active})"><i class="fas fa-edit"></i> 编辑</button> <button class="btn btn-o btn-sm" onclick="NotificationsPage.showEdit(${c.id},'${U.escJs(c.name)}','${c.type}','${U.escJs(c.webhook_url)}','${U.escJs(c.events)}',${c.active})"><i class="fas fa-edit"></i> 编辑</button>
<button class="btn btn-i btn-sm" onclick="NotificationsPage.test(${c.id})"><i class="fas fa-paper-plane"></i> 测试</button> <button class="btn btn-i btn-sm" onclick="NotificationsPage.test(${c.id})"><i class="fas fa-paper-plane"></i> 测试</button>
<button class="btn btn-d btn-sm" onclick="NotificationsPage.del(${c.id})"><i class="fas fa-trash"></i> 删除</button> <button class="btn btn-d btn-sm" onclick="NotificationsPage.del(${c.id})"><i class="fas fa-trash"></i> 删除</button>
</div> </div>
@@ -61,7 +61,7 @@ const NotificationsPage = {
}, },
async test(id) { try { alert((await API.post(`/notifications/${id}/test`)).message); } catch(ex){alert(ex.message);} }, async test(id) { try { alert((await API.post(`/notifications/${id}/test`)).message); } catch(ex){alert(ex.message);} },
async del(id) { if(await U.confirm('确认删除此通知配置?')){ await API.req('DELETE',`/notifications/${id}`); this.load(); } } async del(id) { if(await U.confirm('确认删除此通知配置?')){ try { await API.req('DELETE',`/notifications/${id}`); this.load(); } catch(ex){alert(ex.message);} } }
}; };
function EventOptions(current) { return ['all','ticket_created','ticket_claimed','ticket_transferred','ticket_updated'].map(e=>`<label style="cursor:pointer;font-size:12px"><input type="checkbox" value="${e}" class="ev-check" ${current.split(',').map(s=>s.trim()).includes(e)?'checked':''}> ${EventsLabelSingle(e)}</label>`).join(''); } function EventOptions(current) { return ['all','ticket_created','ticket_claimed','ticket_transferred','ticket_updated'].map(e=>`<label style="cursor:pointer;font-size:12px"><input type="checkbox" value="${e}" class="ev-check" ${current.split(',').map(s=>s.trim()).includes(e)?'checked':''}> ${EventsLabelSingle(e)}</label>`).join(''); }

View File

@@ -65,7 +65,7 @@ const TicketCreate = {
<div class="file-upload" id="file-drop"> <div class="file-upload" id="file-drop">
<i class="fas fa-cloud-upload-alt"></i> <i class="fas fa-cloud-upload-alt"></i>
<p>点击选择文件或拖拽到此处</p> <p>点击选择文件或拖拽到此处</p>
<p class="help">支持: JPG/PNG/GIF/WebP/MP4/WebM</p> <p class="help">支持: JPG/PNG/GIF/WebP/MP4/WebM &nbsp; 每个最大50MB</p>
</div> </div>
<input type="file" id="file-input" multiple accept="image/jpeg,image/png,image/gif,image/webp,video/mp4,video/webm" style="display:none"> <input type="file" id="file-input" multiple accept="image/jpeg,image/png,image/gif,image/webp,video/mp4,video/webm" style="display:none">
<div class="file-list" id="file-list"></div> <div class="file-list" id="file-list"></div>

View File

@@ -7,10 +7,10 @@ const TicketDetail = {
const id = this.id; const id = this.id;
document.getElementById('page-title').textContent = `工单 #${id}`; document.getElementById('page-title').textContent = `工单 #${id}`;
document.getElementById('page-actions').innerHTML = `<button class="btn btn-o btn-sm" onclick="App.navigate('tickets')"><i class="fas fa-arrow-left"></i> 返回列表</button>`; document.getElementById('page-actions').innerHTML = `<button class="btn btn-o btn-sm" onclick="App.navigate('tickets')"><i class="fas fa-arrow-left"></i> 返回列表</button>`;
try { this.render(document.getElementById('page-content'), await API.get(`/tickets/${id}`)); } catch (e) { document.getElementById('page-content').innerHTML = `<div class="alert alert-e">${e.message}</div>`; } try { this.renderDetail(document.getElementById('page-content'), await API.get(`/tickets/${id}`)); } catch (e) { document.getElementById('page-content').innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
}, },
render(ct, t) { renderDetail(ct, t) {
const isStaff = Auth.isAdmin(); const isStaff = Auth.isAdmin();
const isOwner = Auth.isOwner(); const isOwner = Auth.isOwner();
const isRep = t.type === 'report'; const isRep = t.type === 'report';

View File

@@ -27,11 +27,12 @@ const U = {
escJs(s) { escJs(s) {
if (!s) return ''; if (!s) return '';
return String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/\n/g,'\\n'); return String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/"/g,'&quot;').replace(/\n/g,'\\n');
}, },
showAlert(containerId, type, msg) { showAlert(containerId, type, msg) {
const ct = document.getElementById(containerId || 'page-content'); const ct = document.getElementById(containerId || 'page-content');
if (!ct) return;
const el = document.createElement('div'); const el = document.createElement('div');
el.className = `alert alert-${type === 'success' ? 's' : type === 'error' ? 'e' : 'i'}`; el.className = `alert alert-${type === 'success' ? 's' : type === 'error' ? 'e' : 'i'}`;
el.textContent = msg; el.textContent = msg;
@@ -39,20 +40,21 @@ const U = {
setTimeout(() => el.remove(), 6000); setTimeout(() => el.remove(), 6000);
}, },
loading(ct) { ct.innerHTML = '<div class="loading"><i class="fas fa-spinner"></i><p>加载中...</p></div>'; }, loading(ct) { if (!ct) return; ct.innerHTML = '<div class="loading"><i class="fas fa-spinner"></i><p>加载中...</p></div>'; },
modal(title, html, w) { modal(title, html, w) {
const o = document.getElementById('modal-overlay'); const o = document.getElementById('modal-overlay');
if (!o) return;
document.getElementById('modal-title').textContent = title; document.getElementById('modal-title').textContent = title;
document.getElementById('modal-body').innerHTML = html; document.getElementById('modal-body').innerHTML = html;
if (w) o.querySelector('.modal-box').style.width = w; o.querySelector('.modal-box').style.width = w || '';
o.classList.remove('hidden'); o.classList.remove('hidden');
}, },
confirm(msg) { confirm(msg) {
return new Promise(resolve => { return new Promise(resolve => {
this.modal('确认', ` this.modal('确认', `
<p>${msg}</p> <p>${this.esc(msg)}</p>
<div class="modal-f"><button class="btn btn-o btn-confirm-cancel">取消</button><button class="btn btn-p btn-confirm-ok">确认</button></div> <div class="modal-f"><button class="btn btn-o btn-confirm-cancel">取消</button><button class="btn btn-p btn-confirm-ok">确认</button></div>
`); `);
document.querySelector('.btn-confirm-cancel').onclick = () => { App.closeModal(); resolve(false); }; document.querySelector('.btn-confirm-cancel').onclick = () => { App.closeModal(); resolve(false); };