Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

302 lines
9.2 KiB
JavaScript
Raw Permalink 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.

/* ============================================
HoloLake · M06 工单管理界面 · 逻辑层
环节3响应式 + 数据导出JSON/CSV
DEV-010 桔子
============================================ */
// === 状态流转规则 ===
const STATUS_FLOW = {
'pending': { next: 'active', label: '待处理' },
'active': { next: 'done', label: '进行中' },
'done': { next: 'pending', label: '已完成' }
};
// === 初始化 · 从 localStorage 读取工单 ===
let tickets = JSON.parse(localStorage.getItem('holotake_tic
kets') || '[]');
// === 保存到 localStorage ===
function saveTickets() {
localStorage.setItem('holotake_tickets', JSON.stringify(t
ickets));
}
// === 生成工单编号 ===
function generateId() {
const now = new Date();
const dateStr = [
now.getFullYear(),
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 18
String(now.getMonth() + 1).padStart(2, '0'),
String(now.getDate()).padStart(2, '0')
].join('');
const rand = String(Math.floor(Math.random() * 1000)).pad
Start(3, '0');
return 'TK-' + dateStr + '-' + rand;
}
// === 渲染统计卡片 ===
function renderStats() {
const total = tickets.length;
const pending = tickets.filter(t => t.status === 'pendin
g').length;
const active = tickets.filter(t => t.status === 'activ
e').length;
const done = tickets.filter(t => t.status === 'done').len
gth;
document.getElementById('statTotal').textContent = total;
document.getElementById('statPending').textContent = pend
ing;
document.getElementById('statActive').textContent = activ
e;
document.getElementById('statDone').textContent = done;
}
// === 获取筛选和排序后的工单列表 ===
function getFilteredTickets() {
const filterStatus = document.getElementById('filterStatu
s').value;
const sortBy = document.getElementById('sortBy').value;
let list = [...tickets];
// 筛选
if (filterStatus !== 'all') {
list = list.filter(t => t.status === filterStatus);
}
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 19
// 排序
if (sortBy === 'newest') {
list.sort((a, b) => new Date(b.createdAt) - new Date(a.
createdAt));
} else if (sortBy === 'oldest') {
list.sort((a, b) => new Date(a.createdAt) - new Date(b.
createdAt));
} else if (sortBy === 'status') {
const order = { 'pending': 0, 'active': 1, 'done': 2 };
list.sort((a, b) => order[a.status] - order[b.status]);
}
return list;
}
// === 渲染工单列表 ===
function renderTickets() {
const list = getFilteredTickets();
const container = document.getElementById('ticketList');
if (list.length === 0) {
container.innerHTML = '<div class="empty-state"><div cl
ass="empty-icon">📋</div><div class="empty-text">暂无工单,点
击上方按钮新建</div></div>';
return;
}
container.innerHTML = list.map(ticket => {
const statusInfo = STATUS_FLOW[ticket.status];
return '<div class="ticket-item">' +
'<span class="ticket-id">' + ticket.id + '</span>' +
'<div class="ticket-info">' +
'<div class="ticket-title">' + escapeHtml(ticket.ti
tle) + '</div>' +
'<div class="ticket-meta">负责人:' + escapeHtml(tic
ket.assignee) + ' · ' + formatDate(ticket.createdAt) + '</d
iv>' +
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 20
'</div>' +
'<div class="ticket-actions">' +
'<span class="status-badge ' + ticket.status + '" o
nclick="toggleStatus(\'' + ticket.id + '\')">' + statusInf
o.label + '</span>' +
'<button class="btn-delete" onclick="deleteTicket
(\'' + ticket.id + '\')" title="删除">🗑</button>' +
'</div>' +
'</div>';
}).join('');
}
// === HTML转义防XSS ===
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// === 格式化日期 ===
function formatDate(dateStr) {
const d = new Date(dateStr);
return d.getFullYear() + '-' +
String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0') + ' ' +
String(d.getHours()).padStart(2, '0') + ':' +
String(d.getMinutes()).padStart(2, '0');
}
// === 切换工单状态 ===
function toggleStatus(id) {
const ticket = tickets.find(t => t.id === id);
if (!ticket) return;
ticket.status = STATUS_FLOW[ticket.status].next;
saveTickets();
renderAll();
}
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 21
// === 删除工单 ===
function deleteTicket(id) {
if (!confirm('确认删除这条工单?')) return;
tickets = tickets.filter(t => t.id !== id);
saveTickets();
renderAll();
}
// === 新建工单弹窗 ===
function openModal() {
document.getElementById('modalOverlay').classList.add('sh
ow');
document.getElementById('inputTitle').value = '';
document.getElementById('inputAssignee').value = '';
document.getElementById('inputDetail').value = '';
document.getElementById('inputTitle').focus();
}
function closeModal() {
document.getElementById('modalOverlay').classList.remove
('show');
}
// === 提交新建工单 ===
function submitTicket() {
const title = document.getElementById('inputTitle').valu
e.trim();
const assignee = document.getElementById('inputAssigne
e').value.trim();
const detail = document.getElementById('inputDetail').val
ue.trim();
if (!title) {
alert('请填写工单标题');
return;
}
if (!assignee) {
alert('请填写负责人');
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 22
return;
}
const newTicket = {
id: generateId(),
title: title,
assignee: assignee,
detail: detail,
status: 'pending',
createdAt: new Date().toISOString()
};
tickets.unshift(newTicket);
saveTickets();
closeModal();
renderAll();
}
// === 重置全部工单 ===
function resetTickets() {
if (!confirm('确认清空所有工单数据?此操作不可恢复!')) return;
tickets = [];
saveTickets();
renderAll();
}
// ============================================
// 环节3新增导出功能
// ============================================
// === 导出下拉菜单控制 ===
function toggleExportMenu() {
const dropdown = document.getElementById('exportDropdow
n');
const clickAway = document.getElementById('clickAway');
const isOpen = dropdown.classList.contains('show');
if (isOpen) {
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 23
closeExportMenu();
} else {
dropdown.classList.add('show');
clickAway.classList.add('show');
}
}
function closeExportMenu() {
document.getElementById('exportDropdown').classList.remov
e('show');
document.getElementById('clickAway').classList.remove('sh
ow');
}
// === 导出为 JSON 文件 ===
function exportJSON() {
closeExportMenu();
if (tickets.length === 0) {
alert('当前没有工单数据可导出');
return;
}
const data = JSON.stringify(tickets, null, 2);
const blob = new Blob([data], { type: 'application/json;c
harset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'HoloLake-Tickets-' + getDateStamp() + '.jso
n';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 24
// === 导出为 CSV 文件 ===
function exportCSV() {
closeExportMenu();
if (tickets.length === 0) {
alert('当前没有工单数据可导出');
return;
}
// CSV表头
const header = '编号,标题,负责人,状态,详情,创建时间';
// CSV内容行
const rows = tickets.map(t => {
const statusLabel = STATUS_FLOW[t.status] ? STATUS_FLOW
[t.status].label : t.status;
return [
t.id,
csvEscape(t.title),
csvEscape(t.assignee),
statusLabel,
csvEscape(t.detail || ''),
formatDate(t.createdAt)
].join(',');
});
const csv = '\uFEFF' + header + '\n' + rows.join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=ut
f-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'HoloLake-Tickets-' + getDateStamp() + '.cs
v';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 25
URL.revokeObjectURL(url);
}
// === CSV字段转义处理逗号和引号 ===
function csvEscape(str) {
if (!str) return '';
if (str.includes(',') || str.includes('"') || str.include
s('\n')) {
return '"' + str.replace(/"/g, '""') + '"';
}
return str;
}
// === 生成日期戳(用于文件名) ===
function getDateStamp() {
const now = new Date();
return now.getFullYear() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') + '-' +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0');
}
// === 统一渲染 ===
function renderAll() {
renderStats();
renderTickets();
}
// === 筛选/排序变化时重新渲染 ===
document.getElementById('filterStatus').addEventListener('c
hange', renderTickets);
document.getElementById('sortBy').addEventListener('chang
e', renderTickets);
// === 弹窗点击遮罩关闭 ===
document.getElementById('modalOverlay').addEventListener('c
lick', function(e) {
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 26
if (e.target === this) closeModal();
});
// === 页面加载完成 · 首次渲染 ===
renderAll();