<template>
|
<div class="app-container">
|
<div class="search_form" style="margin-bottom: 20px;">
|
<div>
|
<span class="search_title">单号:</span>
|
<el-input v-model="searchForm.orderNo" placeholder="请输入" clearable style="width: 240px; margin-right: 10px" @change="handleQuery" />
|
<span class="search_title">类型:</span>
|
<el-select v-model="searchForm.type" placeholder="请选择" clearable style="width: 240px" @change="handleQuery">
|
<el-option label="盘盈" value="profit" />
|
<el-option label="盘亏" value="loss" />
|
</el-select>
|
<span class="search_title" style="margin-left: 10px;">状态:</span>
|
<el-select v-model="searchForm.status" placeholder="请选择" clearable style="width: 240px" @change="handleQuery">
|
<el-option label="草稿" value="draft" />
|
<el-option label="审批中" value="approving" />
|
<el-option label="待执行" value="pending" />
|
<el-option label="已执行" value="executed" />
|
</el-select>
|
<span class="search_title" style="margin-left: 10px;">来源:</span>
|
<el-select v-model="searchForm.source" placeholder="请选择" clearable style="width: 240px" @change="handleQuery">
|
<el-option label="盘点生成" value="check" />
|
<el-option label="手动创建" value="manual" />
|
</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="1000px"
|
@close="resetForm"
|
@confirm="handleSubmit"
|
>
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
<el-row>
|
<el-col :span="12">
|
<el-form-item label="盈亏类型" prop="type">
|
<el-radio-group v-model="form.type" :disabled="form.source === 'check'">
|
<el-radio-button label="profit">盘盈</el-radio-button>
|
<el-radio-button label="loss">盘亏</el-radio-button>
|
</el-radio-group>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="仓库" prop="warehouseInfoId">
|
<el-select v-model="form.warehouseInfoId" placeholder="请选择仓库" style="width: 100%" filterable :disabled="form.source === 'check'">
|
<el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row v-if="form.source === 'check'">
|
<el-col :span="24">
|
<el-form-item label="关联盘点计划">
|
<el-input v-model="form.checkPlanNo" disabled />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row>
|
<el-col :span="24">
|
<el-form-item label="备注">
|
<el-input v-model="form.remark" type="textarea" :rows="2" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-divider content-position="left">明细信息</el-divider>
|
<div class="mb10" v-if="form.source === 'manual'">
|
<el-button type="primary" size="small" @click="openSelectProduct">添加产品</el-button>
|
</div>
|
<PIMTable
|
:column="itemColumn"
|
:tableData="form.items"
|
:isShowPagination="false"
|
height="300px"
|
/>
|
<div class="total-row" style="margin-top: 10px; text-align: right;">
|
<span style="font-weight: bold;">总差异金额:</span>
|
<span :class="getDiffClass(totalDiffAmount)" style="font-size: 18px; font-weight: bold;">
|
{{ totalDiffAmount > 0 ? '+' : '' }}¥{{ totalDiffAmount.toFixed(2) }}
|
</span>
|
</div>
|
</el-form>
|
</FormDialog>
|
|
<!-- 查看详情 -->
|
<FormDialog
|
v-model="viewDialogVisible"
|
title="盈亏单详情"
|
width="950px"
|
operationType="detail"
|
@close="viewDialogVisible = false"
|
>
|
<el-descriptions :column="2" border>
|
<el-descriptions-item label="单号">{{ viewData.orderNo }}</el-descriptions-item>
|
<el-descriptions-item label="类型">
|
<el-tag :type="viewData.type === 'profit' ? 'success' : 'danger'">
|
{{ viewData.type === 'profit' ? '盘盈' : '盘亏' }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="仓库">{{ viewData.warehouseName }}</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="来源">
|
<el-tag :type="viewData.source === 'check' ? 'primary' : 'info'" size="small">
|
{{ viewData.source === 'check' ? '盘点生成' : '手动创建' }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="关联盘点计划">{{ viewData.checkPlanNo || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="创建人">{{ viewData.createByName }}</el-descriptions-item>
|
<el-descriptions-item label="创建时间">{{ viewData.createTime }}</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="viewItemColumn"
|
:tableData="viewData.items"
|
:isShowPagination="false"
|
height="300px"
|
/>
|
<div class="total-row" style="margin-top: 10px; text-align: right;">
|
<span style="font-weight: bold;">总差异金额:</span>
|
<span :class="getDiffClass(viewData.totalAmount)" style="font-size: 18px; font-weight: bold;">
|
{{ viewData.totalAmount > 0 ? '+' : '' }}¥{{ viewData.totalAmount?.toFixed(2) }}
|
</span>
|
</div>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="viewDialogVisible = false">关闭</el-button>
|
</div>
|
</template>
|
</FormDialog>
|
|
<!-- 选择商品弹框 -->
|
<FormDialog
|
v-model="productDialogVisible"
|
title="选择产品"
|
width="850px"
|
@close="productDialogVisible = false"
|
@confirm="confirmSelectProduct"
|
>
|
<el-form :model="productSearchForm" inline>
|
<el-form-item label="产品名称">
|
<el-input v-model="productSearchForm.productName" placeholder="请输入" clearable style="width: 180px" />
|
</el-form-item>
|
<el-form-item label="规格型号">
|
<el-input v-model="productSearchForm.model" placeholder="请输入" clearable style="width: 150px" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleProductSearch">搜索</el-button>
|
</el-form-item>
|
</el-form>
|
<PIMTable
|
rowKey="id"
|
:column="productColumn"
|
:tableData="productList"
|
:isSelection="true"
|
:isShowPagination="false"
|
height="350px"
|
@selection-change="handleProductSelectionChange"
|
/>
|
</FormDialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import PIMTable from '@/components/PIMTable/PIMTable.vue'
|
import FormDialog from '@/components/Dialog/FormDialog.vue'
|
import {
|
getStockProfitLossPage,
|
getStockProfitLossDetail,
|
addStockProfitLoss,
|
updateStockProfitLoss,
|
deleteStockProfitLoss,
|
submitApproval,
|
executeProfitLoss,
|
} from '@/api/inventoryManagement/stockProfitLoss.js'
|
|
const tableData = ref([])
|
const tableLoading = ref(false)
|
const page = reactive({ current: 1, size: 20, total: 0 })
|
const searchForm = reactive({ orderNo: '', type: '', status: '', source: '' })
|
|
// 主表格列配置
|
const tableColumn = ref([
|
{ label: '单号', prop: 'orderNo' },
|
{
|
label: '类型',
|
prop: 'type',
|
align: 'center',
|
dataType: 'tag',
|
formatData: (v) => v === 'profit' ? '盘盈' : '盘亏',
|
formatType: (v) => v === 'profit' ? 'success' : 'danger'
|
},
|
{ label: '仓库', prop: 'warehouseName' },
|
{ label: '商品数量', prop: 'productCount', align: 'center' },
|
{
|
label: '金额',
|
prop: 'totalAmount',
|
align: 'right',
|
formatData: (v) => `¥${v?.toFixed(2) || '0.00'}`
|
},
|
{
|
label: '来源',
|
prop: 'source',
|
align: 'center',
|
dataType: 'tag',
|
formatData: (v) => v === 'check' ? '盘点生成' : '手动创建',
|
formatType: (v) => v === 'check' ? 'primary' : 'info'
|
},
|
{
|
label: '状态',
|
prop: 'status',
|
align: 'center',
|
dataType: 'tag',
|
formatData: (v) => ({
|
draft: '草稿', approving: '审批中', pending: '待执行', executed: '已执行'
|
}[v] || v),
|
formatType: (v) => ({
|
draft: 'info', approving: 'warning', pending: 'primary', executed: 'success'
|
}[v] || '')
|
},
|
{ label: '创建时间', prop: 'createTime', align: 'center' },
|
{
|
label: '操作',
|
dataType: 'action',
|
fixed: 'right',
|
align: 'center',
|
operation: [
|
{ name: '查看', clickFun: (row) => handleView(row) },
|
{ name: '编辑', clickFun: (row) => handleEdit(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' },
|
{ name: '提交审批', clickFun: (row) => handleSubmitApproval(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' },
|
{ name: '删除', clickFun: (row) => handleDelete(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' },
|
{ name: '执行', clickFun: (row) => handleExecute(row), showHide: (row) => row.status === 'pending' },
|
]
|
}
|
])
|
|
// 明细表格列配置(编辑用)
|
const itemColumn = ref([
|
{ label: '产品名称', prop: 'productName' },
|
{ label: '规格型号', prop: 'model' },
|
{ label: '单位', prop: 'unit', align: 'center' },
|
{ label: '系统库存', prop: 'systemQty', align: 'center' },
|
{
|
label: '实际数量',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'actualQty'
|
},
|
{
|
label: '差异数量',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'diffQty'
|
},
|
{
|
label: '成本单价',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'costPrice'
|
},
|
{
|
label: '差异金额',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'diffAmount'
|
},
|
{
|
label: '操作',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'action'
|
}
|
])
|
|
// 查看明细表格列配置
|
const viewItemColumn = ref([
|
{ label: '产品名称', prop: 'productName' },
|
{ label: '规格型号', prop: 'model' },
|
{ label: '单位', prop: 'unit', align: 'center' },
|
{ label: '系统库存', prop: 'systemQty', align: 'center' },
|
{ label: '实际数量', prop: 'actualQty', align: 'center' },
|
{
|
label: '差异数量',
|
align: 'center',
|
dataType: 'slot',
|
slot: 'viewDiffQty'
|
},
|
{
|
label: '成本单价',
|
align: 'right',
|
formatData: (v) => `¥${v?.toFixed(2) || '0.00'}`
|
},
|
{
|
label: '差异金额',
|
align: 'right',
|
dataType: 'slot',
|
slot: 'viewDiffAmount'
|
}
|
])
|
|
// 产品选择表格列配置
|
const productColumn = ref([
|
{ label: '产品名称', prop: 'productName' },
|
{ label: '规格型号', prop: 'model' },
|
{ label: '单位', prop: 'unit', align: 'center' },
|
{ label: '当前库存', prop: 'currentQty', align: 'center' }
|
])
|
|
const dialogVisible = ref(false)
|
const dialogTitle = ref('')
|
const dialogMode = ref('add')
|
const submitLoading = ref(false)
|
const formRef = ref(null)
|
const form = reactive({
|
id: null,
|
type: 'profit',
|
warehouseInfoId: null,
|
remark: '',
|
source: 'manual',
|
checkPlanNo: '',
|
items: [],
|
})
|
|
const warehouseList = ref([])
|
const productList = ref([])
|
const selectedProducts = ref([])
|
const productDialogVisible = ref(false)
|
const productSearchForm = reactive({ productName: '', model: '' })
|
|
const viewDialogVisible = ref(false)
|
const viewData = reactive({
|
orderNo: '', type: '', warehouseName: '', status: '', source: '',
|
checkPlanNo: '', createByName: '', createTime: '', remark: '',
|
items: [], totalAmount: 0,
|
})
|
|
const rules = {
|
type: [{ required: true, message: '请选择盈亏类型', trigger: 'change' }],
|
warehouseInfoId: [{ required: true, message: '请选择仓库', trigger: 'change' }],
|
}
|
|
const totalDiffAmount = computed(() => {
|
return form.items.reduce((sum, item) => sum + (item.actualQty - item.systemQty) * (item.costPrice || 0), 0)
|
})
|
|
const getList = () => {
|
tableLoading.value = true
|
getStockProfitLossPage({ 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, { orderNo: '', type: '', status: '', source: '' })
|
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, type: 'profit', warehouseInfoId: null, remark: '',
|
source: 'manual', checkPlanNo: '', items: [],
|
})
|
}
|
dialogVisible.value = true
|
}
|
|
const handleEdit = (row) => {
|
getStockProfitLossDetail(row.id).then(res => {
|
const data = res.data
|
Object.assign(form, {
|
id: data.id,
|
type: data.type,
|
warehouseInfoId: data.warehouseInfoId,
|
remark: data.remark,
|
source: data.source,
|
checkPlanNo: data.checkPlanNo,
|
items: data.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' ? addStockProfitLoss : updateStockProfitLoss
|
action(data).then(() => {
|
ElMessage.success('保存成功')
|
dialogVisible.value = false
|
getList()
|
}).finally(() => { submitLoading.value = false })
|
})
|
}
|
|
const resetForm = () => {
|
formRef.value?.resetFields()
|
}
|
|
const handleView = (row) => {
|
getStockProfitLossDetail(row.id).then(res => {
|
const data = res.data || {}
|
Object.assign(viewData, {
|
orderNo: data.orderNo,
|
type: data.type,
|
warehouseName: data.warehouseName,
|
status: data.status,
|
source: data.source,
|
checkPlanNo: data.checkPlanNo,
|
createByName: data.createByName,
|
createTime: data.createTime,
|
remark: data.remark,
|
items: data.items || [],
|
totalAmount: data.totalAmount || 0,
|
})
|
viewDialogVisible.value = true
|
})
|
}
|
|
const handleDelete = (row) => {
|
ElMessageBox.confirm('确认删除该盈亏单?', '提示', { type: 'warning' })
|
.then(() => deleteStockProfitLoss(row.id))
|
.then(() => {
|
ElMessage.success('删除成功')
|
getList()
|
})
|
}
|
|
const handleSubmitApproval = (row) => {
|
ElMessageBox.confirm('确认提交审批?', '提示', { type: 'warning' })
|
.then(() => submitApproval(row.id))
|
.then(() => {
|
ElMessage.success('提交成功')
|
getList()
|
})
|
}
|
|
const handleExecute = (row) => {
|
const actionText = row.type === 'profit' ? '盘盈入库' : '盘亏出库'
|
ElMessageBox.confirm(`确认执行${actionText}?执行后将更新库存数量`, '提示', { type: 'warning' })
|
.then(() => executeProfitLoss(row.id))
|
.then(() => {
|
ElMessage.success('执行成功')
|
getList()
|
})
|
}
|
|
const openSelectProduct = () => {
|
productDialogVisible.value = true
|
handleProductSearch()
|
}
|
|
const handleProductSearch = () => {
|
// 调用接口查询商品库存
|
}
|
|
const handleProductSelectionChange = (selection) => {
|
selectedProducts.value = selection
|
}
|
|
const confirmSelectProduct = () => {
|
if (selectedProducts.value.length === 0) {
|
ElMessage.warning('请选择产品')
|
return
|
}
|
const newItems = selectedProducts.value.map(p => ({
|
productId: p.id,
|
productName: p.productName,
|
model: p.model,
|
unit: p.unit,
|
batchNo: p.batchNo || '',
|
systemQty: p.currentQty || 0,
|
actualQty: p.currentQty || 0,
|
costPrice: p.costPrice || 0,
|
}))
|
form.items.push(...newItems)
|
productDialogVisible.value = false
|
}
|
|
const removeItem = (index) => {
|
form.items.splice(index, 1)
|
}
|
|
const getStatusText = (status) => ({
|
draft: '草稿', approving: '审批中', pending: '待执行', executed: '已执行',
|
}[status] || status)
|
|
const getStatusType = (status) => ({
|
draft: 'info', approving: 'warning', pending: 'primary', executed: 'success',
|
}[status] || '')
|
|
const getDiffClass = (val) => val > 0 ? 'text-profit' : val < 0 ? 'text-loss' : ''
|
|
onMounted(() => {
|
getList()
|
})
|
</script>
|
|
<style scoped lang="scss">
|
.search_form {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 16px;
|
}
|
.dialog-footer {
|
display: flex;
|
justify-content: center;
|
gap: 10px;
|
}
|
.section-title {
|
font-weight: bold;
|
margin-bottom: 10px;
|
padding-left: 10px;
|
border-left: 4px solid #409eff;
|
}
|
.text-profit { color: #67c23a; font-weight: bold; }
|
.text-loss { color: #f56c6c; font-weight: bold; }
|
</style>
|