Files
klp-oa/klp-ui/src/views/wms/post/InvCount/apply.vue
砂糖 3d5c0a7281 feat(wms盘库): 完成多项功能升级与体验优化
1. 新增仓库选择组件多选支持,适配批量库区操作
2. 售后单新增自动生成编号与当前日期填充
3. 盘点计划新增流程审批操作按钮,支持驳回与审批通过
4. 优化库区绑定页面,支持编辑删除已绑定库区
5. 替换人员选择为可搜索下拉框,支持多选参与人
6. 新增驳回审批弹窗,提供快捷驳回理由
7. 优化表单提交逻辑,适配多库区数据格式
2026-06-27 14:38:33 +08:00

170 lines
18 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">
<PlanDetailPanel ref="detailPanel"
:planId="currentRow ? currentRow.planId : null"
subtitle="Application"
emptyText="请在左侧列表中选择一条盘库计划"
@submit-approval="handleSubmitApproval"
@approve="handlePlanApprove"
@reject="handleReject"
@edit-plan="handleUpdate"
@delete-plan="handleDelete"
@bind-warehouse="handleAddWarehouse"
@edit-warehouse="handleEditWarehouse"
@delete-warehouse="handleDeleteWarehouse"
/>
</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" placeholder="请输入计划名称" /></el-form-item>
<el-form-item label="盘库日期" prop="countDate"><el-date-picker v-model="planForm.countDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择盘库日期" 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" placeholder="请选择截止时间" style="width:100%" /></el-form-item>
<el-form-item label="盘点人"><el-select v-model="planForm.countUserName" filterable allow-create default-first-option placeholder="请输入盘点人" style="width:100%"><el-option v-for="e in employeeList" :key="e" :label="e" :value="e" /></el-select></el-form-item>
<el-form-item label="负责人"><el-select v-model="planForm.principalUserName" filterable allow-create default-first-option placeholder="请输入负责人" style="width:100%"><el-option v-for="e in employeeList" :key="e" :label="e" :value="e" /></el-select></el-form-item>
<el-form-item label="参与人"><el-select v-model="participantNamesArr" multiple filterable allow-create default-first-option placeholder="请输入参与人员" style="width:100%"><el-option v-for="e in employeeList" :key="e" :label="e" :value="e" /></el-select></el-form-item>
<el-form-item label="备注"><el-input v-model="planForm.remark" type="textarea" :rows="3" placeholder="请输入备注" /></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="whDialogTitle" :visible.sync="whDialogOpen" width="550px" append-to-body>
<el-form :model="whForm" label-width="120px" :key="'wh-form-' + whDialogMode + '-' + (editingRelId || 0)">
<el-form-item label="逻辑库区"><WarehouseSelect ref="wsRef" v-model="whForm.warehouseIds" multiple @change="onWhChange" /></el-form-item>
<el-form-item label="实际库区"><ActualWarehouseL1L2Select ref="awsRef" v-model="whForm.actualWarehouseIds" multiple @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>
<!-- 驳回审批对话框 -->
<el-dialog title="驳回审批" :visible.sync="rejectDialogVisible" width="520px" append-to-body>
<div class="reject-quick-reasons">
<span class="reject-quick-label">快捷理由</span>
<el-tag v-for="(r, i) in rejectQuickReasons" :key="i" class="reject-tag" size="small" @click="rejectReason = r">{{ r }}</el-tag>
</div>
<el-input v-model="rejectReason" type="textarea" :rows="4" placeholder="请输入驳回原因" style="margin-top:10px;" />
<div slot="footer" class="dialog-footer">
<el-button type="danger" @click="confirmReject" :disabled="!rejectReason.trim()">确认驳回</el-button>
<el-button @click="rejectDialogVisible = false">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listCountPlan, addCountPlan, updateCountPlan, delCountPlan } from "@/api/flow/countPlan";
import { addCountPlanWarehouse, updateCountPlanWarehouse, delCountPlanWarehouse } from "@/api/flow/countPlanWarehouse";
import { listEmployeeInfo } from "@/api/wms/employeeInfo";
import DragResizePanel from "@/components/DragResizePanel/index.vue";
import PlanDetailPanel from "./components/PlanDetailPanel.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, PlanDetailPanel, WarehouseSelect, ActualWarehouseL1L2Select },
data() {
return {
loading: 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: {}, employeeList: [], participantNamesArr: [],
whDialogOpen: false, whDialogTitle: '绑定库区', whDialogMode: 'add', editingRelId: null, whForm: { warehouseIds: [], actualWarehouseIds: [], warehouseName: '', actualWarehouseName: '', ioStartTime: undefined, ioEndTime: undefined },
rejectDialogVisible: false, rejectReason: '', rejectQuickReasons: ['盘点计划信息不完整,请补充后重新提交', '库区绑定有误,请核实库区信息', '盘点时间安排不合理,请调整', '盘点人员安排需调整', '缺少必要的备注说明']
};
},
created() { this.getList(); this.loadEmployees(); },
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; },
loadEmployees() { var self = this; listEmployeeInfo({ pageNum: 1, pageSize: 9999 }).then(function(r) { self.employeeList = (r.rows || []).map(function(e) { return e.name || ''; }).filter(Boolean); }); },
handleAdd() { this.planForm = { planName: new Date().toLocaleDateString().replace(/\//g, '-') + '盘库计划' }; this.participantNamesArr = []; this.planDialogTitle = '新增盘库计划'; this.planDialogOpen = true; },
handleUpdate(row) { this.planForm = Object.assign({}, row); this.participantNamesArr = row.participantNames ? row.participantNames.split(',') : []; this.planDialogTitle = '修改'; this.planDialogOpen = true; },
submitPlan() { var self = this; this.btnLoading = true; this.planForm.planCode = this.planForm.planName; this.planForm.participantNames = this.participantNamesArr.join(','); 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.getList(); self.currentRow = null; }).finally(function() { self.submitLoading = false; }); },
handlePlanApprove() { var self = this; this.$modal.confirm('确认通过审批?通过后进入盘库执行阶段。').then(function() { return updateCountPlan({ planId: self.currentRow.planId, planStatus: 2 }); }).then(function() { self.$modal.msgSuccess('审批通过'); self.getList(); self.currentRow = null; }); },
handleReject() { this.rejectReason = ''; this.rejectDialogVisible = true; },
confirmReject() { var self = this; var targetStatus = self.currentRow.planStatus === 3 ? 2 : 0; var rejectMsg = targetStatus === 2 ? '驳回至盘库执行' : '驳回至草稿'; updateCountPlan({ planId: self.currentRow.planId, planStatus: targetStatus, remark: self.rejectReason }).then(function() { self.$modal.msgSuccess('已' + rejectMsg); self.rejectDialogVisible = false; self.currentRow = null; self.getList(); }); },
handleAddWarehouse() { this.whDialogMode = 'add'; this.editingRelId = null; this.whDialogTitle = '绑定库区'; this.whForm = { warehouseIds: [], actualWarehouseIds: [], warehouseName: '', actualWarehouseName: '', ioStartTime: undefined, ioEndTime: undefined }; this.whDialogOpen = true; },
handleEditWarehouse(wh) { console.log('[apply] wh:', JSON.parse(JSON.stringify(wh))); this.whDialogMode = 'edit'; this.editingRelId = wh.relId; this.whDialogTitle = '编辑库区'; var ids = wh.warehouseIds ? String(wh.warehouseIds).split(',') : []; var aids = wh.actualWarehouseIds ? String(wh.actualWarehouseIds).split(',') : []; console.log('[apply] parsed ids:', ids, 'aids:', aids); this.whForm = { warehouseIds: ids, actualWarehouseIds: aids, warehouseName: wh.warehouseName || '', actualWarehouseName: wh.actualWarehouseName || '', ioStartTime: wh.ioStartTime || undefined, ioEndTime: wh.ioEndTime || undefined }; console.log('[apply] whForm.warehouseIds:', this.whForm.warehouseIds, 'actualWarehouseIds:', this.whForm.actualWarehouseIds); this.whDialogOpen = true; },
handleDeleteWarehouse(wh) { var self = this; this.$modal.confirm('确认删除库区"' + (wh.warehouseName || wh.actualWarehouseName || '') + '"').then(function() { return delCountPlanWarehouse(wh.relId); }).then(function() { self.$modal.msgSuccess('已删除'); self.$refs.detailPanel.refresh(); }).catch(function() {}); },
onWhChange(val) { if (val && val.length && this.$refs.wsRef) { var o = this.$refs.wsRef.warehouseOptions; var names = val.map(function(id) { var f = o.find(function(x) { return x.warehouseId == id; }); return f ? f.warehouseName : ''; }).filter(Boolean); this.whForm.warehouseName = names.join(','); } else this.whForm.warehouseName = ''; },
onAwhChange(val) { if (val && val.length && this.$refs.awsRef) { var o = this.$refs.awsRef.options; var names = val.map(function(id) { var f = o.find(function(x) { return x.actualWarehouseId == id; }); return f ? f.actualWarehouseName : ''; }).filter(Boolean); this.whForm.actualWarehouseName = names.join(','); } else this.whForm.actualWarehouseName = ''; },
submitWh() { var self = this; if ((!this.whForm.warehouseIds || this.whForm.warehouseIds.length === 0) && (!this.whForm.actualWarehouseIds || this.whForm.actualWarehouseIds.length === 0)) { this.$modal.msgWarning('请至少选择一个库区'); return; } this.whBtnLoading = true; var data = { planId: this.currentRow.planId, warehouseIds: (this.whForm.warehouseIds && this.whForm.warehouseIds.length) ? this.whForm.warehouseIds.join(',') : undefined, actualWarehouseIds: (this.whForm.actualWarehouseIds && this.whForm.actualWarehouseIds.length) ? this.whForm.actualWarehouseIds.join(',') : undefined, warehouseName: this.whForm.warehouseName || undefined, actualWarehouseName: this.whForm.actualWarehouseName || undefined, ioStartTime: this.whForm.ioStartTime, ioEndTime: this.whForm.ioEndTime }; if (this.whDialogMode === 'edit') { data.relId = this.editingRelId; } var api = this.whDialogMode === 'edit' ? updateCountPlanWarehouse : addCountPlanWarehouse; api(data).then(function() { self.$modal.msgSuccess(self.whDialogMode === 'edit' ? '修改成功' : '绑定成功'); self.whDialogOpen = false; self.$refs.detailPanel.refresh(); }).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; }
.reject-quick-reasons { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.reject-quick-label { font-size: 13px; color: #606266; flex-shrink: 0; }
.reject-tag { cursor: pointer; user-select: none; }
.reject-tag:hover { opacity: 0.8; }
</style>