<template>
|
<div class="app-container">
|
<div class="search_form" style="margin-bottom: 20px;">
|
<div>
|
<span class="search_title">盘点单号:</span>
|
<el-input v-model="searchForm.planNo" placeholder="请输入" clearable style="width: 240px; margin-right: 10px" @change="handleQuery" />
|
<span class="search_title">状态:</span>
|
<el-select v-model="searchForm.status" placeholder="请选择" clearable style="width: 240px" @change="handleQuery">
|
<el-option label="待执行" value="0" />
|
<el-option label="执行中" value="1" />
|
<el-option label="已完成" value="2" />
|
<el-option label="已取消" value="3" />
|
</el-select>
|
<el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
|
</div>
|
<div>
|
<el-button type="primary" @click="openForm('add')">新增盘点方案</el-button>
|
</div>
|
</div>
|
<div class="table_list">
|
<PIMTable
|
rowKey="id"
|
:column="tableColumn"
|
:tableData="tableData"
|
:tableLoading="tableLoading"
|
:page="page"
|
:isShowPagination="true"
|
@pagination="paginationChange"
|
/>
|
</div>
|
|
<!-- 新增/编辑盘点计划 -->
|
<FormDialog
|
v-model="dialogVisible"
|
:title="dialogTitle"
|
width="950px"
|
@close="resetForm"
|
@confirm="handleSubmit"
|
>
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
<el-divider content-position="left">基本信息</el-divider>
|
<el-row>
|
<el-col :span="8">
|
<el-form-item label="盘点单号">
|
<el-input v-model="form.planNo" disabled placeholder="系统自动生成" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="制单人" prop="createBy">
|
<el-select v-model="form.createBy" placeholder="请选择制单人" style="width: 100%" filterable>
|
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="制单日期">
|
<el-date-picker
|
v-model="form.createTime"
|
type="date"
|
placeholder="请选择"
|
value-format="YYYY-MM-DD"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row>
|
<el-col :span="8">
|
<el-form-item label="盘点人" prop="checkerId">
|
<el-select v-model="form.checkerId" placeholder="请选择盘点人" style="width: 100%" filterable>
|
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="计划日期" prop="planDate">
|
<el-date-picker
|
v-model="form.planDate"
|
type="date"
|
placeholder="请选择"
|
value-format="YYYY-MM-DD"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="备注">
|
<el-input v-model="form.remark" placeholder="请输入备注" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-divider content-position="left">盘点产品</el-divider>
|
<div class="mb10">
|
<el-button type="primary" size="small" @click="openProductDialog">选择产品</el-button>
|
<el-button size="small" @click="clearProducts">清空</el-button>
|
</div>
|
<PIMTable
|
:column="productColumn"
|
:tableData="form.items"
|
:isShowPagination="false"
|
height="350px"
|
/>
|
</el-form>
|
</FormDialog>
|
|
<!-- 选择产品弹框 -->
|
<ProductSelectDialog
|
v-model="productDialogVisible"
|
requestUrl="/stockInventory/pagestockInventoryNoQua"
|
@confirm="handleProductSelect"
|
/>
|
|
<!-- 录入盘点数据 -->
|
<FormDialog
|
v-model="checkDialogVisible"
|
title="录入盘点数据"
|
width="1000px"
|
operationType="detail"
|
@close="checkDialogVisible = false"
|
>
|
<el-alert
|
title="提示:录入实际盘点数量,系统将自动计算差异"
|
type="info"
|
:closable="false"
|
style="margin-bottom: 15px"
|
/>
|
<el-table
|
v-loading="checkLoading"
|
:data="checkItemList"
|
height="400"
|
border
|
>
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
<el-table-column prop="productName" label="产品名称" min-width="150" />
|
<el-table-column prop="model" label="规格型号" min-width="150" />
|
<el-table-column prop="unit" label="单位" width="80" align="center" />
|
<el-table-column prop="batchNo" label="批号" width="120" align="center" />
|
<el-table-column prop="systemQuantity" label="系统库存" width="100" align="center" />
|
<el-table-column label="盘点数量" width="120" align="center">
|
<template #default="{ row }">
|
<el-input-number
|
v-model="row.actualQuantity"
|
:min="0"
|
:precision="2"
|
:controls="false"
|
style="width: 100%"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="差异" width="100" align="center">
|
<template #default="{ row }">
|
<span :class="{ 'text-profit': (row.actualQuantity - row.systemQuantity) > 0, 'text-loss': (row.actualQuantity - row.systemQuantity) < 0 }">
|
{{ row.actualQuantity - row.systemQuantity }}
|
</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="备注" min-width="150">
|
<template #default="{ row }">
|
<el-input v-model="row.remark" placeholder="请输入备注" />
|
</template>
|
</el-table-column>
|
</el-table>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary" @click="handleSaveCheckData" :loading="submitLoading">保存</el-button>
|
<el-button @click="checkDialogVisible = false">取消</el-button>
|
</div>
|
</template>
|
</FormDialog>
|
|
<!-- 查看差异 -->
|
<FormDialog
|
v-model="diffDialogVisible"
|
title="盘点差异汇总"
|
width="1000px"
|
operationType="detail"
|
@close="diffDialogVisible = false"
|
>
|
<div class="diff-summary">
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<div class="stat-item">
|
<div class="stat-label">盘点产品总数</div>
|
<div class="stat-value">{{ diffSummary.totalCount }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<div class="stat-label">盘盈产品</div>
|
<div class="stat-value text-profit">{{ diffSummary.profitCount }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<div class="stat-label">盘亏产品</div>
|
<div class="stat-value text-loss">{{ diffSummary.lossCount }}</div>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<div class="stat-label">无差异</div>
|
<div class="stat-value">{{ diffSummary.normalCount }}</div>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
<PIMTable
|
:column="diffItemColumn"
|
:tableData="diffItemList"
|
:isShowPagination="false"
|
height="400px"
|
/>
|
<template #footer>
|
<div class="dialog-footer">
|
<!-- <el-button type="success" @click="handleGenerateFromDiff">生成盈亏处理单</el-button> -->
|
<el-button @click="diffDialogVisible = false">关闭</el-button>
|
</div>
|
</template>
|
</FormDialog>
|
|
<!-- 查看详情 -->
|
<FormDialog
|
v-model="viewDialogVisible"
|
title="盘点计划详情"
|
width="900px"
|
operationType="detail"
|
@close="viewDialogVisible = false"
|
>
|
<el-descriptions :column="2" border>
|
<el-descriptions-item label="盘点单号">{{ viewData.planNo }}</el-descriptions-item>
|
<el-descriptions-item label="盘点人">{{ viewData.checkerName }}</el-descriptions-item>
|
<el-descriptions-item label="制单人">{{ viewData.createBy }}</el-descriptions-item>
|
<el-descriptions-item label="制单日期">{{ viewData.createTime }}</el-descriptions-item>
|
<el-descriptions-item label="计划日期">{{ viewData.planDate }}</el-descriptions-item>
|
<el-descriptions-item label="状态">
|
<el-tag :type="getStatusType(viewData.status)">{{ getStatusText(viewData.status) }}</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="备注" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item>
|
</el-descriptions>
|
<div style="margin-top: 20px;">
|
<div class="section-title">盘点产品清单</div>
|
<PIMTable
|
:column="viewProductColumn"
|
:tableData="viewData.items"
|
:isShowPagination="false"
|
height="300px"
|
/>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="viewDialogVisible = false">关闭</el-button>
|
</div>
|
</template>
|
</FormDialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import PIMTable from '@/components/PIMTable/PIMTable.vue'
|
import FormDialog from '@/components/Dialog/FormDialog.vue'
|
import ProductSelectDialog from '@/views/basicData/product/ProductSelectDialog.vue'
|
import { userListNoPage } from '@/api/system/user.js'
|
import {
|
getStockCheckPlanPage,
|
addStockCheckPlan,
|
updateStockCheckPlan,
|
deleteStockCheckPlan,
|
submitApproval,
|
startCheck,
|
completeCheck,
|
getCheckItems,
|
saveCheckData,
|
getDiffSummary,
|
generateProfitLoss,
|
} from '@/api/inventoryManagement/stockCheck.js'
|
|
|
|
const tableData = ref([])
|
const tableLoading = ref(false)
|
const page = reactive({ current: 1, size: 20, total: 0 })
|
const searchForm = reactive({ planNo: '', status: '' })
|
|
// 主表格列配置
|
const tableColumn = ref([
|
{ label: '盘点单号', prop: 'planNo' },
|
{ label: '制单人', prop: 'createBy' },
|
{ label: '盘点人', prop: 'checkerName' },
|
{ label: '制单日期', prop: 'createTime', align: 'center' },
|
{ label: '计划日期', prop: 'planDate', align: 'center' },
|
{
|
label: '状态',
|
prop: 'status',
|
align: 'center',
|
dataType: 'tag',
|
formatData: (v) => ({
|
'0': '待执行', '1': '执行中', '2': '已完成', '3': '已取消',
|
}[v] || v),
|
formatType: (v) => ({
|
'0': 'info', '1': 'primary', '2': 'success', '3': 'warning',
|
}[v] || 'info')
|
},
|
{
|
label: '操作',
|
dataType: 'action',
|
fixed: 'right',
|
align: 'center',
|
operation: [
|
{ name: '编辑', clickFun: (row) => handleEdit(row), showHide: (row) => row.status == '0' },
|
{ name: '查看', clickFun: (row) => handleView(row) },
|
{ name: '开始盘点', clickFun: (row) => handleStart(row), showHide: (row) => row.status == '0' },
|
{ name: '录入盘点数据', clickFun: (row) => openCheckDialog(row), showHide: (row) => row.status == '1' },
|
{ name: '查看差异', clickFun: (row) => openDiffDialog(row), showHide: (row) => row.status == '2' },
|
// { name: '生成盈亏单', clickFun: (row) => handleGenerateProfitLoss(row), showHide: (row) => row.status == '2' },
|
]
|
}
|
])
|
|
// 产品选择表格列(编辑用)
|
const productColumn = ref([
|
{ label: '产品名称', prop: 'productName' },
|
{ label: '规格型号', prop: 'model' },
|
{ label: '单位', prop: 'unit', align: 'center' }
|
])
|
|
// 盘点数据录入表格列
|
const checkItemColumn = ref([
|
{ label: '产品名称', prop: 'productName' },
|
{ label: '规格型号', prop: 'model' },
|
{ label: '单位', prop: 'unit', align: 'center' },
|
{ label: '系统库存', prop: 'systemQty', align: 'center' },
|
{
|
label: '盘点数量',
|
prop: 'checkQty',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'checkQty'
|
},
|
{
|
label: '差异',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'diff'
|
}
|
])
|
|
// 差异汇总表格列
|
const diffItemColumn = ref([
|
{ label: '产品名称', prop: 'productName' },
|
{ label: '规格型号', prop: 'model' },
|
{ label: '单位', prop: 'unit', align: 'center' },
|
{ label: '批号', prop: 'batchNo', align: 'center' },
|
{ label: '系统库存', prop: 'systemQuantity', align: 'center' },
|
{ label: '盘点数量', prop: 'actualQuantity', align: 'center' },
|
{ label: '差异数量', prop: 'differenceQuantity', align: 'center' },
|
{ label: '备注', prop: 'remark' }
|
])
|
|
// 查看详情产品表格列
|
const viewProductColumn = ref([
|
{ label: '产品名称', prop: 'productName' },
|
{ label: '规格型号', prop: 'model' },
|
{ label: '单位', prop: 'unit', align: 'center' },
|
{ label: '系统库存', prop: 'systemQuantity', align: 'center' }
|
])
|
|
const dialogVisible = ref(false)
|
const dialogTitle = ref('')
|
const dialogMode = ref('add')
|
const submitLoading = ref(false)
|
const formRef = ref(null)
|
|
// 获取当前日期
|
const getCurrentDate = () => {
|
const now = new Date()
|
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
}
|
|
const form = reactive({
|
id: null,
|
planNo: '',
|
checkerId: null,
|
checkerName: '',
|
createBy: null,
|
createByName: '',
|
createTime: getCurrentDate(),
|
planDate: '',
|
remark: '',
|
items: [],
|
})
|
|
const userList = ref([])
|
|
const productDialogVisible = ref(false)
|
|
const checkDialogVisible = ref(false)
|
const checkLoading = ref(false)
|
const checkItemList = ref([])
|
const currentPlanId = ref(null)
|
|
const diffDialogVisible = ref(false)
|
const diffItemList = ref([])
|
const diffSummary = reactive({ totalCount: 0, profitCount: 0, lossCount: 0, normalCount: 0 })
|
|
const viewDialogVisible = ref(false)
|
const viewData = reactive({
|
planNo: '', checkerName: '', createByName: '', createTime: '', planDate: '', status: '', remark: '', items: []
|
})
|
|
const rules = {
|
createBy: [{ required: true, message: '请选择制单人', trigger: 'change' }],
|
checkerId: [{ required: true, message: '请选择盘点人', trigger: 'change' }],
|
planDate: [{ required: true, message: '请选择计划日期', trigger: 'change' }],
|
}
|
|
const getList = () => {
|
tableLoading.value = true
|
getStockCheckPlanPage({ current: page.current, size: page.size, ...searchForm })
|
.then(res => {
|
tableData.value = res.data?.records || []
|
page.total = res.data?.total || 0
|
})
|
.finally(() => { tableLoading.value = false })
|
}
|
|
const handleQuery = () => {
|
page.current = 1
|
getList()
|
}
|
|
const resetQuery = () => {
|
Object.assign(searchForm, { planNo: '', status: '' })
|
handleQuery()
|
}
|
|
const paginationChange = ({ page: current, limit }) => {
|
page.current = current
|
page.size = limit
|
getList()
|
}
|
|
const openForm = (mode) => {
|
dialogMode.value = mode
|
dialogTitle.value = mode === 'add' ? '新增盘点方案' : '编辑盘点方案'
|
if (mode === 'add') {
|
Object.assign(form, {
|
id: null,
|
planNo: '',
|
checkerId: null,
|
checkerName: '',
|
createBy: null,
|
createByName: '',
|
createTime: getCurrentDate(),
|
planDate: '',
|
remark: '',
|
items: [],
|
})
|
}
|
dialogVisible.value = true
|
}
|
|
const handleEdit = (row) => {
|
Object.assign(form, {
|
id: row.id,
|
planNo: row.planNo,
|
checkerId: row.checkerId,
|
checkerName: row.checkerName,
|
createBy: row.createBy,
|
createByName: row.createByName,
|
createTime: row.createTime,
|
planDate: row.planDate,
|
remark: row.remark,
|
items: row.items || [],
|
})
|
dialogMode.value = 'edit'
|
dialogTitle.value = '编辑盘点方案'
|
dialogVisible.value = true
|
}
|
|
const handleSubmit = () => {
|
formRef.value?.validate(valid => {
|
if (!valid) return
|
if (form.items.length === 0) {
|
ElMessage.warning('请至少选择一个盘点产品')
|
return
|
}
|
submitLoading.value = true
|
const data = { ...form }
|
const action = dialogMode.value === 'add' ? addStockCheckPlan : updateStockCheckPlan
|
action(data).then(() => {
|
ElMessage.success('保存成功')
|
dialogVisible.value = false
|
getList()
|
}).finally(() => { submitLoading.value = false })
|
})
|
}
|
|
const resetForm = () => {
|
formRef.value?.resetFields()
|
}
|
|
const handleView = (row) => {
|
Object.assign(viewData, {
|
planNo: row.planNo,
|
checkerName: row.checkerName,
|
createBy: row.createBy,
|
createTime: row.createTime,
|
planDate: row.planDate,
|
status: row.status,
|
remark: row.remark,
|
items: row.items || [],
|
})
|
viewDialogVisible.value = true
|
}
|
|
const handleDelete = (row) => {
|
ElMessageBox.confirm('确认删除该盘点计划?', '提示', { type: 'warning' })
|
.then(() => deleteStockCheckPlan([row.id]))
|
.then(() => {
|
ElMessage.success('删除成功')
|
getList()
|
})
|
}
|
|
const handleSubmitApproval = (row) => {
|
ElMessageBox.confirm('确认提交审批?', '提示', { type: 'warning' })
|
.then(() => submitApproval(row.id))
|
.then(() => {
|
ElMessage.success('提交成功')
|
getList()
|
})
|
}
|
|
const handleStart = (row) => {
|
ElMessageBox.confirm('确认开始盘点?开始后将锁定相关库存', '提示', { type: 'warning' })
|
.then(() => startCheck(row.id))
|
.then(() => {
|
ElMessage.success('盘点已开始')
|
getList()
|
})
|
}
|
|
const openCheckDialog = (row) => {
|
currentPlanId.value = row.id
|
checkDialogVisible.value = true
|
checkLoading.value = true
|
getCheckItems(row.id).then(res => {
|
const data = res.data || {}
|
// 直接使用接口返回的 checkItems
|
checkItemList.value = (data.checkItems || []).map(item => ({
|
...item,
|
actualQuantity: item.actualQuantity || item.systemQuantity,
|
}))
|
}).finally(() => { checkLoading.value = false })
|
}
|
|
const handleSaveCheckData = () => {
|
submitLoading.value = true
|
completeCheck({
|
id: currentPlanId.value,
|
checkItems: checkItemList.value.map(item => ({
|
id: item.id,
|
productModelId: item.productModelId,
|
productCode: item.productCode,
|
productName: item.productName,
|
specification: item.model,
|
unit: item.unit,
|
batchNo: item.batchNo,
|
systemQuantity: item.systemQuantity,
|
actualQuantity: item.actualQuantity,
|
differenceQuantity: item.actualQuantity - item.systemQuantity,
|
remark: item.remark,
|
})),
|
}).then(() => {
|
ElMessage.success('盘点数据保存成功')
|
checkDialogVisible.value = false
|
getList()
|
}).finally(() => { submitLoading.value = false })
|
}
|
|
const handleComplete = (row) => {
|
ElMessageBox.confirm('确认完成盘点?完成后将生成差异数据', '提示', { type: 'warning' })
|
.then(() => completeCheck({ id: row.id }))
|
.then(() => {
|
ElMessage.success('盘点已完成')
|
getList()
|
})
|
}
|
|
const openDiffDialog = (row) => {
|
currentPlanId.value = row.id
|
diffDialogVisible.value = true
|
getCheckItems(row.id).then(res => {
|
const data = res.data || {}
|
// 直接使用接口返回的 checkItems
|
diffItemList.value = data.checkItems || []
|
// 计算汇总数据
|
const items = diffItemList.value
|
const profitCount = items.filter(i => i.differenceQuantity > 0).length
|
const lossCount = items.filter(i => i.differenceQuantity < 0).length
|
const normalCount = items.filter(i => i.differenceQuantity === 0).length
|
Object.assign(diffSummary, {
|
totalCount: items.length,
|
profitCount,
|
lossCount,
|
normalCount,
|
})
|
})
|
}
|
|
const handleGenerateProfitLoss = (row) => {
|
ElMessageBox.confirm('确认根据盘点差异生成盈亏处理单?', '提示', { type: 'warning' })
|
.then(() => generateProfitLoss(row.id))
|
.then(() => {
|
ElMessage.success('盈亏处理单已生成')
|
getList()
|
})
|
}
|
|
const handleGenerateFromDiff = () => {
|
handleGenerateProfitLoss({ id: currentPlanId.value })
|
diffDialogVisible.value = false
|
}
|
|
// 产品选择相关
|
const openProductDialog = () => {
|
productDialogVisible.value = true
|
}
|
|
const handleProductSelect = (selectedRows) => {
|
// 排除已存在的产品
|
const existingIds = form.items.map(item => item.productModelId)
|
const newItems = selectedRows
|
.filter(p => !existingIds.includes(p.productModelId || p.id))
|
.map(p => ({
|
productModelId: p.productModelId || p.id,
|
productName: p.productName,
|
model: p.model,
|
unit: p.unit,
|
systemQuantity: p.systemQuantity || p.systemQty || 0,
|
}))
|
if (newItems.length === 0) {
|
ElMessage.warning('所选产品已存在')
|
return
|
}
|
form.items.push(...newItems)
|
ElMessage.success(`已添加 ${newItems.length} 个产品`)
|
}
|
|
const clearProducts = () => {
|
if (form.items.length === 0) return
|
ElMessageBox.confirm('确认清空所有产品?', '提示', { type: 'warning' })
|
.then(() => {
|
form.items = []
|
ElMessage.success('已清空')
|
})
|
}
|
|
const removeProduct = (index) => {
|
form.items.splice(index, 1)
|
}
|
|
const getStatusText = (status) => ({
|
'0': '待执行', '1': '执行中', '2': '已完成', '3': '已取消',
|
}[status] || status)
|
|
const getStatusType = (status) => ({
|
'0': 'info', '1': 'primary', '2': 'success', '3': 'warning',
|
}[status] || 'info')
|
|
// 加载用户列表
|
const loadUserList = async () => {
|
try {
|
const res = await userListNoPage()
|
userList.value = res?.data || []
|
} catch (err) {
|
console.error('获取用户列表失败:', err)
|
userList.value = []
|
}
|
}
|
|
onMounted(() => {
|
getList()
|
loadUserList()
|
})
|
</script>
|
|
<style scoped lang="scss">
|
.diff-summary {
|
margin-bottom: 20px;
|
padding: 15px;
|
background: #f5f7fa;
|
border-radius: 4px;
|
.stat-item {
|
text-align: center;
|
.stat-label {
|
color: #606266;
|
font-size: 13px;
|
margin-bottom: 8px;
|
}
|
.stat-value {
|
font-size: 24px;
|
font-weight: bold;
|
color: #303133;
|
}
|
}
|
}
|
.text-profit { color: #67c23a; font-weight: bold; }
|
.text-loss { color: #f56c6c; font-weight: bold; }
|
.mb10 { margin-bottom: 10px; }
|
.section-title {
|
font-weight: bold;
|
margin-bottom: 10px;
|
padding-left: 10px;
|
border-left: 4px solid #409eff;
|
}
|
</style>
|