feat: 新增物料管理看板功能及多项优化
新增物料管理看板功能,包含统计卡片和图表展示 优化物料选择器组件,支持分页和搜索功能 重构物料详情展示组件,支持动态加载数据 添加多个ECharts图表组件用于数据可视化 完善出入库和采购单相关功能,增加在途数量显示 修复若干界面显示问题和交互逻辑
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
"@vueuse/core": "13.3.0",
|
||||
"axios": "1.9.0",
|
||||
"clipboard": "2.0.11",
|
||||
"dayjs": "^1.11.19",
|
||||
"echarts": "5.6.0",
|
||||
"element-plus": "2.9.9",
|
||||
"file-saver": "2.0.5",
|
||||
|
||||
23
gear-ui3/pnpm-lock.yaml
generated
23
gear-ui3/pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
||||
clipboard:
|
||||
specifier: 2.0.11
|
||||
version: 2.0.11
|
||||
dayjs:
|
||||
specifier: ^1.11.19
|
||||
version: 1.11.19
|
||||
echarts:
|
||||
specifier: 5.6.0
|
||||
version: 5.6.0
|
||||
@@ -71,6 +74,9 @@ importers:
|
||||
vue-router:
|
||||
specifier: 4.5.1
|
||||
version: 4.5.1(vue@3.5.16)
|
||||
vue3-treeselect:
|
||||
specifier: ^0.1.10
|
||||
version: 0.1.10(vue@3.5.16)
|
||||
vuedraggable:
|
||||
specifier: 4.1.0
|
||||
version: 4.1.0(vue@3.5.16)
|
||||
@@ -911,8 +917,8 @@ packages:
|
||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
dayjs@1.11.19:
|
||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||
|
||||
debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
@@ -2463,6 +2469,11 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
|
||||
vue3-treeselect@0.1.10:
|
||||
resolution: {integrity: sha512-QawdAbzmlZ7T3uBdSU4FRnrnmcV0Q9Jrph5hUBQJcXmM9OZ8lULQo7O7YbKxkOyuDX9Yx2rGjs6L5FKcL1FeXA==}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
vue@3.5.16:
|
||||
resolution: {integrity: sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==}
|
||||
peerDependencies:
|
||||
@@ -3245,7 +3256,7 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-data-view: 1.0.2
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
dayjs@1.11.19: {}
|
||||
|
||||
debug@2.6.9:
|
||||
dependencies:
|
||||
@@ -3361,7 +3372,7 @@ snapshots:
|
||||
'@types/lodash-es': 4.17.12
|
||||
'@vueuse/core': 9.13.0(vue@3.5.16)
|
||||
async-validator: 4.2.5
|
||||
dayjs: 1.11.13
|
||||
dayjs: 1.11.19
|
||||
escape-html: 1.0.3
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
@@ -4972,6 +4983,10 @@ snapshots:
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.16
|
||||
|
||||
vue3-treeselect@0.1.10(vue@3.5.16):
|
||||
dependencies:
|
||||
vue: 3.5.16
|
||||
|
||||
vue@3.5.16:
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.16
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div @click="open = true" style="cursor: pointer;">
|
||||
<!-- 触发区插槽:默认展示选择按钮/已选物料信息 -->
|
||||
<slot name="trigger">
|
||||
<el-button v-if="!materialId" type="primary" size="small">选择配料</el-button>
|
||||
<el-tag v-else type="info" closable @close="handleClear">
|
||||
{{ currentMaterial.materialName }}({{ currentMaterial.spec }})[{{ currentMaterial.model }}] - {{
|
||||
currentMaterial.factory }},库存:{{ currentMaterial.currentStock }}{{ currentMaterial.unit }}
|
||||
</el-tag>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<!-- 选择弹窗:表格+分页+底部按钮 -->
|
||||
<el-dialog title="选择配料" v-model="open" width="800px" destroy-on-close>
|
||||
<el-table :data="list" style="width: 100%" border stripe @row-click="handleRowSelect" highlight-current-row
|
||||
row-key="materialId" :current-row-key="materialId">
|
||||
<el-table-column prop="materialName" label="配料名称" min-width="120" align="center" />
|
||||
<el-table-column prop="spec" label="配料规格" min-width="100" align="center" />
|
||||
<el-table-column prop="model" label="配料型号" min-width="100" align="center" />
|
||||
<el-table-column prop="factory" label="生产厂家" min-width="120" align="center" />
|
||||
<el-table-column prop="currentStock" label="现存库存" min-width="80" align="center" />
|
||||
<el-table-column prop="unit" label="计量单位" width="80" align="center" />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div style="margin: 15px 0; text-align: right; width: 100%">
|
||||
<el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
|
||||
:page-sizes="[5, 10, 20, 50]" layout="prev, pager, next, jumper, ->, total, sizes"
|
||||
@size-change="fetchMaterialList" @current-change="fetchMaterialList" small />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="open = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="RawSelector">
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { listMaterial, getMaterial } from '@/api/mat/material';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
// 双向绑定物料ID
|
||||
const materialId = defineModel({
|
||||
type: String,
|
||||
default: ""
|
||||
})
|
||||
|
||||
// 计算当前选中的物料信息
|
||||
const currentMaterial = computed(() => {
|
||||
if (!materialId.value) return {};
|
||||
return list.value.find(item => item.materialId === materialId.value) || {};
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
// 基础响应式数据
|
||||
const list = ref([]); // 物料列表
|
||||
const open = ref(false); // 弹窗显隐
|
||||
|
||||
// 分页响应式数据(核心新增)
|
||||
const pageNum = ref(1); // 当前页码
|
||||
const pageSize = ref(10); // 每页条数
|
||||
const total = ref(0); // 总数据条数
|
||||
|
||||
// 组件挂载加载列表
|
||||
onMounted(async () => {
|
||||
await fetchMaterialList();
|
||||
});
|
||||
|
||||
// 加载物料列表(适配分页参数)
|
||||
async function fetchMaterialList() {
|
||||
try {
|
||||
const res = await listMaterial({ pageNum: pageNum.value, pageSize: pageSize.value });
|
||||
|
||||
list.value = res.rows;
|
||||
total.value = res.total; // 赋值总条数供分页使用
|
||||
// 分页查询后检查是否存在当前选中的物料ID
|
||||
const isExist = res.rows.some(item => item.materialId === materialId.value);
|
||||
if (!isExist) {
|
||||
console.log('获取物料详情:', materialId.value);
|
||||
if (!materialId.value) return;
|
||||
const res = await getMaterial(materialId.value);
|
||||
list.value.push(res.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载物料列表失败:', err);
|
||||
ElMessage.error('配料列表加载失败,请刷新重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 表格行选择物料
|
||||
function handleRowSelect(row) {
|
||||
if (row.materialId === materialId.value) return;
|
||||
materialId.value = row.materialId;
|
||||
emit('change', row);
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
// 清空已选物料
|
||||
function handleClear() {
|
||||
materialId.value = '';
|
||||
emit('change', {});
|
||||
ElMessage.info('已清空配料选择');
|
||||
}
|
||||
</script>
|
||||
@@ -1,25 +1,48 @@
|
||||
<template>
|
||||
<el-popover width="400">
|
||||
<template #reference>
|
||||
<el-button type="primary" size="mini" link>{{data.materialName}}</el-button>
|
||||
<el-button type="primary" size="mini" link>{{formattedData.materialName}}</el-button>
|
||||
</template>
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="配料名称">{{ data.materialName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="配料规格">{{ data.spec }}</el-descriptions-item>
|
||||
<el-descriptions-item label="配料型号">{{ data.model }}</el-descriptions-item>
|
||||
<el-descriptions-item label="厂家">{{ data.factory }}</el-descriptions-item>
|
||||
<el-descriptions-item label="现存库存">{{ data.currentStock }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计量单位">{{ data.unit }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ data.remark }}</el-descriptions-item>
|
||||
<el-descriptions-item label="配料名称">{{ formattedData.materialName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="配料规格">{{ formattedData.spec }}</el-descriptions-item>
|
||||
<el-descriptions-item label="配料型号">{{ formattedData.model }}</el-descriptions-item>
|
||||
<el-descriptions-item label="厂家">{{ formattedData.factory }}</el-descriptions-item>
|
||||
<el-descriptions-item label="现存库存">{{ formattedData.currentStock }}</el-descriptions-item>
|
||||
<el-descriptions-item label="计量单位">{{ formattedData.unit }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ formattedData.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getMaterial } from '@/api/mat/material'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
materialId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const content = ref({});
|
||||
|
||||
watch(() => props.materialId, (newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
getMaterial(newVal).then(response => {
|
||||
content.value = response.data;
|
||||
});
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
const formattedData = computed(() => {
|
||||
if (props.materialId) {
|
||||
return content.value;
|
||||
}
|
||||
return props.data;
|
||||
})
|
||||
</script>
|
||||
149
gear-ui3/src/components/StickyDragContainer/index.vue
Normal file
149
gear-ui3/src/components/StickyDragContainer/index.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<!-- @/components/StickyDragContainer/index.vue -->
|
||||
<template>
|
||||
<div class="sticky-drag-wrapper" :style="wrapperStyle">
|
||||
<!-- 拖拽条 -->
|
||||
<div class="drag-bar" ref="dragBarRef" @mousedown="handleDragStart"></div>
|
||||
<!-- 高度可调节+内部滚动容器 -->
|
||||
<div
|
||||
class="content-scroll-container"
|
||||
ref="scrollContainerRef"
|
||||
:style="{ height: `${containerHeight}px` }"
|
||||
>
|
||||
<!-- 默认插槽:接收外部传入的任意内容 -->
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="StickyDragContainer">
|
||||
import { ref, onMounted, onUnmounted, defineProps, watch, nextTick } from 'vue'
|
||||
|
||||
// 新增:接收父容器Ref(必传,用于获取父容器布局信息)
|
||||
const props = defineProps({
|
||||
parentRef: { // 父容器的Ref对象,外部传入
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
initialHeight: { // 容器初始高度(px)
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
minHeight: { // 容器最小高度(px)
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
zIndex: { // 容器层级
|
||||
type: Number,
|
||||
default: 99
|
||||
}
|
||||
})
|
||||
|
||||
// 响应式变量
|
||||
const dragBarRef = ref(null)
|
||||
const scrollContainerRef = ref(null)
|
||||
const startY = ref(0)
|
||||
const startHeight = ref(props.initialHeight)
|
||||
const containerHeight = ref(props.initialHeight)
|
||||
// 新增:组件外层样式(动态绑定宽度、左侧偏移、z-index等)
|
||||
const wrapperStyle = ref({
|
||||
zIndex: props.zIndex,
|
||||
width: '0px',
|
||||
left: '0px'
|
||||
})
|
||||
|
||||
// 核心方法:更新组件布局(宽度/左侧偏移),与父容器保持一致
|
||||
const updateContainerLayout = () => {
|
||||
nextTick(() => {
|
||||
// 校验父容器Ref是否有效
|
||||
console.log(props.parentRef)
|
||||
if (!props.parentRef) {
|
||||
console.warn('StickyDragContainer:传入的parentRef无效,请确保绑定了正确的DOM Ref')
|
||||
return
|
||||
}
|
||||
// 获取父容器的实际布局信息(相对于视口的位置、宽度)
|
||||
const parentRect = props.parentRef.getBoundingClientRect()
|
||||
// 动态设置组件宽度(与父容器完全一致)、左侧偏移(与父容器左对齐)
|
||||
wrapperStyle.value = {
|
||||
zIndex: props.zIndex,
|
||||
width: `${parentRect.width}px`, // 继承父容器宽度
|
||||
left: `${parentRect.left}px` // 与父容器左对齐
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 拖拽相关方法(保持不变)
|
||||
const handleDragStart = (e) => {
|
||||
e.preventDefault()
|
||||
startY.value = e.clientY
|
||||
startHeight.value = scrollContainerRef.value?.getBoundingClientRect().height || containerHeight.value
|
||||
document.addEventListener('mousemove', handleDragMove)
|
||||
document.addEventListener('mouseup', handleDragEnd)
|
||||
}
|
||||
const handleDragMove = (e) => {
|
||||
e.preventDefault()
|
||||
const diffY = startY.value - e.clientY
|
||||
const newHeight = Math.max(props.minHeight, startHeight.value + diffY)
|
||||
containerHeight.value = newHeight
|
||||
}
|
||||
const handleDragEnd = () => {
|
||||
document.removeEventListener('mousemove', handleDragMove)
|
||||
document.removeEventListener('mouseup', handleDragEnd)
|
||||
}
|
||||
|
||||
// 生命周期:初始化+监听窗口缩放
|
||||
onMounted(() => {
|
||||
containerHeight.value = props.initialHeight
|
||||
updateContainerLayout() // 初始化时适配父容器布局
|
||||
// 新增:监听窗口缩放,父容器宽度变化时同步更新组件布局
|
||||
window.addEventListener('resize', updateContainerLayout)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 解绑所有事件,防止内存泄漏
|
||||
document.removeEventListener('mousemove', handleDragMove)
|
||||
document.removeEventListener('mouseup', handleDragEnd)
|
||||
window.removeEventListener('resize', updateContainerLayout) // 解绑窗口监听
|
||||
})
|
||||
|
||||
// 监听props变化,动态更新
|
||||
watch([() => props.initialHeight, () => props.zIndex], () => {
|
||||
containerHeight.value = props.initialHeight
|
||||
wrapperStyle.value.zIndex = props.zIndex
|
||||
}, { immediate: true })
|
||||
|
||||
// 新增:如果父容器Ref变化,重新适配布局
|
||||
watch(() => props.parentRef, () => {
|
||||
updateContainerLayout()
|
||||
}, { deep: true, immediate: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 吸底外层容器:移除left/right:0,改为动态绑定;保留fixed+bottom:0核心吸底 */
|
||||
.sticky-drag-wrapper {
|
||||
position: fixed;
|
||||
bottom: 0; /* 仅保留吸底,宽度/左侧偏移由JS动态设置 */
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.1s ease; /* 宽度/高度变化平滑过渡 */
|
||||
}
|
||||
|
||||
/* 拖拽条样式(保持不变) */
|
||||
.drag-bar {
|
||||
height: 6px;
|
||||
background-color: #e6e6e6;
|
||||
cursor: n-resize;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
.drag-bar:hover {
|
||||
background-color: #409eff;
|
||||
}
|
||||
|
||||
/* 内容滚动容器样式(保持不变) */
|
||||
.content-scroll-container {
|
||||
width: 100%; /* 继承外层wrapper的宽度(即父容器宽度) */
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
transition: height 0.1s ease;
|
||||
}
|
||||
</style>
|
||||
@@ -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