You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

431 lines
18 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="库位编号" prop="locationCode">
<el-input v-model="queryParams.locationCode" placeholder="请输入库位编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="库位名称" prop="locationName">
<el-input v-model="queryParams.locationName" placeholder="请输入库位名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="库位状态" prop="locationStatus">
<el-select v-model="queryParams.locationStatus" placeholder="请选择库位状态" clearable>
<el-option v-for="dict in wcs_location_status" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" style="width: 410px">
<el-date-picker
v-model="dateRange"
:format="CREATED_TIME_PICKER_FORMAT"
:value-format="CREATED_TIME_VALUE_FORMAT"
type="datetimerange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['wcs:locationInfo:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['wcs:locationInfo:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['wcs:locationInfo:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['wcs:locationInfo:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="locationInfoList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" type="index" width="60" />
<el-table-column label="库位编号" align="center" prop="locationCode" />
<el-table-column label="库位名称" align="center" prop="locationName" />
<el-table-column label="库位区域" align="center" prop="locationArea" />
<el-table-column label="所属仓库" align="center" prop="storeName" />
<el-table-column label="排" align="center" prop="locationRows" />
<el-table-column label="列" align="center" prop="locationColumns" />
<el-table-column label="层" align="center" prop="locationLayers" />
<el-table-column label="AGV定位" align="center" prop="agvPosition" />
<el-table-column label="物料信息" align="center" prop="materialName" />
<el-table-column label="托盘条码" align="center" prop="palletBarcode" />
<el-table-column label="库存数量" align="center" prop="stackCount" />
<el-table-column label="库位状态" align="center" prop="locationStatus">
<template #default="scope">
<dict-tag :options="wcs_location_status" :value="scope.row.locationStatus"/>
</template>
</el-table-column>
<el-table-column label="是否标识" align="center" prop="isFlag">
<template #default="scope">
<dict-tag :options="wcs_is_flag" :value="scope.row.isFlag"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, CREATED_TIME_DISPLAY_FORMAT) }}</span>
</template>
</el-table-column>
<el-table-column label="创建人" align="center" prop="createByName" width="100" />
<el-table-column label="更新人" align="center" prop="updateByName" width="100" />
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width" width="120">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['wcs:locationInfo:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['wcs:locationInfo:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="650px" append-to-body>
<el-form ref="locationInfoFormRef" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="库位编号" prop="locationCode">
<el-input v-model="form.locationCode" placeholder="请输入库位编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="库位名称" prop="locationName">
<el-input v-model="form.locationName" placeholder="请输入库位名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="库位区域" prop="locationArea">
<el-input v-model="form.locationArea" placeholder="请输入库位区域" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属仓库" prop="storeCode">
<el-select v-model="form.storeCode" placeholder="请选择仓库" clearable style="width: 100%">
<el-option v-for="item in storeList" :key="item.objId" :label="item.storeName" :value="item.storeCode"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="排" prop="locationRows">
<el-input-number v-model="form.locationRows" :min="0" placeholder="排" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="列" prop="locationColumns">
<el-input-number v-model="form.locationColumns" :min="0" placeholder="列" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="层" prop="locationLayers">
<el-input-number v-model="form.locationLayers" :min="0" placeholder="层" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="AGV定位" prop="agvPosition">
<el-input v-model="form.agvPosition" placeholder="请输入AGV定位" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="物料信息" prop="materialCode">
<el-select v-model="form.materialCode" placeholder="请选择物料" clearable style="width: 100%">
<el-option v-for="item in materialList" :key="item.objId" :label="item.materialName" :value="item.materialCode"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="托盘条码" prop="palletBarcode">
<el-input v-model="form.palletBarcode" placeholder="请输入托盘条码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="库存数量" prop="stackCount">
<el-input v-model="form.stackCount" placeholder="请输入库存数量" inputmode="numeric" @input="normalizeStackCount" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="库位状态" prop="locationStatus">
<el-select v-model="form.locationStatus" placeholder="请选择库位状态" clearable style="width: 100%">
<el-option v-for="dict in wcs_location_status" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否标识" prop="isFlag">
<el-radio-group v-model="form.isFlag">
<el-radio v-for="dict in wcs_is_flag" :key="dict.value" :value="parseInt(dict.value)">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="LocationInfo" lang="ts">
import { listLocationInfo, getLocationInfo, delLocationInfo, addLocationInfo, updateLocationInfo } from '@/api/wcs/locationInfo';
import { getStoreInfoList } from '@/api/wcs/storeInfo';
import { getMaterialInfoList } from '@/api/wcs/materialInfo';
import { LocationInfoVO, LocationInfoQuery, LocationInfoForm } from '@/api/wcs/locationInfo/types';
import { StoreInfoVO } from '@/api/wcs/storeInfo/types';
import { MaterialInfoVO } from '@/api/wcs/materialInfo/types';
import {
CREATED_TIME_DISPLAY_FORMAT,
CREATED_TIME_PICKER_FORMAT,
CREATED_TIME_VALUE_FORMAT,
getTodayCreatedTimeRange
} from '../utils/createTimeRange';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wcs_location_status, wcs_is_flag } = toRefs<any>(proxy?.useDict('wcs_location_status', 'wcs_is_flag'));
const locationInfoList = ref<LocationInfoVO[]>([]);
const storeList = ref<StoreInfoVO[]>([]);
const materialList = ref<MaterialInfoVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
// 创建时间范围筛选,与 SysUser 一致由 addDateRange 注入到 params.beginTime/endTime。
const dateRange = ref<[string, string]>(getTodayCreatedTimeRange());
const queryFormRef = ref<ElFormInstance>();
const locationInfoFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const normalizeStackCount = (value: string) => {
// 数据库当前为 varchar前端只过滤非法字符避免未做迁移时改变接口字段类型。
form.value.stackCount = value.replace(/\D/g, '');
}
const validateStackCount = (_rule: unknown, value: string | undefined, callback: (error?: Error) => void) => {
if (!value || /^\d+$/.test(value)) {
callback();
return;
}
callback(new Error('库存数量只能填写非负整数'));
}
const initFormData: LocationInfoForm = {
objId: undefined,
locationCode: undefined,
locationName: undefined,
locationArea: undefined,
storeCode: undefined,
locationRows: undefined,
locationColumns: undefined,
locationLayers: undefined,
agvPosition: undefined,
materialCode: undefined,
palletBarcode: undefined,
stackCount: undefined,
locationStatus: 0,
isFlag: 1,
remark: undefined,
}
const data = reactive<PageData<LocationInfoForm, LocationInfoQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
locationCode: undefined,
locationName: undefined,
locationArea: undefined,
storeCode: undefined,
locationRows: undefined,
locationColumns: undefined,
locationLayers: undefined,
agvPosition: undefined,
materialCode: undefined,
palletBarcode: undefined,
stackCount: undefined,
locationStatus: undefined,
isFlag: undefined,
params: {}
},
rules: {
locationCode: [
{ required: true, message: "库位编号不能为空", trigger: "blur" }
],
storeCode: [
{ required: true, message: "仓库编号不能为空", trigger: "blur" }
],
stackCount: [
{ validator: validateStackCount, trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
const getList = async () => {
loading.value = true;
// 走 addDateRange 把日期范围塞到 params.beginTime/endTime对齐后端 SysUser 的范围筛选契约。
const res = await listLocationInfo(proxy?.addDateRange(queryParams.value, dateRange.value));
locationInfoList.value = res.rows;
total.value = res.total;
loading.value = false;
}
const getRefLists = async () => {
try {
// 下拉接口只需要筛选条件,不携带分页必填字段,避免为满足类型而使用 any。
const refQuery = { params: {} };
const [storeRes, materialRes] = await Promise.all([
getStoreInfoList(refQuery),
getMaterialInfoList(refQuery)
]);
storeList.value = storeRes.data || [];
materialList.value = materialRes.data || [];
} catch (e) {
console.error('获取库位关联下拉列表失败', e);
}
}
const cancel = () => {
reset();
dialog.visible = false;
}
const reset = () => {
form.value = { ...initFormData, isFlag: 1, locationStatus: 0 };
locationInfoFormRef.value?.resetFields();
}
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
const resetQuery = () => {
queryFormRef.value?.resetFields();
// 重置后恢复当天完整范围,避免刚新增的数据落在旧结束时间之后。
dateRange.value = getTodayCreatedTimeRange();
handleQuery();
}
const handleSelectionChange = (selection: LocationInfoVO[]) => {
ids.value = selection.map(item => item.objId);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
const handleAdd = () => {
reset();
getRefLists();
dialog.visible = true;
dialog.title = "添加库位信息";
}
const handleUpdate = async (row?: LocationInfoVO) => {
reset();
getRefLists();
const _objId = row?.objId || ids.value[0]
const res = await getLocationInfo(_objId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = "修改库位信息";
}
const buildSubmitData = (): LocationInfoForm => ({
objId: form.value.objId,
locationCode: form.value.locationCode,
locationName: form.value.locationName,
locationArea: form.value.locationArea,
storeCode: form.value.storeCode,
locationRows: form.value.locationRows,
locationColumns: form.value.locationColumns,
locationLayers: form.value.locationLayers,
agvPosition: form.value.agvPosition,
materialCode: form.value.materialCode,
palletBarcode: form.value.palletBarcode,
stackCount: form.value.stackCount,
locationStatus: form.value.locationStatus,
isFlag: form.value.isFlag,
remark: form.value.remark
});
const submitForm = () => {
locationInfoFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// 仓库名称、物料名称是选择后带出的展示字段,提交时只传真实业务字段。
const submitData = buildSubmitData();
if (submitData.objId) {
await updateLocationInfo(submitData).finally(() => buttonLoading.value = false);
} else {
await addLocationInfo(submitData).finally(() => buttonLoading.value = false);
}
proxy?.$modal.msgSuccess("操作成功");
dialog.visible = false;
await getList();
}
});
}
const handleDelete = async (row?: LocationInfoVO) => {
const _objIds = row?.objId || ids.value;
try {
await proxy?.$modal.confirm('是否确认删除库位信息编号为"' + _objIds + '"的数据项?');
await delLocationInfo(_objIds);
proxy?.$modal.msgSuccess("删除成功");
await getList();
} catch (e) {
if (e !== 'cancel' && e !== 'close') {
console.error('删除库位信息失败', e);
}
}
}
const handleExport = () => {
proxy?.download('wcs/locationInfo/export', {
...queryParams.value
}, `locationInfo_${new Date().getTime()}.xlsx`)
}
onMounted(() => {
getList();
});
</script>