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): 新增字典管理页面
566 lines
17 KiB
Vue
566 lines
17 KiB
Vue
<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>
|