Files
datie-bom/web_ui/templates/home.html

373 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ERP 成本与数据看板 - 主控台</title>
<!-- 引入 ElementUI 样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入 Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<!-- 引入 ElementUI 组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!-- 引入 axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style>
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f2f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.dashboard-container {
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
padding: 40px;
width: 80%;
max-width: 900px;
text-align: center;
}
.header-title {
color: #303133;
font-size: 28px;
margin-bottom: 10px;
font-weight: 600;
}
.header-subtitle {
color: #909399;
font-size: 16px;
margin-bottom: 40px;
}
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 30px;
}
.nav-card {
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 30px 20px;
cursor: pointer;
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
background-color: #fafafa;
display: flex;
flex-direction: column;
align-items: center;
}
.nav-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.08);
border-color: #409EFF;
background-color: #ecf5ff;
}
.nav-card i {
font-size: 48px;
color: #409EFF;
margin-bottom: 15px;
}
.nav-card h3 {
margin: 0 0 10px 0;
color: #303133;
font-size: 20px;
}
.nav-card p {
margin: 0;
color: #606266;
font-size: 14px;
line-height: 1.5;
}
/* 特定卡片的颜色定制 */
.card-receipt { border-top: 4px solid #67C23A; }
.card-receipt i { color: #67C23A; }
.card-receipt:hover { border-color: #67C23A; background-color: #f0f9eb; }
.card-bom-tree { border-top: 4px solid #409EFF; }
.card-bom-tree i { color: #409EFF; }
.card-bom-tree:hover { border-color: #409EFF; background-color: #ecf5ff; }
.card-bom-compare { border-top: 4px solid #9c27b0; }
.card-bom-compare i { color: #9c27b0; }
.card-bom-compare:hover { border-color: #9c27b0; background-color: #f3e5f5; }
.card-work-order { border-top: 4px solid #E6A23C; }
.card-work-order i { color: #E6A23C; }
.card-work-order:hover { border-color: #E6A23C; background-color: #fdf6ec; }
.card-abnormal { border-top: 4px solid #F56C6C; }
.card-abnormal i { color: #F56C6C; }
.card-abnormal:hover { border-color: #F56C6C; background-color: #fef0f0; }
.card-reconciliation { border-top: 4px solid #909399; }
.card-reconciliation i { color: #909399; }
.card-reconciliation:hover { border-color: #909399; background-color: #f4f4f5; }
.action-group {
margin-top: 40px;
padding-top: 30px;
border-top: 1px dashed #ebeef5;
display: flex;
justify-content: center;
gap: 20px;
position: relative;
}
.log-window {
margin-top: 30px;
background-color: #1e1e1e;
color: #a9b7c6;
border-radius: 8px;
padding: 15px;
height: 250px;
overflow-y: auto;
text-align: left;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
border: 1px solid #333;
}
.log-window::-webkit-scrollbar {
width: 8px;
}
.log-window::-webkit-scrollbar-track {
background: #1e1e1e;
}
.log-window::-webkit-scrollbar-thumb {
background: #555;
border-radius: 4px;
}
.log-window::-webkit-scrollbar-thumb:hover {
background: #777;
}
</style>
</head>
<body>
<div class="dashboard-container" id="app">
<div class="header-title">ERP 数据分析与成本管控台</div>
<div class="header-subtitle">请选择您需要进入的业务模块</div>
<div class="nav-grid">
<!-- 卡片 1: 收货明细表 -->
<div class="nav-card card-receipt" onclick="window.location.href='/receipts'">
<i class="el-icon-document"></i>
<h3>财务收货明细报表</h3>
<p>查看、搜索所有历史财务收货记录及详细价格数据。</p>
</div>
<!-- 卡片 2: BOM 雷达图 -->
<div class="nav-card card-bom-tree" onclick="window.location.href='/bom'">
<i class="el-icon-share"></i>
<h3>BOM 成本雷达图</h3>
<p>以关系图谱形式可视化展示产品的层级结构与汇总成本。</p>
</div>
<!-- 卡片 3: 期间成本对比 -->
<div class="nav-card card-bom-compare" onclick="window.location.href='/compare'">
<i class="el-icon-data-line"></i>
<h3>期间成本对比分析表</h3>
<p>跨时间段核算 BOM 最新价差异,支持虚拟件过滤与历史价回溯。</p>
</div>
<!-- 卡片 4: 生产工单明细 -->
<div class="nav-card card-work-order" onclick="window.location.href='/work_orders'">
<i class="el-icon-document"></i>
<h3>生产工单明细</h3>
<p>查询生产工单记录、领料情况及执行状态。</p>
</div>
<!-- 卡片 5: 发料异常检查 -->
<div class="nav-card card-abnormal" onclick="window.location.href='/abnormal_report'">
<i class="el-icon-warning-outline"></i>
<h3>发料异常检查</h3>
<p>排查生产工单的发料异常,对比理论出料与实际发放数量的差异。</p>
</div>
<!-- 卡片 6: 绩效核查与BOM比对 -->
<div class="nav-card card-reconciliation" onclick="window.location.href='/reconciliation'">
<i class="el-icon-data-analysis"></i>
<h3>绩效核查与BOM比对</h3>
<p>数据清洗、匹配工单号,智能比对 BOM 理论发料量与实际发料量差异。</p>
</div>
</div>
<div class="action-group">
<div v-if="globalTaskName" style="position: absolute; top: -30px; width: 100%; text-align: center; color: #E6A23C; font-weight: bold; font-size: 14px;">
<i class="el-icon-loading"></i> 系统忙碌中:正在执行 {{ globalTaskName }},在此期间无法发起新的抓取。
</div>
<el-button
type="success"
:icon="isSystemBusy ? 'el-icon-loading' : 'el-icon-refresh'"
:disabled="isSystemBusy"
@click="syncReceipts"
round>
<span v-text="syncing ? '请求已发送...' : '读取最新收货明细报表'"></span>
</el-button>
<el-button
type="primary"
:icon="isSystemBusy ? 'el-icon-loading' : 'el-icon-refresh-right'"
:disabled="isSystemBusy"
@click="syncBom"
round>
<span v-text="syncingBom ? '请求已发送...' : '读取最新 BOM 表'"></span>
</el-button>
<el-button
type="warning"
:icon="isSystemBusy ? 'el-icon-loading' : 'el-icon-refresh'"
:disabled="isSystemBusy"
@click="syncWorkOrders"
round>
<span v-text="syncingWorkOrders ? '请求已发送...' : '读取生产工单明细'"></span>
</el-button>
</div>
</div>
{% include 'global_log.html' %}
<script>
new Vue({
el: '#app',
data() {
return {
syncing: false,
syncingBom: false,
syncingWorkOrders: false,
isSystemBusy: false,
globalTaskName: "",
statusTimer: null
}
},
mounted() {
// 页面加载时立刻检查一次
this.checkTaskStatus();
// 之后每隔 3 秒轮询一次后端状态
this.statusTimer = setInterval(this.checkTaskStatus, 3000);
},
beforeDestroy() {
if (this.statusTimer) {
clearInterval(this.statusTimer);
}
},
methods: {
checkTaskStatus() {
axios.get('/api/task_status')
.then(res => {
this.isSystemBusy = res.data.is_busy;
this.globalTaskName = res.data.task_name;
})
.catch(err => {
// 忽略检查错误
});
},
syncReceipts() {
this.syncing = true;
if (window.globalLogApp) {
window.globalLogApp.logDialogVisible = true;
}
axios.post('/api/sync_receipts')
.then(res => {
if (res.data.success) {
this.$message.success('已触发!' + res.data.message);
// 立即主动检查一次状态更新按钮
setTimeout(this.checkTaskStatus, 500);
} else {
this.$message.error('触发失败:' + res.data.message);
}
})
.catch(err => {
if (err.response && err.response.status === 409) {
this.$message.warning(err.response.data.message);
} else {
this.$message.error('请求发生异常,请检查后端日志。');
}
})
.finally(() => {
this.syncing = false;
});
},
syncBom() {
this.$confirm('此操作将启动后台浏览器耗时较长约10-20分钟期间请勿关闭服务器终端。确认执行?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.syncingBom = true;
// 如果存在全局日志组件,自动打开日志面板
if (window.globalLogApp) {
window.globalLogApp.logDialogVisible = true;
}
axios.post('/api/sync_bom')
.then(res => {
if (res.data.success) {
this.$message.success('已触发!' + res.data.message);
setTimeout(this.checkTaskStatus, 500);
} else {
this.$message.error('触发失败:' + res.data.message);
}
})
.catch(err => {
if (err.response && err.response.status === 409) {
this.$message.warning(err.response.data.message);
} else {
this.$message.error('请求发生异常,请检查后端日志。');
}
})
.finally(() => {
this.syncingBom = false;
});
}).catch(() => {});
},
syncWorkOrders() {
this.syncingWorkOrders = true;
if (window.globalLogApp) {
window.globalLogApp.logDialogVisible = true;
}
axios.post('/api/sync_work_orders')
.then(res => {
if (res.data.success) {
this.$message.success('已触发!' + res.data.message);
setTimeout(this.checkTaskStatus, 500);
} else {
this.$message.error('触发失败:' + res.data.message);
}
})
.catch(err => {
if (err.response && err.response.status === 409) {
this.$message.warning(err.response.data.message);
} else {
this.$message.error('请求发生异常,请检查后端日志。');
}
})
.finally(() => {
this.syncingWorkOrders = false;
});
}
}
});
</script>
</body>
</html>