DEV-010 桔子 M06工单管理 + M08数据统计面板 上传完成

This commit is contained in:
juzi0412 2026-03-07 14:05:33 +08:00
parent 56d2dc47a7
commit be4220cbf7
6 changed files with 2897 additions and 0 deletions

189
dashboard/app.js Normal file
View File

@ -0,0 +1,189 @@
// HoloLake 数据统计面板 · 交互脚本 v2含导出 + 环形图)
window.addEventListener('load', function() {
// 条形图动画
setTimeout(function() {
var bars = document.querySelectorAll('.bar-fill');
bars.forEach(function(bar) {
var percent = bar.getAttribute('data-percent');
bar.style.width = percent + '%';
});
}, 300);
// 柱状图动画
setTimeout(function() {
var trendBars = document.querySelectorAll('.trend-bar');
trendBars.forEach(function(bar) {
var height = bar.getAttribute('data-height');
var fill = bar.querySelector('.trend-fill');
fill.style.height = height + '%';
});
}, 600);
// 环形图绘制函数
function drawDonut(percentages) {
var segments = document.querySelectorAll('.donut-segment');
var total = 377; // 2 * Math.PI * 60 ≈ 377
var cumulative = 0;
segments.forEach(function(seg, i) {
var value = percentages[i] || 0;
var dashArray = (value / 100 * total) + ' ' + total;
seg.setAttribute('stroke-dasharray', dashArray);
seg.setAttribute('stroke-dashoffset', 0);
});
}
// 当前选中的时间范围
var currentRange = 'today';
// 模拟数据
var mockData = {
today: {
users: '128', chats: '1,024', personas: '6', api: '3,892',
bars: [72,15,8,3,2],
trends: [45,62,38,78,55,90,85],
donut: [72,15,8,3,2],
apiTotal: '3,892'
},
week: {
users: '128', chats: '6,847', personas: '6', api: '24,103',
bars: [68,18,9,3,2],
trends: [320,415,380,450,290,520,480],
donut: [68,18,9,3,2],
apiTotal: '24,103'
},
month: {
users: '128', chats: '28,392', personas: '6', api: '98,741',
bars: [65,20,10,3,2],
trends: [1200,1450,1100,1680,1320,1890,1750],
donut: [65,20,10,3,2],
apiTotal: '98,741'
}
};
// 更新所有图表和数字
function updateStats(range) {
currentRange = range;
var data = mockData[range];
// 更新统计卡片
var values = document.querySelectorAll('.stat-value');
values[0].textContent = data.users;
values[1].textContent = data.chats;
values[2].textContent = data.personas;
values[3].textContent = data.api;
// 更新条形图
var bars = document.querySelectorAll('.bar-fill');
bars.forEach(function(bar, i) {
bar.style.width = '0%';
setTimeout(function() {
bar.style.width = data.bars[i] + '%';
}, 100);
});
// 更新百分比文字
var barValues = document.querySelectorAll('.bar-value');
data.bars.forEach(function(v, i) {
barValues[i].textContent = v + '%';
});
// 更新柱状图
var maxTrend = Math.max.apply(null, data.trends);
var trendBars = document.querySelectorAll('.trend-bar');
trendBars.forEach(function(bar, i) {
var fill = bar.querySelector('.trend-fill');
fill.style.height = '0%';
setTimeout(function() {
fill.style.height = Math.round(data.trends[i] / maxTrend * 90) + '%';
}, 100);
});
// 更新环形图
drawDonut(data.donut);
document.querySelector('.donut-center-value').textContent = data.apiTotal;
// 更新图例百分比
var legendPercents = document.querySelectorAll('.legend-percent');
data.donut.forEach(function(v, i) {
legendPercents[i].textContent = v + '%';
});
}
// 时间选择器切换
var timeButtons = document.querySelectorAll('.time-btn');
timeButtons.forEach(function(btn) {
btn.addEventListener('click', function() {
timeButtons.forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
var range = btn.getAttribute('data-range');
updateStats(range);
});
});
// 初始化环形图(今日数据)
drawDonut(mockData.today.donut);
// 卡片点击高亮
var cards = document.querySelectorAll('.stat-card');
cards.forEach(function(card) {
card.addEventListener('click', function() {
cards.forEach(function(c) { c.style.borderColor = 'rgba(255,255,255,0.06)'; });
card.style.borderColor = 'rgba(79, 195, 247, 0.5)';
});
});
// CSV 导出功能
var exportBtn = document.getElementById('exportBtn');
var toast = document.getElementById('exportToast');
exportBtn.addEventListener('click', function() {
var data = mockData[currentRange];
var models = ['DeepSeek', '通义千问', 'Kimi', '豆包', 'GPT/Claude'];
var days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
var csv = '\uFEFF'; // BOM for Chinese support
csv += 'HoloLake 数据统计报表\n';
csv += '时间范围,' + currentRange + '\n';
csv += '导出时间,' + new Date().toLocaleString('zh-CN') + '\n\n';
csv += '【统计概览】\n';
csv += '指标,数值,变化\n';
csv += '总用户,' + data.users + ',↑12%\n';
csv += '今日对话,' + data.chats + ',↑8%\n';
csv += '活跃人格体,' + data.personas + ',—\n';
csv += 'API调用,' + data.api + ',↓3%\n\n';
csv += '【模型调用分布】\n';
csv += '模型,占比\n';
models.forEach(function(m, i) {
csv += m + ',' + data.donut[i] + '%\n';
});
csv += '\n';
csv += '【每日对话趋势】\n';
csv += '日期,对话次数\n';
days.forEach(function(d, i) {
csv += d + ',' + data.trends[i] + '\n';
});
// 创建下载链接
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
var link = document.createElement('a');
var url = URL.createObjectURL(blob);
link.href = url;
link.setAttribute('download', 'hololake_stats_' + currentRange + '.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
// 显示提示
toast.classList.add('show');
setTimeout(function() {
toast.classList.remove('show');
}, 2000);
});
console.log('HoloLake Analytics v2 · 交互已加载');
});

182
dashboard/index.html Normal file
View File

@ -0,0 +1,182 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HoloLake · 数据统计</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<nav class="top-bar">
<div class="logo">HoloLake</div>
<div class="nav-links">
<a href="#">工单</a>
<a href="#" class="active">统计</a>
<a href="#">设置</a>
</div>
</nav>
<div class="page-header">
<h1>系统概览</h1>
<div class="time-selector">
<button class="time-btn active" data-range="today">今日</button>
<button class="time-btn" data-range="week">本周</button>
<button class="time-btn" data-range="month">本月</button>
</div>
</div>
<div class="export-bar">
<button class="export-btn" id="exportBtn">导出CSV</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">👥</div>
<div class="stat-value">128</div>
<div class="stat-label">总用户</div>
<div class="stat-change up">↑ 12%</div>
</div>
<div class="stat-card">
<div class="stat-icon">💬</div>
<div class="stat-value">1,024</div>
<div class="stat-label">今日对话</div>
<div class="stat-change up">↑ 8%</div>
</div>
<div class="stat-card">
<div class="stat-icon">🤖</div>
<div class="stat-value">6</div>
<div class="stat-label">活跃人格体</div>
<div class="stat-change same">— 持平</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-value">3,892</div>
<div class="stat-label">API调用</div>
<div class="stat-change down">↓ 3%</div>
</div>
</div>
<div class="section donut-section">
<h2>🔀 模型调用占比</h2>
<div class="donut-container">
<div class="donut-chart">
<svg viewBox="0 0 160 160">
<circle class="donut-bg" cx="80" cy="80" r="60"></circle>
<circle class="donut-segment" id="seg-deepseek" cx="80" cy="80" r="60" stroke="#4fc3f7" stroke-dasharray="0 377" stroke-dashoffset="0"></circle>
<circle class="donut-segment" id="seg-qwen" cx="80" cy="80" r="60" stroke="#7c4dff" stroke-dasharray="0 377" stroke-dashoffset="0"></circle>
<circle class="donut-segment" id="seg-kimi" cx="80" cy="80" r="60" stroke="#ff7043" stroke-dasharray="0 377" stroke-dashoffset="0"></circle>
<circle class="donut-segment" id="seg-doubao" cx="80" cy="80" r="60" stroke="#66bb6a" stroke-dasharray="0 377" stroke-dashoffset="0"></circle>
<circle class="donut-segment" id="seg-premium" cx="80" cy="80" r="60" stroke="#ffd54f" stroke-dasharray="0 377" stroke-dashoffset="0"></circle>
</svg>
<div class="donut-center">
<div class="donut-center-value">3,892</div>
<div class="donut-center-label">总调用</div>
</div>
</div>
<div class="donut-legend">
<div class="legend-item"><span class="legend-dot c-deepseek"></span> DeepSeek <span class="legend-percent">72%</span></div>
<div class="legend-item"><span class="legend-dot c-qwen"></span> 通义千问 <span class="legend-percent">15%</span></div>
<div class="legend-item"><span class="legend-dot c-kimi"></span> Kimi <span class="legend-percent">8%</span></div>
<div class="legend-item"><span class="legend-dot c-doubao"></span> 豆包 <span class="legend-percent">3%</span></div>
<div class="legend-item"><span class="legend-dot c-premium"></span> GPT/Claude <span class="legend-percent">2%</span></div>
</div>
</div>
</div>
<div class="section">
<h2>📊 模型调用分布</h2>
<div class="bar-chart">
<div class="bar-row">
<span class="bar-label">DeepSeek</span>
<div class="bar-track"><div class="bar-fill deepseek" style="width: 0%" data-percent="72"></div></div>
<span class="bar-value">72%</span>
</div>
<div class="bar-row">
<span class="bar-label">通义千问</span>
<div class="bar-track"><div class="bar-fill qwen" style="width: 0%" data-percent="15"></div></div>
<span class="bar-value">15%</span>
</div>
<div class="bar-row">
<span class="bar-label">Kimi</span>
<div class="bar-track"><div class="bar-fill kimi" style="width: 0%" data-percent="8"></div></div>
<span class="bar-value">8%</span>
</div>
<div class="bar-row">
<span class="bar-label">豆包</span>
<div class="bar-track"><div class="bar-fill doubao" style="width: 0%" data-percent="3"></div></div>
<span class="bar-value">3%</span>
</div>
<div class="bar-row">
<span class="bar-label">GPT/Claude</span>
<div class="bar-track"><div class="bar-fill premium" style="width: 0%" data-percent="2"></div></div>
<span class="bar-value">2%</span>
</div>
</div>
</div>
<div class="section">
<h2>每日对话趋势近7天</h2>
<div class="trend-chart">
<div class="trend-bar" data-height="45"><div class="trend-fill" style="height: 0%;"></div><span>周一</span></div>
<div class="trend-bar" data-height="62"><div class="trend-fill" style="height: 0%;"></div><span>周二</span></div>
<div class="trend-bar" data-height="38"><div class="trend-fill" style="height: 0%;"></div><span>周三</span></div>
<div class="trend-bar" data-height="78"><div class="trend-fill" style="height: 0%;"></div><span>周四</span></div>
<div class="trend-bar" data-height="55"><div class="trend-fill" style="height: 0%;"></div><span>周五</span></div>
<div class="trend-bar" data-height="90"><div class="trend-fill" style="height: 0%;"></div><span>周六</span></div>
<div class="trend-bar" data-height="85"><div class="trend-fill" style="height: 0%;"></div><span>周日</span></div>
</div>
</div>
<div class="section">
<h2>最近活动</h2>
<div class="activity-list">
<div class="activity-item">
<div class="activity-dot blue"></div>
<div class="activity-content">
<div class="activity-text">用户「星河」与 <strong>知秋</strong> 完成了32轮对话</div>
<div class="activity-time">5分钟前</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot green"></div>
<div class="activity-content">
<div class="activity-text">新用户「雨落」注册并激活了人格体</div>
<div class="activity-time">12分钟前</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot orange"></div>
<div class="activity-content">
<div class="activity-text">DeepSeek API 调用量达到今日峰值420次/小时)</div>
<div class="activity-time">28分钟前</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot purple"></div>
<div class="activity-content">
<div class="activity-text">人格体「小坍缩核」记忆库自动归档完成</div>
<div class="activity-time">1小时前</div>
</div>
</div>
<div class="activity-item">
<div class="activity-dot blue"></div>
<div class="activity-content">
<div class="activity-text">用户「晚风」上传了3个文件到云盘</div>
<div class="activity-time">2小时前</div>
</div>
</div>
</div>
</div>
<footer class="footer">
<p>HoloLake Era · AGE OS v1.0</p>
<p>Powered by 光湖团队</p>
</footer>
</div>
<div class="export-toast" id="exportToast">CSV已下载</div>
<script src="app.js"></script>
</body>
</html>

