修复仪表盘右下角三个模块:期初库存、采购需求、智能建议
0
.env.example
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/charts.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/colors.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/landing.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/products.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/prompts.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/flutter.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/nextjs.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/react-native.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/react.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/svelte.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/swiftui.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/stacks/vue.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/styles.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/typography.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/data/ux-guidelines.csv
Normal file → Executable file
0
.shared/ui-ux-pro-max/scripts/core.py
Normal file → Executable file
0
.shared/ui-ux-pro-max/scripts/search.py
Normal file → Executable file
0
.windsurf/workflows/ui-ux-pro-max.md
Normal file → Executable file
0
AI_ANALYSIS_README.md
Normal file → Executable file
0
add_customer_name_column.py
Normal file → Executable file
0
backend/.env.example
Normal file → Executable file
0
backend/ai_service.py
Normal file → Executable file
0
backend/api_ai.py
Normal file → Executable file
0
backend/requirements_ai.txt
Normal file → Executable file
0
backend/test_ai.py
Normal file → Executable file
0
check_reconciliations.py
Normal file → Executable file
0
cookies.txt
Normal file → Executable file
0
dashboard.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
0
date-dark.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
0
date.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
deploy/nginx/prod-mgmt.conf
Normal file → Executable file
4
deploy/systemd/prod-mgmt.service
Normal file → Executable file
@ -3,9 +3,9 @@ Description=Production Management Flask App
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/hyx/work/生产管理系统
|
||||
WorkingDirectory=/opt/生产管理系统
|
||||
# 使用Flask但启用多线程和Keep-Alive
|
||||
ExecStart=/home/hyx/work/.venv/bin/python server/app.py
|
||||
ExecStart=/usr/bin/python3 server/app.py
|
||||
Restart=always
|
||||
Environment=APP_SECRET=please-change-to-strong ADMIN_PASSWORD=change-this SUPERADMIN_USERNAME=zzh SUPERADMIN_PASSWORD=Zzh08165511 REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_DB=0 REDIS_PASSWORD=Zzh08165511
|
||||
|
||||
|
||||
0
frontend/assets/ai-icon.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 412 B |
0
frontend/assets/avatars/.gitignore
vendored
Normal file → Executable file
0
frontend/assets/calendar-dark.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
0
frontend/assets/calendar.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
0
frontend/assets/dashboard-enhancements.css
Normal file → Executable file
0
frontend/assets/dashboard.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
0
frontend/assets/favicon.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
0
frontend/assets/icon-tuxi.png
Normal file → Executable file
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
91
frontend/assets/login.css
Normal file → Executable file
@ -262,6 +262,10 @@ body {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.input-block:has(.password-toggle) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input,
|
||||
button {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
@ -304,11 +308,58 @@ button {
|
||||
box-shadow: 0 0 0 3px rgba(138, 43, 226, 0.2);
|
||||
}
|
||||
|
||||
.input-block:has(.password-toggle) .input {
|
||||
padding-right: 45px;
|
||||
}
|
||||
|
||||
.input {
|
||||
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.2),
|
||||
2px 2px 4px rgba(138, 43, 226, 0.1);
|
||||
}
|
||||
|
||||
/* 密码显示/隐藏按钮 */
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background: transparent;
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.password-toggle:active {
|
||||
transform: translateY(-50%) scale(0.95);
|
||||
}
|
||||
|
||||
.password-toggle::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.eye-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 验证码区域 */
|
||||
.captcha-block {
|
||||
display: flex;
|
||||
@ -526,6 +577,46 @@ button:disabled {
|
||||
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
/* 角色容器 */
|
||||
.character-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.character {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
filter: drop-shadow(0 4px 12px rgba(138, 43, 226, 0.3));
|
||||
transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
#char-head,
|
||||
#char-body,
|
||||
#char-hands ellipse,
|
||||
#char-eyes ellipse {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#char-mouth {
|
||||
transition: d 0.3s ease;
|
||||
}
|
||||
|
||||
#hand-left,
|
||||
#hand-right {
|
||||
transition: cx 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55),
|
||||
cy 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55),
|
||||
transform 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
#eye-left,
|
||||
#eye-right {
|
||||
transition: ry 0.15s ease, cx 0.3s ease;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
|
||||
0
frontend/assets/meituan-test.css
Normal file → Executable file
0
frontend/assets/mod.css
Normal file → Executable file
0
frontend/assets/mod.html
Normal file → Executable file
0
frontend/assets/moon.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 251 B After Width: | Height: | Size: 251 B |
0
frontend/assets/pdd.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
0
frontend/assets/sun.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 595 B After Width: | Height: | Size: 595 B |
0
frontend/assets/tuxi-station.png
Normal file → Executable file
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
0
frontend/assets/user-avatar.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
0
frontend/assets/xlsx.min.js
vendored
Normal file → Executable file
0
frontend/assets/yt.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
0
frontend/js/api.js
Normal file → Executable file
0
frontend/js/app.js
Normal file → Executable file
0
frontend/js/bundle.js
Normal file → Executable file
0
frontend/js/components/ai-report.js
Normal file → Executable file
0
frontend/js/components/bom.js
Normal file → Executable file
0
frontend/js/components/customer-order.js
Normal file → Executable file
187
frontend/js/components/dashboard.js
Normal file → Executable file
@ -642,12 +642,8 @@ const Dashboard = (() => {
|
||||
const txCounts = countByTime(txData);
|
||||
const mtCounts = countByTime(mtData);
|
||||
const actualMax = Math.max(...pddCounts, ...ytCounts, ...txCounts, ...mtCounts, 0);
|
||||
// 动态调整Y轴:让小数据更明显
|
||||
let maxCount;
|
||||
if (actualMax <= 2) maxCount = 3; // 数据是1-2时,Y轴最大值设为3,让数据占据1/3到2/3的高度
|
||||
else if (actualMax <= 5) maxCount = 8; // 数据是3-5时,Y轴最大值设为8
|
||||
else if (actualMax <= 10) maxCount = 15; // 数据是6-10时,Y轴最大值设为15
|
||||
else maxCount = actualMax;
|
||||
// 根据最高数据点动态调整Y轴最大值(增加10%的上边距)
|
||||
const maxCount = actualMax === 0 ? 5 : Math.ceil(actualMax * 1.1);
|
||||
const maxDigits = Math.max(maxCount.toString().length, 2);
|
||||
const dynamicLeftPadding = Math.max(50, 25 + maxDigits * 10);
|
||||
const padding = {left: dynamicLeftPadding, right: 20, top: 20, bottom: 30};
|
||||
@ -1946,6 +1942,52 @@ const Dashboard = (() => {
|
||||
productionFillEl.style.width = fillWidth + '%';
|
||||
productionFillEl.style.backgroundColor = color;
|
||||
}
|
||||
|
||||
// 更新期初库存数据
|
||||
if(dashRes.inventory) {
|
||||
const invTotal = document.getElementById('inventory-total');
|
||||
const invZero = document.getElementById('inventory-zero');
|
||||
const invLow = document.getElementById('inventory-low');
|
||||
const invQty = document.getElementById('inventory-qty');
|
||||
|
||||
if(invTotal) invTotal.textContent = dashRes.inventory.total_items || 0;
|
||||
if(invZero) invZero.textContent = dashRes.inventory.zero_stock || 0;
|
||||
if(invLow) invLow.textContent = dashRes.inventory.low_stock || 0;
|
||||
if(invQty) invQty.textContent = dashRes.inventory.total_qty || 0;
|
||||
}
|
||||
|
||||
// 更新采购需求数据
|
||||
if(dashRes.purchase) {
|
||||
const purTotal = document.getElementById('purchase-total');
|
||||
const purPending = document.getElementById('purchase-pending');
|
||||
const purUrgent = document.getElementById('purchase-urgent');
|
||||
const purRate = document.getElementById('purchase-rate');
|
||||
|
||||
if(purTotal) purTotal.textContent = dashRes.purchase.total_demands || 0;
|
||||
if(purPending) purPending.textContent = dashRes.purchase.pending || 0;
|
||||
if(purUrgent) purUrgent.textContent = dashRes.purchase.urgent || 0;
|
||||
if(purRate) purRate.textContent = (dashRes.purchase.completion_rate || 0) + '%';
|
||||
}
|
||||
|
||||
// 更新智能建议
|
||||
if(dashRes.smartSuggestions) {
|
||||
const suggestionsEl = document.getElementById('smart-suggestions');
|
||||
if(suggestionsEl) {
|
||||
if(dashRes.smartSuggestions.length > 0) {
|
||||
suggestionsEl.innerHTML = dashRes.smartSuggestions.map(s => `
|
||||
<div style="margin-bottom:10px;padding:12px;background:var(--bg);border-radius:10px;border:1px solid var(--border)">
|
||||
<div style="font-weight:600;margin-bottom:6px;color:var(--text);font-size:13px;display:flex;align-items:center;gap:6px">
|
||||
<span>${s.icon}</span>
|
||||
<span>${s.title}</span>
|
||||
</div>
|
||||
<div style="color:var(--text-2);font-size:12px;line-height:1.5">${s.message}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
suggestionsEl.innerHTML = '<div style="text-align:center;padding:30px 20px;background:var(--bg);border-radius:10px;border:1px solid var(--border)"><div style="font-size:28px;margin-bottom:8px">✨</div><div style="color:var(--text);font-size:14px;font-weight:600">暂无建议</div><div style="color:var(--text-2);font-size:12px;margin-top:4px">一切正常运行中</div></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
@ -2055,6 +2097,8 @@ const Dashboard = (() => {
|
||||
${metricsCard('发货数量', data.shipments, 'warning')}
|
||||
${todayProductionCard(data.todayPdd || 0, data.todayYt || 0, data.activePlatform || 'pdd')}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 趋势图 + 环形图 -->
|
||||
<div style="display:flex;gap:12px;margin-top:12px;flex-shrink:0">
|
||||
<!-- 左侧趋势卡片 -->
|
||||
@ -2135,15 +2179,15 @@ const Dashboard = (() => {
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧环形图卡片 -->
|
||||
<div class="card" style="flex:1;padding:20px;border-radius:16px;display:flex;flex-direction:column;background:var(--surface);min-height:520px">
|
||||
<div class="card" style="flex:1;padding:20px;border-radius:16px;display:flex;flex-direction:column;background:var(--surface);min-height:340px">
|
||||
<div style="font-weight:600;font-size:14px;margin-bottom:12px">产量占比</div>
|
||||
<div style="flex:1;display:grid;grid-template-columns:repeat(2,1fr);gap:16px;align-items:center">
|
||||
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;min-width:0">
|
||||
<div style="flex:1;display:grid;grid-template-columns:repeat(2,1fr);gap:16px;align-items:start;align-content:center">
|
||||
<div style="display:flex;flex-direction:column;align-items:center;justify-content:flex-start;position:relative;min-width:0">
|
||||
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:8px">审计(本月)</div>
|
||||
<div style="display:flex;align-items:center;justify-content:center;position:relative">
|
||||
<div style="display:flex;align-items:center;justify-content:center;position:relative;height:160px;width:160px;flex-shrink:0">
|
||||
<canvas id="donut-chart" style="width:160px;height:160px"></canvas>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:12px">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:12px;width:100%">
|
||||
<div style="text-align:center">
|
||||
<div id="donut-pdd-pct" style="font-size:20px;font-weight:700;color:#3B82F6">0%</div>
|
||||
<div style="display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--text-secondary);margin-top:2px">
|
||||
@ -2160,19 +2204,19 @@ const Dashboard = (() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;min-width:0">
|
||||
<div style="display:flex;flex-direction:column;align-items:center;justify-content:flex-start;position:relative;min-width:0">
|
||||
<div style="font-size:12px;color:var(--text-secondary);margin-bottom:8px">发货(按机种)</div>
|
||||
<div style="display:flex;align-items:center;justify-content:center;position:relative">
|
||||
<div style="display:flex;align-items:center;justify-content:center;position:relative;height:160px;width:160px;flex-shrink:0">
|
||||
<canvas id="shipment-donut-chart" style="width:160px;height:160px"></canvas>
|
||||
</div>
|
||||
<div id="shipment-donut-legend" style="display:flex;justify-content:center;gap:12px;flex-wrap:wrap;margin-top:12px"></div>
|
||||
<div id="shipment-donut-legend" style="display:flex;justify-content:center;gap:12px;flex-wrap:wrap;margin-top:12px;width:100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:12px;flex:1;min-height:400px;display:flex;gap:12px">
|
||||
<!-- 审计看板(压缩宽度) -->
|
||||
<div id="audit-card" class="card" style="flex:1;display:flex;flex-direction:column;min-height:260px;background:var(--surface);cursor:pointer" onclick="window.Dashboard.showAuditModal()">
|
||||
<div id="audit-card" class="card" style="flex:1;display:flex;flex-direction:column;min-height:260px;background:var(--surface);cursor:pointer;border-radius:16px" onclick="window.Dashboard.showAuditModal()">
|
||||
<div style="font-weight:600;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0;gap:8px">
|
||||
<span style="display:flex;align-items:center;gap:8px">审计看板 <span style="font-size:11px;color:var(--text-2);font-weight:400">点击查看完整数据</span></span>
|
||||
<div style="display:flex;gap:8px;align-items:center" onclick="event.stopPropagation()">
|
||||
@ -2197,8 +2241,117 @@ const Dashboard = (() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI智能报表 -->
|
||||
${AIReport.generateAICard()}
|
||||
<!-- 智能报表:期初库存和采购需求 -->
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;flex:2">
|
||||
<!-- 期初库存看板 -->
|
||||
<div class="card" onclick="location.hash='#/plan-mgmt/initial-stock'" style="padding:20px;border-radius:16px;transition:all 0.3s;cursor:pointer;background:var(--surface);border:1px solid var(--border);box-shadow:0 2px 8px rgba(0,0,0,0.04);display:flex;flex-direction:column">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--text-2);margin-bottom:4px">📦 库存管理</div>
|
||||
<div style="font-size:16px;font-weight:700;color:var(--text)">期初库存</div>
|
||||
</div>
|
||||
<div style="width:44px;height:44px;background:var(--bg);border-radius:12px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border)">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background:var(--bg);border-radius:12px;padding:14px;margin-bottom:12px;border:1px solid var(--border)">
|
||||
<div style="display:flex;align-items:baseline;gap:8px">
|
||||
<span style="font-size:36px;font-weight:800;color:var(--text);letter-spacing:-1px;line-height:1" id="inventory-total">${data.inventory?.total_items || 0}</span>
|
||||
<span style="font-size:14px;color:var(--text-2);font-weight:600">种物料</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
|
||||
<div style="background:var(--bg);border-radius:10px;padding:10px;border:1px solid var(--border)">
|
||||
<div style="font-size:11px;color:var(--text-2);margin-bottom:4px;font-weight:500">零库存</div>
|
||||
<div style="font-size:20px;font-weight:700;color:#ef4444" id="inventory-zero">${data.inventory?.zero_stock || 0}</div>
|
||||
</div>
|
||||
<div style="background:var(--bg);border-radius:10px;padding:10px;border:1px solid var(--border)">
|
||||
<div style="font-size:11px;color:var(--text-2);margin-bottom:4px;font-weight:500">低库存</div>
|
||||
<div style="font-size:20px;font-weight:700;color:#f59e0b" id="inventory-low">${data.inventory?.low_stock || 0}</div>
|
||||
</div>
|
||||
<div style="background:var(--bg);border-radius:10px;padding:10px;border:1px solid var(--border)">
|
||||
<div style="font-size:11px;color:var(--text-2);margin-bottom:4px;font-weight:500">总库存</div>
|
||||
<div style="font-size:20px;font-weight:700;color:#10b981" id="inventory-qty">${data.inventory?.total_qty || 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 采购需求看板 -->
|
||||
<div class="card" onclick="location.hash='#/plan-mgmt/purchase-demand'" style="padding:20px;border-radius:16px;transition:all 0.3s;cursor:pointer;background:var(--surface);border:1px solid var(--border);box-shadow:0 2px 8px rgba(0,0,0,0.04);display:flex;flex-direction:column">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--text-2);margin-bottom:4px">🛒 采购管理</div>
|
||||
<div style="font-size:16px;font-weight:700;color:var(--text)">采购需求</div>
|
||||
</div>
|
||||
<div style="width:44px;height:44px;background:var(--bg);border-radius:12px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border)">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" stroke-width="2">
|
||||
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<path d="M16 10a4 4 0 0 1-8 0"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background:var(--bg);border-radius:12px;padding:14px;margin-bottom:12px;border:1px solid var(--border)">
|
||||
<div style="display:flex;align-items:baseline;gap:8px">
|
||||
<span style="font-size:36px;font-weight:800;color:var(--text);letter-spacing:-1px;line-height:1" id="purchase-total">${data.purchase?.total_demands || 0}</span>
|
||||
<span style="font-size:14px;color:var(--text-2);font-weight:600">个需求</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
|
||||
<div style="background:var(--bg);border-radius:10px;padding:10px;border:1px solid var(--border)">
|
||||
<div style="font-size:11px;color:var(--text-2);margin-bottom:4px;font-weight:500">待处理</div>
|
||||
<div style="font-size:20px;font-weight:700;color:#f59e0b" id="purchase-pending">${data.purchase?.pending || 0}</div>
|
||||
</div>
|
||||
<div style="background:var(--bg);border-radius:10px;padding:10px;border:1px solid var(--border)">
|
||||
<div style="font-size:11px;color:var(--text-2);margin-bottom:4px;font-weight:500">紧急</div>
|
||||
<div style="font-size:20px;font-weight:700;color:#ef4444" id="purchase-urgent">${data.purchase?.urgent || 0}</div>
|
||||
</div>
|
||||
<div style="background:var(--bg);border-radius:10px;padding:10px;border:1px solid var(--border)">
|
||||
<div style="font-size:11px;color:var(--text-2);margin-bottom:4px;font-weight:500">完成率</div>
|
||||
<div style="font-size:20px;font-weight:700;color:#10b981" id="purchase-rate">${data.purchase?.completion_rate || 0}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 智能建议卡片 -->
|
||||
<div class="card" style="padding:20px;border-radius:16px;background:var(--surface);border:1px solid var(--border);box-shadow:0 2px 8px rgba(0,0,0,0.04);display:flex;flex-direction:column">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--text-2);margin-bottom:4px">💡 AI助手</div>
|
||||
<div style="font-size:16px;font-weight:700;color:var(--text)">智能建议</div>
|
||||
</div>
|
||||
<div style="width:44px;height:44px;background:var(--bg);border-radius:12px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border)">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" stroke-width="2">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z"></path>
|
||||
<path d="M2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="smart-suggestions" style="flex:1;overflow-y:auto;min-height:0">
|
||||
${(data.smartSuggestions || []).length > 0 ?
|
||||
data.smartSuggestions.map(s => `
|
||||
<div style="margin-bottom:10px;padding:12px;background:var(--bg);border-radius:10px;border:1px solid var(--border)">
|
||||
<div style="font-weight:600;margin-bottom:6px;color:var(--text);font-size:13px;display:flex;align-items:center;gap:6px">
|
||||
<span>${s.icon}</span>
|
||||
<span>${s.title}</span>
|
||||
</div>
|
||||
<div style="color:var(--text-2);font-size:12px;line-height:1.5">${s.message}</div>
|
||||
</div>
|
||||
`).join('') :
|
||||
'<div style="text-align:center;padding:30px 20px;background:var(--bg);border-radius:10px;border:1px solid var(--border)"><div style="font-size:28px;margin-bottom:8px">✨</div><div style="color:var(--text);font-size:14px;font-weight:600">暂无建议</div><div style="color:var(--text-2);font-size:12px;margin-top:4px">一切正常运行中</div></div>'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的旧卡片 -->
|
||||
<div id="audit-pdd-card" class="card" style="display:none;flex-direction:column;flex:1;min-height:0;background:var(--surface)">
|
||||
|
||||
0
frontend/js/components/defects.js
Normal file → Executable file
0
frontend/js/components/devices.js
Normal file → Executable file
0
frontend/js/components/environment.js
Normal file → Executable file
0
frontend/js/components/export.js
Normal file → Executable file
0
frontend/js/components/finished-goods-receipt.js
Normal file → Executable file
0
frontend/js/components/initial-stock.js
Normal file → Executable file
2
frontend/js/components/login.js
Normal file → Executable file
@ -10,7 +10,7 @@ Router.register('/login', async () => {
|
||||
const info = document.getElementById('user-info');
|
||||
if (info) info.textContent = user?.username || '未登录';
|
||||
}).catch(()=>{});
|
||||
location.href = '#/dashboard';
|
||||
window.location.hash = '#/dashboard';
|
||||
} catch(e) {}
|
||||
});
|
||||
}, 0);
|
||||
|
||||
0
frontend/js/components/material-purchase.js
Normal file → Executable file
0
frontend/js/components/meituan-test.js
Normal file → Executable file
0
frontend/js/components/menu-search.js
Normal file → Executable file
0
frontend/js/components/notifications.js
Normal file → Executable file
0
frontend/js/components/operations-log.js
Normal file → Executable file
0
frontend/js/components/outsourcing-material-issue.js
Normal file → Executable file
0
frontend/js/components/outsourcing-orders.js
Normal file → Executable file
0
frontend/js/components/outsourcing-wip-stock.js
Normal file → Executable file
0
frontend/js/components/personnel.js
Normal file → Executable file
0
frontend/js/components/product-intro.js
Normal file → Executable file
0
frontend/js/components/production.js
Normal file → Executable file
0
frontend/js/components/purchase-demand.js
Normal file → Executable file
0
frontend/js/components/qa.js
Normal file → Executable file
0
frontend/js/components/reconciliation.js
Normal file → Executable file
0
frontend/js/components/settings.js
Normal file → Executable file
0
frontend/js/components/sidebar.js
Normal file → Executable file
0
frontend/js/components/work-order.js
Normal file → Executable file
0
frontend/js/router.js
Normal file → Executable file
0
frontend/js/utils/memory-monitor.js
Normal file → Executable file
458
frontend/login.html
Normal file → Executable file
@ -4,95 +4,128 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>登录 - 韬智生产管理系统</title>
|
||||
<link rel="icon" type="image/svg+xml" href="./assets/favicon.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="../icon.ico" />
|
||||
<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/login.css?v=20251231-1425" />
|
||||
<link rel="stylesheet" href="./assets/login-animated.css?v=20251231-1427" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-wrapper">
|
||||
<div class="container">
|
||||
<div class="left">
|
||||
<div class="form-header">
|
||||
<h1 class="system-title">韬智生产管理系统</h1>
|
||||
<p class="system-subtitle">Production Management System</p>
|
||||
<div class="login-page">
|
||||
<!-- 左侧角色动画区域 -->
|
||||
<div class="characters-section">
|
||||
<div class="brand-logo">
|
||||
<img src="../icon.ico" alt="Logo" class="logo-icon" />
|
||||
<span>韬智生产管理系统</span>
|
||||
</div>
|
||||
|
||||
<div class="characters-container" id="characters-container">
|
||||
<!-- 紫色角色 -->
|
||||
<div class="character purple" id="char-purple">
|
||||
<div class="eyes">
|
||||
<div class="eye" id="purple-eye-left">
|
||||
<div class="pupil" id="purple-pupil-left"></div>
|
||||
</div>
|
||||
<div class="eye" id="purple-eye-right">
|
||||
<div class="pupil" id="purple-pupil-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form" onsubmit="return false;">
|
||||
<div class="input-block">
|
||||
<input class="input" type="text" id="username" required autocomplete="username">
|
||||
|
||||
<!-- 黑色角色 -->
|
||||
<div class="character black" id="char-black">
|
||||
<div class="eyes">
|
||||
<div class="eye" id="black-eye-left">
|
||||
<div class="pupil" id="black-pupil-left"></div>
|
||||
</div>
|
||||
<div class="eye" id="black-eye-right">
|
||||
<div class="pupil" id="black-pupil-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 橙色角色 -->
|
||||
<div class="character orange" id="char-orange">
|
||||
<div class="eyes-simple">
|
||||
<div class="pupil-simple" id="orange-pupil-left"></div>
|
||||
<div class="pupil-simple" id="orange-pupil-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 黄色角色 -->
|
||||
<div class="character yellow" id="char-yellow">
|
||||
<div class="eyes-simple">
|
||||
<div class="pupil-simple" id="yellow-pupil-left"></div>
|
||||
<div class="pupil-simple" id="yellow-pupil-right"></div>
|
||||
</div>
|
||||
<div class="mouth"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-links">
|
||||
<a href="#">隐私政策</a>
|
||||
<a href="#">服务条款</a>
|
||||
<a href="#">联系我们</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧登录表单区域 -->
|
||||
<div class="login-section">
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h1>欢迎回来!</h1>
|
||||
<p>请输入您的登录信息</p>
|
||||
</div>
|
||||
|
||||
<form class="login-form" onsubmit="return false;">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" class="form-input" placeholder="请输入用户名" required autocomplete="username">
|
||||
</div>
|
||||
<div class="input-block">
|
||||
<input class="input" type="password" id="password" required autocomplete="current-password">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<div class="password-wrapper">
|
||||
<input type="text" id="password" class="form-input" placeholder="请输入密码" required autocomplete="current-password">
|
||||
<button type="button" class="password-toggle" id="password-toggle" aria-label="隐藏密码">
|
||||
<svg class="eye-icon eye-open" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
<svg class="eye-icon eye-closed" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:none;">
|
||||
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
|
||||
<line x1="1" y1="1" x2="23" y2="23"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-block captcha-block">
|
||||
<input class="input captcha-input" type="text" id="captcha" required maxlength="4" autocomplete="off">
|
||||
<label for="captcha">验证码</label>
|
||||
|
||||
<div class="form-group captcha-group">
|
||||
<div class="captcha-input-wrapper">
|
||||
<label for="captcha">验证码</label>
|
||||
<input type="text" id="captcha" class="form-input" placeholder="请输入验证码" required maxlength="4" autocomplete="off">
|
||||
</div>
|
||||
<div class="captcha-image-wrapper" id="captcha-image-wrapper" title="点击刷新验证码">
|
||||
<img id="captcha-image" class="captcha-image" alt="验证码" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="error-message" class="error-message" style="display:none;"></div>
|
||||
<div class="input-block">
|
||||
<span class="forgot"><a href="#">© 2025 韬智科技</a></span>
|
||||
<button type="button" id="login-btn">
|
||||
<span id="login-text">登录</span>
|
||||
<span id="login-loader" class="btn-loader" style="display:none;">
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button type="button" id="login-btn" class="login-button">
|
||||
<span id="login-text">登录</span>
|
||||
<span id="login-loader" class="btn-loader" style="display:none;">
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="form-footer">
|
||||
<span class="copyright">© 2025 韬智科技</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="img">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 731.67004 550.61784" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M0,334.13393c0,.66003,.53003,1.19,1.19006,1.19H730.48004c.65997,0,1.19-.52997,1.19-1.19,0-.65997-.53003-1.19-1.19-1.19H1.19006c-.66003,0-1.19006,.53003-1.19006,1.19Z" fill="#3f3d56"></path>
|
||||
<polygon points="466.98463 81.60598 470.81118 130.55703 526.26809 107.39339 494.98463 57.60598 466.98463 81.60598" fill="#a0616a"></polygon>
|
||||
<circle cx="465.32321" cy="55.18079" r="41.33858" fill="#a0616a"></circle>
|
||||
<polygon points="387.98463 440.60598 394.98463 503.39339 345.98463 496.60598 361.98463 438.60598 387.98463 440.60598" fill="#a0616a"></polygon>
|
||||
<polygon points="578.98463 449.60598 585.98463 512.39339 536.98463 505.60598 552.98463 447.60598 578.98463 449.60598" fill="#a0616a"></polygon>
|
||||
<path d="M462.48463,260.10598c-.66897,0-54.14584,2.68515-89.47714,4.46286-16.72275,.84141-29.45202,15.31527-28.15459,32.00884l12.63173,162.5283,36,1,.87795-131,71.12205,4-3-73Z" fill="#2f2e41"></path>
|
||||
<path d="M619.48463,259.10598s9,69,2,76c-7,7-226.5-5.5-226.5-5.5,0,0,48.15354-69.53704,56.82677-71.51852,8.67323-1.98148,146.67323-8.98148,146.67323-8.98148l21,10Z" fill="#2f2e41"></path>
|
||||
<path id="uuid-91047c5b-47d7-4179-8a16-40bd6d529b28-203" d="M335.12666,172.23337c-8.35907-11.69074-9.10267-25.48009-1.66174-30.79863,7.44093-5.31854,20.24665-.15219,28.60713,11.54383,3.40375,4.62627,5.65012,10.00041,6.55111,15.67279l34.79215,49.9814-19.8001,13.70807-35.7745-48.83421c-5.07753-2.68845-9.43721-6.55406-12.71405-11.27326Z" fill="#a0616a"></path>
|
||||
<path d="M464.98463,112.60598l51-21,96,148s-67,15-90,18c-23,3-49-9-49-9l-8-136Z" fill="#5e7eb6"></path>
|
||||
<path d="M526.98463,137.60598l-18.5-57.70866,24,18.20866s68,45,68,64c0,19,21,77,21,77,0,0,23.5,19.5,15.5,37.5-8,18,10.5,15.5,12.5,28.5,2,13-28.5,30.5-28.5,30.5,0,0-7.5-73.5-31.5-73.5-24,0-62.5-124.5-62.5-124.5Z" fill="#3f3d56"></path>
|
||||
<path d="M468.56831,111.13035l-25.08368,9.97563s4,70,8,76c4,6,18,38,18,38v10.42913s-28,8.57087-27,13.57087c1,5,66,19,66,19,0,0-13-40-21-53-8-13-18.91632-113.97563-18.91632-113.97563Z" fill="#3f3d56"></path>
|
||||
<path d="M452.48463,121.10598s-29-4-34,30c-5,34-1.82283,38.5-1.82283,38.5l-8.17717,19.5-27-30-26,17s47,76,66,74c19-2,47-57,47-57l-16-92Z" fill="#3f3d56"></path>
|
||||
<path d="M597.32321,270.14478l-14.83858,209.96121-38.5-1.5s-8.5-198.5-8.5-201.5c0-3,4-20,29-21,25-1,32.83858,14.03879,32.83858,14.03879Z" fill="#2f2e41"></path>
|
||||
<path d="M541.48463,484.10598s20-6,23-2c3,4,20,6,20,6l5,49s-14,10-16,12-55,4-56-8c-1-12,14-27,14-27l10-30Z" fill="#2f2e41"></path>
|
||||
<path d="M394.48463,470.10598s6-5,8,9c2,14,9,37-1,40-10,3-110,4-110-5v-9l9-7,18.00394-2.869s34.99606-32.131,38.99606-32.131c4,0,17,13,17,13l20-6Z" fill="#2f2e41"></path>
|
||||
<path d="M505.98463,77.60598s-20-24-28-22-3,5-3,5l-20-22s-16-6-31,13c0,0-9-16,0-25,9-9,12-8,14-13,2-5,16-9,16-9,0,0-.80315-7.19685,3.59843-3.59843s15.3937,3.59843,15.3937,3.59843c0,0,.06299-4,4.53543,0,4.47244,4,9.47244,2,9.47244,2,0,0,0,6.92126,3.5,6.96063,3.5,.03937,9.5-4.96063,10.5-.96063,1,4,8,6,9,18,1,12-4,47-4,47Z" fill="#2f2e41"></path>
|
||||
<g>
|
||||
<path d="M342.99463,178.84874l-114.2362,78.82694c-3.94205,2.72015-9.36214,1.72624-12.08229-2.21581l-32.16176-46.60891c-2.72015-3.94205-1.7259-9.36208,2.21615-12.08223l114.2362-78.82694c3.94205-2.72015,9.36214-1.72624,12.08229,2.21581l32.16176,46.60891c2.72015,3.94205,1.7259,9.36208-2.21615,12.08223Z" fill="#fff"></path>
|
||||
<path d="M312.83914,120.30274l32.16148,46.6085c2.64627,3.83499,1.68408,9.08121-2.15091,11.72749l-56.06388,38.68602c-14.78562-4.04015-28.2774-13.11486-37.66263-26.71596-6.14766-8.9092-9.85314-18.77211-11.26649-28.80885l63.25494-43.6481c3.83499-2.64627,9.08121-1.68408,11.72749,2.15091Z" fill="#e6e6e6"></path>
|
||||
<path d="M223.84012,260.20913c-3.0791,0-6.10938-1.46094-7.9873-4.18066l-32.16211-46.60938c-1.4668-2.12695-2.01758-4.7002-1.5498-7.24805,.4668-2.54785,1.89551-4.75879,4.02246-6.22559l114.23535-78.82715c4.39746-3.03223,10.44043-1.92285,13.47363,2.4707l32.16211,46.60938c1.4668,2.12695,2.01758,4.7002,1.5498,7.24805-.4668,2.54688-1.89551,4.75879-4.02148,6.22559l-114.23633,78.82715c-1.67578,1.15527-3.59082,1.70996-5.48633,1.70996Zm82.04785-142.80176c-1.50391,0-3.02344,.44043-4.35254,1.35742l-114.23633,78.82715c-1.6875,1.16309-2.82031,2.91797-3.19141,4.94043-.37109,2.02148,.06543,4.06445,1.22949,5.75l32.16211,46.60938c2.40625,3.48633,7.20215,4.36816,10.69043,1.96094l114.2373-78.82715c1.68652-1.16309,2.81934-2.91797,3.19043-4.94043,.37109-2.02148-.06543-4.06445-1.22949-5.75l-32.16211-46.60938c-1.48926-2.1582-3.89453-3.31836-6.33789-3.31836Z" fill="#3f3d56"></path>
|
||||
<path d="M224.6666,236.93718c-2.89521,1.9978-3.6253,5.97848-1.6275,8.87369,1.9978,2.89521,5.97848,3.6253,8.87369,1.6275l11.76134-8.11573c2.89521-1.9978,3.6253-5.97848,1.6275-8.87369-1.9978-2.89521-5.97848-3.6253-8.87369-1.6275l-11.76134,8.11573Z" fill="#5e7eb6"></path>
|
||||
<path d="M232.63862,171.91114c-4.56802,3.15209-5.71978,9.43286-2.56769,14.00088,3.15209,4.56802,9.43252,5.71972,14.00054,2.56763l18.29546-12.6245c4.56802-3.15209,5.72007-9.43245,2.56797-14.00047-3.15209-4.56802-9.4328-5.72013-14.00082-2.56804l-18.29546,12.6245Z" fill="#5e7eb6"></path>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M340.25926,185.80874H201.4659c-4.78947,0-8.68608-3.89636-8.68608-8.68583v-56.62834c0-4.78947,3.89661-8.68583,8.68608-8.68583h138.79336c4.78947,0,8.68608,3.89636,8.68608,8.68583v56.62834c0,4.78947-3.89661,8.68583-8.68608,8.68583Z" fill="#fff"></path>
|
||||
<path d="M348.69017,120.49482v56.62784c0,4.65939-3.77152,8.43091-8.43091,8.43091h-68.11583c-9.87497-11.72273-15.82567-26.8544-15.82567-43.37931,0-10.82439,2.55172-21.04674,7.08876-30.11034h76.85275c4.65939,0,8.43091,3.77152,8.43091,8.43091Z" fill="#e6e6e6"></path>
|
||||
<path d="M340.25907,186.80874H201.4661c-5.34082,0-9.68652-4.34473-9.68652-9.68555v-56.62891c0-5.34082,4.3457-9.68555,9.68652-9.68555h138.79297c5.34082,0,9.68652,4.34473,9.68652,9.68555v56.62891c0,5.34082-4.3457,9.68555-9.68652,9.68555ZM201.4661,112.80874c-4.23828,0-7.68652,3.44727-7.68652,7.68555v56.62891c0,4.23828,3.44824,7.68555,7.68652,7.68555h138.79297c4.23828,0,7.68652-3.44727,7.68652-7.68555v-56.62891c0-4.23828-3.44824-7.68555-7.68652-7.68555H201.4661Z" fill="#3f3d56"></path>
|
||||
<path d="M209.87637,166.41564c-3.51759,0-6.37931,2.86172-6.37931,6.37931s2.86172,6.37931,6.37931,6.37931h14.28966c3.51759,0,6.37931-2.86172,6.37931-6.37931s-2.86172-6.37931-6.37931-6.37931h-14.28966Z" fill="#5e7eb6"></path>
|
||||
<path d="M253.36907,117.42253c-5.55,0-10.06511,4.51536-10.06511,10.06536s4.51511,10.06486,10.06511,10.06486h22.22841c5.55,0,10.06511-4.51486,10.06511-10.06486s-4.51511-10.06536-10.06511-10.06536h-22.22841Z" fill="#5e7eb6"></path>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M456.25926,381.80874h-138.79336c-4.78947,0-8.68608-3.89636-8.68608-8.68583v-56.62834c0-4.78947,3.89661-8.68583,8.68608-8.68583h138.79336c4.78947,0,8.68608,3.89636,8.68608,8.68583v56.62834c0,4.78947-3.89661,8.68583-8.68608,8.68583Z" fill="#fff"></path>
|
||||
<path d="M464.69017,316.49482v56.62784c0,4.65939-3.77152,8.43091-8.43091,8.43091h-68.11583c-9.87497-11.72273-15.82567-26.8544-15.82567-43.37931,0-10.82439,2.55172-21.04674,7.08876-30.11034h76.85275c4.65939,0,8.43091,3.77152,8.43091,8.43091Z" fill="#e6e6e6"></path>
|
||||
<path d="M456.25907,382.80874h-138.79297c-5.34082,0-9.68652-4.34473-9.68652-9.68555v-56.62891c0-5.34082,4.3457-9.68555,9.68652-9.68555h138.79297c5.34082,0,9.68652,4.34473,9.68652,9.68555v56.62891c0,5.34082-4.3457,9.68555-9.68652,9.68555Zm-138.79297-74c-4.23828,0-7.68652,3.44727-7.68652,7.68555v56.62891c0,4.23828,3.44824,7.68555,7.68652,7.68555h138.79297c4.23828,0,7.68652-3.44727,7.68652-7.68555v-56.62891c0-4.23828-3.44824-7.68555-7.68652-7.68555h-138.79297Z" fill="#3f3d56"></path>
|
||||
<path d="M325.87637,362.41564c-3.51759,0-6.37931,2.86172-6.37931,6.37931s2.86172,6.37931,6.37931,6.37931h14.28966c3.51759,0,6.37931-2.86172,6.37931-6.37931s-2.86172-6.37931-6.37931-6.37931h-14.28966Z" fill="#5e7eb6"></path>
|
||||
<path d="M369.36907,313.42253c-5.55,0-10.06511,4.51536-10.06511,10.06536s4.51511,10.06486,10.06511,10.06486h22.22841c5.55,0,10.06511-4.51486,10.06511-10.06486s-4.51511-10.06536-10.06511-10.06536h-22.22841Z" fill="#5e7eb6"></path>
|
||||
</g>
|
||||
<path id="uuid-c026fd96-7d81-4b34-bb39-0646c0e08e96-204" d="M465.67391,331.01678c-12.74718,6.63753-26.5046,5.44058-30.72743-2.67249-4.22283-8.11308,2.6878-20.06802,15.44041-26.70621,5.05777-2.72156,10.69376-4.19231,16.43644-4.28916l54.36547-27.44139,10.79681,21.52636-53.36733,28.57487c-3.37375,4.65048-7.81238,8.42516-12.94437,11.00803Z" fill="#a0616a"></path>
|
||||
<path d="M527.48463,97.10598s56-3,68,27c12,30,22,128,22,128l-122,66.37402-21-32.37402,82-64-29-125Z" fill="#3f3d56"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -106,7 +139,7 @@ async function checkAuth() {
|
||||
if (response.ok) {
|
||||
const user = await response.json();
|
||||
if (user && user.username) {
|
||||
window.location.replace('/index.html#/dashboard');
|
||||
window.location.replace('./index.html#/dashboard');
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
@ -120,6 +153,7 @@ checkAuth();
|
||||
// 登录处理
|
||||
const usernameInput = document.getElementById('username');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const passwordToggle = document.getElementById('password-toggle');
|
||||
const captchaInput = document.getElementById('captcha');
|
||||
const captchaImage = document.getElementById('captcha-image');
|
||||
const captchaImageWrapper = document.getElementById('captcha-image-wrapper');
|
||||
@ -227,7 +261,7 @@ async function handleLogin() {
|
||||
if (result.ok) {
|
||||
// 登录成功,跳转到主页面
|
||||
// 使用 replace 避免 Safari 缓存问题
|
||||
window.location.replace('/index.html#/dashboard');
|
||||
window.location.replace('./index.html#/dashboard');
|
||||
} else {
|
||||
throw new Error('登录失败');
|
||||
}
|
||||
@ -263,6 +297,284 @@ captchaInput.addEventListener('keypress', (e) => {
|
||||
|
||||
// 自动聚焦用户名输入框
|
||||
usernameInput.focus();
|
||||
|
||||
// ============ 角色动画系统 ============
|
||||
let mouseX = 0, mouseY = 0;
|
||||
let isTyping = false;
|
||||
let showPassword = false;
|
||||
|
||||
// 获取所有角色元素
|
||||
const charPurple = document.getElementById('char-purple');
|
||||
const charBlack = document.getElementById('char-black');
|
||||
const charOrange = document.getElementById('char-orange');
|
||||
const charYellow = document.getElementById('char-yellow');
|
||||
|
||||
const purpleEyeLeft = document.getElementById('purple-eye-left');
|
||||
const purpleEyeRight = document.getElementById('purple-eye-right');
|
||||
const purplePupilLeft = document.getElementById('purple-pupil-left');
|
||||
const purplePupilRight = document.getElementById('purple-pupil-right');
|
||||
|
||||
const blackEyeLeft = document.getElementById('black-eye-left');
|
||||
const blackEyeRight = document.getElementById('black-eye-right');
|
||||
const blackPupilLeft = document.getElementById('black-pupil-left');
|
||||
const blackPupilRight = document.getElementById('black-pupil-right');
|
||||
|
||||
const orangePupilLeft = document.getElementById('orange-pupil-left');
|
||||
const orangePupilRight = document.getElementById('orange-pupil-right');
|
||||
|
||||
const yellowPupilLeft = document.getElementById('yellow-pupil-left');
|
||||
const yellowPupilRight = document.getElementById('yellow-pupil-right');
|
||||
|
||||
// 鼠标跟踪 - 使用requestAnimationFrame节流
|
||||
let rafId = null;
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
mouseX = e.clientX;
|
||||
mouseY = e.clientY;
|
||||
|
||||
if (!rafId) {
|
||||
rafId = requestAnimationFrame(() => {
|
||||
updateCharacterPositions();
|
||||
updatePupilPositions();
|
||||
rafId = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 计算瞳孔位置
|
||||
function calculatePupilPosition(element, maxDistance = 5) {
|
||||
if (!element) return { x: 0, y: 0 };
|
||||
const rect = element.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
|
||||
const deltaX = mouseX - centerX;
|
||||
const deltaY = mouseY - centerY;
|
||||
const distance = Math.min(Math.sqrt(deltaX ** 2 + deltaY ** 2), maxDistance);
|
||||
const angle = Math.atan2(deltaY, deltaX);
|
||||
|
||||
return {
|
||||
x: Math.cos(angle) * distance,
|
||||
y: Math.sin(angle) * distance
|
||||
};
|
||||
}
|
||||
|
||||
// 计算身体倾斜角度和脸部位置
|
||||
function calculatePosition(element) {
|
||||
if (!element) return { faceX: 0, faceY: 0, bodySkew: 0 };
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 3; // 聚焦头部区域
|
||||
|
||||
const deltaX = mouseX - centerX;
|
||||
const deltaY = mouseY - centerY;
|
||||
|
||||
// 脸部移动(用于眼睛位置)
|
||||
const faceX = Math.max(-15, Math.min(15, deltaX / 20));
|
||||
const faceY = Math.max(-10, Math.min(10, deltaY / 30));
|
||||
|
||||
// 身体倾斜(负值表示向鼠标方向倾斜)
|
||||
const bodySkew = Math.max(-6, Math.min(6, -deltaX / 120));
|
||||
|
||||
return { faceX, faceY, bodySkew };
|
||||
}
|
||||
|
||||
// 更新角色身体位置
|
||||
function updateCharacterPositions() {
|
||||
const hasPassword = passwordInput.value.length > 0;
|
||||
const isPasswordVisible = passwordInput.getAttribute('type') === 'text';
|
||||
|
||||
// 紫色角色
|
||||
const purplePos = calculatePosition(charPurple);
|
||||
if (hasPassword && isPasswordVisible) {
|
||||
// 密码可见时 - 站高,不倾斜
|
||||
charPurple.style.height = '440px';
|
||||
charPurple.style.transform = `skewX(0deg)`;
|
||||
} else if (isTyping || (hasPassword && !isPasswordVisible)) {
|
||||
// 输入邮箱或密码隐藏时 - 站高并倾斜
|
||||
charPurple.style.height = '440px';
|
||||
charPurple.style.transform = `skewX(${purplePos.bodySkew - 12}deg) translateX(40px)`;
|
||||
} else {
|
||||
// 正常状态 - 根据鼠标晃动
|
||||
charPurple.style.height = '400px';
|
||||
charPurple.style.transform = `skewX(${purplePos.bodySkew}deg)`;
|
||||
}
|
||||
|
||||
// 黑色角色
|
||||
const blackPos = calculatePosition(charBlack);
|
||||
if (hasPassword && isPasswordVisible) {
|
||||
// 密码可见时 - 不倾斜
|
||||
charBlack.style.transform = `skewX(0deg)`;
|
||||
} else if (isLookingAtEachOther) {
|
||||
// 互看时 - 倾斜并移动
|
||||
charBlack.style.transform = `skewX(${blackPos.bodySkew * 1.5 + 10}deg) translateX(20px)`;
|
||||
} else if (isTyping || (hasPassword && !isPasswordVisible)) {
|
||||
charBlack.style.transform = `skewX(${blackPos.bodySkew * 1.5}deg)`;
|
||||
} else {
|
||||
charBlack.style.transform = `skewX(${blackPos.bodySkew}deg)`;
|
||||
}
|
||||
|
||||
// 橙色和黄色角色
|
||||
const orangePos = calculatePosition(charOrange);
|
||||
const yellowPos = calculatePosition(charYellow);
|
||||
|
||||
if (hasPassword && isPasswordVisible) {
|
||||
// 密码可见时 - 不倾斜
|
||||
charOrange.style.transform = `skewX(0deg)`;
|
||||
charYellow.style.transform = `skewX(0deg)`;
|
||||
} else {
|
||||
charOrange.style.transform = `skewX(${orangePos.bodySkew}deg)`;
|
||||
charYellow.style.transform = `skewX(${yellowPos.bodySkew}deg)`;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新瞳孔位置
|
||||
function updatePupilPositions() {
|
||||
const hasPassword = passwordInput.value.length > 0;
|
||||
const isPasswordVisible = passwordInput.getAttribute('type') === 'text';
|
||||
|
||||
// 根据状态决定眼睛方向
|
||||
if (isLookingAtEachOther) {
|
||||
// 输入邮箱时 - 互相看向对方
|
||||
purplePupilLeft.style.transform = 'translate(3px, 4px)';
|
||||
purplePupilRight.style.transform = 'translate(3px, 4px)';
|
||||
blackPupilLeft.style.transform = 'translate(0px, -4px)';
|
||||
blackPupilRight.style.transform = 'translate(0px, -4px)';
|
||||
// 橙色和黄色跟随鼠标
|
||||
const orangeLeftPos = calculatePupilPosition(orangePupilLeft, 5);
|
||||
const orangeRightPos = calculatePupilPosition(orangePupilRight, 5);
|
||||
orangePupilLeft.style.transform = `translate(${orangeLeftPos.x}px, ${orangeLeftPos.y}px)`;
|
||||
orangePupilRight.style.transform = `translate(${orangeRightPos.x}px, ${orangeRightPos.y}px)`;
|
||||
const yellowLeftPos = calculatePupilPosition(yellowPupilLeft, 5);
|
||||
const yellowRightPos = calculatePupilPosition(yellowPupilRight, 5);
|
||||
yellowPupilLeft.style.transform = `translate(${yellowLeftPos.x}px, ${yellowLeftPos.y}px)`;
|
||||
yellowPupilRight.style.transform = `translate(${yellowRightPos.x}px, ${yellowRightPos.y}px)`;
|
||||
} else if (hasPassword && isPasswordVisible) {
|
||||
// 密码可见时 - 所有小人看向左侧
|
||||
purplePupilLeft.style.transform = 'translate(-5px, 0px)';
|
||||
purplePupilRight.style.transform = 'translate(-5px, 0px)';
|
||||
blackPupilLeft.style.transform = 'translate(-4px, 0px)';
|
||||
blackPupilRight.style.transform = 'translate(-4px, 0px)';
|
||||
orangePupilLeft.style.transform = 'translate(-5px, 0px)';
|
||||
orangePupilRight.style.transform = 'translate(-5px, 0px)';
|
||||
yellowPupilLeft.style.transform = 'translate(-5px, 0px)';
|
||||
yellowPupilRight.style.transform = 'translate(-5px, 0px)';
|
||||
} else if (hasPassword && !isPasswordVisible) {
|
||||
// 密码隐藏时 - 所有小人看向右下侧
|
||||
purplePupilLeft.style.transform = 'translate(4px, 4px)';
|
||||
purplePupilRight.style.transform = 'translate(4px, 4px)';
|
||||
blackPupilLeft.style.transform = 'translate(4px, 4px)';
|
||||
blackPupilRight.style.transform = 'translate(4px, 4px)';
|
||||
orangePupilLeft.style.transform = 'translate(5px, 4px)';
|
||||
orangePupilRight.style.transform = 'translate(5px, 4px)';
|
||||
yellowPupilLeft.style.transform = 'translate(5px, 4px)';
|
||||
yellowPupilRight.style.transform = 'translate(5px, 4px)';
|
||||
} else {
|
||||
// 正常状态 - 跟随鼠标
|
||||
const purpleLeftPos = calculatePupilPosition(purpleEyeLeft, 5);
|
||||
const purpleRightPos = calculatePupilPosition(purpleEyeRight, 5);
|
||||
purplePupilLeft.style.transform = `translate(${purpleLeftPos.x}px, ${purpleLeftPos.y}px)`;
|
||||
purplePupilRight.style.transform = `translate(${purpleRightPos.x}px, ${purpleRightPos.y}px)`;
|
||||
|
||||
const blackLeftPos = calculatePupilPosition(blackEyeLeft, 4);
|
||||
const blackRightPos = calculatePupilPosition(blackEyeRight, 4);
|
||||
blackPupilLeft.style.transform = `translate(${blackLeftPos.x}px, ${blackLeftPos.y}px)`;
|
||||
blackPupilRight.style.transform = `translate(${blackRightPos.x}px, ${blackRightPos.y}px)`;
|
||||
|
||||
const orangeLeftPos = calculatePupilPosition(orangePupilLeft, 5);
|
||||
const orangeRightPos = calculatePupilPosition(orangePupilRight, 5);
|
||||
orangePupilLeft.style.transform = `translate(${orangeLeftPos.x}px, ${orangeLeftPos.y}px)`;
|
||||
orangePupilRight.style.transform = `translate(${orangeRightPos.x}px, ${orangeRightPos.y}px)`;
|
||||
|
||||
const yellowLeftPos = calculatePupilPosition(yellowPupilLeft, 5);
|
||||
const yellowRightPos = calculatePupilPosition(yellowPupilRight, 5);
|
||||
yellowPupilLeft.style.transform = `translate(${yellowLeftPos.x}px, ${yellowLeftPos.y}px)`;
|
||||
yellowPupilRight.style.transform = `translate(${yellowRightPos.x}px, ${yellowRightPos.y}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
// 眨眼动画
|
||||
function blinkEye(eyeLeft, eyeRight) {
|
||||
eyeLeft.style.height = '2px';
|
||||
eyeRight.style.height = '2px';
|
||||
setTimeout(() => {
|
||||
eyeLeft.style.height = '';
|
||||
eyeRight.style.height = '';
|
||||
}, 150);
|
||||
}
|
||||
|
||||
// 随机眨眼
|
||||
function scheduleRandomBlink(eyeLeft, eyeRight) {
|
||||
const delay = Math.random() * 4000 + 3000;
|
||||
setTimeout(() => {
|
||||
if (Math.random() > 0.3) {
|
||||
blinkEye(eyeLeft, eyeRight);
|
||||
}
|
||||
scheduleRandomBlink(eyeLeft, eyeRight);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
scheduleRandomBlink(purpleEyeLeft, purpleEyeRight);
|
||||
scheduleRandomBlink(blackEyeLeft, blackEyeRight);
|
||||
|
||||
// 添加isLookingAtEachOther状态
|
||||
let isLookingAtEachOther = false;
|
||||
|
||||
// 输入框聚焦 - 角色互看
|
||||
usernameInput.addEventListener('focus', () => {
|
||||
isTyping = true;
|
||||
isLookingAtEachOther = true;
|
||||
// 短暂互看后恢复
|
||||
setTimeout(() => {
|
||||
isLookingAtEachOther = false;
|
||||
}, 800);
|
||||
});
|
||||
|
||||
usernameInput.addEventListener('blur', () => {
|
||||
if (!passwordInput.value) {
|
||||
isTyping = false;
|
||||
}
|
||||
isLookingAtEachOther = false;
|
||||
});
|
||||
|
||||
// 密码输入
|
||||
passwordInput.addEventListener('focus', () => {
|
||||
isTyping = true;
|
||||
});
|
||||
|
||||
passwordInput.addEventListener('blur', () => {
|
||||
if (!passwordInput.value) {
|
||||
isTyping = false;
|
||||
}
|
||||
});
|
||||
|
||||
passwordInput.addEventListener('input', () => {
|
||||
updateCharacterPositions();
|
||||
updatePupilPositions();
|
||||
});
|
||||
|
||||
// 密码可见性切换(默认显示,点击隐藏)
|
||||
passwordToggle.addEventListener('click', () => {
|
||||
const type = passwordInput.getAttribute('type');
|
||||
const eyeOpen = passwordToggle.querySelector('.eye-open');
|
||||
const eyeClosed = passwordToggle.querySelector('.eye-closed');
|
||||
|
||||
if (type === 'text') {
|
||||
// 点击后隐藏密码
|
||||
passwordInput.setAttribute('type', 'password');
|
||||
eyeOpen.style.display = 'none';
|
||||
eyeClosed.style.display = 'block';
|
||||
passwordToggle.setAttribute('aria-label', '显示密码');
|
||||
} else {
|
||||
// 点击后显示密码
|
||||
passwordInput.setAttribute('type', 'text');
|
||||
eyeOpen.style.display = 'block';
|
||||
eyeClosed.style.display = 'none';
|
||||
passwordToggle.setAttribute('aria-label', '隐藏密码');
|
||||
}
|
||||
|
||||
updatePupilPositions();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
0
frontend/sop_files/sop_20251122172515_xlsx
Normal file → Executable file
0
frontend/sop_files/sop_20251122172848_file.xlsx
Normal file → Executable file
0
frontend/sop_files/sop_20251122175115_010286AD2891-SOP.xlsx
Normal file → Executable file
0
import_reconciliation_excel.py
Normal file → Executable file
0
init_customer_orders.py
Normal file → Executable file
0
init_reconciliations.py
Normal file → Executable file
0
manager.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |