feat(mes): 迁移 materialstock
parent
f27942c8f9
commit
8192bb4777
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as WmMaterialStockSelectDialog } from './wm-material-stock-select-dialog.vue';
|
||||||
|
export { default as WmMaterialStockSelect } from './wm-material-stock-select.vue';
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Alert, message, Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getMaterialStockPage } from '#/api/mes/wm/materialstock';
|
||||||
|
import MdItemTypeTree from '#/views/mes/md/item/type/components/md-item-type-tree.vue';
|
||||||
|
|
||||||
|
import { useSelectGridColumns, useSelectGridFormSchema } from '../data';
|
||||||
|
|
||||||
|
/** 虚拟仓过滤模式:'exclude' 排除虚拟仓(默认),'only' 只看虚拟仓,'all' 不过滤 */
|
||||||
|
type VirtualFilter = 'all' | 'exclude' | 'only';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
batchId?: number;
|
||||||
|
itemId?: number;
|
||||||
|
virtualFilter?: VirtualFilter;
|
||||||
|
warehouseId?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
batchId: undefined,
|
||||||
|
itemId: undefined,
|
||||||
|
virtualFilter: 'exclude',
|
||||||
|
warehouseId: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
selected: [rows: MesWmMaterialStockApi.MaterialStock[]];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const multiple = ref(true);
|
||||||
|
const syncingSingleSelection = ref(false);
|
||||||
|
const selectedRows = ref<MesWmMaterialStockApi.MaterialStock[]>([]);
|
||||||
|
const preSelectedIds = ref<number[]>([]);
|
||||||
|
const searchItemTypeId = ref<number>();
|
||||||
|
|
||||||
|
/** 当前 props 是否带预过滤 */
|
||||||
|
const showAlert = computed(
|
||||||
|
() =>
|
||||||
|
props.batchId != null ||
|
||||||
|
props.warehouseId != null ||
|
||||||
|
props.virtualFilter === 'only',
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 预过滤提示文字 */
|
||||||
|
const alertTitle = computed(() => {
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (props.batchId != null) {
|
||||||
|
parts.push('批次');
|
||||||
|
}
|
||||||
|
if (props.warehouseId != null) {
|
||||||
|
parts.push('仓库');
|
||||||
|
}
|
||||||
|
if (props.virtualFilter === 'only') {
|
||||||
|
parts.push('只看虚拟仓');
|
||||||
|
}
|
||||||
|
return `已按${parts.join('/')}预过滤`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 单选模式同步 VXE 勾选状态 */
|
||||||
|
async function syncSingleSelection(row?: MesWmMaterialStockApi.MaterialStock) {
|
||||||
|
syncingSingleSelection.value = true;
|
||||||
|
await nextTick();
|
||||||
|
await gridApi.grid.clearCheckboxRow();
|
||||||
|
if (row) {
|
||||||
|
await gridApi.grid.setCheckboxRow(row, true);
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
syncingSingleSelection.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理勾选变化 */
|
||||||
|
async function handleCheckboxChange({
|
||||||
|
checked,
|
||||||
|
records,
|
||||||
|
row,
|
||||||
|
}: {
|
||||||
|
checked: boolean;
|
||||||
|
records: MesWmMaterialStockApi.MaterialStock[];
|
||||||
|
row?: MesWmMaterialStockApi.MaterialStock;
|
||||||
|
}) {
|
||||||
|
if (syncingSingleSelection.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!multiple.value) {
|
||||||
|
const selected = checked && row ? [row] : [];
|
||||||
|
selectedRows.value = selected;
|
||||||
|
await syncSingleSelection(selected[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedRows.value = records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理全选变化 */
|
||||||
|
function handleCheckboxAll({
|
||||||
|
records,
|
||||||
|
}: {
|
||||||
|
records: MesWmMaterialStockApi.MaterialStock[];
|
||||||
|
}) {
|
||||||
|
if (syncingSingleSelection.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedRows.value = records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 双击行:单选直接确认;多选切换勾选 */
|
||||||
|
async function handleRowDblclick({
|
||||||
|
row,
|
||||||
|
}: {
|
||||||
|
row: MesWmMaterialStockApi.MaterialStock;
|
||||||
|
}) {
|
||||||
|
if (multiple.value) {
|
||||||
|
const checked = !gridApi.grid.isCheckedByCheckboxRow(row);
|
||||||
|
await gridApi.grid.setCheckboxRow(row, checked);
|
||||||
|
handleCheckboxChange({
|
||||||
|
checked,
|
||||||
|
records: gridApi.grid.getCheckboxRecords() as MesWmMaterialStockApi.MaterialStock[],
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedRows.value = [row];
|
||||||
|
await syncSingleSelection(row);
|
||||||
|
handleConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 回显预选 */
|
||||||
|
function applyPreSelection() {
|
||||||
|
if (preSelectedIds.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rows = gridApi.grid.getData() as MesWmMaterialStockApi.MaterialStock[];
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.id && preSelectedIds.value.includes(row.id)) {
|
||||||
|
gridApi.grid.setCheckboxRow(row, true);
|
||||||
|
if (!multiple.value) {
|
||||||
|
selectedRows.value = [row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useSelectGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useSelectGridColumns(),
|
||||||
|
height: 480,
|
||||||
|
keepSource: true,
|
||||||
|
checkboxConfig: { highlight: true, range: true, reserve: true },
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getMaterialStockPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
batchId: props.batchId,
|
||||||
|
// 默认查未冻结
|
||||||
|
frozen: false,
|
||||||
|
itemTypeId: searchItemTypeId.value,
|
||||||
|
itemId: formValues.itemId ?? props.itemId,
|
||||||
|
warehouseId: formValues.warehouseId ?? props.warehouseId,
|
||||||
|
virtualFilter:
|
||||||
|
props.virtualFilter === 'all' ? undefined : props.virtualFilter,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>,
|
||||||
|
gridEvents: {
|
||||||
|
cellDblclick: handleRowDblclick,
|
||||||
|
checkboxAll: handleCheckboxAll,
|
||||||
|
checkboxChange: handleCheckboxChange,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 物料分类树节点点击 */
|
||||||
|
function handleTypeNodeClick(row: undefined | { id?: number }) {
|
||||||
|
searchItemTypeId.value = row?.id;
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置查询和选择状态 */
|
||||||
|
async function resetQueryState() {
|
||||||
|
selectedRows.value = [];
|
||||||
|
searchItemTypeId.value = undefined;
|
||||||
|
await gridApi.grid.clearCheckboxRow();
|
||||||
|
await gridApi.formApi.resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
async function openModal(
|
||||||
|
selectedIds?: number[],
|
||||||
|
options?: { multiple?: boolean },
|
||||||
|
) {
|
||||||
|
open.value = true;
|
||||||
|
multiple.value = options?.multiple ?? true;
|
||||||
|
preSelectedIds.value = selectedIds || [];
|
||||||
|
await nextTick();
|
||||||
|
await resetQueryState();
|
||||||
|
await gridApi.query();
|
||||||
|
await nextTick();
|
||||||
|
applyPreSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭弹窗 */
|
||||||
|
async function closeModal() {
|
||||||
|
open.value = false;
|
||||||
|
await resetQueryState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 确认选择 */
|
||||||
|
function handleConfirm() {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(
|
||||||
|
'selected',
|
||||||
|
multiple.value ? selectedRows.value : [selectedRows.value[0]!],
|
||||||
|
);
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open: openModal });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
v-model:open="open"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
title="库存物资选择"
|
||||||
|
width="80%"
|
||||||
|
@cancel="closeModal"
|
||||||
|
@ok="handleConfirm"
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
v-if="showAlert"
|
||||||
|
class="mb-3"
|
||||||
|
:message="alertTitle"
|
||||||
|
show-icon
|
||||||
|
type="info"
|
||||||
|
/>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<div class="bg-card w-1/6 rounded p-2">
|
||||||
|
<MdItemTypeTree @node-click="handleTypeNodeClick" />
|
||||||
|
</div>
|
||||||
|
<div class="w-5/6">
|
||||||
|
<Grid table-title="库存列表" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { computed, ref, useAttrs, watch } from 'vue';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Input, Tooltip } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getMaterialStock } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import WmMaterialStockSelectDialog from './wm-material-stock-select-dialog.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'WmMaterialStockSelect', inheritAttrs: false });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
allowClear?: boolean;
|
||||||
|
batchId?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
itemId?: number;
|
||||||
|
modelValue?: number;
|
||||||
|
placeholder?: string;
|
||||||
|
virtualFilter?: 'all' | 'exclude' | 'only';
|
||||||
|
warehouseId?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
allowClear: true,
|
||||||
|
batchId: undefined,
|
||||||
|
disabled: false,
|
||||||
|
itemId: undefined,
|
||||||
|
modelValue: undefined,
|
||||||
|
placeholder: '请选择库存',
|
||||||
|
virtualFilter: 'exclude',
|
||||||
|
warehouseId: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
change: [item: MesWmMaterialStockApi.MaterialStock | undefined];
|
||||||
|
'update:modelValue': [value: number | undefined];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const dialogRef = ref<InstanceType<typeof WmMaterialStockSelectDialog>>();
|
||||||
|
const hovering = ref(false);
|
||||||
|
const selectedItem = ref<MesWmMaterialStockApi.MaterialStock>();
|
||||||
|
|
||||||
|
const displayLabel = computed(() => {
|
||||||
|
const item = selectedItem.value;
|
||||||
|
if (!item) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${item.warehouseName || '-'} / ${item.batchCode || '-'} / 数量:${item.quantity}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const showClear = computed(
|
||||||
|
() =>
|
||||||
|
props.allowClear &&
|
||||||
|
!props.disabled &&
|
||||||
|
hovering.value &&
|
||||||
|
props.modelValue != null,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 根据 ID 单条查询库存信息(用于编辑回显) */
|
||||||
|
async function resolveItemById(id: number | undefined) {
|
||||||
|
if (id == null) {
|
||||||
|
selectedItem.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedItem.value?.id === id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
selectedItem.value = await getMaterialStock(id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[WmMaterialStockSelect] resolveItemById failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.modelValue, resolveItemById, { immediate: true });
|
||||||
|
|
||||||
|
/** 清空已选库存 */
|
||||||
|
function clearSelected() {
|
||||||
|
selectedItem.value = undefined;
|
||||||
|
emit('update:modelValue', undefined);
|
||||||
|
emit('change', undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开库存选择弹窗 */
|
||||||
|
function handleClick(event: MouseEvent) {
|
||||||
|
if (props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
if (showClear.value && target.closest('.ant-input-suffix')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
clearSelected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||||
|
dialogRef.value?.open(selectedIds, { multiple: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 弹窗选中回调 */
|
||||||
|
function handleSelected(rows: MesWmMaterialStockApi.MaterialStock[]) {
|
||||||
|
const item = rows[0];
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedItem.value = item;
|
||||||
|
emit('update:modelValue', item.id);
|
||||||
|
emit('change', item);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-bind="attrs"
|
||||||
|
class="w-full"
|
||||||
|
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
||||||
|
@click="handleClick"
|
||||||
|
@mouseenter="hovering = true"
|
||||||
|
@mouseleave="hovering = false"
|
||||||
|
>
|
||||||
|
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
|
||||||
|
<template #title>
|
||||||
|
<div v-if="selectedItem" class="leading-6">
|
||||||
|
<div>物料:{{ selectedItem.itemName || '-' }}</div>
|
||||||
|
<div>批次:{{ selectedItem.batchCode || '-' }}</div>
|
||||||
|
<div>数量:{{ selectedItem.quantity ?? '-' }}</div>
|
||||||
|
<div>仓库:{{ selectedItem.warehouseName || '-' }}</div>
|
||||||
|
<div>库区:{{ selectedItem.locationName || '-' }}</div>
|
||||||
|
<div>库位:{{ selectedItem.areaName || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Input
|
||||||
|
:disabled="disabled"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:value="displayLabel"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<IconifyIcon
|
||||||
|
class="size-4"
|
||||||
|
:icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Input>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<WmMaterialStockSelectDialog
|
||||||
|
ref="dialogRef"
|
||||||
|
:batch-id="batchId"
|
||||||
|
:item-id="itemId"
|
||||||
|
:virtual-filter="virtualFilter"
|
||||||
|
:warehouse-id="warehouseId"
|
||||||
|
@selected="handleSelected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
|
||||||
|
import MdItemSelect from '#/views/mes/md/item/components/md-item-select.vue';
|
||||||
|
import MdVendorSelect from '#/views/mes/md/vendor/components/md-vendor-select.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
WmWarehouseAreaSelect,
|
||||||
|
WmWarehouseLocationSelect,
|
||||||
|
WmWarehouseSelect,
|
||||||
|
} from '../warehouse/components';
|
||||||
|
|
||||||
|
/** 列表的搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'itemId',
|
||||||
|
label: '物料',
|
||||||
|
component: markRaw(MdItemSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择物料',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'batchCode',
|
||||||
|
label: '批次号',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入批次号',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'warehouseId',
|
||||||
|
label: '仓库',
|
||||||
|
component: markRaw(WmWarehouseSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择仓库',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'locationId',
|
||||||
|
label: '库区',
|
||||||
|
component: markRaw(WmWarehouseLocationSelect),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['warehouseId'],
|
||||||
|
componentProps: (values) => ({
|
||||||
|
warehouseId: values.warehouseId,
|
||||||
|
placeholder: '请选择库区',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'frozen',
|
||||||
|
label: '是否冻结',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '是', value: true },
|
||||||
|
{ label: '否', value: false },
|
||||||
|
],
|
||||||
|
placeholder: '请选择',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的字段 */
|
||||||
|
export function useGridColumns(
|
||||||
|
onFrozenChange: (row: MesWmMaterialStockApi.MaterialStock) => void,
|
||||||
|
): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'itemCode',
|
||||||
|
title: '产品物料编码',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'itemName',
|
||||||
|
title: '产品物料名称',
|
||||||
|
minWidth: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'specification',
|
||||||
|
title: '规格型号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'quantity',
|
||||||
|
title: '在库数量',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'unitMeasureName',
|
||||||
|
title: '单位',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'batchCode',
|
||||||
|
title: '批次号',
|
||||||
|
minWidth: 140,
|
||||||
|
slots: { default: 'batchCode' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'warehouseName',
|
||||||
|
title: '仓库',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'locationName',
|
||||||
|
title: '库区',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'areaName',
|
||||||
|
title: '库位',
|
||||||
|
minWidth: 100,
|
||||||
|
slots: { default: 'areaName' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'receiptTime',
|
||||||
|
title: '入库日期',
|
||||||
|
width: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'frozen',
|
||||||
|
title: '冻结',
|
||||||
|
width: 90,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellSwitch',
|
||||||
|
attrs: { beforeChange: onFrozenChange },
|
||||||
|
props: { checkedValue: true, unCheckedValue: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择弹窗的搜索表单 */
|
||||||
|
export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'itemId',
|
||||||
|
label: '物料',
|
||||||
|
component: markRaw(MdItemSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择物料',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'vendorId',
|
||||||
|
label: '供应商',
|
||||||
|
component: markRaw(MdVendorSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择供应商',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'batchCode',
|
||||||
|
label: '批次号',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入批次号',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'warehouseId',
|
||||||
|
label: '仓库',
|
||||||
|
component: markRaw(WmWarehouseSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择仓库',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'locationId',
|
||||||
|
label: '库区',
|
||||||
|
component: markRaw(WmWarehouseLocationSelect),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['warehouseId'],
|
||||||
|
componentProps: (values) => ({
|
||||||
|
warehouseId: values.warehouseId,
|
||||||
|
placeholder: '请选择库区',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'areaId',
|
||||||
|
label: '库位',
|
||||||
|
component: markRaw(WmWarehouseAreaSelect),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['locationId'],
|
||||||
|
componentProps: (values) => ({
|
||||||
|
locationId: values.locationId,
|
||||||
|
placeholder: '请选择库位',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择弹窗的字段 */
|
||||||
|
export function useSelectGridColumns(): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'itemCode',
|
||||||
|
title: '产品物料编码',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'itemName',
|
||||||
|
title: '产品物料名称',
|
||||||
|
minWidth: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'specification',
|
||||||
|
title: '规格型号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'unitMeasureName',
|
||||||
|
title: '单位',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'batchCode',
|
||||||
|
title: '入库批次号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'warehouseName',
|
||||||
|
title: '仓库',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'locationName',
|
||||||
|
title: '库区',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'areaName',
|
||||||
|
title: '库位',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'quantity',
|
||||||
|
title: '在库数量',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'receiptTime',
|
||||||
|
title: '入库日期',
|
||||||
|
width: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'frozen',
|
||||||
|
title: '冻结',
|
||||||
|
width: 80,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellDict',
|
||||||
|
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// TODO @AI:注释风格;另外,也对齐下 system user index.vue;(整个文件)
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
exportMaterialStock,
|
||||||
|
getMaterialStockPage,
|
||||||
|
updateMaterialStockFrozen,
|
||||||
|
} from '#/api/mes/wm/materialstock';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import MdItemTypeTree from '#/views/mes/md/item/type/components/md-item-type-tree.vue';
|
||||||
|
import AreaForm from '#/views/mes/wm/warehouse/area/modules/form.vue';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
|
const [AreaModal, areaModalApi] = useVbenModal({
|
||||||
|
connectedComponent: AreaForm,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 处理冻结状态切换 */
|
||||||
|
async function handleFrozenChange(row: MesWmMaterialStockApi.MaterialStock) {
|
||||||
|
const text = row.frozen ? '冻结' : '解冻';
|
||||||
|
try {
|
||||||
|
await confirm(`确认要"${text}"该库存记录吗?`);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await updateMaterialStockFrozen({
|
||||||
|
id: row.id!,
|
||||||
|
frozen: row.frozen!,
|
||||||
|
});
|
||||||
|
message.success(`${text}成功`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 已选物料分类 */
|
||||||
|
const searchItemTypeId = ref<number>();
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(handleFrozenChange),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getMaterialStockPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
itemTypeId: searchItemTypeId.value,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 物料分类树节点点击 */
|
||||||
|
function handleTypeNodeClick(row: undefined | { id?: number }) {
|
||||||
|
searchItemTypeId.value = row?.id;
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开库位详情弹窗 */
|
||||||
|
function handleOpenAreaDetail(row: MesWmMaterialStockApi.MaterialStock) {
|
||||||
|
if (!row.areaId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
areaModalApi.setData({ formType: 'detail', id: row.areaId }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出表格 */
|
||||||
|
async function handleExport() {
|
||||||
|
const data = await exportMaterialStock({
|
||||||
|
...(await gridApi.formApi.getValues()),
|
||||||
|
itemTypeId: searchItemTypeId.value,
|
||||||
|
});
|
||||||
|
downloadFileFromBlobPart({ fileName: '库存台账.xls', source: data });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<template #doc>
|
||||||
|
<DocAlert
|
||||||
|
title="【仓库】批次管理、库存现有量、库存事务"
|
||||||
|
url="https://doc.iocoder.cn/mes/wm/stock/"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<AreaModal />
|
||||||
|
|
||||||
|
<div class="flex h-full gap-3">
|
||||||
|
<div class="bg-card w-1/6 rounded p-3">
|
||||||
|
<MdItemTypeTree @node-click="handleTypeNodeClick" />
|
||||||
|
</div>
|
||||||
|
<div class="w-5/6">
|
||||||
|
<Grid table-title="库存台账列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('ui.actionTitle.export'),
|
||||||
|
type: 'primary',
|
||||||
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
|
auth: ['mes:wm-material-stock:export'],
|
||||||
|
onClick: handleExport,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #batchCode="{ row }">
|
||||||
|
<span v-if="row.batchId" :title="row.batchCode">
|
||||||
|
{{ row.batchCode }}
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
<template #areaName="{ row }">
|
||||||
|
<Button
|
||||||
|
v-if="row.areaId"
|
||||||
|
:title="row.areaName"
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
@click="handleOpenAreaDetail(row)"
|
||||||
|
>
|
||||||
|
{{ row.areaName }}
|
||||||
|
</Button>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as WmMaterialStockSelectDialog } from './wm-material-stock-select-dialog.vue';
|
||||||
|
export { default as WmMaterialStockSelect } from './wm-material-stock-select.vue';
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ElAlert, ElButton, ElDialog, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getMaterialStockPage } from '#/api/mes/wm/materialstock';
|
||||||
|
import MdItemTypeTree from '#/views/mes/md/item/type/components/md-item-type-tree.vue';
|
||||||
|
|
||||||
|
import { useSelectGridColumns, useSelectGridFormSchema } from '../data';
|
||||||
|
|
||||||
|
/** 虚拟仓过滤模式:'exclude' 排除虚拟仓(默认),'only' 只看虚拟仓,'all' 不过滤 */
|
||||||
|
type VirtualFilter = 'all' | 'exclude' | 'only';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
batchId?: number;
|
||||||
|
itemId?: number;
|
||||||
|
virtualFilter?: VirtualFilter;
|
||||||
|
warehouseId?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
batchId: undefined,
|
||||||
|
itemId: undefined,
|
||||||
|
virtualFilter: 'exclude',
|
||||||
|
warehouseId: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
selected: [rows: MesWmMaterialStockApi.MaterialStock[]];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const multiple = ref(true);
|
||||||
|
const syncingSingleSelection = ref(false);
|
||||||
|
const selectedRows = ref<MesWmMaterialStockApi.MaterialStock[]>([]);
|
||||||
|
const preSelectedIds = ref<number[]>([]);
|
||||||
|
const searchItemTypeId = ref<number>();
|
||||||
|
|
||||||
|
/** 当前 props 是否带预过滤 */
|
||||||
|
const showAlert = computed(
|
||||||
|
() =>
|
||||||
|
props.batchId != null ||
|
||||||
|
props.warehouseId != null ||
|
||||||
|
props.virtualFilter === 'only',
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 预过滤提示文字 */
|
||||||
|
const alertTitle = computed(() => {
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (props.batchId != null) {
|
||||||
|
parts.push('批次');
|
||||||
|
}
|
||||||
|
if (props.warehouseId != null) {
|
||||||
|
parts.push('仓库');
|
||||||
|
}
|
||||||
|
if (props.virtualFilter === 'only') {
|
||||||
|
parts.push('只看虚拟仓');
|
||||||
|
}
|
||||||
|
return `已按${parts.join('/')}预过滤`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 单选模式同步 VXE 勾选状态 */
|
||||||
|
async function syncSingleSelection(row?: MesWmMaterialStockApi.MaterialStock) {
|
||||||
|
syncingSingleSelection.value = true;
|
||||||
|
await nextTick();
|
||||||
|
await gridApi.grid.clearCheckboxRow();
|
||||||
|
if (row) {
|
||||||
|
await gridApi.grid.setCheckboxRow(row, true);
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
syncingSingleSelection.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理勾选变化 */
|
||||||
|
async function handleCheckboxChange({
|
||||||
|
checked,
|
||||||
|
records,
|
||||||
|
row,
|
||||||
|
}: {
|
||||||
|
checked: boolean;
|
||||||
|
records: MesWmMaterialStockApi.MaterialStock[];
|
||||||
|
row?: MesWmMaterialStockApi.MaterialStock;
|
||||||
|
}) {
|
||||||
|
if (syncingSingleSelection.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!multiple.value) {
|
||||||
|
const selected = checked && row ? [row] : [];
|
||||||
|
selectedRows.value = selected;
|
||||||
|
await syncSingleSelection(selected[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedRows.value = records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理全选变化 */
|
||||||
|
function handleCheckboxAll({
|
||||||
|
records,
|
||||||
|
}: {
|
||||||
|
records: MesWmMaterialStockApi.MaterialStock[];
|
||||||
|
}) {
|
||||||
|
if (syncingSingleSelection.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedRows.value = records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 双击行:单选直接确认;多选切换勾选 */
|
||||||
|
async function handleRowDblclick({
|
||||||
|
row,
|
||||||
|
}: {
|
||||||
|
row: MesWmMaterialStockApi.MaterialStock;
|
||||||
|
}) {
|
||||||
|
if (multiple.value) {
|
||||||
|
const checked = !gridApi.grid.isCheckedByCheckboxRow(row);
|
||||||
|
await gridApi.grid.setCheckboxRow(row, checked);
|
||||||
|
handleCheckboxChange({
|
||||||
|
checked,
|
||||||
|
records:
|
||||||
|
gridApi.grid.getCheckboxRecords() as MesWmMaterialStockApi.MaterialStock[],
|
||||||
|
row,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedRows.value = [row];
|
||||||
|
await syncSingleSelection(row);
|
||||||
|
handleConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 回显预选 */
|
||||||
|
function applyPreSelection() {
|
||||||
|
if (preSelectedIds.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rows = gridApi.grid.getData() as MesWmMaterialStockApi.MaterialStock[];
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.id && preSelectedIds.value.includes(row.id)) {
|
||||||
|
gridApi.grid.setCheckboxRow(row, true);
|
||||||
|
if (!multiple.value) {
|
||||||
|
selectedRows.value = [row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useSelectGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useSelectGridColumns(),
|
||||||
|
height: 480,
|
||||||
|
keepSource: true,
|
||||||
|
checkboxConfig: {
|
||||||
|
highlight: true,
|
||||||
|
range: true,
|
||||||
|
reserve: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getMaterialStockPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
batchId: props.batchId,
|
||||||
|
// 默认查未冻结
|
||||||
|
frozen: false,
|
||||||
|
itemTypeId: searchItemTypeId.value,
|
||||||
|
itemId: formValues.itemId ?? props.itemId,
|
||||||
|
warehouseId: formValues.warehouseId ?? props.warehouseId,
|
||||||
|
virtualFilter:
|
||||||
|
props.virtualFilter === 'all' ? undefined : props.virtualFilter,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>,
|
||||||
|
gridEvents: {
|
||||||
|
cellDblclick: handleRowDblclick,
|
||||||
|
checkboxAll: handleCheckboxAll,
|
||||||
|
checkboxChange: handleCheckboxChange,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 物料分类树节点点击 */
|
||||||
|
function handleTypeNodeClick(row: undefined | { id?: number }) {
|
||||||
|
searchItemTypeId.value = row?.id;
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置查询和选择状态 */
|
||||||
|
async function resetQueryState() {
|
||||||
|
selectedRows.value = [];
|
||||||
|
searchItemTypeId.value = undefined;
|
||||||
|
await gridApi.grid.clearCheckboxRow();
|
||||||
|
await gridApi.formApi.resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
async function openModal(
|
||||||
|
selectedIds?: number[],
|
||||||
|
options?: { multiple?: boolean },
|
||||||
|
) {
|
||||||
|
open.value = true;
|
||||||
|
multiple.value = options?.multiple ?? true;
|
||||||
|
preSelectedIds.value = selectedIds || [];
|
||||||
|
await nextTick();
|
||||||
|
await resetQueryState();
|
||||||
|
await gridApi.query();
|
||||||
|
await nextTick();
|
||||||
|
applyPreSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭弹窗 */
|
||||||
|
async function closeModal() {
|
||||||
|
open.value = false;
|
||||||
|
await resetQueryState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 确认选择 */
|
||||||
|
function handleConfirm() {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
ElMessage.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(
|
||||||
|
'selected',
|
||||||
|
multiple.value ? selectedRows.value : [selectedRows.value[0]!],
|
||||||
|
);
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open: openModal });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ElDialog
|
||||||
|
v-model="open"
|
||||||
|
destroy-on-close
|
||||||
|
title="库存物资选择"
|
||||||
|
width="80%"
|
||||||
|
@close="closeModal"
|
||||||
|
>
|
||||||
|
<ElAlert
|
||||||
|
v-if="showAlert"
|
||||||
|
class="mb-3"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:title="alertTitle"
|
||||||
|
type="info"
|
||||||
|
/>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<div class="bg-card w-1/6 rounded p-2">
|
||||||
|
<MdItemTypeTree @node-click="handleTypeNodeClick" />
|
||||||
|
</div>
|
||||||
|
<div class="w-5/6">
|
||||||
|
<Grid table-title="库存列表" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="closeModal">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirm">确定</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { computed, ref, useAttrs, watch } from 'vue';
|
||||||
|
|
||||||
|
import { CircleX, Search } from '@vben/icons';
|
||||||
|
|
||||||
|
import { ElInput, ElTooltip } from 'element-plus';
|
||||||
|
|
||||||
|
import { getMaterialStock } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import WmMaterialStockSelectDialog from './wm-material-stock-select-dialog.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'WmMaterialStockSelect', inheritAttrs: false });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
batchId?: number;
|
||||||
|
clearable?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
itemId?: number;
|
||||||
|
modelValue?: number;
|
||||||
|
placeholder?: string;
|
||||||
|
virtualFilter?: 'all' | 'exclude' | 'only';
|
||||||
|
warehouseId?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
batchId: undefined,
|
||||||
|
clearable: true,
|
||||||
|
disabled: false,
|
||||||
|
itemId: undefined,
|
||||||
|
modelValue: undefined,
|
||||||
|
placeholder: '请选择库存',
|
||||||
|
virtualFilter: 'exclude',
|
||||||
|
warehouseId: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
change: [item: MesWmMaterialStockApi.MaterialStock | undefined];
|
||||||
|
'update:modelValue': [value: number | undefined];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const dialogRef = ref<InstanceType<typeof WmMaterialStockSelectDialog>>();
|
||||||
|
const hovering = ref(false);
|
||||||
|
const selectedItem = ref<MesWmMaterialStockApi.MaterialStock>();
|
||||||
|
|
||||||
|
const displayLabel = computed(() => {
|
||||||
|
const item = selectedItem.value;
|
||||||
|
if (!item) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${item.warehouseName || '-'} / ${item.batchCode || '-'} / 数量:${item.quantity}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const showClear = computed(
|
||||||
|
() =>
|
||||||
|
props.clearable &&
|
||||||
|
!props.disabled &&
|
||||||
|
hovering.value &&
|
||||||
|
props.modelValue != null,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 根据 ID 单条查询库存信息(用于编辑回显) */
|
||||||
|
async function resolveItemById(id: number | undefined) {
|
||||||
|
if (id == null) {
|
||||||
|
selectedItem.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedItem.value?.id === id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
selectedItem.value = await getMaterialStock(id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[WmMaterialStockSelect] resolveItemById failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.modelValue, resolveItemById, { immediate: true });
|
||||||
|
|
||||||
|
/** 清空已选库存 */
|
||||||
|
function clearSelected() {
|
||||||
|
selectedItem.value = undefined;
|
||||||
|
emit('update:modelValue', undefined);
|
||||||
|
emit('change', undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开库存选择弹窗 */
|
||||||
|
function handleClick(event: MouseEvent) {
|
||||||
|
if (props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
if (showClear.value && target.closest('.el-input__suffix')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
clearSelected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
|
||||||
|
dialogRef.value?.open(selectedIds, { multiple: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 弹窗选中回调 */
|
||||||
|
function handleSelected(rows: MesWmMaterialStockApi.MaterialStock[]) {
|
||||||
|
const item = rows[0];
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedItem.value = item;
|
||||||
|
emit('update:modelValue', item.id);
|
||||||
|
emit('change', item);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-bind="attrs"
|
||||||
|
class="w-full"
|
||||||
|
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
||||||
|
@click="handleClick"
|
||||||
|
@mouseenter="hovering = true"
|
||||||
|
@mouseleave="hovering = false"
|
||||||
|
>
|
||||||
|
<ElTooltip :disabled="!selectedItem" placement="top" :show-after="500">
|
||||||
|
<template #content>
|
||||||
|
<div v-if="selectedItem" class="leading-6">
|
||||||
|
<div>物料:{{ selectedItem.itemName || '-' }}</div>
|
||||||
|
<div>批次:{{ selectedItem.batchCode || '-' }}</div>
|
||||||
|
<div>数量:{{ selectedItem.quantity ?? '-' }}</div>
|
||||||
|
<div>仓库:{{ selectedItem.warehouseName || '-' }}</div>
|
||||||
|
<div>库区:{{ selectedItem.locationName || '-' }}</div>
|
||||||
|
<div>库位:{{ selectedItem.areaName || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<ElInput
|
||||||
|
:disabled="disabled"
|
||||||
|
:model-value="displayLabel"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<CircleX v-if="showClear" class="size-4" />
|
||||||
|
<Search v-else class="size-4" />
|
||||||
|
</template>
|
||||||
|
</ElInput>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
<WmMaterialStockSelectDialog
|
||||||
|
ref="dialogRef"
|
||||||
|
:batch-id="batchId"
|
||||||
|
:item-id="itemId"
|
||||||
|
:virtual-filter="virtualFilter"
|
||||||
|
:warehouse-id="warehouseId"
|
||||||
|
@selected="handleSelected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
|
||||||
|
import MdItemSelect from '#/views/mes/md/item/components/md-item-select.vue';
|
||||||
|
import MdVendorSelect from '#/views/mes/md/vendor/components/md-vendor-select.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
WmWarehouseAreaSelect,
|
||||||
|
WmWarehouseLocationSelect,
|
||||||
|
WmWarehouseSelect,
|
||||||
|
} from '../warehouse/components';
|
||||||
|
|
||||||
|
/** 列表的搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'itemId',
|
||||||
|
label: '物料',
|
||||||
|
component: markRaw(MdItemSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择物料',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'batchCode',
|
||||||
|
label: '批次号',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入批次号',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'warehouseId',
|
||||||
|
label: '仓库',
|
||||||
|
component: markRaw(WmWarehouseSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择仓库',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'locationId',
|
||||||
|
label: '库区',
|
||||||
|
component: markRaw(WmWarehouseLocationSelect),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['warehouseId'],
|
||||||
|
componentProps: (values) => ({
|
||||||
|
placeholder: '请选择库区',
|
||||||
|
warehouseId: values.warehouseId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'frozen',
|
||||||
|
label: '是否冻结',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
options: [
|
||||||
|
{ label: '是', value: true },
|
||||||
|
{ label: '否', value: false },
|
||||||
|
],
|
||||||
|
placeholder: '请选择',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的字段 */
|
||||||
|
export function useGridColumns(
|
||||||
|
onFrozenChange: (row: MesWmMaterialStockApi.MaterialStock) => void,
|
||||||
|
): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'itemCode',
|
||||||
|
title: '产品物料编码',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'itemName',
|
||||||
|
title: '产品物料名称',
|
||||||
|
minWidth: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'specification',
|
||||||
|
title: '规格型号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'quantity',
|
||||||
|
title: '在库数量',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'unitMeasureName',
|
||||||
|
title: '单位',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'batchCode',
|
||||||
|
title: '批次号',
|
||||||
|
minWidth: 140,
|
||||||
|
slots: { default: 'batchCode' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'warehouseName',
|
||||||
|
title: '仓库',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'locationName',
|
||||||
|
title: '库区',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'areaName',
|
||||||
|
title: '库位',
|
||||||
|
minWidth: 100,
|
||||||
|
slots: { default: 'areaName' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'receiptTime',
|
||||||
|
title: '入库日期',
|
||||||
|
width: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'frozen',
|
||||||
|
title: '冻结',
|
||||||
|
width: 90,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellSwitch',
|
||||||
|
attrs: { beforeChange: onFrozenChange },
|
||||||
|
props: { activeValue: true, inactiveValue: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择弹窗的搜索表单 */
|
||||||
|
export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'itemId',
|
||||||
|
label: '物料',
|
||||||
|
component: markRaw(MdItemSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择物料',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'vendorId',
|
||||||
|
label: '供应商',
|
||||||
|
component: markRaw(MdVendorSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择供应商',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'batchCode',
|
||||||
|
label: '批次号',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: '请输入批次号',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'warehouseId',
|
||||||
|
label: '仓库',
|
||||||
|
component: markRaw(WmWarehouseSelect),
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请选择仓库',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'locationId',
|
||||||
|
label: '库区',
|
||||||
|
component: markRaw(WmWarehouseLocationSelect),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['warehouseId'],
|
||||||
|
componentProps: (values) => ({
|
||||||
|
placeholder: '请选择库区',
|
||||||
|
warehouseId: values.warehouseId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'areaId',
|
||||||
|
label: '库位',
|
||||||
|
component: markRaw(WmWarehouseAreaSelect),
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['locationId'],
|
||||||
|
componentProps: (values) => ({
|
||||||
|
locationId: values.locationId,
|
||||||
|
placeholder: '请选择库位',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择弹窗的字段 */
|
||||||
|
export function useSelectGridColumns(): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'itemCode',
|
||||||
|
title: '产品物料编码',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'itemName',
|
||||||
|
title: '产品物料名称',
|
||||||
|
minWidth: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'specification',
|
||||||
|
title: '规格型号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'unitMeasureName',
|
||||||
|
title: '单位',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'batchCode',
|
||||||
|
title: '入库批次号',
|
||||||
|
minWidth: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'warehouseName',
|
||||||
|
title: '仓库',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'locationName',
|
||||||
|
title: '库区',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'areaName',
|
||||||
|
title: '库位',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'quantity',
|
||||||
|
title: '在库数量',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'receiptTime',
|
||||||
|
title: '入库日期',
|
||||||
|
width: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'frozen',
|
||||||
|
title: '冻结',
|
||||||
|
width: 80,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellDict',
|
||||||
|
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
|
import { ElButton, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
exportMaterialStock,
|
||||||
|
getMaterialStockPage,
|
||||||
|
updateMaterialStockFrozen,
|
||||||
|
} from '#/api/mes/wm/materialstock';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import MdItemTypeTree from '#/views/mes/md/item/type/components/md-item-type-tree.vue';
|
||||||
|
import AreaForm from '#/views/mes/wm/warehouse/area/modules/form.vue';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
|
const [AreaModal, areaModalApi] = useVbenModal({
|
||||||
|
connectedComponent: AreaForm,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 处理冻结状态切换 */
|
||||||
|
async function handleFrozenChange(row: MesWmMaterialStockApi.MaterialStock) {
|
||||||
|
const text = row.frozen ? '冻结' : '解冻';
|
||||||
|
try {
|
||||||
|
await confirm(`确认要"${text}"该库存记录吗?`);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await updateMaterialStockFrozen({
|
||||||
|
id: row.id!,
|
||||||
|
frozen: row.frozen!,
|
||||||
|
});
|
||||||
|
ElMessage.success(`${text}成功`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 已选物料分类 */
|
||||||
|
const searchItemTypeId = ref<number>();
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(handleFrozenChange),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getMaterialStockPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
itemTypeId: searchItemTypeId.value,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 物料分类树节点点击 */
|
||||||
|
function handleTypeNodeClick(row: undefined | { id?: number }) {
|
||||||
|
searchItemTypeId.value = row?.id;
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开库位详情弹窗 */
|
||||||
|
function handleOpenAreaDetail(row: MesWmMaterialStockApi.MaterialStock) {
|
||||||
|
if (!row.areaId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
areaModalApi.setData({ formType: 'detail', id: row.areaId }).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出表格 */
|
||||||
|
async function handleExport() {
|
||||||
|
const data = await exportMaterialStock({
|
||||||
|
...(await gridApi.formApi.getValues()),
|
||||||
|
itemTypeId: searchItemTypeId.value,
|
||||||
|
});
|
||||||
|
downloadFileFromBlobPart({ fileName: '库存台账.xls', source: data });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<template #doc>
|
||||||
|
<DocAlert
|
||||||
|
title="【仓库】批次管理、库存现有量、库存事务"
|
||||||
|
url="https://doc.iocoder.cn/mes/wm/stock/"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<AreaModal />
|
||||||
|
|
||||||
|
<div class="flex h-full gap-3">
|
||||||
|
<div class="bg-card w-1/6 rounded p-3">
|
||||||
|
<MdItemTypeTree @node-click="handleTypeNodeClick" />
|
||||||
|
</div>
|
||||||
|
<div class="w-5/6">
|
||||||
|
<Grid table-title="库存台账列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('ui.actionTitle.export'),
|
||||||
|
type: 'primary',
|
||||||
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
|
auth: ['mes:wm-material-stock:export'],
|
||||||
|
onClick: handleExport,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #batchCode="{ row }">
|
||||||
|
<span v-if="row.batchId" :title="row.batchCode">
|
||||||
|
{{ row.batchCode }}
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
<template #areaName="{ row }">
|
||||||
|
<ElButton
|
||||||
|
v-if="row.areaId"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
:title="row.areaName"
|
||||||
|
type="primary"
|
||||||
|
@click="handleOpenAreaDetail(row)"
|
||||||
|
>
|
||||||
|
{{ row.areaName }}
|
||||||
|
</ElButton>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
Loading…
Reference in New Issue