889
dashboard/style.css Normal file
View File

@ -0,0 +1,889 @@
/* HoloLake 数据统计面板 · 样式表 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
background: linear-gradient(135deg, #0a1628 0%, #1a2a4a 50%, #0d1f3c 100%);
color: #e0e6ed;
min-height: 100vh;
}
.container {
max-width: 640px;
margin: 0 auto;
padding: 0 20px;
}
/* 顶部导航 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.logo {
font-size: 20px;
font-weight: bold;
}
.nav-links a {
color: #8899aa;
text-decoration: none;
margin-left: 16px;
font-size: 14px;
}
.nav-links a.active {
color: #4fc3f7;
}
.nav-links a:hover {
color: #fff;
}
/* 页面标题 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 0 16px;
}
.page-header h1 {
font-size: 22px;
font-weight: 700;
}
.time-selector {
display: flex;
gap: 6px;
}
.time-btn {
background: rgba(255,255,255,0.06);
color: #8899aa;
border: 1px solid rgba(255,255,255,0.1);
padding: 6px 14px;
border-radius: 20px;
font-size: 13px;
cursor: pointer;
transition: all 0.3s;
}
.time-btn.active {
background: rgba(79,195,247,0.15);
color: #4fc3f7;
border-color: rgba(79,195,247,0.4);
}
.time-btn:hover {
border-color: rgba(79,195,247,0.3);
color: #b0c4d8;
}
/* 统计卡片 */
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
padding-bottom: 24px;
}
.stat-card {
background: rgba(255,255,255,0.04);
border-radius: 16px;
padding: 20px 16px;
border: 1px solid rgba(255,255,255,0.06);
transition: all 0.3s;
cursor: pointer;
}
.stat-card:hover {
background: rgba(79,195,247,0.06);
border-color: rgba(79,195,247,0.2);
transform: translateY(-2px);
}
.stat-icon {
font-size: 24px;
margin-bottom: 8px;
}
.stat-value {
font-size: 32px;
font-weight: 800;
margin-bottom: 4px;
}
.stat-label {
font-size: 13px;
color: #8899aa;
margin-bottom: 6px;
}
.stat-change {
font-size: 12px;
font-weight: 600;
}
.stat-change.up { color: #4caf50; }
.stat-change.down { color: #ef5350; }
.stat-change.same { color: #8899aa; }
/* 区块通用 */
.section {
padding-bottom: 28px;
}
.section h2 {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
}
/* 横向条形图 */
.bar-chart {
display: flex;
flex-direction: column;
gap: 12px;
}
.bar-row {
display: flex;
align-items: center;
gap: 12px;
}
.bar-label {
font-size: 13px;
color: #aab;
min-width: 70px;
text-align: right;
}
.bar-track {
flex: 1;
height: 22px;
background: rgba(255,255,255,0.04);
border-radius: 11px;
overflow: hidden;
}
.bar-fill {
height: 100%;
border-radius: 11px;
transition: width 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.bar-fill.deepseek { background: linear-gradient(90deg, #4fc3f7, #29b6f6); }
.bar-fill.qwen { background: linear-gradient(90deg, #7c4dff, #651fff); }
.bar-fill.kimi { background: linear-gradient(90deg, #ff7043, #f4511e); }
.bar-fill.doubao { background: linear-gradient(90deg, #66bb6a, #43a047); }
.bar-fill.premium { background: linear-gradient(90deg, #ffd54f, #ffb300); }
.bar-value {
font-size: 13px;
color: #8899aa;
min-width: 36px;
font-weight: 600;
}
/* 每日趋势柱状图 */
.trend-chart {
display: flex;
align-items: flex-end;
justify-content: space-between;
height: 160px;
padding: 16px 0;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.trend-bar {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
height: 100%;
justify-content: flex-end;
gap: 8px;
}
.trend-fill {
width: 28px;
border-radius: 6px 6px 2px 2px;
background: linear-gradient(180deg, #4fc3f7, rgba(79,195,247,0.3));
transition: height 1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
height: 0;
}
.trend-bar span {
font-size: 11px;
color: #667;
}
/* 最近活动 */
.activity-list {
display: flex;
flex-direction: column;
gap: 0;
}
.activity-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid rgba(255,255,255,0.04);
}
.activity-item:last-child {
border-bottom: none;
}
.activity-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-top: 6px;
flex-shrink: 0;
}
.activity-dot.blue { background: #4fc3f7; }
.activity-dot.green { background: #66bb6a; }
.activity-dot.orange { background: #ffb74d; }
.activity-dot.purple { background: #ab47bc; }
.activity-content {
flex: 1;
}
.activity-text {
font-size: 14px;
line-height: 1.5;
}
.activity-text strong {
color: #4fc3f7;
}
.activity-time {
font-size: 12px;
color: #556;
margin-top: 2px;
}
/* 底部 */
.footer {
text-align: center;
padding: 32px 0;
color: #556677;
font-size: 12px;
line-height: 1.8;
}
/* 响应式 */
@media (max-width: 480px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.stat-value {
font-size: 26px;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.trend-fill {
width: 22px;
}
.bar-label {
min-width: 56px;
font-size: 12px;
}
}
/* ========== 响应式布局增强 ========== */
/* 平板768px以下 */
@media (max-width: 768px) {
.container {
padding: 0 16px;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.page-header h1 {
font-size: 20px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.stat-value {
font-size: 28px;
}
.bar-label {
min-width: 60px;
font-size: 12px;
}
.trend-fill {
width: 24px;
}
}
/* 手机480px以下 */
📡 BC-M08-002 · DEV-010桔子 · 数据统计面板·环节2~3·响应式布局+数据导出+高级图表 2
@media (max-width: 480px) {
.container {
padding: 0 12px;
}
.top-bar {
padding: 12px 0;
}
.logo {
font-size: 17px;
}
.nav-links a {
font-size: 13px;
margin-left: 12px;
}
.page-header h1 {
font-size: 18px;
}
.time-btn {
padding: 5px 10px;
font-size: 12px;
}
.stats-grid {
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.stat-card {
padding: 14px 12px;
border-radius: 12px;
}
.stat-icon {
font-size: 20px;
margin-bottom: 6px;
}
.stat-value {
font-size: 24px;
}
.stat-label {
font-size: 12px;
📡 BC-M08-002 · DEV-010桔子 · 数据统计面板·环节2~3·响应式布局+数据导出+高级图表 3
}
.stat-change {
font-size: 11px;
}
.section h2 {
font-size: 15px;
}
.bar-row {
gap: 8px;
}
.bar-label {
min-width: 50px;
font-size: 11px;
}
.bar-track {
height: 18px;
}
.bar-value {
font-size: 11px;
min-width: 30px;
}
.trend-chart {
height: 120px;
}
.trend-fill {
width: 18px;
border-radius: 4px 4px 2px 2px;
}
.trend-bar span {
font-size: 10px;
}
.activity-item {
padding: 10px 0;
gap: 10px;
}
.activity-text {
font-size: 13px;
}
📡 BC-M08-002 · DEV-010桔子 · 数据统计面板·环节2~3·响应式布局+数据导出+高级图表 4
.activity-time {
font-size: 11px;
}
.footer {
padding: 20px 0;
font-size: 11px;
}
}
/* 超小屏360px以下 */
@media (max-width: 360px) {
.stats-grid {
grid-template-columns: 1fr;
}
.stat-card {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
}
.stat-icon {
margin-bottom: 0;
}
.stat-value {
font-size: 22px;
}
.time-selector {
width: 100%;
justify-content: space-between;
}
}
/* ========== 导出按钮样式 ========== */
.export-bar {
display: flex;
justify-content: flex-end;
padding: 8px 0;
}
📡 BC-M08-002 · DEV-010桔子 · 数据统计面板·环节2~3·响应式布局+数据导出+高级图表 5
.export-btn {
background: rgba(76, 175, 80, 0.1);
color: #81c784;
border: 1px solid rgba(76, 175, 80, 0.2);
padding: 8px 18px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.export-btn:hover {
background: rgba(76, 175, 80, 0.2);
transform: scale(1.02);
}
.export-btn:active {
transform: scale(0.98);
}
.export-toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(60px);
background: rgba(76, 175, 80, 0.9);
color: #fff;
padding: 10px 24px;
border-radius: 8px;
font-size: 14px;
opacity: 0;
transition: all 0.4s;
pointer-events: none;
z-index: 999;
}
.export-toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ========== 环形图样式 ========== */
📡 BC-M08-002 · DEV-010桔子 · 数据统计面板·环节2~3·响应式布局+数据导出+高级图表 6
.donut-section {
padding-bottom: 28px;
}
.donut-container {
display: flex;
align-items: center;
gap: 24px;
justify-content: center;
}
.donut-chart {
width: 160px;
height: 160px;
position: relative;
}
.donut-chart svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.donut-chart circle {
fill: none;
stroke-width: 20;
cx: 80;
cy: 80;
r: 60;
}
.donut-bg {
stroke: rgba(255,255,255,0.06);
}
.donut-segment {
transition: stroke-dasharray 1.2s cubic-bezier(0.25, 0.
46, 0.45, 0.94);
}
.donut-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
📡 BC-M08-002 · DEV-010桔子 · 数据统计面板·环节2~3·响应式布局+数据导出+高级图表 7
text-align: center;
}
.donut-center-value {
font-size: 24px;
font-weight: 800;
}
.donut-center-label {
font-size: 11px;
color: #8899aa;
}
.donut-legend {
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 3px;
flex-shrink: 0;
}
.legend-dot.c-deepseek { background: #4fc3f7; }
.legend-dot.c-qwen { background: #7c4dff; }
.legend-dot.c-kimi { background: #ff7043; }
.legend-dot.c-doubao { background: #66bb6a; }
.legend-dot.c-premium { background: #ffd54f; }
.legend-percent {
color: #8899aa;
margin-left: auto;
font-weight: 600;
}
📡 BC-M08-002 · DEV-010桔子 · 数据统计面板·环节2~3·响应式布局+数据导出+高级图表 8
@media (max-width: 480px) {
.donut-container {
flex-direction: column;
gap: 16px;
}
.donut-chart {
width: 140px;
height: 140px;
}
.donut-legend {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 6px 16px;
}
}
/* ========== 响应式布局增强 ========== */
/* 平板768px以下 */
@media (max-width: 768px) {
.container { padding: 0 16px; }
.page-header { flex-direction: column; align-items: flex-start; gap: 12px; }
.page-header h1 { font-size: 20px; }
.stats-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
.stat-value { font-size: 28px; }
.bar-label { min-width: 60px; font-size: 12px; }
.trend-fill { width: 24px; }
}
/* 手机480px以下 */
@media (max-width: 480px) {
.container { padding: 0 12px; }
.top-bar { padding: 12px 0; }
.logo { font-size: 17px; }
.nav-links a { font-size: 13px; margin-left: 12px; }
.page-header h1 { font-size: 18px; }
.time-btn { padding: 5px 10px; font-size: 12px; }
.stats-grid { grid-template-columns: 1fr 1fr; gap: 8px; }
.stat-card { padding: 14px 12px; border-radius: 12px; }
.stat-icon { font-size: 20px; margin-bottom: 6px; }
.stat-value { font-size: 24px; }
.stat-label { font-size: 12px; }
.activity-time { font-size: 11px; }
.footer { padding: 20px 0; font-size: 11px; }
}
/* 超小屏360px以下 */
@media (max-width: 360px) {
.stats-grid { grid-template-columns: 1fr; }
.stat-card { display: flex; align-items: center; gap: 12px; padding: 12px 14px; }
.stat-icon { margin-bottom: 0; }
.stat-value { font-size: 22px; }
.time-selector { width: 100%; justify-content: space-between; }
}
/* ========== 导出按钮样式 ========== */
.export-bar {
display: flex;
justify-content: flex-end;
padding: 8px 0;
}
.export-btn {
background: rgba(76,175,80,0.1);
color: #81c784;
border: 1px solid rgba(76,175,80,0.2);
padding: 8px 18px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.export-btn:hover {
background: rgba(76,175,80,0.2);
transform: scale(1.02);
}
.export-btn:active {
transform: scale(0.98);
}
.export-toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(60px);
background: rgba(76,175,80,0.9);
color: #fff;
padding: 10px 24px;
border-radius: 8px;
font-size: 14px;
opacity: 0;
transition: all 0.4s;
pointer-events: none;
z-index: 999;
}
.export-toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ========== 环形图样式 ========== */
.donut-section {
padding-bottom: 28px;
}
.donut-container {
display: flex;
align-items: center;
gap: 24px;
justify-content: center;
}
.donut-chart {
width: 160px;
height: 160px;
position: relative;
}
.donut-chart svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.donut-chart circle {
fill: none;
stroke-width: 20;
cx: 80;
cy: 80;
r: 60;
}
.donut-bg {
stroke: rgba(255,255,255,0.06);
}
.donut-segment {
transition: stroke-dasharray 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.donut-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.donut-center-value {
font-size: 24px;
font-weight: 800;
}
.donut-center-label {
font-size: 11px;
color: #8899aa;
}
.donut-legend {
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 3px;
flex-shrink: 0;
}
.legend-dot.c-deepseek { background: #4fc3f7; }
.legend-dot.c-qwen { background: #7c4dff; }
.legend-dot.c-kimi { background: #ff7043; }
.legend-dot.c-doubao { background: #66bb6a; }
.legend-dot.c-premium { background: #ffd54f; }
.legend-percent {
color: #8899aa;
margin-left: auto;
font-weight: 600;
}
/* ========== 响应式布局增强 ========== */
@media (max-width: 768px) {
.container { padding: 0 16px; }
.page-header { flex-direction: column; align-items: flex-start; gap: 12px; }
.page-header h1 { font-size: 20px; }
.stats-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
.stat-value { font-size: 28px; }
.bar-label { min-width: 60px; font-size: 12px; }
.trend-fill { width: 24px; }
}
@media (max-width: 480px) {
.container { padding: 0 12px; }
.top-bar { padding: 12px 0; }
.logo { font-size: 17px; }
.nav-links a { font-size: 13px; margin-left: 12px; }
.page-header h1 { font-size: 18px; }
.time-btn { padding: 5px 10px; font-size: 12px; }
.stats-grid { grid-template-columns: 1fr 1fr; gap: 8px; }
.stat-card { padding: 14px 12px; border-radius: 12px; }
.stat-icon { font-size: 20px; margin-bottom: 6px; }
.stat-value { font-size: 24px; }
.stat-label { font-size: 12px; }
.activity-time { font-size: 11px; }
.footer { padding: 20px 0; font-size: 11px; }
}
@media (max-width: 360px) {
.stats-grid { grid-template-columns: 1fr; }
.stat-card { display: flex; align-items: center; gap: 12px; padding: 12px 14px; }
.stat-icon { margin-bottom: 0; }
.stat-value { font-size: 22px; }
.time-selector { width: 100%; justify-content: space-between; }
}
/* ========== 导出按钮样式 ========== */
.export-bar {
display: flex;
justify-content: flex-end;
padding: 8px 0;
}
.export-btn {
background: rgba(76,175,80,0.1);
color: #81c784;
border: 1px solid rgba(76,175,80,0.2);
padding: 8px 18px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.export-btn:hover {
background: rgba(76,175,80,0.2);
transform: scale(1.02);
}
.export-btn:active {
transform: scale(0.98);
}
.export-toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(60px);
background: rgba(76,175,80,0.9);
color: #fff;
padding: 10px 24px;
border-radius: 8px;
font-size: 14px;
opacity: 0;
transition: all 0.4s;
pointer-events: none;
z-index: 999;
}
.export-toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ========== 环形图样式 ========== */
.donut-section {
padding-bottom: 28px;
}
.donut-container {
display: flex;
align-items: center;
gap: 24px;
justify-content: center;
}
.donut-chart {
width: 160px;
height: 160px;
position: relative;
}
.donut-chart svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.donut-chart circle {
fill: none;
stroke-width: 20;
cx: 80;
cy: 80;
r: 60;
}
.donut-bg {
stroke: rgba(255,255,255,0.06);
}
.donut-segment {
transition: stroke-dasharray 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.donut-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.donut-center-value {
font-size: 24px;
font-weight: 800;
}
.donut-center-label {
font-size: 11px;
color: #8899aa;
}
.donut-legend {
display: flex;
flex-direction: column;
gap: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 3px;
flex-shrink: 0;
}
.legend-dot.c-deepseek { background: #4fc3f7; }
.legend-dot.c-qwen { background: #7c4dff; }
.legend-dot.c-kimi { background: #ff7043; }
.legend-dot.c-doubao { background: #66bb6a; }
.legend-dot.c-premium { background: #ffd54f; }
.legend-percent {
color: #8899aa;
margin-left: auto;
font-weight: 600;
}

809
ticket-system/index.html Normal file
View File

@ -0,0 +1,809 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工单管理 · HoloLake · 视觉优化版</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
/* ========== 基础样式(深蓝渐变保持不变) ========== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #0a1628 0%, #1a2a4a 50%, #0d1f3c 100%);
color: #e0e6ed;
min-height: 100vh;
}
.app {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 导航栏(增加图标) */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 24px;
}
.logo {
display: flex;
align-items: center;
gap: 8px;
font-size: 24px;
font-weight: bold;
color: #fff;
}
.logo i {
color: #4fc3f7;
font-size: 28px;
}
.nav-links a {
margin-left: 24px;
text-decoration: none;
color: #8899aa;
font-size: 14px;
transition: color 0.2s;
display: inline-flex;
align-items: center;
gap: 4px;
}
.nav-links a i {
font-size: 16px;
}
.nav-links a.active {
color: #4fc3f7;
}
.nav-links a:hover {
color: #fff;
}
/* 统计卡片区(微调圆角与阴影) */
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 20px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.stat-card:hover {
background: rgba(255, 255, 255, 0.12);
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.stat-card.active {
border: 2px solid #4fc3f7;
background: rgba(79, 195, 247, 0.15);
}
.stat-number {
font-size: 32px;
font-weight: bold;
color: #fff;
margin-bottom: 4px;
}
.stat-label {
color: #8899aa;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.stat-label i {
color: #4fc3f7;
font-size: 16px;
}
/* 工单列表头部 */
.ticket-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
flex-wrap: wrap;
gap: 10px;
}
.ticket-header h2 {
font-size: 18px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
}
.ticket-header h2 i {
color: #4fc3f7;
}
.btn-primary {
background: #4fc3f7;
color: #0a1628;
border: none;
padding: 8px 20px;
border-radius: 30px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 6px;
box-shadow: 0 2px 8px rgba(79, 195, 247, 0.3);
}
.btn-primary i {
font-size: 16px;
}
.btn-primary:hover {
background: #81d4fa;
transform: scale(1.02);
box-shadow: 0 4px 12px rgba(79, 195, 247, 0.5);
}
/* 工具栏 */
.toolbar {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.sort-btn {
color: #4fc3f7;
cursor: pointer;
font-weight: 500;
user-select: none;
padding: 6px 12px;
border-radius: 30px;
transition: all 0.2s;
background: rgba(79, 195, 247, 0.1);
display: inline-flex;
align-items: center;
gap: 6px;
}
.sort-btn:hover {
background: rgba(79, 195, 247, 0.2);
}
.tool-btn {
background: rgba(255, 255, 255, 0.08);
color: #e0e6ed;
border: 1px solid rgba(255, 255, 255, 0.15);
padding: 6px 16px;
border-radius: 30px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 6px;
backdrop-filter: blur(2px);
}
.tool-btn i {
font-size: 14px;
color: #4fc3f7;
}
.tool-btn:hover {
background: rgba(255, 255, 255, 0.15);
border-color: #4fc3f7;
}
/* 工单列表容器 */
.ticket-list {
background: rgba(0, 0, 0, 0.25);
border-radius: 24px;
overflow: hidden;
margin-bottom: 32px;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(2px);
}
/* 单个工单项(优化悬停与图标) */
.ticket-item {
display: grid;
grid-template-columns: 80px 1fr 120px 100px 60px;
align-items: center;
padding: 18px 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
cursor: pointer;
transition: background 0.2s, transform 0.1s;
line-height: 1.6;
letter-spacing: 0.3px;
}
.ticket-item:hover {
background: rgba(255, 255, 255, 0.08);
}
.ticket-item:last-child {
border-bottom: none;
}
.ticket-id {
color: #8899aa;
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
}
.ticket-id i {
color: #4fc3f7;
font-size: 14px;
}
.ticket-title {
font-weight: 400;
color: #fff;
text-shadow: 0 0 8px rgba(79,195,247,0.2);
}
.ticket-assignee {
color: #8899aa;
font-size: 14px;
display: flex;
align-items: center;
gap: 4px;
opacity: 0.9;
}
.ticket-assignee i {
color: #4fc3f7;
font-size: 12px;
}
.ticket-status {
padding: 6px 14px;
border-radius: 30px;
font-size: 12px;
font-weight: 600;
text-align: center;
display: inline-flex;
align-items: center;
gap: 6px;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
letter-spacing: 0.5px;
}
.ticket-status:hover {
filter: brightness(1.1);
transform: scale(1.02);
}
.status-进行中 {
background: rgba(79, 195, 247, 0.2);
color: #4fc3f7;
}
.status-进行中 i {
color: #4fc3f7;
}
.status-已完成 {
background: rgba(76, 175, 80, 0.2);
color: #81c784;
}
.status-已完成 i {
color: #81c784;
}
.status-待开始 {
background: rgba(255, 255, 255, 0.1);
color: #b0bec5;
}
.status-待开始 i {
color: #b0bec5;
}
/* 删除按钮 */
.delete-btn {
color: #f44336;
cursor: pointer;
font-size: 18px;
text-align: center;
transition: all 0.2s;
opacity: 0.7;
}
.delete-btn:hover {
opacity: 1;
transform: scale(1.2);
}
/* 底部版权 */
.footer {
text-align: center;
color: #62748c;
font-size: 14px;
padding: 32px 0;
border-top: 1px solid rgba(255, 255, 255, 0.05);
margin-top: 32px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.footer i {
color: #4fc3f7;
}
/* ========== 模态框(弹窗)优化 ========== */
.modal {
display: none;
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8);
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(5px);
}
.modal.show {
display: flex;
}
.modal-content {
background: #1e2a3a;
border-radius: 32px;
padding: 32px;
width: 90%;
max-width: 500px;
color: #e0e6ed;
border: 1px solid rgba(255,255,255,0.2);
box-shadow: 0 25px 50px rgba(0,0,0,0.5);
animation: modalFadeIn 0.3s ease;
}
@keyframes modalFadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.modal-content h3 {
margin-top: 0;
margin-bottom: 24px;
color: #4fc3f7;
font-size: 22px;
display: flex;
align-items: center;
gap: 10px;
}
.modal-content h3 i {
font-size: 24px;
}
.modal-content input,
.modal-content select,
.modal-content textarea {
width: 100%;
padding: 14px 16px;
margin: 8px 0 20px;
background: #0f1a2b;
border: 1px solid #2a3a4a;
color: #e0e6ed;
border-radius: 16px;
font-size: 14px;
transition: border 0.2s, box-shadow 0.2s;
}
.modal-content input:focus,
.modal-content select:focus,
.modal-content textarea:focus {
outline: none;
border-color: #4fc3f7;
box-shadow: 0 0 0 3px rgba(79, 195, 247, 0.2);
}
.modal-content button {
background: #4fc3f7;
color: #0a1628;
border: none;
padding: 12px 24px;
border-radius: 40px;
cursor: pointer;
margin-right: 12px;
font-weight: 600;
font-size: 14px;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 12px rgba(79, 195, 247, 0.3);
}
.modal-content button.cancel {
background: #3a4a5a;
color: #e0e6ed;
box-shadow: none;
}
.modal-content button.cancel:hover {
background: #4a5a6a;
}
.modal-content button:hover {
background: #81d4fa;
transform: translateY(-2px);
}
.modal-content button i {
font-size: 16px;
}
/* 响应式微调 */
@media (max-width: 768px) {
.stats {
grid-template-columns: repeat(2, 1fr);
}
.ticket-item {
grid-template-columns: 60px 1fr 100px 80px 40px;
padding: 12px 16px;
font-size: 13px;
}
.ticket-status {
padding: 4px 8px;
font-size: 11px;
}
}
@media (max-width: 480px) {
.stats {
grid-template-columns: 1fr;
}
.navbar {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.nav-links a {
margin-left: 0;
margin-right: 16px;
}
.ticket-item {
grid-template-columns: 50px 1fr 80px 70px 35px;
padding: 10px 12px;
font-size: 12px;
}
.toolbar {
justify-content: flex-start;
}
}
.page-title {
color: #003366; /* 深蓝色示例 */
}</style>
</head>
<body>
<div class="app">
<nav class="navbar">
<div class="logo">
<i class="fas fa-water"></i> HoloLake
</div>
<div class="nav-links">
<a href="#" class="active"><i class="fas fa-tasks"></i> 工单管理</a>
<a href="#"><i class="fas fa-chart-pie"></i> 统计</a>
<a href="#"><i class="fas fa-cog"></i> 设置</a>
</div>
</nav>
<main class="main-content">
<div class="stats">
<div class="stat-card" data-status="全部">
<div class="stat-number" id="stat-all">5</div>
<div class="stat-label"><i class="fas fa-clipboard-list"></i> 总工单</div>
</div>
<div class="stat-card" data-status="进行中">
<div class="stat-number" id="stat-progress">3</div>
<div class="stat-label"><i class="fas fa-spinner"></i> 进行中</div>
</div>
<div class="stat-card" data-status="已完成">
<div class="stat-number" id="stat-done">1</div>
<div class="stat-label"><i class="fas fa-check-circle"></i> 已完成</div>
</div>
<div class="stat-card" data-status="待开始">
<div class="stat-number" id="stat-todo">1</div>
<div class="stat-label"><i class="fas fa-clock"></i> 待分配</div>
</div>
</div>
<div class="ticket-header">
<h2><i class="fas fa-list-ul"></i> 工单列表</h2>
<button class="btn-primary" id="new-ticket-btn"><i class="fas fa-plus-circle"></i> 新建工单</button>
</div>
<div class="toolbar">
<span id="sort-btn" class="sort-btn"><i class="fas fa-sort-amount-down-alt"></i> 编号↑</span>
<button class="tool-btn" id="export-btn"><i class="fas fa-download"></i> 导出CSV</button>
<button class="tool-btn" id="reset-btn"><i class="fas fa-undo-alt"></i> 重置</button>
</div>
<div id="ticket-list" class="ticket-list"></div>
</main>
<footer class="footer">
<i class="fas fa-water"></i> HoloLake Era · AGE OS v1.0 · <i class="fas fa-heart" style="color: #f44336;"></i> 光湖团队
</footer>
</div>
<!-- 新建工单模态框 -->
<div id="ticket-modal" class="modal">
<div class="modal-content">
<h3><i class="fas fa-pen-alt"></i> 新建工单</h3>
<input type="text" id="ticket-title" placeholder="工单标题" required>
<input type="text" id="ticket-assignee" placeholder="负责人 (如: 肥猫·M01)" required>
<select id="ticket-status">
<option value="进行中">进行中</option>
<option value="已完成">已完成</option>
<option value="待开始">待开始</option>
</select>
<textarea id="ticket-desc" placeholder="详情描述(选填)" rows="3"></textarea>
<div style="text-align: right;">
<button class="cancel" id="cancel-modal"><i class="fas fa-times"></i> 取消</button>
<button id="save-ticket"><i class="fas fa-check"></i> 确认创建</button>
</div>
</div>
</div>
<script>
// ---------- 数据持久化 ----------
let tickets = [];
const DEFAULT_TICKETS = [
{ id: '#001', title: '用户登录界面开发', assignee: '肥猫·M01', status: '进行中', desc: '' },
{ id: '#002', title: '后端服务器部署上线', assignee: '页页·后端', status: '已完成', desc: '' },
{ id: '#003', title: '系统状态看板搭建', assignee: '小草莓·M-DASHBOARD', status: '进行中', desc: '' },
{ id: '#004', title: '网站云盘系统', assignee: '燕樊·M15', status: '进行中', desc: '' },
{ id: '#005', title: '用户中心界面', assignee: '花尔·M05', status: '待开始', desc: '' }
];
function loadTickets() {
const stored = localStorage.getItem('hololake_tickets');
if (stored) {
tickets = JSON.parse(stored);
} else {
tickets = [...DEFAULT_TICKETS];
saveTickets();
}
}
function saveTickets() {
localStorage.setItem('hololake_tickets', JSON.stringify(tickets));
}
function generateNewId() {
if (tickets.length === 0) return '#001';
const maxNum = Math.max(...tickets.map(t => parseInt(t.id.slice(1))));
return '#' + String(maxNum + 1).padStart(3, '0');
}
// ---------- 渲染 ----------
let currentFilter = '全部';
let currentSort = 'asc';
function renderTickets() {
let filtered = tickets;
if (currentFilter !== '全部') {
filtered = tickets.filter(t => t.status === currentFilter);
}
filtered.sort((a, b) => {
const numA = parseInt(a.id.slice(1));
const numB = parseInt(b.id.slice(1));
return currentSort === 'asc' ? numA - numB : numB - numA;
});
const listHtml = filtered.map(t => {
let statusIcon = '';
if (t.status === '进行中') statusIcon = '<i class="fas fa-sync-alt fa-spin"></i>';
else if (t.status === '已完成') statusIcon = '<i class="fas fa-check-circle"></i>';
else if (t.status === '待开始') statusIcon = '<i class="fas fa-hourglass-start"></i>';
return `
<div class="ticket-item" data-id="${t.id}">
<span class="ticket-id"><i class="fas fa-hashtag"></i> ${t.id.slice(1)}</span>
<span class="ticket-title">${t.title}</span>
<span class="ticket-assignee"><i class="fas fa-user"></i> ${t.assignee}</span>
<span class="ticket-status status-${t.status}" data-id="${t.id}">${statusIcon} ${t.status}</span>
<span class="delete-btn" data-id="${t.id}"><i class="fas fa-trash-alt"></i></span>
</div>
`;
}).join('');
document.getElementById('ticket-list').innerHTML = listHtml;
document.getElementById('stat-all').innerText = tickets.length;
document.getElementById('stat-progress').innerText = tickets.filter(t => t.status === '进行中').length;
document.getElementById('stat-done').innerText = tickets.filter(t => t.status === '已完成').length;
document.getElementById('stat-todo').innerText = tickets.filter(t => t.status === '待开始').length;
}
function exportToCSV() {
let filtered = tickets;
if (currentFilter !== '全部') {
filtered = tickets.filter(t => t.status === currentFilter);
}
filtered.sort((a, b) => {
const numA = parseInt(a.id.slice(1));
const numB = parseInt(b.id.slice(1));
return currentSort === 'asc' ? numA - numB : numB - numA;
});
let csv = "编号,标题,负责人,状态,描述\n";
filtered.forEach(t => {
const desc = (t.desc || '').replace(/,/g, '').replace(/\n/g, ' ');
csv += `${t.id},${t.title},${t.assignee},${t.status},${desc}\n`;
});
const blob = new Blob(["\uFEFF" + csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.href = url;
link.setAttribute('download', '工单数据.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
function resetToDefault() {
if (confirm('重置将恢复默认工单,当前所有自定义工单会被清除。确定吗?')) {
tickets = DEFAULT_TICKETS.map(t => ({ ...t }));
saveTickets();
currentFilter = '全部';
currentSort = 'asc';
document.getElementById('sort-btn').innerHTML = '<i class="fas fa-sort-amount-down-alt"></i> 编号↑';
document.querySelectorAll('.stat-card').forEach(c => c.classList.remove('active'));
renderTickets();
}
}
document.addEventListener('DOMContentLoaded', () => {
loadTickets();
renderTickets();
document.querySelectorAll('.stat-card').forEach(card => {
card.addEventListener('click', () => {
const status = card.getAttribute('data-status');
currentFilter = status === '全部' ? '全部' : status;
renderTickets();
document.querySelectorAll('.stat-card').forEach(c => c.classList.remove('active'));
card.classList.add('active');
});
});
const sortBtn = document.getElementById('sort-btn');
if (sortBtn) {
sortBtn.addEventListener('click', () => {
currentSort = currentSort === 'asc' ? 'desc' : 'asc';
sortBtn.innerHTML = currentSort === 'asc'
? '<i class="fas fa-sort-amount-down-alt"></i> 编号↑'
: '<i class="fas fa-sort-amount-up-alt"></i> 编号↓';
renderTickets();
});
}
document.getElementById('new-ticket-btn').addEventListener('click', () => {
document.getElementById('ticket-modal').classList.add('show');
document.getElementById('ticket-title').focus();
});
document.getElementById('cancel-modal').addEventListener('click', () => {
document.getElementById('ticket-modal').classList.remove('show');
});
document.getElementById('ticket-modal').addEventListener('click', (e) => {
if (e.target === document.getElementById('ticket-modal')) {
document.getElementById('ticket-modal').classList.remove('show');
}
});
document.getElementById('save-ticket').addEventListener('click', () => {
const title = document.getElementById('ticket-title').value.trim();
const assignee = document.getElementById('ticket-assignee').value.trim();
const status = document.getElementById('ticket-status').value;
const desc = document.getElementById('ticket-desc').value.trim();
if (!title || !assignee) {
alert('请填写标题和负责人');
return;
}
const newTicket = {
id: generateNewId(),
title: title,
assignee: assignee,
status: status,
desc: desc
};
tickets.push(newTicket);
saveTickets();
renderTickets();
document.getElementById('ticket-modal').classList.remove('show');
document.getElementById('ticket-title').value = '';
document.getElementById('ticket-assignee').value = '';
document.getElementById('ticket-status').value = '进行中';
document.getElementById('ticket-desc').value = '';
});
document.getElementById('ticket-list').addEventListener('click', (e) => {
const deleteBtn = e.target.closest('.delete-btn');
if (deleteBtn) {
const id = deleteBtn.getAttribute('data-id');
tickets = tickets.filter(t => t.id !== id);
saveTickets();
renderTickets();
return;
}
const statusSpan = e.target.closest('.ticket-status');
if (statusSpan) {
const id = statusSpan.getAttribute('data-id');
const ticket = tickets.find(t => t.id === id);
if (ticket) {
if (ticket.status === '进行中') ticket.status = '已完成';
else if (ticket.status === '已完成') ticket.status = '进行中';
else if (ticket.status === '待开始') ticket.status = '进行中';
saveTickets();
renderTickets();
}
return;
}
const item = e.target.closest('.ticket-item');
if (item && !e.target.closest('.delete-btn') && !e.target.closest('.ticket-status')) {
const id = item.getAttribute('data-id');
const ticket = tickets.find(t => t.id === id);
alert(`工单详情\n编号${ticket.id}\n标题${ticket.title}\n负责人${ticket.assignee}\n状态${ticket.status}\n描述${ticket.desc || '无'}`);
}
});
document.getElementById('export-btn').addEventListener('click', exportToCSV);
document.getElementById('reset-btn').addEventListener('click', resetToDefault);
});
</script>
background: linear-gradient(135deg, #0a1628 0%, #1a2a4a 50%, #0d1f3c 100%);</body>
</html>

301
ticket-system/script.js Normal file
View File

@ -0,0 +1,301 @@
/* ============================================
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();

527
ticket-system/style.css Normal file
View File

@ -0,0 +1,527 @@
/* ============================================
HoloLake · M06 工单管理界面 · 样式表
环节3响应式布局 · 深蓝主题
DEV-010 桔子
============================================ */
/* === 全局变量 === */
:root {
--bg-primary: #0a0e27;
--bg-secondary: #111638;
--bg-card: #161b4a;
--bg-hover: #1e2460;
--border-color: #2a3070;
--text-primary: #e8eaff;
--text-secondary: #8b90c0;
--accent-blue: #4a6cf7;
--accent-blue-hover: #5b7bf8;
--accent-green: #22c55e;
--accent-yellow: #eab308;
--accent-red: #ef4444;
--accent-purple: #a855f7;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
}
/* === 基础重置 === */
* {
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 2
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe U
I", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
line-height: 1.6;
}
/* === 主容器 === */
.app-container {
max-width: 900px;
margin: 0 auto;
padding: 20px 24px;
}
/* === 顶部标题栏 === */
.app-header {
text-align: center;
padding: 24px 0 20px;
border-bottom: 1px solid var(--border-color);
margin-bottom: 24px;
}
.app-header h1 {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: 1px;
}
.app-header .subtitle {
font-size: 13px;
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 3
color: var(--text-secondary);
margin-top: 4px;
}
/* === 操作栏(新建 + 导出) === */
.header-actions {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 20px;
border: none;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.btn-primary {
background: var(--accent-blue);
color: #fff;
}
.btn-primary:hover {
background: var(--accent-blue-hover);
box-shadow: var(--shadow-sm);
}
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 4
.btn-secondary {
background: var(--bg-card);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--bg-hover);
}
/* === 导出下拉菜单 === */
.export-wrapper {
position: relative;
}
.export-dropdown {
display: none;
position: absolute;
top: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-md);
z-index: 100;
min-width: 160px;
overflow: hidden;
}
.export-dropdown.show {
display: block;
}
.export-dropdown .export-item {
display: block;
width: 100%;
padding: 10px 16px;
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 5
background: none;
border: none;
color: var(--text-primary);
font-size: 14px;
text-align: left;
cursor: pointer;
transition: background 0.15s;
}
.export-dropdown .export-item:hover {
background: var(--bg-hover);
}
.export-dropdown .export-item + .export-item {
border-top: 1px solid var(--border-color);
}
/* === 统计卡片 === */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 24px;
}
.stat-card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 16px;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-sm);
}
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 6
.stat-card .stat-number {
font-size: 28px;
font-weight: 700;
line-height: 1.2;
}
.stat-card .stat-label {
font-size: 12px;
color: var(--text-secondary);
margin-top: 4px;
}
.stat-card.total .stat-number { color: var(--accent-blue);
}
.stat-card.pending .stat-number { color: var(--accent-yello
w); }
.stat-card.active .stat-number { color: var(--accent-gree
n); }
.stat-card.done .stat-number { color: var(--accent-purple);
}
/* === 筛选排序栏 === */
.filter-bar {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.filter-bar select {
flex: 1;
min-width: 120px;
padding: 8px 12px;
background: var(--bg-card);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 7
font-size: 13px;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='h
ttp://www.w3.org/2000/svg' width='12' height='12' fill='%23
8b90c0'%3E%3Cpath d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 30px;
}
.filter-bar select:focus {
outline: none;
border-color: var(--accent-blue);
}
/* === 工单列表 === */
.ticket-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.ticket-item {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 16px;
display: flex;
align-items: center;
gap: 14px;
transition: background 0.15s;
}
.ticket-item:hover {
background: var(--bg-hover);
}
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 8
.ticket-id {
font-size: 12px;
color: var(--text-secondary);
font-family: "SF Mono", "Fira Code", monospace;
min-width: 60px;
}
.ticket-info {
flex: 1;
min-width: 0;
}
.ticket-title {
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ticket-meta {
font-size: 12px;
color: var(--text-secondary);
margin-top: 2px;
}
.ticket-actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
/* === 状态标签(可点击切换) === */
.status-badge {
display: inline-block;
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 9
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
white-space: nowrap;
}
.status-badge:hover {
opacity: 0.85;
}
.status-badge.pending {
background: rgba(234, 179, 8, 0.15);
color: var(--accent-yellow);
border: 1px solid rgba(234, 179, 8, 0.3);
}
.status-badge.active {
background: rgba(34, 197, 94, 0.15);
color: var(--accent-green);
border: 1px solid rgba(34, 197, 94, 0.3);
}
.status-badge.done {
background: rgba(168, 85, 247, 0.15);
color: var(--accent-purple);
border: 1px solid rgba(168, 85, 247, 0.3);
}
/* === 删除按钮 === */
.btn-delete {
background: none;
border: 1px solid transparent;
color: var(--text-secondary);
font-size: 16px;
cursor: pointer;
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 10
padding: 4px 8px;
border-radius: var(--radius-sm);
transition: all 0.2s;
}
.btn-delete:hover {
color: var(--accent-red);
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
}
/* === 空状态 === */
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-secondary);
}
.empty-state .empty-icon {
font-size: 48px;
margin-bottom: 12px;
}
.empty-state .empty-text {
font-size: 15px;
}
/* === 弹窗遮罩 === */
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 1000;
justify-content: center;
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 11
align-items: center;
padding: 20px;
}
.modal-overlay.show {
display: flex;
}
/* === 弹窗 === */
.modal {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 28px;
width: 100%;
max-width: 480px;
box-shadow: var(--shadow-lg);
}
.modal h2 {
font-size: 18px;
margin-bottom: 20px;
color: var(--text-primary);
}
.modal .form-group {
margin-bottom: 16px;
}
.modal label {
display: block;
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 6px;
}
.modal input,
.modal select,
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 12
.modal textarea {
width: 100%;
padding: 10px 12px;
background: var(--bg-card);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
font-size: 14px;
}
.modal input:focus,
.modal select:focus,
.modal textarea:focus {
outline: none;
border-color: var(--accent-blue);
}
.modal textarea {
resize: vertical;
min-height: 80px;
}
.modal .form-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
/* === 点击外部关闭下拉菜单的辅助层 === */
.click-away {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 13
}
.click-away.show {
display: block;
}
/* ============================================
响应式布局 · 平板端481px ~ 768px
============================================ */
@media screen and (max-width: 768px) {
.app-container {
padding: 16px;
}
.app-header h1 {
font-size: 20px;
}
.stats-grid {
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
.stat-card {
padding: 12px 8px;
}
.stat-card .stat-number {
font-size: 24px;
}
.stat-card .stat-label {
font-size: 11px;
}
.ticket-item {
padding: 12px;
gap: 10px;
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 14
}
}
/* ============================================
响应式布局 · 手机端<=480px
============================================ */
@media screen and (max-width: 480px) {
.app-container {
padding: 12px;
}
.app-header {
padding: 16px 0 14px;
margin-bottom: 16px;
}
.app-header h1 {
font-size: 18px;
}
/* 统计卡片手机端变2列 */
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.stat-card {
padding: 12px 10px;
}
.stat-card .stat-number {
font-size: 22px;
}
/* 操作按钮自适应 */
.header-actions {
gap: 8px;
}
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 15
.btn {
padding: 8px 14px;
font-size: 13px;
}
/* 筛选栏纵向排列 */
.filter-bar {
flex-direction: column;
gap: 8px;
}
.filter-bar select {
width: 100%;
}
/* 工单卡片纵向排列 */
.ticket-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
padding: 14px;
}
.ticket-id {
min-width: auto;
}
.ticket-info {
width: 100%;
}
.ticket-title {
white-space: normal;
word-break: break-all;
}
.ticket-actions {
📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出 16
width: 100%;
justify-content: space-between;
}
/* 导出下拉菜单手机端定位优化 */
.export-dropdown {
left: auto;
right: 0;
transform: none;
}
/* 弹窗手机端全屏 */
.modal-overlay {
padding: 0;
align-items: flex-end;
}
.modal {
max-width: 100%;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
padding: 24px 20px;
max-height: 90vh;
overflow-y: auto;
}
.modal .form-actions {
flex-direction: column;
}
.modal .form-actions .btn {
width: 100%;
justify-content: center;
}
}