样式修改

This commit is contained in:
zzh 2025-11-22 16:27:41 +08:00
parent 290d9eccf6
commit b9a6b80651
8 changed files with 242 additions and 47 deletions

3
frontend/assets/moon.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>

After

Width:  |  Height:  |  Size: 251 B

View File

@ -25,17 +25,25 @@ body{margin:0;background:var(--bg);color:var(--text);font-family:Inter,system-ui
.caret{margin-left:auto;transition:transform .2s ease}
.caret.rotate{transform:rotate(90deg)}
.theme-toggle-container{padding:16px;border-top:1px solid var(--border);margin-top:auto}
.theme-toggle-btn{width:100%;display:flex;align-items:center;justify-content:center;padding:10px;background:transparent;border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:all 0.2s ease}
.theme-toggle-btn:hover{background:rgba(79,140,255,.12);border-color:var(--primary)}
.theme-icon{width:20px;height:20px;filter:invert(1);transition:transform 0.3s ease}
[data-theme="light"] .theme-icon{filter:invert(0)}
.theme-toggle-btn:hover .theme-icon{transform:rotate(20deg)}
.btn{border:0;border-radius:8px;padding:8px 12px;font-weight:600;cursor:pointer;background:var(--primary);color:#ffffff}
.btn:hover{background:var(--primary-600)}
.btn-secondary{background:#253045;color:var(--text)}
.btn-secondary:hover{background:#2b3750}
[data-theme="light"] .btn-secondary{background:#e5e7eb;color:var(--text)}
[data-theme="light"] .btn-secondary:hover{background:#d1d5db}
.content{flex:1;min-width:0;display:flex;flex-direction:column;height:100vh;overflow:hidden}
.content-header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--border);background:var(--surface);flex-shrink:0;position:relative;z-index:10}
.content{flex:1;min-width:0;display:flex;flex-direction:column;height:100vh;overflow:hidden;background:var(--bg)}
.content-header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--border);background:var(--bg) !important;flex-shrink:0;position:relative;z-index:10}
[data-theme="light"] .content-header{background:#f5f7fa !important}
#breadcrumb{color:var(--text-2)}
#actions{position:relative;z-index:999;display:flex;align-items:center;gap:8px}
.view{position:relative;padding:20px;flex:1;overflow-y:auto;overflow-x:hidden}
.view{position:relative;padding:20px;flex:1;overflow-y:auto;overflow-x:hidden;background:var(--bg);min-height:100%}
.card{background:var(--surface-2);border:1px solid var(--border);border-radius:12px;padding:16px}
.grid{display:grid;gap:12px}
.grid.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
@ -116,8 +124,8 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{
.loader .dot:nth-child(2){animation-delay:.15s}
.loader .dot:nth-child(3){animation-delay:.3s}
@keyframes bounce{0%,100%{transform:translateY(0);opacity:.7}50%{transform:translateY(-8px);opacity:1}}
.fade-enter{opacity:0;transform:translateY(6px)}
.fade-enter-active{transition:opacity .2s ease,transform .2s ease;opacity:1;transform:translateY(0)}
.fade-enter{opacity:0;transform:translateY(8px)}
.fade-enter-active{transition:opacity .25s ease-out,transform .25s ease-out;opacity:1;transform:translateY(0)}
.error{color:#ffb4b4}
.menu-toggle{display:none}
@media(max-width:1024px){.grid.cols-3{grid-template-columns:repeat(2,minmax(0,1fr))}}

11
frontend/assets/sun.svg Normal file
View File

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>

After

Width:  |  Height:  |  Size: 595 B

View File

@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="./assets/styles.css" />
<link rel="stylesheet" href="./assets/styles.css?v=20251122" />
</head>
<body>
<div id="app">
@ -114,9 +114,14 @@
</a>
</div>
</nav>
<div class="theme-toggle-container">
<button id="theme-toggle-btn" class="theme-toggle-btn" title="切换主题">
<img id="theme-icon" class="theme-icon" src="./assets/sun.svg" alt="主题切换" />
</button>
</div>
</aside>
<main class="content">
<header class="content-header">
<header class="content-header" style="background: var(--bg) !important;">
<div id="breadcrumb"></div>
<div id="actions">
<button id="notification-bell" class="notification-bell" style="display:none;">

View File

@ -98,6 +98,36 @@
sidebar.classList.toggle('open');
});
// 主题切换
const themeToggleBtn = document.getElementById('theme-toggle-btn');
const themeIcon = document.getElementById('theme-icon');
// 更新主题图标
function updateThemeIcon(theme) {
if (themeIcon) {
themeIcon.src = theme === 'dark' ? './assets/sun.svg' : './assets/moon.svg';
themeIcon.alt = theme === 'dark' ? '切换到浅色模式' : '切换到深色模式';
}
}
// 初始化主题图标
updateThemeIcon(savedTheme);
// 主题切换按钮点击事件
themeToggleBtn?.addEventListener('click', () => {
const currentTheme = localStorage.getItem('theme') || 'light';
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
updateThemeIcon(newTheme);
API.toast(`已切换到${newTheme === 'dark' ? '深色' : '浅色'}主题`);
// 触发自定义事件,通知其他组件主题已更改
window.dispatchEvent(new CustomEvent('themeChanged', { detail: { theme: newTheme } }));
});
// 暴露更新用户显示的函数,供设置页面使用
window.updateUserDisplay = updateUserDisplay;
})();

View File

@ -30,6 +30,11 @@ const Dashboard = (() => {
const ytDateEl = document.getElementById('audit-date-yt');
if(pddDateEl) pddDateEl.onchange = null;
if(ytDateEl) ytDateEl.onchange = null;
// 清理resize监听器
if(window.__dashboardResizeHandler){
window.removeEventListener('resize', window.__dashboardResizeHandler);
window.__dashboardResizeHandler = null;
}
// 清理全局变量
window.__auditBusy = false;
window.__pddParams = null;
@ -67,6 +72,48 @@ const Dashboard = (() => {
oldCanvas.onmouseleave = null;
}
// 动态计算审计框高度
const calculateAuditHeight = () => {
const viewEl = document.getElementById('view');
if(!viewEl) return 460;
const viewHeight = viewEl.clientHeight;
const viewPadding = 40; // view的padding (20px * 2)
// 获取其他元素的高度
const metricsEl = document.querySelector('.dashboard-metrics-4col');
const trendEl = document.querySelector('.card');
const metricsHeight = metricsEl ? metricsEl.offsetHeight : 60;
const trendHeight = trendEl ? trendEl.offsetHeight : 260;
const gaps = 24; // margin-top间距 (12px * 2)
// 计算剩余高度
const remainingHeight = viewHeight - viewPadding - metricsHeight - trendHeight - gaps;
// 最小高度300px最大不超过剩余空间
return Math.max(300, Math.min(remainingHeight, 460));
};
// 设置审计框高度
const setAuditHeight = () => {
const height = calculateAuditHeight();
const pddCard = document.getElementById('audit-pdd-card');
const ytCard = document.getElementById('audit-yt-card');
if(pddCard) pddCard.style.height = height + 'px';
if(ytCard) ytCard.style.height = height + 'px';
};
// 初始设置高度
setAuditHeight();
// 监听窗口大小变化
if(window.__dashboardResizeHandler){
window.removeEventListener('resize', window.__dashboardResizeHandler);
}
window.__dashboardResizeHandler = () => setAuditHeight();
window.addEventListener('resize', window.__dashboardResizeHandler);
window.__pddParams = window.__pddParams || {};
window.__ytParams = window.__ytParams || {};
window.__auditBusy=false;
@ -426,29 +473,29 @@ const Dashboard = (() => {
window.__auditTimer=setInterval(refreshAll, 10000);
},0);
return `
<div style="display:flex;flex-direction:column;height:100%">
<div class="dashboard-metrics-4col" style="flex-shrink:0">
<div>
<div class="dashboard-metrics-4col">
${metricsCard('直通良品率', data.fpyRate || '—', 'success')}
${metricsCard('良品率', data.goodRate, 'success')}
${metricsCard('发货数量', data.shipments, 'warning')}
${metricsCard('不良数量', data.badCount ?? data.defects, 'danger')}
</div>
<div class="card" style="margin-top:12px;flex-shrink:0">
<div class="card" style="margin-top:12px">
<div style="font-weight:600;margin-bottom:8px">审计趋势最近30天</div>
<div style="height:220px;background:var(--bg);border:1px solid var(--border);border-radius:8px;position:relative">
<canvas id="trend-chart" style="width:100%;height:100%;cursor:crosshair"></canvas>
<div id="chart-tooltip" style="position:absolute;background:rgba(0,0,0,0.85);color:#fff;padding:6px 10px;border-radius:4px;font-size:12px;pointer-events:none;display:none;white-space:nowrap"></div>
</div>
</div>
<div class="grid cols-2" style="margin-top:12px;flex:1;min-height:0;align-items:stretch">
<div class="card" style="display:flex;flex-direction:column;min-height:0">
<div class="grid cols-2" style="margin-top:12px">
<div id="audit-pdd-card" class="card" style="display:flex;flex-direction:column;height:460px">
<div style="font-weight:600;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0">
<span>拼多多审计</span>
<input id="audit-date-pdd" type="date" style="padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;outline:none" />
</div>
<ul id="audit-pdd" class="list" style="overflow-y:auto;flex:1;min-height:0">${pddList}</ul>
</div>
<div class="card" style="display:flex;flex-direction:column;min-height:0">
<div id="audit-yt-card" class="card" style="display:flex;flex-direction:column;height:460px">
<div style="font-weight:600;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0">
<span>圆通审计</span>
<input id="audit-date-yt" type="date" style="padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;outline:none" />

View File

@ -3,18 +3,6 @@ Router.register('/settings', async () => {
const users = (me && me.role === 'superadmin') ? await API.adminUsers().catch(()=>({list:[]})) : {list:[]};
const userList = (users.list||[]).map(u=>`<li><span>${u.username}</span><span class="badge">${u.role}</span></li>`).join('') || '<li>暂无用户</li>';
const html = `<div class="grid cols-2">
<div class="card">
<div style="font-weight:600;margin-bottom:8px">外观设置</div>
<div class="field"><label>主题</label>
<select class="input" id="theme-select">
<option value="dark">深色</option>
<option value="light">浅色</option>
</select>
</div>
<div style="margin-top:12px">
<button class="btn" id="refresh-page-btn" style="width:100%">🔄 刷新页面</button>
</div>
</div>
<div class="card">
<div style="font-weight:600;margin-bottom:16px">账户设置</div>
<div style="margin-bottom:16px">
@ -124,28 +112,6 @@ Router.register('/settings', async () => {
` : ''}
</div>`;
setTimeout(() => {
// 主题切换
const themeSelect = document.getElementById('theme-select');
const savedTheme = localStorage.getItem('theme') || 'dark';
if(themeSelect) {
themeSelect.value = savedTheme;
themeSelect.addEventListener('change', (e) => {
const theme = e.target.value;
localStorage.setItem('theme', theme);
document.documentElement.setAttribute('data-theme', theme);
API.toast(`已切换到${theme === 'dark' ? '深色' : '浅色'}主题`);
// 触发自定义事件,通知其他组件主题已更改
window.dispatchEvent(new CustomEvent('themeChanged', { detail: { theme } }));
});
}
// 刷新页面按钮
const refreshBtn = document.getElementById('refresh-page-btn');
refreshBtn?.addEventListener('click', () => {
location.reload();
});
// 头像文件选择预览
const avatarFileInput = document.getElementById('avatar-file');
const previewAvatar = document.getElementById('preview-avatar');

View File

@ -0,0 +1,125 @@
Router.register('/shipments/summary', async () => {
setTimeout(async () => {
const queryBtn = document.getElementById('summary-query-btn');
const startDateInput = document.getElementById('start-date');
const endDateInput = document.getElementById('end-date');
const resultDiv = document.getElementById('summary-result');
// 设置默认日期为当前月份
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
if (startDateInput) {
startDateInput.value = firstDay.toISOString().split('T')[0];
}
if (endDateInput) {
endDateInput.value = lastDay.toISOString().split('T')[0];
}
const performQuery = async () => {
const startDate = startDateInput?.value;
const endDate = endDateInput?.value;
if (!startDate || !endDate) {
resultDiv.innerHTML = '<div class="error">请选择开始和结束日期</div>';
return;
}
try {
resultDiv.innerHTML = '<div>查询中...</div>';
const res = await fetch(`/api/shipments/summary?start=${encodeURIComponent(startDate)}&end=${encodeURIComponent(endDate)}`, {
credentials: 'include'
});
const data = await res.json();
if (data.records && data.records.length > 0) {
const totalQty = data.records.reduce((sum, r) => sum + (r.qty || 0), 0);
const tableRows = data.records.map(record => `
<tr>
<td>${record.date}</td>
<td>${record.qty}</td>
<td>${record.receiver || '-'}</td>
<td>${record.ts || '-'}</td>
</tr>
`).join('');
resultDiv.innerHTML = `
<div style="margin-bottom:16px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:8px">
<div style="display:flex;align-items:center;gap:16px">
<div>
<div style="color:var(--text-2);font-size:14px">查询时间段</div>
<div style="font-weight:600;margin-top:4px">${startDate} ${endDate}</div>
</div>
<div style="margin-left:auto">
<div style="color:var(--text-2);font-size:14px">总出货数量</div>
<div style="font-weight:600;font-size:24px;color:var(--primary);margin-top:4px">${totalQty}</div>
</div>
</div>
</div>
<div style="overflow-x:auto">
<table class="table">
<thead>
<tr>
<th>发货日期</th>
<th>数量</th>
<th>收货方</th>
<th>录入时间</th>
</tr>
</thead>
<tbody>
${tableRows}
</tbody>
</table>
</div>
`;
} else {
resultDiv.innerHTML = `
<div class="result-card error">
<div class="result-title">未找到记录</div>
<div class="result-item">该时间段内没有发货记录</div>
</div>
`;
}
} catch (e) {
resultDiv.innerHTML = `<div class="error">查询失败:${e.message}</div>`;
}
};
queryBtn?.addEventListener('click', performQuery);
// 自动执行一次查询
performQuery();
}, 0);
return `<div class="card">
<div style="font-weight:600;margin-bottom:16px">发货汇总信息查询</div>
<div style="margin-bottom:16px">
<div class="grid cols-2" style="margin-bottom:12px">
<div class="field">
<label>开始日期</label>
<input
id="start-date"
type="date"
class="input"
/>
</div>
<div class="field">
<label>结束日期</label>
<input
id="end-date"
type="date"
class="input"
/>
</div>
</div>
<button class="btn" id="summary-query-btn">查询</button>
</div>
<div id="summary-result"></div>
</div>`;
});