Files
klp-oa/klp-ui/src/views/wms/warehouse/record.vue
砂糖 1fa4c55869 feat(CoilSelector): 新增入场卷号字段并调整当前卷号显示
feat(customer): 新增客户相关配卷和财务信息查询接口

fix(base.vue): 修复发货单时间条件显示问题

refactor(CustomerEdit): 替换地址选择组件为普通输入框

feat(CoilSelector): 增加入场卷号查询条件并调整对话框宽度

style(OrderEdit): 调整客户名称和销售员选择框宽度

refactor(ChinaAreaSelect): 优化地址解析逻辑并支持空对象处理

feat(FileUpload/FileList): 新增文件预览功能组件

refactor(KLPService/CustomerSelect): 优化客户选择组件并支持自定义字段绑定

fix(AbnormalForm): 修复异常位置校验逻辑并保留当前卷号

feat(ContractTabs): 新增合同附件展示功能

refactor(warehouse/record): 重构操作记录统计展示方式

feat(contract): 集成客户选择组件并优化合同信息填充

refactor(order): 调整订单表单布局并集成合同信息

feat(FilePreview): 新增文件预览组件

feat(customer): 新增财务状态和发货配卷展示

refactor(CustomerOrder): 移除冗余代码并优化布局

feat(PlanDetailForm): 新增合同附件查看功能

feat(dict): 新增字典管理页面
2026-04-06 13:16:45 +08:00

