merge
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="mission-page">
|
||||
<ReflectionManagerPanel
|
||||
kind="process"
|
||||
kind-label="任务"
|
||||
title="任务编排(Mission / 进程,含插件 MissionType 实例化)"
|
||||
empty-text="当前没有任务;点击右上角「新建任务」从已加载的 MissionType 中选一个实例化。"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* /admin/missions 路由的页面入口。
|
||||
*
|
||||
* 由于「任务」与「进程」在后端 ReflectionApiController 共用 kind=process / mission
|
||||
* (UiDiscoveryCache.MissionCreatableTypes 同源),这里直接用 ReflectionManagerPanel
|
||||
* 接入,按下「新建任务」会列出所有 MissionType 子类(含 taskflow / trigger)。
|
||||
*
|
||||
* 旧版本的 DataTablePro + listMissions mock 已经下线,所有数据走 SimpleLite 反射 API。
|
||||
*/
|
||||
import ReflectionManagerPanel from '@/components/reflection/ReflectionManagerPanel.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mission-page {
|
||||
height: calc(100vh - 56px);
|
||||
padding: 12px 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="orchestration-page">
|
||||
<el-tabs v-model="tab" class="orch-tabs" type="border-card" @tab-change="onTabChange">
|
||||
<el-tab-pane label="车辆" name="cars">
|
||||
<CarPanelView v-if="tab === 'cars'" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="进程" name="process">
|
||||
<MissionEditorView v-if="tab === 'process'" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="脚本" name="script">
|
||||
<ReflectionKindTable
|
||||
v-if="tab === 'script'"
|
||||
kind="script"
|
||||
empty-text="脚本(继承 CarProgram 的子类型)未发现可暴露条目"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="任务流" name="taskflow">
|
||||
<ReflectionKindTable
|
||||
v-if="tab === 'taskflow'"
|
||||
kind="mission"
|
||||
extra-filter="taskflow"
|
||||
empty-text="未发现任务流条目"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="触发器" name="trigger">
|
||||
<ReflectionKindTable
|
||||
v-if="tab === 'trigger'"
|
||||
kind="mission"
|
||||
extra-filter="trigger"
|
||||
empty-text="未发现触发器条目"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import CarPanelView from '@/views/admin/CarPanelView.vue'
|
||||
import MissionEditorView from '@/views/admin/MissionEditorView.vue'
|
||||
import ReflectionKindTable from '@/components/orchestration/ReflectionKindTable.vue'
|
||||
|
||||
const tab = ref<'cars' | 'process' | 'script' | 'taskflow' | 'trigger'>('cars')
|
||||
|
||||
function onTabChange(name: string | number) {
|
||||
tab.value = name as typeof tab.value
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const cached = sessionStorage.getItem('orchestration.tab')
|
||||
if (cached) tab.value = cached as typeof tab.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.orchestration-page {
|
||||
height: calc(100vh - 56px);
|
||||
padding: 12px;
|
||||
}
|
||||
.orch-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
.orch-tabs :deep(.el-tabs__content) {
|
||||
height: calc(100% - 40px);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<div class="warehouse-page">
|
||||
<el-card shadow="never" class="warehouse-card">
|
||||
<template #header>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>仓储管理</h2>
|
||||
<p>库区、库位、容器、物料主数据,以及容器位置和容器物料关系。</p>
|
||||
</div>
|
||||
<el-button :icon="Refresh" :loading="loading" @click="loadAll">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tabs v-model="activeGroup">
|
||||
<el-tab-pane label="基础资料" name="master">
|
||||
<el-tabs v-model="activeMaster" lazy>
|
||||
<el-tab-pane label="库区" name="areas">
|
||||
<div class="toolbar"><el-input v-model="keyword" placeholder="搜索编码/名称" clearable /><el-button type="primary" @click="openArea()">新增库区</el-button></div>
|
||||
<el-table :data="filteredAreas" border size="small">
|
||||
<el-table-column prop="code" label="编码" width="140" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="type" label="类型" width="120" />
|
||||
<el-table-column prop="enabled" label="启用" width="90"><template #default="{ row }"><el-tag :type="row.enabled ? 'success' : 'info'">{{ row.enabled ? '是' : '否' }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="isLock" label="锁定" width="90"><template #default="{ row }"><el-tag :type="row.isLock ? 'warning' : 'info'">{{ row.isLock ? '是' : '否' }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" />
|
||||
<el-table-column label="操作" width="170"><template #default="{ row }"><el-button size="small" :disabled="row.isLock" @click="openArea(row)">编辑</el-button><el-button size="small" type="danger" :disabled="row.isLock" @click="removeArea(row)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="库位" name="storages">
|
||||
<div class="toolbar"><el-input v-model="keyword" placeholder="搜索编码/名称/站点" clearable /><el-button type="primary" @click="openStorage()">新增库位</el-button></div>
|
||||
<el-table :data="filteredStorages" border size="small">
|
||||
<el-table-column prop="code" label="编码" width="140" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column label="库区" width="140"><template #default="{ row }">{{ areaName(row.areaId) }}</template></el-table-column>
|
||||
<el-table-column prop="storageType" label="类型" width="120" />
|
||||
<el-table-column prop="siteId" label="站点" width="100" />
|
||||
<el-table-column prop="capacity" label="容量" width="90" />
|
||||
<el-table-column prop="enabled" label="启用" width="90"><template #default="{ row }"><el-tag :type="row.enabled ? 'success' : 'info'">{{ row.enabled ? '是' : '否' }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="170"><template #default="{ row }"><el-button size="small" :disabled="row.isLock" @click="openStorage(row)">编辑</el-button><el-button size="small" type="danger" :disabled="row.isLock" @click="removeStorage(row)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="容器" name="containers">
|
||||
<div class="toolbar"><el-input v-model="keyword" placeholder="搜索编码/名称" clearable /><el-button type="primary" @click="openContainer()">新增容器</el-button></div>
|
||||
<el-table :data="filteredContainers" border size="small">
|
||||
<el-table-column prop="code" label="编码" width="140" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="containerType" label="类型" width="120" />
|
||||
<el-table-column prop="status" label="状态" width="120" />
|
||||
<el-table-column label="当前位置"><template #default="{ row }">{{ containerLocationLabel(row.id) }}</template></el-table-column>
|
||||
<el-table-column prop="enabled" label="启用" width="90"><template #default="{ row }"><el-tag :type="row.enabled ? 'success' : 'info'">{{ row.enabled ? '是' : '否' }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="170"><template #default="{ row }"><el-button size="small" :disabled="row.isLock" @click="openContainer(row)">编辑</el-button><el-button size="small" type="danger" :disabled="row.isLock" @click="removeContainer(row)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="物料" name="materials">
|
||||
<div class="toolbar"><el-input v-model="keyword" placeholder="搜索编码/名称/规格" clearable /><el-button type="primary" @click="openMaterial()">新增物料</el-button></div>
|
||||
<el-table :data="filteredMaterials" border size="small">
|
||||
<el-table-column prop="code" label="编码" width="140" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="spec" label="规格" />
|
||||
<el-table-column prop="unit" label="单位" width="90" />
|
||||
<el-table-column prop="category" label="类别" width="120" />
|
||||
<el-table-column label="操作" width="170"><template #default="{ row }"><el-button size="small" :disabled="row.isLock" @click="openMaterial(row)">编辑</el-button><el-button size="small" type="danger" :disabled="row.isLock" @click="removeMaterial(row)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="关系与历史" name="relations" lazy>
|
||||
<el-tabs v-model="activeRelation" lazy>
|
||||
<el-tab-pane label="容器位置" name="locations">
|
||||
<div class="toolbar">
|
||||
<el-select v-model="locationFilter" clearable placeholder="位置类型"><el-option label="库位" value="Storage" /><el-option label="车辆" value="Car" /></el-select>
|
||||
<el-input v-model="keyword" placeholder="搜索位置编码/名称" clearable />
|
||||
<el-button type="primary" @click="openLocation()">绑定/转移</el-button>
|
||||
</div>
|
||||
<el-table :data="filteredContainerLocations" border size="small">
|
||||
<el-table-column label="容器" width="160"><template #default="{ row }">{{ containerName(row.containerId) }}</template></el-table-column>
|
||||
<el-table-column prop="locationType" label="位置类型" width="100" />
|
||||
<el-table-column prop="locationCode" label="位置编码" width="140" />
|
||||
<el-table-column prop="locationName" label="位置名称" />
|
||||
<el-table-column prop="status" label="状态" width="110" />
|
||||
<el-table-column prop="enteredAt" label="进入时间" width="180" />
|
||||
<el-table-column label="操作" width="120"><template #default="{ row }"><el-button size="small" type="danger" :disabled="row.isLock" @click="removeLocation(row)">解绑</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="容器物料" name="materials">
|
||||
<div class="toolbar"><el-input v-model="keyword" placeholder="搜索" clearable /><el-button type="primary" @click="openContainerMaterial()">装料/调整</el-button></div>
|
||||
<el-table :data="containerMaterials" border size="small">
|
||||
<el-table-column label="容器" width="160"><template #default="{ row }">{{ containerName(row.containerId) }}</template></el-table-column>
|
||||
<el-table-column label="物料" width="160"><template #default="{ row }">{{ materialName(row.materialId) }}</template></el-table-column>
|
||||
<el-table-column prop="quantity" label="数量" width="100" />
|
||||
<el-table-column prop="batchNo" label="批次" width="140" />
|
||||
<el-table-column prop="serialNo" label="序列号" width="140" />
|
||||
<el-table-column prop="status" label="状态" width="110" />
|
||||
<el-table-column label="操作" width="120"><template #default="{ row }"><el-button size="small" type="danger" :disabled="row.isLock" @click="removeContainerMaterial(row)">卸料</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="历史记录" name="history">
|
||||
<el-table :data="historyRows" border size="small">
|
||||
<el-table-column prop="kind" label="类型" width="110" />
|
||||
<el-table-column prop="eventType" label="事件" width="120" />
|
||||
<el-table-column prop="operator" label="操作人" width="120" />
|
||||
<el-table-column prop="operatedAt" label="时间" width="180" />
|
||||
<el-table-column prop="reason" label="原因" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px">
|
||||
<el-form label-width="96px">
|
||||
<template v-if="dialogKind === 'area' || dialogKind === 'container'">
|
||||
<el-form-item label="编码"><el-input v-model="masterForm.code" /></el-form-item>
|
||||
<el-form-item label="名称"><el-input v-model="masterForm.name" /></el-form-item>
|
||||
<el-form-item label="类型"><el-input v-model="masterForm.type" /></el-form-item>
|
||||
<el-form-item v-if="dialogKind === 'container'" label="状态"><el-input v-model="masterForm.status" /></el-form-item>
|
||||
<el-form-item label="启用"><el-switch v-model="masterForm.enabled" /></el-form-item>
|
||||
<el-form-item label="锁定"><el-switch v-model="masterForm.isLock" /></el-form-item>
|
||||
</template>
|
||||
<template v-if="dialogKind === 'storage'">
|
||||
<el-form-item label="库区"><el-select v-model="storageForm.areaId"><el-option v-for="a in areas" :key="a.id" :label="a.name" :value="a.id" /></el-select></el-form-item>
|
||||
<el-form-item label="编码"><el-input v-model="storageForm.code" /></el-form-item>
|
||||
<el-form-item label="名称"><el-input v-model="storageForm.name" /></el-form-item>
|
||||
<el-form-item label="类型"><el-input v-model="storageForm.storageType" /></el-form-item>
|
||||
<el-form-item label="站点"><el-input v-model="storageForm.siteId" /></el-form-item>
|
||||
<el-form-item label="容量"><el-input-number v-model="storageForm.capacity" :min="0" /></el-form-item>
|
||||
<el-form-item label="启用"><el-switch v-model="storageForm.enabled" /></el-form-item>
|
||||
<el-form-item label="锁定"><el-switch v-model="storageForm.isLock" /></el-form-item>
|
||||
</template>
|
||||
<template v-if="dialogKind === 'material'">
|
||||
<el-form-item label="编码"><el-input v-model="materialForm.code" /></el-form-item>
|
||||
<el-form-item label="名称"><el-input v-model="materialForm.name" /></el-form-item>
|
||||
<el-form-item label="规格"><el-input v-model="materialForm.spec" /></el-form-item>
|
||||
<el-form-item label="单位"><el-input v-model="materialForm.unit" /></el-form-item>
|
||||
<el-form-item label="类别"><el-input v-model="materialForm.category" /></el-form-item>
|
||||
<el-form-item label="启用"><el-switch v-model="materialForm.enabled" /></el-form-item>
|
||||
<el-form-item label="锁定"><el-switch v-model="materialForm.isLock" /></el-form-item>
|
||||
</template>
|
||||
<template v-if="dialogKind === 'location'">
|
||||
<el-form-item label="容器"><el-select v-model="locationForm.containerId"><el-option v-for="c in containers" :key="c.id" :label="c.code" :value="c.id" /></el-select></el-form-item>
|
||||
<el-form-item label="位置类型"><el-select v-model="locationForm.locationType"><el-option label="库位" value="Storage" /><el-option label="车辆" value="Car" /></el-select></el-form-item>
|
||||
<el-form-item v-if="locationForm.locationType === 'Storage'" label="库位"><el-select v-model="locationForm.locationId"><el-option v-for="s in storages" :key="s.id" :label="`${s.code} ${s.name}`" :value="s.id" /></el-select></el-form-item>
|
||||
<el-form-item v-else label="车辆 ID"><el-input v-model="locationForm.locationId" /></el-form-item>
|
||||
<el-form-item label="状态"><el-select v-model="locationForm.status"><el-option label="Active" value="Active" /><el-option label="Locked" value="Locked" /><el-option label="Exception" value="Exception" /></el-select></el-form-item>
|
||||
</template>
|
||||
<template v-if="dialogKind === 'containerMaterial'">
|
||||
<el-form-item label="容器"><el-select v-model="containerMaterialForm.containerId"><el-option v-for="c in containers" :key="c.id" :label="c.code" :value="c.id" /></el-select></el-form-item>
|
||||
<el-form-item label="物料"><el-select v-model="containerMaterialForm.materialId"><el-option v-for="m in materials" :key="m.id" :label="m.code" :value="m.id" /></el-select></el-form-item>
|
||||
<el-form-item label="数量"><el-input-number v-model="containerMaterialForm.quantity" :min="0.0001" :precision="4" /></el-form-item>
|
||||
<el-form-item label="批次"><el-input v-model="containerMaterialForm.batchNo" /></el-form-item>
|
||||
<el-form-item label="序列号"><el-input v-model="containerMaterialForm.serialNo" /></el-form-item>
|
||||
<el-form-item label="状态"><el-select v-model="containerMaterialForm.status"><el-option label="Loaded" value="Loaded" /><el-option label="Adjusted" value="Adjusted" /><el-option label="Frozen" value="Frozen" /></el-select></el-form-item>
|
||||
</template>
|
||||
<el-form-item label="备注"><el-input v-model="currentRemark" type="textarea" /></el-form-item>
|
||||
<el-form-item label="扩展 JSON"><el-input v-model="currentExtend" type="textarea" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="submitDialog">保存</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Refresh } from '@element-plus/icons-vue'
|
||||
import * as wmsApi from '@/api/wms'
|
||||
import type {
|
||||
WarehouseArea, Storage, Container, Material, ContainerLocation, ContainerMaterial,
|
||||
ContainerLocationHistory, ContainerMaterialHistory, MasterDataPayload, StoragePayload,
|
||||
MaterialPayload, ContainerLocationPayload, ContainerMaterialPayload
|
||||
} from '@/types/wms'
|
||||
|
||||
type DialogKind = 'area' | 'storage' | 'container' | 'material' | 'location' | 'containerMaterial'
|
||||
|
||||
const activeGroup = ref('master')
|
||||
const activeMaster = ref('areas')
|
||||
const activeRelation = ref('locations')
|
||||
const keyword = ref('')
|
||||
const locationFilter = ref('')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const dialogKind = ref<DialogKind>('area')
|
||||
|
||||
const areas = ref<WarehouseArea[]>([])
|
||||
const storages = ref<Storage[]>([])
|
||||
const containers = ref<Container[]>([])
|
||||
const materials = ref<Material[]>([])
|
||||
const containerLocations = ref<ContainerLocation[]>([])
|
||||
const containerMaterials = ref<ContainerMaterial[]>([])
|
||||
const locationHistory = ref<ContainerLocationHistory[]>([])
|
||||
const materialHistory = ref<ContainerMaterialHistory[]>([])
|
||||
|
||||
const masterForm = reactive<MasterDataPayload>(baseMaster())
|
||||
const storageForm = reactive<StoragePayload>(baseStorage())
|
||||
const materialForm = reactive<MaterialPayload>(baseMaterial())
|
||||
const locationForm = reactive<ContainerLocationPayload>(baseLocation())
|
||||
const containerMaterialForm = reactive<ContainerMaterialPayload>(baseContainerMaterial())
|
||||
|
||||
const dialogTitle = computed(() => ({
|
||||
area: '库区',
|
||||
storage: '库位',
|
||||
container: '容器',
|
||||
material: '物料',
|
||||
location: '容器位置',
|
||||
containerMaterial: '容器物料'
|
||||
}[dialogKind.value]))
|
||||
|
||||
const currentRemark = computed({
|
||||
get: () => formOf(dialogKind.value).remark,
|
||||
set: (v: string) => { formOf(dialogKind.value).remark = v }
|
||||
})
|
||||
const currentExtend = computed({
|
||||
get: () => formOf(dialogKind.value).extend,
|
||||
set: (v: string) => { formOf(dialogKind.value).extend = v }
|
||||
})
|
||||
|
||||
const filteredAreas = computed(() => filterRows(areas.value, keyword.value))
|
||||
const filteredStorages = computed(() => filterRows(storages.value, keyword.value))
|
||||
const filteredContainers = computed(() => filterRows(containers.value, keyword.value))
|
||||
const filteredMaterials = computed(() => filterRows(materials.value, keyword.value))
|
||||
const filteredContainerLocations = computed(() => containerLocations.value.filter((r) =>
|
||||
(!locationFilter.value || r.locationType === locationFilter.value) &&
|
||||
(!keyword.value || `${r.locationCode} ${r.locationName}`.toLowerCase().includes(keyword.value.toLowerCase()))))
|
||||
const historyRows = computed(() => [
|
||||
...locationHistory.value.map((x) => ({ ...x, kind: '位置' })),
|
||||
...materialHistory.value.map((x) => ({ ...x, kind: '物料' }))
|
||||
].sort((a, b) => String(b.operatedAt).localeCompare(String(a.operatedAt))))
|
||||
|
||||
async function loadAll() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [a, s, c, m, cl, cm, lh, mh] = await Promise.all([
|
||||
wmsApi.listAreas(), wmsApi.listStorages(), wmsApi.listContainers(), wmsApi.listMaterials(),
|
||||
wmsApi.listContainerLocations(), wmsApi.listContainerMaterials(), wmsApi.listContainerLocationHistory(), wmsApi.listContainerMaterialHistory()
|
||||
])
|
||||
areas.value = a; storages.value = s; containers.value = c; materials.value = m
|
||||
containerLocations.value = cl; containerMaterials.value = cm; locationHistory.value = lh; materialHistory.value = mh
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openArea(row?: WarehouseArea) { dialogKind.value = 'area'; Object.assign(masterForm, baseMaster(row)); dialogVisible.value = true }
|
||||
function openStorage(row?: Storage) { dialogKind.value = 'storage'; Object.assign(storageForm, baseStorage(row)); dialogVisible.value = true }
|
||||
function openContainer(row?: Container) { dialogKind.value = 'container'; Object.assign(masterForm, baseMaster(row)); dialogVisible.value = true }
|
||||
function openMaterial(row?: Material) { dialogKind.value = 'material'; Object.assign(materialForm, baseMaterial(row)); dialogVisible.value = true }
|
||||
function openLocation(row?: ContainerLocation) { dialogKind.value = 'location'; Object.assign(locationForm, baseLocation(row)); dialogVisible.value = true }
|
||||
function openContainerMaterial(row?: ContainerMaterial) { dialogKind.value = 'containerMaterial'; Object.assign(containerMaterialForm, baseContainerMaterial(row)); dialogVisible.value = true }
|
||||
|
||||
async function submitDialog() {
|
||||
saving.value = true
|
||||
try {
|
||||
JSON.parse(currentExtend.value || '{}')
|
||||
if (dialogKind.value === 'area') await wmsApi.saveArea(masterForm)
|
||||
else if (dialogKind.value === 'container') await wmsApi.saveContainer(masterForm)
|
||||
else if (dialogKind.value === 'storage') await wmsApi.saveStorage(storageForm)
|
||||
else if (dialogKind.value === 'material') await wmsApi.saveMaterial(materialForm)
|
||||
else if (dialogKind.value === 'location') await wmsApi.saveContainerLocation(locationForm)
|
||||
else await wmsApi.saveContainerMaterial(containerMaterialForm)
|
||||
ElMessage.success('已保存')
|
||||
dialogVisible.value = false
|
||||
await loadAll()
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : String(e))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete(message: string) {
|
||||
await ElMessageBox.confirm(message, '确认操作', { type: 'warning' })
|
||||
}
|
||||
async function removeArea(row: WarehouseArea) { await confirmDelete('删除该库区?'); await wmsApi.deleteArea(row.id, row.version); await loadAll() }
|
||||
async function removeStorage(row: Storage) { await confirmDelete('删除该库位?'); await wmsApi.deleteStorage(row.id, row.version); await loadAll() }
|
||||
async function removeContainer(row: Container) { await confirmDelete('删除该容器?'); await wmsApi.deleteContainer(row.id, row.version); await loadAll() }
|
||||
async function removeMaterial(row: Material) { await confirmDelete('删除该物料?'); await wmsApi.deleteMaterial(row.id, row.version); await loadAll() }
|
||||
async function removeLocation(row: ContainerLocation) { await confirmDelete('解绑该容器位置?'); await wmsApi.deleteContainerLocation(row.containerId); await loadAll() }
|
||||
async function removeContainerMaterial(row: ContainerMaterial) { await confirmDelete('卸载该容器物料?'); await wmsApi.deleteContainerMaterial(row.id); await loadAll() }
|
||||
|
||||
function areaName(id: string) { return areas.value.find((a) => a.id === id)?.name ?? id }
|
||||
function containerName(id: string) { const c = containers.value.find((x) => x.id === id); return c ? `${c.code} ${c.name}` : id }
|
||||
function materialName(id: string) { const m = materials.value.find((x) => x.id === id); return m ? `${m.code} ${m.name}` : id }
|
||||
function containerLocationLabel(id: string) {
|
||||
const loc = containerLocations.value.find((x) => x.containerId === id)
|
||||
return loc ? `${loc.locationType}:${loc.locationCode || loc.locationId}` : '未绑定'
|
||||
}
|
||||
function filterRows<T extends Record<string, unknown>>(rows: T[], q: string) {
|
||||
if (!q) return rows
|
||||
const s = q.toLowerCase()
|
||||
return rows.filter((r) => Object.values(r).some((v) => String(v ?? '').toLowerCase().includes(s)))
|
||||
}
|
||||
function formOf(kind: DialogKind): { remark: string; extend: string } {
|
||||
if (kind === 'storage') return storageForm
|
||||
if (kind === 'material') return materialForm
|
||||
if (kind === 'location') return locationForm
|
||||
if (kind === 'containerMaterial') return containerMaterialForm
|
||||
return masterForm
|
||||
}
|
||||
function baseCommon(row?: { id: string; version: number; isLock: boolean; remark: string; extend: string }) {
|
||||
return { id: row?.id, version: row?.version, isLock: row?.isLock ?? false, remark: row?.remark ?? '', extend: row?.extend ?? '{}' }
|
||||
}
|
||||
function baseMaster(row?: WarehouseArea | Container): MasterDataPayload {
|
||||
return { ...baseCommon(row), code: row?.code ?? '', name: row?.name ?? '', type: 'type' in (row ?? {}) ? (row as WarehouseArea).type : (row as Container | undefined)?.containerType ?? '', status: (row as Container | undefined)?.status ?? 'Idle', enabled: row?.enabled ?? true, sortOrder: (row as WarehouseArea | undefined)?.sortOrder ?? 0 }
|
||||
}
|
||||
function baseStorage(row?: Storage): StoragePayload {
|
||||
return { ...baseCommon(row), areaId: row?.areaId ?? areas.value[0]?.id ?? '', code: row?.code ?? '', name: row?.name ?? '', storageType: row?.storageType ?? 'Storage', siteId: row?.siteId ?? '', capacity: row?.capacity ?? 0, enabled: row?.enabled ?? true }
|
||||
}
|
||||
function baseMaterial(row?: Material): MaterialPayload {
|
||||
return { ...baseCommon(row), code: row?.code ?? '', name: row?.name ?? '', spec: row?.spec ?? '', unit: row?.unit ?? 'pcs', category: row?.category ?? '', enabled: row?.enabled ?? true }
|
||||
}
|
||||
function baseLocation(row?: ContainerLocation): ContainerLocationPayload {
|
||||
return { ...baseCommon(row), containerId: row?.containerId ?? containers.value[0]?.id ?? '', locationType: row?.locationType ?? 'Storage', locationId: row?.locationId ?? '', status: row?.status ?? 'Active', enteredAt: row?.enteredAt, source: 'Manual', reason: '' }
|
||||
}
|
||||
function baseContainerMaterial(row?: ContainerMaterial): ContainerMaterialPayload {
|
||||
return { ...baseCommon(row), containerId: row?.containerId ?? containers.value[0]?.id ?? '', materialId: row?.materialId ?? materials.value[0]?.id ?? '', quantity: row?.quantity ?? 1, batchNo: row?.batchNo ?? '', serialNo: row?.serialNo ?? '', status: row?.status ?? 'Loaded', loadedAt: row?.loadedAt, source: 'Manual', reason: '' }
|
||||
}
|
||||
|
||||
onMounted(loadAll)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.warehouse-page { padding: 16px; height: 100%; overflow: auto; }
|
||||
.warehouse-card { min-height: 100%; }
|
||||
.page-header { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
|
||||
.page-header h2 { margin: 0 0 4px; font-size: 20px; color: var(--mg-text-light); }
|
||||
.page-header p { margin: 0; color: var(--mg-text-muted); font-size: 13px; }
|
||||
.toolbar { display: flex; gap: 10px; align-items: center; margin: 0 0 12px; }
|
||||
.toolbar .el-input { max-width: 260px; }
|
||||
.toolbar .el-select { width: 160px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user