<template>
|
<div class="stock-page">
|
<!-- 搜索区域 -->
|
<el-card class="search-card" shadow="never">
|
<el-form :inline="true" :model="queryForm" label-width="80px">
|
<el-form-item label="托盘编号">
|
<el-input v-model="queryForm.palletCode" placeholder="请输入托盘编号" clearable style="width: 180px" />
|
</el-form-item>
|
<el-form-item label="货位编号">
|
<el-input v-model="queryForm.locationCode" placeholder="请输入货位编号" clearable style="width: 180px" />
|
</el-form-item>
|
<el-form-item label="仓库">
|
<el-select v-model="queryForm.warehouseId" placeholder="请选择仓库" clearable style="width: 160px">
|
<el-option v-for="item in warehouseOptions" :key="item.key" :label="item.value" :value="item.key" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="库存状态">
|
<el-select v-model="queryForm.stockStatus" placeholder="请选择状态" clearable style="width: 140px">
|
<el-option v-for="item in stockStatusOptions" :key="item.key" :label="item.value" :value="item.key" />
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- 数据表格 -->
|
<el-card class="table-card" shadow="never">
|
<div class="toolbar">
|
<el-button type="primary" @click="handleAdd">新增</el-button>
|
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">批量删除</el-button>
|
</div>
|
|
<el-table
|
v-loading="loading"
|
:data="tableData"
|
border
|
stripe
|
@selection-change="handleSelectionChange"
|
@expand-change="handleExpandChange"
|
:expand-row-keys="expandedRows"
|
row-key="id"
|
>
|
<el-table-column type="selection" width="50" align="center" />
|
<el-table-column type="expand" width="50">
|
<template #default="{ row }">
|
<div class="expand-panel">
|
<div class="expand-header">
|
<span class="expand-title">库存明细</span>
|
<span class="expand-subtitle">托盘:{{ row.palletCode }} / 货位:{{ row.locationCode }}</span>
|
</div>
|
<el-table :data="detailData[row.id] || []" border stripe size="small" max-height="400">
|
<el-table-column prop="materielName" label="物料名称" min-width="160" show-overflow-tooltip />
|
<el-table-column prop="serialNumber" label="电芯码" min-width="160" show-overflow-tooltip />
|
<el-table-column prop="stockQuantity" label="库存数量" min-width="120" align="center" />
|
<el-table-column prop="status" label="状态" min-width="120" align="center">
|
<template #default="{ row: dr }">
|
{{ getStatusText(dr.status) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="inboundOrderRowNo" label="通道号" min-width="120" align="center" />
|
</el-table>
|
<div v-if="!detailData[row.id] && !detailLoading[row.id]" class="expand-empty">暂无明细数据</div>
|
<div v-if="detailLoading[row.id]" class="expand-loading">加载中...</div>
|
</div>
|
</template>
|
</el-table-column>
|
<el-table-column prop="palletCode" label="托盘编号" min-width="130" show-overflow-tooltip />
|
<el-table-column prop="locationCode" label="货位编号" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="warehouseId" label="仓库" min-width="110" align="center">
|
<template #default="{ row }">
|
{{ getWarehouseText(row.warehouseId) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="stockStatus" label="库存状态" min-width="120" align="center">
|
<template #default="{ row }">
|
{{ getStockStatusText(row.stockStatus) }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="outboundDate" label="出库日期" min-width="160" align="center" />
|
<el-table-column prop="creater" label="创建人" min-width="100" align="center" />
|
<el-table-column prop="createDate" label="创建时间" min-width="160" align="center" />
|
<el-table-column label="操作" width="150" align="center" fixed="right">
|
<template #default="{ row }">
|
<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<el-pagination
|
v-model:current-page="pagination.page"
|
v-model:page-size="pagination.pageSize"
|
:total="pagination.total"
|
:page-sizes="[10, 20, 50, 100]"
|
layout="total, sizes, prev, pager, next, jumper"
|
@size-change="handleSizeChange"
|
@current-change="handlePageChange"
|
style="margin-top: 16px; justify-content: flex-end"
|
/>
|
</el-card>
|
|
<!-- 编辑弹窗 -->
|
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px" destroy-on-close>
|
<el-form :model="editForm" :rules="editRules" ref="editFormRef" label-width="100px">
|
<el-form-item label="托盘编号" prop="palletCode">
|
<el-input v-model="editForm.palletCode" placeholder="请输入托盘编号" />
|
</el-form-item>
|
<el-form-item label="货位编号" prop="locationCode">
|
<el-input v-model="editForm.locationCode" placeholder="请输入货位编号" />
|
</el-form-item>
|
<el-form-item label="仓库" prop="warehouseId">
|
<el-select v-model="editForm.warehouseId" placeholder="请选择仓库" style="width: 100%">
|
<el-option v-for="item in warehouseOptions" :key="item.key" :label="item.value" :value="item.key" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="库存状态" prop="stockStatus">
|
<el-select v-model="editForm.stockStatus" placeholder="请选择库存状态" style="width: 100%">
|
<el-option v-for="item in stockStatusOptions" :key="item.key" :label="item.value" :value="item.key" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="备注">
|
<el-input v-model="editForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted } from 'vue';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { stockApi } from '@/api/modules/stock';
|
import { client } from '@/api/client';
|
|
const BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:9291';
|
|
// 搜索
|
const queryForm = reactive({
|
palletCode: '',
|
locationCode: '',
|
warehouseId: undefined,
|
stockStatus: undefined,
|
});
|
|
// 表格
|
const loading = ref(false);
|
const tableData = ref([]);
|
const selectedRows = ref([]);
|
const expandedRows = ref([]);
|
const detailData = ref({});
|
const detailLoading = ref({});
|
|
// 字典
|
const warehouseOptions = ref([]);
|
const stockStatusOptions = ref([]);
|
|
// 分页
|
const pagination = reactive({ page: 1, pageSize: 20, total: 0 });
|
|
// 编辑弹窗
|
const dialogVisible = ref(false);
|
const dialogTitle = ref('新增库存');
|
const editFormRef = ref(null);
|
const submitLoading = ref(false);
|
const editForm = reactive({
|
id: undefined,
|
palletCode: '',
|
locationCode: '',
|
warehouseId: undefined,
|
stockStatus: undefined,
|
remark: '',
|
});
|
const editRules = {
|
palletCode: [{ required: true, message: '请输入托盘编号', trigger: 'blur' }],
|
locationCode: [{ required: true, message: '请输入货位编号', trigger: 'blur' }],
|
warehouseId: [{ required: true, message: '请选择仓库', trigger: 'change' }],
|
};
|
|
async function loadData() {
|
loading.value = true;
|
try {
|
const params = buildQueryParams();
|
const res = await stockApi.getPageList(params);
|
tableData.value = res?.rows || [];
|
pagination.total = res?.total || 0;
|
} catch {
|
ElMessage.error('加载数据失败');
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function buildQueryParams() {
|
const wheres = [
|
queryForm.palletCode ? { name: 'palletCode', value: queryForm.palletCode, displayType: 'like' } : null,
|
queryForm.locationCode ? { name: 'locationCode', value: queryForm.locationCode, displayType: 'like' } : null,
|
queryForm.warehouseId ? { name: 'warehouseId', value: queryForm.warehouseId, displayType: 'int' } : null,
|
queryForm.stockStatus !== undefined ? { name: 'stockStatus', value: queryForm.stockStatus, displayType: 'int' } : null,
|
].filter(Boolean);
|
return {
|
page: pagination.page,
|
rows: pagination.pageSize,
|
sort: 'id',
|
order: 'desc',
|
wheres: JSON.stringify(wheres),
|
};
|
}
|
|
async function loadDetailData(row) {
|
if (detailData.value[row.id] || detailLoading.value[row.id]) return;
|
detailLoading.value[row.id] = true;
|
try {
|
const res = await client.post('/api/StockInfoDetail/getPageData', {
|
page: 1, rows: 200, sort: 'id', order: 'asc',
|
wheres: JSON.stringify([{ name: 'stockId', value: String(row.id), displayType: 'int' }]),
|
});
|
detailData.value[row.id] = res?.rows || [];
|
} catch {
|
detailData.value[row.id] = [];
|
ElMessage.error('加载明细失败');
|
} finally {
|
detailLoading.value[row.id] = false;
|
}
|
}
|
|
async function loadDictionary() {
|
try {
|
const res = await client.post('/api/Sys_Dictionary/GetVueDictionary', ['warehouseEnum', 'stockStatusEmun']);
|
const wh = res?.find(item => item.dicNo === 'warehouseEnum');
|
warehouseOptions.value = wh?.data || [];
|
const ss = res?.find(item => item.dicNo === 'stockStatusEmun');
|
stockStatusOptions.value = ss?.data || [];
|
} catch {
|
warehouseOptions.value = [];
|
stockStatusOptions.value = [];
|
}
|
}
|
|
function handleSearch() { pagination.page = 1; loadData(); }
|
|
function handleReset() {
|
queryForm.palletCode = '';
|
queryForm.locationCode = '';
|
queryForm.warehouseId = undefined;
|
queryForm.stockStatus = undefined;
|
handleSearch();
|
}
|
|
function handleAdd() {
|
editForm.id = undefined;
|
editForm.palletCode = '';
|
editForm.locationCode = '';
|
editForm.warehouseId = undefined;
|
editForm.stockStatus = undefined;
|
editForm.remark = '';
|
dialogTitle.value = '新增库存';
|
dialogVisible.value = true;
|
}
|
|
function handleEdit(row) {
|
Object.assign(editForm, { id: row.id, palletCode: row.palletCode, locationCode: row.locationCode, warehouseId: row.warehouseId, stockStatus: row.stockStatus, remark: row.remark });
|
dialogTitle.value = '编辑库存';
|
dialogVisible.value = true;
|
}
|
|
async function handleSubmit() {
|
await editFormRef.value.validate();
|
submitLoading.value = true;
|
try {
|
if (editForm.id) {
|
await stockApi.updateStock(editForm);
|
ElMessage.success('更新成功');
|
} else {
|
await stockApi.addStock(editForm);
|
ElMessage.success('新增成功');
|
}
|
dialogVisible.value = false;
|
loadData();
|
} catch { ElMessage.error('操作失败'); }
|
finally { submitLoading.value = false; }
|
}
|
|
function handleDelete(row) {
|
ElMessageBox.confirm(`确定删除库存「${row.palletCode}」吗?`, '提示', { type: 'warning' })
|
.then(async () => {
|
await stockApi.deleteStock(row.id);
|
ElMessage.success('删除成功');
|
loadData();
|
}).catch(() => {});
|
}
|
|
function handleBatchDelete() {
|
const ids = selectedRows.value.map(r => r.id);
|
ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条记录吗?`, '提示', { type: 'warning' })
|
.then(async () => {
|
await Promise.all(ids.map(id => stockApi.deleteStock(id)));
|
ElMessage.success('删除成功');
|
loadData();
|
}).catch(() => {});
|
}
|
|
function handleSelectionChange(rows) { selectedRows.value = rows; }
|
|
function handleExpandChange(row, expanded) {
|
if (expanded) {
|
expandedRows.value.push(row.id);
|
loadDetailData(row);
|
} else {
|
expandedRows.value = expandedRows.value.filter(id => id !== row.id);
|
}
|
}
|
|
function handleSizeChange() { loadData(); }
|
function handlePageChange() { loadData(); }
|
|
const getWarehouseText = (val) => warehouseOptions.value.find(o => `${o.key}` === `${val}`)?.value || val || '-';
|
const getStockStatusText = (val) => stockStatusOptions.value.find(o => `${o.key}` === `${val}`)?.value || val || '-';
|
const getStatusText = (val) => stockStatusOptions.value.find(o => `${o.key}` === `${val}`)?.value || val || '-';
|
|
onMounted(() => { loadDictionary(); loadData(); });
|
</script>
|
|
<style scoped>
|
.stock-page { padding: 16px; }
|
.search-card { margin-bottom: 12px; }
|
.table-card { margin-bottom: 12px; }
|
.toolbar { margin-bottom: 12px; }
|
.expand-panel { margin: 8px 16px 16px; padding: 12px; background: #fafafa; border-radius: 8px; }
|
.expand-header { margin-bottom: 12px; }
|
.expand-title { font-size: 15px; font-weight: 700; color: #303133; margin-right: 12px; }
|
.expand-subtitle { font-size: 13px; color: #606266; }
|
.expand-empty, .expand-loading { padding: 14px 12px; color: #909399; text-align: center; }
|
</style>
|