feat: 新增物料管理看板功能及多项优化
新增物料管理看板功能,包含统计卡片和图表展示 优化物料选择器组件,支持分页和搜索功能 重构物料详情展示组件,支持动态加载数据 添加多个ECharts图表组件用于数据可视化 完善出入库和采购单相关功能,增加在途数量显示 修复若干界面显示问题和交互逻辑
This commit is contained in:
@@ -20,7 +20,11 @@
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="关联ID 主键" align="center" prop="relationId" v-if="true"/> -->
|
||||
<!-- <el-table-column label="产品ID 关联t_product.id" align="center" prop="productId" /> -->
|
||||
<el-table-column label="配料" align="center" prop="materialId" />
|
||||
<el-table-column label="配料" align="center" prop="materialId">
|
||||
<template #default="scope">
|
||||
<raw :data="scope.row" :materialId="scope.row.materialId" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所需数量" align="center" prop="materialNum" />
|
||||
<!-- <el-table-column label="配料排序 用于前端展示顺序" align="center" prop="sort" /> -->
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
@@ -42,7 +46,7 @@
|
||||
<el-input v-model="form.productId" placeholder="请输入产品ID 关联t_product.id" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="form.materialId" placeholder="请输入配料" />
|
||||
<raw-selector ref="rawSelector" v-model="form.materialId" placeholder="请选择配料" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所需数量" prop="materialNum">
|
||||
<el-input v-model="form.materialNum" placeholder="请输入所需数量" />
|
||||
@@ -67,7 +71,8 @@
|
||||
|
||||
<script setup name="ProductMaterialRelation">
|
||||
import { listProductMaterialRelation, getProductMaterialRelation, delProductMaterialRelation, addProductMaterialRelation, updateProductMaterialRelation } from "@/api/mat/productMaterialRelation";
|
||||
|
||||
import RawSelector from '@/components/RawSelector/index.vue'
|
||||
import Raw from '@/components/Renderer/Raw.vue'
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const productMaterialRelationList = ref([]);
|
||||
@@ -193,7 +198,7 @@ function submitForm() {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.relationId != null) {
|
||||
updateProductMaterialRelation(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess("修改成功");
|
||||
proxy.$modal.msgSuccess("修改成功,刷新后生效");
|
||||
open.value = false;
|
||||
getList();
|
||||
}).finally(() => {
|
||||
@@ -201,7 +206,7 @@ function submitForm() {
|
||||
});
|
||||
} else {
|
||||
addProductMaterialRelation(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess("新增成功");
|
||||
proxy.$modal.msgSuccess("新增成功,刷新后生效");
|
||||
open.value = false;
|
||||
getList();
|
||||
}).finally(() => {
|
||||
@@ -221,7 +226,7 @@ function handleDelete(row) {
|
||||
}).then(() => {
|
||||
loading.value = true;
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("删除成功");
|
||||
proxy.$modal.msgSuccess("删除成功,刷新后生效");
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
|
||||
135
gear-ui3/src/views/mat/components/charts/FactoryBarChart.vue
Normal file
135
gear-ui3/src/views/mat/components/charts/FactoryBarChart.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div ref="chartRef" class="echarts-container"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUpdated, onUnmounted, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 接收父组件传入的厂家数据
|
||||
const props = defineProps({
|
||||
factoryData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const chartRef = ref(null) // ECharts容器ref
|
||||
let myChart = null // ECharts实例
|
||||
|
||||
// 初始化/更新ECharts
|
||||
const initEcharts = () => {
|
||||
// 容器不存在则返回
|
||||
if (!chartRef.value) return
|
||||
// 初始化实例(单例模式)
|
||||
myChart = echarts.init(chartRef.value)
|
||||
// 设置配置项
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' }
|
||||
},
|
||||
legend: {
|
||||
data: ['材料个数', '库存总数'],
|
||||
top: 0
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: props.factoryData.map(item => item.factory),
|
||||
axisLabel: {
|
||||
rotate: 30, // 标签旋转,防止重叠
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
// 双Y轴:适配材料个数(小数值)和库存总数(大数值)
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '材料个数',
|
||||
min: 0,
|
||||
axisLabel: { formatter: '{value} 种' }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '库存总数',
|
||||
min: 0,
|
||||
axisLabel: { formatter: '{value} 件' }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '材料个数',
|
||||
type: 'bar',
|
||||
yAxisIndex: 0, // 对应第一个Y轴
|
||||
data: props.factoryData.map(item => item.materialCount),
|
||||
itemStyle: { color: '#409EFF' }
|
||||
},
|
||||
{
|
||||
name: '库存总数',
|
||||
type: 'bar',
|
||||
yAxisIndex: 1, // 对应第二个Y轴
|
||||
data: props.factoryData.map(item => item.stockTotal),
|
||||
itemStyle: { color: '#67C23A' }
|
||||
}
|
||||
],
|
||||
// 空数据提示
|
||||
noDataLoadingOption: {
|
||||
text: '暂无厂家数据',
|
||||
textStyle: { fontSize: 14 }
|
||||
}
|
||||
}
|
||||
// 设置配置项并渲染
|
||||
myChart.setOption(option, true)
|
||||
// 自适应窗口大小
|
||||
window.addEventListener('resize', resizeEcharts)
|
||||
}
|
||||
|
||||
// 图表自适应
|
||||
const resizeEcharts = () => {
|
||||
if (myChart) myChart.resize()
|
||||
}
|
||||
|
||||
// 销毁ECharts实例
|
||||
const destroyEcharts = () => {
|
||||
if (myChart) {
|
||||
myChart.dispose()
|
||||
myChart = null
|
||||
window.removeEventListener('resize', resizeEcharts)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
initEcharts()
|
||||
})
|
||||
|
||||
// 组件更新时(props变化)重绘
|
||||
onUpdated(() => {
|
||||
destroyEcharts()
|
||||
initEcharts()
|
||||
})
|
||||
|
||||
// 组件卸载时销毁实例
|
||||
onUnmounted(() => {
|
||||
destroyEcharts()
|
||||
})
|
||||
|
||||
// 深度监听数据源变化,重绘图表
|
||||
watch(() => props.factoryData, () => {
|
||||
destroyEcharts()
|
||||
initEcharts()
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.echarts-container {
|
||||
width: 100%;
|
||||
height: 280px; /* 固定图表高度,可根据需求调整 */
|
||||
}
|
||||
</style>
|
||||
112
gear-ui3/src/views/mat/components/charts/InOutCompareChart.vue
Normal file
112
gear-ui3/src/views/mat/components/charts/InOutCompareChart.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div ref="chartRef" class="echarts-container"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUpdated, onUnmounted, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 接收父组件传入的出入库对比数据
|
||||
const props = defineProps({
|
||||
inOutData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const chartRef = ref(null)
|
||||
let myChart = null
|
||||
|
||||
const initEcharts = () => {
|
||||
if (!chartRef.value) return
|
||||
myChart = echarts.init(chartRef.value)
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '日期:{b}<br/>{a}:{c} 件'
|
||||
},
|
||||
legend: {
|
||||
data: ['入库数量', '出库数量'],
|
||||
top: 0
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: props.inOutData.map(item => item.time),
|
||||
axisLabel: {
|
||||
rotate: 45,
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '操作数量(件)',
|
||||
min: 0
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '入库数量',
|
||||
type: 'line',
|
||||
data: props.inOutData.map(item => item.inNum),
|
||||
smooth: true,
|
||||
lineStyle: { width: 2, color: '#67C23A' },
|
||||
itemStyle: { color: '#67C23A' },
|
||||
symbol: 'circle',
|
||||
symbolSize: 6
|
||||
},
|
||||
{
|
||||
name: '出库数量',
|
||||
type: 'line',
|
||||
data: props.inOutData.map(item => item.outNum),
|
||||
smooth: true,
|
||||
lineStyle: { width: 2, color: '#E6A23C' },
|
||||
itemStyle: { color: '#E6A23C' },
|
||||
symbol: 'triangle',
|
||||
symbolSize: 6
|
||||
}
|
||||
],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无出入库数据',
|
||||
textStyle: { fontSize: 14 }
|
||||
}
|
||||
}
|
||||
myChart.setOption(option, true)
|
||||
window.addEventListener('resize', resizeEcharts)
|
||||
}
|
||||
|
||||
const resizeEcharts = () => {
|
||||
if (myChart) myChart.resize()
|
||||
}
|
||||
|
||||
const destroyEcharts = () => {
|
||||
if (myChart) {
|
||||
myChart.dispose()
|
||||
myChart = null
|
||||
window.removeEventListener('resize', resizeEcharts)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => initEcharts())
|
||||
onUpdated(() => {
|
||||
destroyEcharts()
|
||||
initEcharts()
|
||||
})
|
||||
onUnmounted(() => destroyEcharts())
|
||||
|
||||
watch(() => props.inOutData, () => {
|
||||
destroyEcharts()
|
||||
initEcharts()
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.echarts-container {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
}
|
||||
</style>
|
||||
103
gear-ui3/src/views/mat/components/charts/StockTrendChart.vue
Normal file
103
gear-ui3/src/views/mat/components/charts/StockTrendChart.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div ref="chartRef" class="echarts-container"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUpdated, onUnmounted, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 接收父组件传入的库存趋势数据
|
||||
const props = defineProps({
|
||||
trendData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const chartRef = ref(null)
|
||||
let myChart = null
|
||||
|
||||
const initEcharts = () => {
|
||||
if (!chartRef.value) return
|
||||
myChart = echarts.init(chartRef.value)
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '日期:{b}<br/>库存总量:{c} 件'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: props.trendData.map(item => item.time),
|
||||
axisLabel: {
|
||||
rotate: 45,
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '库存数量(件)',
|
||||
min: 0
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '库存总量',
|
||||
type: 'line',
|
||||
data: props.trendData.map(item => item.stock.toFixed(2)),
|
||||
smooth: true, // 平滑折线
|
||||
lineStyle: { width: 2 },
|
||||
itemStyle: { color: '#F56C6C' },
|
||||
areaStyle: {
|
||||
// 面积渐变背景
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(245,108,108,0.3)' },
|
||||
{ offset: 1, color: 'rgba(245,108,108,0)' }
|
||||
])
|
||||
}
|
||||
}
|
||||
],
|
||||
noDataLoadingOption: {
|
||||
text: '暂无库存趋势数据',
|
||||
textStyle: { fontSize: 14 }
|
||||
}
|
||||
}
|
||||
myChart.setOption(option, true)
|
||||
window.addEventListener('resize', resizeEcharts)
|
||||
}
|
||||
|
||||
const resizeEcharts = () => {
|
||||
if (myChart) myChart.resize()
|
||||
}
|
||||
|
||||
const destroyEcharts = () => {
|
||||
if (myChart) {
|
||||
myChart.dispose()
|
||||
myChart = null
|
||||
window.removeEventListener('resize', resizeEcharts)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => initEcharts())
|
||||
onUpdated(() => {
|
||||
destroyEcharts()
|
||||
initEcharts()
|
||||
})
|
||||
onUnmounted(() => destroyEcharts())
|
||||
|
||||
watch(() => props.trendData, () => {
|
||||
destroyEcharts()
|
||||
initEcharts()
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.echarts-container {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
}
|
||||
</style>
|
||||
@@ -13,19 +13,6 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- <el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row> -->
|
||||
|
||||
<el-table v-loading="loading" :data="purchaseInDetailList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="入库明细ID 主键" align="center" prop="detailId" v-if="false" />
|
||||
@@ -283,4 +270,8 @@ watch(() => props.purchaseId, (newVal, oldVal) => {
|
||||
getList();
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
defineExpose({
|
||||
getList
|
||||
})
|
||||
</script>
|
||||
|
||||
197
gear-ui3/src/views/mat/components/price.vue
Normal file
197
gear-ui3/src/views/mat/components/price.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div v-if="!materialId" style="height: 400px; display: flex; align-items: center; justify-content: center;">
|
||||
<el-empty description="请选择配料"></el-empty>
|
||||
</div>
|
||||
<!-- 图表容器:必须设置宽高,否则ECharts无法渲染 -->
|
||||
<div v-else-if="priceHistoryList.length > 0" ref="chartRef" class="price-chart-container"></div>
|
||||
<div v-else style="height: 400px; display: flex; align-items: center; justify-content: center;">
|
||||
<el-empty description="暂无价格历史记录"></el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Price">
|
||||
import * as echarts from 'echarts';
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue';
|
||||
import { listMatPriceHistory } from "@/api/mat/matPriceHistory";
|
||||
|
||||
// 接收父组件传参:物料ID
|
||||
const props = defineProps({
|
||||
materialId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 价格历史列表
|
||||
const priceHistoryList = ref([]);
|
||||
// ECharts实例引用(核心:用于实例管理和自适应)
|
||||
const chartRef = ref(null);
|
||||
// 存储ECharts实例,方便后续销毁和更新
|
||||
let myChart = null;
|
||||
// 窗口大小监听标识(用于销毁监听)
|
||||
let resizeObserver = null;
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const getListChart = () => {
|
||||
loading.value = true;
|
||||
|
||||
listMatPriceHistory({
|
||||
materialId: props.materialId,
|
||||
pageSize: 1000,
|
||||
pageNum: 1
|
||||
}).then(response => {
|
||||
// 倒序显示,最新数据在顶部
|
||||
priceHistoryList.value = (response.rows || []).reverse();
|
||||
// 关键:等待Vue异步更新DOM,确保图表容器已渲染,再初始化图表
|
||||
nextTick(() => {
|
||||
initECharts();
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
// 初始化ECharts图表
|
||||
const initECharts = () => {
|
||||
// 容器不存在,直接返回
|
||||
if (!chartRef.value) return;
|
||||
// 先销毁旧实例,避免叠加
|
||||
destroyChart();
|
||||
// 初始化新实例
|
||||
myChart = echarts.init(chartRef.value);
|
||||
// 解析图表数据
|
||||
const chartData = parseChartData();
|
||||
// 设置图表配置项
|
||||
const option = {
|
||||
// 标题:显示物料名称+规格,居中
|
||||
title: {
|
||||
text: `历史平均价格`,
|
||||
left: 'center',
|
||||
textStyle: { fontSize: 16 }
|
||||
},
|
||||
// 提示框:鼠标悬浮显示价格,格式化保留2位小数
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '价格:<b>{c} 元</b>',
|
||||
axisPointer: { type: 'line', lineStyle: { type: 'dashed' } }
|
||||
},
|
||||
// 图例:单系列可隐藏,多系列需配置
|
||||
legend: { data: ['单价'], left: 'left' },
|
||||
// 网格:防止坐标轴标签超出容器
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
// X轴:类目轴,使用historyId作为标识
|
||||
xAxis: {
|
||||
// 隐藏x轴
|
||||
show: false,
|
||||
type: 'category',
|
||||
data: chartData.xData,
|
||||
// X轴标签旋转,防止重叠
|
||||
axisLabel: { rotate: 30 },
|
||||
// 隐藏X轴刻度线
|
||||
axisTick: { show: false }
|
||||
},
|
||||
// Y轴:数值轴,价格保留2位小数,单位元
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '单价(元)',
|
||||
min: 0,
|
||||
// 格式化Y轴标签,保留2位小数
|
||||
axisLabel: { formatter: '{value} ' },
|
||||
// 保留2位小数的刻度
|
||||
splitNumber: 5,
|
||||
precision: 2
|
||||
},
|
||||
// 系列:折线图,核心价格趋势
|
||||
series: [
|
||||
{
|
||||
name: '单价',
|
||||
type: 'line',
|
||||
data: chartData.yData,
|
||||
// 折线平滑
|
||||
smooth: true,
|
||||
// 标记点:显示每个价格节点
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
// 标记点悬浮放大
|
||||
emphasis: { symbolSize: 8 },
|
||||
// 区域填充:增加视觉效果
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(66, 165, 245, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(66, 165, 245, 0)' }
|
||||
])
|
||||
},
|
||||
// 折线颜色
|
||||
lineStyle: { color: '#42a5f5', width: 2 }
|
||||
}
|
||||
]
|
||||
};
|
||||
// 设置配置项并渲染
|
||||
myChart.setOption(option);
|
||||
// 绑定窗口大小自适应事件(仅绑定一次)
|
||||
if (!resizeObserver) {
|
||||
resizeObserver = () => { myChart && myChart.resize(); };
|
||||
window.addEventListener('resize', resizeObserver);
|
||||
}
|
||||
};
|
||||
|
||||
// 解析接口数据为ECharts所需格式
|
||||
const parseChartData = () => {
|
||||
const list = priceHistoryList.value || [];
|
||||
// 物料名称和规格(取第一条即可,同物料ID下一致)
|
||||
const materialName = list[0]?.materialName || '物料';
|
||||
const spec = list[0]?.spec || '';
|
||||
// X轴数据:historyId(唯一标识),Y轴数据:价格(转数值)
|
||||
const xData = list.map(item => item.historyId);
|
||||
const yData = list.map(item => Number(item.price) || 0);
|
||||
return { materialName, spec, xData, yData };
|
||||
};
|
||||
|
||||
// 销毁ECharts实例
|
||||
const destroyChart = () => {
|
||||
if (myChart && echarts.getInstanceByDom(chartRef.value)) {
|
||||
myChart.dispose();
|
||||
myChart = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听物料ID变化,重新请求数据
|
||||
watch(() => props.materialId, (newVal, oldVal) => {
|
||||
if (newVal && newVal !== oldVal) {
|
||||
getListChart();
|
||||
} else if (!newVal) {
|
||||
// 清空物料ID时,重置列表和销毁图表
|
||||
priceHistoryList.value = [];
|
||||
destroyChart();
|
||||
}
|
||||
}, { immediate: true }); // 立即执行:初始加载若有materialId则直接请求
|
||||
|
||||
// 初始化:若初始有物料ID,直接请求数据
|
||||
onMounted(() => {
|
||||
if (props.materialId) {
|
||||
getListChart();
|
||||
}
|
||||
});
|
||||
|
||||
// 销毁:移除监听、销毁图表实例,防止内存泄漏
|
||||
onUnmounted(() => {
|
||||
// destroyChart();
|
||||
if (resizeObserver) {
|
||||
window.removeEventListener('resize', resizeObserver);
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
getListChart
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 图表容器样式:设置固定高度,宽度100%自适应父容器 */
|
||||
.price-chart-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
min-width: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,312 @@
|
||||
<template>
|
||||
<div class="stock-dashboard-container" style="padding: 20px;">
|
||||
<!-- 加载状态 -->
|
||||
<el-loading v-loading="loading" text="数据加载中...">
|
||||
<!-- 第一行:4个指标卡 -->
|
||||
<el-row :gutter="20" mb="20">
|
||||
<el-col :span="6" v-for="item in statCards" :key="item.key">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<el-statistic
|
||||
:title="item.title"
|
||||
:value="item.value"
|
||||
:precision="item.precision || 2"
|
||||
:suffix="item.suffix"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二行:3个图表组件 -->
|
||||
<el-row :gutter="20" mb="20" style="margin-top: 10px;">
|
||||
<!-- 厂家汇总柱状图 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" title="按厂家汇总材料数/库存数">
|
||||
<FactoryBarChart :factory-data="factoryBarData" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 库存总量时间趋势折线图 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" title="库存总量随时间变化趋势">
|
||||
<StockTrendChart :trend-data="stockTrendData" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 出入库数量对比折线图 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" title="出入库数量按时间对比">
|
||||
<InOutCompareChart :in-out-data="inOutCompareData" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第三行:材料原始数据表格(带分页) -->
|
||||
<el-row style="margin-top: 10px;">
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover" title="材料原始数据">
|
||||
<el-table
|
||||
:data="paginationMaterialList"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%; margin-bottom: 20px;"
|
||||
empty-text="暂无材料数据"
|
||||
>
|
||||
<el-table-column prop="materialName" label="材料名称" align="center" />
|
||||
<el-table-column prop="spec" label="规格" align="center" />
|
||||
<el-table-column prop="model" label="型号" align="center" />
|
||||
<el-table-column prop="factory" label="生产厂家" align="center" />
|
||||
<el-table-column prop="unit" label="单位" align="center" />
|
||||
<el-table-column
|
||||
prop="currentStock"
|
||||
label="当前库存"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column prop="inTransitNum" label="在途数" align="center" />
|
||||
<!-- <el-table-column prop="planNum" label="计划数" align="center" /> -->
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.pageNum"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
@size-change="getMaterialList"
|
||||
@current-change="getMaterialList"
|
||||
style="text-align: right;"
|
||||
/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 库存看板
|
||||
import * as echarts from 'echarts';
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import dayjs from 'dayjs'
|
||||
// 导入接口
|
||||
import { listPurchaseInDetail } from "@/api/mat/purchaseInDetail";
|
||||
import { listMaterialOut } from "@/api/mat/materialOut";
|
||||
import { listPurchase } from "@/api/mat/purchase";
|
||||
import { listMaterial } from "@/api/mat/material";
|
||||
// 导入封装的图表组件
|
||||
import FactoryBarChart from '../components/charts/FactoryBarChart.vue'
|
||||
import StockTrendChart from '../components/charts/StockTrendChart.vue'
|
||||
import InOutCompareChart from '../components/charts/InOutCompareChart.vue'
|
||||
|
||||
const purchaseList = ref([]);
|
||||
const materialList = ref([]);
|
||||
const purchaseInDetailList = ref([]);
|
||||
const materialOutList = ref([]);
|
||||
</script>
|
||||
// 响应式数据:接口返回列表
|
||||
const materialList = ref([]) // 材料列表
|
||||
const purchaseInDetailList = ref([]) // 入库列表
|
||||
const materialOutList = ref([]) // 出库列表
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
// 分页参数(若依接口标准分页)
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
const total = ref(0) // 材料总条数
|
||||
|
||||
// 格式化数值:保留2位小数,处理字符串转数字
|
||||
const formatNumber = (row, column) => {
|
||||
return Number(row[column.prop] || 0).toFixed(2)
|
||||
}
|
||||
|
||||
// 分页后的材料列表(计算属性)
|
||||
const paginationMaterialList = computed(() => {
|
||||
const start = (queryParams.pageNum - 1) * queryParams.pageSize
|
||||
const end = start + queryParams.pageSize
|
||||
return materialList.value.slice(start, end)
|
||||
})
|
||||
|
||||
// 指标卡数据:库存总量、材料类型数、库存操作数、库存变化量
|
||||
const statCards = ref([
|
||||
{ title: '库存总量', value: 0, suffix: '件', precision: 2, key: 'totalStock' },
|
||||
{ title: '材料类型数量', value: 0, key: 'materialTypeCount', precision: 0 },
|
||||
{ title: '库存操作数', value: 0, key: 'stockOperateCount', precision: 0 },
|
||||
{ title: '库存变化量', value: 0, suffix: '件', precision: 2, key: 'stockChange' }
|
||||
])
|
||||
|
||||
// 图表数据源(供子组件使用)
|
||||
const factoryBarData = ref([]) // 厂家汇总柱状图数据
|
||||
const stockTrendData = ref([]) // 库存趋势折线图数据
|
||||
const inOutCompareData = ref([]) // 出入库对比折线图数据
|
||||
|
||||
// 1. 获取材料列表(带分页)
|
||||
const getMaterialList = async () => {
|
||||
try {
|
||||
const res = await listMaterial(queryParams) // 传入分页参数
|
||||
materialList.value = res.rows || []
|
||||
total.value = res.total || 0 // 若依接口返回total总条数
|
||||
} catch (err) {
|
||||
console.error('获取材料列表失败:', err)
|
||||
materialList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 获取入库+出库列表(无需分页,取全部数据做统计)
|
||||
const getInOutList = async () => {
|
||||
try {
|
||||
// 并行请求,提高效率
|
||||
const [inRes, outRes] = await Promise.all([
|
||||
listPurchaseInDetail(),
|
||||
listMaterialOut()
|
||||
])
|
||||
purchaseInDetailList.value = inRes.rows || []
|
||||
materialOutList.value = outRes.rows || []
|
||||
} catch (err) {
|
||||
console.error('获取出入库列表失败:', err)
|
||||
purchaseInDetailList.value = []
|
||||
materialOutList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 计算指标卡数值
|
||||
const calcStatCards = () => {
|
||||
// 库存总量:所有材料当前库存求和
|
||||
const totalStock = materialList.value.reduce((sum, item) => {
|
||||
return sum + Number(item.currentStock || 0)
|
||||
}, 0)
|
||||
// 材料类型数量:去重(按materialId)
|
||||
const materialTypeCount = new Set(materialList.value.map(item => item.materialId)).size
|
||||
// 库存操作数:入库次数+出库次数
|
||||
const stockOperateCount = purchaseInDetailList.value.length + materialOutList.value.length
|
||||
// 库存变化量:总入库数 - 总出库数
|
||||
const totalInNum = purchaseInDetailList.value.reduce((sum, item) => sum + Number(item.inNum || 0), 0)
|
||||
const totalOutNum = materialOutList.value.reduce((sum, item) => sum + Number(item.outNum || 0), 0)
|
||||
const stockChange = totalInNum - totalOutNum
|
||||
|
||||
// 更新指标卡
|
||||
statCards.value = [
|
||||
{ title: '库存总量', value: totalStock, suffix: '件', precision: 2, key: 'totalStock' },
|
||||
{ title: '材料类型数量', value: materialTypeCount, key: 'materialTypeCount', precision: 0 },
|
||||
{ title: '库存操作数', value: stockOperateCount, key: 'stockOperateCount', precision: 0 },
|
||||
{ title: '库存变化量', value: stockChange, suffix: '件', precision: 2, key: 'stockChange' }
|
||||
]
|
||||
}
|
||||
|
||||
// 4. 转换厂家汇总柱状图数据:按厂家分组,统计材料数+库存数
|
||||
const transformFactoryBarData = () => {
|
||||
const factoryMap = new Map()
|
||||
// 按厂家分组
|
||||
materialList.value.forEach(item => {
|
||||
const factory = item.factory || '未知厂家'
|
||||
if (!factoryMap.has(factory)) {
|
||||
factoryMap.set(factory, {
|
||||
materialCount: 0, // 材料个数(去重后)
|
||||
stockTotal: 0, // 库存总数
|
||||
materialIds: new Set() // 去重材料ID
|
||||
})
|
||||
}
|
||||
const factoryItem = factoryMap.get(factory)
|
||||
factoryItem.materialIds.add(item.materialId)
|
||||
factoryItem.materialCount = factoryItem.materialIds.size
|
||||
factoryItem.stockTotal += Number(item.currentStock || 0)
|
||||
})
|
||||
// 转换为数组供图表使用
|
||||
factoryBarData.value = Array.from(factoryMap).map(([factory, data]) => ({
|
||||
factory,
|
||||
materialCount: data.materialCount,
|
||||
stockTotal: data.stockTotal.toFixed(2)
|
||||
}))
|
||||
}
|
||||
|
||||
// 5. 转换库存趋势+出入库对比图表数据(按时间分组)
|
||||
const transformTimeTrendData = () => {
|
||||
// 整合所有时间操作记录:入库+出库,统一格式
|
||||
const allOperate = [
|
||||
...purchaseInDetailList.value.map(item => ({
|
||||
time: dayjs(item.inTime).format('YYYY-MM-DD'),
|
||||
type: 'in',
|
||||
num: Number(item.inNum || 0)
|
||||
})),
|
||||
...materialOutList.value.map(item => ({
|
||||
time: dayjs(item.outTime).format('YYYY-MM-DD'),
|
||||
type: 'out',
|
||||
num: Number(item.outNum || 0)
|
||||
}))
|
||||
]
|
||||
if (allOperate.length === 0) {
|
||||
stockTrendData.value = []
|
||||
inOutCompareData.value = []
|
||||
return
|
||||
}
|
||||
|
||||
// 按时间排序,获取所有唯一时间(升序)
|
||||
const timeSet = new Set(allOperate.map(item => item.time))
|
||||
const timeList = Array.from(timeSet).sort((a, b) => dayjs(a) - dayjs(b))
|
||||
|
||||
// 初始化时间分组数据:统计每日入库/出库数
|
||||
const timeDataMap = {}
|
||||
timeList.forEach(time => {
|
||||
timeDataMap[time] = { in: 0, out: 0 }
|
||||
})
|
||||
allOperate.forEach(item => {
|
||||
timeDataMap[item.time][item.type] += item.num
|
||||
})
|
||||
|
||||
// 转换出入库对比数据
|
||||
inOutCompareData.value = timeList.map(time => ({
|
||||
time,
|
||||
inNum: timeDataMap[time].in,
|
||||
outNum: timeDataMap[time].out
|
||||
}))
|
||||
|
||||
// 转换库存趋势数据:累计库存 = 初始库存 + 累计入库 - 累计出库
|
||||
// 初始库存:当前总库存 - (总入库 - 总出库),保证趋势图最后一个点匹配当前库存
|
||||
const totalStockNow = materialList.value.reduce((sum, item) => sum + Number(item.currentStock || 0), 0)
|
||||
const totalInAll = purchaseInDetailList.value.reduce((sum, item) => sum + Number(item.inNum || 0), 0)
|
||||
const totalOutAll = materialOutList.value.reduce((sum, item) => sum + Number(item.outNum || 0), 0)
|
||||
const initStock = totalStockNow - (totalInAll - totalOutAll)
|
||||
|
||||
let cumulativeIn = 0 // 累计入库
|
||||
let cumulativeOut = 0 // 累计出库
|
||||
stockTrendData.value = timeList.map(time => {
|
||||
cumulativeIn += timeDataMap[time].in
|
||||
cumulativeOut += timeDataMap[time].out
|
||||
const currentStock = initStock + cumulativeIn - cumulativeOut
|
||||
return {
|
||||
time,
|
||||
stock: currentStock < 0 ? 0 : currentStock // 库存不能为负
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 核心:加载所有数据并处理转换
|
||||
const loadAllData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 并行请求所有数据
|
||||
await Promise.all([
|
||||
getMaterialList(),
|
||||
getInOutList()
|
||||
])
|
||||
// 数据转换:指标卡 + 所有图表
|
||||
calcStatCards()
|
||||
transformFactoryBarData()
|
||||
transformTimeTrendData()
|
||||
} catch (err) {
|
||||
console.error('数据加载失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 页面挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadAllData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* 图表容器高度由子组件控制,父组件仅做布局 */
|
||||
.el-card {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="queryParams.materialId" placeholder="请输入配料" clearable @keyup.enter="handleQuery" />
|
||||
<raw-selector ref="rawSelector" v-model="queryParams.materialId" placeholder="请选择配料" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人" prop="operator">
|
||||
<el-input v-model="queryParams.operator" placeholder="请输入入库操作人" clearable @keyup.enter="handleQuery" />
|
||||
@@ -73,7 +73,7 @@
|
||||
<el-input v-model="form.purchaseId" placeholder="请输入采购单ID 关联t_purchase.id" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="form.materialId" placeholder="请输入配料" />
|
||||
<raw-selector ref="rawSelector" v-model="form.materialId" placeholder="请选择配料" />
|
||||
</el-form-item>
|
||||
<el-form-item label="入库数量" prop="inNum">
|
||||
<el-input v-model="form.inNum" placeholder="请输入入库数量" />
|
||||
@@ -109,6 +109,7 @@
|
||||
<script setup name="PurchaseInDetail">
|
||||
import { listPurchaseInDetail, getPurchaseInDetail, delPurchaseInDetail, addPurchaseInDetail, updatePurchaseInDetail } from "@/api/mat/purchaseInDetail";
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import RawSelector from '@/components/RawSelector/index.vue'
|
||||
import Raw from '@/components/Renderer/Raw.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<el-input v-model="queryParams.outNo" placeholder="请输入出库单号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="queryParams.materialId" placeholder="请输入配料" clearable @keyup.enter="handleQuery" />
|
||||
<raw-selector ref="rawSelector" v-model="queryParams.materialId" placeholder="请选择配料" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="操作人" prop="operator">
|
||||
<el-input v-model="queryParams.operator" placeholder="请输入出库操作人" clearable @keyup.enter="handleQuery" />
|
||||
@@ -75,7 +75,7 @@
|
||||
<el-input v-model="form.outNo" placeholder="请输入出库单号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="form.materialId" placeholder="请输入配料" />
|
||||
<raw-selector ref="rawSelector" v-model="form.materialId" placeholder="请选择配料" />
|
||||
</el-form-item>
|
||||
<el-form-item label="出库数量" prop="outNum">
|
||||
<el-input v-model="form.outNum" placeholder="请输入出库数量" />
|
||||
@@ -109,6 +109,7 @@
|
||||
import { listMaterialOut, getMaterialOut, delMaterialOut, addMaterialOut, updateMaterialOut } from "@/api/mat/materialOut";
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { computed } from "vue";
|
||||
import RawSelector from '@/components/RawSelector/index.vue'
|
||||
import Raw from '@/components/Renderer/Raw.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="app-container" ref="appContainer">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="产品名称" prop="productName">
|
||||
<el-input
|
||||
v-model="queryParams.productName"
|
||||
placeholder="请输入产品名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品规格" prop="spec">
|
||||
<el-input
|
||||
v-model="queryParams.spec"
|
||||
placeholder="请输入产品规格"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.spec" placeholder="请输入产品规格" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品型号" prop="model">
|
||||
<el-input
|
||||
v-model="queryParams.model"
|
||||
placeholder="请输入产品型号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
<el-input v-model="queryParams.model" placeholder="请输入产品型号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
@@ -33,66 +18,40 @@
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Edit"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
>修改</el-button>
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
>删除</el-button>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
>导出</el-button>
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="productList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" :data="productList" @selection-change="handleSelectionChange"
|
||||
@row-click="handleRowClick">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="产品ID 主键" align="center" prop="productId" v-if="false"/>
|
||||
<el-table-column label="产品ID 主键" align="center" prop="productId" v-if="false" />
|
||||
<el-table-column label="产品名称" align="center" prop="productName" />
|
||||
<el-table-column label="产品规格" align="center" prop="spec" />
|
||||
<el-table-column label="产品型号" align="center" prop="model" />
|
||||
<el-table-column label="产品单价" align="center" prop="unitPrice" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Plus" @click="handleBom(scope.row)">配方</el-button>
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Plus" @click="handleBom(scope.row)">配方</el-button>
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 添加或修改产品基础信息对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
||||
@@ -121,15 +80,35 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="产品配方" v-model="bomOpen" width="500px" append-to-body>
|
||||
<el-dialog title="产品配方" v-model="bomOpen" width="800px" append-to-body>
|
||||
<bom :productId="currentProductId" @close="bomOpen = false" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 将这个表格改为始终吸底,且外层容器可以拖拽调节高度 -->
|
||||
<!-- <StickyDragContainer v-if="currentProduct.productId" :parent-ref="appContainer"> -->
|
||||
<div v-if="currentProduct.productId">
|
||||
<h3>产品配料</h3>
|
||||
<!-- 插槽内容:原有配料表格,无需任何修改 -->
|
||||
<el-table :data="currentProduct.materials">
|
||||
<el-table-column label="配料名称" align="center" prop="materialName" />
|
||||
<el-table-column label="配料规格" align="center" prop="spec" />
|
||||
<el-table-column label="配料型号" align="center" prop="model" />
|
||||
<el-table-column label="厂家" align="center" prop="factory" />
|
||||
<el-table-column label="计量单位" align="center" prop="unit" />
|
||||
<el-table-column label="现存库存" align="center" prop="currentStock" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty v-else description="选择产品查看配料信息" />
|
||||
|
||||
<!-- </StickyDragContainer> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Product">
|
||||
import { listProduct, getProduct, delProduct, addProduct, updateProduct } from "@/api/mat/product";
|
||||
import bom from "@/views/mat/components/bom.vue";
|
||||
import StickyDragContainer from "@/components/StickyDragContainer/index.vue";
|
||||
|
||||
const bomOpen = ref(false);
|
||||
|
||||
@@ -146,6 +125,8 @@ const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const title = ref("");
|
||||
const currentProductId = ref(null);
|
||||
const currentProduct = ref({});
|
||||
const appContainer = ref(null);
|
||||
|
||||
const formatterTime = (time) => {
|
||||
return proxy.parseTime(time, '{y}-{m}-{d}')
|
||||
@@ -268,7 +249,7 @@ function submitForm() {
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const _productIds = row.productId || ids.value;
|
||||
proxy.$modal.confirm('是否确认删除产品基础信息编号为"' + _productIds + '"的数据项?').then(function() {
|
||||
proxy.$modal.confirm('是否确认删除产品基础信息编号为"' + _productIds + '"的数据项?').then(function () {
|
||||
loading.value = true;
|
||||
return delProduct(_productIds);
|
||||
}).then(() => {
|
||||
@@ -293,5 +274,9 @@ function handleExport() {
|
||||
}, `product_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
|
||||
function handleRowClick(row) {
|
||||
currentProduct.value = row;
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<el-input v-model="queryParams.factory" placeholder="请输入供应商" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="queryParams.materialId" placeholder="请输入配料" clearable @keyup.enter="handleQuery" />
|
||||
<raw-selector ref="rawSelector" v-model="queryParams.materialId" placeholder="请选择配料" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="采购状态" prop="status">
|
||||
<el-select style="width: 120px" v-model="queryParams.status" placeholder="请选择采购状态" clearable>
|
||||
@@ -51,7 +51,9 @@
|
||||
<raw :data="scope.row" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="采购数量" align="center" prop="planNum" />
|
||||
<el-table-column label="采购总数" align="center" prop="planNum" />
|
||||
<el-table-column label="已收数量" align="center" prop="receivedNum" />
|
||||
<el-table-column label="在途数量" align="center" prop="inTransitNum" />
|
||||
<el-table-column label="采购单价" align="center" prop="purchasePrice" />
|
||||
<el-table-column label="截止日期" align="center" prop="deadline" width="180">
|
||||
<template #default="scope">
|
||||
@@ -73,8 +75,8 @@
|
||||
@click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button link type="primary" icon="Delete" v-if="scope.row.status == 1 || scope.row.status == 3"
|
||||
@click="handleDelete(scope.row)">删除</el-button>
|
||||
<el-button link type="primary" icon="Delete" v-if="scope.row.status == 1 || scope.row.status == 3"
|
||||
@click="handleZero(scope.row)">归零</el-button>
|
||||
<el-button link type="primary" icon="Check" v-if="scope.row.status == 1 || scope.row.status == 3"
|
||||
@click="handleZero(scope.row)">完成</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -92,7 +94,9 @@
|
||||
<el-input v-model="form.factory" placeholder="请输入供应商" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="form.materialId" placeholder="请输入配料ID 关联material.id" />
|
||||
<raw-selector v-model="form.materialId" />
|
||||
<!-- <el-input v-model="form.materialId" placeholder="请输入配料ID 关联material.id" /> -->
|
||||
<!-- <el-input v-model="form.materialId" placeholder="请输入配料ID 关联material.id" /> -->
|
||||
</el-form-item>
|
||||
<el-form-item label="采购数量" prop="planNum" v-if="!form.purchaseId">
|
||||
<el-input v-model="form.planNum" placeholder="请输入采购数量" />
|
||||
@@ -135,7 +139,7 @@
|
||||
<el-input v-model="inForm.materialId" placeholder="请输入配料" />
|
||||
</el-form-item> -->
|
||||
<el-form-item label="入库数量" prop="inNum">
|
||||
<el-input v-model="inForm.inNum" placeholder="请输入入库数量" />
|
||||
<el-input-number style="width: 100%" v-model="inForm.inNum" :placeholder="'请输入入库数量,最大数量为' + maxInNum" :max="maxInNum" :controls="false" />
|
||||
</el-form-item>
|
||||
<el-form-item label="入库单价" prop="inPrice">
|
||||
<el-input v-model="inForm.inPrice" placeholder="请输入入库单价" />
|
||||
@@ -161,7 +165,7 @@
|
||||
</el-dialog>
|
||||
|
||||
<div style="border: 1px solid #aaa; margin-top: 10px;">
|
||||
<PurchaseInDetail v-if="currentPurchaseId" :purchaseId="currentPurchaseId" />
|
||||
<PurchaseInDetail ref="inDetailRef" v-if="currentPurchaseId" :purchaseId="currentPurchaseId" />
|
||||
<el-empty v-else description="选择采购单查看入库记录详情" />
|
||||
</div>
|
||||
|
||||
@@ -172,6 +176,7 @@
|
||||
import { listPurchase, getPurchase, delPurchase, addPurchase, updatePurchase } from "@/api/mat/purchase";
|
||||
import { listPurchaseInDetail, addPurchaseInDetail } from "@/api/mat/purchaseInDetail";
|
||||
import PurchaseInDetail from "@/views/mat/components/in.vue"
|
||||
import RawSelector from '@/components/RawSelector/index.vue'; // 引入组件
|
||||
import Raw from "@/components/Renderer/Raw.vue"
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { computed } from "vue";
|
||||
@@ -362,10 +367,13 @@ function handleExport() {
|
||||
|
||||
const inOpen = ref(false);
|
||||
const inForm = ref({});
|
||||
const maxInNum = ref(0);
|
||||
const inDetailRef = ref(null);
|
||||
|
||||
function handleIn(row) {
|
||||
inOpen.value = true;
|
||||
const inTime = proxy.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
|
||||
maxInNum.value = row.inTransitNum;
|
||||
|
||||
inForm.value = {
|
||||
detailId: null,
|
||||
@@ -388,6 +396,7 @@ function submitFormIn() {
|
||||
proxy.$modal.msgSuccess("新增成功");
|
||||
inOpen.value = false;
|
||||
getList();
|
||||
inDetailRef.value?.getList();
|
||||
}).finally(() => {
|
||||
buttonLoading.value = false;
|
||||
loading.value = false;
|
||||
@@ -400,5 +409,22 @@ function cancelIn() {
|
||||
inOpen.value = false;
|
||||
}
|
||||
|
||||
function handleZero(row) {
|
||||
proxy.$modal.confirm('是否将采购单编号为"' + row.purchaseId + '"的计划数量设为与已收数量相同,且状态设为已完成?').then(function () {
|
||||
loading.value = true;
|
||||
return updatePurchase({
|
||||
purchaseId: row.purchaseId,
|
||||
planNum: row.receivedNum,
|
||||
status: 2,
|
||||
});
|
||||
}).then(() => {
|
||||
loading.value = true;
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("发货计划已归档");
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="materialList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" :data="materialList" @selection-change="handleSelectionChange" @row-click="handleRowClick">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="配料ID 主键" align="center" prop="materialId" v-if="true" /> -->
|
||||
<el-table-column label="配料名称" align="center" prop="materialName" />
|
||||
@@ -50,6 +50,7 @@
|
||||
<el-table-column label="厂家" align="center" prop="factory" />
|
||||
<el-table-column label="计量单位" align="center" prop="unit" />
|
||||
<el-table-column label="现存库存" align="center" prop="currentStock" />
|
||||
<el-table-column label="在途数量" align="center" prop="inTransitNum" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
@@ -100,7 +101,7 @@
|
||||
<el-dialog title="入库" v-model="inOpen" width="500px" append-to-body>
|
||||
<el-form ref="materialInRef" :model="inForm" label-width="80px">
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="inForm.materialId" placeholder="请输入配料" />
|
||||
<raw-selector ref="rawSelector" v-model="inForm.materialId" placeholder="请选择配料" />
|
||||
</el-form-item>
|
||||
<el-form-item label="入库数量" prop="inNum">
|
||||
<el-input v-model="inForm.inNum" placeholder="请输入入库数量" />
|
||||
@@ -134,7 +135,7 @@
|
||||
<el-input v-model="outForm.outNo" placeholder="请输入出库单号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配料" prop="materialId">
|
||||
<el-input v-model="outForm.materialId" placeholder="请输入配料" />
|
||||
<raw-selector ref="rawSelector" v-model="outForm.materialId" placeholder="请选择配料" />
|
||||
</el-form-item>
|
||||
<el-form-item label="出库数量" prop="outNum">
|
||||
<el-input v-model="outForm.outNum" placeholder="请输入出库数量" />
|
||||
@@ -161,6 +162,9 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
<Price :materialId="currentMaterialId" ref="priceRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -168,6 +172,8 @@
|
||||
import { listMaterial, getMaterial, delMaterial, addMaterial, updateMaterial } from "@/api/mat/material";
|
||||
import { addPurchaseInDetail } from "@/api/mat/purchaseInDetail";
|
||||
import { addMaterialOut } from "@/api/mat/materialOut";
|
||||
import Price from "@/views/mat/components/price.vue";
|
||||
import RawSelector from "@/components/RawSelector/index.vue";
|
||||
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
@@ -191,6 +197,14 @@ const outForm = ref({});
|
||||
const inOpen = ref(false);
|
||||
const outOpen = ref(false);
|
||||
|
||||
const currentMaterialId = ref(null);
|
||||
|
||||
function handleRowClick(row) {
|
||||
currentMaterialId.value = row.materialId;
|
||||
}
|
||||
|
||||
const priceHistoryList = ref([]);
|
||||
|
||||
const nickName = computed(() => userStore.nickName)
|
||||
|
||||
|
||||
@@ -367,6 +381,8 @@ function cancelIn() {
|
||||
inOpen.value = false;
|
||||
}
|
||||
|
||||
const priceRef = ref(null);
|
||||
|
||||
function submitFormIn() {
|
||||
loading.value = true;
|
||||
buttonLoading.value = true;
|
||||
@@ -374,6 +390,7 @@ function submitFormIn() {
|
||||
proxy.$modal.msgSuccess("新增成功");
|
||||
inOpen.value = false;
|
||||
getList();
|
||||
priceRef.value.getListChart();
|
||||
}).finally(() => {
|
||||
buttonLoading.value = false;
|
||||
loading.value = false;
|
||||
|
||||
Reference in New Issue
Block a user