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

292 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>发料异常检查报表</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 { margin: 0; padding: 20px; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; background-color: #f0f2f5; }
.box-card { margin-bottom: 20px; }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.page-header h2 { margin: 0; color: #303133; }
.search-row { display: flex; gap: 15px; margin-bottom: 20px; }
.pagination-container { margin-top: 20px; text-align: right; }
</style>
</head>
<body>
<div id="app">
<el-card class="box-card">
<div class="page-header">
<h2><i class="el-icon-warning-outline" style="margin-right: 10px; color: #F56C6C;"></i>发料异常检查报表</h2>
<div>
<el-button type="info" plain icon="el-icon-back" @click="goBack" size="small">返回主控台</el-button>
</div>
</div>
<!-- 搜索区域 -->
<div class="search-row">
<el-input v-model="searchParams.work_orders_number" placeholder="工单号 (支持模糊搜索)" style="width: 250px" clearable @clear="handleSearch" @keyup.enter.native="handleSearch">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
<el-input v-model="searchParams.material_code" placeholder="物料代码 (支持模糊搜索)" style="width: 250px" clearable @clear="handleSearch" @keyup.enter.native="handleSearch">
<i slot="prefix" class="el-input__icon el-icon-search"></i>
</el-input>
<el-select v-model="searchParams.issue_status" placeholder="发料情况" style="width: 150px" clearable @change="handleSearch">
<el-option label="全部" value=""></el-option>
<el-option label="发料正常" value="发料正常"></el-option>
<el-option label="发料异常" value="发料异常"></el-option>
<el-option label="未发料" value="未发料"></el-option>
</el-select>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
<el-button icon="el-icon-refresh-left" @click="resetSearch">重置</el-button>
<el-button type="warning" :icon="isSystemBusy ? 'el-icon-loading' : 'el-icon-download'" :disabled="isSystemBusy" @click="syncAbnormalReport">
<span v-text="isSystemBusy ? '抓取中...' : '抓取异常报表'"></span>
</el-button>
</div>
<!-- 全局忙碌提示条 -->
<el-alert
v-if="isSystemBusy && globalTaskName"
type="warning"
show-icon
:closable="false"
style="margin-bottom: 15px;">
<template slot="title">
<div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
<span>系统忙碌中:正在执行 {{ globalTaskName }},请耐心等待完成后再操作。</span>
<el-button size="mini" type="primary" plain @click="showLogDialog = true" style="margin-left: 15px;">查看执行进度</el-button>
</div>
</template>
</el-alert>
<!-- 日志弹窗 -->
<el-dialog title="后台执行日志 (实时)" :visible.sync="showLogDialog" width="60%">
<div id="log-container" style="background-color: #1e1e1e; color: #67C23A; padding: 15px; height: 350px; overflow-y: auto; font-family: Consolas, monospace; border-radius: 4px; line-height: 1.5; font-size: 14px;">
<div v-for="(log, index) in taskLogs" :key="index" v-text="log"></div>
<div v-if="taskLogs.length === 0" style="color: #909399;">正在启动任务,等待输出...</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="showLogDialog = false">关闭窗口 (后台会继续执行)</el-button>
</span>
</el-dialog>
<!-- 数据表格 -->
<el-table :data="tableData" v-loading="loading" border style="width: 100%" stripe size="small" :header-cell-style="{background:'#f5f7fa',color:'#606266'}">
<el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
<el-table-column prop="work_orders_number" label="生产工单号" width="160"></el-table-column>
<el-table-column prop="product_code" label="产品代码" width="120"></el-table-column>
<el-table-column prop="product_name" label="产品名称" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="status" label="工单状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status === '作业中' ? 'primary' : 'success'" size="small" v-text="scope.row.status"></el-tag>
</template>
</el-table-column>
<el-table-column prop="order_date" label="下单日期" width="120"></el-table-column>
<el-table-column prop="workshop" label="生产车间" width="120"></el-table-column>
<el-table-column label="需求物料信息" align="center">
<el-table-column prop="material_code" label="需求物料代码" width="120"></el-table-column>
<el-table-column prop="material_name" label="需求物料名称" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="material_specification" label="规格" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="unit_qty" label="单机用量" width="80" align="right"></el-table-column>
<el-table-column prop="total_demand_qty" label="需求总量" width="90" align="right"></el-table-column>
</el-table-column>
<el-table-column label="发料异常比对" align="center">
<el-table-column prop="warehouse_issue_qty" label="仓库发放数量" width="100" align="right">
<template slot-scope="scope">
<span style="font-weight: bold;" v-text="scope.row.warehouse_issue_qty"></span>
</template>
</el-table-column>
<el-table-column prop="theoretical_issue_qty" label="理论出料数量" width="100" align="right">
<template slot-scope="scope">
<span style="color: #909399;" v-text="scope.row.theoretical_issue_qty"></span>
</template>
</el-table-column>
<el-table-column label="差异" width="90" align="right">
<template slot-scope="scope">
<span :style="{
color: (scope.row.warehouse_issue_qty - scope.row.theoretical_issue_qty) > 0 ? '#F56C6C' : '#67C23A',
fontWeight: 'bold'
}" v-text="(scope.row.warehouse_issue_qty - scope.row.theoretical_issue_qty).toFixed(2)">
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="issue_status" label="发料情况" width="100" align="center">
<template slot-scope="scope">
<el-tag
:type="scope.row.issue_status === '发料异常' ? 'danger' : (scope.row.issue_status === '未发料' ? 'info' : (scope.row.issue_status === '发料正常' ? 'success' : 'warning'))"
size="small"
v-text="scope.row.issue_status">
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[20, 50, 100, 200]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</el-card>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
loading: false,
isSystemBusy: false,
globalTaskName: "",
statusTimer: null,
showLogDialog: false,
taskLogs: [],
logTimer: null,
tableData: [],
total: 0,
currentPage: 1,
pageSize: 50,
searchParams: {
work_orders_number: '',
material_code: '',
issue_status: ''
}
}
},
mounted() {
this.fetchData();
this.checkTaskStatus();
this.statusTimer = setInterval(this.checkTaskStatus, 3000);
this.logTimer = setInterval(this.fetchLogs, 1000);
},
beforeDestroy() {
if (this.statusTimer) {
clearInterval(this.statusTimer);
}
if (this.logTimer) {
clearInterval(this.logTimer);
}
},
methods: {
fetchLogs() {
if (this.isSystemBusy && this.showLogDialog) {
axios.get('/api/task_logs')
.then(res => {
this.taskLogs = res.data.logs;
this.$nextTick(() => {
const container = document.getElementById('log-container');
if (container) {
container.scrollTop = container.scrollHeight;
}
});
})
.catch(err => {});
}
},
checkTaskStatus() {
axios.get('/api/task_status')
.then(res => {
this.isSystemBusy = res.data.is_busy;
this.globalTaskName = res.data.task_name;
})
.catch(err => {});
},
goBack() {
window.location.href = '/';
},
fetchData() {
this.loading = true;
const params = {
page: this.currentPage,
limit: this.pageSize,
work_orders_number: this.searchParams.work_orders_number,
material_code: this.searchParams.material_code,
issue_status: this.searchParams.issue_status
};
axios.get('/api/abnormal_report', { params })
.then(res => {
this.tableData = res.data.rows;
this.total = res.data.total;
})
.catch(err => {
this.$message.error('获取数据失败');
console.error(err);
})
.finally(() => {
this.loading = false;
});
},
handleSearch() {
this.currentPage = 1;
this.fetchData();
},
resetSearch() {
this.searchParams.work_orders_number = '';
this.searchParams.material_code = '';
this.searchParams.issue_status = '';
this.handleSearch();
},
handleSizeChange(val) {
this.pageSize = val;
this.fetchData();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchData();
},
syncAbnormalReport() {
this.$confirm('确定要抓取发料异常报表吗?该操作会在后台打开 ERP 进行翻页抓取。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.isSystemBusy = true; // 立即将状态置为 busy展示 loading
axios.post('/api/sync_abnormal_report')
.then(res => {
if (res.data.success) {
this.$message.success(res.data.message);
setTimeout(this.checkTaskStatus, 500);
// 自动弹开日志窗口让用户看进度
this.showLogDialog = true;
} else {
this.$message.error(res.data.message);
this.isSystemBusy = false;
}
})
.catch(err => {
if (err.response && err.response.status === 409) {
this.$message.warning(err.response.data.message);
} else {
this.$message.error(err.response?.data?.message || '触发抓取异常报表失败');
}
this.isSystemBusy = false;
console.error(err);
});
}).catch(() => {});
}
}
});
</script>
{% include "global_log.html" %}
</body>
</html>