Files
klp-oa/klp-ui/src/views/wms/post/InvCount/apply.vue
砂糖 39eaab139e feat: 新增盘库管理全流程功能模块
1.  新增批量新增盘库差异记录API
2.  新增盘库Excel对比工具函数
3.  新增盘库申请页面与库区明细组件
4.  优化流程图页面,新增流程图下载功能
5.  重构盘库主页面流程状态与操作逻辑
6.  新增多组件拆分与页面模块化改造
2026-06-26 15:41:21 +08:00

170 lines
15 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 count-container">
<DragResizePanel :initialSize="280" :minSize="280" :maxSize="600">
<template #panelA>
<div class="left-panel">
<div class="panel-header">
<div class="header-title"><i class="el-icon-s-check"></i><span>盘库申请</span><el-button size="mini" type="text" icon="el-icon-refresh" @click="getList" title="刷新列表" /></div>
<el-select v-model="queryParams.planStatus" size="mini" @change="handleQuery" class="header-filter">
<el-option label="草稿" :value="0" />
<el-option label="待审批" :value="1" />
</el-select>
</div>
<div class="search-row"><el-input v-model="queryParams.planCode" placeholder="搜索计划编号..." clearable prefix-icon="el-icon-search" size="small" @keyup.enter.native="handleQuery" @clear="handleQuery" /><el-button type="primary" size="small" @click="handleAdd"><i class="el-icon-plus"></i></el-button></div>
<div v-loading="loading" class="list-body">
<div v-for="item in dataList" :key="item.planId" class="list-item" :class="{ active: currentRow && currentRow.planId === item.planId }" @click="handleRowClick(item)">
<div class="item-main"><span class="item-title">{{ item.planCode }}</span><span class="item-sub">{{ item.planName }}</span></div>
<div class="item-meta"><el-tag v-if="item.planStatus === 0" type="info" size="mini">草稿</el-tag><el-tag v-else size="mini">待审批</el-tag></div>
</div>
<div v-if="dataList.length === 0 && !loading" class="list-empty"><i class="el-icon-folder-opened"></i><span>暂无盘库计划</span></div>
</div>
<div class="list-footer"><pagination :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /></div>
</div>
</template>
<template #panelB>
<div class="right-panel">
<div v-if="!currentRow" class="empty-tip"><i class="el-icon-info"></i><span>请在左侧列表中选择一条盘库计划</span></div>
<div v-else v-loading="detailLoading" class="detail-content">
<div class="doc-header">
<div class="doc-header-top">
<div class="doc-title-group"><div class="doc-title">{{ currentRow.planCode }}</div><div class="doc-subtitle">Application</div></div>
<div class="doc-header-right">
<el-button size="mini" type="text" icon="el-icon-refresh" @click="handleRefreshDetail">刷新</el-button>
<el-button v-if="currentRow.planStatus === 0" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(currentRow)">编辑</el-button>
<el-button v-if="currentRow.planStatus === 0" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(currentRow)">删除</el-button>
</div>
</div>
<div class="doc-status-row"><span class="doc-status-label">Status</span><el-tag v-if="currentRow.planStatus === 0" type="info" size="small">草稿</el-tag><el-tag v-else size="small">待审批</el-tag></div>
</div>
<div class="detail-meta">
<span><i class="el-icon-document"></i>{{ currentRow.planName }}</span>
<span v-if="currentRow.countDate"><i class="el-icon-date"></i>{{ parseTime(currentRow.countDate, '{y}-{m}-{d}') }}</span>
<span v-if="currentRow.countUserName"><i class="el-icon-user-solid"></i>{{ currentRow.countUserName }}</span>
</div>
<CountFlowSection :planStatus="currentRow.planStatus" />
<el-divider />
<div class="section-title">库区盘点明细 <span class="en-sub">· Warehouses</span>
<el-button v-if="currentRow.planStatus === 0" size="mini" type="primary" plain icon="el-icon-plus" @click="handleAddWarehouse">绑定库区</el-button>
</div>
<WarehouseDetailPanel ref="whPanel"
:planId="currentRow.planId"
:planStatus="currentRow.planStatus"
@submit-approval="handleSubmitApproval"
/>
<div class="section-gap" />
<div class="section-title">备注 <span class="en-sub">· Remarks</span></div>
<div class="remark-content">{{ currentRow.remark || '无' }}</div>
</div>
</div>
</template>
</DragResizePanel>
<el-dialog :title="planDialogTitle" :visible.sync="planDialogOpen" width="650px" append-to-body>
<el-form ref="planForm" :model="planForm" label-width="100px">
<el-form-item label="计划名称" prop="planName"><el-input v-model="planForm.planName" /></el-form-item>
<el-form-item label="盘库日期" prop="countDate"><el-date-picker v-model="planForm.countDate" type="date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item>
<el-form-item label="截止时间" prop="deadlineTime"><el-date-picker v-model="planForm.deadlineTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" style="width:100%" /></el-form-item>
<el-form-item label="盘点人"><el-input v-model="planForm.countUserName" /></el-form-item>
<el-form-item label="负责人"><el-input v-model="planForm.principalUserName" /></el-form-item>
<el-form-item label="参与人"><el-input v-model="planForm.participantNames" /></el-form-item>
<el-form-item label="备注"><el-input v-model="planForm.remark" type="textarea" :rows="3" /></el-form-item>
</el-form>
<div slot="footer"><el-button :loading="btnLoading" type="primary" @click="submitPlan">确定</el-button><el-button @click="planDialogOpen = false">取消</el-button></div>
</el-dialog>
<el-dialog title="绑定库区" :visible.sync="whDialogOpen" width="550px" append-to-body>
<el-form :model="whForm" label-width="120px">
<el-form-item label="逻辑库区"><WarehouseSelect ref="wsRef" v-model="whForm.warehouseId" @change="onWhChange" /></el-form-item>
<el-form-item label="实际库区"><ActualWarehouseL1L2Select ref="awsRef" v-model="whForm.actualWarehouseId" @change="onAwhChange" /></el-form-item>
<el-form-item label="出入库起始"><el-date-picker v-model="whForm.ioStartTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" style="width:100%" /></el-form-item>
<el-form-item label="出入库截止"><el-date-picker v-model="whForm.ioEndTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" style="width:100%" /></el-form-item>
</el-form>
<div slot="footer"><el-button :loading="whBtnLoading" type="primary" @click="submitWh">确定</el-button><el-button @click="whDialogOpen = false">取消</el-button></div>
</el-dialog>
</div>
</template>
<script>
import { listCountPlan, getCountPlan, addCountPlan, updateCountPlan, delCountPlan } from "@/api/flow/countPlan";
import { addCountPlanWarehouse } from "@/api/flow/countPlanWarehouse";
import DragResizePanel from "@/components/DragResizePanel/index.vue";
import CountFlowSection from "./components/CountFlowSection.vue";
import WarehouseDetailPanel from "./components/WarehouseDetailPanel.vue";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect/index.vue";
import ActualWarehouseL1L2Select from "@/components/KLPService/ActualWarehouseL1L2Select/index.vue";
import { parseTime } from '@/utils/klp'
export default {
name: "InvCountApply",
components: { DragResizePanel, CountFlowSection, WarehouseDetailPanel, WarehouseSelect, ActualWarehouseL1L2Select },
data() {
return {
loading: false, detailLoading: false, btnLoading: false, whBtnLoading: false, submitLoading: false, total: 0, dataList: [], currentRow: null,
queryParams: { pageNum: 1, pageSize: 10, planCode: undefined, planStatus: 0 },
planDialogOpen: false, planDialogTitle: '', planForm: {},
whDialogOpen: false, whForm: { warehouseId: undefined, actualWarehouseId: undefined, warehouseName: '', actualWarehouseName: '', ioStartTime: undefined, ioEndTime: undefined }
};
},
created() { this.getList(); },
methods: {
parseTime,
getList() { var self = this; this.loading = true; listCountPlan(this.queryParams).then(function(r) { self.dataList = r.rows; self.total = r.total; }).finally(function() { self.loading = false; }); },
handleQuery() { this.queryParams.pageNum = 1; this.getList(); },
handleRowClick(row) { this.currentRow = row; this.loadDetail(row.planId); },
loadDetail(planId) { var self = this; this.detailLoading = true; getCountPlan(planId).then(function(r) { self.currentRow = r.data; }).finally(function() { self.detailLoading = false; }); },
handleRefreshDetail() { if (this.currentRow) this.loadDetail(this.currentRow.planId); },
handleAdd() { this.planForm = {}; this.planDialogTitle = '新增盘库计划'; this.planDialogOpen = true; },
handleUpdate(row) { var self = this; getCountPlan(row.planId).then(function(r) { self.planForm = r.data; self.planDialogTitle = '修改'; self.planDialogOpen = true; }); },
submitPlan() { var self = this; this.btnLoading = true; var api = this.planForm.planId ? updateCountPlan : addCountPlan; api(this.planForm).then(function() { self.$modal.msgSuccess('成功'); self.planDialogOpen = false; self.getList(); }).finally(function() { self.btnLoading = false; }); },
handleDelete(row) { var self = this; this.$modal.confirm('确认删除?').then(function() { return delCountPlan(row.planId); }).then(function() { self.$modal.msgSuccess('已删除'); self.currentRow = null; self.getList(); }).catch(function() {}); },
handleSubmitApproval() { var self = this; this.$modal.confirm('确认提交审批?').then(function() { self.submitLoading = true; return updateCountPlan({ planId: self.currentRow.planId, planStatus: 1 }); }).then(function() { self.$modal.msgSuccess('已提交'); self.loadDetail(self.currentRow.planId); self.getList(); }).finally(function() { self.submitLoading = false; }); },
handleAddWarehouse() { this.whForm = { warehouseId: undefined, actualWarehouseId: undefined, warehouseName: '', actualWarehouseName: '', ioStartTime: undefined, ioEndTime: undefined }; this.whDialogOpen = true; },
onWhChange(val) { if (val && this.$refs.wsRef) { var o = this.$refs.wsRef.warehouseOptions; var f = o.find(function(x) { return x.warehouseId === val; }); this.whForm.warehouseName = f ? f.warehouseName : ''; } else this.whForm.warehouseName = ''; },
onAwhChange(val) { if (val && this.$refs.awsRef) { var o = this.$refs.awsRef.options; var f = o.find(function(x) { return x.actualWarehouseId === val; }); this.whForm.actualWarehouseName = f ? f.actualWarehouseName : ''; } else this.whForm.actualWarehouseName = ''; },
submitWh() { var self = this; if (!this.whForm.warehouseId && !this.whForm.actualWarehouseId) { this.$modal.msgWarning('请至少选择一个库区'); return; } this.whBtnLoading = true; addCountPlanWarehouse({ planId: this.currentRow.planId, warehouseId: this.whForm.warehouseId || undefined, actualWarehouseId: this.whForm.actualWarehouseId || undefined, warehouseName: this.whForm.warehouseName || undefined, actualWarehouseName: this.whForm.actualWarehouseName || undefined, ioStartTime: this.whForm.ioStartTime, ioEndTime: this.whForm.ioEndTime }).then(function() { self.$modal.msgSuccess('绑定成功'); self.whDialogOpen = false; self.$refs.whPanel.refreshAll(); }).finally(function() { self.whBtnLoading = false; }); }
}
};
</script>
<style scoped>
.count-container { height: calc(100vh - 84px); }
.left-panel { display: flex; flex-direction: column; height: 100%; background: #f5f7fa; border-right: 1px solid #e4e7ed; }
.panel-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px 8px; background: #f5f7fa; }
.header-title { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; color: #303133; }
.header-title i { color: #409eff; font-size: 16px; }
.header-filter { width: 100px; }
.search-row { display: flex; align-items: center; gap: 6px; padding: 0 14px 10px; background: #f5f7fa; }
.list-body { flex: 1; overflow-y: auto; padding: 0 6px; }
.list-item { display: flex; align-items: center; padding: 10px 12px; margin-bottom: 2px; cursor: pointer; border-radius: 6px; transition: all 0.15s; }
.list-item:hover { background: #ebeef5; }
.list-item.active { background: #d9ecff; }
.list-item.active .item-title { color: #409eff; font-weight: 600; }
.item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.item-title { font-size: 13px; font-weight: 500; color: #303133; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.item-sub { font-size: 12px; color: #909399; }
.item-meta { flex-shrink: 0; margin: 0 8px; }
.list-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 0; color: #c0c4cc; font-size: 13px; gap: 8px; }
.list-footer { border-top: 1px solid #e4e7ed; padding: 2px 8px 0; background: #f5f7fa; }
.right-panel { height: 100%; overflow-y: auto; padding: 12px 16px; background: #faf8f5; }
.detail-content { margin: 0 auto; background: #fff; padding: 28px 32px 36px; box-shadow: 0 1px 4px rgba(0,0,0,0.06), 0 2px 12px rgba(0,0,0,0.04); min-height: 100%; }
.empty-tip { display: flex; align-items: center; justify-content: center; height: 100%; color: #909399; font-size: 14px; gap: 8px; }
.doc-header { margin-bottom: 18px; padding-bottom: 14px; border-bottom: 2px solid #1a3c6e; }
.doc-header-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.doc-title-group { flex: 1; min-width: 0; }
.doc-title { font-size: 24px; font-weight: 700; color: #1a1a1a; line-height: 1.3; letter-spacing: 0.5px; }
.doc-subtitle { font-size: 12px; color: #8c8c8c; font-style: italic; letter-spacing: 0.8px; margin-top: 2px; }
.doc-header-right { flex-shrink: 0; }
.doc-status-row { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.doc-status-label { font-size: 11px; color: #8c8c8c; letter-spacing: 0.3px; }
.detail-meta { display: flex; flex-wrap: wrap; gap: 16px; font-size: 12px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #e0dcd6; }
.detail-meta span { display: inline-flex; align-items: center; gap: 4px; }
.detail-meta i { font-size: 13px; }
.section-title { font-size: 15px; font-weight: 700; color: #1a1a1a; margin: 22px 0 12px; padding: 0 0 10px; border-bottom: 1px solid #d4d0c8; display: flex; align-items: center; gap: 10px; }
.section-title .en-sub { font-size: 11px; font-weight: 400; color: #8c8c8c; font-style: italic; }
.remark-content { padding: 12px 16px; background: #faf8f5; border: 1px solid #e8e4de; border-radius: 2px; font-size: 13px; line-height: 1.8; color: #1a1a1a; }
.section-gap { height: 16px; }
</style>