Files
fad_oa/ruoyi-ui/src/views/oa/oaWarehouse/task.vue
2026-06-05 13:54:11 +08:00

707 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="app-container">
<!-- 状态筛选 tabs全部 / 加急 / 未完成 / 已完成 -->
<el-tabs v-model="statusFilter" @tab-click="onStatusTabChange" class="compact-tabs">
<el-tab-pane label="全部" name="all" />
<el-tab-pane name="urgent">
<span slot="label" style="color:#f56c6c;">
<i class="el-icon-warning-outline"></i> 加急 ({{ stat.urgent }})
</span>
</el-tab-pane>
<el-tab-pane :label="`未完成 (${stat.undone})`" name="undone" />
<el-tab-pane :label="`已完成 (${stat.done})`" name="done" />
</el-tabs>
<el-form :model="queryParams" ref="queryForm" size="mini" :inline="true" v-show="showSearch"
label-width="68px" class="compact-search">
<el-form-item label="关联需求" prop="requirementId">
<el-select v-model="queryParams.requirementId" placeholder="选择需求" filterable clearable style="width: 200px">
<el-option v-for="item in requirementList" :key="item.requirementId" :label="item.title"
:value="item.requirementId" />
</el-select>
</el-form-item>
<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-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>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
<el-button type="primary" size="mini" icon="el-icon-plus" class="add-purchase-btn"
@click="$router.push('/oa/warehouse-data/addTask')">新建采购单</el-button>
<el-table v-loading="loading" :data="TaskList" @selection-change="handleSelectionChange" stripe size="small"
row-key="masterId" :row-class-name="rowClassName">
<el-table-column type="selection" width="44" align="center" />
<el-table-column label="操作时间" prop="signTime" width="100">
<template slot-scope="scope">{{ parseTime(scope.row.signTime, "{y}-{m}-{d}") }}</template>
</el-table-column>
<el-table-column label="操作人" prop="signUser" width="90" />
<el-table-column label="关联需求" min-width="220">
<template slot-scope="scope">
<el-select v-model="scope.row.requirementId" placeholder="选择需求" filterable clearable size="mini"
style="width: 100%" @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="状态" prop="status" width="90" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'warning'" size="mini">
{{ scope.row.status === 1 ? "已完成" : "未完成" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="200">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" size="mini" placeholder="点击编辑"
@blur="updateMasterRemark(scope.row)" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="260" 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" v-if="scope.row.status === 0"
@click="showDetail(scope.row)">执行入库</el-button>
<el-button size="mini" type="text" v-if="scope.row.status === 0"
@click="handComplete(scope.row)">完成</el-button>
<el-button size="mini" type="text" @click="handleExport(scope.row)">导出</el-button>
<el-button size="mini" type="text" style="color:#f56c6c" @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="(searchItem.masterNum || parseTime(searchItem.signTime, '{y}-{m}-{d}')) + ' - 物料明细'"
:visible.sync="drawer">
<div style="padding: 0 16px 16px;">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:8px;">
<div style="display:flex; align-items:center; gap:8px;">
<span style="font-weight:600;">物料明细{{ warehouseTaskList.length }} </span>
<el-radio-group v-model="mode" size="mini" v-if="searchItem.status === 0">
<el-radio-button label="single">单个操作</el-radio-button>
<el-radio-button label="batch">批量操作</el-radio-button>
</el-radio-group>
</div>
<div v-if="mode === 'batch' && searchItem.status === 0"
style="display:flex; align-items:center; gap:6px;">
<el-select v-model="batchStatus" size="mini" placeholder="批量设置状态" style="width:140px">
<el-option v-for="s in statusOptions" :key="s.value" :value="s.value" :label="s.label" />
</el-select>
<el-button size="mini" type="success" @click="submitComplete">执行入库</el-button>
</div>
</div>
<el-table v-loading="loading" :data="warehouseTaskList" size="mini" stripe ref="warehouseTable">
<el-table-column v-if="mode === 'batch' && searchItem.status === 0"
type="selection" width="44" align="center" />
<el-table-column label="物料名" prop="name" min-width="120" />
<el-table-column label="截止" prop="endTime" width="120" align="center">
<template slot-scope="s">
<template v-if="s.row.endTime != null && s.row.taskStatus !== 2">
<span v-if="dayDiff(s.row.endTime) > 3">{{ parseTime(s.row.endTime, '{y}-{m}-{d}') }}</span>
<el-tag v-else-if="dayDiff(s.row.endTime) > 0" type="warning" size="mini" effect="plain">{{ dayDiff(s.row.endTime) }}</el-tag>
<el-tag v-else-if="dayDiff(s.row.endTime) === 0" type="danger" size="mini" effect="plain">今日</el-tag>
<el-tag v-else type="danger" size="mini" effect="plain">{{ Math.abs(dayDiff(s.row.endTime)) }}</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="数量" prop="taskInventory" width="64" align="right" />
<el-table-column label="单位" prop="unit" width="56" />
<el-table-column label="单价" width="110" align="right">
<template slot-scope="s">
<el-input v-if="s.row.taskStatus !== 2 && searchItem.status === 0"
v-model="s.row.price" size="mini" type="number" placeholder="¥" />
<span v-else>¥{{ Number(s.row.price || 0).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="型号" prop="model" min-width="100" />
<el-table-column label="规格" prop="specifications" min-width="100" />
<el-table-column label="品牌" prop="brand" min-width="80" />
<el-table-column label="备注" prop="remark" min-width="140">
<template slot-scope="s">
<el-input v-model="s.row.remark" size="mini" placeholder="备注"
:disabled="s.row.taskStatus === 2 || searchItem.status === 1"
@blur="updateRemark(s.row)" />
</template>
</el-table-column>
<el-table-column label="收货单" width="240">
<template slot-scope="s">
<file-upload v-model="s.row.receiptDoc"
:limit="3"
:isShowTip="false"
:disabled="s.row.taskStatus === 2 || searchItem.status === 1"
class="cell-upload"
@input="onReceiptDocChange(s.row)" />
</template>
</el-table-column>
<el-table-column label="状态" prop="taskStatus" width="110" align="center">
<template slot-scope="s">
<el-tag v-if="s.row.taskStatus === 2" type="success" size="mini">完成</el-tag>
<el-select v-else-if="mode === 'single' && searchItem.status === 0"
v-model="s.row.taskStatus" size="mini" placeholder="状态"
@change="handleUpdateTask(s.row)">
<el-option v-for="opt in filteredStatusOptions(s.row)" :key="opt.value"
:value="opt.value" :label="opt.label" />
</el-select>
<el-tag v-else size="mini">{{ statusLabel(s.row.taskStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="60">
<template slot-scope="s">
<el-button v-if="s.row.taskStatus !== 2 && searchItem.status === 0"
size="mini" type="text" style="color:#f56c6c"
@click="handleBatchDelete(s.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-drawer>
</div>
</template>
<script>
import { listRequirements } from "@/api/oa/requirement";
import FileUpload from "@/components/FileUpload";
import AddPurchaseDialog from "./components/AddPurchaseDialog.vue";
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",
components: { AddPurchaseDialog, FileUpload },
data () {
return {
// 顶部状态筛选
statusFilter: 'all',
stat: { undone: 0, done: 0, urgent: 0 },
// 新建采购单 dialog
addDialogVisible: false,
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(() => {
this.getList()
this.drawer = false
this.$message.success(`已批量入库 ${rows.length}`)
})
},
// 单条收货单上传后立刻保存
onReceiptDocChange (row) {
updateOaWarehouseTask({
taskId: row.taskId,
receiptDoc: row.receiptDoc
}).then(() => {
this.$message.success('收货单已保存')
})
},
/** 执行入库操作 */
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 || 0;
this.loading = false;
});
this.refreshStat();
},
// 加急判定(备注里含「急」)
isUrgent (remark) {
return !!remark && remark.indexOf('急') !== -1
},
// 加急且未完成 → 行红底
rowClassName ({ row }) {
if (row.status !== 1 && this.isUrgent(row.remark)) return 'row-urgent'
return ''
},
// 拉总览数量(不分页快速 head 计数)
refreshStat () {
const base = { ...this.queryParams, pageNum: 1, pageSize: 1, status: undefined, remark: undefined };
Promise.all([
listOaWarehouseMaster({ ...base, status: 0 }),
listOaWarehouseMaster({ ...base, status: 1 }),
listOaWarehouseMaster({ ...base, remark: '急' })
]).then(([u, d, urg]) => {
this.stat.undone = u.total || 0
this.stat.done = d.total || 0
this.stat.urgent = urg.total || 0
})
},
// tab 切换
onStatusTabChange () {
// 重置过滤
this.queryParams.status = undefined
this.queryParams.remark = undefined
if (this.statusFilter === 'undone') {
this.queryParams.status = 0
} else if (this.statusFilter === 'done') {
this.queryParams.status = 1
} else if (this.statusFilter === 'urgent') {
this.queryParams.remark = '急'
}
this.queryParams.pageNum = 1
this.getList()
},
// 取消按钮
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>
<style lang="scss" scoped>
.add-purchase-btn.el-button {
position: absolute;
top: 12px;
right: 110px; /* 让出 right-toolbar 的位置 */
z-index: 3;
height: 24px;
line-height: 22px;
padding: 0 8px !important;
font-size: 11px !important;
border-radius: 4px;
i { font-size: 11px; margin-right: 2px; }
}
// 表格里的紧凑上传按钮 / 文件列表
.cell-upload {
::v-deep .upload-file-uploader { margin-bottom: 4px; }
::v-deep .el-upload .el-button {
padding: 4px 8px !important;
font-size: 12px !important;
height: 24px;
line-height: 14px;
}
// 已上传文件列表
::v-deep .upload-file-list {
margin: 0;
padding: 0;
.el-upload-list__item {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
height: 24px;
line-height: 22px;
margin: 2px 0 0 0 !important;
padding: 0 6px;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fafafa;
// 文件名:超出省略
.el-icon-document {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
color: #303133;
padding-right: 6px;
}
// 操作按钮:纯图标,不换行
.ele-upload-list__item-content-action {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 2px;
.el-button {
padding: 0 4px !important;
margin: 0 !important;
height: 20px;
line-height: 20px;
font-size: 0; // 隐藏文字
[class^="el-icon-"] {
font-size: 14px;
margin: 0;
}
}
}
}
}
}
</style>