重构盘点范围选择交互,将单一抽屉改为分步选择模式,新增盘点范围类型字段,支持仓库/库区/库位分别选择并可移除,优化选择流程和用户体验
This commit is contained in:
@@ -22,23 +22,59 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="盘点范围" prop="warehouseIds">
|
||||
<el-form-item label="盘点范围类型" prop="scope">
|
||||
<el-select v-model="form.scope" placeholder="请选择盘点范围类型" style="width: 100%" @change="onScopeTypeChange">
|
||||
<el-option label="按仓库盘点" :value="1" />
|
||||
<el-option label="按库区盘点" :value="2" />
|
||||
<el-option label="按库位盘点" :value="3" />
|
||||
<el-option label="按SKU盘点" :value="4" />
|
||||
<el-option label="按资产盘点" :value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 选择仓库(按仓库/库区/库位盘点都需要) -->
|
||||
<el-form-item label="选择仓库" prop="warehouseIds" v-if="form.scope >= 1 && form.scope <= 3">
|
||||
<div class="scope-select-box">
|
||||
<div class="scope-tags" v-if="scopeDisplayText">
|
||||
<el-tag type="info" size="small">{{ getScopeLabel(form.scope) }}</el-tag>
|
||||
<el-tag v-for="name in selectedWarehouseNames" :key="name" size="small" style="margin-left: 4px;">{{ name }}</el-tag>
|
||||
<el-tag v-for="name in selectedZoneNames" :key="name" size="small" type="success" style="margin-left: 4px;">{{ name }}</el-tag>
|
||||
<el-tag v-for="name in selectedLocationNames" :key="name" size="small" type="warning" style="margin-left: 4px;">{{ name }}</el-tag>
|
||||
<div class="scope-tags" v-if="form.warehouseIds.length > 0">
|
||||
<el-tag v-for="name in selectedWarehouseNames" :key="name" size="small" closable @close="removeWarehouse(name)">{{ name }}</el-tag>
|
||||
</div>
|
||||
<el-button type="primary" link @click="openScopeDrawer">
|
||||
<el-icon><ele-Setting /></el-icon>
|
||||
{{ scopeDisplayText ? '修改' : '选择盘点范围' }}
|
||||
<el-button type="primary" @click="openSelectDialog(1)">
|
||||
<el-icon><ele-Plus /></el-icon>
|
||||
{{ form.warehouseIds.length > 0 ? '继续选择' : '选择仓库' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 盘点范围选择抽屉 -->
|
||||
<ScopeSelectDrawer ref="scopeDrawerRef" @confirm="onScopeConfirm" />
|
||||
<!-- 选择库区(按库区/库位盘点需要) -->
|
||||
<el-form-item label="选择库区" prop="zoneIds" v-if="form.scope >= 2 && form.scope <= 3">
|
||||
<div class="scope-select-box">
|
||||
<div class="scope-tags" v-if="form.zoneIds.length > 0">
|
||||
<el-tag v-for="name in selectedZoneNames" :key="name" size="small" type="success" closable @close="removeZone(name)">{{ name }}</el-tag>
|
||||
</div>
|
||||
<el-button type="primary" @click="openSelectDialog(2)" :disabled="form.warehouseIds.length === 0">
|
||||
<el-icon><ele-Plus /></el-icon>
|
||||
{{ form.zoneIds.length > 0 ? '继续选择' : '选择库区' }}
|
||||
</el-button>
|
||||
<span v-if="form.warehouseIds.length === 0" class="tip-text">请先选择仓库</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 选择库位(按库位盘点需要) -->
|
||||
<el-form-item label="选择库位" prop="locationIds" v-if="form.scope === 3">
|
||||
<div class="scope-select-box">
|
||||
<div class="scope-tags" v-if="form.locationIds.length > 0">
|
||||
<el-tag v-for="name in selectedLocationNames" :key="name" size="small" type="warning" closable @close="removeLocation(name)">{{ name }}</el-tag>
|
||||
</div>
|
||||
<el-button type="primary" @click="openSelectDialog(3)" :disabled="form.zoneIds.length === 0">
|
||||
<el-icon><ele-Plus /></el-icon>
|
||||
{{ form.locationIds.length > 0 ? '继续选择' : '选择库位' }}
|
||||
</el-button>
|
||||
<span v-if="form.zoneIds.length === 0" class="tip-text">请先选择库区</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 选择弹窗 -->
|
||||
<ScopeSelectDialog ref="scopeSelectDialogRef" @confirm="onSelectConfirm" />
|
||||
|
||||
|
||||
<el-form-item label="负责人" prop="assigneeId">
|
||||
@@ -66,14 +102,14 @@ import { ref, reactive, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { createInventoryCount, updateInventoryCount, getInventoryCount } from '/@/api/assets/operation/count';
|
||||
import ScopeSelectDrawer from './scopeSelectDrawer.vue';
|
||||
import ScopeSelectDialog from './scopeSelectDialog.vue';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const scopeDrawerRef = ref();
|
||||
const scopeSelectDialogRef = ref();
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
@@ -98,15 +134,53 @@ const selectedLocationNames = ref<string[]>([]);
|
||||
// 参与人员输入框
|
||||
const participantsInput = ref('');
|
||||
|
||||
// 盘点范围显示文本
|
||||
const scopeDisplayText = computed(() => {
|
||||
return form.warehouseIds.length > 0;
|
||||
});
|
||||
// 当前选择的类型(1-仓库 2-库区 3-库位)
|
||||
const currentSelectType = ref(1);
|
||||
|
||||
// 获取盘点范围标签
|
||||
const getScopeLabel = (scope: number) => {
|
||||
const labels: Record<number, string> = { 1: '按仓库盘点', 2: '按库区盘点', 3: '按库位盘点', 4: '按SKU盘点', 5: '按资产盘点' };
|
||||
return labels[scope] || '';
|
||||
// 移除仓库
|
||||
const removeWarehouse = (name: string) => {
|
||||
const idx = selectedWarehouseNames.value.indexOf(name);
|
||||
if (idx > -1) {
|
||||
selectedWarehouseNames.value.splice(idx, 1);
|
||||
form.warehouseIds.splice(idx, 1);
|
||||
// 清空下级选择
|
||||
form.zoneIds = [];
|
||||
form.locationIds = [];
|
||||
selectedZoneNames.value = [];
|
||||
selectedLocationNames.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 移除库区
|
||||
const removeZone = (name: string) => {
|
||||
const idx = selectedZoneNames.value.indexOf(name);
|
||||
if (idx > -1) {
|
||||
selectedZoneNames.value.splice(idx, 1);
|
||||
form.zoneIds.splice(idx, 1);
|
||||
// 清空下级选择
|
||||
form.locationIds = [];
|
||||
selectedLocationNames.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 移除库位
|
||||
const removeLocation = (name: string) => {
|
||||
const idx = selectedLocationNames.value.indexOf(name);
|
||||
if (idx > -1) {
|
||||
selectedLocationNames.value.splice(idx, 1);
|
||||
form.locationIds.splice(idx, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 盘点范围类型变化
|
||||
const onScopeTypeChange = () => {
|
||||
// 清空已选项
|
||||
form.warehouseIds = [];
|
||||
form.zoneIds = [];
|
||||
form.locationIds = [];
|
||||
selectedWarehouseNames.value = [];
|
||||
selectedZoneNames.value = [];
|
||||
selectedLocationNames.value = [];
|
||||
};
|
||||
|
||||
// 表单校验规则
|
||||
@@ -186,25 +260,52 @@ const resetForm = () => {
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 打开盘点范围选择抽屉
|
||||
const openScopeDrawer = () => {
|
||||
scopeDrawerRef.value?.openDrawer({
|
||||
scope: form.scope,
|
||||
// 打开选择弹窗
|
||||
const openSelectDialog = (type: number) => {
|
||||
currentSelectType.value = type;
|
||||
let selectedIds: string[] = [];
|
||||
let selectedNames: string[] = [];
|
||||
|
||||
if (type === 1) {
|
||||
selectedIds = form.warehouseIds;
|
||||
selectedNames = selectedWarehouseNames.value;
|
||||
} else if (type === 2) {
|
||||
selectedIds = form.zoneIds;
|
||||
selectedNames = selectedZoneNames.value;
|
||||
} else if (type === 3) {
|
||||
selectedIds = form.locationIds;
|
||||
selectedNames = selectedLocationNames.value;
|
||||
}
|
||||
|
||||
scopeSelectDialogRef.value?.open({
|
||||
scope: type,
|
||||
selectedIds,
|
||||
selectedNames,
|
||||
warehouseIds: form.warehouseIds,
|
||||
zoneIds: form.zoneIds,
|
||||
locationIds: form.locationIds,
|
||||
});
|
||||
};
|
||||
|
||||
// 盘点范围选择确认
|
||||
const onScopeConfirm = (data: any) => {
|
||||
form.scope = data.scope;
|
||||
form.warehouseIds = data.warehouseIds;
|
||||
form.zoneIds = data.zoneIds;
|
||||
form.locationIds = data.locationIds;
|
||||
selectedWarehouseNames.value = data.warehouseNames;
|
||||
selectedZoneNames.value = data.zoneNames;
|
||||
selectedLocationNames.value = data.locationNames;
|
||||
// 选择确认
|
||||
const onSelectConfirm = (data: any) => {
|
||||
if (currentSelectType.value === 1) {
|
||||
form.warehouseIds = data.selectedIds;
|
||||
selectedWarehouseNames.value = data.selectedNames;
|
||||
// 清空下级选择
|
||||
form.zoneIds = [];
|
||||
form.locationIds = [];
|
||||
selectedZoneNames.value = [];
|
||||
selectedLocationNames.value = [];
|
||||
} else if (currentSelectType.value === 2) {
|
||||
form.zoneIds = data.selectedIds;
|
||||
selectedZoneNames.value = data.selectedNames;
|
||||
// 清空下级选择
|
||||
form.locationIds = [];
|
||||
selectedLocationNames.value = [];
|
||||
} else if (currentSelectType.value === 3) {
|
||||
form.locationIds = data.selectedIds;
|
||||
selectedLocationNames.value = data.selectedNames;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
@@ -269,7 +370,7 @@ defineExpose({
|
||||
<style scoped lang="scss">
|
||||
.scope-select-box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
@@ -277,7 +378,12 @@ defineExpose({
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
313
src/views/assets/operation/count/component/scopeSelectDialog.vue
Normal file
313
src/views/assets/operation/count/component/scopeSelectDialog.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogTitle"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入关键词搜索"
|
||||
clearable
|
||||
style="width: 300px"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="handleSearch">
|
||||
<el-icon><ele-Search /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
border
|
||||
style="width: 100%; margin-top: 15px"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="name" :label="getColumnLabel()" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="code" label="编码" width="150" show-overflow-tooltip v-if="scope <= 2" />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-box">
|
||||
<el-pagination
|
||||
v-model:current-page="pageNum"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 已选项展示 -->
|
||||
<div class="selected-box" v-if="selectedItems.length > 0">
|
||||
<span class="selected-label">已选择 {{ selectedItems.length }} 项:</span>
|
||||
<el-tag
|
||||
v-for="item in selectedItems"
|
||||
:key="item.id"
|
||||
size="small"
|
||||
closable
|
||||
style="margin-right: 4px; margin-bottom: 4px"
|
||||
@close="removeSelected(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { listWarehouses } from '/@/api/assets/warehouse';
|
||||
import { listZones } from '/@/api/assets/zone';
|
||||
import { listLocations } from '/@/api/assets/location';
|
||||
|
||||
const emit = defineEmits(['confirm']);
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const scope = ref(1); // 1-仓库 2-库区 3-库位
|
||||
const searchKeyword = ref('');
|
||||
const tableData = ref<any[]>([]);
|
||||
const total = ref(0);
|
||||
const pageNum = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const tableRef = ref();
|
||||
|
||||
// 已选项
|
||||
const selectedItems = ref<any[]>([]);
|
||||
|
||||
// 父级ID(库区需要仓库ID,库位需要库区ID)
|
||||
const parentWarehouseIds = ref<string[]>([]);
|
||||
const parentZoneIds = ref<string[]>([]);
|
||||
|
||||
// 弹窗标题
|
||||
const dialogTitle = computed(() => {
|
||||
const titles: Record<number, string> = {
|
||||
1: '选择仓库',
|
||||
2: '选择库区',
|
||||
3: '选择库位',
|
||||
};
|
||||
return titles[scope.value] || '选择';
|
||||
});
|
||||
|
||||
// 获取列标题
|
||||
const getColumnLabel = () => {
|
||||
const labels: Record<number, string> = {
|
||||
1: '仓库名称',
|
||||
2: '库区名称',
|
||||
3: '库位名称',
|
||||
};
|
||||
return labels[scope.value] || '名称';
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const open = async (data: {
|
||||
scope: number;
|
||||
selectedIds: string[];
|
||||
selectedNames: string[];
|
||||
warehouseIds?: string[];
|
||||
zoneIds?: string[];
|
||||
}) => {
|
||||
scope.value = data.scope;
|
||||
parentWarehouseIds.value = data.warehouseIds || [];
|
||||
parentZoneIds.value = data.zoneIds || [];
|
||||
|
||||
// 恢复已选项
|
||||
selectedItems.value = data.selectedIds.map((id, idx) => ({
|
||||
id,
|
||||
name: data.selectedNames[idx] || id,
|
||||
}));
|
||||
|
||||
searchKeyword.value = '';
|
||||
pageNum.value = 1;
|
||||
visible.value = true;
|
||||
|
||||
await loadData();
|
||||
};
|
||||
|
||||
// 加载数据
|
||||
const loadData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
let res: any;
|
||||
const params = {
|
||||
keyword: searchKeyword.value,
|
||||
pageNum: pageNum.value,
|
||||
pageSize: pageSize.value,
|
||||
};
|
||||
|
||||
if (scope.value === 1) {
|
||||
// 加载仓库
|
||||
res = await listWarehouses(params);
|
||||
const list = res.data?.list || [];
|
||||
tableData.value = list.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.warehouseName || item.name,
|
||||
code: item.warehouseCode || item.code,
|
||||
}));
|
||||
total.value = res.data?.total || 0;
|
||||
} else if (scope.value === 2) {
|
||||
// 加载库区
|
||||
if (parentWarehouseIds.value.length === 0) {
|
||||
tableData.value = [];
|
||||
total.value = 0;
|
||||
return;
|
||||
}
|
||||
const allZones: any[] = [];
|
||||
let totalCount = 0;
|
||||
for (const warehouseId of parentWarehouseIds.value) {
|
||||
res = await listZones({ ...params, warehouseId });
|
||||
const list = res.data?.list || res.data?.items || res.data || [];
|
||||
const zones = (Array.isArray(list) ? list : []).map((item: any) => ({
|
||||
id: item.id || item._id,
|
||||
name: item.zoneName || item.name,
|
||||
code: item.zoneCode || item.code,
|
||||
}));
|
||||
allZones.push(...zones);
|
||||
totalCount += res.data?.total || zones.length;
|
||||
}
|
||||
tableData.value = allZones;
|
||||
total.value = totalCount;
|
||||
} else if (scope.value === 3) {
|
||||
// 加载库位
|
||||
if (parentZoneIds.value.length === 0) {
|
||||
tableData.value = [];
|
||||
total.value = 0;
|
||||
return;
|
||||
}
|
||||
const allLocations: any[] = [];
|
||||
let totalCount = 0;
|
||||
for (const zoneId of parentZoneIds.value) {
|
||||
res = await listLocations({ ...params, zoneId });
|
||||
const list = res.data?.list || res.data?.items || res.data || [];
|
||||
const locations = (Array.isArray(list) ? list : []).map((item: any) => ({
|
||||
id: item.id || item._id,
|
||||
name: item.locationName || item.name,
|
||||
code: item.locationCode || item.code,
|
||||
}));
|
||||
allLocations.push(...locations);
|
||||
totalCount += res.data?.total || locations.length;
|
||||
}
|
||||
tableData.value = allLocations;
|
||||
total.value = totalCount;
|
||||
}
|
||||
|
||||
// 恢复选中状态
|
||||
setTimeout(() => {
|
||||
tableData.value.forEach((row: any) => {
|
||||
if (selectedItems.value.some(item => item.id === row.id)) {
|
||||
tableRef.value?.toggleRowSelection(row, true);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pageNum.value = 1;
|
||||
loadData();
|
||||
};
|
||||
|
||||
// 分页大小变化
|
||||
const handleSizeChange = () => {
|
||||
pageNum.value = 1;
|
||||
loadData();
|
||||
};
|
||||
|
||||
// 页码变化
|
||||
const handleCurrentChange = () => {
|
||||
loadData();
|
||||
};
|
||||
|
||||
// 选择变化
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
// 合并当前页选中和之前选中的(去除当前页取消选中的)
|
||||
const currentPageIds = tableData.value.map(item => item.id);
|
||||
|
||||
// 移除当前页的旧选中
|
||||
const otherSelected = selectedItems.value.filter(item => !currentPageIds.includes(item.id));
|
||||
|
||||
// 添加当前页新选中
|
||||
selectedItems.value = [...otherSelected, ...selection];
|
||||
};
|
||||
|
||||
// 移除已选
|
||||
const removeSelected = (item: any) => {
|
||||
const idx = selectedItems.value.findIndex(i => i.id === item.id);
|
||||
if (idx > -1) {
|
||||
selectedItems.value.splice(idx, 1);
|
||||
// 取消表格选中
|
||||
const row = tableData.value.find(r => r.id === item.id);
|
||||
if (row) {
|
||||
tableRef.value?.toggleRowSelection(row, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', {
|
||||
selectedIds: selectedItems.value.map(item => item.id),
|
||||
selectedNames: selectedItems.value.map(item => item.name),
|
||||
});
|
||||
handleClose();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-box {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.selected-box {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
|
||||
.selected-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user