抓取生产工单,赚取发料异常

This commit is contained in:
hjq
2026-06-11 15:58:56 +08:00
parent 66eecd0daa
commit 5b19790037
40 changed files with 4942 additions and 54 deletions

View File

@@ -0,0 +1,287 @@
<!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-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' : '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>
</body>
</html>