样式修改
This commit is contained in:
parent
290d9eccf6
commit
b9a6b80651
3
frontend/assets/moon.svg
Normal file
3
frontend/assets/moon.svg
Normal 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 |
@ -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
11
frontend/assets/sun.svg
Normal 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 |
@ -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;">
|
||||
|
||||
@ -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;
|
||||
})();
|
||||
@ -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" />
|
||||
|
||||
@ -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');
|
||||
|
||||
125
frontend/js/components/shipment-summary.js
Normal file
125
frontend/js/components/shipment-summary.js
Normal 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>`;
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user