Merge remote-tracking branch 'gitee/0.8.X' into 0.8.X
This commit is contained in:
@@ -102,10 +102,10 @@
|
||||
<groupId>com.klp</groupId>
|
||||
<artifactId>klp-erp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klp</groupId>
|
||||
<artifactId>klp-hrm</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.klp</groupId>-->
|
||||
<!-- <artifactId>klp-hrm</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.klp</groupId>
|
||||
<artifactId>klp-crm</artifactId>
|
||||
|
||||
18
klp-ui/src/api/lines/zinc/dashboard.js
Normal file
18
klp-ui/src/api/lines/zinc/dashboard.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import zinc1Request from '@/utils/zinc1Request'
|
||||
|
||||
// 获取当前生产中的计划信息
|
||||
export function getCurrentPlan() {
|
||||
return zinc1Request({
|
||||
url: '/business/dashboard/currentPlan',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前生产卷的关键工艺参数
|
||||
export function getCurrentProcess() {
|
||||
return zinc1Request({
|
||||
url: '/business/dashboard/currentProcess',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
20
klp-ui/src/api/lines/zinc/report.js
Normal file
20
klp-ui/src/api/lines/zinc/report.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import zinc1Request from '@/utils/zinc1Request'
|
||||
|
||||
// 生产实绩汇总
|
||||
export function getReportSummary(params) {
|
||||
return zinc1Request({
|
||||
url: '/report/summary',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 生产实绩明细
|
||||
export function getReportDetails(params) {
|
||||
return zinc1Request({
|
||||
url: '/report/details',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
11
klp-ui/src/api/lines/zinc/stoppage.js
Normal file
11
klp-ui/src/api/lines/zinc/stoppage.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import zinc1Request from '@/utils/zinc1Request'
|
||||
|
||||
// 停机记录列表
|
||||
export function listStoppage(data) {
|
||||
return zinc1Request({
|
||||
url: '/stoppage/list',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
9
klp-ui/src/api/pocket/deviceEnum.js
Normal file
9
klp-ui/src/api/pocket/deviceEnum.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import zinc1Request from '@/utils/zinc1Request'
|
||||
|
||||
export function listDeviceEnumAll() {
|
||||
return zinc1Request({
|
||||
url: '/api/deviceEnum/all',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
9
klp-ui/src/api/pocket/deviceFieldMeta.js
Normal file
9
klp-ui/src/api/pocket/deviceFieldMeta.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import zinc1Request from '@/utils/zinc1Request'
|
||||
|
||||
export function getDeviceFieldMetaAll() {
|
||||
return zinc1Request({
|
||||
url: '/api/deviceFieldMeta/all',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
20
klp-ui/src/api/pocket/deviceSnapshot.js
Normal file
20
klp-ui/src/api/pocket/deviceSnapshot.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import zinc1Request from '@/utils/zinc1Request'
|
||||
|
||||
// 获取最新N条设备快照
|
||||
export function listDeviceSnapshotLatest(params) {
|
||||
return zinc1Request({
|
||||
url: '/api/deviceSnapshot/latest',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 按时间范围查询设备快照
|
||||
export function listDeviceSnapshotRange(params) {
|
||||
return zinc1Request({
|
||||
url: '/api/deviceSnapshot/range',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
162
klp-ui/src/api/pocket/plantState.js
Normal file
162
klp-ui/src/api/pocket/plantState.js
Normal file
@@ -0,0 +1,162 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// =========================== 状态定义相关 ===========================
|
||||
|
||||
/**
|
||||
* 获取所有状态定义及其当前值(用于初始化缓存)
|
||||
* 返回数据结构:
|
||||
* {
|
||||
* id: 定义ID (对应VALUE字段编号,如id=14对应VALUE14),
|
||||
* name: 指标名称,
|
||||
* units: 单位,
|
||||
* comments: 说明,
|
||||
* currentValue: 当前值,
|
||||
* currentInsdate: 当前值时间
|
||||
* }
|
||||
*/
|
||||
export function getAllPlantStateDefines() {
|
||||
return request({
|
||||
url: '/pocket/proPlantStateDefine/allWithValues',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// =========================== 当前数据相关 ===========================
|
||||
|
||||
// 查询设备状态当前数据列表
|
||||
export function listPlantStateCurrent(query) {
|
||||
return request({
|
||||
url: '/pocket/proPlantStateCurrent/selectAll',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询单条设备状态当前数据
|
||||
export function getPlantStateCurrent(type, insdate) {
|
||||
return request({
|
||||
url: '/pocket/proPlantStateCurrent/one',
|
||||
method: 'get',
|
||||
params: { type, insdate }
|
||||
})
|
||||
}
|
||||
|
||||
// 查询设备状态历史数据列表
|
||||
export function listPlantStateHistory(query) {
|
||||
return request({
|
||||
url: '/pocket/proPlantStateHistory/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询设备状态历史数据详情
|
||||
export function getPlantStateHistory(insdate) {
|
||||
return request({
|
||||
url: '/pocket/proPlantStateHistory/' + insdate,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询停机列表
|
||||
export function listStoppage(query) {
|
||||
return request({
|
||||
url: '/pocket/proStoppage/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询停机详情
|
||||
export function getStoppage(stopId) {
|
||||
return request({
|
||||
url: '/pocket/proStoppage/' + stopId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// =========================== 班组信息相关 ===========================
|
||||
|
||||
/**
|
||||
* 获取当前班组信息
|
||||
* 返回数据结构:
|
||||
* {
|
||||
* shift: 班次 (如:A/B/C 或 早/中/晚),
|
||||
* crew: 班组编号,
|
||||
* seqNum: 序列号,
|
||||
* sysTime: 系统时间
|
||||
* }
|
||||
*/
|
||||
export function getCurrentShift() {
|
||||
return request({
|
||||
url: '/pocket/shiftCurrent/current',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// =========================== 生产统计相关 ===========================
|
||||
|
||||
/**
|
||||
* 获取生产统计汇总数据
|
||||
* @param {string} startDate - 开始日期 (yyyy-MM-dd)
|
||||
* @param {string} endDate - 结束日期 (yyyy-MM-dd)
|
||||
*/
|
||||
export function getProductionSummary(startDate, endDate) {
|
||||
return request({
|
||||
url: '/pocket/productionStatistics/summary',
|
||||
method: 'get',
|
||||
params: { startDate, endDate }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取班组产量统计
|
||||
* @param {string} startDate - 开始日期
|
||||
* @param {string} endDate - 结束日期
|
||||
*/
|
||||
export function getCrewProduction(startDate, endDate) {
|
||||
return request({
|
||||
url: '/pocket/productionStatistics/crewProduction',
|
||||
method: 'get',
|
||||
params: { startDate, endDate }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取厚度分布统计
|
||||
* @param {string} startDate - 开始日期
|
||||
* @param {string} endDate - 结束日期
|
||||
*/
|
||||
export function getThicknessDistribution(startDate, endDate) {
|
||||
return request({
|
||||
url: '/pocket/productionStatistics/thicknessDistribution',
|
||||
method: 'get',
|
||||
params: { startDate, endDate }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宽度分布统计
|
||||
* @param {string} startDate - 开始日期
|
||||
* @param {string} endDate - 结束日期
|
||||
*/
|
||||
export function getWidthDistribution(startDate, endDate) {
|
||||
return request({
|
||||
url: '/pocket/productionStatistics/widthDistribution',
|
||||
method: 'get',
|
||||
params: { startDate, endDate }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取班组绩效统计
|
||||
* @param {string} startDate - 开始日期
|
||||
* @param {string} endDate - 结束日期
|
||||
*/
|
||||
export function getTeamPerformance(startDate, endDate) {
|
||||
return request({
|
||||
url: '/pocket/productionStatistics/teamPerformance',
|
||||
method: 'get',
|
||||
params: { startDate, endDate }
|
||||
})
|
||||
}
|
||||
@@ -194,13 +194,14 @@ export function cancelExportCoil(coilId) {
|
||||
}
|
||||
|
||||
// 检查入场钢卷号或当前钢卷号是否合法(是否存在)
|
||||
export function checkCoilNo({ currentCoilNo, enterCoilNo }) {
|
||||
export function checkCoilNo({ currentCoilNo, enterCoilNo, coilId }) {
|
||||
return request({
|
||||
url: '/wms/materialCoil/checkCoilNoDuplicate',
|
||||
method: 'get',
|
||||
params: {
|
||||
currentCoilNo,
|
||||
enterCoilNo
|
||||
enterCoilNo,
|
||||
coilId
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -214,4 +215,14 @@ export function getMaxCoilNo(enterCoilNoPrefix) {
|
||||
enterCoilNoPrefix
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询存在重号的钢卷
|
||||
*/
|
||||
export function getDuplicateGroups() {
|
||||
return request({
|
||||
url: '/wms/materialCoil/duplicateGroups',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
72
klp-ui/src/api/wms/leaveRequest.js
Normal file
72
klp-ui/src/api/wms/leaveRequest.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询员工请假申请列表
|
||||
export function listLeaveRequest(query) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询员工请假申请详细
|
||||
export function getLeaveRequest(leaveId) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest/' + leaveId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增员工请假申请
|
||||
export function addLeaveRequest(data) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改员工请假申请
|
||||
export function updateLeaveRequest(data) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除员工请假申请
|
||||
export function delLeaveRequest(leaveId) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest/' + leaveId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 报表相关接口
|
||||
// 按请假类型统计
|
||||
export function getLeaveTypeCount(query) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest/report/leaveType',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 按部门统计
|
||||
export function getDeptCount(query) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest/report/dept',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 按月份统计
|
||||
export function getMonthCount(query) {
|
||||
return request({
|
||||
url: '/wms/leaveRequest/report/monthly',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
72
klp-ui/src/api/wms/mealReport.js
Normal file
72
klp-ui/src/api/wms/mealReport.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询部门报餐主列表
|
||||
export function listMealReport(query) {
|
||||
return request({
|
||||
url: '/wms/mealReport/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询部门报餐主详细
|
||||
export function getMealReport(reportId) {
|
||||
return request({
|
||||
url: '/wms/mealReport/' + reportId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增部门报餐主
|
||||
export function addMealReport(data) {
|
||||
return request({
|
||||
url: '/wms/mealReport',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改部门报餐主
|
||||
export function updateMealReport(data) {
|
||||
return request({
|
||||
url: '/wms/mealReport',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除部门报餐主
|
||||
export function delMealReport(reportId) {
|
||||
return request({
|
||||
url: '/wms/mealReport/' + reportId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 报表相关接口
|
||||
// 按餐别统计
|
||||
export function getMealTypeCount(query) {
|
||||
return request({
|
||||
url: '/wms/mealReport/report/mealType',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 按部门统计
|
||||
export function getDeptCount(query) {
|
||||
return request({
|
||||
url: '/wms/mealReport/report/dept',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 按日期统计
|
||||
export function getDateCount(query) {
|
||||
return request({
|
||||
url: '/wms/mealReport/report/date',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -161,7 +161,8 @@ export default {
|
||||
pageSize: 10,
|
||||
currentCoilNo: null,
|
||||
grade: null,
|
||||
selectType: 'raw_material',
|
||||
selectType: 'product',
|
||||
status: 0, // 不包含已发货的钢卷
|
||||
dataType: 1 // 只查询当前数据,不查询历史数据
|
||||
},
|
||||
columns: defaultColumns,
|
||||
|
||||
317
klp-ui/src/components/DictSelect/index.vue
Normal file
317
klp-ui/src/components/DictSelect/index.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div style="display: flex; align-items: center;" v-loading="loading">
|
||||
<!-- 下拉选择器:绑定计算属性做双向绑定,保留原有样式+清空功能 -->
|
||||
<el-select v-model="innerValue" :placeholder="placeholder" clearable filterable style="width: 200px;">
|
||||
<el-option
|
||||
v-for="item in dictOptions"
|
||||
:key="item.dictValue"
|
||||
:label="item.dictLabel"
|
||||
:value="item.dictValue"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<!-- 编辑按钮:点击打开弹窗 -->
|
||||
<div
|
||||
v-if="editable"
|
||||
@click="openDictDialog"
|
||||
style="cursor: pointer; height: 24px; width: 24px; margin-top: 4px; display: flex; align-items: center; justify-content: center; border: 1px solid #828991; margin-left: 8px; border-radius: 2px;"
|
||||
>
|
||||
<i class="el-icon-setting"></i>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="refresh"
|
||||
@click="handleRefresh"
|
||||
style="cursor: pointer; height: 24px; width: 24px; margin-top: 4px; display: flex; align-items: center; justify-content: center; border: 1px solid #828991; margin-left: 8px; border-radius: 2px;"
|
||||
>
|
||||
<i class="el-icon-refresh"></i>
|
||||
</div>
|
||||
|
||||
<!-- 字典编辑弹窗 -->
|
||||
<el-dialog
|
||||
v-if="editable"
|
||||
:visible.sync="open"
|
||||
title="字典数据配置"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<!-- 快捷新增表单区域【仅做新增,无编辑功能】 -->
|
||||
<el-form
|
||||
ref="dictFormRef"
|
||||
:model="form"
|
||||
:rules="dictRules"
|
||||
label-width="68px"
|
||||
label-position="left"
|
||||
style="margin-bottom: 20px;"
|
||||
>
|
||||
<el-row :gutter="15">
|
||||
<el-col :span="kisv ? 24 : 12">
|
||||
<el-form-item label="值/标签" prop="dictValue" v-if="kisv">
|
||||
<el-input v-model="form.dictValue" placeholder="请输入字典值(标签自动同步)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="字典标签" prop="dictLabel" v-else>
|
||||
<el-input v-model="form.dictLabel" placeholder="请输入字典标签" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="!kisv">
|
||||
<el-form-item label="字典值" prop="dictValue">
|
||||
<el-input v-model="form.dictValue" placeholder="请输入字典值" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<el-button type="primary" size="small" @click="submitAddDict" :loading="btnLoading">新增字典项</el-button>
|
||||
<el-button size="small" @click="resetDictForm">清空表单</el-button>
|
||||
|
||||
<span style="margin-left: 10px;">提示:双击单元格中的文字可以快速编辑</span>
|
||||
</div>
|
||||
|
||||
<!-- 字典列表 ✅核心修改:双击单元格激活输入框,失焦还原文本 -->
|
||||
<el-table
|
||||
:data="dictOptions"
|
||||
border
|
||||
stripe
|
||||
size="mini"
|
||||
style="width: 100%;"
|
||||
empty-text="暂无字典数据"
|
||||
>
|
||||
<!-- 字典标签列:kisv=true隐藏,false正常展示 -->
|
||||
<el-table-column label="字典标签" align="center" v-if="!kisv">
|
||||
<template #default="scope">
|
||||
<!-- 双击span触发编辑,失焦/回车后变回span -->
|
||||
<span
|
||||
v-if="editRowId !== scope.row.dictCode"
|
||||
@dblclick.stop="handleDbEdit(scope.row)"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
{{ scope.row.dictLabel }}
|
||||
</span>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="scope.row.dictLabel"
|
||||
size="mini"
|
||||
style="width: 100%;"
|
||||
@change="handleSaveRow(scope.row)"
|
||||
auto-focus
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 字典值列:kisv=true时标题合并 -->
|
||||
<el-table-column :label="kisv ? '字典值/标签' : '字典值'" align="center">
|
||||
<template #default="scope">
|
||||
<!-- ✅ 核心:默认文本,双击才显示输入框,失焦立刻消失 -->
|
||||
<span
|
||||
v-if="editRowId !== scope.row.dictCode"
|
||||
@dblclick.stop="handleDbEdit(scope.row)"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
{{ scope.row.dictValue }}
|
||||
</span>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="scope.row.dictValue"
|
||||
size="mini"
|
||||
style="width: 100%;"
|
||||
@input="() => handleKisvTableSync(scope.row)"
|
||||
@change="handleSaveRow(scope.row)"
|
||||
auto-focus
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
type="text"
|
||||
text-danger
|
||||
@click="handleDelete(scope.row)"
|
||||
:loading="delBtnLoading === scope.row.dictCode"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDicts, addData, updateData, delData, listData } from '@/api/system/dict/data'
|
||||
import { listType } from '@/api/system/dict/type'
|
||||
|
||||
export default {
|
||||
name: 'DictSelectEdit',
|
||||
props: {
|
||||
dictType: { type: String, default: '' },
|
||||
editable: { type: Boolean, default: true },
|
||||
kisv: { type: Boolean, default: false },
|
||||
value: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '请选择' },
|
||||
refresh: { type: Boolean, default: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dictOptions: [],
|
||||
dictId: '',
|
||||
loading: false,
|
||||
btnLoading: false,
|
||||
delBtnLoading: '',
|
||||
open: false,
|
||||
editRowId: '', // 控制当前编辑行,为空则所有单元格都是文本状态
|
||||
form: {
|
||||
dictId: '',
|
||||
dictLabel: '',
|
||||
dictValue: '',
|
||||
dictType: '',
|
||||
sort: 0
|
||||
},
|
||||
dictRules: {
|
||||
dictLabel: [{ required: true, message: '请输入字典标签', trigger: 'blur' }],
|
||||
dictValue: [{ required: true, message: '请输入字典值', trigger: 'blur' }]
|
||||
},
|
||||
dictFormRef: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
innerValue: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dictType: {
|
||||
async handler(newVal) {
|
||||
if (newVal) {
|
||||
this.loading = true
|
||||
try {
|
||||
const dictId = await this.getDictId(newVal)
|
||||
await this.getDictOptions(dictId)
|
||||
} catch (err) {
|
||||
console.error('加载字典失败:', err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
'form.dictValue': {
|
||||
handler(val) {
|
||||
if (this.kisv && val) this.form.dictLabel = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getDictId(type) {
|
||||
const res = await listType({ dictType: type })
|
||||
if (res.rows?.length !== 1) {
|
||||
this.$message.error('字典类型异常,未查询到对应配置')
|
||||
return Promise.reject('字典类型异常')
|
||||
}
|
||||
this.dictId = res.rows[0].dictId
|
||||
return this.dictId
|
||||
},
|
||||
// 新增:刷新字典数据
|
||||
async handleRefresh() {
|
||||
this.loading = true
|
||||
try {
|
||||
await this.getDictOptions(this.dictId)
|
||||
} catch (err) {
|
||||
console.error('刷新字典失败:', err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getDictOptions(dictId) {
|
||||
const res = await listData({ dictType: this.dictType, pageSize: 1000 })
|
||||
this.dictOptions = res.rows || []
|
||||
return this.dictOptions
|
||||
},
|
||||
openDictDialog() {
|
||||
this.open = true
|
||||
this.resetDictForm()
|
||||
this.form.dictId = this.dictId
|
||||
this.form.dictType = this.dictType
|
||||
this.editRowId = '' // 打开弹窗重置编辑状态
|
||||
},
|
||||
resetDictForm() {
|
||||
this.form = { dictId: this.dictId, dictLabel: '', dictValue: '', dictType: this.dictType, sort: 0 }
|
||||
this.$refs.dictFormRef && this.$refs.dictFormRef.clearValidate()
|
||||
},
|
||||
handleKisvTableSync(row) {
|
||||
if (this.kisv && row.dictValue) row.dictLabel = row.dictValue
|
||||
},
|
||||
async submitAddDict() {
|
||||
const valid = await this.$refs.dictFormRef.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
this.loading = true
|
||||
this.btnLoading = true
|
||||
try {
|
||||
await addData(this.form)
|
||||
this.$message.success('字典项新增成功!')
|
||||
await this.getDictOptions(this.dictId)
|
||||
this.resetDictForm()
|
||||
} catch (err) {
|
||||
this.$message.error('新增失败,请稍后重试')
|
||||
console.error(err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.btnLoading = false
|
||||
}
|
||||
},
|
||||
// ✅ 新增:双击单元格 激活编辑状态 核心方法
|
||||
handleDbEdit(row) {
|
||||
// 同一时间只允许编辑一行,双击其他行关闭当前行编辑
|
||||
this.editRowId = row.dictCode
|
||||
},
|
||||
// ✅ 核心完善:失去焦点/回车 保存数据 + 强制关闭编辑态 → 还原成普通单元格
|
||||
async handleSaveRow(row) {
|
||||
if (!row.dictLabel || !row.dictValue) {
|
||||
this.$message.warning('字典标签和字典值不能为空!')
|
||||
await this.getDictOptions(this.dictId)
|
||||
this.editRowId = '' // 校验失败,也必须还原单元格
|
||||
return
|
||||
}
|
||||
row.sort = 0
|
||||
this.loading = true
|
||||
try {
|
||||
await updateData(row)
|
||||
this.$message.success('字典项修改成功!')
|
||||
} catch (err) {
|
||||
this.$message.error('修改失败,请稍后重试')
|
||||
await this.getDictOptions(this.dictId)
|
||||
console.error(err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.editRowId = '' // ✅必加:保存完成,立刻关闭编辑态,变回文本
|
||||
}
|
||||
},
|
||||
async handleDelete(row) {
|
||||
const confirm = await this.$confirm('确定要删除该字典项吗?删除后不可恢复!', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).catch(() => false)
|
||||
if (!confirm) return
|
||||
this.loading = true
|
||||
this.delBtnLoading = row.dictCode
|
||||
try {
|
||||
await delData(row.dictCode)
|
||||
this.$message.success('删除成功!')
|
||||
await this.getDictOptions(this.dictId)
|
||||
} catch (err) {
|
||||
this.$message.error('删除失败,请稍后重试')
|
||||
console.error(err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.delBtnLoading = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,7 +1,9 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
const ZincTokenKey = 'Zinc-Token'
|
||||
|
||||
// L3 Token管理
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
}
|
||||
@@ -13,3 +15,16 @@ export function setToken(token) {
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
||||
|
||||
// Zinc Token管理
|
||||
export function getZincToken() {
|
||||
return Cookies.get(ZincTokenKey)
|
||||
}
|
||||
|
||||
export function setZincToken(token) {
|
||||
return Cookies.set(ZincTokenKey, token)
|
||||
}
|
||||
|
||||
export function removeZincToken() {
|
||||
return Cookies.remove(ZincTokenKey)
|
||||
}
|
||||
|
||||
142
klp-ui/src/utils/zinc1Request.js
Normal file
142
klp-ui/src/utils/zinc1Request.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import axios from 'axios'
|
||||
import { Notification, MessageBox, Message } from 'element-ui'
|
||||
import store from '@/store'
|
||||
import { getZincToken } from '@/utils/auth'
|
||||
import errorCode from '@/utils/errorCode'
|
||||
import { tansParams } from "@/utils/klp";
|
||||
import cache from '@/plugins/cache'
|
||||
// 是否显示重新登录
|
||||
export let isRelogin = { show: false };
|
||||
|
||||
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
|
||||
// 对应国际化资源文件后缀
|
||||
axios.defaults.headers['Content-Language'] = 'zh_CN'
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
// axios中请求配置有baseURL选项,表示请求URL公共部分
|
||||
baseURL: '/zinc-api',
|
||||
// 超时
|
||||
timeout: 10000
|
||||
})
|
||||
|
||||
// request拦截器
|
||||
service.interceptors.request.use(config => {
|
||||
// 是否需要设置 token
|
||||
const isToken = (config.headers || {}).isToken === false
|
||||
// 是否需要防止数据重复提交
|
||||
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
|
||||
if (getZincToken() && !isToken) {
|
||||
config.headers['Authorization'] = 'Bearer ' + getZincToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
}
|
||||
// get请求映射params参数
|
||||
if (config.method === 'get' && config.params) {
|
||||
let url = config.url + '?' + tansParams(config.params);
|
||||
url = url.slice(0, -1);
|
||||
config.params = {};
|
||||
config.url = url;
|
||||
}
|
||||
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
|
||||
// 对于盖章请求,添加特殊处理确保 0 值不被过滤
|
||||
if (config.url && config.url.includes('/stamp/java')) {
|
||||
console.log('Stamp request - original data:', JSON.stringify(config.data, null, 2))
|
||||
if (config.data && typeof config.data === 'object') {
|
||||
// 确保 yPx 和 xPx 即使是 0 也被正确包含
|
||||
// 创建一个新对象,确保所有值都被正确设置
|
||||
const cleanData = {
|
||||
targetFileUrl: String(config.data.targetFileUrl || ''),
|
||||
stampImageUrl: String(config.data.stampImageUrl || ''),
|
||||
pageNo: Number(config.data.pageNo) || 1,
|
||||
xPx: Number(config.data.xPx) || 0,
|
||||
yPx: Number(config.data.yPx) || 0
|
||||
}
|
||||
if (config.data.widthPx !== undefined && config.data.widthPx !== null) {
|
||||
cleanData.widthPx = Number(config.data.widthPx)
|
||||
}
|
||||
if (config.data.heightPx !== undefined && config.data.heightPx !== null) {
|
||||
cleanData.heightPx = Number(config.data.heightPx)
|
||||
}
|
||||
console.log('Stamp request - cleaned data:', JSON.stringify(cleanData, null, 2))
|
||||
console.log('yPx in cleaned data:', cleanData.yPx, typeof cleanData.yPx)
|
||||
config.data = cleanData
|
||||
}
|
||||
}
|
||||
const requestObj = {
|
||||
url: config.url,
|
||||
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
|
||||
time: new Date().getTime()
|
||||
}
|
||||
const sessionObj = cache.session.getJSON('sessionObj')
|
||||
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
|
||||
cache.session.setJSON('sessionObj', requestObj)
|
||||
} else {
|
||||
const s_url = sessionObj.url; // 请求地址
|
||||
const s_data = sessionObj.data; // 请求数据
|
||||
const s_time = sessionObj.time; // 请求时间
|
||||
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
|
||||
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
|
||||
const message = '数据正在处理,请勿重复提交';
|
||||
console.warn(`[${s_url}]: ` + message)
|
||||
return Promise.reject(new Error(message))
|
||||
} else {
|
||||
cache.session.setJSON('sessionObj', requestObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
return config
|
||||
}, error => {
|
||||
console.log(error)
|
||||
Promise.reject(error)
|
||||
})
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(res => {
|
||||
// 未设置状态码则默认成功状态
|
||||
const code = res.data.code || 200;
|
||||
// 获取错误信息
|
||||
const msg = errorCode[code] || res.data.msg || errorCode['default']
|
||||
// 二进制数据则直接返回
|
||||
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
|
||||
return res.data
|
||||
}
|
||||
if (code === 401) {
|
||||
if (!isRelogin.show) {
|
||||
isRelogin.show = true;
|
||||
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
||||
isRelogin.show = false;
|
||||
store.dispatch('LogOut').then(() => {
|
||||
location.href = process.env.VUE_APP_CONTEXT_PATH + "index";
|
||||
})
|
||||
}).catch(() => {
|
||||
isRelogin.show = false;
|
||||
});
|
||||
}
|
||||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||||
} else if (code === 500) {
|
||||
Message({ message: msg, type: 'error' })
|
||||
return Promise.reject(new Error(msg))
|
||||
} else if (code === 601) {
|
||||
Message({ message: msg, type: 'warning' })
|
||||
return Promise.reject('error')
|
||||
} else if (code !== 200) {
|
||||
Notification.error({ title: msg })
|
||||
return Promise.reject('error')
|
||||
} else {
|
||||
return res.data
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.log('err' + error)
|
||||
let { message } = error;
|
||||
if (message == "Network Error") {
|
||||
message = "后端接口连接异常";
|
||||
} else if (message.includes("timeout")) {
|
||||
message = "系统接口请求超时";
|
||||
} else if (message.includes("Request failed with status code")) {
|
||||
message = "系统接口" + message.substr(message.length - 3) + "异常";
|
||||
}
|
||||
Message({ message: message, type: 'error', duration: 5 * 1000 })
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default service
|
||||
640
klp-ui/src/views/lines/acid/components/product-statistic.vue
Normal file
640
klp-ui/src/views/lines/acid/components/product-statistic.vue
Normal file
@@ -0,0 +1,640 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<!-- 时间维度切换:Element-UI 选项卡替代uniapp自定义tab -->
|
||||
<!-- 时间维度切换 -->
|
||||
<div class="time-tab-bar">
|
||||
<div v-for="item in timeTabs" :key="item.value" class="time-tab-item"
|
||||
:class="{ 'time-tab-active': activeTab === item.value }" @click="handleTabChange(item.value)">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 日期选择区:Element-UI 日期选择器替代uniapp picker -->
|
||||
<div class="date-selector">
|
||||
<!-- 日模式-单日期选择 -->
|
||||
<el-date-picker v-if="activeTab === 'day'" v-model="startDate" type="date" value-format="yyyy-MM-dd"
|
||||
placeholder="选择日期" @change="handleDateChange" class="single-date-picker" />
|
||||
|
||||
<!-- 月模式-月份范围选择 -->
|
||||
<div v-else-if="activeTab === 'month'" class="date-range-group">
|
||||
<el-date-picker v-model="startDate" type="month" value-format="yyyy-MM" placeholder="选择开始月份"
|
||||
@change="handleStartMonthChange" class="range-date-picker" />
|
||||
<span class="date-separator">至</span>
|
||||
<el-date-picker v-model="endDate" type="month" value-format="yyyy-MM" placeholder="选择结束月份"
|
||||
:picker-options="monthPickerOptions" @change="handleEndMonthChange" class="range-date-picker" />
|
||||
</div>
|
||||
|
||||
<!-- 年模式-年份范围选择 -->
|
||||
<div v-else class="date-range-group">
|
||||
<el-date-picker v-model="startDate" type="year" value-format="yyyy" placeholder="选择开始年份"
|
||||
@change="handleStartYearChange" class="range-date-picker" />
|
||||
<span class="date-separator">至</span>
|
||||
<el-date-picker v-model="endDate" type="year" value-format="yyyy" placeholder="选择结束年份"
|
||||
@change="handleEndYearChange" class="range-date-picker" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 生产汇总数据 -->
|
||||
<div class="summary-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">生产汇总</span>
|
||||
<span class="section-date">{{ displayDateRange }}</span>
|
||||
</div>
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card" v-for="(item, index) in summaryData" :key="index">
|
||||
<span class="summary-label">{{ item.label }}</span>
|
||||
<div class="summary-value-box">
|
||||
<span class="summary-value">{{ item.value }}</span>
|
||||
<span v-if="item.unit" class="summary-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<!-- 产量趋势图:Echarts容器 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">产量趋势</span>
|
||||
</div>
|
||||
<div class="chart-wrapper" ref="productionChart" id="productionChart"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- 班组产量对比:Echarts容器 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">班组产量对比</span>
|
||||
</div>
|
||||
<div class="chart-wrapper" ref="crewChart" id="crewChart"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 规格分布:Echarts饼图容器 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">规格分布</span>
|
||||
</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="pie-charts-row">
|
||||
<div class="pie-chart-item">
|
||||
<span class="pie-title">厚度分布</span>
|
||||
<div class="chart-wrapper" ref="thicknessPie" id="thicknessPie"></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<div class="pie-charts-row">
|
||||
<div class="pie-chart-item">
|
||||
<span class="pie-title">宽度分布</span>
|
||||
<div class="chart-wrapper" ref="widthPie" id="widthPie"></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// ✅ 【完全未改动】原API导入保持不变
|
||||
import {
|
||||
getProductionSummary,
|
||||
getCrewProduction,
|
||||
getThicknessDistribution,
|
||||
getWidthDistribution
|
||||
} from '@/api/pocket/plantState'
|
||||
// 引入Echarts
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// ✅ 【完全未改动】原工具函数
|
||||
function getDefaultDate(type = "day") {
|
||||
const date = new Date();
|
||||
return formatDate(date, type);
|
||||
}
|
||||
|
||||
function formatDate(date, type) {
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
|
||||
switch (type) {
|
||||
case "day": return `${year}-${month}-${day}`;
|
||||
case "month": return `${year}-${month}`;
|
||||
case "year": return `${year}`;
|
||||
default: return `${year}-${month}-${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'PlantProduction',
|
||||
data() {
|
||||
return {
|
||||
activeTab: "day",
|
||||
startDate: getDefaultDate(),
|
||||
endDate: getDefaultDate(),
|
||||
// Echarts实例存储
|
||||
productionChart: null,
|
||||
crewChart: null,
|
||||
thicknessPie: null,
|
||||
widthPie: null,
|
||||
// 月份选择器限制条件
|
||||
monthPickerOptions: {},
|
||||
timeTabs: [
|
||||
{ label: "日", value: "day" },
|
||||
{ label: "月", value: "month" },
|
||||
{ label: "年", value: "year" }
|
||||
],
|
||||
// ✅ 【完全未改动】业务数据
|
||||
summaryData: [
|
||||
{ label: "生产钢卷数", value: 0, unit: "卷" },
|
||||
{ label: "平均宽度", value: 0, unit: "mm" },
|
||||
{ label: "平均厚度", value: 0, unit: "mm" },
|
||||
{ label: "原料总量", value: 0, unit: "t" },
|
||||
{ label: "成品总量", value: 0, unit: "t" },
|
||||
{ label: "成材率", value: 0, unit: "%" }
|
||||
],
|
||||
// Echarts配色沿用原配色
|
||||
mainColor: ["#0066cc", "#00b96b"],
|
||||
columnColor: ["#0066cc", "#409eff", "#66b1ff"],
|
||||
pieColor: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff", "#d9ecff"]
|
||||
};
|
||||
},
|
||||
// ✅ 【完全未改动】计算属性
|
||||
computed: {
|
||||
maxMonthEnd() {
|
||||
if (!this.startDate) return "";
|
||||
const date = new Date(this.startDate);
|
||||
date.setFullYear(date.getFullYear() + 1);
|
||||
return formatDate(date, "month");
|
||||
},
|
||||
displayDateRange() {
|
||||
switch (this.activeTab) {
|
||||
case "day":
|
||||
return this.startDate;
|
||||
case "month":
|
||||
return `${this.startDate} 至 ${this.endDate}`;
|
||||
case "year":
|
||||
return `${this.startDate} 至 ${this.endDate}`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听月份开始值,更新结束月份的可选范围
|
||||
startDate(val) {
|
||||
if (this.activeTab === 'month' && val) {
|
||||
this.monthPickerOptions = {
|
||||
disabledDate: (time) => {
|
||||
const maxDate = new Date(this.maxMonthEnd + '-01')
|
||||
return time.getTime() > maxDate.getTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化Echarts图表
|
||||
this.initCharts()
|
||||
// 加载业务数据
|
||||
this.loadProductionData()
|
||||
// 窗口自适应
|
||||
window.addEventListener('resize', this.resizeCharts)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁Echarts实例,防止内存泄漏
|
||||
window.removeEventListener('resize', this.resizeCharts)
|
||||
this.productionChart && this.productionChart.dispose()
|
||||
this.crewChart && this.crewChart.dispose()
|
||||
this.thicknessPie && this.thicknessPie.dispose()
|
||||
this.widthPie && this.widthPie.dispose()
|
||||
},
|
||||
methods: {
|
||||
// ✅ 【逻辑未改动】仅适配Element的tab参数
|
||||
handleTabChange(tab) {
|
||||
this.activeTab = tab;
|
||||
const defaultDate = getDefaultDate();
|
||||
this.startDate = defaultDate;
|
||||
this.endDate = tab === "day" ? defaultDate : getDefaultDate(tab);
|
||||
this.loadProductionData();
|
||||
},
|
||||
// ✅ 【逻辑未改动】日期变更事件
|
||||
handleDateChange(val) {
|
||||
this.startDate = val;
|
||||
this.endDate = val;
|
||||
this.loadProductionData();
|
||||
},
|
||||
handleStartMonthChange(val) {
|
||||
this.startDate = val;
|
||||
const maxEndDate = new Date(this.startDate);
|
||||
maxEndDate.setFullYear(maxEndDate.getFullYear() + 1);
|
||||
const maxEndStr = formatDate(maxEndDate, "month");
|
||||
if (new Date(this.endDate) > maxEndDate) {
|
||||
this.endDate = maxEndStr;
|
||||
}
|
||||
this.loadProductionData();
|
||||
},
|
||||
handleEndMonthChange(val) {
|
||||
this.endDate = val;
|
||||
this.loadProductionData();
|
||||
},
|
||||
handleStartYearChange(val) {
|
||||
this.startDate = val;
|
||||
this.loadProductionData();
|
||||
},
|
||||
handleEndYearChange(val) {
|
||||
this.endDate = val;
|
||||
this.loadProductionData();
|
||||
},
|
||||
|
||||
// ✅ 【核心逻辑未改动】仅替换uni加载提示为Element
|
||||
loadProductionData() {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '加载中',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
this.loadSummaryData(),
|
||||
this.loadCrewProductionData(),
|
||||
this.loadSpecDistribution()
|
||||
]).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
},
|
||||
|
||||
// ✅ ✅ ✅ 【完全未改动】汇总数据加载
|
||||
loadSummaryData() {
|
||||
const start = this.formatFullDate(this.startDate, true)
|
||||
const end = this.formatFullDate(this.endDate, false)
|
||||
|
||||
return getProductionSummary(start, end).then(response => {
|
||||
if (response.code === 200 && response.data) {
|
||||
const data = response.data
|
||||
this.summaryData = [
|
||||
{ label: "生产钢卷数", value: data.coilCount || 0, unit: "卷" },
|
||||
{ label: "平均宽度", value: data.avgWidth || 0, unit: "mm" },
|
||||
{ label: "平均厚度", value: data.avgThick || 0, unit: "mm" },
|
||||
{ label: "原料总量", value: data.totalEntryWeight || 0, unit: "t" },
|
||||
{ label: "成品总量", value: data.totalExitWeight || 0, unit: "t" },
|
||||
{ label: "成材率", value: data.yieldRate || 0, unit: "%" }
|
||||
]
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载汇总数据失败:', error)
|
||||
})
|
||||
},
|
||||
|
||||
// ✅ ✅ ✅ 【完全未改动】班组产量加载+图表数据组装
|
||||
loadCrewProductionData() {
|
||||
const start = this.formatFullDate(this.startDate, true)
|
||||
const end = this.formatFullDate(this.endDate, false)
|
||||
|
||||
return getCrewProduction(start, end).then(response => {
|
||||
if (response.code === 200 && response.data && response.data.length > 0) {
|
||||
const crewData = response.data
|
||||
|
||||
const categories = []
|
||||
const crewMap = {}
|
||||
|
||||
crewData.forEach(item => {
|
||||
const label = `${item.crew}-${item.shift}`
|
||||
categories.push(label)
|
||||
|
||||
if (!crewMap[item.shift]) {
|
||||
crewMap[item.shift] = []
|
||||
}
|
||||
crewMap[item.shift].push(Number(item.totalWeight) || 0)
|
||||
})
|
||||
|
||||
const series = Object.keys(crewMap).map(shift => ({
|
||||
name: shift,
|
||||
data: crewMap[shift],
|
||||
type: 'bar'
|
||||
}))
|
||||
|
||||
// 渲染图表
|
||||
this.setCrewChart(series.map(item => item.name), series)
|
||||
this.setProductionChart(
|
||||
categories,
|
||||
crewData.map(item => Number(item.totalWeight) || 0),
|
||||
crewData.map(item => Number(item.avgThick) || 0)
|
||||
)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载班组产量失败:', error)
|
||||
})
|
||||
},
|
||||
|
||||
// ✅ ✅ ✅ 【完全未改动】规格分布加载
|
||||
loadSpecDistribution() {
|
||||
const start = this.formatFullDate(this.startDate, true)
|
||||
const end = this.formatFullDate(this.endDate, false)
|
||||
|
||||
return Promise.all([
|
||||
getThicknessDistribution(start, end),
|
||||
getWidthDistribution(start, end)
|
||||
]).then(([thicknessRes, widthRes]) => {
|
||||
if (thicknessRes.code === 200 && thicknessRes.data) {
|
||||
const pieData = thicknessRes.data.map(item => ({
|
||||
name: item.category,
|
||||
value: item.count
|
||||
}))
|
||||
this.setThicknessPie(pieData)
|
||||
}
|
||||
|
||||
if (widthRes.code === 200 && widthRes.data) {
|
||||
const pieData = widthRes.data.map(item => ({
|
||||
name: item.category,
|
||||
value: item.count
|
||||
}))
|
||||
this.setWidthPie(pieData)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载规格分布失败:', error)
|
||||
})
|
||||
},
|
||||
|
||||
// ✅ 【完全未改动】日期格式化工具
|
||||
formatFullDate(dateStr, isStart) {
|
||||
if (!dateStr) return ''
|
||||
if (dateStr.length === 10) {
|
||||
return dateStr
|
||||
}
|
||||
if (dateStr.length === 7) {
|
||||
if (isStart) {
|
||||
return `${dateStr}-01`
|
||||
} else {
|
||||
const [year, month] = dateStr.split('-')
|
||||
const lastDay = new Date(year, month, 0).getDate()
|
||||
return `${dateStr}-${String(lastDay).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
if (dateStr.length === 4) {
|
||||
if (isStart) {
|
||||
return `${dateStr}-01-01`
|
||||
} else {
|
||||
return `${dateStr}-12-31`
|
||||
}
|
||||
}
|
||||
return dateStr
|
||||
},
|
||||
|
||||
// ===== Echarts 图表初始化/渲染/自适应 新增方法 =====
|
||||
initCharts() {
|
||||
// 产量趋势图-混合图(柱形+折线)
|
||||
this.productionChart = echarts.init(document.getElementById('productionChart'))
|
||||
// 班组产量-柱状图
|
||||
this.crewChart = echarts.init(document.getElementById('crewChart'))
|
||||
// 厚度分布-饼图
|
||||
this.thicknessPie = echarts.init(document.getElementById('thicknessPie'))
|
||||
// 宽度分布-饼图
|
||||
this.widthPie = echarts.init(document.getElementById('widthPie'))
|
||||
},
|
||||
// 产量趋势图渲染
|
||||
setProductionChart(xData, prodData, thickData) {
|
||||
const option = {
|
||||
color: this.mainColor,
|
||||
grid: { left: 30, right: 30, top: 40, bottom: 30 },
|
||||
legend: { top: 0, left: 'center' },
|
||||
xAxis: { type: 'category', data: xData, axisLine: { show: false } },
|
||||
yAxis: [
|
||||
{ type: 'value', name: '产量(t)', position: 'left', splitLine: { type: 'dashed' } },
|
||||
{ type: 'value', name: '平均厚度(mm)', position: 'right', splitLine: { show: false } }
|
||||
],
|
||||
series: [
|
||||
{ name: '产量', type: 'bar', data: prodData, barWidth: 20 },
|
||||
{ name: '平均厚度', type: 'line', yAxisIndex: 1, data: thickData, smooth: true }
|
||||
]
|
||||
}
|
||||
this.productionChart.setOption(option)
|
||||
},
|
||||
// 班组产量柱状图渲染
|
||||
setCrewChart(xData, seriesData) {
|
||||
console.log(seriesData, xData)
|
||||
const option = {
|
||||
color: this.columnColor,
|
||||
grid: { left: 30, right: 30, top: 40, bottom: 30 },
|
||||
legend: { top: 0, left: 'center' },
|
||||
xAxis: { type: 'category', data: xData, axisLine: { show: false } },
|
||||
yAxis: { type: 'value', name: '产量(t)', splitLine: { type: 'dashed' } },
|
||||
series: seriesData
|
||||
}
|
||||
this.crewChart.setOption(option)
|
||||
},
|
||||
// 厚度饼图渲染
|
||||
setThicknessPie(pieData) {
|
||||
this.setPieOption(this.thicknessPie, pieData)
|
||||
},
|
||||
// 宽度饼图渲染
|
||||
setWidthPie(pieData) {
|
||||
this.setPieOption(this.widthPie, pieData)
|
||||
},
|
||||
// 饼图公共配置
|
||||
setPieOption(chart, data) {
|
||||
const option = {
|
||||
color: this.pieColor,
|
||||
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
|
||||
legend: { bottom: 0, left: 'center', textStyle: { fontSize: 12 } },
|
||||
series: [{ name: '分布占比', type: 'pie', radius: ['40%', '70%'], data }]
|
||||
}
|
||||
chart.setOption(option)
|
||||
},
|
||||
// 图表自适应
|
||||
resizeCharts() {
|
||||
this.productionChart && this.productionChart.resize()
|
||||
this.crewChart && this.crewChart.resize()
|
||||
this.thicknessPie && this.thicknessPie.resize()
|
||||
this.widthPie && this.widthPie.resize()
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// PC端样式适配:rpx转px(1rpx=0.5px),保留原有布局逻辑
|
||||
.page-container {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
min-height: calc(100vh - 20px);
|
||||
}
|
||||
|
||||
.time-tab-bar {
|
||||
display: flex;
|
||||
width: 240px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.time-tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
border-radius: 3px;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.time-tab-active {
|
||||
background: #0066cc;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 日期选择区
|
||||
.date-selector {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
margin-left: 20px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.single-date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-range-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.range-date-picker {
|
||||
flex: 1;
|
||||
border-radius: 3px 0 0 3px;
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.date-separator {
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
// 区块通用样式
|
||||
.summary-section,
|
||||
.chart-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 8px;
|
||||
border-left: 2px solid #0066cc;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-date {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
// 汇总卡片网格
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 14px 10px;
|
||||
text-align: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.summary-value-box {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #0066cc;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.summary-unit {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
// 图表容器
|
||||
.chart-wrapper {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 12px 8px;
|
||||
min-height: 225px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 饼图布局
|
||||
.pie-charts-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pie-chart-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pie-title {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
</style>
|
||||
837
klp-ui/src/views/lines/acid/components/real-time-monitoring.vue
Normal file
837
klp-ui/src/views/lines/acid/components/real-time-monitoring.vue
Normal file
@@ -0,0 +1,837 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 刷新按钮(固定在右下角,保留) -->
|
||||
<div class="refresh-btn-fixed" @click="refreshData">
|
||||
<span class="refresh-icon" :class="{ 'rotating': isRefreshing }">⟳</span>
|
||||
</div>
|
||||
|
||||
<!-- 快速导航菜单(固定在左下角,保留全部导航项) -->
|
||||
<div class="nav-menu-fixed" :class="{ 'nav-expanded': navMenuExpanded }">
|
||||
<div class="nav-toggle" @click="navMenuExpanded = !navMenuExpanded">
|
||||
<span class="nav-toggle-icon">{{ navMenuExpanded ? '✕' : '☰' }}</span>
|
||||
</div>
|
||||
<div class="nav-items" v-if="navMenuExpanded">
|
||||
<div class="nav-item" @click="scrollToSection('speed-monitor')">
|
||||
<span class="nav-label">速度监控</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('exit-speed-chart')">
|
||||
<span class="nav-label">出口速度趋势</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('temp-chart')">
|
||||
<span class="nav-label">酸槽温度趋势</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('looper-status')">
|
||||
<span class="nav-label">活套运行状态</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('tank-concentration')">
|
||||
<span class="nav-label">酸槽浓度监控</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('force-chart')">
|
||||
<span class="nav-label">轧制力趋势</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('process-params')">
|
||||
<span class="nav-label">工艺参数</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('roll-speed')">
|
||||
<span class="nav-label">轧辊速度监控</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('reduc-rate')">
|
||||
<span class="nav-label">机架压下率</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('tension-monitor')">
|
||||
<span class="nav-label">带钢张力监控</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('power-ratio')">
|
||||
<span class="nav-label">机架功率百分比</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('paint-speed')">
|
||||
<span class="nav-label">涂装速度监控</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('tlv-params')">
|
||||
<span class="nav-label">拉矫参数</span>
|
||||
</div>
|
||||
<div class="nav-item" @click="scrollToSection('paint-temp-chart')">
|
||||
<span class="nav-label">烘干温度趋势</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 核心:纯实时监控滚动容器 【移除tab判断,直接展示】 -->
|
||||
<div class="scroll-container">
|
||||
<!-- 顶部状态栏 -->
|
||||
<div class="status-bar">
|
||||
<div class="status-item">
|
||||
<span class="status-label">网络状态</span>
|
||||
<span class="status-value" :class="'status-' + webStatus[0].value">{{ webStatus[0].value }}</span>
|
||||
</div>
|
||||
<div class="status-divider"></div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">当前班组</span>
|
||||
<span class="status-value">{{ webStatus[1].value }}</span>
|
||||
</div>
|
||||
<div class="status-divider"></div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">更新时间</span>
|
||||
<span class="status-value status-time">{{ lastUpdateTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 速度监控 -->
|
||||
<div class="section" id="speed-monitor">
|
||||
<div class="section-title">速度监控</div>
|
||||
<div class="metrics-grid-3">
|
||||
<div class="metric-box" v-for="(item, index) in speedMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 出口速度趋势 -->
|
||||
<div class="section" id="exit-speed-chart">
|
||||
<div class="section-title">出口速度趋势</div>
|
||||
<div class="chart-box" ref="exitSpeedChart" id="exitSpeedChart"></div>
|
||||
</div>
|
||||
|
||||
<!-- 酸槽温度趋势 -->
|
||||
<div class="section" id="temp-chart">
|
||||
<div class="section-title">酸槽温度趋势</div>
|
||||
<div class="chart-box" ref="tempChart" id="tempChart"></div>
|
||||
</div>
|
||||
|
||||
<!-- 活套运行状态 -->
|
||||
<div class="section" id="looper-status">
|
||||
<div class="section-title">活套运行状态</div>
|
||||
<div class="metrics-grid-3">
|
||||
<div class="metric-box" v-for="(item, index) in looperMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 酸槽浓度监控 -->
|
||||
<div class="section" id="tank-concentration">
|
||||
<div class="section-title">酸槽浓度监控</div>
|
||||
<div class="tank-grid">
|
||||
<div class="tank-card" v-for="(tank, index) in tankConcentration" :key="index">
|
||||
<div class="tank-header">{{ tank.name }}</div>
|
||||
<div class="tank-data">
|
||||
<div class="tank-row">
|
||||
<span class="data-label">酸浓度</span>
|
||||
<span class="data-value">{{ tank.hclCont }} <span class="data-unit">g/L</span></span>
|
||||
</div>
|
||||
<div class="tank-divider"></div>
|
||||
<div class="tank-row">
|
||||
<span class="data-label">铁盐浓度</span>
|
||||
<span class="data-value">{{ tank.feCont }} <span class="data-unit">g/L</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 轧制力趋势 -->
|
||||
<div class="section" id="force-chart">
|
||||
<div class="section-title">轧制力趋势</div>
|
||||
<div class="chart-box" ref="forceChart" id="forceChart"></div>
|
||||
</div>
|
||||
|
||||
<!-- 工艺参数 -->
|
||||
<div class="section" id="process-params">
|
||||
<div class="section-title">工艺参数</div>
|
||||
<div class="metrics-grid-2">
|
||||
<div class="metric-box" v-for="(item, index) in processMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ 镀锌线数据 ============ -->
|
||||
<div class="section-divider">
|
||||
<span class="divider-text">镀锌线监控数据</span>
|
||||
</div>
|
||||
|
||||
<!-- 轧辊速度监控 -->
|
||||
<div class="section" id="roll-speed">
|
||||
<div class="section-title">轧辊速度监控</div>
|
||||
<div class="metrics-grid-3">
|
||||
<div class="metric-box" v-for="(item, index) in rollSpeedMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 机架压下率 -->
|
||||
<div class="section" id="reduc-rate">
|
||||
<div class="section-title">机架压下率</div>
|
||||
<div class="metrics-grid-3">
|
||||
<div class="metric-box" v-for="(item, index) in reducMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 带钢张力监控 -->
|
||||
<div class="section" id="tension-monitor">
|
||||
<div class="section-title">带钢张力监控</div>
|
||||
<div class="metrics-grid-3">
|
||||
<div class="metric-box" v-for="(item, index) in tensionMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 机架功率百分比 -->
|
||||
<div class="section" id="power-ratio">
|
||||
<div class="section-title">机架功率百分比</div>
|
||||
<div class="metrics-grid-3">
|
||||
<div class="metric-box" v-for="(item, index) in powerMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ 涂装线数据 ============ -->
|
||||
<div class="section-divider">
|
||||
<span class="divider-text">涂装线监控数据</span>
|
||||
</div>
|
||||
|
||||
<!-- 涂装速度监控 -->
|
||||
<div class="section" id="paint-speed">
|
||||
<div class="section-title">涂装速度监控</div>
|
||||
<div class="metrics-grid-3">
|
||||
<div class="metric-box" v-for="(item, index) in paintSpeedMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 拉矫参数 -->
|
||||
<div class="section" id="tlv-params">
|
||||
<div class="section-title">拉矫参数</div>
|
||||
<div class="metrics-grid-2">
|
||||
<div class="metric-box" v-for="(item, index) in tlvMetrics" :key="index">
|
||||
<span class="metric-name">{{ item.label }}</span>
|
||||
<span class="metric-value">{{ item.value }}</span>
|
||||
<span class="metric-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 烘干温度趋势 -->
|
||||
<div class="section" id="paint-temp-chart">
|
||||
<div class="section-title">烘干温度趋势</div>
|
||||
<div class="chart-box" ref="paintTempChart" id="paintTempChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// ✅ 原接口导入 完全未改动
|
||||
import { getAllPlantStateDefines, listPlantStateHistory, getCurrentShift } from '@/api/pocket/plantState'
|
||||
// ✅ 引入Echarts5.x 兼容Vue2
|
||||
import * as echarts from 'echarts'
|
||||
// ✅ 引入axios替换uni.request (PC端网络请求)
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'RealTimeMonitor',
|
||||
data() {
|
||||
return {
|
||||
webStatus: [
|
||||
{ label: '网络状态', value: '检测中...' },
|
||||
{ label: '当前班组', value: '—' }
|
||||
],
|
||||
lastUpdateTime: '—', // 最后更新时间
|
||||
isRefreshing: false, // 是否正在刷新
|
||||
refreshTimer: null, // 定时器
|
||||
navMenuExpanded: false, // 导航菜单是否展开
|
||||
// ✅ 移除了 tabData/currentTab 无用属性
|
||||
// 速度监控指标(ID=1,2,3)
|
||||
speedMetrics: [
|
||||
{ label: '出口带钢速度', value: '—', unit: 'm/min' },
|
||||
{ label: '酸洗带钢速度', value: '—', unit: 'm/min' },
|
||||
{ label: '圆盘剪速度', value: '—', unit: 'm/min' }
|
||||
],
|
||||
// 活套状态(ID=8,9,10)
|
||||
looperMetrics: [
|
||||
{ label: '入口活套', value: '—', unit: '%' },
|
||||
{ label: '出口活套', value: '—', unit: '%' },
|
||||
{ label: '联机活套', value: '—', unit: '%' }
|
||||
],
|
||||
// 酸槽浓度(ID=11-16)
|
||||
tankConcentration: [
|
||||
{ name: '1#酸槽', hclCont: '—', feCont: '—' },
|
||||
{ name: '2#酸槽', hclCont: '—', feCont: '—' },
|
||||
{ name: '3#酸槽', hclCont: '—', feCont: '—' }
|
||||
],
|
||||
// 其他工艺参数(ID=7,17,18,19,20)
|
||||
processMetrics: [
|
||||
{ label: '漂洗温度', value: '—', unit: '°C' },
|
||||
{ label: '烘干温度', value: '—', unit: '°C' },
|
||||
{ label: '漂洗电导率', value: '—', unit: 'g/L' },
|
||||
{ label: '联机活套张力', value: '—', unit: 'kN' },
|
||||
{ label: '拉矫机延伸率', value: '—', unit: '%' }
|
||||
],
|
||||
// 镀锌线数据
|
||||
rollSpeedMetrics: [
|
||||
{ label: '1#机架', value: '—', unit: 'm/min' },
|
||||
{ label: '2#机架', value: '—', unit: 'm/min' },
|
||||
{ label: '3#机架', value: '—', unit: 'm/min' },
|
||||
{ label: '4#机架', value: '—', unit: 'm/min' },
|
||||
{ label: '5#机架', value: '—', unit: 'm/min' },
|
||||
{ label: '6#机架', value: '—', unit: 'm/min' }
|
||||
],
|
||||
reducMetrics: [
|
||||
{ label: '1#机架', value: '—', unit: '%' },
|
||||
{ label: '2#机架', value: '—', unit: '%' },
|
||||
{ label: '3#机架', value: '—', unit: '%' },
|
||||
{ label: '4#机架', value: '—', unit: '%' },
|
||||
{ label: '5#机架', value: '—', unit: '%' },
|
||||
{ label: '6#机架', value: '—', unit: '%' }
|
||||
],
|
||||
tensionMetrics: [
|
||||
{ label: '0#张力', value: '—', unit: 'kN' },
|
||||
{ label: '1#张力', value: '—', unit: 'kN' },
|
||||
{ label: '2#张力', value: '—', unit: 'kN' },
|
||||
{ label: '3#张力', value: '—', unit: 'kN' },
|
||||
{ label: '4#张力', value: '—', unit: 'kN' },
|
||||
{ label: '5#张力', value: '—', unit: 'kN' },
|
||||
{ label: '6#张力', value: '—', unit: 'kN' }
|
||||
],
|
||||
powerMetrics: [
|
||||
{ label: '1#机架', value: '—', unit: '%' },
|
||||
{ label: '2#机架', value: '—', unit: '%' },
|
||||
{ label: '3#机架', value: '—', unit: '%' },
|
||||
{ label: '4#机架', value: '—', unit: '%' },
|
||||
{ label: '5#机架', value: '—', unit: '%' },
|
||||
{ label: '6#机架', value: '—', unit: '%' }
|
||||
],
|
||||
// 涂装线数据
|
||||
paintSpeedMetrics: [
|
||||
{ label: '出口带钢速度', value: '—', unit: 'm/min' },
|
||||
{ label: '涂装带钢速度', value: '—', unit: 'm/min' },
|
||||
{ label: '圆盘剪速度', value: '—', unit: 'm/min' }
|
||||
],
|
||||
tlvMetrics: [
|
||||
{ label: '拉矫延伸率', value: '—', unit: '%' },
|
||||
{ label: '破磷机插入量1', value: '—', unit: 'mm' },
|
||||
{ label: '破磷机插入量2', value: '—', unit: 'mm' },
|
||||
{ label: '破磷机插入量3', value: '—', unit: 'mm' }
|
||||
],
|
||||
// Echarts图表实例
|
||||
exitSpeedChart: null,
|
||||
tempChart: null,
|
||||
forceChart: null,
|
||||
paintTempChart: null,
|
||||
// 图表配色与原qiun一致
|
||||
lineColor: ["#0066cc", "#409eff", "#66b1ff"],
|
||||
forceColor: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff", "#d9ecff", "#ecf5ff"],
|
||||
paintColor: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff", "#d9ecff"],
|
||||
plantStateDefines: [] // 缓存所有的状态定义
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadAllData() // 加载所有数据
|
||||
this.startAutoRefresh() // 启动自动刷新
|
||||
this.initEcharts() // 初始化图表
|
||||
window.addEventListener('resize', this.resizeEcharts) // 窗口自适应
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopAutoRefresh() // 页面销毁时清除定时器
|
||||
window.removeEventListener('resize', this.resizeEcharts)
|
||||
this.disposeEcharts() // 销毁图表实例 防内存泄漏
|
||||
},
|
||||
methods: {
|
||||
// ✅ 初始化Echarts图表
|
||||
initEcharts() {
|
||||
this.exitSpeedChart = echarts.init(document.getElementById('exitSpeedChart'))
|
||||
this.tempChart = echarts.init(document.getElementById('tempChart'))
|
||||
this.forceChart = echarts.init(document.getElementById('forceChart'))
|
||||
this.paintTempChart = echarts.init(document.getElementById('paintTempChart'))
|
||||
},
|
||||
// ✅ 图表自适应
|
||||
resizeEcharts() {
|
||||
this.exitSpeedChart && this.exitSpeedChart.resize()
|
||||
this.tempChart && this.tempChart.resize()
|
||||
this.forceChart && this.forceChart.resize()
|
||||
this.paintTempChart && this.paintTempChart.resize()
|
||||
},
|
||||
// ✅ 销毁图表实例
|
||||
disposeEcharts() {
|
||||
this.exitSpeedChart && this.exitSpeedChart.dispose()
|
||||
this.tempChart && this.tempChart.dispose()
|
||||
this.forceChart && this.forceChart.dispose()
|
||||
this.paintTempChart && this.paintTempChart.dispose()
|
||||
},
|
||||
// ✅ 渲染折线图通用方法
|
||||
renderLineChart(chart, xData, seriesData, color, yTitle) {
|
||||
if(!chart) return
|
||||
const option = {
|
||||
color: color,
|
||||
grid: { left: 30, right: 30, top: 40, bottom: 30 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { top: 0, left: 'center', textStyle: { fontSize: 12 } },
|
||||
xAxis: { type: 'category', data: xData, axisLine: { show: false } },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: yTitle,
|
||||
splitLine: { type: 'dashed', color: '#e4e7ed' }
|
||||
},
|
||||
series: seriesData.map(item => ({
|
||||
name: item.name,
|
||||
type: 'line',
|
||||
data: item.data,
|
||||
smooth: true,
|
||||
lineStyle: { width: 2 },
|
||||
symbol: 'none'
|
||||
}))
|
||||
}
|
||||
chart.setOption(option, true)
|
||||
},
|
||||
// 启动自动刷新(每30秒) 逻辑完全未改
|
||||
startAutoRefresh() {
|
||||
this.refreshTimer = setInterval(() => {
|
||||
console.log('自动刷新数据...')
|
||||
this.refreshData(true) // 静默刷新
|
||||
}, 30000) // 30秒刷新一次
|
||||
},
|
||||
// 停止自动刷新 逻辑完全未改
|
||||
stopAutoRefresh() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer)
|
||||
this.refreshTimer = null
|
||||
}
|
||||
},
|
||||
// 加载所有数据(初始化) 逻辑完全未改
|
||||
loadAllData() {
|
||||
this.checkNetworkStatus() // 检测网络状态
|
||||
this.loadCurrentShift() // 加载当前班组
|
||||
this.initPlantStateDefines() // 加载所有定义
|
||||
this.updateLastTime() // 更新时间
|
||||
},
|
||||
// 刷新数据(手动或自动) ✅ 替换uni弹窗为Element弹窗
|
||||
refreshData(isSilent = false) {
|
||||
if (this.isRefreshing) return // 防止重复刷新
|
||||
this.isRefreshing = true
|
||||
|
||||
let loading = null
|
||||
if (!isSilent) {
|
||||
loading = this.$loading({ lock: true, text: '刷新中', spinner: 'el-icon-loading' })
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
this.checkNetworkStatus(),
|
||||
this.loadCurrentShift(),
|
||||
this.initPlantStateDefines(isSilent)
|
||||
]).finally(() => {
|
||||
this.isRefreshing = false
|
||||
loading && loading.close()
|
||||
if (!isSilent) {
|
||||
this.$message.success('刷新成功')
|
||||
}
|
||||
this.updateLastTime()
|
||||
})
|
||||
},
|
||||
// 更新最后刷新时间 逻辑完全未改
|
||||
updateLastTime() {
|
||||
const now = new Date()
|
||||
const hour = String(now.getHours()).padStart(2, '0')
|
||||
const minute = String(now.getMinutes()).padStart(2, '0')
|
||||
const second = String(now.getSeconds()).padStart(2, '0')
|
||||
this.lastUpdateTime = `${hour}:${minute}:${second}`
|
||||
},
|
||||
// 检测网络状态 ✅ 替换uni.request为axios
|
||||
checkNetworkStatus() {
|
||||
return new Promise((resolve) => {
|
||||
const startTime = Date.now()
|
||||
axios.get(`http://140.143.206.120:8080/pocket/proPlantStateDefine/allWithValues`, { timeout: 5000 })
|
||||
.then(() => {
|
||||
const responseTime = Date.now() - startTime
|
||||
if (responseTime < 500) this.webStatus[0].value = '通畅'
|
||||
else if (responseTime < 2000) this.webStatus[0].value = '卡顿'
|
||||
else this.webStatus[0].value = '异常'
|
||||
resolve()
|
||||
}).catch(() => {
|
||||
this.webStatus[0].value = '异常'
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 加载当前班组信息 逻辑完全未改
|
||||
loadCurrentShift() {
|
||||
return getCurrentShift().then(response => {
|
||||
if (response.code === 200 && response.data) {
|
||||
const shiftData = response.data
|
||||
const shiftName = this.getShiftName(shiftData.shift)
|
||||
const crewName = this.getCrewName(shiftData.crew)
|
||||
this.webStatus[1].value = `${crewName} / ${shiftName}`
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载班组信息失败:', error)
|
||||
})
|
||||
},
|
||||
// 获取班次名称 逻辑完全未改
|
||||
getShiftName(shift) {
|
||||
const shiftMap = { 'A': '早班', 'B': '中班', 'C': '晚班' }
|
||||
return shiftMap[shift] || shift || '—'
|
||||
},
|
||||
// 获取班组名称 逻辑完全未改
|
||||
getCrewName(crew) {
|
||||
const crewMap = { 1: '甲', 2: '乙', 3: '丙', 4: '丁' }
|
||||
return crewMap[crew] || crew || '—'
|
||||
},
|
||||
// 初始化:加载所有状态定义及其当前值 ✅ 替换uni弹窗为Element弹窗
|
||||
initPlantStateDefines(isSilent = false) {
|
||||
let loading = null
|
||||
if (!isSilent) {
|
||||
loading = this.$loading({ lock: true, text: '加载中', spinner: 'el-icon-loading' })
|
||||
}
|
||||
return getAllPlantStateDefines().then(response => {
|
||||
if (response.code === 200 && response.data) {
|
||||
this.plantStateDefines = response.data
|
||||
this.updateCurrentMetrics()
|
||||
return this.loadTempTrend(isSilent)
|
||||
}
|
||||
}).finally(() => {
|
||||
loading && loading.close()
|
||||
}).catch(error => {
|
||||
loading && loading.close()
|
||||
console.error('加载状态定义失败:', error)
|
||||
})
|
||||
},
|
||||
// 更新所有实时指标 逻辑完全未改
|
||||
updateCurrentMetrics() {
|
||||
// 速度监控
|
||||
const exitSpeed = this.getDefineById(1)
|
||||
const plSpeed = this.getDefineById(2)
|
||||
const trimSpeed = this.getDefineById(3)
|
||||
this.speedMetrics = [
|
||||
{ label: exitSpeed?.comments || '出口带钢速度', value: this.formatValue(exitSpeed?.currentValue), unit: exitSpeed?.units || 'm/min' },
|
||||
{ label: plSpeed?.comments || '酸洗带钢速度', value: this.formatValue(plSpeed?.currentValue), unit: plSpeed?.units || 'm/min' },
|
||||
{ label: trimSpeed?.comments || '圆盘剪速度', value: this.formatValue(trimSpeed?.currentValue), unit: trimSpeed?.units || 'm/min' }
|
||||
]
|
||||
// 活套状态
|
||||
const celLooper = this.getDefineById(8)
|
||||
const cxlLooper = this.getDefineById(9)
|
||||
const telLooper = this.getDefineById(10)
|
||||
this.looperMetrics = [
|
||||
{ label: celLooper?.comments || '入口重套', value: this.formatValue(celLooper?.currentValue), unit: celLooper?.units || '%' },
|
||||
{ label: cxlLooper?.comments || '出口活套', value: this.formatValue(cxlLooper?.currentValue), unit: cxlLooper?.units || '%' },
|
||||
{ label: telLooper?.comments || '联机活套', value: this.formatValue(telLooper?.currentValue), unit: telLooper?.units || '%' }
|
||||
]
|
||||
// 酸槽浓度
|
||||
this.tankConcentration = [
|
||||
{ name: '1#酸槽', hclCont: this.formatValue(this.getDefineById(11)?.currentValue), feCont: this.formatValue(this.getDefineById(12)?.currentValue) },
|
||||
{ name: '2#酸槽', hclCont: this.formatValue(this.getDefineById(13)?.currentValue), feCont: this.formatValue(this.getDefineById(14)?.currentValue) },
|
||||
{ name: '3#酸槽', hclCont: this.formatValue(this.getDefineById(15)?.currentValue), feCont: this.formatValue(this.getDefineById(16)?.currentValue) }
|
||||
]
|
||||
// 工艺参数
|
||||
const rinseTemp = this.getDefineById(7)
|
||||
const windTemp = this.getDefineById(17)
|
||||
const rinseFlow = this.getDefineById(18)
|
||||
const telTension = this.getDefineById(19)
|
||||
const tlvElong = this.getDefineById(20)
|
||||
this.processMetrics = [
|
||||
{ label: rinseTemp?.comments || '漂洗温度', value: this.formatValue(rinseTemp?.currentValue), unit: rinseTemp?.units || '°C' },
|
||||
{ label: windTemp?.comments || '烘干温度', value: this.formatValue(windTemp?.currentValue), unit: windTemp?.units || '°C' },
|
||||
{ label: rinseFlow?.comments || '漂洗电导率', value: this.formatValue(rinseFlow?.currentValue), unit: rinseFlow?.units || 'g/L' },
|
||||
{ label: telTension?.comments || '联机活套张力', value: this.formatValue(telTension?.currentValue), unit: telTension?.units || 'kN' },
|
||||
{ label: tlvElong?.comments || '拉矫机延伸率', value: this.formatValue(tlvElong?.currentValue), unit: tlvElong?.units || '%' }
|
||||
]
|
||||
// 镀锌线-轧辊速度
|
||||
this.rollSpeedMetrics = [
|
||||
{ label: '1#机架', value: this.formatValue(this.getDefineById(36)?.currentValue), unit: 'm/min' },
|
||||
{ label: '2#机架', value: this.formatValue(this.getDefineById(37)?.currentValue), unit: 'm/min' },
|
||||
{ label: '3#机架', value: this.formatValue(this.getDefineById(38)?.currentValue), unit: 'm/min' },
|
||||
{ label: '4#机架', value: this.formatValue(this.getDefineById(39)?.currentValue), unit: 'm/min' },
|
||||
{ label: '5#机架', value: this.formatValue(this.getDefineById(40)?.currentValue), unit: 'm/min' },
|
||||
{ label: '6#机架', value: this.formatValue(this.getDefineById(41)?.currentValue), unit: 'm/min' }
|
||||
]
|
||||
// 镀锌线-压下率
|
||||
this.reducMetrics = [
|
||||
{ label: '1#机架', value: this.formatValue(this.getDefineById(24)?.currentValue), unit: '%' },
|
||||
{ label: '2#机架', value: this.formatValue(this.getDefineById(25)?.currentValue), unit: '%' },
|
||||
{ label: '3#机架', value: this.formatValue(this.getDefineById(26)?.currentValue), unit: '%' },
|
||||
{ label: '4#机架', value: this.formatValue(this.getDefineById(27)?.currentValue), unit: '%' },
|
||||
{ label: '5#机架', value: this.formatValue(this.getDefineById(28)?.currentValue), unit: '%' },
|
||||
{ label: '6#机架', value: this.formatValue(this.getDefineById(29)?.currentValue), unit: '%' }
|
||||
]
|
||||
// 镀锌线-张力
|
||||
this.tensionMetrics = [
|
||||
{ label: '0#张力', value: this.formatValue(this.getDefineById(42)?.currentValue), unit: 'kN' },
|
||||
{ label: '1#张力', value: this.formatValue(this.getDefineById(43)?.currentValue), unit: 'kN' },
|
||||
{ label: '2#张力', value: this.formatValue(this.getDefineById(44)?.currentValue), unit: 'kN' },
|
||||
{ label: '3#张力', value: this.formatValue(this.getDefineById(45)?.currentValue), unit: 'kN' },
|
||||
{ label: '4#张力', value: this.formatValue(this.getDefineById(46)?.currentValue), unit: 'kN' },
|
||||
{ label: '5#张力', value: this.formatValue(this.getDefineById(47)?.currentValue), unit: 'kN' },
|
||||
{ label: '6#张力', value: this.formatValue(this.getDefineById(48)?.currentValue), unit: 'kN' }
|
||||
]
|
||||
// 镀锌线-功率
|
||||
this.powerMetrics = [
|
||||
{ label: '1#机架', value: this.formatValue(this.getDefineById(49)?.currentValue), unit: '%' },
|
||||
{ label: '2#机架', value: this.formatValue(this.getDefineById(50)?.currentValue), unit: '%' },
|
||||
{ label: '3#机架', value: this.formatValue(this.getDefineById(51)?.currentValue), unit: '%' },
|
||||
{ label: '4#机架', value: this.formatValue(this.getDefineById(52)?.currentValue), unit: '%' },
|
||||
{ label: '5#机架', value: this.formatValue(this.getDefineById(53)?.currentValue), unit: '%' },
|
||||
{ label: '6#机架', value: this.formatValue(this.getDefineById(54)?.currentValue), unit: '%' }
|
||||
]
|
||||
// 涂装线-速度
|
||||
this.paintSpeedMetrics = [
|
||||
{ label: '出口带钢速度', value: this.formatValue(this.getDefineById(1)?.currentValue), unit: 'm/min' },
|
||||
{ label: '涂装带钢速度', value: this.formatValue(this.getDefineById(2)?.currentValue), unit: 'm/min' },
|
||||
{ label: '圆盘剪速度', value: this.formatValue(this.getDefineById(3)?.currentValue), unit: 'm/min' }
|
||||
]
|
||||
// 涂装线-拉矫参数
|
||||
this.tlvMetrics = [
|
||||
{ label: '拉矫延伸率', value: this.formatValue(this.getDefineById(20)?.currentValue), unit: '%' },
|
||||
{ label: '破磷机插入量1', value: this.formatValue(this.getDefineById(21)?.currentValue), unit: 'mm' },
|
||||
{ label: '破磷机插入量2', value: this.formatValue(this.getDefineById(22)?.currentValue), unit: 'mm' },
|
||||
{ label: '破磷机插入量3', value: this.formatValue(this.getDefineById(23)?.currentValue), unit: 'mm' }
|
||||
]
|
||||
},
|
||||
// 加载历史趋势图数据 ✅ 渲染Echarts图表
|
||||
loadTempTrend(isSilent = false) {
|
||||
return listPlantStateHistory({ pageNum: 1, pageSize: 30 }).then(response => {
|
||||
if (response.code === 200 && response.rows && response.rows.length > 0) {
|
||||
const categories = []
|
||||
const tank1Data = [];const tank2Data = [];const tank3Data = [];
|
||||
const exitSpeedData = [];
|
||||
const force1Data = [];const force2Data = [];const force3Data = [];const force4Data = [];const force5Data = [];const force6Data = [];
|
||||
const paintTemp1 = [];const paintTemp2 = [];const paintTemp3 = [];const paintTemp4 = [];const paintTemp5 = [];
|
||||
|
||||
response.rows.forEach(item => {
|
||||
const dateStr = this.formatDate(item.insdate)
|
||||
categories.push(dateStr)
|
||||
tank1Data.push(Number(item.value4) || 0)
|
||||
tank2Data.push(Number(item.value5) || 0)
|
||||
tank3Data.push(Number(item.value6) || 0)
|
||||
exitSpeedData.push(Number(item.value1) || 0)
|
||||
force1Data.push(Number(item.value30) || 0)
|
||||
force2Data.push(Number(item.value31) || 0)
|
||||
force3Data.push(Number(item.value32) || 0)
|
||||
force4Data.push(Number(item.value33) || 0)
|
||||
force5Data.push(Number(item.value34) || 0)
|
||||
force6Data.push(Number(item.value35) || 0)
|
||||
paintTemp1.push(Number(item.value4) || 0)
|
||||
paintTemp2.push(Number(item.value5) || 0)
|
||||
paintTemp3.push(Number(item.value6) || 0)
|
||||
paintTemp4.push(Number(item.value7) || 0)
|
||||
paintTemp5.push(Number(item.value17) || 0)
|
||||
})
|
||||
const reverseCate = categories.reverse()
|
||||
// 渲染出口速度趋势
|
||||
this.renderLineChart(this.exitSpeedChart, reverseCate, [{name:'出口带钢速度',data:exitSpeedData.reverse()}], this.lineColor, 'm/min')
|
||||
// 渲染酸槽温度趋势
|
||||
this.renderLineChart(this.tempChart, reverseCate, [
|
||||
{name:'1#酸槽温度',data:tank1Data.reverse()},
|
||||
{name:'2#酸槽温度',data:tank2Data.reverse()},
|
||||
{name:'3#酸槽温度',data:tank3Data.reverse()}
|
||||
], this.lineColor, '°C')
|
||||
// 渲染轧制力趋势
|
||||
this.renderLineChart(this.forceChart, reverseCate, [
|
||||
{name:'1#轧制力',data:force1Data.reverse()},
|
||||
{name:'2#轧制力',data:force2Data.reverse()},
|
||||
{name:'3#轧制力',data:force3Data.reverse()},
|
||||
{name:'4#轧制力',data:force4Data.reverse()},
|
||||
{name:'5#轧制力',data:force5Data.reverse()},
|
||||
{name:'6#轧制力',data:force6Data.reverse()}
|
||||
], this.forceColor, 'kN')
|
||||
// 渲染烘干温度趋势
|
||||
this.renderLineChart(this.paintTempChart, reverseCate, [
|
||||
{name:'1#酸槽温度',data:paintTemp1.reverse()},
|
||||
{name:'2#酸槽温度',data:paintTemp2.reverse()},
|
||||
{name:'3#酸槽温度',data:paintTemp3.reverse()},
|
||||
{name:'漂洗温度',data:paintTemp4.reverse()},
|
||||
{name:'烘干温度',data:paintTemp5.reverse()}
|
||||
], this.paintColor, '°C')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载历史趋势失败:', error)
|
||||
})
|
||||
},
|
||||
// 根据ID获取Define对象 逻辑完全未改
|
||||
getDefineById(id) {
|
||||
return this.plantStateDefines.find(item => item.id == id)
|
||||
},
|
||||
// 格式化日期 逻辑完全未改
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const hour = String(date.getHours()).padStart(2, '0')
|
||||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${hour}:${minute}`
|
||||
},
|
||||
// 格式化数值 逻辑完全未改
|
||||
formatValue(value) {
|
||||
if (value === null || value === undefined || value === '') return '—'
|
||||
const num = Number(value)
|
||||
if (isNaN(num)) return '—'
|
||||
return num.toFixed(2)
|
||||
},
|
||||
// 滚动到指定部分 ✅ PC端原生滚动适配
|
||||
scrollToSection(sectionId) {
|
||||
document.getElementById(sectionId).scrollIntoView({ behavior: 'smooth' })
|
||||
this.navMenuExpanded = false // 点击后关闭菜单
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// ✅ 所有rpx转px(1rpx=0.5px) PC端完美适配 | 移除tab相关样式
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fa;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 刷新按钮(固定在右下角) */
|
||||
.refresh-btn-fixed {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 60px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #0066cc;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 10px rgba(0, 102, 204, 0.4);
|
||||
z-index: 999;
|
||||
cursor: pointer;
|
||||
&:active { opacity: 0.8; transform: scale(0.95); }
|
||||
}
|
||||
.refresh-icon {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
&.rotating { animation: rotate 1s linear infinite; }
|
||||
}
|
||||
@keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||
|
||||
/* 快速导航菜单(固定在左下角) */
|
||||
.nav-menu-fixed {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
bottom: 60px;
|
||||
z-index: 998;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
.nav-toggle {
|
||||
width: 48px;height: 48px;
|
||||
background: #409eff;
|
||||
border-radius: 50%;
|
||||
display: flex;align-items: center;justify-content: center;
|
||||
box-shadow: 0 4px 10px rgba(64, 158, 255, 0.4);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
&:active { opacity: 0.8; transform: scale(0.95); }
|
||||
}
|
||||
.nav-toggle-icon { font-size: 24px; color: #fff; display: block; line-height: 1; }
|
||||
.nav-items {
|
||||
display: flex;flex-direction: column;gap:4px;
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
@keyframes slideUp { from { opacity:0; transform: translateY(10px); } to { opacity:1; transform: translateY(0); } }
|
||||
.nav-item {
|
||||
background: #fff;border:1px solid #409eff;border-radius:4px;
|
||||
padding:8px 12px;cursor: pointer;
|
||||
box-shadow: 0 2px 6px rgba(64,158,255,0.2);
|
||||
transition: all 0.2s ease;
|
||||
&:active { background: #f0f9ff; transform: scale(0.95); }
|
||||
}
|
||||
.nav-label { font-size:13px; color:#409eff; font-weight:500; white-space: nowrap; }
|
||||
|
||||
/* 滚动容器 - PC端原生滚动 核心修改 */
|
||||
.scroll-container {
|
||||
height: calc(100vh - 20px);
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* 顶部状态栏 */
|
||||
.status-bar {
|
||||
display: flex;align-items: center;
|
||||
background: #fff;padding:12px 16px;margin-bottom:12px;
|
||||
border-radius:4px;border:1px solid #e4e7ed;
|
||||
}
|
||||
.status-item { flex:1; display:flex;align-items:center;justify-content:center;gap:8px; }
|
||||
.status-label { font-size:13px;color:#909399; }
|
||||
.status-value {
|
||||
font-size:14px;font-weight:500;color:#303133;
|
||||
&.status-通畅 { color: #67c23a; }
|
||||
&.status-卡顿 { color: #e6a23c; }
|
||||
&.status-异常 { color: #f56c6c; }
|
||||
&.status-time { color:#909399;font-size:12px; }
|
||||
}
|
||||
.status-divider { width:1px;height:20px;background:#e4e7ed; }
|
||||
|
||||
/* 区块样式 */
|
||||
.section { margin-bottom:12px; }
|
||||
.section-title {
|
||||
font-size:15px;font-weight:500;color:#303133;
|
||||
margin-bottom:10px;padding-left:8px;border-left:2px solid #0066cc;
|
||||
}
|
||||
|
||||
/* 分隔符 */
|
||||
.section-divider {
|
||||
display:flex;align-items:center;margin:20px 0 12px 0;padding:0 8px;
|
||||
}
|
||||
.divider-text {
|
||||
font-size:14px;font-weight:600;color:#0066cc;
|
||||
padding:0 6px;background:#f5f7fa;
|
||||
border-left:2px solid #0066cc;padding-left:8px;
|
||||
}
|
||||
|
||||
/* 指标卡片 - 3列布局 */
|
||||
.metrics-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap:10px; }
|
||||
/* 指标卡片 - 2列布局 */
|
||||
.metrics-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap:10px; }
|
||||
.metric-box {
|
||||
background:#fff;border:1px solid #e4e7ed;border-radius:4px;
|
||||
padding:14px 10px;text-align:center;
|
||||
}
|
||||
.metric-name { display:block;font-size:12px;color:#909399;margin-bottom:8px; }
|
||||
.metric-value { display:block;font-size:24px;font-weight:600;color:#0066cc;margin-bottom:4px;line-height:1; }
|
||||
.metric-unit { display:block;font-size:11px;color:#909399; }
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-box {
|
||||
background:#fff;border:1px solid #e4e7ed;border-radius:4px;
|
||||
padding:12px 8px;height:225px;width:100%;
|
||||
}
|
||||
|
||||
/* 酸槽监控网格 */
|
||||
.tank-grid { display:grid;grid-template-columns:repeat(3,1fr);gap:10px; }
|
||||
.tank-card { background:#fff;border:2px solid #0066cc;border-radius:4px;overflow:hidden; }
|
||||
.tank-header { background:#0066cc;color:#fff;font-size:14px;font-weight:500;padding:10px;text-align:center; }
|
||||
.tank-data { padding:12px 8px; }
|
||||
.tank-row { display:flex;flex-direction:column;align-items:center;padding:6px 0; }
|
||||
.data-label { font-size:11px;color:#909399;margin-bottom:4px; }
|
||||
.data-value { font-size:18px;font-weight:600;color:#303133; }
|
||||
.data-unit { font-size:10px;font-weight:400;color:#909399;margin-left:2px; }
|
||||
.tank-divider { height:1px;background:#e4e7ed;margin:6px 0; }
|
||||
</style>
|
||||
908
klp-ui/src/views/lines/acid/components/shutdown-statistic.vue
Normal file
908
klp-ui/src/views/lines/acid/components/shutdown-statistic.vue
Normal file
@@ -0,0 +1,908 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<!-- 时间维度切换 -->
|
||||
<div class="time-tab-bar">
|
||||
<div v-for="item in timeTabs" :key="item.value" class="time-tab-item"
|
||||
:class="{ 'time-tab-active': activeTab === item.value }" @click="handleTabChange(item.value)">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日期选择区 -->
|
||||
<div class="date-selector">
|
||||
<!-- 日模式 -->
|
||||
<el-date-picker v-if="activeTab === 'day'" v-model="startDate" type="date" value-format="yyyy-MM-dd"
|
||||
placeholder="选择日期" @change="handleDateChange" class="single-date-picker" />
|
||||
|
||||
<!-- 月模式 -->
|
||||
<div v-else-if="activeTab === 'month'" class="date-range-group">
|
||||
<el-date-picker v-model="startDate" type="month" value-format="yyyy-MM" placeholder="选择开始月份"
|
||||
@change="handleStartMonthChange" class="range-date-picker" />
|
||||
<span class="date-separator">至</span>
|
||||
<el-date-picker v-model="endDate" type="month" value-format="yyyy-MM" placeholder="选择结束月份"
|
||||
:picker-options="monthPickerOptions" @change="handleEndMonthChange" class="range-date-picker" />
|
||||
</div>
|
||||
|
||||
<!-- 年模式 -->
|
||||
<div v-else class="date-range-group">
|
||||
<el-date-picker v-model="startDate" type="year" value-format="yyyy" placeholder="选择开始年份"
|
||||
@change="handleStartYearChange" class="range-date-picker" />
|
||||
<span class="date-separator">至</span>
|
||||
<el-date-picker v-model="endDate" type="year" value-format="yyyy" placeholder="选择结束年份"
|
||||
@change="handleEndYearChange" class="range-date-picker" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 停机汇总 -->
|
||||
<div class="summary-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">停机汇总</span>
|
||||
<span class="section-date">{{ displayDateRange }}</span>
|
||||
</div>
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card" v-for="(item, index) in summaryData" :key="index">
|
||||
<span class="summary-label">{{ item.label }}</span>
|
||||
<div class="summary-value-box">
|
||||
<span class="summary-value">{{ item.value }}</span>
|
||||
<span v-if="item.unit" class="summary-unit">{{ item.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<!-- 停机分布 - 班组 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">班组停机分布</span>
|
||||
</div>
|
||||
<div v-if="crewPieData.length > 0" class="pie-chart-single" ref="crewPie" id="crewPie"></div>
|
||||
<div class="empty-chart" v-else>
|
||||
<span class="empty-icon">📊</span>
|
||||
<span class="empty-text">此时间段未发生停机</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<!-- 停机分布 - 类型 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">停机类型分布</span>
|
||||
</div>
|
||||
<div v-if="typePieData.length > 0" class="pie-chart-single" ref="typePie" id="typePie"></div>
|
||||
<div class="empty-chart" v-else>
|
||||
<span class="empty-icon">📊</span>
|
||||
<span class="empty-text">此时间段未发生停机</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 停机详细列表(日视图) -->
|
||||
<div class="detail-section" v-if="activeTab === 'day'">
|
||||
<div class="section-header">
|
||||
<span class="section-title">停机详情</span>
|
||||
</div>
|
||||
<div class="detail-list">
|
||||
<div v-if="tableData.length === 0" class="empty-state">
|
||||
<span class="empty-text">暂无停机记录</span>
|
||||
</div>
|
||||
<el-table v-else :data="tableData">
|
||||
<el-table-column label="时间范围" prop="time"></el-table-column>
|
||||
<el-table-column label="持续时间" prop="duration"></el-table-column>
|
||||
<el-table-column label="机组" prop="machine"></el-table-column>
|
||||
<el-table-column label="备注" prop="remark" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 停机趋势图(月/年视图) -->
|
||||
<div class="chart-section" v-else>
|
||||
<div class="section-header">
|
||||
<span class="section-title">停机趋势</span>
|
||||
</div>
|
||||
<div class="chart-wrapper trend-chart" ref="trendChart" id="trendChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// ✅【完全未改动】原API导入
|
||||
import { listStoppage } from '@/api/pocket/plantState'
|
||||
// 引入Echarts5.x 兼容Vue2
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// ✅【完全未改动】原独立工具函数
|
||||
function getDefaultDate(type = "day") {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
|
||||
switch (type) {
|
||||
case "day":
|
||||
return `${year}-${month}-${day}`;
|
||||
case "month":
|
||||
return `${year}-${month}`;
|
||||
case "year":
|
||||
return `${year}`;
|
||||
default:
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getLastMonth() {
|
||||
const date = new Date();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
return `${year}-${month}`;
|
||||
}
|
||||
|
||||
function formatDate(date, type) {
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
|
||||
switch (type) {
|
||||
case "day":
|
||||
return `${year}-${month}-${day}`;
|
||||
case "month":
|
||||
return `${year}-${month}`;
|
||||
case "year":
|
||||
return `${year}`;
|
||||
default:
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'PlantStoppage',
|
||||
data() {
|
||||
return {
|
||||
// ✅【完全未改动】业务数据
|
||||
activeTab: "day",
|
||||
startDate: getDefaultDate(),
|
||||
endDate: getDefaultDate(),
|
||||
timeTabs: [
|
||||
{ label: "日", value: "day" },
|
||||
{ label: "月", value: "month" },
|
||||
{ label: "年", value: "year" }
|
||||
],
|
||||
summaryData: [
|
||||
{ label: "停机时间", value: 0, unit: "min" },
|
||||
{ label: "停机次数", value: 0, unit: "次" },
|
||||
{ label: "作业率", value: 0, unit: "%" }
|
||||
],
|
||||
// Echarts图表实例
|
||||
trendChart: null,
|
||||
crewPie: null,
|
||||
typePie: null,
|
||||
// 月份选择器限制条件
|
||||
monthPickerOptions: {},
|
||||
// 趋势图临时存储X轴数据
|
||||
trendXData: [],
|
||||
// ✅【完全未改动】原图表配色
|
||||
mainColor: ["#0066cc", "#f56c6c"],
|
||||
pieColor: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff", "#d9ecff"],
|
||||
crewPieData: [],
|
||||
typePieData: [],
|
||||
tableData: [],
|
||||
// ✅【新增】resize防抖定时器,解决窗口缩放卡顿
|
||||
resizeTimer: null
|
||||
};
|
||||
},
|
||||
// ✅【完全未改动】计算属性
|
||||
computed: {
|
||||
maxMonthEnd() {
|
||||
if (!this.startDate) return "";
|
||||
const date = new Date(this.startDate);
|
||||
date.setFullYear(date.getFullYear() + 1);
|
||||
return formatDate(date, "month");
|
||||
},
|
||||
displayDateRange() {
|
||||
switch (this.activeTab) {
|
||||
case "day":
|
||||
return this.startDate;
|
||||
case "month":
|
||||
return `${this.startDate} 至 ${this.endDate}`;
|
||||
case "year":
|
||||
return `${this.startDate} 至 ${this.endDate}`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听开始月份,更新结束月份可选范围
|
||||
startDate(val) {
|
||||
if (this.activeTab === 'month' && val) {
|
||||
this.monthPickerOptions = {
|
||||
disabledDate: (time) => {
|
||||
const maxDate = new Date(this.maxMonthEnd + '-01')
|
||||
return time.getTime() > maxDate.getTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// ✅【新增】监听tab切换,自动触发图表自适应
|
||||
activeTab() {
|
||||
this.$nextTick(() => {
|
||||
this.resizeEcharts()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化Echarts图表
|
||||
this.initEcharts()
|
||||
// 加载业务数据
|
||||
this.loadStoppageData()
|
||||
// 窗口自适应监听
|
||||
window.addEventListener('resize', this.resizeEcharts)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// ✅【修复】安全销毁图表实例+清除监听+清除定时器,防止内存泄漏
|
||||
window.removeEventListener('resize', this.resizeEcharts)
|
||||
clearTimeout(this.resizeTimer)
|
||||
if (this.trendChart && !this.trendChart.isDisposed()) {
|
||||
this.trendChart.dispose()
|
||||
this.trendChart = null
|
||||
}
|
||||
if (this.crewPie && !this.crewPie.isDisposed()) {
|
||||
this.crewPie.dispose()
|
||||
this.crewPie = null
|
||||
}
|
||||
if (this.typePie && !this.typePie.isDisposed()) {
|
||||
this.typePie.dispose()
|
||||
this.typePie = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// ✅【逻辑未改动】仅适配Element传参
|
||||
handleTabChange(tab) {
|
||||
this.activeTab = tab;
|
||||
if (tab === "day") {
|
||||
const today = getDefaultDate();
|
||||
this.startDate = today;
|
||||
this.endDate = today;
|
||||
} else if (tab === "month") {
|
||||
this.startDate = getLastMonth();
|
||||
this.endDate = getDefaultDate("month");
|
||||
} else {
|
||||
const currentYear = getDefaultDate("year");
|
||||
this.startDate = currentYear;
|
||||
this.endDate = currentYear;
|
||||
}
|
||||
this.loadStoppageData();
|
||||
},
|
||||
handleDateChange(val) {
|
||||
this.startDate = val;
|
||||
this.endDate = val;
|
||||
this.loadStoppageData();
|
||||
},
|
||||
handleStartMonthChange(val) {
|
||||
this.startDate = val;
|
||||
const maxEndDate = new Date(this.startDate);
|
||||
maxEndDate.setFullYear(maxEndDate.getFullYear() + 1);
|
||||
const maxEndStr = formatDate(maxEndDate, "month");
|
||||
if (new Date(this.endDate) > maxEndDate) {
|
||||
this.endDate = maxEndStr;
|
||||
}
|
||||
this.loadStoppageData();
|
||||
},
|
||||
handleEndMonthChange(val) {
|
||||
this.endDate = val;
|
||||
this.loadStoppageData();
|
||||
},
|
||||
handleStartYearChange(val) {
|
||||
this.startDate = val;
|
||||
this.loadStoppageData();
|
||||
},
|
||||
handleEndYearChange(val) {
|
||||
this.endDate = val;
|
||||
this.loadStoppageData();
|
||||
},
|
||||
|
||||
// ✅【核心逻辑完全未改动】仅替换uni加载提示为Element + 新增清空图表逻辑
|
||||
loadStoppageData() {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '加载中',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
|
||||
const start = this.formatFullDate(this.startDate, true)
|
||||
let end = this.formatFullDate(this.endDate, false)
|
||||
|
||||
if (this.activeTab === 'month' && this.endDate && this.endDate.length === 7) {
|
||||
const today = new Date()
|
||||
const todayYear = today.getFullYear()
|
||||
const todayMonth = today.getMonth() + 1
|
||||
const [endYear, endMonth] = this.endDate.split('-').map(Number)
|
||||
if (endYear === todayYear && endMonth === todayMonth) {
|
||||
const todayDay = today.getDate()
|
||||
end = `${this.endDate}-${String(todayDay).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
|
||||
const queryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
startDate: start,
|
||||
endDate: end
|
||||
}
|
||||
|
||||
console.log('停机查询参数:', queryParams)
|
||||
|
||||
listStoppage(queryParams).then(response => {
|
||||
loading.close()
|
||||
console.log('停机统计响应:', response)
|
||||
|
||||
if (response.code === 200 && response.rows && response.rows.length > 0) {
|
||||
this.tableData = response.rows.map(item => ({
|
||||
time: this.formatDateTime(item.startDate) + ' - ' + this.formatDateTime(item.endDate),
|
||||
duration: this.secondsToMinutes(item.duration) + 'min',
|
||||
remark: item.remark || '-',
|
||||
machine: item.unit || '-'
|
||||
}))
|
||||
|
||||
const totalDurationSeconds = response.rows.reduce((sum, item) => sum + (Number(item.duration) || 0), 0)
|
||||
const totalDurationMinutes = this.secondsToMinutes(totalDurationSeconds)
|
||||
const totalCount = response.rows.length
|
||||
const totalAvailableMinutes = this.getTotalAvailableMinutes()
|
||||
const workRate = this.calculateWorkRate(totalDurationMinutes, totalAvailableMinutes)
|
||||
|
||||
this.summaryData = [
|
||||
{ label: '停机时间', value: totalDurationMinutes, unit: 'min' },
|
||||
{ label: '停机次数', value: totalCount, unit: '次' },
|
||||
{ label: '作业率', value: workRate, unit: '%' }
|
||||
]
|
||||
|
||||
const crewMap = {}
|
||||
const typeMap = {}
|
||||
response.rows.forEach(item => {
|
||||
const crew = item.crew || '未知班组'
|
||||
const type = item.stopType || '未知类型'
|
||||
const durationMinutes = this.secondsToMinutes(item.duration)
|
||||
crewMap[crew] = (crewMap[crew] || 0) + durationMinutes
|
||||
typeMap[type] = (typeMap[type] || 0) + durationMinutes
|
||||
})
|
||||
|
||||
this.crewPieData = Object.keys(crewMap).map(crew => ({ name: crew, value: crewMap[crew] }))
|
||||
this.typePieData = Object.keys(typeMap).map(type => ({ name: type, value: typeMap[type] }))
|
||||
|
||||
// 渲染饼图
|
||||
this.$nextTick(() => {
|
||||
this.renderPieChart('crew', this.crewPieData)
|
||||
this.renderPieChart('type', this.typePieData)
|
||||
})
|
||||
|
||||
if (this.activeTab !== 'day') {
|
||||
if (response.rows.length > 0) {
|
||||
this.buildTrendChart(response.rows)
|
||||
} else {
|
||||
this.trendXData = []
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('暂无停机数据')
|
||||
this.tableData = []
|
||||
this.summaryData = [
|
||||
{ label: '停机时间', value: 0, unit: 'min' },
|
||||
{ label: '停机次数', value: 0, unit: '次' },
|
||||
{ label: '作业率', value: 100, unit: '%' }
|
||||
]
|
||||
this.crewPieData = []
|
||||
this.typePieData = []
|
||||
this.trendXData = []
|
||||
// ✅【修复】清空所有图表数据
|
||||
this.clearChart(this.trendChart)
|
||||
this.renderPieChart('crew', [])
|
||||
this.renderPieChart('type', [])
|
||||
}
|
||||
}).catch(error => {
|
||||
loading.close()
|
||||
console.error('加载停机数据失败:', error)
|
||||
this.$message.error('加载失败,请稍后重试')
|
||||
})
|
||||
},
|
||||
|
||||
// ✅【完全未改动】原所有工具方法
|
||||
formatDateTime(dateStr) {
|
||||
if (!dateStr) return '-'
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
},
|
||||
getTotalAvailableMinutes() {
|
||||
const start = new Date(this.formatFullDate(this.startDate, true))
|
||||
const end = new Date(this.formatFullDate(this.endDate, false))
|
||||
const diffTime = end - start
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1
|
||||
return diffDays * 1440
|
||||
},
|
||||
calculateWorkRate(stopDuration, totalAvailableMinutes) {
|
||||
if (!totalAvailableMinutes || totalAvailableMinutes === 0) {
|
||||
return 100
|
||||
}
|
||||
const workRate = ((totalAvailableMinutes - stopDuration) / totalAvailableMinutes) * 100
|
||||
return Math.max(0, Math.min(100, workRate)).toFixed(2)
|
||||
},
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
return `${month}/${day}`
|
||||
},
|
||||
formatDateByMonth(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const month = date.getMonth() + 1
|
||||
return `${month}月`
|
||||
},
|
||||
secondsToMinutes(seconds) {
|
||||
if (!seconds || seconds === 0) return 0
|
||||
return Math.round(Number(seconds) / 60)
|
||||
},
|
||||
formatFullDate(dateStr, isStart) {
|
||||
if (!dateStr) return ''
|
||||
if (dateStr.length === 10) {
|
||||
return dateStr
|
||||
}
|
||||
if (dateStr.length === 7) {
|
||||
if (isStart) {
|
||||
return `${dateStr}-01`
|
||||
} else {
|
||||
const [year, month] = dateStr.split('-')
|
||||
const lastDay = new Date(year, month, 0).getDate()
|
||||
return `${dateStr}-${String(lastDay).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
if (dateStr.length === 4) {
|
||||
if (isStart) {
|
||||
return `${dateStr}-01-01`
|
||||
} else {
|
||||
return `${dateStr}-12-31`
|
||||
}
|
||||
}
|
||||
return dateStr
|
||||
},
|
||||
|
||||
// ✅【逻辑未改动】仅修改图表渲染方式
|
||||
buildTrendChart(stoppageData) {
|
||||
if (!stoppageData || stoppageData.length === 0) {
|
||||
console.log('无法构建趋势图:数据为空')
|
||||
this.trendXData = []
|
||||
return
|
||||
}
|
||||
const dateMap = {}
|
||||
const isYearView = this.activeTab === 'year'
|
||||
stoppageData.forEach(item => {
|
||||
if (!item.startDate) return
|
||||
let key
|
||||
if (isYearView) {
|
||||
key = this.formatDateByMonth(item.startDate)
|
||||
} else {
|
||||
key = this.formatDate(item.startDate)
|
||||
}
|
||||
if (!dateMap[key]) {
|
||||
dateMap[key] = { duration: 0, count: 0 }
|
||||
}
|
||||
const durationMinutes = this.secondsToMinutes(item.duration)
|
||||
dateMap[key].duration += durationMinutes
|
||||
dateMap[key].count += 1
|
||||
})
|
||||
|
||||
let categories = []
|
||||
if (isYearView) {
|
||||
categories = Array.from({ length: 12 }, (_, i) => `${i + 1}月`)
|
||||
} else {
|
||||
const startStr = this.formatFullDate(this.startDate, true)
|
||||
let endStr = this.formatFullDate(this.endDate, false)
|
||||
const today = new Date()
|
||||
const todayYear = today.getFullYear()
|
||||
const todayMonth = today.getMonth() + 1
|
||||
if (this.endDate && this.endDate.length === 7) {
|
||||
const [endYear, endMonth] = this.endDate.split('-').map(Number)
|
||||
if (endYear === todayYear && endMonth === todayMonth) {
|
||||
const todayDay = today.getDate()
|
||||
endStr = `${this.endDate}-${String(todayDay).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
const start = new Date(startStr)
|
||||
const end = new Date(endStr)
|
||||
const dateList = []
|
||||
const currentDate = new Date(start)
|
||||
while (currentDate <= end) {
|
||||
const month = currentDate.getMonth() + 1
|
||||
const day = currentDate.getDate()
|
||||
const dateKey = `${month}/${day}`
|
||||
dateList.push(dateKey)
|
||||
currentDate.setDate(currentDate.getDate() + 1)
|
||||
}
|
||||
categories = dateList
|
||||
}
|
||||
|
||||
if (categories.length === 0) {
|
||||
console.log('无法构建趋势图:无有效日期')
|
||||
this.trendXData = []
|
||||
return
|
||||
}
|
||||
|
||||
this.trendXData = categories
|
||||
const durationData = []
|
||||
const rateData = []
|
||||
categories.forEach(key => {
|
||||
const data = dateMap[key] || { duration: 0, count: 0 }
|
||||
durationData.push(data.duration)
|
||||
let totalMinutes
|
||||
if (isYearView) {
|
||||
const year = parseInt(this.startDate) || new Date().getFullYear()
|
||||
const monthIndex = parseInt(key.replace('月', '')) - 1
|
||||
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate()
|
||||
totalMinutes = daysInMonth * 1440
|
||||
} else {
|
||||
totalMinutes = 1440
|
||||
}
|
||||
const rate = this.calculateWorkRate(data.duration, totalMinutes)
|
||||
rateData.push(Number(rate))
|
||||
})
|
||||
// 渲染趋势混合图
|
||||
this.renderTrendChart(categories, durationData, rateData)
|
||||
},
|
||||
|
||||
// ===== ✅【全部修复】Echarts 图表初始化/渲染/自适应 核心方法 =====
|
||||
// 安全初始化单个图表实例【新增核心方法】
|
||||
initSingleChart(chartId) {
|
||||
const dom = document.getElementById(chartId)
|
||||
console.log('获取dom', dom)
|
||||
if (!dom) return null
|
||||
const chartInstance = echarts.init(dom)
|
||||
return chartInstance
|
||||
},
|
||||
// 安全清空图表内容【新增核心方法】
|
||||
clearChart(chartInstance) {
|
||||
if (chartInstance && !chartInstance.isDisposed()) {
|
||||
chartInstance.clear()
|
||||
}
|
||||
},
|
||||
// 初始化图表(懒初始化占位)
|
||||
initEcharts() {
|
||||
this.trendChart = this.initSingleChart('trendChart')
|
||||
this.crewPie = this.initSingleChart('crewPie')
|
||||
this.typePie = this.initSingleChart('typePie')
|
||||
},
|
||||
// 渲染停机趋势图 (柱状+折线 双Y轴)【修复完整版】
|
||||
renderTrendChart(xData, durData, rateData) {
|
||||
this.trendChart = this.initSingleChart('trendChart')
|
||||
if (!this.trendChart) return
|
||||
if (xData.length === 0) {
|
||||
this.clearChart(this.trendChart)
|
||||
return
|
||||
}
|
||||
const option = {
|
||||
color: this.mainColor,
|
||||
grid: { left: 30, right: 30, top: 40, bottom: 60 },
|
||||
legend: { top: 0, left: 'center' },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xData,
|
||||
axisLine: { show: false },
|
||||
axisLabel: { rotate: 60, fontSize: 12 }
|
||||
},
|
||||
yAxis: [
|
||||
{ type: 'value', name: '停机时间(min)', position: 'left', splitLine: { type: 'dashed', color: '#e4e7ed' } },
|
||||
{ type: 'value', name: '作业率(%)', position: 'right', splitLine: { show: false } }
|
||||
],
|
||||
series: [
|
||||
{ name: '停机时间', type: 'bar', data: durData, barWidth: 40 },
|
||||
{ name: '作业率', type: 'line', yAxisIndex: 1, data: rateData, smooth: true }
|
||||
]
|
||||
}
|
||||
this.trendChart.setOption(option, true)
|
||||
},
|
||||
// 渲染饼图 (班组/类型 通用)【修复完整版】
|
||||
renderPieChart(type, data) {
|
||||
const chartId = type === 'crew' ? 'crewPie' : 'typePie'
|
||||
if (type === 'crew') {
|
||||
this.crewPie = this.initSingleChart(chartId)
|
||||
} else {
|
||||
this.typePie = this.initSingleChart(chartId)
|
||||
}
|
||||
console.log('渲染饼图数据:', type, data, chartId)
|
||||
const chart = type === 'crew' ? this.crewPie : this.typePie
|
||||
console.log('获取到的图表实例:', chart)
|
||||
if (!chart) return
|
||||
if (data.length === 0) {
|
||||
this.clearChart(chart)
|
||||
return
|
||||
}
|
||||
const option = {
|
||||
color: this.pieColor,
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c}min ({d}%)' },
|
||||
legend: { bottom: 0, left: 'center', textStyle: { fontSize: 12, color: '#666' } },
|
||||
series: [{
|
||||
name: type === 'crew' ? '班组停机' : '类型停机',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: data,
|
||||
label: { show: true, fontSize: 12 }
|
||||
}]
|
||||
}
|
||||
chart.setOption(option, true)
|
||||
},
|
||||
// 图表自适应 - 防抖+安全校验【修复完整版】
|
||||
resizeEcharts: function () {
|
||||
clearTimeout(this.resizeTimer)
|
||||
this.resizeTimer = setTimeout(() => {
|
||||
if (this.trendChart && !this.trendChart.isDisposed()) this.trendChart.resize()
|
||||
if (this.crewPie && !this.crewPie.isDisposed()) this.crewPie.resize()
|
||||
if (this.typePie && !this.typePie.isDisposed()) this.typePie.resize()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// ✅ PC端适配:rpx转px(1rpx=0.5px) 保留原布局+样式+间距 + 修复图表高度问题
|
||||
.page-container {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
min-height: calc(100vh - 20px);
|
||||
}
|
||||
|
||||
.time-tab-bar {
|
||||
display: flex;
|
||||
width: 240px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.time-tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
border-radius: 3px;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.time-tab-active {
|
||||
background: #0066cc;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.date-selector {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
margin-left: 20px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.single-date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-range-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.range-date-picker {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.date-separator {
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.summary-section,
|
||||
.chart-section,
|
||||
.detail-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 8px;
|
||||
border-left: 2px solid #0066cc;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-date {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 14px 10px;
|
||||
text-align: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.summary-value-box {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #0066cc;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.summary-unit {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 12px 8px;
|
||||
min-height: 225px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// ✅【修复】趋势图高度强制生效
|
||||
.trend-chart {
|
||||
height: 250px !important;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
// ✅【修复】饼图容器增加固定高度,解决高度塌陷
|
||||
.pie-chart-single {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 16px 0;
|
||||
height: 260px !important;
|
||||
min-height: 240px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// ✅【修复】空图表容器高度和饼图一致
|
||||
.empty-chart {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 50px 0;
|
||||
height: 260px !important;
|
||||
min-height: 240px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 40px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-list {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-time {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.detail-duration {
|
||||
font-size: 14px;
|
||||
color: #0066cc;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.detail-text {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 50px 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
526
klp-ui/src/views/lines/acid/components/team-performance.vue
Normal file
526
klp-ui/src/views/lines/acid/components/team-performance.vue
Normal file
@@ -0,0 +1,526 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 月份选择器 -->
|
||||
<div class="month-selector">
|
||||
<div class="month-arrow" @click="changeMonth(-1)" :class="{ disabled: isMinMonth }">
|
||||
<span>◀</span>
|
||||
</div>
|
||||
<el-date-picker v-model="selectedMonth" type="month" value-format="yyyy-MM" placeholder="选择月份"
|
||||
@change="handleMonthChange" class="month-display" />
|
||||
<div class="month-arrow" @click="changeMonth(1)" :class="{ disabled: isMaxMonth }">
|
||||
<span>▶</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<!-- 班组产量对比 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">产量对比</span>
|
||||
</div>
|
||||
<div class="chart-wrapper" ref="outputChart" id="outputChart"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- 质量指标对比 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">质量指标</span>
|
||||
</div>
|
||||
<div class="chart-wrapper" ref="radarChart" id="radarChart"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 班组详细数据 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<!-- 班组绩效排名 -->
|
||||
<div class="ranking-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">绩效排名</span>
|
||||
</div>
|
||||
<div class="ranking-list">
|
||||
<div class="ranking-item" v-for="(item, index) in teamRankingData" :key="index"
|
||||
:class="'rank-' + (index + 1)">
|
||||
<div class="rank-badge">{{ index + 1 }}</div>
|
||||
<div class="rank-info">
|
||||
<span class="team-name">{{ item.team }}</span>
|
||||
<span class="team-shift">{{ item.shift }}</span>
|
||||
</div>
|
||||
<div class="rank-score">
|
||||
<span class="score-value">{{ item.score }}</span>
|
||||
<span class="score-label">分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="detail-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">详细数据</span>
|
||||
</div>
|
||||
<div class="detail-table">
|
||||
<el-table :data="tableData">
|
||||
<el-table-column prop="team" label="班组" />
|
||||
<el-table-column prop="output" label="产量(t)" />
|
||||
<el-table-column prop="yieldRate" label="成材率" />
|
||||
<el-table-column prop="passRate" label="合格率" />
|
||||
<el-table-column prop="score" label="综合评分" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// ✅【完全未改动】原API导入
|
||||
import { getTeamPerformance } from '@/api/pocket/plantState'
|
||||
// 引入Echarts5.x 兼容Vue2
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// ✅【完全未改动】原工具函数
|
||||
function getCurrentMonth() {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
return `${year}-${month}`;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TeamPerformance',
|
||||
data() {
|
||||
return {
|
||||
selectedMonth: getCurrentMonth(),
|
||||
startDate: "2020-01",
|
||||
endDate: getCurrentMonth(),
|
||||
// ✅【完全未改动】表格列配置
|
||||
columns: [
|
||||
{ title: '班组', key: 'team' },
|
||||
{ title: '产量(t)', key: 'output' },
|
||||
{ title: '成材率', key: 'yieldRate' },
|
||||
{ title: '合格率', key: 'passRate' },
|
||||
{ title: '综合评分', key: 'score' }
|
||||
],
|
||||
tableData: [],
|
||||
teamRankingData: [
|
||||
{ team: '甲班', shift: '早班', score: 96.8 },
|
||||
{ team: '乙班', shift: '中班', score: 95.5 },
|
||||
{ team: '丙班', shift: '晚班', score: 94.2 },
|
||||
{ team: '丁班', shift: '早班', score: 93.6 }
|
||||
],
|
||||
// Echarts图表实例
|
||||
outputChart: null,
|
||||
radarChart: null,
|
||||
// ✅【保留原配色】
|
||||
columnColor: ["#0066cc"],
|
||||
radarColor: ["#0066cc", "#409eff", "#66b1ff", "#a0cfff"]
|
||||
};
|
||||
},
|
||||
// ✅【完全未改动】计算属性
|
||||
computed: {
|
||||
isMinMonth() {
|
||||
return this.selectedMonth === this.startDate;
|
||||
},
|
||||
isMaxMonth() {
|
||||
return this.selectedMonth === this.endDate;
|
||||
},
|
||||
formattedMonth() {
|
||||
const [year, month] = this.selectedMonth.split("-");
|
||||
return `${year}年${month}月`;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化Echarts图表
|
||||
this.initCharts()
|
||||
// 加载业务数据
|
||||
this.loadTeamPerformance();
|
||||
// 窗口自适应监听
|
||||
window.addEventListener('resize', this.resizeCharts)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表实例,防止内存泄漏
|
||||
window.removeEventListener('resize', this.resizeCharts)
|
||||
this.outputChart && this.outputChart.dispose()
|
||||
this.radarChart && this.radarChart.dispose()
|
||||
},
|
||||
methods: {
|
||||
// ✅【逻辑未改动】仅适配Element的参数格式
|
||||
handleMonthChange(val) {
|
||||
this.selectedMonth = val;
|
||||
this.loadTeamPerformance();
|
||||
},
|
||||
|
||||
// ✅【完全未改动】月份左右切换逻辑
|
||||
changeMonth(step) {
|
||||
const [year, month] = this.selectedMonth.split("-").map(Number);
|
||||
const targetDate = new Date(year, month - 1 + step);
|
||||
const targetMonth = `${targetDate.getFullYear()}-${(targetDate.getMonth() + 1).toString().padStart(2, "0")}`;
|
||||
|
||||
if (this.compareMonths(targetMonth, this.startDate) < 0) return;
|
||||
if (this.compareMonths(targetMonth, this.endDate) > 0) return;
|
||||
|
||||
this.selectedMonth = targetMonth;
|
||||
this.loadTeamPerformance();
|
||||
},
|
||||
|
||||
// ✅【完全未改动】月份比较工具
|
||||
compareMonths(a, b) {
|
||||
const [aYear, aMonth] = a.split("-").map(Number);
|
||||
const [bYear, bMonth] = b.split("-").map(Number);
|
||||
if (aYear !== bYear) return aYear - bYear;
|
||||
return aMonth - bMonth;
|
||||
},
|
||||
|
||||
// ✅【核心逻辑完全未改动】加载数据 + 修复1个BUG + 替换加载提示
|
||||
loadTeamPerformance() {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '加载中',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
|
||||
const startDate = `${this.selectedMonth}-01`
|
||||
const [year, month] = this.selectedMonth.split('-')
|
||||
const lastDay = new Date(year, month, 0).getDate()
|
||||
const endDate = `${this.selectedMonth}-${String(lastDay).padStart(2, '0')}`
|
||||
|
||||
console.log('查询班组绩效:', startDate, endDate)
|
||||
|
||||
getTeamPerformance(startDate, endDate).then(response => {
|
||||
loading.close()
|
||||
|
||||
if (response.code === 200 && response.data && response.data.length > 0) {
|
||||
const teamData = response.data
|
||||
console.log('班组绩效数据:', teamData)
|
||||
|
||||
// 排名数据(取前4名)
|
||||
this.teamRankingData = teamData.slice(0, 4).map(item => ({
|
||||
team: item.crew,
|
||||
shift: item.shift,
|
||||
score: Number(item.score) || 0
|
||||
}))
|
||||
|
||||
// ✅【修复原代码BUG】item.teamName 改为 item.crew (接口实际返回的字段)
|
||||
this.tableData = teamData.map(item => ({
|
||||
team: item.crew,
|
||||
output: Number(item.output) || 0,
|
||||
yieldRate: `${Number(item.yieldRate) || 0}%`,
|
||||
passRate: `${Number(item.passRate) || 0}%`,
|
||||
score: Number(item.score) || 0
|
||||
}))
|
||||
|
||||
// 提取图表所需数据
|
||||
const categories = teamData.map(item => item.crew)
|
||||
const outputData = teamData.map(item => Number(item.output) || 0)
|
||||
const radarSeries = teamData.map(item => ({
|
||||
name: item.crew,
|
||||
value: [
|
||||
Number(this.normalizeValue(item.output, 1500)),
|
||||
Number(item.yieldRate) || 0,
|
||||
Number(item.passRate) || 0,
|
||||
Number(item.avgThickQuality) || 0,
|
||||
Number(item.avgShapeQuality) || 0
|
||||
],
|
||||
areaStyle: {
|
||||
color: '#409eff70'
|
||||
}
|
||||
}))
|
||||
|
||||
// 渲染图表
|
||||
this.renderOutputChart(categories, outputData)
|
||||
this.renderRadarChart(radarSeries)
|
||||
} else {
|
||||
console.log('暂无班组绩效数据')
|
||||
this.tableData = []
|
||||
this.teamRankingData = []
|
||||
// 清空图表
|
||||
this.renderOutputChart([], [])
|
||||
this.renderRadarChart([])
|
||||
}
|
||||
}).catch(error => {
|
||||
loading.close()
|
||||
console.error('加载班组绩效失败:', error)
|
||||
this.$message.error('加载失败,请稍后重试')
|
||||
})
|
||||
},
|
||||
|
||||
// ✅【完全未改动】数值归一化工具
|
||||
normalizeValue(value, max) {
|
||||
return Math.min(100, (Number(value) / max) * 100).toFixed(2)
|
||||
},
|
||||
|
||||
// ===== Echarts 图表初始化/渲染/自适应 新增方法 =====
|
||||
initCharts() {
|
||||
this.outputChart = echarts.init(document.getElementById('outputChart'))
|
||||
this.radarChart = echarts.init(document.getElementById('radarChart'))
|
||||
},
|
||||
// 渲染产量对比柱状图
|
||||
renderOutputChart(xData, yData) {
|
||||
if (!this.outputChart) return
|
||||
const emptyOption = {
|
||||
grid: { left: 30, right: 30, top: 40, bottom: 30 },
|
||||
title: { text: '暂无产量数据', left: 'center', textStyle: { color: '#909399' } }
|
||||
}
|
||||
if (!xData.length) {
|
||||
this.outputChart.setOption(emptyOption)
|
||||
return
|
||||
}
|
||||
const option = {
|
||||
color: this.columnColor,
|
||||
grid: { left: 30, right: 30, top: 20, bottom: 30 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: { type: 'category', data: xData, axisLine: { show: false } },
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '产量(t)',
|
||||
splitLine: { type: 'dashed', color: '#e4e7ed' }
|
||||
},
|
||||
series: [{ name: '产量', type: 'bar', data: yData, barWidth: 30 }]
|
||||
}
|
||||
this.outputChart.setOption(option, true)
|
||||
},
|
||||
// 渲染质量指标雷达图
|
||||
renderRadarChart(seriesData) {
|
||||
if (!this.radarChart) return
|
||||
const emptyOption = {
|
||||
grid: { left: 30, right: 30, top: 40, bottom: 30 },
|
||||
title: { text: '暂无质量指标数据', left: 'center', textStyle: { color: '#909399' } }
|
||||
}
|
||||
if (!seriesData.length) {
|
||||
this.radarChart.setOption(emptyOption)
|
||||
return
|
||||
}
|
||||
const option = {
|
||||
color: this.radarColor,
|
||||
legend: { bottom: 0, left: 'center', textStyle: { fontSize: 12, color: '#666' } },
|
||||
tooltip: { trigger: 'item' },
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '产量指标', max: 100 },
|
||||
{ name: '成材率', max: 100 },
|
||||
{ name: '合格率', max: 100 },
|
||||
{ name: '厚度质量', max: 100 },
|
||||
{ name: '板形质量', max: 100 }
|
||||
],
|
||||
splitLine: { lineStyle: { type: 'dashed', color: '#e4e7ed' } },
|
||||
splitArea: { areaStyle: { opacity: 0.2 } }
|
||||
},
|
||||
series: [{ type: 'radar', data: seriesData }]
|
||||
}
|
||||
console.log('雷达图数据:', option)
|
||||
this.radarChart.setOption(option, true)
|
||||
},
|
||||
// 图表自适应窗口大小
|
||||
resizeCharts() {
|
||||
this.outputChart && this.outputChart.resize()
|
||||
this.radarChart && this.radarChart.resize()
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// ✅ PC端完美适配:rpx转px(1rpx=0.5px) 保留原布局+样式+间距+配色 无改动
|
||||
.page-container {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
min-height: calc(100vh - 20px);
|
||||
}
|
||||
|
||||
/* 月份选择器 */
|
||||
.month-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.month-arrow {
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
color: #0066cc;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #dcdfe6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.month-display {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 区块样式 */
|
||||
.ranking-section,
|
||||
.chart-section,
|
||||
.detail-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 8px;
|
||||
border-left: 2px solid #0066cc;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 排名列表 */
|
||||
.ranking-list {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.rank-1 .rank-badge {
|
||||
background: #0066cc;
|
||||
}
|
||||
|
||||
&.rank-2 .rank-badge {
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
&.rank-3 .rank-badge {
|
||||
background: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-badge {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #a0cfff;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.rank-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.team-name {
|
||||
font-size: 15px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.team-shift {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.rank-score {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 24px;
|
||||
color: #0066cc;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-wrapper {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 12px 8px;
|
||||
min-height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 详细数据表格 */
|
||||
.detail-table {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
background: #0066cc;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
color: #fff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
flex: 1;
|
||||
padding: 12px 6px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
padding: 50px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
56
klp-ui/src/views/lines/acid/index.vue
Normal file
56
klp-ui/src/views/lines/acid/index.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="实时监控" name="1">
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生产统计" name="2">
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="停机统计" name="3">
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="班组绩效" name="4">
|
||||
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div>
|
||||
<div v-if="activeTab === '1'">
|
||||
<realTimeMonitor></realTimeMonitor>
|
||||
</div>
|
||||
<div v-if="activeTab === '2'">
|
||||
<productStatistic></productStatistic>
|
||||
</div>
|
||||
<div v-if="activeTab === '3'">
|
||||
<shutdownStatistic></shutdownStatistic>
|
||||
</div>
|
||||
<div v-if="activeTab === '4'">
|
||||
<TeamPerformance></TeamPerformance>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TeamPerformance from './components/team-performance.vue'
|
||||
import ShutdownStatistic from './components/shutdown-statistic.vue';
|
||||
import ProductStatistic from './components/product-statistic.vue';
|
||||
import RealTimeMonitor from './components/real-time-monitoring.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: '1'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
TeamPerformance,
|
||||
ShutdownStatistic,
|
||||
ProductStatistic,
|
||||
RealTimeMonitor
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
155
klp-ui/src/views/lines/zinc/components/product-statistic.vue
Normal file
155
klp-ui/src/views/lines/zinc/components/product-statistic.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="report-page" style="padding: 20px;">
|
||||
<!-- 汇总统计信息 el-descriptions 展示 -->
|
||||
<el-card shadow="hover" class="summary-card" style="margin-bottom: 20px;">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-size: 16px; font-weight: bold;">生产报表-数据汇总</span>
|
||||
</div>
|
||||
<el-descriptions
|
||||
:data="productStatistic"
|
||||
title=""
|
||||
border
|
||||
column="4"
|
||||
size="middle"
|
||||
label-width="140px"
|
||||
>
|
||||
<el-descriptions-item label="总出口宽度(mm)">{{ productStatistic.totalExitWidth || 0 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总出口长度(m)">{{ formatNum(productStatistic.totalExitLength) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总理论重量(kg)">{{ formatNum(productStatistic.totalTheoryWeight) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总实际重量(kg)">{{ formatNum(productStatistic.totalActualWeight) }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="总出口厚度(mm)">{{ formatNum(productStatistic.totalExitThickness) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平均出口宽度(mm)">{{ formatNum(productStatistic.avgExitWidth) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平均出口长度(m)">{{ formatNum(productStatistic.avgExitLength) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平均理论重量(kg)">{{ formatNum(productStatistic.avgTheoryWeight) }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="平均实际重量(kg)">{{ formatNum(productStatistic.avgActualWeight) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平均出口厚度(mm)">{{ formatNum(productStatistic.avgExitThickness) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总来料重量(kg)">{{ formatNum(productStatistic.totalEntryWeight) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总卷数">{{ productStatistic.coilCount || 0 }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="成材率" span="4" label-width="140px">
|
||||
<span style="color: #1890ff;">{{ formatRate(productStatistic.yieldRate) }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 明细数据 el-table 展示 -->
|
||||
<el-card shadow="hover" class="detail-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-size: 16px; font-weight: bold;">生产报表-明细数据</span>
|
||||
</div>
|
||||
<el-table
|
||||
:data="reportDetails"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
v-loading="tableLoading"
|
||||
element-loading-text="加载中..."
|
||||
style="width: 100%;"
|
||||
highlight-current-row
|
||||
>
|
||||
<el-table-column prop="exitMatId" label="出口物料编码" align="center" />
|
||||
<el-table-column prop="entryMatId" label="入口物料编码" align="center" />
|
||||
<el-table-column prop="groupNo" label="班组号" align="center" width="60" />
|
||||
<el-table-column prop="shiftNo" label="班次号" align="center" width="60" />
|
||||
<el-table-column prop="steelGrade" label="钢种" align="center" width="80" />
|
||||
<el-table-column prop="exitWidth" label="出口宽度(mm)" align="center">
|
||||
<template #default="scope">{{ formatNum(scope.row.exitWidth) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="exitLength" label="出口长度(m)" align="center">
|
||||
<template #default="scope">{{ formatNum(scope.row.exitLength) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="exitThickness" label="出口厚度(mm)" align="center">
|
||||
<template #default="scope">{{ formatNum(scope.row.exitThickness) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="theoryWeight" label="理论重量(kg)" align="center">
|
||||
<template #default="scope">{{ formatNum(scope.row.theoryWeight) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="actualWeight" label="实际重量(kg)" align="center">
|
||||
<template #default="scope">{{ formatNum(scope.row.actualWeight) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="onlineTime" label="上线时间" align="center" width="190">
|
||||
<template #default="scope">{{ formatTime(scope.row.onlineTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="结束时间" align="center" width="190">
|
||||
<template #default="scope">{{ formatTime(scope.row.endTime) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getReportSummary, getReportDetails } from '@/api/lines/zinc/report'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
productStatistic: {}, // 汇总数据
|
||||
reportDetails: [], // 明细数据
|
||||
tableLoading: false, // 表格加载状态
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 数字格式化:保留2位小数,空值显示0
|
||||
formatNum(num) {
|
||||
return num ? Number(num).toFixed(2) : '0.00'
|
||||
},
|
||||
// 成材率格式化:保留2位小数+百分比
|
||||
formatRate(rate) {
|
||||
return rate ? (Number(rate) * 100).toFixed(2) + '%' : '0.00%'
|
||||
},
|
||||
// 时间格式化:处理ISO时间/空值/null,统一格式
|
||||
formatTime(time) {
|
||||
if (!time) return '-'
|
||||
const date = new Date(time)
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
const hh = date.getHours().toString().padStart(2, '0')
|
||||
const mm = date.getMinutes().toString().padStart(2, '0')
|
||||
const ss = date.getSeconds().toString().padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hh}:${mm}:${ss}`
|
||||
},
|
||||
// 获取汇总数据
|
||||
async getReportSummary() {
|
||||
try {
|
||||
const res = await getReportSummary()
|
||||
this.productStatistic = res || {}
|
||||
} catch (err) {
|
||||
this.$message.error('获取汇总数据失败!')
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
// 获取明细数据
|
||||
async getReportDetails() {
|
||||
this.tableLoading = true
|
||||
try {
|
||||
const res = await getReportDetails()
|
||||
this.reportDetails = res || []
|
||||
} catch (err) {
|
||||
this.$message.error('获取明细数据失败!')
|
||||
console.error(err)
|
||||
} finally {
|
||||
this.tableLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载时调用两个接口
|
||||
this.getReportSummary()
|
||||
this.getReportDetails()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.report-page {
|
||||
background: #f5f7fa;
|
||||
min-height: calc(100vh - 120px);
|
||||
}
|
||||
.summary-card, .detail-card {
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
实时监控
|
||||
</div>
|
||||
</template>
|
||||
165
klp-ui/src/views/lines/zinc/components/shutdown-statistic.vue
Normal file
165
klp-ui/src/views/lines/zinc/components/shutdown-statistic.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="stoppage-page" style="padding: 20px;">
|
||||
<!-- 月份筛选查询区域 -->
|
||||
<div class="search-box" style="margin: 16px 0; display: flex; align-items: center;">
|
||||
<el-date-picker
|
||||
v-model="month"
|
||||
type="month"
|
||||
placeholder="请选择月份"
|
||||
value-format="yyyy-MM"
|
||||
style="width: 200px;"
|
||||
@change="handleMonthChange"
|
||||
/>
|
||||
<el-button type="primary" icon="el-icon-search" style="margin-left: 10px;" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 停机统计表格 ✅【全部修改】适配真实JSON数据字段 -->
|
||||
<el-table
|
||||
:data="stoppageList"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
v-loading="loading"
|
||||
element-loading-text="数据加载中..."
|
||||
style="width: 100%;"
|
||||
highlight-current-row
|
||||
>
|
||||
<el-table-column prop="stopType" label="停机类型" align="center" width="140" />
|
||||
<el-table-column prop="startDate" label="停机开始时间" align="center" width="200" />
|
||||
<el-table-column prop="endDate" label="停机结束时间" align="center" width="200" />
|
||||
<el-table-column prop="duration" label="停机时长" align="center" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatDuration(scope.row.duration) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="insdate" label="数据录入时间" align="center" width="200" />
|
||||
<!-- 生产相关附属字段 -->
|
||||
<el-table-column prop="coilid" label="钢卷号" align="center" width="120" />
|
||||
<el-table-column prop="shift" label="班次" align="center" width="100" />
|
||||
<!-- <el-table-column prop="crew" label="班组人员" align="center" width="120" />
|
||||
<el-table-column prop="area" label="区域" align="center" width="100" />
|
||||
<el-table-column prop="unit" label="机组" align="center" width="100" />
|
||||
<el-table-column prop="seton" label="开机人" align="center" width="100" /> -->
|
||||
<!-- 备注字段 -->
|
||||
<el-table-column prop="remark" label="备注" align="center" min-width="220" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listStoppage } from '@/api/lines/zinc/stoppage'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
stoppageList: [], // 停机统计列表
|
||||
total: 0, // 数据总条数
|
||||
loading: false, // 加载状态
|
||||
month: '', // 选中的月份 yyyy-MM格式
|
||||
queryParams: {
|
||||
startDate: '', // 开始时间 yyyy-MM-dd✅ 与后端字段名一致
|
||||
endDate: '' // 结束时间 yyyy-MM-dd✅ 与后端字段名一致
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化:默认选中当前月份,并生成当月的起止时间
|
||||
this.initDefaultMonth()
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载时,默认查询当月停机数据
|
||||
this.listStoppage()
|
||||
},
|
||||
methods: {
|
||||
// 格式化持续时间:将分钟数转换为"X天X小时X分钟"格式
|
||||
formatDuration(minutes) {
|
||||
if (minutes === null || minutes === undefined || minutes === '') return '—'
|
||||
const totalMinutes = Math.floor(Number(minutes))
|
||||
if (isNaN(totalMinutes) || totalMinutes < 0) return '—'
|
||||
|
||||
const days = Math.floor(totalMinutes / (24 * 60))
|
||||
const hours = Math.floor((totalMinutes % (24 * 60)) / 60)
|
||||
const mins = Math.floor(totalMinutes % 60)
|
||||
|
||||
let result = ''
|
||||
if (days > 0) {
|
||||
result += `${days}天`
|
||||
}
|
||||
if (hours > 0 || days > 0) {
|
||||
result += `${hours}小时`
|
||||
}
|
||||
if (mins > 0 || result === '') {
|
||||
result += `${mins}分钟`
|
||||
}
|
||||
|
||||
return result || '0分钟'
|
||||
},
|
||||
/** 初始化默认月份和起止时间 */
|
||||
initDefaultMonth() {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0')
|
||||
this.month = `${year}-${month}`
|
||||
// 根据当前月份生成时间范围
|
||||
this.setMonthTimeRange(this.month)
|
||||
},
|
||||
|
||||
/** 选中月份 → 生成 当月1号00:00:00 至 下个月1号00:00:00 的时间格式 */
|
||||
setMonthTimeRange(month) {
|
||||
if (!month) return
|
||||
// 开始时间:选中月份的 1号 00:00:00
|
||||
this.queryParams.startDate = `${month}-01`
|
||||
// 解析选中的月份为日期对象
|
||||
const selectMonth = new Date(month + '-01')
|
||||
// 下个月1号:月份+1,日期为1号
|
||||
const nextMonth = new Date(selectMonth.setMonth(selectMonth.getMonth() + 1))
|
||||
const nextYear = nextMonth.getFullYear()
|
||||
const nextMonthNum = (nextMonth.getMonth() + 1).toString().padStart(2, '0')
|
||||
// 结束时间:下个月 1号 00:00:00
|
||||
this.queryParams.endDate = `${nextYear}-${nextMonthNum}-01`
|
||||
},
|
||||
|
||||
/** 月份选择器切换事件 */
|
||||
handleMonthChange(val) {
|
||||
this.setMonthTimeRange(val)
|
||||
},
|
||||
|
||||
/** 查询按钮点击事件 */
|
||||
handleQuery() {
|
||||
this.listStoppage()
|
||||
},
|
||||
|
||||
/** 核心:查询停机统计列表数据,带时间筛选参数 */
|
||||
async listStoppage() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = { ...this.queryParams }
|
||||
const res = await listStoppage(params)
|
||||
// 适配后端返回格式:数组/分页对象都兼容
|
||||
this.stoppageList = res.data || res || []
|
||||
this.total = res.total || this.stoppageList.length
|
||||
} catch (err) {
|
||||
this.$message.error('查询停机统计数据失败!')
|
||||
console.error('停机统计查询异常:', err)
|
||||
this.stoppageList = []
|
||||
this.total = 0
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stoppage-page {
|
||||
background: #f5f7fa;
|
||||
min-height: calc(100vh - 40px);
|
||||
}
|
||||
.search-box {
|
||||
background: #fff;
|
||||
padding: 10px 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
</style>
|
||||
56
klp-ui/src/views/lines/zinc/index.vue
Normal file
56
klp-ui/src/views/lines/zinc/index.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="实时监控" name="1">
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生产统计" name="2">
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="停机统计" name="3">
|
||||
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane label="班组绩效" name="4">
|
||||
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
|
||||
<div>
|
||||
<div v-if="activeTab === '1'">
|
||||
<realTimeMonitor></realTimeMonitor>
|
||||
</div>
|
||||
<div v-if="activeTab === '2'">
|
||||
<productStatistic></productStatistic>
|
||||
</div>
|
||||
<div v-if="activeTab === '3'">
|
||||
<shutdownStatistic></shutdownStatistic>
|
||||
</div>
|
||||
<!-- <div v-if="activeTab === '4'">
|
||||
<TeamPerformance></TeamPerformance>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import TeamPerformance from './components/team-performance.vue'
|
||||
import ShutdownStatistic from './components/shutdown-statistic.vue';
|
||||
import ProductStatistic from './components/product-statistic.vue';
|
||||
import RealTimeMonitor from './components/real-time-monitoring.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: '1'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
// TeamPerformance,
|
||||
ShutdownStatistic,
|
||||
ProductStatistic,
|
||||
RealTimeMonitor
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -5,7 +5,11 @@
|
||||
<el-col :span="24">
|
||||
<div class="section-card material-section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">查找钢卷并修正</h3>
|
||||
<h3 class="section-title">
|
||||
查找钢卷并修正
|
||||
<span class="section-desc">绿色边框表示当前钢卷,灰色边框表示历史钢卷, 蓝色边框表示已发货钢卷</span>
|
||||
</h3>
|
||||
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="getMaterialCoil">刷新</el-button>
|
||||
</div>
|
||||
|
||||
@@ -31,13 +35,13 @@
|
||||
<i class="el-icon-box"></i>
|
||||
<p>暂无待领物料</p>
|
||||
</div>
|
||||
|
||||
<div v-for="(item, index) in materialCoilList" :key="item.coilId || index" class="material-card"
|
||||
:style="{ border: item.abnormalCount > 0 ? '1px solid red' : ' 1px solid #e4e7ed' }">
|
||||
:style="getBorderStyle(item)">
|
||||
<div class="card-header">
|
||||
<div class="header-left">
|
||||
<el-tag type="info" size="small" class="coil-no-tag">{{ item.currentCoilNo }}</el-tag>
|
||||
<!-- <span class="material-type">{{ item.materialType || '原料' }}</span> -->
|
||||
<el-tag type="info" size="small" class="coil-no-tag" :style="getBorderStyle(item)">{{ getTypeLabel(item) }}</el-tag>
|
||||
<span class="material-type">{{ item.materialType || '原料' }}</span>
|
||||
<el-popover v-if="item.rawMaterial || item.product" placement="top" width="280" trigger="hover"
|
||||
popper-class="material-params-popover">
|
||||
<div class="material-params-content">
|
||||
@@ -178,10 +182,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<div class="card-footer" v-if="item.dataType != 10">
|
||||
<el-button type="primary" icon="el-icon-check" size="mini" @click="handleCorrectMaterial(item)"
|
||||
:loading="item.picking" class="action-btn">修正</el-button>
|
||||
<el-button type="danger" icon="el-icon-alarm-clock" :plain="item.abnormalCount == 0" size="mini"
|
||||
<el-button type="danger" icon="el-icon-delete" size="mini" @click="hanleDeleteMaterial(item)"
|
||||
:loading="item.picking" class="action-btn">删除</el-button>
|
||||
<el-button type="warning" icon="el-icon-alarm-clock" :plain="item.abnormalCount == 0" size="mini"
|
||||
@click="handleAddAbnormal(item)" :loading="item.cancelling" class="action-btn">
|
||||
异常
|
||||
<span v-if="item.abnormalCount > 0">({{ item.abnormalCount }})</span>
|
||||
@@ -237,7 +243,7 @@
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="钢卷信息修正" :visible.sync="correctVisible" width="600px">
|
||||
<el-form ref="form" :model="form" label-width="100px">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="入场钢卷号" prop="enterCoilNo">
|
||||
<el-input v-model="form.enterCoilNo" placeholder="请输入入场钢卷号" :disabled="form.coilId" />
|
||||
</el-form-item>
|
||||
@@ -260,7 +266,7 @@
|
||||
<el-option key="乙" label="乙" value="乙" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="材料类型" prop="materialType" v-if="!form.coilId">
|
||||
<el-form-item label="材料类型" prop="materialType">
|
||||
<el-select v-model="form.materialType" placeholder="请选择材料类型" @change="handleMaterialTypeChange">
|
||||
<el-option label="成品" value="成品" />
|
||||
<el-option label="原料" value="原料" />
|
||||
@@ -275,16 +281,8 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态" prop="qualityStatus">
|
||||
<el-select v-model="form.qualityStatus" placeholder="请选择质量状态" style="width: 100%">
|
||||
<!-- <el-option label="A+" value="A+" />
|
||||
<el-option label="A" value="A" />
|
||||
<el-option label="A-" value="A-" />
|
||||
<el-option label="B+" value="B+" />
|
||||
<el-option label="B" value="B" />
|
||||
<el-option label="B-" value="B-" />
|
||||
<el-option label="C+" value="C+" />
|
||||
<el-option label="C" value="C" />
|
||||
<el-option label="C-" value="C-" /> -->
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label" :value="item.value" />
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -339,9 +337,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listMaterialCoil, updateMaterialCoilSimple } from '@/api/wms/coil'
|
||||
import { listMaterialCoil, updateMaterialCoilSimple, checkCoilNo, delMaterialCoil } from '@/api/wms/coil'
|
||||
import { listUser } from '@/api/system/user'
|
||||
import { listPendingAction, startProcess, cancelAction, delPendingAction, addPendingAction } from '@/api/wms/pendingAction'
|
||||
import { listPendingAction, startProcess, cancelAction, delPendingAction } from '@/api/wms/pendingAction'
|
||||
import { parseTime } from '@/utils/klp'
|
||||
import ProductInfo from '@/components/KLPService/Renderer/ProductInfo'
|
||||
import RawMaterialInfo from '@/components/KLPService/Renderer/RawMaterialInfo'
|
||||
@@ -354,7 +352,7 @@ import RawMaterialSelect from "@/components/KLPService/RawMaterialSelect";
|
||||
|
||||
export default {
|
||||
name: 'DoPage',
|
||||
dicts: ['action_type', 'coil_abnormal_code', 'coil_abnormal_position', 'coil_abnormal_degree'],
|
||||
dicts: ['action_type', 'coil_abnormal_code', 'coil_abnormal_position', 'coil_abnormal_degree', 'coil_quality_status'],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
@@ -374,7 +372,6 @@ export default {
|
||||
ProductSelect,
|
||||
RawMaterialSelect,
|
||||
},
|
||||
dicts: ['coil_quality_status'],
|
||||
data() {
|
||||
return {
|
||||
// 物料列表相关
|
||||
@@ -385,8 +382,8 @@ export default {
|
||||
materialQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
dataType: 1,
|
||||
status: 0,
|
||||
// dataType: 1,
|
||||
// status: 0,
|
||||
enterCoilNo: null,
|
||||
currentCoilNo: null
|
||||
},
|
||||
@@ -417,6 +414,77 @@ export default {
|
||||
degree: null,
|
||||
remark: null
|
||||
},
|
||||
// 表单校验
|
||||
rules: {
|
||||
planId: [
|
||||
{ required: true, message: "请选择收货计划", trigger: "change" }
|
||||
],
|
||||
enterCoilNo: [
|
||||
{ required: true, message: "入场钢卷号不能为空", trigger: "blur" },
|
||||
// 自定义校验,必须是8位的阿拉伯数字
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!/^\d{8}$/.test(value)) {
|
||||
callback(new Error('入场钢卷号必须是8位的阿拉伯数字'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (this.form.coilId) {
|
||||
// 新增时触发校验
|
||||
console.log('新增时触发校验');
|
||||
callback();
|
||||
} else {
|
||||
checkCoilNo({ enterCoilNo: value }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'enter' || duplicateType === 'both') {
|
||||
// alert('入场钢卷号重复,请重新输入');
|
||||
callback(new Error('入场钢卷号重复,请重新输入'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
],
|
||||
currentCoilNo: [
|
||||
{ required: true, message: "当前钢卷号不能为空", trigger: "blur" },
|
||||
// 远程校验,当前钢卷号不能重复
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
checkCoilNo({ currentCoilNo: value, coilId: this.form.coilId }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'current' || duplicateType === 'both') {
|
||||
// alert('当前钢卷号重复,请重新输入');
|
||||
callback(new Error('当前钢卷号重复,请重新输入'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
],
|
||||
materialType: [
|
||||
{ required: true, message: "材料类型不能为空", trigger: "change" }
|
||||
],
|
||||
itemId: [
|
||||
{ required: true, message: "物品ID不能为空", trigger: "blur" }
|
||||
],
|
||||
itemType: [
|
||||
{ required: true, message: "物品类型不能为空", trigger: "change" }
|
||||
],
|
||||
// 净重和毛重
|
||||
netWeight: [
|
||||
{ required: true, message: "净重不能为空", trigger: "blur" }
|
||||
],
|
||||
grossWeight: [
|
||||
{ required: true, message: "毛重不能为空", trigger: "blur" }
|
||||
],
|
||||
},
|
||||
form: {},
|
||||
correctVisible: false,
|
||||
buttonLoading: false,
|
||||
@@ -433,7 +501,7 @@ export default {
|
||||
})
|
||||
return acidAction ? parseInt(acidAction.value) : null
|
||||
},
|
||||
// 动态显示标签
|
||||
// 动态显示标签
|
||||
getItemLabel() {
|
||||
if (this.form.materialType === '成品') {
|
||||
return '产品类型';
|
||||
@@ -500,6 +568,41 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
parseTime,
|
||||
getBorderStyle(row) {
|
||||
console.log(row);
|
||||
// 已发货
|
||||
if (row.status == 1) {
|
||||
return {border: '1.5px solid #007bff'}
|
||||
}
|
||||
// 历史钢卷
|
||||
if (row.dataType == 0) {
|
||||
return {border: '1.5px solid #6c757d'}
|
||||
}
|
||||
// 当前钢卷
|
||||
if (row.dataType == 1) {
|
||||
return {border: '1.5px solid #28a745'}
|
||||
}
|
||||
// 还未入库的钢卷
|
||||
if (row.dataType == 10) {
|
||||
return {border: '1.5px solid #ffc107'}
|
||||
}
|
||||
return {border: '1.5px solid #e4e7ed'}
|
||||
},
|
||||
getTypeLabel(row) {
|
||||
if (row.status == 1) {
|
||||
return '已发货'
|
||||
}
|
||||
if (row.dataType == 0) {
|
||||
return '历史钢卷'
|
||||
}
|
||||
if (row.dataType == 1) {
|
||||
return '当前钢卷'
|
||||
}
|
||||
if (row.dataType == 10) {
|
||||
return '还未入库的钢卷'
|
||||
}
|
||||
return '未知'
|
||||
},
|
||||
/** 查询用户列表 */
|
||||
getUsers() {
|
||||
listUser({ pageNum: 1, pageSize: 1000 }).then(response => {
|
||||
@@ -555,36 +658,6 @@ export default {
|
||||
this.materialQueryParams.currentCoilNo = null
|
||||
this.handleMaterialQuery()
|
||||
},
|
||||
/** 领料操作 */
|
||||
handlePickMaterial(row) {
|
||||
if (!this.acidRollingActionType) {
|
||||
this.$message.error(`未找到${this.label}操作类型,请检查字典配置`)
|
||||
return
|
||||
}
|
||||
|
||||
this.$set(row, 'picking', true)
|
||||
|
||||
const pendingData = {
|
||||
coilId: row.coilId,
|
||||
currentCoilNo: row.currentCoilNo,
|
||||
actionType: this.acidRollingActionType,
|
||||
actionStatus: 0, // 待处理
|
||||
sourceType: 'manual', // 手动创建
|
||||
warehouseId: row.warehouseId,
|
||||
priority: 0, // 默认普通优先级
|
||||
remark: `PC端领料创建-${this.label}`
|
||||
}
|
||||
|
||||
this.$modal.confirm(`是否确认从${row.warehouseName || '仓库'}领料${row.currentCoilNo || '物料'}?`).then(() => {
|
||||
// 用户点击确认后执行的操作
|
||||
return addPendingAction(pendingData)
|
||||
}).then(response => {
|
||||
this.$message.success('领料成功,已创建待操作任务')
|
||||
this.getPendingAction() // 刷新待操作列表
|
||||
}).finally(() => {
|
||||
this.$set(row, 'picking', false)
|
||||
})
|
||||
},
|
||||
|
||||
// ========== 待操作列表相关方法 ==========
|
||||
/** 查询待操作列表 */
|
||||
@@ -612,15 +685,6 @@ export default {
|
||||
this.actionLoading = false
|
||||
})
|
||||
},
|
||||
/** 待操作搜索 */
|
||||
handleActionQuery() {
|
||||
this.actionQueryParams.pageNum = 1
|
||||
// 确保始终使用酸连轧工序的 actionType
|
||||
if (this.acidRollingActionType) {
|
||||
this.actionQueryParams.actionType = this.acidRollingActionType
|
||||
}
|
||||
this.getPendingAction()
|
||||
},
|
||||
handleCorrectMaterial(row) {
|
||||
this.form = {
|
||||
...row,
|
||||
@@ -632,26 +696,35 @@ export default {
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.buttonLoading = true;
|
||||
updateMaterialCoilSimple(this.form).then(_ => {
|
||||
this.$modal.msgSuccess("修正成功");
|
||||
this.correctVisible = false;
|
||||
this.getMaterialCoil();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (!valid) {
|
||||
this.$message.error('请填写完整信息')
|
||||
return
|
||||
}
|
||||
this.buttonLoading = true;
|
||||
updateMaterialCoilSimple(this.form).then(_ => {
|
||||
this.$modal.msgSuccess("修正成功");
|
||||
this.correctVisible = false;
|
||||
this.getMaterialCoil();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
/** 重置待操作搜索 */
|
||||
resetActionQuery() {
|
||||
this.resetForm('actionQueryForm')
|
||||
this.actionQueryParams.currentCoilNo = null
|
||||
this.actionQueryParams.actionStatus = null
|
||||
// 确保始终使用酸连轧工序的 actionType
|
||||
if (this.acidRollingActionType) {
|
||||
this.actionQueryParams.actionType = this.acidRollingActionType
|
||||
}
|
||||
this.handleActionQuery()
|
||||
/** 删除按钮操作 */
|
||||
hanleDeleteMaterial(row) {
|
||||
const coilIds = row.coilId;
|
||||
this.$modal.confirm('是否确认删除钢卷物料编号为"' + coilIds + '"的数据项,会同时清理刚钢卷相关的待操作记录且无法恢复!!!是否继续删除?').then(() => {
|
||||
this.loading = true;
|
||||
return delMaterialCoil(coilIds);
|
||||
}).then(() => {
|
||||
this.loading = false;
|
||||
this.getMaterialCoil();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 处理操作 - 跳转到对应页面 */
|
||||
handleProcess(row) {
|
||||
@@ -811,6 +884,11 @@ export default {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.material-section {
|
||||
.section-header {
|
||||
border-bottom-color: #409eff;
|
||||
@@ -842,7 +920,7 @@ export default {
|
||||
// 卡片网格容器
|
||||
.card-grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 8px;
|
||||
max-height: calc(100vh - 320px);
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -220,7 +220,9 @@ export default {
|
||||
// 钢卷选择器筛选参数
|
||||
coilFilters: {
|
||||
dataType: 1,
|
||||
materialType: '成品'
|
||||
materialType: '成品',
|
||||
selectType: 'product',
|
||||
status: 0,
|
||||
},
|
||||
coilSelectorVisible: false,
|
||||
loading: false,
|
||||
|
||||
@@ -61,8 +61,7 @@
|
||||
<el-row v-if="form.materialType === '成品'">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="质量状态" prop="qualityStatus">
|
||||
<el-select v-model="form.qualityStatus" placeholder="请选择质量状态" style="width: 100%"
|
||||
:disabled="readonly">
|
||||
<el-select v-model="form.qualityStatus" placeholder="请选择质量状态" style="width: 100%">
|
||||
<!-- <el-option label="A+" value="A+" />
|
||||
<el-option label="A" value="A" />
|
||||
<el-option label="A-" value="A-" />
|
||||
@@ -79,8 +78,7 @@
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="切边要求" prop="trimmingRequirement">
|
||||
<el-select v-model="form.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%"
|
||||
:disabled="readonly">
|
||||
<el-select v-model="form.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%">
|
||||
<el-option label="净边料" value="净边料" />
|
||||
<el-option label="毛边料" value="毛边料" />
|
||||
</el-select>
|
||||
@@ -94,8 +92,7 @@
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="包装要求" prop="packagingRequirement">
|
||||
<el-select v-model="form.packagingRequirement" placeholder="请选择包装要求" style="width: 100%"
|
||||
:disabled="readonly">
|
||||
<el-select v-model="form.packagingRequirement" placeholder="请选择包装要求" style="width: 100%">
|
||||
<el-option label="裸包" value="裸包" />
|
||||
<el-option label="普包" value="普包" />
|
||||
<el-option label="简包" value="简包" />
|
||||
@@ -204,7 +201,7 @@
|
||||
@click="openReceiptModal(scope.row)" v-loading="buttonLoading">签收</el-button>
|
||||
<el-button v-if="scope.row.actionStatus == 0 || scope.row.actionStatus == 1" type="danger"
|
||||
@click="handleReject(scope.row)" v-loading="buttonLoading">拒签</el-button>
|
||||
<el-button v-if="scope.row.actionStatus == 3" type="warning" v-loading="buttonLoading"
|
||||
<el-button type="warning" v-loading="buttonLoading"
|
||||
@click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -285,8 +282,7 @@
|
||||
<div v-else>请先选择材料类型</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态" prop="qualityStatus">
|
||||
<el-select v-model="editCoil.form.qualityStatus" placeholder="请选择质量状态" style="width: 100%"
|
||||
:disabled="readonly">
|
||||
<el-select v-model="editCoil.form.qualityStatus" placeholder="请选择质量状态" style="width: 100%">
|
||||
<!-- <el-option label="A+" value="A+" />
|
||||
<el-option label="A" value="A" />
|
||||
<el-option label="A-" value="A-" />
|
||||
@@ -302,21 +298,19 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="切边要求" prop="trimmingRequirement">
|
||||
<el-select v-model="editCoil.form.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%"
|
||||
:disabled="readonly">
|
||||
<el-select v-model="editCoil.form.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%">
|
||||
<el-option label="净边料" value="净边料" />
|
||||
<el-option label="毛边料" value="毛边料" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="打包状态" prop="packingStatus">
|
||||
<el-input v-model="editCoil.form.packingStatus" placeholder="请输入打包状态" :disabled="readonly">
|
||||
<el-input v-model="editCoil.form.packingStatus" placeholder="请输入打包状态">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="包装要求" prop="packagingRequirement">
|
||||
<el-select v-model="editCoil.form.packagingRequirement" placeholder="请选择包装要求" style="width: 100%"
|
||||
:disabled="readonly">
|
||||
<el-select v-model="editCoil.form.packagingRequirement" placeholder="请选择包装要求" style="width: 100%">
|
||||
<el-option label="裸包" value="裸包" />
|
||||
<el-option label="普包" value="普包" />
|
||||
<el-option label="简包" value="简包" />
|
||||
@@ -344,7 +338,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMaterialCoil, listMaterialCoil, updateMaterialCoilSimple, getMaxCoilNo, checkCoilNo } from '@/api/wms/coil'
|
||||
import { getMaterialCoil, listMaterialCoil, updateMaterialCoilSimple, getMaxCoilNo, checkCoilNo, delMaterialCoil } from '@/api/wms/coil'
|
||||
import { listPendingAction, delPendingAction, updatePendingAction } from '@/api/wms/pendingAction'
|
||||
import MaterialSelect from "@/components/KLPService/MaterialSelect";
|
||||
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
|
||||
@@ -421,15 +415,21 @@ export default {
|
||||
// 远程校验,检查钢卷号是否存在
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
checkCoilNo({ enterCoilNo: value }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'enter' || duplicateType === 'both') {
|
||||
// alert('入场钢卷号重复,请重新输入');
|
||||
callback(new Error('入场钢卷号重复,请重新输入'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
if (this.form.coilId) {
|
||||
// 新增时触发校验
|
||||
console.log('新增时触发校验');
|
||||
callback();
|
||||
} else {
|
||||
checkCoilNo({ enterCoilNo: value }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'enter' || duplicateType === 'both') {
|
||||
// alert('入场钢卷号重复,请重新输入');
|
||||
callback(new Error('入场钢卷号重复,请重新输入'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
}, trigger: 'blur'
|
||||
},
|
||||
],
|
||||
@@ -438,7 +438,7 @@ export default {
|
||||
// 远程校验,当前钢卷号不能重复
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
checkCoilNo({ currentCoilNo: value }).then(res => {
|
||||
checkCoilNo({ currentCoilNo: value, coilId: this.form.coilId }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'current' || duplicateType === 'both') {
|
||||
// alert('当前钢卷号重复,请重新输入');
|
||||
@@ -607,9 +607,11 @@ export default {
|
||||
handleCoil(COIL_ACTIONS.RECEIVE, this.form, this.form.planId)
|
||||
.then(res => {
|
||||
this.$modal.msgSuccess("入库成功");
|
||||
|
||||
this.form = {
|
||||
itemId: null,
|
||||
itemType: 'raw_material',
|
||||
materialType: '原料',
|
||||
warehouseId: '1988150044862377986', // 酸连轧原料库
|
||||
netWeight: null,
|
||||
grossWeight: null,
|
||||
@@ -618,8 +620,9 @@ export default {
|
||||
trimmingRequirement: null,
|
||||
packingStatus: null,
|
||||
packagingRequirement: null,
|
||||
planId: this.todayPlanId,
|
||||
planId: this.form.planId,
|
||||
}
|
||||
this.getMaxCoilNoByPrefix()
|
||||
this.getList()
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
@@ -698,12 +701,18 @@ export default {
|
||||
|
||||
},
|
||||
handleDelete(row) {
|
||||
console.log(row.coilId)
|
||||
this.$modal.confirm("确认删除吗?", "删除确认", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.buttonLoading = true;
|
||||
if (row.coilId) {
|
||||
delMaterialCoil(row.coilId).catch(err => {
|
||||
this.$message.error(err.message || '删除钢卷失败')
|
||||
})
|
||||
}
|
||||
delPendingAction(row.actionId).then(response => {
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
this.getList();
|
||||
@@ -725,7 +734,8 @@ export default {
|
||||
getMaxCoilNo(prefix).then(res => {
|
||||
console.log(res)
|
||||
// 扣掉最后一位
|
||||
this.form.enterCoilNo = res.data.maxEnterCoilNo.slice(0, -1);
|
||||
this.$set(this.form, 'enterCoilNo', res.data.maxEnterCoilNo.slice(0, -1));
|
||||
this.$set(this.form, 'currentCoilNo', res.data.maxEnterCoilNo.slice(0, -1));
|
||||
})
|
||||
},
|
||||
// 检查钢卷号是否合法(后端检查)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BasePage :qrcode="qrcode" :querys="querys" :labelType="labelType" :hideType="hideType" />
|
||||
<BasePage :qrcode="qrcode" :querys="querys" :labelType="labelType" :hideType="hideType" :showControl="showControl" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -9,12 +9,13 @@ export default {
|
||||
components: {
|
||||
BasePage
|
||||
},
|
||||
data() {
|
||||
data() {
|
||||
return {
|
||||
qrcode: true,
|
||||
querys: {
|
||||
dataType: 0,
|
||||
},
|
||||
showControl: true,
|
||||
labelType: '2',
|
||||
hideType: false
|
||||
}
|
||||
|
||||
26
klp-ui/src/views/wms/coil/onlySplit.vue
Normal file
26
klp-ui/src/views/wms/coil/onlySplit.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<BasePage :qrcode="qrcode" :querys="querys" :labelType="labelType" :hideWarehouseQuery="hideWarehouseQuery" :hideType="hideType" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BasePage from './panels/base.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BasePage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
qrcode: false,
|
||||
querys: {
|
||||
dataType: 1,
|
||||
warehouseIds: '1988150150521090049,1988150487185289217,1988150750390448129,1988150971895836674',
|
||||
// materialType: '废品'
|
||||
},
|
||||
hideWarehouseQuery: true,
|
||||
labelType: '2',
|
||||
hideType: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -22,8 +22,8 @@
|
||||
|
||||
<el-form-item label="实际库区" prop="actualWarehouseId" v-if="!hideWarehouseQuery">
|
||||
<actual-warehouse-select v-model="queryParams.actualWarehouseId" placeholder="请选择仓库/库区/库位"
|
||||
style="width: 100%; display: inline-block;" clearable
|
||||
:canSelectDisabled="true" :canSelectLevel2="true" :clearInput="false" :showEmpty="true"/>
|
||||
style="width: 100%; display: inline-block;" clearable :canSelectDisabled="true" :canSelectLevel2="true"
|
||||
:clearInput="false" :showEmpty="true" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="产品名称" prop="itemName">
|
||||
@@ -91,7 +91,8 @@
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="厂家卷号" align="center" prop="supplierCoilNo" /> -->
|
||||
<el-table-column label="逻辑库位" align="center" prop="warehouseName" v-if="!hideWarehouseQuery" />
|
||||
<el-table-column label="实际库区" align="center" prop="actualWarehouseName" v-if="!hideWarehouseQuery && !showExportTime" />
|
||||
<el-table-column label="实际库区" align="center" prop="actualWarehouseName"
|
||||
v-if="!hideWarehouseQuery && !showExportTime" />
|
||||
<!-- <el-table-column label="物料类型" align="center" prop="materialType" /> -->
|
||||
<el-table-column label="产品类型" align="center" width="250">
|
||||
<template slot-scope="scope">
|
||||
@@ -111,7 +112,8 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="发货人" v-if="showExportTime" align="center" prop="exportByName" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.exportBy" placeholder="请选择发货人" filterable @change="handleExportByNameChange(scope.row)">
|
||||
<el-select v-model="scope.row.exportBy" placeholder="请选择发货人" filterable
|
||||
@change="handleExportByNameChange(scope.row)">
|
||||
<el-option v-for="item in userList" :key="item.userName" :value="item.userName" :label="item.nickName" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -139,7 +141,8 @@
|
||||
<el-table-column v-if="showGrade" label="质量状态" align="center" prop="qualityStatus">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.qualityStatus" placeholder="请选择质量状态" @change="handleGradeChange(scope.row)">
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :value="item.value" :label="item.label" />
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :value="item.value"
|
||||
:label="item.label" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -190,8 +193,8 @@
|
||||
<warehouse-select v-model="form.warehouseId" placeholder="请选择仓库/库区/库位" style="width: 100%;" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="实际库区" prop="actualWarehouseId">
|
||||
<actual-warehouse-select v-model="form.actualWarehouseId" :clearInput="form.coilId != null" placeholder="请选择实际库区" style="width: 100%;"
|
||||
clearable />
|
||||
<actual-warehouse-select v-model="form.actualWarehouseId" :clearInput="form.coilId != null"
|
||||
placeholder="请选择实际库区" style="width: 100%;" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="班组" prop="team">
|
||||
<el-select v-model="form.team" placeholder="请选择班组" style="width: 100%">
|
||||
@@ -199,7 +202,7 @@
|
||||
<el-option key="乙" label="乙" value="乙" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="材料类型" prop="materialType" v-if="!form.coilId">
|
||||
<el-form-item label="材料类型" prop="materialType">
|
||||
<el-select v-model="form.materialType" placeholder="请选择材料类型" @change="handleMaterialTypeChange">
|
||||
<el-option label="成品" value="成品" />
|
||||
<el-option label="原料" value="原料" />
|
||||
@@ -214,7 +217,8 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="质量状态" prop="qualityStatus">
|
||||
<el-select v-model="form.qualityStatus" placeholder="请选择质量状态" style="width: 100%">
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label" :value="item.value" />
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -407,7 +411,7 @@ export default {
|
||||
{ label: 'C+', value: 'C+' },
|
||||
{ label: 'C', value: 'C' },
|
||||
{ label: 'C-', value: 'C-' },
|
||||
{ label: 'D+', value: 'D+' },
|
||||
{ label: 'D+', value: 'D+' },
|
||||
{ label: 'D', value: 'D' },
|
||||
{ label: 'D-', value: 'D-' },
|
||||
],
|
||||
@@ -503,22 +507,22 @@ export default {
|
||||
// 仅在新增的时候校验
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (this.form.coilId) {
|
||||
// 修改时会有coilId,不触发校验
|
||||
console.log('修改时会有coilId,不触发校验');
|
||||
callback();
|
||||
} else {
|
||||
// 没有coilId则为新增 触发校验
|
||||
checkCoilNo({ currentCoilNo: value }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'current' || duplicateType === 'both') {
|
||||
// alert('当前钢卷号重复,请重新输入');
|
||||
callback(new Error('当前钢卷号重复,请重新输入'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
// if (this.form.coilId) {
|
||||
// // 修改时会有coilId,不触发校验
|
||||
// console.log('修改时会有coilId,不触发校验');
|
||||
// callback();
|
||||
// } else {
|
||||
// 没有coilId则为新增 触发校验
|
||||
checkCoilNo({ currentCoilNo: value, coilId: this.form.coilId }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'current' || duplicateType === 'both') {
|
||||
// alert('当前钢卷号重复,请重新输入');
|
||||
callback(new Error('当前钢卷号重复,请重新输入'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
// }
|
||||
}, trigger: 'blur'
|
||||
}
|
||||
],
|
||||
@@ -633,7 +637,7 @@ export default {
|
||||
endTime: this.queryParams.updateTime?.[1],
|
||||
}
|
||||
// 如果没有设置itemType,则设置为raw_material
|
||||
query.selectType = this.querys.materialType === '成品' ? 'product' : 'raw_material';
|
||||
query.selectType = this.querys.materialType === '原料' ? 'raw_material' : 'product';
|
||||
listMaterialCoil(query).then(response => {
|
||||
if (this.querys.warehouseId != 111) {
|
||||
// 排除掉111仓库的
|
||||
@@ -645,16 +649,6 @@ export default {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 状态改变时触发 */
|
||||
// handleStatusChange(row) {
|
||||
// updateMaterialCoilSimple(row).then(res => {
|
||||
// this.$message.success('状态更新成功');
|
||||
// this.getList(); // 刷新列表
|
||||
// }).catch(err => {
|
||||
// console.error('状态更新失败:', err);
|
||||
// this.$message.error('状态更新失败,请重试');
|
||||
// });
|
||||
// },
|
||||
/** 追溯按钮操作 */
|
||||
handleTrace(row) {
|
||||
this.traceOpen = true;
|
||||
|
||||
300
klp-ui/src/views/wms/coil/repeated.vue
Normal file
300
klp-ui/src/views/wms/coil/repeated.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div class="coil-duplicate-page" v-loading="loading">
|
||||
<el-card shadow="hover" border>
|
||||
<!-- Tab标签页 区分两个重号组 -->
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<!-- 标签页一:当前卷号重号组 -->
|
||||
<el-tab-pane label="当前卷号重号组" name="current">
|
||||
<!-- 搜索区域 + 刷新按钮 -->
|
||||
<div class="search-bar">
|
||||
<el-input v-model="currentSearchKey" placeholder="请输入卷号/规格/材质/厂家查询" clearable size="small"
|
||||
@change="handleCurrentSearch" class="search-input" />
|
||||
<el-button icon="el-icon-search" size="small" type="primary" plain @click="handleCurrentSearch"
|
||||
:loading="loading" class="refresh-btn">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" size="small" type="primary" plain @click="handleCurrentRefresh"
|
||||
:loading="loading" class="refresh-btn">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 重号组折叠面板 -->
|
||||
<el-collapse v-model="currentOpenAllKeys" :accordion="false" style="margin-top:10px;" class="collapse-panel">
|
||||
<el-collapse-item v-for="(item, index) in currentTableData" :key="`current-${index}`"
|
||||
:name="`current-${index}`">
|
||||
<!-- 折叠面板标题 -->
|
||||
<template slot="title">
|
||||
<span class="red-text">当前卷号:{{ item.currentCoilNo }}</span>
|
||||
<span class="count-text">(共 {{ item.coils.length }} 条重号钢卷数据)</span>
|
||||
</template>
|
||||
|
||||
<!-- 钢卷表格 -->
|
||||
<el-table :data="item.coils" border stripe size="small" highlight-current-row style="width: 100%;"
|
||||
empty-text="该重号组暂无钢卷数据">
|
||||
<el-table-column prop="enterCoilNo" label="入厂卷号" align="center" min-width="120" />
|
||||
<el-table-column prop="currentCoilNo" label="当前卷号" align="center" min-width="120" />
|
||||
<el-table-column prop="productSpecification" label="规格" align="center" />
|
||||
<el-table-column prop="manufacturer" label="厂家" align="center" />
|
||||
<el-table-column prop="productMaterial" label="材质" align="center" />
|
||||
<el-table-column prop="grossWeight" label="毛重(t)" align="center" width="90" />
|
||||
<el-table-column prop="netWeight" label="净重(t)" align="center" width="90" />
|
||||
<el-table-column prop="materialType" label="物料类型" align="center" />
|
||||
<!-- <el-table-column prop="qualityStatus" label="质量状态" align="center" />
|
||||
<el-table-column prop="trimmingRequirement" label="切边要求" align="center" />
|
||||
<el-table-column prop="packagingRequirement" label="包装要求" align="center" /> -->
|
||||
<el-table-column prop="createTime" label="创建时间" align="center" min-width="160" />
|
||||
<el-table-column prop="updateTime" label="更新时间" align="center" min-width="160" />
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<!-- 空数据兜底 -->
|
||||
<div v-if="currentTableData.length === 0" class="empty-data">暂无当前卷号重号的钢卷数据</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 标签页二:入厂卷号重号组 -->
|
||||
<el-tab-pane label="入厂卷号重号组" name="enter">
|
||||
<!-- 搜索区域 + 刷新按钮 -->
|
||||
<div class="search-bar">
|
||||
<el-input v-model="enterSearchKey" placeholder="请输入卷号/规格/材质/厂家查询" clearable size="small"
|
||||
@change="handleEnterSearch" class="search-input" />
|
||||
<el-button icon="el-icon-search" size="small" type="primary" plain @click="handleEnterSearch"
|
||||
:loading="loading" class="refresh-btn">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" size="small" type="primary" plain @click="handleEnterRefresh"
|
||||
:loading="loading" class="refresh-btn">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 重号组折叠面板 -->
|
||||
<el-collapse v-model="enterOpenAllKeys" :accordion="false" style="margin-top:10px;" class="collapse-panel">
|
||||
<el-collapse-item v-for="(item, index) in enterTableData" :key="`enter-${index}`" :name="`enter-${index}`">
|
||||
<!-- 折叠面板标题 -->
|
||||
<template slot="title">
|
||||
<span class="red-text">入厂卷号:{{ item.enterCoilNo }}</span>
|
||||
<span class="count-text">(共 {{ item.coils.length }} 条重号钢卷数据)</span>
|
||||
</template>
|
||||
|
||||
<!-- 钢卷表格 -->
|
||||
<el-table :data="item.coils" border stripe size="small" highlight-current-row style="width: 100%;"
|
||||
empty-text="该重号组暂无钢卷数据">
|
||||
<el-table-column prop="enterCoilNo" label="入厂卷号" align="center" min-width="120" />
|
||||
<el-table-column prop="currentCoilNo" label="当前卷号" align="center" min-width="120" />
|
||||
<el-table-column prop="productSpecification" label="规格" align="center" />
|
||||
<el-table-column prop="manufacturer" label="厂家" align="center" />
|
||||
<el-table-column prop="productMaterial" label="材质" align="center" />
|
||||
<el-table-column prop="grossWeight" label="毛重(t)" align="center" width="90" />
|
||||
<el-table-column prop="netWeight" label="净重(t)" align="center" width="90" />
|
||||
<el-table-column prop="materialType" label="物料类型" align="center" />
|
||||
<!-- <el-table-column prop="qualityStatus" label="质量状态" align="center" />
|
||||
<el-table-column prop="trimmingRequirement" label="切边要求" align="center" />
|
||||
<el-table-column prop="packagingRequirement" label="包装要求" align="center" /> -->
|
||||
<el-table-column prop="createTime" label="创建时间" align="center" min-width="160" />
|
||||
<el-table-column prop="updateTime" label="更新时间" align="center" min-width="160" />
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<!-- 空数据兜底 -->
|
||||
<div v-if="enterTableData.length === 0" class="empty-data">暂无入厂卷号重号的钢卷数据</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDuplicateGroups } from '@/api/wms/coil'
|
||||
|
||||
export default {
|
||||
name: 'CoilDuplicateList',
|
||||
data() {
|
||||
return {
|
||||
// 基础数据-保留原始分组结构
|
||||
currentNoGroups: [],
|
||||
enterNoGroups: [],
|
||||
loading: false,
|
||||
activeTab: 'current',
|
||||
|
||||
// 当前卷号重号组 - 仅保留搜索配置,删除所有分页相关变量
|
||||
currentSearchKey: '',
|
||||
currentTableData: [],
|
||||
|
||||
// 入厂卷号重号组 - 仅保留搜索配置,删除所有分页相关变量
|
||||
enterSearchKey: '',
|
||||
enterTableData: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 当前卷号折叠面板默认全开
|
||||
currentOpenAllKeys() {
|
||||
return this.currentTableData.map((_, index) => `current-${index}`)
|
||||
},
|
||||
// 入厂卷号折叠面板默认全开
|
||||
enterOpenAllKeys() {
|
||||
return this.enterTableData.map((_, index) => `enter-${index}`)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getRepeatedGroups()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 统一格式化钢卷数据 - 处理所有嵌套字段判空,生成扁平字段,数值兜底
|
||||
* @param {Object} coil 原始钢卷对象
|
||||
* @returns {Object} 格式化后的钢卷对象
|
||||
*/
|
||||
formatCoilData(coil) {
|
||||
const product = coil.product || {}
|
||||
const rawMaterial = coil.rawMaterial || {}
|
||||
const warehouse = coil.warehouse || {}
|
||||
|
||||
return {
|
||||
...coil,
|
||||
productSpecification: product.specification || rawMaterial.specification || '-',
|
||||
productMaterial: product.material || rawMaterial.material || '-',
|
||||
manufacturer: product.manufacturer || rawMaterial.manufacturer || '-',
|
||||
warehouseName: warehouse.warehouseName || '-',
|
||||
grossWeight: coil.grossWeight || '-',
|
||||
netWeight: coil.netWeight || '-',
|
||||
materialType: coil.materialType || '-',
|
||||
qualityStatus: coil.qualityStatus || '-',
|
||||
trimmingRequirement: coil.trimmingRequirement || '-',
|
||||
packagingRequirement: coil.packagingRequirement || '-'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化重号组数据 - 对组内所有钢卷执行格式化
|
||||
* @param {Array} groups 原始重号组数组
|
||||
* @returns {Array} 格式化后的重号组数组
|
||||
*/
|
||||
formatGroupData(groups) {
|
||||
return groups.map(group => ({
|
||||
...group,
|
||||
coils: group.coils?.map(coil => this.formatCoilData(coil)) || []
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询存在重号的钢卷 - 核心请求方法
|
||||
*/
|
||||
async getRepeatedGroups() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getDuplicateGroups()
|
||||
this.currentNoGroups = this.formatGroupData(res.data.currentGroups || [])
|
||||
this.enterNoGroups = this.formatGroupData(res.data.enterGroups || [])
|
||||
// 初始化搜索过滤
|
||||
this.handleCurrentSearch()
|
||||
this.handleEnterSearch()
|
||||
} catch (err) {
|
||||
this.$message.error('查询重号钢卷数据失败!')
|
||||
console.error(err)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 当前卷号重号组 - 搜索过滤逻辑(精简版,无分页)
|
||||
*/
|
||||
handleCurrentSearch() {
|
||||
const key = this.currentSearchKey.trim().toLowerCase()
|
||||
if (!key) {
|
||||
this.currentTableData = [...this.currentNoGroups]
|
||||
} else {
|
||||
this.currentTableData = this.currentNoGroups.filter(group => {
|
||||
const groupNo = (group.currentCoilNo || '').toLowerCase()
|
||||
const hasMatchCoil = group.coils.some(item => {
|
||||
const enterNo = (item.enterCoilNo || '').toLowerCase()
|
||||
const currentNo = (item.currentCoilNo || '').toLowerCase()
|
||||
const spec = (item.productSpecification || '').toLowerCase()
|
||||
const material = (item.productMaterial || '').toLowerCase()
|
||||
const manufacturer = (item.manufacturer || '').toLowerCase()
|
||||
// const warehouse = (item.warehouseName || '').toLowerCase()
|
||||
const matType = (item.materialType || '').toLowerCase()
|
||||
return enterNo.includes(key) || currentNo.includes(key) || spec.includes(key) || material.includes(key) || manufacturer.includes(key) || matType.includes(key)
|
||||
})
|
||||
return groupNo.includes(key) || hasMatchCoil
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 入厂卷号重号组 - 搜索过滤逻辑(精简版,无分页)
|
||||
*/
|
||||
handleEnterSearch() {
|
||||
const key = this.enterSearchKey.trim().toLowerCase()
|
||||
if (!key) {
|
||||
this.enterTableData = [...this.enterNoGroups]
|
||||
} else {
|
||||
this.enterTableData = this.enterNoGroups.filter(group => {
|
||||
const groupNo = (group.enterCoilNo || '').toLowerCase()
|
||||
const hasMatchCoil = group.coils.some(item => {
|
||||
const enterNo = (item.enterCoilNo || '').toLowerCase()
|
||||
const currentNo = (item.currentCoilNo || '').toLowerCase()
|
||||
const spec = (item.productSpecification || '').toLowerCase()
|
||||
const material = (item.productMaterial || '').toLowerCase()
|
||||
const manufacturer = (item.manufacturer || '').toLowerCase()
|
||||
// const warehouse = (item.warehouseName || '').toLowerCase()
|
||||
const matType = (item.materialType || '').toLowerCase()
|
||||
return enterNo.includes(key) || currentNo.includes(key) || spec.includes(key) || material.includes(key) || manufacturer.includes(key) || matType.includes(key)
|
||||
})
|
||||
return groupNo.includes(key) || hasMatchCoil
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 刷新按钮方法 - 重置搜索+重新请求最新数据
|
||||
handleCurrentRefresh() {
|
||||
this.currentSearchKey = ''
|
||||
this.getRepeatedGroups()
|
||||
},
|
||||
handleEnterRefresh() {
|
||||
this.enterSearchKey = ''
|
||||
this.getRepeatedGroups()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coil-duplicate-page {
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.search-bar {
|
||||
text-align: right;
|
||||
|
||||
.search-input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠面板样式优化
|
||||
.collapse-panel {
|
||||
::v-deep .el-collapse-item__header {
|
||||
font-weight: 600;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
.red-text {
|
||||
color: #E6A23C;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.count-text {
|
||||
color: #666;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// 空数据样式
|
||||
.empty-data {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -230,7 +230,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMaterialCoil, updateMaterialCoil, getMaterialCoilTrace } from '@/api/wms/coil';
|
||||
import { getMaterialCoil, updateMaterialCoil, getMaterialCoilTrace, checkCoilNo } from '@/api/wms/coil';
|
||||
import { completeAction } from '@/api/wms/pendingAction';
|
||||
import { listWarehouse } from '@/api/wms/warehouse';
|
||||
import { listRawMaterialWithBom } from '@/api/wms/rawMaterial';
|
||||
@@ -292,7 +292,22 @@ export default {
|
||||
},
|
||||
rules: {
|
||||
currentCoilNo: [
|
||||
{ required: true, message: '请输入当前钢卷号', trigger: 'blur' }
|
||||
{ required: true, message: '请输入当前钢卷号', trigger: 'blur' },
|
||||
// 仅在新增的时候校验
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
// 没有coilId则为新增 触发校验
|
||||
checkCoilNo({ currentCoilNo: value, coilId: this.updateForm.coilId }).then(res => {
|
||||
const { duplicateType } = res.data;
|
||||
if (duplicateType === 'current' || duplicateType === 'both') {
|
||||
// alert('当前钢卷号重复,请重新输入');
|
||||
callback(new Error('当前钢卷号重复,请重新输入'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
}, trigger: 'blur'
|
||||
}
|
||||
],
|
||||
team: [
|
||||
{ required: true, message: '请输入班组', trigger: 'blur' }
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination v-if="!rangeMode" v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -68,8 +68,9 @@ export default {
|
||||
currentCoilNo: null,
|
||||
grade: null,
|
||||
materialType: '成品',
|
||||
selectType: 'raw_material',
|
||||
dataType: 1 // 只查询当前数据,不查询历史数据
|
||||
selectType: 'product',
|
||||
dataType: 1, // 只查询当前数据,不查询历史数据
|
||||
// status: 0 // 只查询未发货的数据
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
@@ -122,7 +123,7 @@ export default {
|
||||
width: '100'
|
||||
},
|
||||
{
|
||||
label: '库区',
|
||||
label: '逻辑库区',
|
||||
align: 'center',
|
||||
prop: 'warehouseName',
|
||||
width: '120',
|
||||
|
||||
@@ -223,6 +223,8 @@ export default {
|
||||
queryParamsWithSalesId: {
|
||||
selectType: 'product',
|
||||
materialType: '成品',
|
||||
dataType: 1,
|
||||
status: 0,
|
||||
saleId: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
@@ -231,6 +233,8 @@ export default {
|
||||
queryParamsWithUnAssignedId: {
|
||||
selectType: 'product',
|
||||
materialType: '成品',
|
||||
dataType: 1,
|
||||
status: 0,
|
||||
saleId: -1,
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
|
||||
@@ -142,8 +142,8 @@ export default {
|
||||
const nowDay = addZero(now.getDate())
|
||||
|
||||
// ✅ 目标时间区间:昨天早上6点 至 今天早上6点
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 06:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 06:00:00`
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 07:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 07:00:00`
|
||||
return {
|
||||
list: [],
|
||||
queryParams: {
|
||||
|
||||
48
klp-ui/src/views/wms/hrm/components/LeaveDeptChart.vue
Normal file
48
klp-ui/src/views/wms/hrm/components/LeaveDeptChart.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="dept-chart" ref="chartRef"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'LeaveDeptChart',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({ xAxis: [], countData: [], dayData: [] })
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chartData: { deep: true, handler: 'initChart' }
|
||||
},
|
||||
mounted() { this.initChart(); window.addEventListener('resize', this.resizeChart); },
|
||||
beforeDestroy() { window.removeEventListener('resize', this.resizeChart); this.chart?.dispose(); },
|
||||
data() { return { chart: null }; },
|
||||
methods: {
|
||||
initChart() {
|
||||
const dom = this.$refs.chartRef;
|
||||
if (!dom) return;
|
||||
this.chart = echarts.init(dom);
|
||||
const option = {
|
||||
title: { text: '按部门请假统计', left: 'center', textStyle: { fontSize: 14 } },
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['请假记录数', '请假总天数'], top: 30 },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: [{ type: 'category', data: this.chartData.xAxis }],
|
||||
yAxis: [{ type: 'value' }],
|
||||
series: [
|
||||
{ name: '请假记录数', type: 'bar', data: this.chartData.countData, barWidth: '30%' },
|
||||
{ name: '请假总天数', type: 'bar', data: this.chartData.dayData, barWidth: '30%' }
|
||||
]
|
||||
};
|
||||
this.chart.setOption(option);
|
||||
},
|
||||
resizeChart() { this.chart?.resize(); }
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dept-chart { width: 100%; height: 100%; }
|
||||
</style>
|
||||
38
klp-ui/src/views/wms/hrm/components/LeaveRecordTable.vue
Normal file
38
klp-ui/src/views/wms/hrm/components/LeaveRecordTable.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<el-table :data="tableData" :loading="loading" highlight-current-row border stripe style="width: 100%"
|
||||
:row-key="'leaveId'" :tree-props="{ children: 'children' }" default-expand-all>
|
||||
<el-table-column prop="userName" label="请假人" align="center" width="120" />
|
||||
<el-table-column label="请假总次数" align="center" prop="leaveCount"></el-table-column>
|
||||
<el-table-column label="请假总时长(天)" align="center" prop="leaveDays"></el-table-column>
|
||||
<el-table-column prop="leaveType" label="请假类型" align="center" width="120" />
|
||||
<el-table-column prop="leaveDays" label="请假时长(天)" align="center" width="120" />
|
||||
<el-table-column prop="leaveReason" label="请假理由" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="startTime" label="开始时间" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="endTime" label="结束时间" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="leaveShift" label="请假班次" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LeaveRecordTable',
|
||||
props: {
|
||||
tableData: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ userName: '张三', leaveCount: 2, leaveDays: 3.5, children: [] },
|
||||
{ userName: '李四', leaveCount: 1, leaveDays: 1.5, children: [] },
|
||||
]
|
||||
},
|
||||
loading: { type: Boolean, default: false }
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ✅ 完全正确:Vue2的深度选择器就是 ::v-deep,不用改! */
|
||||
::v-deep .el-table {
|
||||
--el-table-header-text-color: #333;
|
||||
--el-table-row-hover-bg-color: #fafafa;
|
||||
}
|
||||
</style>
|
||||
68
klp-ui/src/views/wms/hrm/components/LeaveTrendChart.vue
Normal file
68
klp-ui/src/views/wms/hrm/components/LeaveTrendChart.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="trend-chart" ref="chartRef"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'LeaveTrendChart',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({ xAxis: [], countData: [], dayData: [] })
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chartData: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.initChart();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart();
|
||||
window.addEventListener('resize', this.resizeChart);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.resizeChart);
|
||||
this.chart && this.chart.dispose();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
const dom = this.$refs.chartRef;
|
||||
if (!dom) return;
|
||||
this.chart = echarts.init(dom);
|
||||
const option = {
|
||||
title: { text: '按时间请假统计', left: 'center', textStyle: { fontSize: 14 } },
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { data: ['请假记录数', '请假总天数'], top: 30 },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: [{ type: 'category', data: this.chartData.xAxis }],
|
||||
yAxis: [{ type: 'value' }],
|
||||
series: [
|
||||
{ name: '请假记录数', type: 'line', data: this.chartData.countData, smooth: true },
|
||||
{ name: '请假总天数', type: 'line', data: this.chartData.dayData, smooth: true }
|
||||
]
|
||||
};
|
||||
this.chart.setOption(option);
|
||||
},
|
||||
resizeChart() {
|
||||
this.chart && this.chart.resize();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trend-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
60
klp-ui/src/views/wms/hrm/components/LeaveTypeChart.vue
Normal file
60
klp-ui/src/views/wms/hrm/components/LeaveTypeChart.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="type-chart" ref="chartRef"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'LeaveTypeChart',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Object,
|
||||
default: () => ({ countPie: [], dayPie: [] })
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chartData: { deep: true, handler: 'initChart' }
|
||||
},
|
||||
mounted() { this.initChart(); window.addEventListener('resize', this.resizeChart); },
|
||||
beforeDestroy() { window.removeEventListener('resize', this.resizeChart); this.chart?.dispose(); },
|
||||
data() { return { chart: null }; },
|
||||
methods: {
|
||||
initChart() {
|
||||
const dom = this.$refs.chartRef;
|
||||
if (!dom) return;
|
||||
this.chart = echarts.init(dom);
|
||||
const option = {
|
||||
title: { text: '按请假类型统计', left: 'center', textStyle: { fontSize: 14 } },
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { top: 30 },
|
||||
grid: { left: '0%', right: '0%', bottom: '0%', containLabel: true },
|
||||
series: [
|
||||
{
|
||||
name: '请假记录数占比',
|
||||
type: 'pie',
|
||||
radius: ['20%', '40%'],
|
||||
center: ['25%', '60%'],
|
||||
data: this.chartData.countPie,
|
||||
label: { show: true, fontSize: 12 }
|
||||
},
|
||||
{
|
||||
name: '请假天数占比',
|
||||
type: 'pie',
|
||||
radius: ['20%', '40%'],
|
||||
center: ['75%', '60%'],
|
||||
data: this.chartData.dayPie,
|
||||
label: { show: true, fontSize: 12 }
|
||||
}
|
||||
]
|
||||
};
|
||||
this.chart.setOption(option);
|
||||
},
|
||||
resizeChart() { this.chart?.resize(); }
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.type-chart { width: 100%; height: 100%; }
|
||||
</style>
|
||||
256
klp-ui/src/views/wms/hrm/leaveReport.vue
Normal file
256
klp-ui/src/views/wms/hrm/leaveReport.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="leave-statistics-container">
|
||||
<!-- 1. 时间筛选区域 -->
|
||||
<div class="search-form">
|
||||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||
<el-form-item label="请假时间">
|
||||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始时间"
|
||||
end-placeholder="结束时间" value-format="yyyy-MM-dd" style="width: 260px"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假人">
|
||||
<dict-select v-model="queryParams.applicantName" dict-type="hrm_leave_employee" placeholder="请选择请假人"
|
||||
style="width: 260px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||||
<el-button icon="el-icon-download" type="warning" plain @click="handleExport">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 2. 图表展示区域 - 横向三个图表组件 -->
|
||||
<div class="chart-box">
|
||||
<div class="chart-item">
|
||||
<leave-trend-chart :chart-data="trendChartData" />
|
||||
</div>
|
||||
<div class="chart-item">
|
||||
<leave-dept-chart :chart-data="deptChartData" />
|
||||
</div>
|
||||
<div class="chart-item">
|
||||
<leave-type-chart :chart-data="typeChartData" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 请假记录表格区域 - 封装的折叠表格组件 -->
|
||||
<div class="table-box">
|
||||
<leave-record-table :table-data="tableGroupData" :loading="loading" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listLeaveRequest } from "@/api/wms/leaveRequest";
|
||||
// 引入4个封装的子组件
|
||||
import LeaveTrendChart from './components/LeaveTrendChart';
|
||||
import LeaveDeptChart from './components/LeaveDeptChart';
|
||||
import LeaveTypeChart from './components/LeaveTypeChart';
|
||||
import LeaveRecordTable from './components/LeaveRecordTable';
|
||||
import DictSelect from '@/components/DictSelect';
|
||||
|
||||
export default {
|
||||
name: 'LeaveStatistics',
|
||||
components: {
|
||||
LeaveTrendChart,
|
||||
LeaveDeptChart,
|
||||
LeaveTypeChart,
|
||||
LeaveRecordTable,
|
||||
DictSelect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 9999, // 查全部数据用于图表统计
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
},
|
||||
leaveIds: '', // 请假人ID列表
|
||||
dateRange: [], // 时间筛选绑定值
|
||||
leaveRequestList: [], // 原始请假数据列表
|
||||
// 图表渲染数据
|
||||
trendChartData: { xAxis: [], countData: [], dayData: [] }, // 折线图数据
|
||||
deptChartData: { xAxis: [], countData: [], dayData: [] }, // 柱状图数据
|
||||
typeChartData: { countPie: [], dayPie: [] }, // 饼图数据
|
||||
tableGroupData: [] // 表格分组数据(按请假人聚合)
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// 初始化默认时间:当月第一天 至 当前时间
|
||||
this.initDefaultTime();
|
||||
// 加载数据
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
// 初始化默认时间 - 当月1号 00:00:00 至 当前时间 yyyy-MM-dd HH:mm:ss
|
||||
initDefaultTime() {
|
||||
const now = new Date();
|
||||
// 当月第一天
|
||||
const monthFirst = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const startTime = this.formatDateTime(monthFirst);
|
||||
const endTime = this.formatDateTime(now);
|
||||
this.dateRange = [startTime, endTime];
|
||||
// this.queryParams.startTime = startTime;
|
||||
// this.queryParams.endTime = endTime;
|
||||
},
|
||||
|
||||
// 时间格式化:yyyy-MM-dd HH:mm:ss
|
||||
formatDateTime(date) {
|
||||
const y = date.getFullYear();
|
||||
const m = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const d = date.getDate().toString().padStart(2, '0');
|
||||
const h = date.getHours().toString().padStart(2, '0');
|
||||
const mm = date.getMinutes().toString().padStart(2, '0');
|
||||
const s = date.getSeconds().toString().padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
},
|
||||
|
||||
// 查询按钮事件
|
||||
handleQuery() {
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
// this.queryParams.startTime = this.dateRange[0];
|
||||
// this.queryParams.endTime = this.dateRange[1];
|
||||
}
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 重置按钮事件
|
||||
resetQuery() {
|
||||
this.dateRange = [];
|
||||
this.initDefaultTime();
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 调取接口获取数据
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listLeaveRequest(this.queryParams).then(res => {
|
||||
this.leaveRequestList = res.rows || [];
|
||||
// 提取所有请假人ID
|
||||
this.leaveIds = this.leaveRequestList.map(item => item.leaveId).join(',');
|
||||
// 核心:数据处理,为图表和表格格式化数据
|
||||
this.formatAllData();
|
||||
this.loading = false;
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
handleExport() {
|
||||
// 导出逻辑
|
||||
if (!this.leaveIds) {
|
||||
this.$message.warning('请先查询数据');
|
||||
return;
|
||||
}
|
||||
this.download('/wms/leaveRequest/export', {
|
||||
leaveIds: this.leaveIds
|
||||
}, '请假记录.xlsx');
|
||||
},
|
||||
|
||||
// 格式化所有图表+表格数据
|
||||
formatAllData() {
|
||||
const list = this.leaveRequestList;
|
||||
// ========== 1. 处理【按时间汇总】折线图数据 ==========
|
||||
const timeObj = {};
|
||||
list.forEach(item => {
|
||||
const day = item.startTime?.split(' ')[0]; // 按日期聚合
|
||||
if (!day) return;
|
||||
if (!timeObj[day]) {
|
||||
timeObj[day] = { count: 0, days: 0 };
|
||||
}
|
||||
timeObj[day].count += 1; // 记录数+1
|
||||
timeObj[day].days += Number(item.leaveDays) || 0; // 请假天数累加
|
||||
});
|
||||
this.trendChartData.xAxis = Object.keys(timeObj).sort(); // 日期排序
|
||||
this.trendChartData.countData = this.trendChartData.xAxis.map(k => timeObj[k].count);
|
||||
this.trendChartData.dayData = this.trendChartData.xAxis.map(k => timeObj[k].days);
|
||||
|
||||
// ========== 2. 处理【按部门汇总】柱状图数据 ==========
|
||||
const deptObj = {};
|
||||
list.forEach(item => {
|
||||
const dept = item.applicantDeptName || '未知部门';
|
||||
if (!deptObj[dept]) {
|
||||
deptObj[dept] = { count: 0, days: 0 };
|
||||
}
|
||||
deptObj[dept].count += 1;
|
||||
deptObj[dept].days += Number(item.leaveDays) || 0;
|
||||
});
|
||||
this.deptChartData.xAxis = Object.keys(deptObj);
|
||||
this.deptChartData.countData = this.deptChartData.xAxis.map(k => deptObj[k].count);
|
||||
this.deptChartData.dayData = this.deptChartData.xAxis.map(k => deptObj[k].days);
|
||||
|
||||
// ========== 3. 处理【按请假类型汇总】饼图数据 ==========
|
||||
const typeObj = {};
|
||||
list.forEach(item => {
|
||||
const type = item.leaveType || '未知类型';
|
||||
if (!typeObj[type]) {
|
||||
typeObj[type] = { count: 0, days: 0 };
|
||||
}
|
||||
typeObj[type].count += 1;
|
||||
typeObj[type].days += Number(item.leaveDays) || 0;
|
||||
});
|
||||
this.typeChartData.countPie = Object.keys(typeObj).map(k => ({ name: k, value: typeObj[k].count }));
|
||||
this.typeChartData.dayPie = Object.keys(typeObj).map(k => ({ name: k, value: typeObj[k].days }));
|
||||
|
||||
console.log(this.tableGroupData, '按请假人分组表格数据');
|
||||
// ========== 4. 处理【按请假人分组】表格折叠数据 ==========
|
||||
const userObj = {};
|
||||
list.forEach(item => {
|
||||
const userName = item.applicantName || '未知人员';
|
||||
if (!userObj[userName]) {
|
||||
userObj[userName] = [];
|
||||
}
|
||||
userObj[userName].push({
|
||||
...item,
|
||||
leaveTime: `${item.startTime} ~ ${item.endTime}`, // 拼接请假时间段
|
||||
leaveDays: parseInt(item.leaveDays || 0) // 转换为整数
|
||||
});
|
||||
});
|
||||
this.tableGroupData = Object.keys(userObj).map(user => ({
|
||||
leaveId: user,
|
||||
userName: user,
|
||||
leaveCount: userObj[user].length || 0, // 请假次数
|
||||
leaveDays: parseInt(userObj[user].reduce((sum, item) => sum + item.leaveDays, 0)), // 请假总时长(整数)
|
||||
children: userObj[user] // 折叠子数据
|
||||
}));
|
||||
console.log(this.tableGroupData, '按请假人分组表格数据');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.leave-statistics-container {
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
height: 320px;
|
||||
|
||||
.chart-item {
|
||||
flex: 1;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.table-box {
|
||||
width: 100%;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
371
klp-ui/src/views/wms/hrm/leaveRequest.vue
Normal file
371
klp-ui/src/views/wms/hrm/leaveRequest.vue
Normal file
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="60px">
|
||||
<el-form-item label="请假类型" prop="leaveType">
|
||||
<el-select v-model="queryParams.leaveType" placeholder="请选择请假类型" clearable>
|
||||
<el-option v-for="dict in dict.type.hrm_leave_type" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假人" prop="applicantName">
|
||||
<dict-select v-model="queryParams.applicantName" dict-type="hrm_leave_employee"
|
||||
placeholder="请选择请假人姓名"></dict-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门名称" prop="applicantDeptName">
|
||||
<dict-select v-model="queryParams.applicantDeptName" dict-type="hrm_department"
|
||||
placeholder="请选择请假人部门名称"></dict-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假班次" prop="leaveShift">
|
||||
<el-select v-model="queryParams.leaveShift" placeholder="请选择请假班次" clearable>
|
||||
<el-option v-for="dict in dict.type.hrm_leave_shift" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single"
|
||||
@click="handleUpdate">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple"
|
||||
@click="handleDelete">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="leaveRequestList" @selection-change="handleSelectionChange">
|
||||
|
||||
<el-table-column label="请假类型" align="center" prop="leaveType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.hrm_leave_type" :value="scope.row.leaveType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="请假人姓名" align="center" prop="applicantName">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.hrm_leave_employee" :value="scope.row.applicantName" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部门名称" align="center" prop="applicantDeptName">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.hrm_department" :value="scope.row.applicantDeptName" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="请假标题" align="center" prop="leaveTitle" show-overflow-tooltip />
|
||||
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" align="center" prop="endTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="请假班次" align="center" prop="leaveShift">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.hrm_leave_shift" :value="scope.row.leaveShift" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="具体原因" align="center" prop="leaveReason" show-overflow-tooltip />
|
||||
<el-table-column label="请假天数" align="center" prop="leaveDays" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
|
||||
<!-- 添加或修改员工请假申请对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="请假类型" prop="leaveType">
|
||||
<el-select v-model="form.leaveType" placeholder="请选择请假类型">
|
||||
<el-option v-for="dict in dict.type.hrm_leave_type" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假人姓名" prop="applicantName">
|
||||
<dict-select v-model="form.applicantName" dict-type="hrm_leave_employee" placeholder="请选择请假人姓名"></dict-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门名称" prop="applicantDeptName">
|
||||
<dict-select v-model="form.applicantDeptName" dict-type="hrm_department" placeholder="请选择部门名称"></dict-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker clearable v-model="form.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择请假开始时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker clearable v-model="form.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择请假结束时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假班次" prop="leaveShift">
|
||||
<el-select v-model="form.leaveShift" placeholder="请选择请假班次">
|
||||
<el-option v-for="dict in dict.type.hrm_leave_shift" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假天数" prop="leaveDays">
|
||||
<el-input v-model="form.leaveDays" placeholder="选择时间后自动计算,也可手动修改" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请假原因" prop="leaveReason">
|
||||
<el-input v-model="form.leaveReason" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="请假标题" prop="leaveTitle">
|
||||
<el-input v-model="form.leaveTitle" placeholder="请输入请假标题" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="附件" prop="attachmentUrls">
|
||||
<FileUpload v-model="form.attachmentUrls" :max-count="1" :show-file-list="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listLeaveRequest, getLeaveRequest, delLeaveRequest, addLeaveRequest, updateLeaveRequest } from "@/api/wms/leaveRequest";
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
import DictSelect from '@/components/DictSelect'
|
||||
|
||||
export default {
|
||||
name: "LeaveRequest",
|
||||
dicts: ['hrm_leave_shift', 'hrm_leave_type', 'hrm_department', 'hrm_leave_employee'],
|
||||
components: {
|
||||
FileUpload,
|
||||
DictSelect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 按钮loading
|
||||
buttonLoading: false,
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 员工请假申请表格数据
|
||||
leaveRequestList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
leaveType: undefined,
|
||||
applicantName: undefined,
|
||||
applicantDeptName: undefined,
|
||||
leaveShift: undefined,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验规则【核心新增:完整必填校验】
|
||||
rules: {
|
||||
leaveTitle: [{ required: true, message: '请假原因不能为空', trigger: ['blur', 'change'] }],
|
||||
leaveType: [{ required: true, message: '请假类型不能为空', trigger: 'change' }],
|
||||
applicantName: [{ required: true, message: '请假人姓名不能为空', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }],
|
||||
leaveShift: [{ required: true, message: '请假班次不能为空', trigger: 'change' }],
|
||||
leaveDays: [
|
||||
{ required: true, message: '请假天数不能为空', trigger: ['blur', 'change'] },
|
||||
],
|
||||
leaveReason: [
|
||||
{ required: true, message: '具体原因不能为空', trigger: ['blur', 'change'] },
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
// 核心新增:监听开始/结束时间变化,自动计算天数
|
||||
watch: {
|
||||
'form.startTime': {
|
||||
handler() {
|
||||
this.calculateLeaveDays()
|
||||
},
|
||||
immediate: false
|
||||
},
|
||||
'form.endTime': {
|
||||
handler() {
|
||||
this.calculateLeaveDays()
|
||||
},
|
||||
immediate: false
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 查询员工请假申请列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listLeaveRequest(this.queryParams).then(response => {
|
||||
this.leaveRequestList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
leaveId: undefined,
|
||||
leaveTitle: undefined,
|
||||
leaveType: undefined,
|
||||
applicantName: undefined,
|
||||
applicantDeptName: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
leaveShift: undefined,
|
||||
leaveDays: undefined,
|
||||
leaveReason: undefined,
|
||||
attachmentUrls: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
delFlag: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.leaveId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "添加员工请假申请";
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.loading = true;
|
||||
this.reset();
|
||||
const leaveId = row.leaveId || this.ids
|
||||
getLeaveRequest(leaveId).then(response => {
|
||||
this.loading = false;
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改员工请假申请";
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.form.leaveTitle = `${this.form.applicantName}-${this.form.leaveType}-${this.form.startTime}-${this.form.leaveReason}`
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
this.buttonLoading = true;
|
||||
if (this.form.leaveId != null) {
|
||||
updateLeaveRequest(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
} else {
|
||||
addLeaveRequest(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const leaveIds = row.leaveId || this.ids;
|
||||
this.$modal.confirm('是否确认删除员工请假申请编号为"' + leaveIds + '"的数据项?').then(() => {
|
||||
this.loading = true;
|
||||
return delLeaveRequest(leaveIds);
|
||||
}).then(() => {
|
||||
this.loading = false;
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('wms/leaveRequest/export', {
|
||||
...this.queryParams
|
||||
}, `leaveRequest_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
// 核心新增:自动计算请假天数的方法
|
||||
calculateLeaveDays() {
|
||||
const { startTime, endTime } = this.form;
|
||||
// 两个时间都选择后才计算
|
||||
if (startTime && endTime) {
|
||||
// 转成时间戳
|
||||
const start = new Date(startTime).getTime();
|
||||
const end = new Date(endTime).getTime();
|
||||
// 判断结束时间不能小于开始时间
|
||||
if (end < start) {
|
||||
this.$modal.msgWarning('结束时间不能早于开始时间,请重新选择!');
|
||||
this.form.leaveDays = undefined;
|
||||
return;
|
||||
}
|
||||
// 计算时间差(毫秒) → 转天 → 保留2位小数
|
||||
const diffTime = end - start;
|
||||
const diffDays = (diffTime / (1000 * 60 * 60 * 24)).toFixed(2);
|
||||
// 赋值到天数输入框
|
||||
this.form.leaveDays = diffDays;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
369
klp-ui/src/views/wms/hrm/mealReport.vue
Normal file
369
klp-ui/src/views/wms/hrm/mealReport.vue
Normal file
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="60px">
|
||||
<el-form-item label="餐别" prop="mealType">
|
||||
<el-select v-model="queryParams.mealType" placeholder="请选择餐别" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.hrm_meal_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用餐日期" prop="reportDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.reportDate"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择用餐日期"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门名称" prop="deptName">
|
||||
<DictSelect dictType="hrm_department" v-model="queryParams.deptName" placeholder="请选择部门名称"></DictSelect>
|
||||
</el-form-item>
|
||||
<el-form-item label="报餐人" prop="reportUserName">
|
||||
<DictSelect dictType="hrm_leave_employee" v-model="queryParams.reportUserName" placeholder="请选择报餐人姓名"></DictSelect>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- ✅ 新增:表格上方的人数合计统计组件 el-description -->
|
||||
<el-descriptions size="small" border class="mb8" :column="3">
|
||||
<el-descriptions-item label="堂食人数合计">{{ totalDineIn }}</el-descriptions-item>
|
||||
<el-descriptions-item label="打包人数合计">{{ totalTakeout }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用餐总人数合计">{{ totalAll }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-table v-loading="loading" :data="mealReportList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="reportId" v-if="false"/>
|
||||
<el-table-column label="用餐日期" align="center" prop="reportDate" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.reportDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="餐别" align="center" prop="mealType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.hrm_meal_type" :value="scope.row.mealType"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="部门名称" align="center" prop="deptName">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.hrm_department" :value="scope.row.deptName"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用餐总人数" align="center" prop="totalPeople" />
|
||||
<el-table-column label="堂食人数" align="center" prop="dineInPeople" />
|
||||
<el-table-column label="打包人数" align="center" prop="takeoutPeople" />
|
||||
<el-table-column label="报餐人姓名" align="center" prop="reportUserName">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.hrm_leave_employee" :value="scope.row.reportUserName"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报餐时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 添加或修改部门报餐主对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="用餐日期" prop="reportDate">
|
||||
<el-date-picker clearable
|
||||
v-model="form.reportDate"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择用餐日期">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="餐别" prop="mealType">
|
||||
<el-select v-model="form.mealType" placeholder="请选择餐别">
|
||||
<el-option
|
||||
v-for="dict in dict.type.hrm_meal_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="parseInt(dict.value)"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门名称" prop="deptName">
|
||||
<DictSelect dictType="hrm_department" v-model="form.deptName" placeholder="请选择部门名称"></DictSelect>
|
||||
</el-form-item>
|
||||
<!-- 优化:增加number类型,禁止输入非数字,限制最小值0 -->
|
||||
<el-form-item label="堂食人数" prop="dineInPeople">
|
||||
<el-input v-model="form.dineInPeople" placeholder="请输入堂食人数" type="number" min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="打包人数" prop="takeoutPeople">
|
||||
<el-input v-model="form.takeoutPeople" placeholder="请输入打包人数" type="number" min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用餐总人数" prop="totalPeople">
|
||||
<el-input v-model="form.totalPeople" placeholder="请输入用餐总人数" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="报餐人姓名" prop="reportUserName">
|
||||
<DictSelect dictType="hrm_leave_employee" v-model="form.reportUserName" placeholder="请选择报餐人姓名"></DictSelect>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listMealReport, getMealReport, delMealReport, addMealReport, updateMealReport } from "@/api/wms/mealReport";
|
||||
import DictSelect from "@/components/DictSelect";
|
||||
|
||||
export default {
|
||||
name: "MealReport",
|
||||
dicts: ['hrm_meal_type', 'hrm_leave_employee', 'hrm_department'],
|
||||
components: { DictSelect },
|
||||
data() {
|
||||
// 用餐日期默认选中今天
|
||||
// 格式化日期为yyyy-MM-dd
|
||||
function addZero(num) {
|
||||
return num < 10 ? '0' + num : num;
|
||||
}
|
||||
const now = new Date()
|
||||
const nowYear = now.getFullYear()
|
||||
const nowMonth = addZero(now.getMonth() + 1)
|
||||
const nowDay = addZero(now.getDate())
|
||||
const nowTime = `${nowYear}-${nowMonth}-${nowDay}`
|
||||
return {
|
||||
buttonLoading: false,
|
||||
loading: true,
|
||||
ids: [],
|
||||
single: true,
|
||||
multiple: true,
|
||||
showSearch: true,
|
||||
total: 0,
|
||||
mealReportList: [],
|
||||
title: "",
|
||||
open: false,
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
reportDate: nowTime,
|
||||
mealType: undefined,
|
||||
deptName: undefined,
|
||||
reportUserName: undefined,
|
||||
status: undefined,
|
||||
},
|
||||
form: {},
|
||||
// 合计统计的三个变量 ✅新增
|
||||
totalDineIn: 0, // 堂食合计
|
||||
totalTakeout: 0, // 打包合计
|
||||
totalAll: 0, // 总人数合计
|
||||
// 表单校验规则
|
||||
rules: {
|
||||
reportDate: [{ required: true, message: '用餐日期不能为空', trigger: 'change' }],
|
||||
mealType: [{ required: true, message: '餐别不能为空', trigger: 'change' }],
|
||||
deptName: [{ required: true, message: '部门名称不能为空', trigger: 'change' }],
|
||||
dineInPeople: [{ required: true, message: '堂食人数不能为空', trigger: 'blur' }],
|
||||
takeoutPeople: [{ required: true, message: '打包人数不能为空', trigger: 'blur' }],
|
||||
totalPeople: [{ required: true, message: '用餐总人数不能为空', trigger: 'blur' }],
|
||||
reportUserName: [{ required: true, message: '报餐人姓名不能为空', trigger: 'change' }]
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
watch: {
|
||||
'form.dineInPeople': {
|
||||
handler(val) {
|
||||
this.calcTotalPeople();
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
'form.takeoutPeople': {
|
||||
handler(val) {
|
||||
this.calcTotalPeople();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 查询部门报餐主列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listMealReport(this.queryParams).then(response => {
|
||||
this.mealReportList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
this.calcTableSum(); // ✅新增:获取表格数据后,立即计算合计
|
||||
});
|
||||
},
|
||||
// ✅ 核心新增:计算表格中堂食、打包、总人数的合计方法
|
||||
calcTableSum(){
|
||||
let dineIn = 0;
|
||||
let takeout = 0;
|
||||
let all = 0;
|
||||
// 遍历表格数据累加,处理空值/非数字,避免NaN
|
||||
this.mealReportList.forEach(item => {
|
||||
dineIn += item.dineInPeople ? Number(item.dineInPeople) : 0;
|
||||
takeout += item.takeoutPeople ? Number(item.takeoutPeople) : 0;
|
||||
all += item.totalPeople ? Number(item.totalPeople) : 0;
|
||||
});
|
||||
// 赋值到统计变量
|
||||
this.totalDineIn = dineIn;
|
||||
this.totalTakeout = takeout;
|
||||
this.totalAll = all;
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
reportId: undefined,
|
||||
reportDate: undefined,
|
||||
mealType: undefined,
|
||||
deptName: undefined,
|
||||
totalPeople: undefined,
|
||||
dineInPeople: undefined,
|
||||
takeoutPeople: undefined,
|
||||
reportUserName: undefined,
|
||||
status: undefined,
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
delFlag: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.reportId)
|
||||
this.single = selection.length!==1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "添加部门报餐主";
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
const hour = now.getHours().toString().padStart(2, '0');
|
||||
const minute = now.getMinutes().toString().padStart(2, '0');
|
||||
const second = now.getSeconds().toString().padStart(2, '0');
|
||||
this.form.reportDate = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.loading = true;
|
||||
this.reset();
|
||||
const reportId = row.reportId || this.ids
|
||||
getMealReport(reportId).then(response => {
|
||||
this.loading = false;
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改部门报餐主";
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
this.buttonLoading = true;
|
||||
if (this.form.reportId != null) {
|
||||
updateMealReport(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
} else {
|
||||
addMealReport(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const reportIds = row.reportId || this.ids;
|
||||
this.$modal.confirm('是否确认删除部门报餐主编号为"' + reportIds + '"的数据项?').then(() => {
|
||||
this.loading = true;
|
||||
return delMealReport(reportIds);
|
||||
}).then(() => {
|
||||
this.loading = false;
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('wms/mealReport/export', { ...this.queryParams }, `mealReport_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
// 计算单行总人数
|
||||
calcTotalPeople() {
|
||||
const dine = this.form.dineInPeople ? Number(this.form.dineInPeople) : 0;
|
||||
const take = this.form.takeoutPeople ? Number(this.form.takeoutPeople) : 0;
|
||||
this.form.totalPeople = dine + take;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
405
klp-ui/src/views/wms/hrm/report.vue
Normal file
405
klp-ui/src/views/wms/hrm/report.vue
Normal file
@@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 请假申请统计模块 -->
|
||||
<el-row :gutter="20">
|
||||
<!-- 请假类型统计-饼图+柱状图 占比+数量 -->
|
||||
<el-col :span="8">
|
||||
<div class="chart-title">请假类型分布统计</div>
|
||||
<div id="leaveTypeChart" class="chart-container"></div>
|
||||
</el-col>
|
||||
<!-- 请假部门统计-横向柱状图 -->
|
||||
<el-col :span="8">
|
||||
<div class="chart-title">各部门请假情况统计</div>
|
||||
<div id="leaveDeptChart" class="chart-container"></div>
|
||||
</el-col>
|
||||
|
||||
<!-- 餐别统计-饼图 堂食/打包占比 -->
|
||||
<el-col :span="8">
|
||||
<div class="chart-title">餐别-堂食/打包占比</div>
|
||||
<div id="mealTypePieChart" class="chart-container"></div>
|
||||
</el-col>
|
||||
|
||||
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<!-- 请假月度趋势统计-组合图 次数+总天数 -->
|
||||
<el-col :span="12">
|
||||
<div class="chart-title">月度请假趋势统计 (次数/总天数)</div>
|
||||
<div id="leaveMonthChart" class="chart-container"></div>
|
||||
</el-col>
|
||||
|
||||
<!-- 报餐日期趋势统计 -->
|
||||
<el-col :span="12">
|
||||
<div class="chart-title">日报餐人数趋势统计</div>
|
||||
<div id="mealDateChart" class="chart-container"></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<!-- 餐别统计-柱状图 用餐人数+报餐次数 -->
|
||||
<el-col :span="12">
|
||||
<div class="chart-title">各餐别用餐人数&报餐次数</div>
|
||||
<div id="mealTypeBarChart" class="chart-container"></div>
|
||||
</el-col>
|
||||
<!-- 报餐部门统计-柱状图 -->
|
||||
<el-col :span="12">
|
||||
<div class="chart-title">各部门报餐汇总统计</div>
|
||||
<div id="mealDeptChart" class="chart-container"></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import { getLeaveTypeCount, getDeptCount as getLeaveDeptCount, getMonthCount as getLeaveMonthCount } from '@/api/wms/leaveRequest'
|
||||
import { getMealTypeCount, getDeptCount as getMealDeptCount, getDateCount as getMealDateCount } from '@/api/wms/mealReport'
|
||||
|
||||
export default {
|
||||
name: 'Report',
|
||||
data() {
|
||||
return {
|
||||
// echarts实例对象
|
||||
leaveTypeChart: null,
|
||||
leaveDeptChart: null,
|
||||
leaveMonthChart: null,
|
||||
mealTypePieChart: null,
|
||||
mealTypeBarChart: null,
|
||||
mealDeptChart: null,
|
||||
mealDateChart: null,
|
||||
// 各接口数据存储
|
||||
leaveTypeData: [],
|
||||
leaveDeptData: [],
|
||||
leaveMonthData: [],
|
||||
mealTypeData: [],
|
||||
mealDeptData: [],
|
||||
mealDateData: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载完成后初始化所有图表+请求数据
|
||||
this.initAllCharts()
|
||||
this.fetchAllData()
|
||||
// 窗口大小改变时,图表自适应
|
||||
window.addEventListener('resize', this.resizeAllCharts)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表实例,防止内存泄漏
|
||||
window.removeEventListener('resize', this.resizeAllCharts)
|
||||
this.disposeAllCharts()
|
||||
},
|
||||
methods: {
|
||||
// 初始化所有图表容器
|
||||
initAllCharts() {
|
||||
this.leaveTypeChart = echarts.init(document.getElementById('leaveTypeChart'))
|
||||
this.leaveDeptChart = echarts.init(document.getElementById('leaveDeptChart'))
|
||||
this.leaveMonthChart = echarts.init(document.getElementById('leaveMonthChart'))
|
||||
this.mealTypePieChart = echarts.init(document.getElementById('mealTypePieChart'))
|
||||
this.mealTypeBarChart = echarts.init(document.getElementById('mealTypeBarChart'))
|
||||
this.mealDeptChart = echarts.init(document.getElementById('mealDeptChart'))
|
||||
this.mealDateChart = echarts.init(document.getElementById('mealDateChart'))
|
||||
},
|
||||
// 请求所有接口数据
|
||||
fetchAllData() {
|
||||
Promise.all([
|
||||
this.getLeaveTypeCount({}),
|
||||
this.getLeaveDeptCount({}),
|
||||
this.getLeaveMonthCount({}),
|
||||
this.getMealTypeCount({}),
|
||||
this.getMealDeptCount({}),
|
||||
this.getMealDateCount({})
|
||||
]).then(() => {
|
||||
// 所有数据请求完成后绘制图表
|
||||
this.renderLeaveTypeChart()
|
||||
this.renderLeaveDeptChart()
|
||||
this.renderLeaveMonthChart()
|
||||
this.renderMealTypePieChart()
|
||||
this.renderMealTypeBarChart()
|
||||
this.renderMealDeptChart()
|
||||
this.renderMealDateChart()
|
||||
})
|
||||
},
|
||||
// 图表自适应窗口大小
|
||||
resizeAllCharts() {
|
||||
this.leaveTypeChart?.resize()
|
||||
this.leaveDeptChart?.resize()
|
||||
this.leaveMonthChart?.resize()
|
||||
this.mealTypePieChart?.resize()
|
||||
this.mealTypeBarChart?.resize()
|
||||
this.mealDeptChart?.resize()
|
||||
this.mealDateChart?.resize()
|
||||
},
|
||||
// 销毁所有图表实例
|
||||
disposeAllCharts() {
|
||||
this.leaveTypeChart?.dispose()
|
||||
this.leaveDeptChart?.dispose()
|
||||
this.leaveMonthChart?.dispose()
|
||||
this.mealTypePieChart?.dispose()
|
||||
this.mealTypeBarChart?.dispose()
|
||||
this.mealDeptChart?.dispose()
|
||||
this.mealDateChart?.dispose()
|
||||
},
|
||||
|
||||
// ======================== 请假相关接口及图表绘制 ========================
|
||||
// 请假类型统计
|
||||
getLeaveTypeCount(query) {
|
||||
return getLeaveTypeCount(query).then(res => {
|
||||
this.leaveTypeData = res.data || []
|
||||
return res
|
||||
})
|
||||
},
|
||||
// 绘制请假类型饼图
|
||||
renderLeaveTypeChart() {
|
||||
const data = this.leaveTypeData.map(item => ({
|
||||
name: item.type,
|
||||
value: item.count,
|
||||
days: item.total_days
|
||||
}))
|
||||
const option = {
|
||||
tooltip: { trigger: 'item', formatter: '{b}: <br/>请假次数: {c}次 <br/>请假总天数: {d}天' },
|
||||
legend: { orient: 'vertical', top: 'middle', right: 10 },
|
||||
series: [
|
||||
{
|
||||
name: '请假类型',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['40%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: { borderRadius: 8, borderColor: '#fff', borderWidth: 2 },
|
||||
color: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399'],
|
||||
label: { show: false, position: 'center' },
|
||||
emphasis: { label: { show: true, fontSize: 16, fontWeight: 'bold' } },
|
||||
labelLine: { show: false },
|
||||
data: data
|
||||
}
|
||||
]
|
||||
}
|
||||
this.leaveTypeChart.setOption(option)
|
||||
},
|
||||
|
||||
// 部门请假统计
|
||||
getLeaveDeptCount(query) {
|
||||
return getLeaveDeptCount(query).then(res => {
|
||||
this.leaveDeptData = res.data || []
|
||||
return res
|
||||
})
|
||||
},
|
||||
// 绘制部门请假横向柱状图
|
||||
renderLeaveDeptChart() {
|
||||
const xData = this.leaveDeptData.map(item => item.dept_name)
|
||||
const countData = this.leaveDeptData.map(item => item.count)
|
||||
const dayData = this.leaveDeptData.map(item => Number(item.total_days))
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { top: 0, right: 0 },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'value', axisLabel: { formatter: '{value}' } },
|
||||
yAxis: { type: 'category', data: xData },
|
||||
series: [
|
||||
{ name: '请假次数', type: 'bar', barWidth: '40%', data: countData, itemStyle: { color: '#409EFF' } },
|
||||
{ name: '请假天数', type: 'bar', barWidth: '40%', data: dayData, itemStyle: { color: '#F56C6C' } }
|
||||
]
|
||||
}
|
||||
this.leaveDeptChart.setOption(option)
|
||||
},
|
||||
|
||||
// 月份请假统计
|
||||
getLeaveMonthCount(query) {
|
||||
return getLeaveMonthCount(query).then(res => {
|
||||
this.leaveMonthData = res.data || []
|
||||
return res
|
||||
})
|
||||
},
|
||||
// 绘制月度请假趋势组合图
|
||||
renderLeaveMonthChart() {
|
||||
const xData = this.leaveMonthData.map(item => item.month)
|
||||
const countData = this.leaveMonthData.map(item => item.count)
|
||||
const dayData = this.leaveMonthData.map(item => Number(item.total_days))
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { top: 0, right: 0 },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'category', boundaryGap: false, data: xData },
|
||||
yAxis: [{ type: 'value', name: '请假次数' }, { type: 'value', name: '请假天数', right: '15%' }],
|
||||
series: [
|
||||
{ name: '请假次数', type: 'bar', yAxisIndex: 0, data: countData, itemStyle: { color: '#409EFF' } },
|
||||
{ name: '请假总天数', type: 'line', yAxisIndex: 1, data: dayData, itemStyle: { color: '#F56C6C' }, lineStyle: { width: 3 }, symbol: 'circle' }
|
||||
]
|
||||
}
|
||||
this.leaveMonthChart.setOption(option)
|
||||
},
|
||||
|
||||
// ======================== 报餐相关接口及图表绘制 ========================
|
||||
// 餐别统计
|
||||
getMealTypeCount(query) {
|
||||
return getMealTypeCount(query).then(res => {
|
||||
this.mealTypeData = res.data || []
|
||||
return res
|
||||
})
|
||||
},
|
||||
// 绘制餐别-堂食打包占比饼图
|
||||
renderMealTypePieChart() {
|
||||
let totalDineIn = 0
|
||||
let totalTakeout = 0
|
||||
this.mealTypeData.forEach(item => {
|
||||
totalDineIn += Number(item.total_dine_in)
|
||||
totalTakeout += Number(item.total_takeout)
|
||||
})
|
||||
const option = {
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c}人 ({d}%)' },
|
||||
legend: { orient: 'vertical', bottom: 0, left: 'center' },
|
||||
series: [
|
||||
{
|
||||
name: '用餐方式',
|
||||
type: 'pie',
|
||||
radius: ['30%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
itemStyle: { borderRadius: 8, borderColor: '#fff', borderWidth: 2 },
|
||||
color: ['#67C23A', '#E6A23C'],
|
||||
data: [
|
||||
{ name: '堂食人数', value: totalDineIn },
|
||||
{ name: '打包人数', value: totalTakeout }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
this.mealTypePieChart.setOption(option)
|
||||
},
|
||||
// 绘制餐别人数+次数柱状图
|
||||
renderMealTypeBarChart() {
|
||||
const xData = this.mealTypeData.map(item => item.meal_name)
|
||||
const peopleData = this.mealTypeData.map(item => Number(item.total_people))
|
||||
const countData = this.mealTypeData.map(item => item.report_count)
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { top: 0, right: 0 },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'category', data: xData },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{
|
||||
name: '总用餐人数',
|
||||
type: 'bar',
|
||||
data: peopleData,
|
||||
itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#409EFF' }, { offset: 1, color: '#66B1FF' }]) }
|
||||
},
|
||||
{
|
||||
name: '报餐次数',
|
||||
type: 'line',
|
||||
data: countData,
|
||||
itemStyle: { color: '#F56C6C' },
|
||||
lineStyle: { width: 3 },
|
||||
symbol: 'circle',
|
||||
symbolSize: 8
|
||||
}
|
||||
]
|
||||
}
|
||||
this.mealTypeBarChart.setOption(option)
|
||||
},
|
||||
|
||||
// 报餐部门统计
|
||||
getMealDeptCount(query) {
|
||||
return getMealDeptCount(query).then(res => {
|
||||
this.mealDeptData = res.data || []
|
||||
return res
|
||||
})
|
||||
},
|
||||
// 绘制部门报餐柱状图
|
||||
renderMealDeptChart() {
|
||||
const xData = this.mealDeptData.map(item => item.dept_name)
|
||||
const peopleData = this.mealDeptData.map(item => Number(item.total_people))
|
||||
const dineInData = this.mealDeptData.map(item => Number(item.total_dine_in))
|
||||
const takeoutData = this.mealDeptData.map(item => Number(item.total_takeout))
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { top: 0, right: 0 },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'category', data: xData },
|
||||
yAxis: { type: 'value', name: '人数' },
|
||||
series: [
|
||||
{ name: '总人数', type: 'bar', data: peopleData, itemStyle: { color: '#409EFF' } },
|
||||
{ name: '堂食', type: 'bar', data: dineInData, itemStyle: { color: '#67C23A' } },
|
||||
{ name: '打包', type: 'bar', data: takeoutData, itemStyle: { color: '#E6A23C' } }
|
||||
]
|
||||
}
|
||||
this.mealDeptChart.setOption(option)
|
||||
},
|
||||
|
||||
// 报餐日期统计
|
||||
getMealDateCount(query) {
|
||||
return getMealDateCount(query).then(res => {
|
||||
this.mealDateData = res.data || []
|
||||
return res
|
||||
})
|
||||
},
|
||||
// 绘制日报餐趋势图
|
||||
renderMealDateChart() {
|
||||
const xData = this.mealDateData.map(item => item.report_date)
|
||||
const peopleData = this.mealDateData.map(item => Number(item.total_people))
|
||||
const dineInData = this.mealDateData.map(item => Number(item.total_dine_in))
|
||||
const takeoutData = this.mealDateData.map(item => Number(item.total_takeout))
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { top: 0, right: 0 },
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
xAxis: { type: 'category', boundaryGap: false, data: xData },
|
||||
yAxis: { type: 'value', name: '人数' },
|
||||
series: [
|
||||
{ name: '总用餐人数', type: 'line', data: peopleData, itemStyle: { color: '#409EFF' }, lineStyle: { width: 3 } },
|
||||
{ name: '堂食人数', type: 'line', data: dineInData, itemStyle: { color: '#67C23A' }, lineStyle: { width: 2 } },
|
||||
{ name: '打包人数', type: 'line', data: takeoutData, itemStyle: { color: '#E6A23C' }, lineStyle: { width: 2 } }
|
||||
]
|
||||
}
|
||||
this.mealDateChart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面整体样式 */
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.mb15 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 图表标题 */
|
||||
.chart-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 图表容器基础样式 */
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
|
||||
/* 大图表样式 */
|
||||
.big-chart {
|
||||
height: 400px;
|
||||
}
|
||||
</style>
|
||||
@@ -129,14 +129,15 @@ export default {
|
||||
const nowDay = addZero(now.getDate())
|
||||
|
||||
// ✅ 目标时间区间:昨天早上6点 至 今天早上6点
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 06:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 06:00:00`
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 07:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 07:00:00`
|
||||
return {
|
||||
list: [],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
status: 1,
|
||||
dataType: 1,
|
||||
byExportTimeStart: startTime,
|
||||
byExportTimeEnd: endTime,
|
||||
selectType: 'product',
|
||||
|
||||
@@ -138,8 +138,8 @@ export default {
|
||||
const nowDay = addZero(now.getDate())
|
||||
|
||||
// ✅ 目标时间区间:昨天早上6点 至 今天早上6点
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 06:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 06:00:00`
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 07:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 07:00:00`
|
||||
return {
|
||||
list: [],
|
||||
queryParams: {
|
||||
@@ -148,7 +148,7 @@ export default {
|
||||
// status: 1,
|
||||
byCreateTimeStart: startTime,
|
||||
byCreateTimeEnd: endTime,
|
||||
selectType: 'product',
|
||||
selectType: 'raw_material',
|
||||
enterCoilNo: '',
|
||||
currentCoilNo: '',
|
||||
warehouseId: '',
|
||||
|
||||
@@ -19,8 +19,11 @@
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库位" prop="endTime">
|
||||
<warehouse-select v-model="queryParams.warehouseId" placeholder="请选择仓库/库区/库位"
|
||||
style="width: 100%; display: inline-block; width: 200px;" clearable />
|
||||
<!-- <warehouse-select v-model="queryParams.warehouseId" placeholder="请选择仓库/库区/库位"
|
||||
style="width: 100%; display: inline-block; width: 200px;" clearable /> -->
|
||||
<el-select v-model="warehouseIds" collapse-tags multiple placeholder="请选择逻辑库位" style="width: 200px;">
|
||||
<el-option v-for="item in warehouseOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品名称" prop="endTime">
|
||||
<el-input style="width: 200px;" v-model="queryParams.itemName" placeholder="请输入产品名称" clearable
|
||||
@@ -90,9 +93,6 @@
|
||||
|
||||
<script>
|
||||
import { listCoilWithIds } from "@/api/wms/coil";
|
||||
import {
|
||||
listPendingAction,
|
||||
} from '@/api/wms/pendingAction';
|
||||
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
|
||||
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
|
||||
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
|
||||
@@ -112,7 +112,7 @@ export default {
|
||||
},
|
||||
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer'],
|
||||
data() {
|
||||
// 工具函数:个位数补零,保证格式统一(比如 9 → 09,5 → 05)
|
||||
// 工具函数:个位数补零,保证格式统一(比如 9 → 09,5 → 05)
|
||||
const addZero = (num) => num.toString().padStart(2, '0')
|
||||
|
||||
const now = new Date() // 当前本地北京时间
|
||||
@@ -131,8 +131,8 @@ export default {
|
||||
const nowDay = addZero(now.getDate())
|
||||
|
||||
// ✅ 目标时间区间:昨天早上6点 至 今天早上6点
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 06:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 06:00:00`
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 07:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 07:00:00`
|
||||
return {
|
||||
list: [],
|
||||
queryParams: {
|
||||
@@ -151,6 +151,40 @@ export default {
|
||||
itemManufacturer: '',
|
||||
},
|
||||
loading: false,
|
||||
warehouseIds: [
|
||||
'1988150099140866050',
|
||||
'1988150263284953089',
|
||||
'1988150545175736322',
|
||||
'1988150150521090049',
|
||||
],
|
||||
warehouseOptions: [
|
||||
{ label: '酸连轧成品库', value: '1988150099140866050' },
|
||||
{ label: '镀锌原料库', value: '1988150263284953089' },
|
||||
{ label: '脱脂原料库', value: '1988150545175736322' },
|
||||
{ label: '酸连轧纵剪分条原料库', value: '1988150150521090049' },
|
||||
],
|
||||
warehouseQueryMap: {
|
||||
'1988150099140866050': {
|
||||
selectType: 'product',
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150099140866050'
|
||||
},
|
||||
'1988150263284953089': {
|
||||
selectType: 'raw_material',
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150263284953089'
|
||||
},
|
||||
'1988150545175736322': {
|
||||
selectType: 'raw_material',
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150545175736322'
|
||||
},
|
||||
'1988150150521090049': {
|
||||
selectType: 'raw_material',
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150150521090049'
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -169,46 +203,61 @@ export default {
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
Promise.all([
|
||||
// 酸连轧成品库
|
||||
listCoilWithIds({
|
||||
...this.queryParams,
|
||||
pageSize: 9999,
|
||||
pageNum: 1,
|
||||
dataType: 1,
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150099140866050'
|
||||
}),
|
||||
// 镀锌原料库
|
||||
listCoilWithIds({
|
||||
...this.queryParams,
|
||||
pageSize: 9999,
|
||||
pageNum: 1,
|
||||
dataType: 1,
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150263284953089'
|
||||
}),
|
||||
// 脱脂原料库
|
||||
listCoilWithIds({
|
||||
...this.queryParams,
|
||||
pageSize: 9999,
|
||||
pageNum: 1,
|
||||
dataType: 1,
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150545175736322'
|
||||
}),
|
||||
// 酸连轧纵剪分条原料库1988150150521090049
|
||||
listCoilWithIds({
|
||||
...this.queryParams,
|
||||
pageSize: 9999,
|
||||
pageNum: 1,
|
||||
dataType: 1,
|
||||
createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150150521090049'
|
||||
}),
|
||||
]).then(([res1, res2, res3, res4]) => {
|
||||
console.log(res1, res2, res3, res4)
|
||||
const list = [...res1.rows, ...res2.rows, ...res3.rows, ...res4.rows]
|
||||
Promise.all(
|
||||
this.warehouseIds.map(warehouseId => {
|
||||
const params = this.warehouseQueryMap[warehouseId]
|
||||
return listCoilWithIds({
|
||||
...this.queryParams,
|
||||
pageSize: 9999,
|
||||
pageNum: 1,
|
||||
// dataType: 1,
|
||||
...params
|
||||
})
|
||||
})
|
||||
// [
|
||||
// // 酸连轧成品库
|
||||
// listCoilWithIds({
|
||||
// ...this.queryParams,
|
||||
// pageSize: 9999,
|
||||
// pageNum: 1,
|
||||
// // dataType: 1,
|
||||
// createBy: 'suanzhakuguan',
|
||||
// warehouseId: '1988150099140866050'
|
||||
// }),
|
||||
// // 镀锌原料库
|
||||
// listCoilWithIds({
|
||||
// ...this.queryParams,
|
||||
// pageSize: 9999,
|
||||
// pageNum: 1,
|
||||
// // dataType: 1,
|
||||
// selectType: 'raw_material',
|
||||
// createBy: 'suanzhakuguan',
|
||||
// warehouseId: '1988150263284953089'
|
||||
// }),
|
||||
// // 脱脂原料库
|
||||
// listCoilWithIds({
|
||||
// ...this.queryParams,
|
||||
// pageSize: 9999,
|
||||
// pageNum: 1,
|
||||
// // dataType: 1,
|
||||
// selectType: 'raw_material',
|
||||
// createBy: 'suanzhakuguan',
|
||||
// warehouseId: '1988150545175736322'
|
||||
// }),
|
||||
// // 酸连轧纵剪分条原料库1988150150521090049
|
||||
// listCoilWithIds({
|
||||
// ...this.queryParams,
|
||||
// pageSize: 9999,
|
||||
// pageNum: 1,
|
||||
// // dataType: 1,
|
||||
// selectType: 'raw_material',
|
||||
// createBy: 'suanzhakuguan',
|
||||
// warehouseId: '1988150150521090049'
|
||||
// }),
|
||||
// ]
|
||||
).then((resList) => {
|
||||
console.log(resList)
|
||||
const list = resList.flatMap(res => res.rows)
|
||||
// 按照createTime 降序排序
|
||||
this.list = list.sort(
|
||||
(a, b) => new Date(b.createTime) - new Date(a.createTime)
|
||||
|
||||
243
klp-ui/src/views/wms/report/zinc.vue
Normal file
243
klp-ui/src/views/wms/report/zinc.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<div class="app-container" v-loading="loading">
|
||||
<el-row>
|
||||
<el-form label-width="80px" inline>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker style="width: 200px;" v-model="queryParams.byCreateTimeStart" type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择开始时间"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker style="width: 200px;" v-model="queryParams.byCreateTimeEnd" type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择结束时间"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="入场钢卷号" prop="endTime">
|
||||
<el-input style="width: 200px; display: inline-block;" v-model="queryParams.enterCoilNo"
|
||||
placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前钢卷号" prop="endTime">
|
||||
<el-input style="width: 200px;" v-model="queryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="逻辑库位" prop="endTime">
|
||||
<!-- <warehouse-select v-model="queryParams.warehouseId" placeholder="请选择仓库/库区/库位"
|
||||
style="width: 100%; display: inline-block; width: 200px;" clearable /> -->
|
||||
<el-select v-model="warehouseIds" collapse-tags multiple placeholder="请选择逻辑库位" style="width: 200px;">
|
||||
<el-option v-for="item in warehouseOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品名称" prop="endTime">
|
||||
<el-input style="width: 200px;" v-model="queryParams.itemName" placeholder="请输入产品名称" clearable
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格" prop="endTime">
|
||||
<memo-input style="width: 200px;" v-model="queryParams.itemSpecification" storageKey="coilSpec"
|
||||
placeholder="请选择规格" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="材质" prop="endTime">
|
||||
<muti-select style="width: 200px;" v-model="queryParams.itemMaterial" :options="dict.type.coil_material"
|
||||
placeholder="请选择材质" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" prop="endTime">
|
||||
<muti-select style="width: 200px;" v-model="queryParams.itemManufacturer"
|
||||
:options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="endTime">
|
||||
<el-button type="primary" @click="getList">查询</el-button>
|
||||
<el-button type="primary" @click="exportData">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<el-descriptions title="统计信息" :column="3" border>
|
||||
<el-descriptions-item label="总钢卷数量">{{ summary.totalCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总重">{{ summary.totalWeight }}t</el-descriptions-item>
|
||||
<el-descriptions-item label="均重">{{ summary.avgWeight }}t</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions title="明细信息" :column="3" border>
|
||||
</el-descriptions>
|
||||
<el-table :data="list" border height="calc(100vh - 320px)">
|
||||
<el-table-column label="入场钢卷号" align="center" prop="enterCoilNo">
|
||||
<template slot-scope="scope">
|
||||
<coil-no :coil-no="scope.row.enterCoilNo"></coil-no>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前钢卷号" align="center" prop="currentCoilNo">
|
||||
<template slot-scope="scope">
|
||||
<coil-no :coil-no="scope.row.currentCoilNo"></coil-no>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" />
|
||||
<el-table-column label="逻辑库位" align="center" prop="warehouseName" />
|
||||
<el-table-column label="实际库区" align="center" prop="actualWarehouseName" />
|
||||
<el-table-column label="产品类型" align="center" width="250">
|
||||
<template slot-scope="scope">
|
||||
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row.product" />
|
||||
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row.rawMaterial" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="重量 (吨)" align="center" prop="netWeight" />
|
||||
<el-table-column label="长度 (米)" align="center" prop="length" />
|
||||
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
|
||||
<el-table-column label="出库状态" align="center" prop="status">
|
||||
<!-- 0在库,1已出库 -->
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.status === 0 ? '在库' : '已出库' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新人" align="center" prop="updateByName" />
|
||||
<el-table-column label="更新时间" align="center" prop="updateTime" />
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listCoilWithIds } from "@/api/wms/coil";
|
||||
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
|
||||
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
|
||||
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
|
||||
import MemoInput from "@/components/MemoInput";
|
||||
import MutiSelect from "@/components/MutiSelect";
|
||||
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProductInfo,
|
||||
RawMaterialInfo,
|
||||
CoilNo,
|
||||
MemoInput,
|
||||
MutiSelect,
|
||||
WarehouseSelect,
|
||||
},
|
||||
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer'],
|
||||
data() {
|
||||
// 工具函数:个位数补零,保证格式统一(比如 9 → 09,5 → 05)
|
||||
const addZero = (num) => num.toString().padStart(2, '0')
|
||||
|
||||
const now = new Date() // 当前本地北京时间
|
||||
// 核心:获取【昨天】的日期对象(自动处理跨月/跨年,无边界问题)
|
||||
const yesterday = new Date(now)
|
||||
yesterday.setDate(yesterday.getDate() - 1)
|
||||
|
||||
// 昨天的年、月、日(补零格式化)
|
||||
const yesYear = yesterday.getFullYear()
|
||||
const yesMonth = addZero(yesterday.getMonth() + 1)
|
||||
const yesDay = addZero(yesterday.getDate())
|
||||
|
||||
// 今天的年、月、日(补零格式化)
|
||||
const nowYear = now.getFullYear()
|
||||
const nowMonth = addZero(now.getMonth() + 1)
|
||||
const nowDay = addZero(now.getDate())
|
||||
|
||||
// ✅ 目标时间区间:昨天早上6点 至 今天早上6点
|
||||
const startTime = `${yesYear}-${yesMonth}-${yesDay} 07:00:00`
|
||||
const endTime = `${nowYear}-${nowMonth}-${nowDay} 07:00:00`
|
||||
return {
|
||||
list: [],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
// status: 1,
|
||||
byCreateTimeStart: startTime,
|
||||
byCreateTimeEnd: endTime,
|
||||
selectType: 'product',
|
||||
enterCoilNo: '',
|
||||
currentCoilNo: '',
|
||||
warehouseId: '',
|
||||
productName: '',
|
||||
itemSpecification: '',
|
||||
itemMaterial: '',
|
||||
itemManufacturer: '',
|
||||
},
|
||||
loading: false,
|
||||
warehouseIds: [
|
||||
'1988150323162836993',
|
||||
'1988150487185289217',
|
||||
],
|
||||
warehouseOptions: [
|
||||
{ value: '1988150323162836993', label: '镀锌成品库' },
|
||||
{ value: '1988150487185289217', label: '镀锌纵剪分条原料库' },
|
||||
],
|
||||
warehouseQueryMap: {
|
||||
'1988150323162836993': {
|
||||
selectType: 'product',
|
||||
// createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150323162836993'
|
||||
},
|
||||
'1988150487185289217': {
|
||||
selectType: 'raw_material',
|
||||
// createBy: 'suanzhakuguan',
|
||||
warehouseId: '1988150487185289217'
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
summary() {
|
||||
// 总钢卷数量、总重、均重
|
||||
const totalCount = this.list.length
|
||||
const totalWeight = this.list.reduce((acc, cur) => acc + parseFloat(cur.netWeight), 0)
|
||||
const avgWeight = totalCount > 0 ? (totalWeight / totalCount).toFixed(2) : 0
|
||||
return {
|
||||
totalCount,
|
||||
totalWeight: totalWeight.toFixed(2),
|
||||
avgWeight,
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
Promise.all([
|
||||
...this.warehouseIds.map(warehouseId => listCoilWithIds({
|
||||
...this.queryParams,
|
||||
...this.warehouseQueryMap[warehouseId],
|
||||
pageSize: 9999,
|
||||
pageNum: 1,
|
||||
// dataType: 1,
|
||||
}))
|
||||
// // 镀锌成品库
|
||||
// listCoilWithIds({
|
||||
// ...this.queryParams,
|
||||
// pageSize: 9999,
|
||||
// pageNum: 1,
|
||||
// // dataType: 1,
|
||||
// selectType: 'product',
|
||||
// // createBy: 'suanzhakuguan',
|
||||
// warehouseId: '1988150323162836993'
|
||||
// }),
|
||||
// // 镀锌纵剪分条原料库
|
||||
// listCoilWithIds({
|
||||
// ...this.queryParams,
|
||||
// pageSize: 9999,
|
||||
// pageNum: 1,
|
||||
// // dataType: 1,
|
||||
// selectType: 'raw_material',
|
||||
// // createBy: 'suanzhakuguan',
|
||||
// warehouseId: '1988150487185289217'
|
||||
// }),
|
||||
]).then((resList) => {
|
||||
console.log(resList)
|
||||
const list = resList.flatMap(res => res.rows)
|
||||
// 按照createTime 降序排序
|
||||
this.list = list.sort(
|
||||
(a, b) => new Date(b.createTime) - new Date(a.createTime)
|
||||
)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 导出
|
||||
exportData() {
|
||||
this.download('wms/materialCoil/export', {
|
||||
coilIds: this.list.map(item => item.coilId).join(',')
|
||||
}, `materialCoil_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -45,6 +45,14 @@ module.exports = {
|
||||
['^' + process.env.VUE_APP_BASE_API]: ''
|
||||
}
|
||||
},
|
||||
// 直接代理Zinc1相关路径
|
||||
'/zinc-api': {
|
||||
target: `http://140.143.206.120:18082/prod-api`,
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
['^' + process.env.VUE_APP_BASE_API]: ''
|
||||
}
|
||||
},
|
||||
// 直接代理WebSocket相关路径
|
||||
'/websocket': {
|
||||
target: `http://localhost:8080`,
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.klp.controller;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import com.klp.common.annotation.RepeatSubmit;
|
||||
import com.klp.common.annotation.Log;
|
||||
import com.klp.common.core.controller.BaseController;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
import com.klp.common.core.domain.R;
|
||||
import com.klp.common.core.validate.AddGroup;
|
||||
import com.klp.common.core.validate.EditGroup;
|
||||
import com.klp.common.enums.BusinessType;
|
||||
import com.klp.common.utils.poi.ExcelUtil;
|
||||
import com.klp.domain.vo.WmsLeaveRequestVo;
|
||||
import com.klp.domain.bo.WmsLeaveRequestBo;
|
||||
import com.klp.service.IWmsLeaveRequestService;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 员工请假申请
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/wms/leaveRequest")
|
||||
public class WmsLeaveRequestController extends BaseController {
|
||||
|
||||
private final IWmsLeaveRequestService iWmsLeaveRequestService;
|
||||
|
||||
/**
|
||||
* 查询员工请假申请列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<WmsLeaveRequestVo> list(WmsLeaveRequestBo bo, PageQuery pageQuery) {
|
||||
return iWmsLeaveRequestService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出员工请假申请列表
|
||||
*/
|
||||
@Log(title = "员工请假申请", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(WmsLeaveRequestBo bo, HttpServletResponse response) {
|
||||
List<WmsLeaveRequestVo> list = iWmsLeaveRequestService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "员工请假申请", WmsLeaveRequestVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取员工请假申请详细信息
|
||||
*
|
||||
* @param leaveId 主键
|
||||
*/
|
||||
@GetMapping("/{leaveId}")
|
||||
public R<WmsLeaveRequestVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long leaveId) {
|
||||
return R.ok(iWmsLeaveRequestService.queryById(leaveId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增员工请假申请
|
||||
*/
|
||||
@Log(title = "员工请假申请", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody WmsLeaveRequestBo bo) {
|
||||
return toAjax(iWmsLeaveRequestService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改员工请假申请
|
||||
*/
|
||||
@Log(title = "员工请假申请", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody WmsLeaveRequestBo bo) {
|
||||
return toAjax(iWmsLeaveRequestService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除员工请假申请
|
||||
*
|
||||
* @param leaveIds 主键串
|
||||
*/
|
||||
@Log(title = "员工请假申请", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{leaveIds}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] leaveIds) {
|
||||
return toAjax(iWmsLeaveRequestService.deleteWithValidByIds(Arrays.asList(leaveIds), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按请假类型统计
|
||||
*/
|
||||
@GetMapping("/report/leaveType")
|
||||
public R<List<Map<String, Object>>> getLeaveTypeReport(WmsLeaveRequestBo bo) {
|
||||
return R.ok(iWmsLeaveRequestService.getLeaveTypeReport(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按部门统计
|
||||
*/
|
||||
@GetMapping("/report/dept")
|
||||
public R<List<Map<String, Object>>> getLeaveDeptReport(WmsLeaveRequestBo bo) {
|
||||
return R.ok(iWmsLeaveRequestService.getLeaveDeptReport(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按月份统计
|
||||
*/
|
||||
@GetMapping("/report/monthly")
|
||||
public R<List<Map<String, Object>>> getLeaveMonthlyReport(WmsLeaveRequestBo bo) {
|
||||
return R.ok(iWmsLeaveRequestService.getLeaveMonthlyReport(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据请假人分组获取请假信息
|
||||
*/
|
||||
@GetMapping("/grouped")
|
||||
public R<List<Map<String, Object>>> getLeaveListGroupedByApplicant(WmsLeaveRequestBo bo) {
|
||||
return R.ok(iWmsLeaveRequestService.getLeaveListGroupedByApplicant(bo));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -266,9 +266,10 @@ public class WmsMaterialCoilController extends BaseController {
|
||||
*/
|
||||
@GetMapping("/checkCoilNoDuplicate")
|
||||
public R<Map<String, Object>> checkCoilNoDuplicate(
|
||||
@RequestParam(required = false) Long coilId,
|
||||
@RequestParam(required = false) String enterCoilNo,
|
||||
@RequestParam(required = false) String currentCoilNo) {
|
||||
Map<String, Object> result = iWmsMaterialCoilService.checkCoilNoDuplicate(enterCoilNo, currentCoilNo);
|
||||
Map<String, Object> result = iWmsMaterialCoilService.checkCoilNoDuplicate(coilId,enterCoilNo, currentCoilNo);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
@@ -288,5 +289,16 @@ public class WmsMaterialCoilController extends BaseController {
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询data_type=1的重复钢卷分组
|
||||
* - 入场钢卷号重复分组
|
||||
* - 当前钢卷号重复分组
|
||||
*/
|
||||
@GetMapping("/duplicateGroups")
|
||||
public R<Map<String, Object>> getDuplicateCoilGroups() {
|
||||
Map<String, Object> result = iWmsMaterialCoilService.getDuplicateCoilGroups();
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.klp.controller;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import com.klp.common.annotation.RepeatSubmit;
|
||||
import com.klp.common.annotation.Log;
|
||||
import com.klp.common.core.controller.BaseController;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
import com.klp.common.core.domain.R;
|
||||
import com.klp.common.core.validate.AddGroup;
|
||||
import com.klp.common.core.validate.EditGroup;
|
||||
import com.klp.common.enums.BusinessType;
|
||||
import com.klp.common.utils.poi.ExcelUtil;
|
||||
import com.klp.domain.vo.WmsMealReportVo;
|
||||
import com.klp.domain.bo.WmsMealReportBo;
|
||||
import com.klp.service.IWmsMealReportService;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 部门报餐主
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/wms/mealReport")
|
||||
public class WmsMealReportController extends BaseController {
|
||||
|
||||
private final IWmsMealReportService iWmsMealReportService;
|
||||
|
||||
/**
|
||||
* 查询部门报餐主列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<WmsMealReportVo> list(WmsMealReportBo bo, PageQuery pageQuery) {
|
||||
return iWmsMealReportService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出部门报餐主列表
|
||||
*/
|
||||
@Log(title = "部门报餐主", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(WmsMealReportBo bo, HttpServletResponse response) {
|
||||
List<WmsMealReportVo> list = iWmsMealReportService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "部门报餐主", WmsMealReportVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门报餐主详细信息
|
||||
*
|
||||
* @param reportId 主键
|
||||
*/
|
||||
@GetMapping("/{reportId}")
|
||||
public R<WmsMealReportVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long reportId) {
|
||||
return R.ok(iWmsMealReportService.queryById(reportId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门报餐主
|
||||
*/
|
||||
@Log(title = "部门报餐主", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody WmsMealReportBo bo) {
|
||||
return toAjax(iWmsMealReportService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门报餐主
|
||||
*/
|
||||
@Log(title = "部门报餐主", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody WmsMealReportBo bo) {
|
||||
return toAjax(iWmsMealReportService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门报餐主
|
||||
*
|
||||
* @param reportIds 主键串
|
||||
*/
|
||||
@Log(title = "部门报餐主", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{reportIds}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] reportIds) {
|
||||
return toAjax(iWmsMealReportService.deleteWithValidByIds(Arrays.asList(reportIds), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按餐别统计
|
||||
*/
|
||||
@GetMapping("/report/mealType")
|
||||
public R<List<Map<String, Object>>> getMealTypeReport(WmsMealReportBo bo) {
|
||||
return R.ok(iWmsMealReportService.getMealTypeReport(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按部门统计
|
||||
*/
|
||||
@GetMapping("/report/dept")
|
||||
public R<List<Map<String, Object>>> getMealDeptReport(WmsMealReportBo bo) {
|
||||
return R.ok(iWmsMealReportService.getMealDeptReport(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按日期统计
|
||||
*/
|
||||
@GetMapping("/report/date")
|
||||
public R<List<Map<String, Object>>> getMealDateReport(WmsMealReportBo bo) {
|
||||
return R.ok(iWmsMealReportService.getMealDateReport(bo));
|
||||
}
|
||||
}
|
||||
80
klp-wms/src/main/java/com/klp/domain/WmsLeaveRequest.java
Normal file
80
klp-wms/src/main/java/com/klp/domain/WmsLeaveRequest.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package com.klp.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/**
|
||||
* 员工请假申请对象 wms_leave_request
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("wms_leave_request")
|
||||
public class WmsLeaveRequest extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID=1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "leave_id")
|
||||
private Long leaveId;
|
||||
/**
|
||||
* 请假标题
|
||||
*/
|
||||
private String leaveTitle;
|
||||
/**
|
||||
* 请假类型(年假/事假/病假/调休/外出/出差等)
|
||||
*/
|
||||
private String leaveType;
|
||||
/**
|
||||
* 请假人姓名
|
||||
*/
|
||||
private String applicantName;
|
||||
/**
|
||||
* 请假人部门名称
|
||||
*/
|
||||
private String applicantDeptName;
|
||||
/**
|
||||
* 请假开始时间
|
||||
*/
|
||||
private Date startTime;
|
||||
/**
|
||||
* 请假结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
/**
|
||||
* 请假班次(早班/中班/晚班/夜班/全天等)
|
||||
*/
|
||||
private String leaveShift;
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
private BigDecimal leaveDays;
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
private String leaveReason;
|
||||
/**
|
||||
* 附件(病假证明等)
|
||||
*/
|
||||
private String attachmentUrls;
|
||||
/**
|
||||
* 逻辑删除标识:0=正常,1=已删
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
71
klp-wms/src/main/java/com/klp/domain/WmsMealReport.java
Normal file
71
klp-wms/src/main/java/com/klp/domain/WmsMealReport.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package com.klp.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/**
|
||||
* 部门报餐主对象 wms_meal_report
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("wms_meal_report")
|
||||
public class WmsMealReport extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID=1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "report_id")
|
||||
private Long reportId;
|
||||
/**
|
||||
* 用餐日期
|
||||
*/
|
||||
private Date reportDate;
|
||||
/**
|
||||
* 餐别:1早餐 2午餐 3晚餐 4夜宵
|
||||
*/
|
||||
private Long mealType;
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
/**
|
||||
* 用餐总人数
|
||||
*/
|
||||
private Long totalPeople;
|
||||
/**
|
||||
* 堂食人数
|
||||
*/
|
||||
private Long dineInPeople;
|
||||
/**
|
||||
* 打包人数
|
||||
*/
|
||||
private Long takeoutPeople;
|
||||
/**
|
||||
* 报餐人姓名
|
||||
*/
|
||||
private String reportUserName;
|
||||
/**
|
||||
* 状态:1已提交 2已修改 3已作废
|
||||
*/
|
||||
private Long status;
|
||||
/**
|
||||
* 逻辑删除标识:0=正常,1=已删
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.klp.domain.bo;
|
||||
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 员工请假申请业务对象 wms_leave_request
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WmsLeaveRequestBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long leaveId;
|
||||
|
||||
/**
|
||||
* 请假标题
|
||||
*/
|
||||
private String leaveTitle;
|
||||
|
||||
/**
|
||||
* 请假类型(年假/事假/病假/调休/外出/出差等)
|
||||
*/
|
||||
private String leaveType;
|
||||
|
||||
/**
|
||||
* 请假人姓名
|
||||
*/
|
||||
private String applicantName;
|
||||
|
||||
/**
|
||||
* 请假人部门名称
|
||||
*/
|
||||
private String applicantDeptName;
|
||||
|
||||
/**
|
||||
* 请假开始时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date startTime;
|
||||
|
||||
/**
|
||||
* 请假结束时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date endTime;
|
||||
|
||||
/**
|
||||
* 导出ID列表(逗号分隔)
|
||||
*/
|
||||
private String leaveIds;
|
||||
|
||||
/**
|
||||
* 请假班次(早班/中班/晚班/夜班/全天等)
|
||||
*/
|
||||
private String leaveShift;
|
||||
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
private BigDecimal leaveDays;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
private String leaveReason;
|
||||
|
||||
/**
|
||||
* 附件(病假证明等)
|
||||
*/
|
||||
private String attachmentUrls;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@@ -56,6 +56,11 @@ public class WmsMaterialCoilBo extends BaseEntity {
|
||||
*/
|
||||
private Long warehouseId;
|
||||
|
||||
/**
|
||||
* 所在库区IDs(逗号分隔)
|
||||
*/
|
||||
private String warehouseIds;
|
||||
|
||||
/**
|
||||
* 下一库区ID
|
||||
*/
|
||||
|
||||
76
klp-wms/src/main/java/com/klp/domain/bo/WmsMealReportBo.java
Normal file
76
klp-wms/src/main/java/com/klp/domain/bo/WmsMealReportBo.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package com.klp.domain.bo;
|
||||
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
/**
|
||||
* 部门报餐主业务对象 wms_meal_report
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WmsMealReportBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long reportId;
|
||||
|
||||
/**
|
||||
* 用餐日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date reportDate;
|
||||
|
||||
/**
|
||||
* 餐别:1早餐 2午餐 3晚餐 4夜宵
|
||||
*/
|
||||
private Long mealType;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 用餐总人数
|
||||
*/
|
||||
private Long totalPeople;
|
||||
|
||||
/**
|
||||
* 堂食人数
|
||||
*/
|
||||
private Long dineInPeople;
|
||||
|
||||
/**
|
||||
* 打包人数
|
||||
*/
|
||||
private Long takeoutPeople;
|
||||
|
||||
/**
|
||||
* 报餐人姓名
|
||||
*/
|
||||
private String reportUserName;
|
||||
|
||||
/**
|
||||
* 状态:1已提交 2已修改 3已作废
|
||||
*/
|
||||
private Long status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.klp.domain.vo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.klp.common.annotation.ExcelDictFormat;
|
||||
import com.klp.common.convert.ExcelDictConvert;
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 员工请假申请视图对象 wms_leave_request
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class WmsLeaveRequestVo extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long leaveId;
|
||||
|
||||
/**
|
||||
* 请假标题
|
||||
*/
|
||||
@ExcelProperty(value = "请假标题")
|
||||
private String leaveTitle;
|
||||
|
||||
/**
|
||||
* 请假类型(年假/事假/病假/调休/外出/出差等)
|
||||
*/
|
||||
@ExcelProperty(value = "请假类型", converter = ExcelDictConvert.class)
|
||||
private String leaveType;
|
||||
|
||||
/**
|
||||
* 请假人姓名
|
||||
*/
|
||||
@ExcelProperty(value = "请假人姓名")
|
||||
private String applicantName;
|
||||
|
||||
/**
|
||||
* 请假人部门名称
|
||||
*/
|
||||
@ExcelProperty(value = "请假人部门名称")
|
||||
private String applicantDeptName;
|
||||
|
||||
/**
|
||||
* 请假开始时间
|
||||
*/
|
||||
@ExcelProperty(value = "请假开始时间")
|
||||
private Date startTime;
|
||||
|
||||
/**
|
||||
* 请假结束时间
|
||||
*/
|
||||
@ExcelProperty(value = "请假结束时间")
|
||||
private Date endTime;
|
||||
|
||||
/**
|
||||
* 请假班次(早班/中班/晚班/夜班/全天等)
|
||||
*/
|
||||
@ExcelProperty(value = "请假班次", converter = ExcelDictConvert.class)
|
||||
private String leaveShift;
|
||||
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
@ExcelProperty(value = "请假天数")
|
||||
private BigDecimal leaveDays;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
@ExcelProperty(value = "请假原因")
|
||||
private String leaveReason;
|
||||
|
||||
/**
|
||||
* 附件(病假证明等)
|
||||
*/
|
||||
private String attachmentUrls;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
@@ -166,4 +166,7 @@ public class WmsMaterialCoilExportVo {
|
||||
* 更新时间(仅临时存储,不导出,用于发货时间为空时兜底)
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
// 数据类型
|
||||
private Integer dataType;
|
||||
}
|
||||
|
||||
90
klp-wms/src/main/java/com/klp/domain/vo/WmsMealReportVo.java
Normal file
90
klp-wms/src/main/java/com/klp/domain/vo/WmsMealReportVo.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package com.klp.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.klp.common.annotation.ExcelDictFormat;
|
||||
import com.klp.common.convert.ExcelDictConvert;
|
||||
import com.klp.common.core.domain.BaseEntity;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 部门报餐主视图对象 wms_meal_report
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class WmsMealReportVo extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@ExcelProperty(value = "主键ID")
|
||||
private Long reportId;
|
||||
|
||||
/**
|
||||
* 用餐日期
|
||||
*/
|
||||
@ExcelProperty(value = "用餐日期")
|
||||
private Date reportDate;
|
||||
|
||||
/**
|
||||
* 餐别:1早餐 2午餐 3晚餐 4夜宵
|
||||
*/
|
||||
@ExcelProperty(value = "餐别:1早餐 2午餐 3晚餐 4夜宵")
|
||||
private Long mealType;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@ExcelProperty(value = "部门名称")
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 用餐总人数
|
||||
*/
|
||||
@ExcelProperty(value = "用餐总人数")
|
||||
private Long totalPeople;
|
||||
|
||||
/**
|
||||
* 堂食人数
|
||||
*/
|
||||
@ExcelProperty(value = "堂食人数")
|
||||
private Long dineInPeople;
|
||||
|
||||
/**
|
||||
* 打包人数
|
||||
*/
|
||||
@ExcelProperty(value = "打包人数")
|
||||
private Long takeoutPeople;
|
||||
|
||||
/**
|
||||
* 报餐人姓名
|
||||
*/
|
||||
@ExcelProperty(value = "报餐人姓名")
|
||||
private String reportUserName;
|
||||
|
||||
/**
|
||||
* 状态:1已提交 2已修改 3已作废
|
||||
*/
|
||||
@ExcelProperty(value = "状态:1已提交 2已修改 3已作废")
|
||||
private Long status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
private String createByName;
|
||||
private String updateByName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.klp.mapper;
|
||||
|
||||
import com.klp.domain.WmsLeaveRequest;
|
||||
import com.klp.domain.vo.WmsLeaveRequestVo;
|
||||
import com.klp.common.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* 员工请假申请Mapper接口
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
public interface WmsLeaveRequestMapper extends BaseMapperPlus<WmsLeaveRequestMapper, WmsLeaveRequest, WmsLeaveRequestVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.klp.mapper;
|
||||
|
||||
import com.klp.domain.WmsMealReport;
|
||||
import com.klp.domain.vo.WmsMealReportVo;
|
||||
import com.klp.common.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* 部门报餐主Mapper接口
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
public interface WmsMealReportMapper extends BaseMapperPlus<WmsMealReportMapper, WmsMealReport, WmsMealReportVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.klp.service;
|
||||
|
||||
import com.klp.domain.WmsLeaveRequest;
|
||||
import com.klp.domain.vo.WmsLeaveRequestVo;
|
||||
import com.klp.domain.bo.WmsLeaveRequestBo;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 员工请假申请Service接口
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
public interface IWmsLeaveRequestService {
|
||||
|
||||
/**
|
||||
* 查询员工请假申请
|
||||
*/
|
||||
WmsLeaveRequestVo queryById(Long leaveId);
|
||||
|
||||
/**
|
||||
* 查询员工请假申请列表
|
||||
*/
|
||||
TableDataInfo<WmsLeaveRequestVo> queryPageList(WmsLeaveRequestBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询员工请假申请列表
|
||||
*/
|
||||
List<WmsLeaveRequestVo> queryList(WmsLeaveRequestBo bo);
|
||||
|
||||
/**
|
||||
* 新增员工请假申请
|
||||
*/
|
||||
Boolean insertByBo(WmsLeaveRequestBo bo);
|
||||
|
||||
/**
|
||||
* 修改员工请假申请
|
||||
*/
|
||||
Boolean updateByBo(WmsLeaveRequestBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除员工请假申请信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按请假类型统计
|
||||
*/
|
||||
List<Map<String, Object>> getLeaveTypeReport(WmsLeaveRequestBo bo);
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按部门统计
|
||||
*/
|
||||
List<Map<String, Object>> getLeaveDeptReport(WmsLeaveRequestBo bo);
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按月份统计
|
||||
*/
|
||||
List<Map<String, Object>> getLeaveMonthlyReport(WmsLeaveRequestBo bo);
|
||||
|
||||
/**
|
||||
* 根据请假人分组获取请假信息
|
||||
*/
|
||||
List<Map<String, Object>> getLeaveListGroupedByApplicant(WmsLeaveRequestBo bo);
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public interface IWmsMaterialCoilService {
|
||||
* - enterCoilNoDuplicate: 入场钢卷号是否重复 (true/false)
|
||||
* - currentCoilNoDuplicate: 当前钢卷号是否重复 (true/false)
|
||||
*/
|
||||
Map<String, Object> checkCoilNoDuplicate(String enterCoilNo, String currentCoilNo);
|
||||
Map<String, Object> checkCoilNoDuplicate(Long coilId, String enterCoilNo, String currentCoilNo);
|
||||
|
||||
/**
|
||||
* 根据入场钢卷号前缀查询最大的入场钢卷号
|
||||
@@ -137,5 +137,16 @@ public interface IWmsMaterialCoilService {
|
||||
* - prefix: 前缀值
|
||||
*/
|
||||
Map<String, Object> getMaxEnterCoilNoByPrefix(String enterCoilNoPrefix);
|
||||
|
||||
/**
|
||||
* 查询data_type=1时的重复钢卷分组
|
||||
* 将入场钢卷号重复的分为一组,将当前钢卷号重复的分为一组
|
||||
* 返回结构:
|
||||
* {
|
||||
* enterGroups: [ { enterCoilNo: "xxx", coils: [WmsMaterialCoilVo...] }, ...],
|
||||
* currentGroups: [ { currentCoilNo: "yyy", coils: [WmsMaterialCoilVo...] }, ...]
|
||||
* }
|
||||
*/
|
||||
Map<String, Object> getDuplicateCoilGroups();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.klp.service;
|
||||
|
||||
import com.klp.domain.WmsMealReport;
|
||||
import com.klp.domain.vo.WmsMealReportVo;
|
||||
import com.klp.domain.bo.WmsMealReportBo;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 部门报餐主Service接口
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
public interface IWmsMealReportService {
|
||||
|
||||
/**
|
||||
* 查询部门报餐主
|
||||
*/
|
||||
WmsMealReportVo queryById(Long reportId);
|
||||
|
||||
/**
|
||||
* 查询部门报餐主列表
|
||||
*/
|
||||
TableDataInfo<WmsMealReportVo> queryPageList(WmsMealReportBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询部门报餐主列表
|
||||
*/
|
||||
List<WmsMealReportVo> queryList(WmsMealReportBo bo);
|
||||
|
||||
/**
|
||||
* 新增部门报餐主
|
||||
*/
|
||||
Boolean insertByBo(WmsMealReportBo bo);
|
||||
|
||||
/**
|
||||
* 修改部门报餐主
|
||||
*/
|
||||
Boolean updateByBo(WmsMealReportBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除部门报餐主信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按餐别统计
|
||||
*/
|
||||
List<Map<String, Object>> getMealTypeReport(WmsMealReportBo bo);
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按部门统计
|
||||
*/
|
||||
List<Map<String, Object>> getMealDeptReport(WmsMealReportBo bo);
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按日期统计
|
||||
*/
|
||||
List<Map<String, Object>> getMealDateReport(WmsMealReportBo bo);
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.klp.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.klp.domain.bo.WmsLeaveRequestBo;
|
||||
import com.klp.domain.vo.WmsLeaveRequestVo;
|
||||
import com.klp.domain.WmsLeaveRequest;
|
||||
import com.klp.mapper.WmsLeaveRequestMapper;
|
||||
import com.klp.service.IWmsLeaveRequestService;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
/**
|
||||
* 员工请假申请Service业务层处理
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class WmsLeaveRequestServiceImpl implements IWmsLeaveRequestService {
|
||||
|
||||
private final WmsLeaveRequestMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询员工请假申请
|
||||
*/
|
||||
@Override
|
||||
public WmsLeaveRequestVo queryById(Long leaveId){
|
||||
return baseMapper.selectVoById(leaveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询员工请假申请列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<WmsLeaveRequestVo> queryPageList(WmsLeaveRequestBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<WmsLeaveRequest> lqw = buildQueryWrapper(bo);
|
||||
Page<WmsLeaveRequestVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询员工请假申请列表
|
||||
*/
|
||||
@Override
|
||||
public List<WmsLeaveRequestVo> queryList(WmsLeaveRequestBo bo) {
|
||||
LambdaQueryWrapper<WmsLeaveRequest> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<WmsLeaveRequest> buildQueryWrapper(WmsLeaveRequestBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<WmsLeaveRequest> lqw = Wrappers.lambdaQuery();
|
||||
|
||||
// 如果传入了ids,则按ids筛选,否则按其他条件筛选
|
||||
if (StringUtils.isNotBlank(bo.getLeaveIds())) {
|
||||
String[] idArray = bo.getLeaveIds().split(",");
|
||||
List<Long> idList = new ArrayList<>();
|
||||
for (String id : idArray) {
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
try {
|
||||
idList.add(Long.parseLong(id.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略无效的ID
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!idList.isEmpty()) {
|
||||
lqw.in(WmsLeaveRequest::getLeaveId, idList);
|
||||
}
|
||||
} else {
|
||||
// 正常的条件筛选
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getLeaveTitle()), WmsLeaveRequest::getLeaveTitle, bo.getLeaveTitle());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getLeaveType()), WmsLeaveRequest::getLeaveType, bo.getLeaveType());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getApplicantName()), WmsLeaveRequest::getApplicantName, bo.getApplicantName());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getApplicantDeptName()), WmsLeaveRequest::getApplicantDeptName, bo.getApplicantDeptName());
|
||||
|
||||
// 请假时间范围筛选:筛选出请假时间与查询时间范围有交集的记录
|
||||
// 条件:(start_time <= endTime AND end_time >= startTime)
|
||||
if (bo.getStartTime() != null && bo.getEndTime() != null) {
|
||||
lqw.le(WmsLeaveRequest::getStartTime, bo.getEndTime())
|
||||
.ge(WmsLeaveRequest::getEndTime, bo.getStartTime());
|
||||
} else if (bo.getStartTime() != null) {
|
||||
lqw.ge(WmsLeaveRequest::getEndTime, bo.getStartTime());
|
||||
} else if (bo.getEndTime() != null) {
|
||||
lqw.le(WmsLeaveRequest::getStartTime, bo.getEndTime());
|
||||
}
|
||||
|
||||
lqw.eq(bo.getStartTime() != null, WmsLeaveRequest::getStartTime, bo.getStartTime());
|
||||
lqw.eq(bo.getEndTime() != null, WmsLeaveRequest::getEndTime, bo.getEndTime());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getLeaveShift()), WmsLeaveRequest::getLeaveShift, bo.getLeaveShift());
|
||||
lqw.eq(bo.getLeaveDays() != null, WmsLeaveRequest::getLeaveDays, bo.getLeaveDays());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getLeaveReason()), WmsLeaveRequest::getLeaveReason, bo.getLeaveReason());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getAttachmentUrls()), WmsLeaveRequest::getAttachmentUrls, bo.getAttachmentUrls());
|
||||
}
|
||||
|
||||
// 按请假开始时间倒序排列
|
||||
lqw.orderByDesc(WmsLeaveRequest::getStartTime);
|
||||
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增员工请假申请
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(WmsLeaveRequestBo bo) {
|
||||
WmsLeaveRequest add = BeanUtil.toBean(bo, WmsLeaveRequest.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setLeaveId(add.getLeaveId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改员工请假申请
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(WmsLeaveRequestBo bo) {
|
||||
WmsLeaveRequest update = BeanUtil.toBean(bo, WmsLeaveRequest.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(WmsLeaveRequest entity){
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除员工请假申请
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按请假类型统计
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getLeaveTypeReport(WmsLeaveRequestBo bo) {
|
||||
QueryWrapper<WmsLeaveRequest> qw = new QueryWrapper<>();
|
||||
qw.select("leave_type as type",
|
||||
"COUNT(*) as count",
|
||||
"SUM(leave_days) as total_days")
|
||||
.eq(bo.getApplicantDeptName() != null, "applicant_dept_name", bo.getApplicantDeptName())
|
||||
.ge(bo.getStartTime() != null, "start_time", bo.getStartTime())
|
||||
.le(bo.getEndTime() != null, "end_time", bo.getEndTime())
|
||||
.eq("del_flag", 0)
|
||||
.groupBy("leave_type")
|
||||
.orderByDesc("total_days");
|
||||
return baseMapper.selectMaps(qw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按部门统计
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getLeaveDeptReport(WmsLeaveRequestBo bo) {
|
||||
QueryWrapper<WmsLeaveRequest> qw = new QueryWrapper<>();
|
||||
qw.select("applicant_dept_name as dept_name",
|
||||
"COUNT(*) as count",
|
||||
"SUM(leave_days) as total_days")
|
||||
.isNotNull("applicant_dept_name")
|
||||
.eq(StringUtils.isNotBlank(bo.getLeaveType()), "leave_type", bo.getLeaveType())
|
||||
.ge(bo.getStartTime() != null, "start_time", bo.getStartTime())
|
||||
.le(bo.getEndTime() != null, "end_time", bo.getEndTime())
|
||||
.eq("del_flag", 0)
|
||||
.groupBy("applicant_dept_name")
|
||||
.orderByDesc("total_days");
|
||||
return baseMapper.selectMaps(qw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请假统计报表 - 按月份统计
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getLeaveMonthlyReport(WmsLeaveRequestBo bo) {
|
||||
QueryWrapper<WmsLeaveRequest> qw = new QueryWrapper<>();
|
||||
qw.select("DATE_FORMAT(start_time, '%Y-%m') as month",
|
||||
"COUNT(*) as count",
|
||||
"SUM(leave_days) as total_days")
|
||||
.ge(bo.getStartTime() != null, "start_time", bo.getStartTime())
|
||||
.le(bo.getEndTime() != null, "end_time", bo.getEndTime())
|
||||
.eq(StringUtils.isNotBlank(bo.getLeaveType()), "leave_type", bo.getLeaveType())
|
||||
.eq(bo.getApplicantDeptName() != null, "applicant_dept_name", bo.getApplicantDeptName())
|
||||
.eq("del_flag", 0)
|
||||
.groupBy("month")
|
||||
.orderByAsc("month");
|
||||
return baseMapper.selectMaps(qw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据请假人分组获取请假信息
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getLeaveListGroupedByApplicant(WmsLeaveRequestBo bo) {
|
||||
QueryWrapper<WmsLeaveRequest> qw = new QueryWrapper<>();
|
||||
qw.select("applicant_name",
|
||||
"applicant_dept_name",
|
||||
"GROUP_CONCAT(CONCAT(leave_type, ':', DATE_FORMAT(start_time, '%Y-%m-%d %H:%i'), '~', DATE_FORMAT(end_time, '%Y-%m-%d %H:%i'), '(', leave_days, '天)') ORDER BY start_time SEPARATOR '; ') as leave_details",
|
||||
"SUM(leave_days) as total_days",
|
||||
"COUNT(*) as leave_count")
|
||||
.eq("del_flag", 0);
|
||||
|
||||
// 时间范围筛选
|
||||
if (bo.getStartTime() != null && bo.getEndTime() != null) {
|
||||
qw.le("start_time", bo.getEndTime())
|
||||
.ge("end_time", bo.getStartTime());
|
||||
} else if (bo.getStartTime() != null) {
|
||||
qw.ge("end_time", bo.getStartTime());
|
||||
} else if (bo.getEndTime() != null) {
|
||||
qw.le("start_time", bo.getEndTime());
|
||||
}
|
||||
|
||||
// 其他筛选条件
|
||||
qw.eq(StringUtils.isNotBlank(bo.getLeaveType()), "leave_type", bo.getLeaveType())
|
||||
.like(StringUtils.isNotBlank(bo.getApplicantName()), "applicant_name", bo.getApplicantName())
|
||||
.like(StringUtils.isNotBlank(bo.getApplicantDeptName()), "applicant_dept_name", bo.getApplicantDeptName())
|
||||
.groupBy("applicant_name", "applicant_dept_name")
|
||||
.orderByDesc("total_days");
|
||||
|
||||
return baseMapper.selectMaps(qw);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.klp.common.exception.ServiceException;
|
||||
import com.klp.common.helper.LoginHelper;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import com.klp.common.utils.spring.SpringUtils;
|
||||
@@ -341,12 +342,31 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
qw.like(StringUtils.isNotBlank(bo.getSupplierCoilNo()), "mc.supplier_coil_no", bo.getSupplierCoilNo());
|
||||
qw.eq(bo.getDataType() != null, "mc.data_type", bo.getDataType());
|
||||
qw.eq(bo.getMaterialType() != null, "mc.material_type", bo.getMaterialType());
|
||||
qw.eq(bo.getWarehouseId() != null, "mc.warehouse_id", bo.getWarehouseId());
|
||||
qw.eq(bo.getHasMergeSplit() != null, "mc.has_merge_split", bo.getHasMergeSplit());
|
||||
qw.eq(bo.getStatus() != null, "mc.status", bo.getStatus());
|
||||
qw.eq(StringUtils.isNotBlank(bo.getItemType()), "mc.item_type", bo.getItemType());
|
||||
qw.eq(StringUtils.isNotBlank(bo.getCreateBy()), "mc.create_by", bo.getCreateBy());
|
||||
qw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), "mc.update_by", bo.getUpdateBy());
|
||||
// 统一处理 warehouseId 与 warehouseIds:
|
||||
List<Long> warehouseIdList = new ArrayList<>();
|
||||
if (bo.getWarehouseId() != null) {
|
||||
warehouseIdList.add(bo.getWarehouseId());
|
||||
}
|
||||
if (StringUtils.isNotBlank(bo.getWarehouseIds())) {
|
||||
String[] warehouseIdArray = bo.getWarehouseIds().split(",");
|
||||
for (String warehouseIdStr : warehouseIdArray) {
|
||||
if (StringUtils.isNotBlank(warehouseIdStr)) {
|
||||
try {
|
||||
warehouseIdList.add(Long.parseLong(warehouseIdStr.trim()));
|
||||
} catch (NumberFormatException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!warehouseIdList.isEmpty()) {
|
||||
qw.in("mc.warehouse_id", warehouseIdList.stream().distinct().collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// 新增长度
|
||||
qw.eq(bo.getLength() != null, "mc.length", bo.getLength());
|
||||
// 如果actualWarehouseId不为空,则根据实际库区ID进行查询 如果为-1,则查询无库区的数据
|
||||
@@ -412,9 +432,9 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
}
|
||||
|
||||
// 使用 EXISTS 针对 selectType 的细粒度筛选(使用参数占位符防注入)
|
||||
if (hasSelectType) {
|
||||
qw.eq("mc.item_type", bo.getSelectType());
|
||||
if (hasAnyItemFilter) {
|
||||
if (hasSelectType && hasAnyItemFilter) {
|
||||
// 执行筛选逻辑(和上面完全一样)
|
||||
qw.eq("mc.item_type", bo.getSelectType());
|
||||
StringBuilder existsSql = new StringBuilder();
|
||||
List<Object> existsArgs = new ArrayList<>();
|
||||
if ("product".equals(bo.getSelectType())) {
|
||||
@@ -454,7 +474,6 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
qw.apply(existsSql.toString(), existsArgs.toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显式 itemId 条件:与 EXISTS 共存时,语义为交集
|
||||
if (CollectionUtils.isNotEmpty(explicitItemIds)) {
|
||||
@@ -540,6 +559,72 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
return qw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDuplicateCoilGroups() {
|
||||
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(WmsMaterialCoil::getDataType, 1);
|
||||
lqw.eq(WmsMaterialCoil::getDelFlag, 0);
|
||||
List<WmsMaterialCoil> all = baseMapper.selectList(lqw);
|
||||
|
||||
Map<String, List<WmsMaterialCoil>> enterGrouped = all.stream()
|
||||
.filter(e -> StringUtils.isNotBlank(e.getEnterCoilNo()))
|
||||
.collect(Collectors.groupingBy(WmsMaterialCoil::getEnterCoilNo));
|
||||
Map<String, List<WmsMaterialCoil>> currentGrouped = all.stream()
|
||||
.filter(e -> StringUtils.isNotBlank(e.getCurrentCoilNo()))
|
||||
.collect(Collectors.groupingBy(WmsMaterialCoil::getCurrentCoilNo));
|
||||
|
||||
List<Map<String, Object>> enterGroups = enterGrouped.entrySet().stream()
|
||||
.filter(en -> en.getValue() != null && en.getValue().size() > 1)
|
||||
.map(en -> {
|
||||
List<WmsMaterialCoilVo> vos = en.getValue().stream().map(this::toVoBasic).collect(Collectors.toList());
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("enterCoilNo", en.getKey());
|
||||
m.put("coils", vos);
|
||||
return m;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Map<String, Object>> currentGroups = currentGrouped.entrySet().stream()
|
||||
.filter(en -> en.getValue() != null && en.getValue().size() > 1)
|
||||
.map(en -> {
|
||||
List<WmsMaterialCoilVo> vos = en.getValue().stream().map(this::toVoBasic).collect(Collectors.toList());
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("currentCoilNo", en.getKey());
|
||||
m.put("coils", vos);
|
||||
return m;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 可选:批量填充关联对象信息
|
||||
List<WmsMaterialCoilVo> allVos = new ArrayList<>();
|
||||
for (Map<String, Object> g : enterGroups) {
|
||||
Object list = g.get("coils");
|
||||
if (list instanceof List) {
|
||||
allVos.addAll((List<WmsMaterialCoilVo>) list);
|
||||
}
|
||||
}
|
||||
for (Map<String, Object> g : currentGroups) {
|
||||
Object list = g.get("coils");
|
||||
if (list instanceof List) {
|
||||
allVos.addAll((List<WmsMaterialCoilVo>) list);
|
||||
}
|
||||
}
|
||||
if (!allVos.isEmpty()) {
|
||||
fillRelatedObjectsBatch(allVos);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("enterGroups", enterGroups);
|
||||
result.put("currentGroups", currentGroups);
|
||||
return result;
|
||||
}
|
||||
|
||||
private WmsMaterialCoilVo toVoBasic(WmsMaterialCoil e) {
|
||||
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
|
||||
BeanUtils.copyProperties(e, vo);
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 OR 连接的 LIKE 子句,使用 MyBatis-Plus apply 的 {index} 占位符并将参数加入 args。
|
||||
* 例如:column = "p.product_name", values = "A,B" -> 返回 "(p.product_name LIKE {0} OR p.product_name LIKE {1})"
|
||||
@@ -744,6 +829,10 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
// 2. 查找或创建stock
|
||||
findOrCreateStock(bo);
|
||||
|
||||
// 处理实际库区:-1 表示空置库,统一转 NULL
|
||||
if (bo.getActualWarehouseId() != null && bo.getActualWarehouseId() == -1) {
|
||||
bo.setActualWarehouseId(null);
|
||||
}
|
||||
// 3. 插入钢卷数据
|
||||
WmsMaterialCoil add = BeanUtil.toBean(bo, WmsMaterialCoil.class);
|
||||
if(bo.getDataType() != null && bo.getDataType() == 10){
|
||||
@@ -948,6 +1037,11 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
throw new RuntimeException("原钢卷不存在");
|
||||
}
|
||||
|
||||
// oldCoil 如果是历史卷也就是date_type=0 的时候就是历史钢卷
|
||||
if (oldCoil.getDataType() == 0) {
|
||||
throw new RuntimeException("原钢卷已被更新");
|
||||
}
|
||||
|
||||
// 若修改实际库位,先进行校验
|
||||
if (bo.getActualWarehouseId() != null) {
|
||||
Long ignoreOccupiedId = Objects.equals(bo.getActualWarehouseId(), oldCoil.getActualWarehouseId())
|
||||
@@ -2116,6 +2210,11 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
vo.setCreateTime(finalDate);
|
||||
}
|
||||
// 非1的情况,保持原有create_time不变
|
||||
|
||||
// 如果是dataType=0的历史数据,将实际库区设置为null
|
||||
if (vo.getDataType() != null && vo.getDataType() == 0) {
|
||||
vo.setActualWarehouseName(null);
|
||||
}
|
||||
});
|
||||
return wmsMaterialCoilExportVos;
|
||||
}
|
||||
@@ -2131,6 +2230,10 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
WmsMaterialCoilVo wmsMaterialCoilVo = queryById(coilId);
|
||||
Long oldActualWarehouseId = wmsMaterialCoilVo != null ? wmsMaterialCoilVo.getActualWarehouseId() : null;
|
||||
|
||||
// 如果当前钢卷为历史数据应该抛异常
|
||||
if (wmsMaterialCoilVo != null && wmsMaterialCoilVo.getDataType() == 0) {
|
||||
throw new RuntimeException("当前数据为历史数据,请勿发货!");
|
||||
}
|
||||
// 1. 更新钢卷为已发货,并记录发货时间,同时清空实际库区占用(改用Wrapper实现)
|
||||
int rows = 0;
|
||||
//获取当前调用接口的这个人的username
|
||||
@@ -2503,11 +2606,25 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
/**
|
||||
* 检查钢卷号是否重复
|
||||
* 根据入场钢卷号和当前钢卷号查询数据库,判断哪个钢卷号重复
|
||||
* 新增逻辑:修改历史记录时不检查重复
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> checkCoilNoDuplicate(String enterCoilNo, String currentCoilNo) {
|
||||
public Map<String, Object> checkCoilNoDuplicate(Long coilId, String enterCoilNo, String currentCoilNo) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 新增核心逻辑:先判断是否操作的是历史记录
|
||||
// 1. 如果coilId不为空(修改操作),先查询该钢卷的dataType
|
||||
if (coilId != null) {
|
||||
WmsMaterialCoil coil = baseMapper.selectById(coilId);
|
||||
// 2. 如果查询到钢卷且dataType!=1(说明是历史记录),直接返回无重复
|
||||
if (coil != null && coil.getDataType() != 1) {
|
||||
result.put("duplicateType", "none");
|
||||
result.put("enterCoilNoDuplicate", false);
|
||||
result.put("currentCoilNoDuplicate", false);
|
||||
return result; // 直接返回,不执行后续检查
|
||||
}
|
||||
}
|
||||
|
||||
boolean enterCoilNoDuplicate = false;
|
||||
boolean currentCoilNoDuplicate = false;
|
||||
|
||||
@@ -2515,7 +2632,12 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
if (StringUtils.isNotBlank(enterCoilNo)) {
|
||||
LambdaQueryWrapper<WmsMaterialCoil> enterWrapper = Wrappers.lambdaQuery();
|
||||
enterWrapper.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo)
|
||||
.eq(WmsMaterialCoil::getDelFlag, 0);
|
||||
.eq(WmsMaterialCoil::getDelFlag, 0)
|
||||
.eq(WmsMaterialCoil::getDataType, 1); // 过滤历史数据
|
||||
// 如果是修改操作,排除自身
|
||||
if (coilId != null) {
|
||||
enterWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
|
||||
}
|
||||
long enterCount = baseMapper.selectCount(enterWrapper);
|
||||
enterCoilNoDuplicate = enterCount > 0;
|
||||
}
|
||||
@@ -2524,7 +2646,13 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
if (StringUtils.isNotBlank(currentCoilNo)) {
|
||||
LambdaQueryWrapper<WmsMaterialCoil> currentWrapper = Wrappers.lambdaQuery();
|
||||
currentWrapper.eq(WmsMaterialCoil::getCurrentCoilNo, currentCoilNo)
|
||||
.eq(WmsMaterialCoil::getDelFlag, 0);
|
||||
.eq(WmsMaterialCoil::getDelFlag, 0)
|
||||
.eq(WmsMaterialCoil::getDataType, 1);
|
||||
// 如果是修改操作,排除自身
|
||||
if (coilId != null) {
|
||||
currentWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
|
||||
}
|
||||
|
||||
long currentCount = baseMapper.selectCount(currentWrapper);
|
||||
currentCoilNoDuplicate = currentCount > 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
package com.klp.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import com.klp.domain.vo.WmsMaterialCoilVo;
|
||||
import com.klp.system.service.impl.SysUserServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.klp.domain.bo.WmsMealReportBo;
|
||||
import com.klp.domain.vo.WmsMealReportVo;
|
||||
import com.klp.domain.WmsMealReport;
|
||||
import com.klp.mapper.WmsMealReportMapper;
|
||||
import com.klp.service.IWmsMealReportService;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
/**
|
||||
* 部门报餐主Service业务层处理
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-17
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class WmsMealReportServiceImpl implements IWmsMealReportService {
|
||||
|
||||
private final WmsMealReportMapper baseMapper;
|
||||
private final SysUserServiceImpl userService;
|
||||
|
||||
/**
|
||||
* 查询部门报餐主
|
||||
*/
|
||||
@Override
|
||||
public WmsMealReportVo queryById(Long reportId){
|
||||
return baseMapper.selectVoById(reportId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门报餐主列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<WmsMealReportVo> queryPageList(WmsMealReportBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<WmsMealReport> lqw = buildQueryWrapper(bo);
|
||||
Page<WmsMealReportVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
List<WmsMealReportVo> records = result.getRecords();
|
||||
if (records == null || records.isEmpty()) {
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
Set<String> userNames = records.stream()
|
||||
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getUpdateBy()))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<String, String> nickMap = Collections.emptyMap();
|
||||
if (!userNames.isEmpty()) {
|
||||
nickMap = userService.selectNickNameMapByUserNames(new ArrayList<>(userNames));
|
||||
}
|
||||
|
||||
// 单次遍历:填充创建/更新/发货人昵称,并构建物料/产品对象
|
||||
for (WmsMealReportVo vo : records) {
|
||||
if (!nickMap.isEmpty()) {
|
||||
if (StringUtils.isNotBlank(vo.getCreateBy())) {
|
||||
vo.setCreateByName(nickMap.getOrDefault(vo.getCreateBy(), vo.getCreateBy()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(vo.getUpdateBy())) {
|
||||
vo.setUpdateByName(nickMap.getOrDefault(vo.getUpdateBy(), vo.getUpdateBy()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门报餐主列表
|
||||
*/
|
||||
@Override
|
||||
public List<WmsMealReportVo> queryList(WmsMealReportBo bo) {
|
||||
LambdaQueryWrapper<WmsMealReport> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<WmsMealReport> buildQueryWrapper(WmsMealReportBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<WmsMealReport> lqw = Wrappers.lambdaQuery();
|
||||
// 修改日期查询逻辑:查询当天的所有记录(从当天00:00:00到23:59:59)
|
||||
if (bo.getReportDate() != null) {
|
||||
LocalDateTime startDate = bo.getReportDate().toInstant()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime()
|
||||
.with(LocalTime.MIN); // 当天的开始时间 00:00:00
|
||||
LocalDateTime endDate = bo.getReportDate().toInstant()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime()
|
||||
.with(LocalTime.MAX); // 当天的结束时间 23:59:59
|
||||
|
||||
lqw.between(WmsMealReport::getReportDate, Date.from(startDate.atZone(ZoneId.systemDefault()).toInstant()),
|
||||
Date.from(endDate.atZone(ZoneId.systemDefault()).toInstant()));
|
||||
}
|
||||
lqw.eq(bo.getMealType() != null, WmsMealReport::getMealType, bo.getMealType());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getDeptName()), WmsMealReport::getDeptName, bo.getDeptName());
|
||||
lqw.eq(bo.getTotalPeople() != null, WmsMealReport::getTotalPeople, bo.getTotalPeople());
|
||||
lqw.eq(bo.getDineInPeople() != null, WmsMealReport::getDineInPeople, bo.getDineInPeople());
|
||||
lqw.eq(bo.getTakeoutPeople() != null, WmsMealReport::getTakeoutPeople, bo.getTakeoutPeople());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getReportUserName()), WmsMealReport::getReportUserName, bo.getReportUserName());
|
||||
lqw.eq(bo.getStatus() != null, WmsMealReport::getStatus, bo.getStatus());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门报餐主
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(WmsMealReportBo bo) {
|
||||
WmsMealReport add = BeanUtil.toBean(bo, WmsMealReport.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setReportId(add.getReportId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门报餐主
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(WmsMealReportBo bo) {
|
||||
WmsMealReport update = BeanUtil.toBean(bo, WmsMealReport.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(WmsMealReport entity){
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除部门报餐主
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按餐别统计
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getMealTypeReport(WmsMealReportBo bo) {
|
||||
QueryWrapper<WmsMealReport> qw = new QueryWrapper<>();
|
||||
qw.select("CASE meal_type " +
|
||||
"WHEN 1 THEN '早餐' " +
|
||||
"WHEN 2 THEN '午餐' " +
|
||||
"WHEN 3 THEN '晚餐' " +
|
||||
"WHEN 4 THEN '夜宵' " +
|
||||
"ELSE '未知' END as meal_name",
|
||||
"SUM(total_people) as total_people",
|
||||
"SUM(dine_in_people) as total_dine_in",
|
||||
"SUM(takeout_people) as total_takeout",
|
||||
"COUNT(*) as report_count")
|
||||
.eq(StringUtils.isNotBlank(bo.getDeptName()), "dept_name", bo.getDeptName())
|
||||
.ge(bo.getReportDate() != null, "report_date", bo.getReportDate())
|
||||
.le(bo.getReportDate() != null, "report_date", bo.getReportDate())
|
||||
.eq("del_flag", 0)
|
||||
.eq(bo.getStatus() != null, "status", bo.getStatus())
|
||||
.groupBy("meal_type")
|
||||
.orderByAsc("meal_type");
|
||||
return baseMapper.selectMaps(qw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按部门统计
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getMealDeptReport(WmsMealReportBo bo) {
|
||||
QueryWrapper<WmsMealReport> qw = new QueryWrapper<>();
|
||||
qw.select("dept_name",
|
||||
"SUM(total_people) as total_people",
|
||||
"SUM(dine_in_people) as total_dine_in",
|
||||
"SUM(takeout_people) as total_takeout",
|
||||
"COUNT(*) as report_count",
|
||||
"AVG(total_people) as avg_people")
|
||||
.eq(bo.getMealType() != null, "meal_type", bo.getMealType())
|
||||
.ge(bo.getReportDate() != null, "report_date", bo.getReportDate())
|
||||
.le(bo.getReportDate() != null, "report_date", bo.getReportDate())
|
||||
.eq("del_flag", 0)
|
||||
.eq(bo.getStatus() != null, "status", bo.getStatus())
|
||||
.groupBy("dept_name")
|
||||
.orderByDesc("total_people");
|
||||
return baseMapper.selectMaps(qw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报餐统计报表 - 按日期统计
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> getMealDateReport(WmsMealReportBo bo) {
|
||||
QueryWrapper<WmsMealReport> qw = new QueryWrapper<>();
|
||||
qw.select("report_date",
|
||||
"SUM(total_people) as total_people",
|
||||
"SUM(dine_in_people) as total_dine_in",
|
||||
"SUM(takeout_people) as total_takeout",
|
||||
"COUNT(*) as report_count")
|
||||
.eq(StringUtils.isNotBlank(bo.getDeptName()), "dept_name", bo.getDeptName())
|
||||
.eq(bo.getMealType() != null, "meal_type", bo.getMealType())
|
||||
.ge(bo.getReportDate() != null, "report_date", bo.getReportDate())
|
||||
.le(bo.getReportDate() != null, "report_date", bo.getReportDate())
|
||||
.eq("del_flag", 0)
|
||||
.eq(bo.getStatus() != null, "status", bo.getStatus())
|
||||
.groupBy("report_date")
|
||||
.orderByAsc("report_date");
|
||||
return baseMapper.selectMaps(qw);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.klp.mapper.WmsLeaveRequestMapper">
|
||||
|
||||
<resultMap type="com.klp.domain.WmsLeaveRequest" id="WmsLeaveRequestResult">
|
||||
<result property="leaveId" column="leave_id"/>
|
||||
<result property="leaveTitle" column="leave_title"/>
|
||||
<result property="leaveType" column="leave_type"/>
|
||||
<result property="applicantName" column="applicant_name"/>
|
||||
<result property="applicantDeptName" column="applicant_dept_name"/>
|
||||
<result property="startTime" column="start_time"/>
|
||||
<result property="endTime" column="end_time"/>
|
||||
<result property="leaveShift" column="leave_shift"/>
|
||||
<result property="leaveDays" column="leave_days"/>
|
||||
<result property="leaveReason" column="leave_reason"/>
|
||||
<result property="attachmentUrls" column="attachment_urls"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
|
||||
</mapper>
|
||||
@@ -350,6 +350,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
END AS itemTypeDesc,
|
||||
-- 物品ID
|
||||
mc.item_id AS itemId,
|
||||
-- 数据类型
|
||||
mc.data_type AS dataType,
|
||||
-- 逻辑库区
|
||||
w.warehouse_name AS warehouseName,
|
||||
-- 实际库区
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.klp.mapper.WmsMealReportMapper">
|
||||
|
||||
<resultMap type="com.klp.domain.WmsMealReport" id="WmsMealReportResult">
|
||||
<result property="reportId" column="report_id"/>
|
||||
<result property="reportDate" column="report_date"/>
|
||||
<result property="mealType" column="meal_type"/>
|
||||
<result property="deptName" column="dept_name"/>
|
||||
<result property="totalPeople" column="total_people"/>
|
||||
<result property="dineInPeople" column="dine_in_people"/>
|
||||
<result property="takeoutPeople" column="takeout_people"/>
|
||||
<result property="reportUserName" column="report_user_name"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
|
||||
</mapper>
|
||||
12
pom.xml
12
pom.xml
@@ -384,11 +384,11 @@
|
||||
<artifactId>klp-erp</artifactId>
|
||||
<version>${klp-flowable-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klp</groupId>
|
||||
<artifactId>klp-hrm</artifactId>
|
||||
<version>${klp-flowable-plus.version}</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.klp</groupId>-->
|
||||
<!-- <artifactId>klp-hrm</artifactId>-->
|
||||
<!-- <version>${klp-flowable-plus.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.klp</groupId>
|
||||
<artifactId>klp-crm</artifactId>
|
||||
@@ -416,7 +416,7 @@
|
||||
<module>klp-ems</module>
|
||||
<module>klp-pocket</module>
|
||||
<module>klp-erp</module>
|
||||
<module>klp-hrm</module>
|
||||
<!-- <module>klp-hrm</module>-->
|
||||
<module>klp-crm</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
Reference in New Issue
Block a user