整合前端

This commit is contained in:
砂糖
2026-04-13 17:04:38 +08:00
parent 69609a2cb1
commit 5d4794c9bd
915 changed files with 144259 additions and 0 deletions

View File

@@ -0,0 +1,568 @@
<template>
<el-card style="margin: 10px" v-loading="loading">
<el-form
:model="queryParams"
ref="queryForm"
size="small"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="任意搜索" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入物料名/型号/品牌"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="型号" prop="model">
<el-input
v-model="queryParams.model"
placeholder="请输入型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="品牌" prop="brand">
<el-input
v-model="queryParams.brand"
placeholder="请输入品牌"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
size="mini"
@click="handleQuery"
>搜索</el-button
>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
>重置</el-button
>
</el-form-item>
</el-form>
<div class="transfer-box-container">
<el-row>
<!-- 左侧区域 -->
<el-col :span="8">
<el-tabs v-model="activeName">
<el-tab-pane label="低于阈值" name="first">
<!-- 低于阈值表格 -->
<el-table
:data="thresholdData"
border
:loading="loading"
highlight-current-row
@row-click="handleThresholdRowClick"
:row-class-name="thresholdRowClassName"
style="width: 100%; margin-bottom: 20px"
>
<el-table-column
prop="name"
label="物料名"
align="center"
></el-table-column>
<el-table-column
prop="model"
label="型号"
align="center"
></el-table-column>
<el-table-column
prop="brand"
label="品牌"
align="center"
></el-table-column>
<el-table-column
prop="unit"
label="单位"
align="center"
></el-table-column>
<el-table-column prop="inventory" label="库存量" align="center">
<template slot-scope="scope">
<span class="inventory-value">{{ scope.row.inventory }}</span>
</template>
</el-table-column>
<el-table-column prop="taskInventory" label="在途料" align="center">
<template slot-scope="scope">
<span class="task-inventory-value">{{ scope.row.taskInventory || 0 }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total1 > 0"
:total="total1"
:page.sync="queryParams1.pageNum"
:limit.sync="queryParams1.pageSize"
@pagination="getList"
/>
</el-tab-pane>
<el-tab-pane label="未低于阈值" name="second">
<!-- 未低于阈值表格 -->
<el-table
:data="notThresholdData"
border
:loading="loading"
highlight-current-row
@row-click="handleNotThresholdRowClick"
:row-class-name="notThresholdRowClassName"
style="width: 100%; margin-bottom: 10px"
>
<el-table-column
prop="name"
label="物料名"
align="center"
></el-table-column>
<el-table-column
prop="model"
label="型号"
align="center"
></el-table-column>
<el-table-column
prop="brand"
label="品牌"
align="center"
></el-table-column>
<el-table-column
prop="unit"
label="单位"
align="center"
></el-table-column>
<el-table-column prop="inventory" label="库存量" align="center">
<template slot-scope="scope">
<span class="inventory-value">{{ scope.row.inventory }}</span>
</template>
</el-table-column>
<el-table-column prop="taskInventory" label="在途料" align="center">
<template slot-scope="scope">
<span class="task-inventory-value">{{ scope.row.taskInventory || 0 }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total2 > 0"
:total="total2"
:page.sync="queryParams2.pageNum"
:limit.sync="queryParams2.pageSize"
@pagination="getList"
/>
</el-tab-pane>
</el-tabs>
</el-col>
<!-- 中间操作按钮 -->
<el-col :span="4" style="text-align: center; margin-top: 120px">
<el-button
type="primary"
@click="moveToRight"
style="margin-bottom: 20px"
>
加入采购单 >>
</el-button>
<br />
<el-button type="primary" @click="moveToLeft"> << 撤销 </el-button>
</el-col>
<!-- 右侧列表 -->
<el-col :span="12">
<div class="table-title">
补充库存
<el-button
type="text"
@click="addCustomItem"
style="margin-left: 10px"
>
新增
</el-button>
</div>
<el-table
:data="rightData"
border
highlight-current-row
@row-click="handleRightRowClick"
:row-class-name="rightRowClassName"
style="width: 100%"
>
<!-- 物料名 -->
<el-table-column label="物料名" align="center">
<template slot-scope="{ row }">
<!-- 如果是来自左侧的数据就显示纯文本否则可以输入 -->
<el-input
v-if="!row.fromLeft"
v-model="row.name"
size="small"
placeholder="请输入物料名"
/>
<span v-else>{{ row.name }}</span>
</template>
</el-table-column>
<!-- 型号 -->
<el-table-column label="型号" align="center">
<template slot-scope="{ row }">
<el-input
v-if="!row.fromLeft"
v-model="row.model"
size="small"
placeholder="请输入型号"
/>
<span v-else>{{ row.model }}</span>
</template>
</el-table-column>
<!-- 品牌 -->
<el-table-column label="品牌" align="center">
<template slot-scope="{ row }">
<el-input
v-if="!row.fromLeft"
v-model="row.brand"
size="small"
placeholder="请输入品牌"
/>
<span v-else>{{ row.brand }}</span>
</template>
</el-table-column>
<el-table-column label="规格" align="center">
<template slot-scope="{ row }">
<el-input
v-if="!row.fromLeft"
v-model="row.specifications"
size="small"
placeholder="请输入规格"
/>
<span v-else>{{ row.specifications }}</span>
</template>
</el-table-column>
<el-table-column label="单位" align="center">
<template slot-scope="{ row }">
<el-input
v-if="!row.fromLeft"
v-model="row.unit"
size="small"
placeholder="请输入单位"
/>
<span v-else>{{ row.unit }}</span>
</template>
</el-table-column>
<!-- 补充库存量 -->
<el-table-column label="补充库存量" align="center">
<template slot-scope="{ row }">
<el-input
v-model="row.taskInventory"
size="small"
type="number"
placeholder="请输入补充库存量"
/>
</template>
</el-table-column>
<el-table-column label="截止日期" align="center">
<template slot-scope="{ row }">
<el-date-picker
v-model="row.endTime"
align="right"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
>
</el-date-picker>
</template>
</el-table-column>
<!-- 操作列只能删除自定义新增的行来自左侧的只能移回 -->
<el-table-column label="操作" align="center">
<template slot-scope="{ row }">
<el-button
v-if="!row.fromLeft"
type="text"
@click.stop="removeRightItem(row)"
>
删除
</el-button>
<el-button v-else type="text" @click.stop="moveRowBack(row)">
移回左侧
</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<div style="display: flex; justify-content: flex-end; margin-top: 40px">
<el-button type="primary" @click="submitForm">提交采购单</el-button>
<el-button @click="cancel"> </el-button>
</div>
</div>
</el-card>
</template>
<script>
import { listNotThreshold, listThreshold } from "@/api/oa/warehouse/oaWarehouse";
import { addOaWarehouseTaskBatch } from "@/api/oa/warehouse/warehouseTask";
export default {
name: "TransferBox",
data() {
return {
loading: false,
thresholdData: [],
// 显示搜索条件
showSearch: true,
notThresholdData: [],
// 右侧列表
rightData: [],
total1: 0,
total2: 0,
// 左侧选中项:为了区分两个表格,各自记录
thresholdSelection: null,
notThresholdSelection: null,
// 右侧选中项
rightSelection: null,
wareList: [],
queryParams1: {
pageSize: 10,
pageNum: 1,
},
queryParams2: {
pageSize: 10,
pageNum: 1,
},
queryParams: {
pageSize: 10,
pageNum: 1,
},
activeName: "first",
};
},
created() {
this.getList();
},
methods: {
submitForm() {
addOaWarehouseTaskBatch(this.rightData).then((res) => {
this.goBack();
});
},
/** 返回页面 */
goBack() {
// 关闭当前标签页并返回上个页面
this.$tab.closePage(this.$route);
this.$router.back();
},
cancel() {
this.resetQuery();
this.rightData = [];
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams1 = this.queryParams;
this.queryParams2 = this.queryParams;
this.queryParams1.pageNum = 1;
this.queryParams2.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm1");
this.resetForm("queryForm2");
this.resetForm("queryForm");
this.handleQuery();
},
/** 查询库存管理列表 */
getList() {
this.loading = true;
listThreshold(this.queryParams1).then((response) => {
this.thresholdData = response.rows;
this.total1 = response.total;
listNotThreshold(this.queryParams2).then((response) => {
this.notThresholdData = response.rows;
this.total2 = response.total;
this.loading = false;
});
});
},
// ========= 左侧:低于阈值表格,选中处理 =========
handleThresholdRowClick(row) {
this.thresholdSelection = row;
// 若点击了低阈值表格,则清空另一个表格以及右侧选中的状态
this.notThresholdSelection = null;
this.rightSelection = null;
},
// ========= 左侧:未低于阈值表格,选中处理 =========
handleNotThresholdRowClick(row) {
this.notThresholdSelection = row;
// 若点击了未阈值表格,则清空另一个表格以及右侧选中的状态
this.thresholdSelection = null;
this.rightSelection = null;
},
// ========= 右侧表格,选中处理 =========
handleRightRowClick(row) {
this.rightSelection = row;
// 点击右侧时,可以把左侧两个表格的选中都清掉
this.thresholdSelection = null;
this.notThresholdSelection = null;
},
// ========= 移到右侧 =========
moveToRight() {
// 优先判断是否选中"低阈值"表格的行
if (this.thresholdSelection) {
// 1. 从 thresholdData 移除
this.thresholdData = this.thresholdData.filter(
(item) => item.id !== this.thresholdSelection.id
);
// 2. push到右侧
this.thresholdSelection.fromLeft = true;
this.thresholdSelection.taskInventory = 0;
this.thresholdSelection.origin = "threshold";
if (
this.rightData.find(
(item) => item.id === this.thresholdSelection.id
) === undefined
) {
this.rightData.push({ ...this.thresholdSelection });
}
// 3. 清除选中
this.thresholdSelection = null;
return;
}
// 再判断是否选中"未低阈值"表格的行
if (this.notThresholdSelection) {
// 1. 从 notThresholdData 移除
this.notThresholdData = this.notThresholdData.filter(
(item) => item.id !== this.notThresholdSelection.id
);
// 2. push到右侧
this.notThresholdSelection.fromLeft = true;
this.notThresholdSelection.taskInventory = 0;
this.notThresholdSelection.origin = "notThreshold";
if (
this.rightData.find(
(item) => item.id === this.notThresholdSelection.id
) === undefined
) {
this.rightData.push({ ...this.notThresholdSelection });
}
// 3. 清除选中
this.notThresholdSelection = null;
return;
}
// 若都没有选中,提示
this.$message.warning("请先选择左侧的一条数据!");
},
// ========= 从右侧移回左侧 =========
moveToLeft() {
if (!this.rightSelection) {
this.$message.warning("请先选择右侧的一条数据!");
return;
}
// 若选中的行是用户自定义的(fromLeft=false),则无法移回
if (!this.rightSelection.fromLeft) {
this.$message.warning("该数据是新增的,无法移回左侧!");
return;
}
// 如果是来自左侧,则看它 origin 是哪个
const row = { ...this.rightSelection };
this.rightData = this.rightData.filter((item) => item.id !== row.id);
// 按 origin 放回对应数组
if (row.origin === "threshold") {
this.thresholdData.push(row);
} else if (row.origin === "notThreshold") {
this.notThresholdData.push(row);
}
// 清空选中
this.rightSelection = null;
},
// ========= 右侧操作列:移回 =========
moveRowBack(row) {
// 同理,把这个 row 从右侧移除,并放回对应左侧表格
this.rightData = this.rightData.filter((item) => item.id !== row.id);
if (row.origin === "threshold") {
this.thresholdData.push({ ...row });
} else if (row.origin === "notThreshold") {
this.notThresholdData.push({ ...row });
}
},
// ========= 新增一条自定义行到右侧 =========
addCustomItem() {
const newId = new Date().getTime(); // 简单生成唯一id
this.rightData.push({
id: newId,
name: "",
model: "",
brand: "",
inventory: "",
fromLeft: false, // 标识为手动新增
});
},
// ========= 删除右侧的自定义行 =========
removeRightItem(row) {
this.rightData = this.rightData.filter((item) => item.id !== row.id);
},
// ========= 行样式控制(高亮) =========
thresholdRowClassName({ row }) {
return this.thresholdSelection && this.thresholdSelection.id === row.id
? "selected-row"
: "";
},
notThresholdRowClassName({ row }) {
return this.notThresholdSelection &&
this.notThresholdSelection.id === row.id
? "selected-row"
: "";
},
rightRowClassName({ row }) {
return this.rightSelection && this.rightSelection.id === row.id
? "selected-row"
: "";
},
},
};
</script>
<style scoped>
.transfer-box-container {
padding: 20px;
}
.table-title {
font-weight: bold;
margin-bottom: 10px;
}
.selected-row {
background-color: #f0f9eb !important;
}
.inventory-value {
color: #409EFF;
font-weight: bold;
}
.task-inventory-value {
color: #67C23A;
margin-left: 5px;
}
</style>

View File

@@ -0,0 +1,160 @@
<template>
<div class="app-container">
<!-- 返回按钮 -->
<el-row class="mb8">
<el-col>
<el-card class="box-card" style="display: flex; align-items: center;">
<el-button
icon="el-icon-arrow-left"
@click="goBack"
circle
>
</el-button>
<span class="text item">
物料<strong>{{ warehouse.name }}</strong> 的出/入库详情
</span>
</el-card>
</el-col>
</el-row>
<el-row class="mb8">
<el-col>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="全部" name="all" />
<el-tab-pane label="出库" name="out" />
<el-tab-pane label="入库" name="in" />
</el-tabs>
</el-col>
</el-row>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
<el-table v-loading="loading" :data="outWarehouseList">
<!-- 全部 -->
<template v-if="activeTab === 'all'">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" />
<el-table-column label="类型" align="center" >
<template slot-scope="scope">
<el-tag :type="scope.row.type===0?'success':'warning'">{{scope.row.type===0?'出库':'入库'}}</el-tag>
</template>
</el-table-column>
<el-table-column label="单据号" align="center" prop="masterNum" />
<el-table-column :label="`数量(${warehouse.unit})`" align="center" prop="amount" />
<el-table-column label="价格快照" align="center" prop="signPrice" />
<el-table-column label="操作时间" align="center" prop="createTime" />
<el-table-column label="关联项目" align="center" prop="projectName" />
<el-table-column label="备注" align="center" prop="remark" />
</template>
<!-- 出库 -->
<template v-else-if="activeTab === 'out'">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" />
<el-table-column label="出库单" align="center" prop="masterNum" />
<el-table-column :label="`出库数量(${warehouse.unit})`" align="center" prop="amount" />
<el-table-column label="出库价格快照" align="center" prop="signPrice" />
<el-table-column label="操作时间" align="center" prop="createTime" />
<el-table-column label="关联项目" align="center" prop="projectName" />
<el-table-column label="备注" align="center" prop="remark" />
</template>
<!-- 入库 -->
<template v-else>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" />
<el-table-column label="入库单" align="center" prop="masterNum" />
<el-table-column :label="`入库数量(${warehouse.unit})`" align="center" prop="amount" />
<el-table-column label="入库价格快照" align="center" prop="signPrice" />
<el-table-column label="操作时间" align="center" prop="createTime" />
<el-table-column label="关联项目" align="center" prop="projectName" />
<el-table-column label="备注" align="center" prop="remark" />
</template>
</el-table>
</div>
</template>
<script>
import { listOaOutWarehouse } from "@/api/oa/warehouse/oaOutWarehouse";
import { getOaWarehouse } from "@/api/oa/warehouse/oaWarehouse";
export default {
name: "Data",
data() {
return {
// Tab 控制:'all' | 'out' | 'in'
activeTab: "all",
showSearch: true,
// 出/入库列表
outWarehouseList: [],
// 查询参数:初始 type 为 null全部
queryParams: {
pageNum: 1,
pageSize: 9999,
warehouseId: this.$route.params.warehouseId,
type: null,
},
// 物料信息
warehouse: {},
// 加载状态
loading: false,
};
},
created() {
this.getWarehouse();
this.getList();
},
methods: {
// Tab 切换
handleTabClick(tab) {
switch (tab.name) {
case "out":
this.queryParams.type = 0;
break;
case "in":
this.queryParams.type = 1;
break;
default:
this.queryParams.type = null;
}
this.queryParams.pageNum = 1;
this.getList();
},
// 获取物料详情
getWarehouse() {
getOaWarehouse(this.queryParams.warehouseId).then((res) => {
this.warehouse = res.data;
});
},
// 获取列表
getList() {
this.loading = true;
listOaOutWarehouse(this.queryParams)
.then((res) => {
this.outWarehouseList = res.rows;
this.total = res.total;
})
.finally(() => {
this.loading = false;
});
},
/** 返回页面 */
goBack() {
this.$tab.closePage(this.$route);
this.$router.back();
}
},
};
</script>
<style scoped lang="scss">
/* 根据需要补充自定义样式 */
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<el-card shadow="hover" class="chart-card">
<div slot="header" class="clearfix">
<span>{{ title }}</span>
<el-date-picker
v-model="selectedYear"
type="month"
placeholder="选择时间"
size="mini"
class="float-right"
value-format="yyyy-MM"
@change="onYearChange"
/>
</div>
<div ref="chart" class="chart-container"></div>
</el-card>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'ChartContainer',
props: {
title: String,
ranges: Array,
range: String,
option: Object
},
data() {
return {
localRange: this.range,
selectedYear: null,
}
},
watch: {
localRange(val) {
this.$emit('update:range', val)
this.renderChart()
},
// 只要 parent 更新了 option就重绘
option: {
deep: true,
immediate: true,
handler() {
this.renderChart()
}
}
},
mounted() {
this.renderChart()
},
methods: {
onYearChange(val) {
// year: Date 对象,或者 Number部分版本 ElementUI 会直接返回年)
const year = val instanceof Date ? val.getFullYear() : val;
this.selectedYear = year;
this.$emit('year-change', year);
},
renderChart() {
const chart = echarts.init(this.$refs.chart)
chart.setOption(this.option)
}
}
}
</script>
<style scoped>
.chart-card {
height: 360px;
}
.chart-container {
width: 100%;
height: 300px;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<chart-container
title="出入库对比"
:option="chartOption"
@year-change="onChildYearChange"
/>
</template>
<script>
import { monthDataAnalysis } from "@/api/oa/warehouse/oaWarehouse";
import ChartContainer from './ChartContainer.vue';
export default {
name: 'InOutCompare',
components: {ChartContainer},
props: {},
data() {
return {
year: undefined,
chartOption: {}
}
},
computed: {
},
mounted() {
this.getData()
},
methods: {
onChildYearChange(year) {
this.year = year;
this.getData()
},
getData() {
monthDataAnalysis({month:this.year}).then(res => {
this.chartOption = {
legend: {},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {type: 'value'},
series: [
{name: '入库量', type: 'bar', data: res.data.inData["月"]},
{name: '出库量', type: 'bar', data: res.data.outData["月"]}
]
}
})
}
},
}
</script>

View File

@@ -0,0 +1,60 @@
<template>
<chart-container
title="库存趋势分析"
@year-change="onChildYearChange"
:option="dataMap"
/>
</template>
<script>
import { monthDataAnalysis } from "@/api/oa/warehouse/oaWarehouse";
import ChartContainer from './ChartContainer.vue';
export default {
name: 'InventoryTrend',
components: {ChartContainer},
props: {},
data() {
return {
year: undefined,
dataMap:{}
}
},
computed: {
},
mounted() {
this.getData()
},
methods: {
onChildYearChange(year) {
this.year = year;
this.getData()
},
getData() {
monthDataAnalysis({month:this.year}).then(res => {
console.log(res.data)
const today = new Date();
// 2. 拿到“本月已过天数”
const daysSoFar = today.getDate(); // 131
// 3. 生成一个长度为 daysSoFar 的数组,内容是 “1日”、“2日”…“N日”
const dayCategories = Array.from(
{ length: daysSoFar },
(_, i) => `${i + 1}`
);
this.dataMap = {
xAxis: {
type: 'category',
data: dayCategories
},
yAxis: {type: 'value'},
series: [{data: res.data.dataMap["日"], type: 'line'}]
}
})
}
}
}
</script>

View File

@@ -0,0 +1,309 @@
<template>
<div class="dashboard">
<el-row>
<el-col span="24">
<el-form :inline="true" :model="filters" class="filter-form" size="mini">
<el-form-item label="时间范围:">
<el-date-picker
v-model="filters.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
></el-date-picker>
</el-form-item>
<el-form-item label="物料分类:">
<el-select v-model="filters.category" placeholder="全部分类" clearable>
<el-option v-for="opt in categoryOptions" :key="opt.value" :label="opt.label" :value="opt.value"/>
</el-select>
</el-form-item>
<el-form-item label="仓库位置:">
<el-select v-model="filters.warehouse" placeholder="全部仓库" clearable disabled="">
<el-option v-for="opt in warehouseOptions" :key="opt.value" :label="opt.label" :value="opt.value"/>
</el-select>
</el-form-item>
<el-form-item label="供应商:">
<el-select v-model="filters.supplier" placeholder="全部供应商" clearable disabled="">
<el-option v-for="opt in supplierOptions" :key="opt.value" :label="opt.label" :value="opt.value"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchData">查询</el-button>
<el-button type="success" icon="el-icon-download" @click="exportReport">导出报表</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
<el-row
type="flex"
class="card-row"
:gutter="20"
justify="space-between"
>
<el-col
v-for="card in summaryCards"
:key="card.title"
:style="{ flex: `0 0 ${cardWidth}` }"
>
<el-card shadow="hover" class="summary-card">
<div class="card-content">
<div class="card-title">{{ card.title }}</div>
<div class="card-value">{{ card.value }}</div>
<div
class="card-desc"
:class="card.trend > 0 ? 'trend-up' : 'trend-down'"
>
<i
:class="
card.trend > 0
? 'el-icon-caret-top'
: 'el-icon-caret-bottom'
"
></i>
{{ Math.abs(card.trend) }}%
{{ card.trend > 0 ? '环比上月' : '环比昨日' }}
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- Encapsulated Charts -->
<el-row :gutter="20" class="chart-row">
<el-col :span="12">
<inventory-trend :range.sync="chartRange.inventory"/>
</el-col>
<el-col :span="12">
<in-out-compare :range.sync="chartRange.movements"/>
</el-col>
</el-row>
<!-- Table -->
<el-card shadow="hover" class="table-card">
<div slot="header" class="clearfix">
<span>最近记录</span>
</div>
<el-table
:data="tableData"
border
size="small"
style="width: 100%"
>
<el-table-column prop="name" label="物料名称"/>
<el-table-column prop="model" label="型号"/>
<el-table-column prop="brand" label="品牌"/>
<el-table-column prop="specifications" label="规格型号"/>
<el-table-column prop="inventory" label="当前库存"/>
<el-table-column prop="threshold" label="安全库存"/>
<el-table-column prop="taskInventory" label="在途数量"/>
<el-table-column prop="lastInbound" label="最近入库"/>
<el-table-column prop="lastOutbound" label="最近出库"/>
<el-table-column label="库存状态">
<template #default="{ row }">
<el-tag
:type="calcInventoryStatus(row.inventory, row.taskInventory,row.threshold).type"
>
{{ calcInventoryStatus(row.inventory, row.taskInventory,row.threshold).status }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
import { CardDataAnalysis, recentWarehouse } from "@/api/oa/warehouse/oaWarehouse";
import InOutCompare from "@/views/oa/oaWarehouse/data/components/InOutCompare.vue";
import InventoryTrend from "@/views/oa/oaWarehouse/data/components/InventoryTrend.vue";
export default {
name: 'Dashboard',
components: {InventoryTrend, InOutCompare},
data() {
return {
filters: {
dateRange: [],
category: null,
warehouse: null,
supplier: null,
},
categoryOptions: [
{label: '全部分类', value: null},
{label: '全部分类', value: 'hardware'},
{label: '全部分类', value: 'pipe'},
// ...
],
warehouseOptions: [
{label: '全部仓库', value: null},
{label: '全部仓库', value: 'main'},
{label: '全部仓库', value: 'A'},
// ...
],
supplierOptions: [
{label: '全部供应商', value: null},
{label: '全部供应商', value: 'A'},
{label: '全部供应商', value: 'B'},
// ...
],
summaryCards: [
{title: '当前总库存量', value: 238526, trend: 8.5},
{title: '在途物料数量', value: 12385, trend: -3.2},
{title: '今日入库量', value: 1856, trend: 12.8},
{title: '今日出库量', value: 2156, trend: 5.6},
{title: '预警信息', value: 15, trend: 0},
],
chartRange: {
inventory: '月',
movements: '月',
},
loading:false,
tableData: [],
tableFilter: {keyword: ''},
queryParams: {
},
queryWarehouse:{
limit:10
}
}
},
computed: {
// 自动计算卡片宽度:(100% - 总间距) / 卡片数
cardWidth() {
const n = this.summaryCards.length;
const totalGutter = (n - 1) * 20; // gutter 20px * (n-1)
return `calc((100% - ${totalGutter}px) / ${n})`;
},
filteredTableData() {
const kw = this.tableFilter.keyword && this.tableFilter.keyword.toLowerCase()
let data = this.tableData
if (kw) {
data = data.filter(r => r.name.toLowerCase().includes(kw))
}
const start = (this.currentPage - 1) * this.pageSize
return data.slice(start, start + this.pageSize)
}
},
methods: {
calcInventoryStatus(inventory, inTransit, threshold) {
const total = inventory + inTransit;
if (total > threshold+10) {
return { status: '库存积压', type: 'warning' };
}else
if (total > threshold) {
return { status: '正常', type: 'success' };
}else
if (total < threshold-2 && total > threshold) {
return { status: '库存预警', type: 'danger' };
}
return { status: '库存不足', type: 'info' };
},
fetchData() {
this.loading = true
// TODO: 调用后端接口,获取 summaryCards, tableData以及图表数据
if (this.filters.dateRange.length > 0) {
this.queryParams["beginTime"] = this.filters.dateRange[0]+" 00:00:00";
this.queryParams["endTime"] = this.filters.dateRange[1]+" 23:59:59";
}
CardDataAnalysis(this.queryParams).then(res => {
this.summaryCards = res.data
this.initCharts()
})
recentWarehouse(this.queryWarehouse).then(response => {
this.tableData = response.data;
this.loading = false;
});
},
exportReport() {
// TODO: 导出报表逻辑
},
},
mounted() {
this.fetchData()
}
}
</script>
<style scoped>
.dashboard {
padding: 20px;
}
.filter-form {
margin-bottom: 20px;
}
.card-row {
margin-bottom: 20px;
}
.summary-card {
text-align: center;
}
.card-content {
padding: 10px 0;
}
.card-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.card-value {
font-size: 24px;
font-weight: bold;
}
.card-desc {
margin-top: 4px;
font-size: 12px;
}
.trend-up {
color: #67c23a;
}
.trend-down {
color: #f56c6c;
}
.chart-row {
margin-bottom: 20px;
}
.chart-card {
height: 360px;
}
.chart-container {
width: 100%;
height: 300px;
}
.table-card {
margin-bottom: 20px;
}
.filter-input {
width: 200px;
margin-left: 16px;
}
.pagination {
margin-top: 16px;
text-align: right;
}
</style>

View File

@@ -0,0 +1,483 @@
<template>
<div class="app-container" v-loading="loading">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="型号" prop="model">
<el-input
v-model="queryParams.model"
placeholder="请输入型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入物料名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="品牌" prop="brand">
<el-input
v-model="queryParams.brand"
placeholder="请输入品牌"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['oa:oaWarehouse:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['oa:oaWarehouse:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['oa:oaWarehouse:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['oa:oaWarehouse:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-upload2"
size="mini"
@click="handleImport"
>导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button
plain
icon="el-icon-upload"
size="mini"
@click="addWarehouseTask"
>添加采购计划</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="oaWarehouseList" @selection-change="handleSelectionChange" :row-class-name="tableRowClassName">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index"/>
<el-table-column label="物料" align="center" prop="name" :show-overflow-tooltip="true">
<template slot-scope="scope">
<router-link :to="'/oa/warehouse-data/index/' + scope.row.id" class="link-type">
<i class="el-icon-warning" v-if="scope.row.inventory<scope.row.threshold" style="color: red"></i>
<span>{{ scope.row.name }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="型号" align="center" prop="model" />
<el-table-column label="单价" align="center" prop="price" />
<el-table-column label="库存数量" align="center" prop="inventory">
<template slot-scope="scope">
<span>{{scope.row.inventory}}
<span v-if="scope.row.taskInventory!==null">
({{scope.row.taskInventory}})
</span>
</span>
</template>
</el-table-column>
<el-table-column label="单位" align="center" prop="unit" />
<el-table-column label="品牌" align="center" prop="brand" />
<el-table-column label="规格" align="center" prop="specifications" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['oa:oaWarehouse:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['oa:oaWarehouse:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改库存管理对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="物料名称" prop="name">
<el-input v-model="form.name" placeholder="请输入物料名称" />
</el-form-item>
<el-form-item label="告警阈值" prop="threshold">
<el-input v-model="form.threshold" type="number" placeholder="请输入告警阈值" />
</el-form-item>
<el-form-item label="型号" prop="model">
<el-input v-model="form.model" placeholder="请输入型号" />
</el-form-item>
<el-form-item label="单价" prop="price">
<el-input v-model="form.price" placeholder="请输入单价" />
</el-form-item>
<el-form-item label="库存数量" prop="inventory">
<el-input v-model="form.inventory" placeholder="请输入库存数量" />
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input v-model="form.unit" placeholder="请输入单位" />
</el-form-item>
<el-form-item label="品牌" prop="brand">
<el-input v-model="form.brand" placeholder="请输入品牌" />
</el-form-item>
<el-form-item label="规格" prop="specifications">
<el-input v-model="form.specifications" placeholder="请输入规格" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload
ref="upload"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:on-error="handleFileError"
:auto-upload="false"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip text-center" slot="tip">
<span>仅允许导入xlsxlsx格式文件</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { delOaWarehouse, getOaWarehouse, listOaWarehouse, updateOaWarehouse } from "@/api/oa/warehouse/oaWarehouse";
import { addOaWarehouseMasterToIn } from "@/api/oa/warehouse/warehouseMaster";
import { getToken } from "@/utils/auth";
export default {
name: "OaWarehouse",
data() {
return {
// 用户导入参数
upload: {
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: "",
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/oa/oaWarehouse/importData"
},
// 按钮loading
buttonLoading: false,
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 库存管理表格数据
oaWarehouseList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
loadingInstance: null, // 全屏 Loading 句柄
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
inventory: undefined,
model: undefined,
unit: undefined,
name: undefined,
brand: undefined,
specifications: undefined,
},
// 表单参数
form: {},
// 表单校验
rules: {
inventory: [
{ required: true, message: "库存数量不能为空", trigger: "blur" }
],
name: [
{ required: true, message: "物料名称不能为空", trigger: "blur" }
],
}
};
},
created() {
this.getList();
},
methods: {
tableRowClassName({row, rowIndex}){
if (row.inventory+row.taskInventory<row.threshold) {
return 'warning-row';
}
return '';
},
/** 查询库存管理列表 */
getList() {
this.loading = true;
listOaWarehouse(this.queryParams).then(response => {
this.oaWarehouseList = response.rows;
this.total = response.total;
this.loading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: undefined,
inventory: undefined,
model: undefined,
unit: undefined,
name: undefined,
brand: undefined,
specifications: undefined,
remark: undefined,
createTime: undefined,
createBy: undefined,
updateTime: undefined,
updateBy: undefined,
delFlag: undefined
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加库存管理";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.loading = true;
this.reset();
const id = row.id || this.ids
getOaWarehouse(id).then(response => {
this.loading = false;
this.form = response.data;
this.open = true;
this.title = "修改库存管理";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.id != null) {
updateOaWarehouse(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
} else {
addOaWarehouseMasterToIn(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除库存管理编号为"' + ids + '"的数据项?').then(() => {
this.loading = true;
return delOaWarehouse(ids);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
}).finally(() => {
this.loading = false;
});
},
/** 导出按钮操作 */
handleExport() {
this.download('oa/oaWarehouse/export', {
...this.queryParams
}, `oaWarehouse_${new Date().getTime()}.xlsx`)
},
/** 导入按钮操作 */
handleImport() {
this.upload.title = "用户导入";
this.upload.open = true;
},
/** 下载模板操作 */
importTemplate() {
this.download('oa/oaWarehouse/importTemplate', {
}, `ware_template_${new Date().getTime()}.xlsx`)
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
if (this.loadingInstance) {
this.loadingInstance.close();
this.loadingInstance = null;
}
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
this.getList();
},
handleFileError(err) {
if (this.loadingInstance) {
this.loadingInstance.close();
this.loadingInstance = null;
}
this.upload.isUploading = false;
this.loading = false;
this.$message.error('导入失败,请重试');
},
// 提交上传文件
submitFileForm() {
/* 1. 打开全屏加载遮罩 */
this.loadingInstance = this.$loading({
lock: true,
fullscreen: true, // 覆盖整页
text: '正在导入中,请稍候…',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.4)'
});
this.$refs.upload.submit();
},
/** 路由 添加采购计划 */
addWarehouseTask(){
this.$router.push("/oa/warehouse-data/addTask");
}
}
};
</script>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>

View File

@@ -0,0 +1,641 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="操作时间" prop="signTime">
<el-date-picker v-model="searchTime" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
v-hasPermi="['oa:oaOutWarehouse:remove']">删除
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="TaskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" />
<el-table-column label="采购单编号" align="center" prop="masterNum">
<template slot-scope="scope">
<el-input v-model="scope.row.masterNum" size="mini" placeholder="请输入采购单编号"
@blur="updateMasterRemark(scope.row)" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'warning'">{{
scope.row.status === 1 ? "完成" : "未完成"
}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="最近截止日期" align="center" prop="nearestEndTime">
<template slot-scope="scope" v-if="scope.row.nearestEndTime != null && scope.row.status !== 1">
<template v-if="dayDiff(scope.row.nearestEndTime) > 3">
<!-- 超过 3 正常显示 -->
<span>{{ parseTime(scope.row.nearestEndTime, '{y}-{m}-{d}') }}</span>
</template><template v-else-if="dayDiff(scope.row.nearestEndTime) > 0">
<!-- 未来 13 -->
<el-tag type="warning" effect="plain">
剩余{{ dayDiff(scope.row.nearestEndTime) }}
</el-tag>
</template><template v-else-if="dayDiff(scope.row.nearestEndTime) === 0">
<!-- 今天到期 -->
<el-tag type="danger" effect="plain">
<i class="el-icon-warning-outline"></i> 今日过期
</el-tag>
</template><template v-else>
<!-- 已过期 -->
<el-tag type="danger" effect="plain">
逾期{{ Math.abs(dayDiff(scope.row.endTime)) }}
</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="操作时间" align="center" prop="signTime">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.signTime, "{y}-{m}-{d}") }}</span>
</template>
</el-table-column>
<el-table-column label="操作人" align="center" prop="signUser" />
<el-table-column label="备注" align="center" prop="remark">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" size="mini" type="textarea" placeholder="请输入备注"
@blur="updateMasterRemark(scope.row)" />
</template>
</el-table-column>
<el-table-column label="需求编号" align="center" prop="requirementNum">
<template slot-scope="scope">
<el-select v-model="scope.row.requirementId" placeholder="请选择需求编号" filterable clearable
@change="updateMasterRemark(scope.row)">
<el-option v-for="item in requirementList" :key="item.requirementId" :label="item.title"
:value="item.requirementId" />
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-search" @click="showDetail(scope.row)">查看
</el-button>
<el-button size="mini" type="text" icon="el-icon-finished" v-if="scope.row.status === 0"
@click="handleIn(scope.row)">执行入库
</el-button>
<el-button size="mini" type="text" icon="el-icon-check" v-if="scope.row.status === 0"
@click="handComplete(scope.row)">完成
</el-button>
<el-button size="mini" type="text" icon="el-icon-download" @click="handleExport(scope.row)">导出
</el-button>
<el-button size="mini" type="text" icon="el-icon-remove" @click="handleDelete(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<el-drawer size="70%" :title="parseTime(searchItem.signTime) + '-采购单'" :visible.sync="drawer">
<el-table v-loading="loading" :data="warehouseTaskList">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" />
<el-table-column label="物料名" align="center" prop="name" />
<el-table-column label="采购数量" align="center" prop="taskInventory" />
<el-table-column label="单位" align="center" prop="unit" />
<el-table-column label="品牌" align="center" prop="brand" />
<el-table-column label="型号" align="center" prop="model" />
<el-table-column label="规格" align="center" prop="specifications" />
<el-table-column label="操作" align="center" prop="remark">
<template slot-scope="scope">
<el-button type="text" icon="el-icon-edit" @click="handleRemoveTask(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-drawer>
<el-drawer size="70%" :title="parseTime(searchItem.signTime, '{y}_{m}_{d}') + '_采购单'"
:visible.sync="completeDrawer">
<!-- 工具栏 -->
<div style="display:flex;justify-content:space-between;margin-bottom:10px">
<!-- 模式切换 -->
<el-radio-group v-model="mode" size="mini">
<el-radio-button label="single">单个入库</el-radio-button>
<el-radio-button label="batch">批量操作</el-radio-button>
</el-radio-group>
</div>
<el-table v-loading="loading" :data="warehouseTaskList" ref="warehouseTable">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" />
<el-table-column label="物料名" align="center" prop="name" />
<el-table-column label="截止日期" align="center" prop="endTime">
<template slot-scope="scope" v-if="scope.row.endTime != null">
<template v-if="dayDiff(scope.row.endTime) > 3">
<!-- 超过 3 正常显示 -->
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
</template><template v-else-if="dayDiff(scope.row.endTime) > 0">
<!-- 未来 13 -->
<el-tag type="warning" effect="plain">
剩余{{ dayDiff(scope.row.endTime) }}
</el-tag>
</template><template v-else-if="dayDiff(scope.row.endTime) === 0">
<!-- 今天到期 -->
<el-tag type="danger" effect="plain">
<i class="el-icon-warning-outline"></i> 今日过期
</el-tag>
</template><template v-else>
<!-- 已过期 -->
<el-tag type="danger" effect="plain">
逾期{{ Math.abs(dayDiff(scope.row.endTime)) }}
</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="采购数量" align="center" prop="taskInventory" />
<el-table-column label="采购价格" align="center" prop="price">
<template slot-scope="scope">
<el-input v-model="scope.row.price" v-if="scope.row.taskStatus !== 2" type="number"></el-input>
<span v-else>
已经操作入库录入价格请前往入库明细查看
</span>
</template>
</el-table-column>
<el-table-column label="品牌" align="center" prop="brand" />
<el-table-column label="规格" align="center" prop="specifications" />
<el-table-column label="备注" align="center" prop="remark">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" size="mini" placeholder="请输入备注" :disabled="scope.row.taskStatus === 2"
@blur="updateRemark(scope.row)" />
</template>
</el-table-column>
<!-- 操作列单行按钮 -->
<!-- 新增状态列 -->
<el-table-column label="状态" prop="taskStatus" width="140" align="center">
<template slot-scope="scope">
<!-- 单个模式可编辑 -->
<el-tag type="success" v-if="scope.row.taskStatus === 2">完成</el-tag>
<!-- 单个模式下可编辑动态过滤选项 -->
<el-select v-else-if="mode === 'single'" v-model="scope.row.taskStatus" size="mini" placeholder="状态"
@change="handleUpdateTask(scope.row)">
<el-option v-for="s in filteredStatusOptions(scope.row)" :key="s.value" :value="s.value"
:label="s.label" />
</el-select>
<!-- 批量模式只显示 -->
<span v-else>{{ statusLabel(scope.row.taskStatus) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button size="mini" type="danger" v-if="scope.row.taskStatus !== 2"
@click="handleBatchDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="display: flex; justify-content: flex-end; margin: 20px">
<!-- 批量模式下才显示 -->
<div v-if="mode === 'batch'">
<el-select v-model="batchStatus" size="mini" placeholder="选择批量状态">
<el-option v-for="s in statusOptions" :key="s.value" :value="s.value" :label="s.label" />
</el-select>
</div>
<el-button @click="submitComplete" v-if="mode === 'batch'" size="mini" type="success">执行入库</el-button>
<el-button @click="completeDrawer = false" size="mini">关闭</el-button>
</div>
</el-drawer>
</div>
</template>
<script>
import { listRequirements } from "@/api/oa/requirement";
import {
addOaWarehouseMaster,
delOaWarehouseMaster, listOaWarehouseMaster,
updateOaWarehouseMaster,
} from "@/api/oa/warehouse/warehouseMaster";
import {
delOaWarehouseTask,
getOaWarehouseTaskByMasterId,
updateOaWarehouseTask,
updateOaWarehouseTaskBatch,
updateOaWarehouseTaskStatus,
updateTaskRemark
} from "@/api/oa/warehouse/warehouseTask";
export default {
name: "OaOutWarehouse",
data () {
return {
completeDrawer: false,
mode: 'single',
batchStatus: null, // 批量入库时选择的状态
searchTime: [], // 搜索时间范围
// 细节数据
detailData: {},
// 抽屉
drawer: false,
// 选中项目名称
selectedProject: "",
// 查看详情弹窗
outDetail: {},
// 弹窗标志
detail: false,
// 绑定项目详情
projectDetail: {},
// 物料信息详情
warehouseDetail: {},
// 入库列表
TaskList: [],
// 按钮loading
buttonLoading: false,
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
selectedRows: [],
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 弹出层标题
title: "",
// 选择对象
searchItem: {},
// 是否显示弹出层
open: false,
warehouseTaskList: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 50,
type: 2,
},
// 导出参数
exportParams: {
pageNum: 1,
pageSize: 10,
},
statusOptions: [
{ value: 0, label: '未采购' },
{ value: 1, label: '在途' },
{ value: 2, label: '完成' },
{ value: 3, label: '作废' }
],
// 表单参数
form: {},
currentMasterId: null,
requirementList: [],
};
},
created () {
this.getList();
this.getRequirementList();
},
methods: {
// 获取需求列表
getRequirementList () {
listRequirements({
pageNum: 1,
pageSize: 1000,
}).then((res) => {
this.requirementList = res.rows;
});
},
// 返回 endTime 与今天(不含时分秒)的差值(单位:天)
dayDiff (endTime) {
const end = new Date(endTime)
const now = new Date()
end.setHours(0, 0, 0, 0)
now.setHours(0, 0, 0, 0)
// 正数 = future days 0 = today 负数 = past days
return Math.floor((end - now) / (1000 * 60 * 60 * 24))
},
// 根据 price 动态过滤:如果行 price 为空,就干掉 value===2
filteredStatusOptions (row) {
if (row.price == null || row.price === '') {
return this.statusOptions.filter(opt => opt.value !== 2);
}
return this.statusOptions;
},
/** 单个入库接口 */
handleUpdateTask (row) {
this.loading = true;
// 这里如果用户选了“完成”但 price 还是空,你也可以额外做下兜底校验
if (row.taskStatus === 2 && (row.price == null || row.price === '')) {
this.$message.error('请先填写价格,才能标记为完成');
row.taskStatus = 0; // 或者恢复到之前的状态
return;
}
updateOaWarehouseTaskStatus(row).then(response => {
getOaWarehouseTaskByMasterId(row.masterId).then((res) => {
this.currentMasterId = row.masterId;
this.warehouseTaskList = res.data;
this.loading = false;
this.$modal.msgSuccess("操作成功")
this.getList();
});
})
},
/** 状态值 → 文字 */
statusLabel (val) {
const item = this.statusOptions.find(s => s.value === val)
return item ? item.label : ''
},
handleRemoveTask (row) {
this.$confirm('确定删除该物料吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true;
delOaWarehouseTask(row.taskId).then((res) => {
getOaWarehouseTaskByMasterId(this.currentMasterId).then((res) => {
this.warehouseTaskList = res.data;
this.$message.success('删除成功')
})
})
.finally(() => {
this.loading = false;
})
})
},
/** 批量删除 */
handleBatchDelete (row) {
let rows = this.$refs.warehouseTable.selection
if (!rows.length) {
rows = [row]
}
if (!rows.length) return this.$message.warning('请先勾选物料')
const taskIds = rows.map(row => row.taskId)
this.$confirm(`确认删除 ${rows.length} 条记录?`, '提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
delOaWarehouseTask(taskIds).then((res) => {
this.getList();
this.drawer = false;
this.$message.success('删除成功')
})
})
},
/** 单行删除 */
handleDeleteTask (index) {
this.warehouseTaskList.splice(index, 1)
this.$message.success('删除成功')
},
submitComplete () {
const rows = this.$refs.warehouseTable.selection || []
if (!rows.length) {
return this.$message.warning('请先勾选物料')
}
if (this.batchStatus === null) {
return this.$message.warning('请选择批量状态')
}
// 前端直接设值(实际项目可调用接口)
rows.forEach(r => { r.taskStatus = this.batchStatus })
// 批量入库采购单
updateOaWarehouseTaskBatch(rows).then(res => {
this.getList();
this.drawer = false;
this.$message.success(`已批量入库 ${rows.length}`)
})
},
/** 执行入库操作 */
handleIn (row) {
// 更新采购单情况
this.completeDrawer = true;
this.searchItem = row;
this.form = row;
this.loading = true;
this.currentMasterId = row.masterId;
getOaWarehouseTaskByMasterId(row.masterId).then((res) => {
this.currentMasterId = row.masterId;
this.warehouseTaskList = res.data;
this.loading = false;
});
},
/** 执行完成操作, 这里只是把状态改为1 */
handComplete (row) {
this.form = row;
this.form.status = 1;
updateOaWarehouseMaster(this.form).then((res) => {
this.$modal.msgSuccess("操作成功");
this.getList();
});
},
getDateStr (date) {
if (!date) {
return ''
}
return this.parseTime(date, '{y}-{m}-{d} {h}:{i}:{s}')
},
/** 查询仓库入库列表 */
getList () {
this.loading = true;
// 处理日期范围查询参数
if (this.searchTime && this.searchTime.length === 2) {
this.queryParams.startTime = this.getDateStr(this.searchTime[0]);
this.queryParams.endTime = this.getDateStr(this.searchTime[1]);
} else {
this.queryParams.startTime = '';
this.queryParams.endTime = '';
}
listOaWarehouseMaster(this.queryParams).then((res) => {
this.TaskList = res.rows;
this.total = res.total;
this.loading = false;
});
},
// 取消按钮
cancel () {
this.open = false;
this.reset();
},
// 表单重置
reset () {
this.form = {
projectId: undefined,
warehouseList: [],
remark: ''
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery () {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery () {
this.resetForm("queryForm");
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange (selection) {
this.ids = selection.map((item) => item.masterId);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
/** 新增按钮操作 */
handleAdd () {
this.reset();
this.open = true;
if (this.drawer) {
// 如果抽屉是打开的说明是从项目处进入的新增从而加入projectId
this.projectFlag = true;
this.form.projectId = this.selectedProject.projectId;
this.form.masterId = this.searchItem.masterId;
this.form.projectName = this.searchItem.projectName;
this.form.masterNum = this.searchItem.masterNum;
}
this.title = "添加仓库入库";
this.form.remark = '';
},
/** 修改按钮操作 */
handleUpdate (row) {
this.loading = true;
this.reset();
const id = row.masterId || this.ids;
},
/** 提交按钮 */
submitForm () {
this.$refs["form"].validate((valid) => {
if (valid) {
this.buttonLoading = true;
if (this.form.masterId != null) {
updateOaWarehouseMaster(this.form)
.then((response) => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.drawer = false;
this.getList();
})
.finally(() => {
this.buttonLoading = false;
});
} else {
this.form.type = 1;
addOaWarehouseMaster(this.form)
.then((response) => {
this.$modal.msgSuccess("新增成功");
this.open = false;
// this.getList();
})
.finally(() => {
this.buttonLoading = false;
});
}
}
});
},
/** 删除按钮操作 */
handleDelete (row) {
const ids = row.masterId || this.ids;
this.$modal
.confirm('是否确认删除仓库入库编号为"' + ids + '"的数据项?')
.then(() => {
this.loading = true;
return delOaWarehouseMaster(ids);
})
.then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
this.open = false;
this.drawer = false;
})
.catch(() => {
})
.finally(() => {
this.loading = false;
});
},
/** 导出按钮操作 */
handleExport (row) {
this.exportParams.masterId = row.masterId;
this.download(
"oa/oaWarehouseTask/export",
{
...this.exportParams,
},
`采购单_${new Date().getTime()}.xlsx`
);
},
// 查看入库单独条目详情
showDetail (row) {
this.drawer = true;
this.searchItem = row;
this.loading = true;
getOaWarehouseTaskByMasterId(row.masterId).then((res) => {
this.currentMasterId = row.masterId;
this.warehouseTaskList = res.data;
this.loading = false;
});
},
// 修改采购单详情的备注
updateRemark (row) {
// 只提交remark字段避免无关字段被覆盖
updateOaWarehouseTask({
taskId: row.taskId,
remark: row.remark
}).then(() => {
this.$message.success('备注已保存');
});
},
// 修改采购单据的备注
updateMasterRemark (row) {
updateTaskRemark({
masterId: row.masterId,
remark: row.remark,
masterNum: row.masterNum,
requirementId: row.requirementId ? row.requirementId : 0,
}).then(() => {
this.$message.success('已保存');
});
},
},
};
</script>