566 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container" v-loading="loading">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="操作时间" prop="createTimeRange">
<el-date-picker clearable v-model="queryParams.createTimeRange" type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期">
</el-date-picker>
</el-form-item>
<el-form-item label="创建人" prop="createBy">
<el-input v-model="queryParams.createBy" placeholder="请输入创建人" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="出入库类型" prop="inOutType">
<el-select v-model="queryParams.inOutType" placeholder="请选择类型" clearable @keyup.enter.native="handleQuery">
<el-option label="入库" :value="1" />
<el-option label="出库" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="业务类型" prop="operationType">
<el-select v-model="queryParams.operationType" placeholder="请选择类型" clearable @keyup.enter.native="handleQuery">
<el-option label="收货" :value="1" />
<el-option label="加工" :value="2" />
<el-option label="调拨" :value="3" />
<el-option label="发货" :value="4" />
</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="mb10">
<el-col :span="8">
<el-card shadow="hover" :body-style="{ padding: '10px' }">
<div class="stat-card">
<div class="stat-icon stat-icon-primary">
<i class="el-icon-document"></i>
</div>
<div class="stat-content">
<div class="stat-title">操作记录总数</div>
<div class="stat-value">{{ stats.totalCount }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" :body-style="{ padding: '10px' }">
<div class="stat-card">
<div class="stat-icon stat-icon-success">
<i class="el-icon-s-finance"></i>
</div>
<div class="stat-content">
<div class="stat-title">总重量</div>
<div class="stat-value">{{ stats.totalWeight }} kg</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" :body-style="{ padding: '10px' }">
<div class="stat-card">
<div class="stat-icon stat-icon-warning">
<i class="el-icon-location"></i>
</div>
<div class="stat-content">
<div class="stat-title">涉及库位</div>
<div class="stat-value">{{ stats.warehouseCount }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 操作人汇总表格 -->
<el-card shadow="hover" :body-style="{ padding: '10px' }" class="mb10">
<div slot="header" class="clearfix" style="padding-bottom: 5px;">
<span>操作记录趋势</span>
</div>
<div id="trendChart" style="height: 250px;"></div>
</el-card>
<!-- 图表区域 -->
<el-row :gutter="10" class="mb10">
<el-col :span="12">
<el-card shadow="hover" :body-style="{ padding: '10px' }">
<el-table :data="userSummaryData" style="width: 100%" height="280px">
<el-table-column prop="createBy" label="操作人" width="180"></el-table-column>
<el-table-column prop="coilCount" label="操作卷数" width="120"></el-table-column>
<el-table-column prop="totalWeight" label="总重量(kg)">
<template slot-scope="scope">
{{ scope.row.totalWeight.toFixed(2) }}
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{ padding: '10px' }">
<div slot="header" class="clearfix" style="padding-bottom: 5px;">
<span>操作人分布饼图</span>
</div>
<div id="pieChart" style="height: 250px;"></div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{ padding: '10px' }">
<div slot="header" class="clearfix" style="padding-bottom: 5px;">
<span>操作人分布柱状图</span>
</div>
<div id="barChart" style="height: 250px;"></div>
</el-card>
</el-col>
</el-row>
<el-table height="calc(100vh - 400px)" :data="coilWarehouseOperationLogList"
@selection-change="handleSelectionChange">
<el-table-column prop="createTime" label="操作时间" width="180">
<template slot-scope="scope">
{{ scope.row.createTime }}
</template>
</el-table-column>
<el-table-column prop="createBy" label="操作人" width="180">
<template slot-scope="scope">
{{ scope.row.createBy }}
</template>
</el-table-column>
<el-table-column prop="operationType" label="操作类型" width="100">
<template slot-scope="scope">
<span v-if="scope.row.operationType === 1">收货</span>
<span v-else-if="scope.row.operationType === 2">加工</span>
<span v-else-if="scope.row.operationType === 3">调拨</span>
<span v-else-if="scope.row.operationType === 4">发货</span>
<!-- {{ scope.row.operationType === 1 ? '入库' : '出库' }} -->
</template>
</el-table-column>
<el-table-column prop="inOutType" label="出入库类型" width="100">
<template slot-scope="scope">
{{ scope.row.inOutType === 1 ? '入库' : '出库' }}
</template>
</el-table-column>
<el-table-column prop="coil.enterCoilNo" label="入场卷号" width="150">
<template slot-scope="scope">
{{ scope.row.coil && scope.row.coil.enterCoilNo ? scope.row.coil.enterCoilNo : '-' }}
</template>
</el-table-column>
<el-table-column prop="coil.currentCoilNo" label="当前卷号" width="150">
<template slot-scope="scope">
{{ scope.row.coil && scope.row.coil.currentCoilNo ? scope.row.coil.currentCoilNo : '-' }}
</template>
</el-table-column>
<el-table-column prop="coil.weight" label="重量">
<template slot-scope="scope">
{{ scope.row.coil && scope.row.coil.netWeight ? scope.row.coil.netWeight : '-' }}
</template>
</el-table-column>
<el-table-column prop="coil.specification" label="规格" width="180">
<template slot-scope="scope">
<ProductInfo v-if="scope.row.coil.itemType == 'product'" :product="scope.row.coil" />
<RawMaterialInfo v-else-if="scope.row.coil.itemType === 'raw_material'" :material="scope.row.coil" />
</template>
</el-table-column>
<el-table-column prop="warehouse.actualWarehouseName" label="库位">
<template slot-scope="scope">
{{ scope.row.warehouse && scope.row.warehouse.actualWarehouseName ? scope.row.warehouse.actualWarehouseName :
'-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button :loading="buttonLoading" type="danger" size="mini" 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="setPageData" />
</div>
</template>
<script>
import { listCoilWarehouseOperationLog, delCoilWarehouseOperationLog } from "@/api/wms/coilWarehouseOperationLog";
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
import dayjs from "dayjs";
import * as echarts from "echarts";
export default {
name: "CoilWarehouseOperationLog",
components: {
ProductInfo,
RawMaterialInfo,
},
data() {
// 筛选时间设置为本月时间
const createTimeRange = [
dayjs().startOf('month').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('month').format('YYYY-MM-DD HH:mm:ss')
];
return {
// 按钮loading
buttonLoading: false,
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 钢卷库区操作记录表格数据
coilWarehouseOperationLogList: [],
// 原始数据(用于前端分页)
allData: [],
// 统计指标
stats: {
totalCount: 0,
totalWeight: 0,
warehouseCount: 0
},
// 操作人汇总数据
userSummaryData: [],
// 图表实例
trendChart: null,
pieChart: null,
barChart: null,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 50,
actualWarehouseId: undefined,
createTimeRange: createTimeRange,
operationType: undefined,
inOutType: undefined,
createTime: undefined,
createBy: undefined,
}
};
},
created() {
this.getList();
},
mounted() {
// 初始化图表
this.initCharts();
},
beforeDestroy() {
// 销毁图表实例
if (this.trendChart) {
this.trendChart.dispose();
}
if (this.pieChart) {
this.pieChart.dispose();
}
if (this.barChart) {
this.barChart.dispose();
}
},
methods: {
/** 查询钢卷库区操作记录列表 */
getList() {
this.loading = true;
// 处理时间范围
if (this.queryParams.createTimeRange) {
this.queryParams.createStartTime = this.queryParams.createTimeRange[0];
this.queryParams.createEndTime = this.queryParams.createTimeRange[1];
}
// 移除分页参数,获取全部数据
const params = { ...this.queryParams, pageNum: 1, pageSize: 10000 };
listCoilWarehouseOperationLog(params).then(response => {
this.allData = response.rows;
this.total = response.total;
this.calculateStats();
this.setPageData();
this.loading = false;
});
},
/** 设置分页数据 */
setPageData() {
const start = (this.queryParams.pageNum - 1) * this.queryParams.pageSize;
const end = start + parseInt(this.queryParams.pageSize);
this.coilWarehouseOperationLogList = this.allData.slice(start, end);
},
/** 计算统计指标 */
calculateStats() {
// 计算操作记录总数
this.stats.totalCount = this.allData.length;
// 计算总重量
this.stats.totalWeight = this.allData.reduce((sum, item) => {
if (item.coil && item.coil.netWeight) {
return sum + parseFloat(item.coil.netWeight);
}
return sum;
}, 0).toFixed(2);
// 计算涉及的库位数量(去重)
const warehouseIds = new Set();
this.allData.forEach(item => {
if (item.warehouse && item.warehouse.actualWarehouseId) {
warehouseIds.add(item.warehouse.actualWarehouseId);
}
});
this.stats.warehouseCount = warehouseIds.size;
// 按操作人汇总数据
const userMap = {};
this.allData.forEach(item => {
const user = item.createBy || '未知';
if (!userMap[user]) {
userMap[user] = {
createBy: user,
coilCount: 0,
totalWeight: 0
};
}
userMap[user].coilCount++;
if (item.coil && item.coil.netWeight) {
userMap[user].totalWeight += parseFloat(item.coil.netWeight);
}
});
// 转换为数组并按操作卷数降序排序
this.userSummaryData = Object.values(userMap).sort((a, b) => b.coilCount - a.coilCount);
// 更新图表数据
this.updateCharts();
},
/** 初始化图表 */
initCharts() {
// 趋势图
this.trendChart = echarts.init(document.getElementById('trendChart'));
// 饼图
this.pieChart = echarts.init(document.getElementById('pieChart'));
// 柱状图
this.barChart = echarts.init(document.getElementById('barChart'));
},
/** 更新图表数据 */
updateCharts() {
this.updateTrendChart();
this.updatePieChart();
this.updateBarChart();
},
/** 更新趋势图 */
updateTrendChart() {
if (!this.trendChart) return;
// 按日期分组数据
const dateMap = {};
this.allData.forEach(item => {
const date = item.createTime.substring(0, 10);
if (!dateMap[date]) {
dateMap[date] = 0;
}
dateMap[date]++;
});
// 转换为数组并排序
const dateArr = Object.keys(dateMap).sort();
const dataArr = dateArr.map(date => dateMap[date]);
const option = {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: dateArr
},
yAxis: {
type: 'value'
},
series: [{
data: dataArr,
type: 'line',
smooth: true
}]
};
this.trendChart.setOption(option);
},
/** 更新饼图 */
updatePieChart() {
if (!this.pieChart) return;
// 按操作人分组数据
const userMap = {};
this.allData.forEach(item => {
const user = item.createBy || '未知';
if (!userMap[user]) {
userMap[user] = 0;
}
userMap[user]++;
});
// 转换为数组并按操作次数从大到小排序
const data = Object.entries(userMap)
.sort((a, b) => b[1] - a[1])
.map(([user, value]) => ({
name: user,
value: value
}));
const option = {
tooltip: {
trigger: 'item'
},
series: [{
type: 'pie',
data: data,
radius: '50%'
}]
};
this.pieChart.setOption(option);
},
/** 更新柱状图 */
updateBarChart() {
if (!this.barChart) return;
// 按操作人分组数据
const userMap = {};
this.allData.forEach(item => {
const user = item.createBy || '未知';
if (!userMap[user]) {
userMap[user] = 0;
}
userMap[user]++;
});
// 转换为数组并按操作次数从大到小排序
const sortedEntries = Object.entries(userMap).sort((a, b) => b[1] - a[1]);
const users = sortedEntries.map(([user]) => user);
const counts = sortedEntries.map(([, count]) => count);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
data: users
},
yAxis: {
type: 'value'
},
series: [{
data: counts,
type: 'bar'
}]
};
this.barChart.setOption(option);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.logId)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 删除按钮操作 */
handleDelete(row) {
const logIds = row.logId || this.ids;
this.$modal.confirm('是否确认删除钢卷库区操作记录编号为"' + logIds + '"的数据项?').then(() => {
this.loading = true;
return delCoilWarehouseOperationLog(logIds);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
}).finally(() => {
this.loading = false;
});
},
/** 导出按钮操作 */
handleExport() {
this.download('wms/coilWarehouseOperationLog/export', {
...this.queryParams
}, `coilWarehouseOperationLog_${new Date().getTime()}.xlsx`)
}
}
};
</script>
<style scoped>
.stat-card {
display: flex;
align-items: center;
padding: 5px 0;
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
font-size: 20px;
color: #fff;
}
.stat-icon-primary {
background-color: #409eff;
}
.stat-icon-success {
background-color: #67c23a;
}
.stat-icon-warning {
background-color: #e6a23c;
}
.stat-content {
flex: 1;
}
.stat-title {
font-size: 12px;
color: #606266;
margin-bottom: 3px;
}
.stat-value {
font-size: 20px;
font-weight: bold;
color: #303133;
}
.mb10 {
margin-bottom: 10px;
}
.mb20 {
margin-bottom: 20px;
}
</style>