no message

main
HUOJIN\92525 2025-12-21 18:41:10 +08:00
parent d308c16400
commit bf33b23154
13 changed files with 485 additions and 365 deletions

View File

@ -5,6 +5,7 @@ const { createConfirm } = useMessage();
enum Api { enum Api {
list = '/base/area/list', list = '/base/area/list',
queryById='/base/area/queryById',
save = '/base/area/add', save = '/base/area/add',
edit = '/base/area/edit', edit = '/base/area/edit',
deleteOne = '/base/area/delete', deleteOne = '/base/area/delete',
@ -30,6 +31,11 @@ export const getImportUrl = Api.importExcel;
*/ */
export const list = (params) => defHttp.get({ url: Api.list, params }); export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* id
* @param params
*/
export const queryById = (params) => defHttp.get({ url: Api.queryById, params });
/** /**
* *
* @param params * @param params

View File

@ -1,4 +1,4 @@
<!-- 库区下拉选择--> <!-- 库区选择 -->
<template> <template>
<a-select <a-select
v-model:value="selectedValue" v-model:value="selectedValue"
@ -12,350 +12,407 @@
@change="handleChange" @change="handleChange"
@search="handleSearch" @search="handleSearch"
@focus="handleFocus" @focus="handleFocus"
@popupScroll="handlePopupScroll" @popup-scroll="handlePopupScroll"
:getPopupContainer="getParentContainer" :getPopupContainer="getParentContainer"
v-bind="attrs" v-bind="attrs"
> >
<template #notFoundContent> <template #notFoundContent>
<a-spin v-if="loading" size="small" />
<span v-else></span>
</template> </template>
<a-select-option v-for="option in areaOptions" :key="option.id" :value="getOptionValue(option)"> <a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)">
{{ getOptionLabel(option) }} {{ getOptionLabel(option) }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watch, computed, onMounted } from 'vue'; import { defineComponent, ref, watch, computed, onMounted } from 'vue';
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
import { setPopContainer } from '/@/utils'; import { queryById } from '@/views/base/area/Area.api';
import { debounce } from 'lodash-es'; import { setPopContainer } from '/@/utils';
import { debounce } from 'lodash-es';
// //
interface Area { interface Area {
id: string; id: string;
areaCode: string; areaCode: string;
areaName: string; areaName: string;
} }
// //
interface ResponseData { interface ResponseData {
records: Area[]; records: Area[];
total: number; total: number;
size: number; size: number;
current: number; current: number;
page: number; page: number;
} }
export default defineComponent({ export default defineComponent({
name: 'AreaSelect', name: 'AreaSelect',
inheritAttrs: false, inheritAttrs: false,
props: { props: {
// v-model // v-model
value: propTypes.oneOfType([propTypes.string, propTypes.array, propTypes.object]), value: propTypes.oneOfType([propTypes.string, propTypes.array, propTypes.object]),
// //
placeholder: propTypes.string.def('请选择库区'), placeholder: propTypes.string.def('请选择库区'),
// //
multiple: propTypes.bool.def(false), multiple: propTypes.bool.def(false),
// //
async: propTypes.bool.def(true), async: propTypes.bool.def(true),
// //
pageSize: propTypes.number.def(20), pageSize: propTypes.number.def(10),
// //
popContainer: propTypes.string, popContainer: propTypes.string,
// //
getPopupContainer: { getPopupContainer: {
type: Function, type: Function,
default: (node: HTMLElement) => node?.parentNode, default: (node: HTMLElement) => node?.parentNode,
},
// change
immediateChange: propTypes.bool.def(false),
// : 'id'() | 'object' |
returnValue: propTypes.string.def('id'),
//
izActive: propTypes.number.def(1),
}, },
emits: ['change', 'update:value', 'optionsLoaded'], // change
setup(props, { emit }) { immediateChange: propTypes.bool.def(false),
const areaOptions = ref<Area[]>([]); // : 'id'() | 'object' |
const loading = ref<boolean>(false); returnValue: propTypes.string.def('id'),
const allAreas = ref<Area[]>([]); //
const attrs = useAttrs({ excludeDefaultKeys: false }); izActive: propTypes.number.def(1),
//
delFlag:propTypes.number.def(0)
},
emits: ['change', 'update:value', 'optionsLoaded'],
setup(props, { emit }) {
const Options = ref<Area[]>([]);
const loading = ref<boolean>(false);
const allAreas = ref<Area[]>([]);
const attrs = useAttrs({ excludeDefaultKeys: false });
// //
const pageNo = ref(1); const pageNo = ref(1);
const isHasData = ref(true); const isHasData = ref(true);
const scrollLoading = ref(false); const scrollLoading = ref(false);
const searchKeyword = ref(''); const searchKeyword = ref('');
// //
const selectedValue = ref<string | string[] | undefined>(undefined); const selectedValue = ref<string | string[] | undefined>(undefined);
// //
const notFoundContent = computed(() => { const notFoundContent = computed(() => {
return loading.value ? undefined : null; return loading.value ? undefined : '暂无数据';
}); });
/** /**
* 获取选项显示文本 - 始终显示完整格式 * 获取选项显示文本 - 始终显示完整格式
*/ */
function getOptionLabel(option: Area) { function getOptionLabel(option: Area) {
return `${option.areaCode} - ${option.areaName}`; return `${option.areaCode} - ${option.areaName}`;
}
/**
* 获取选项值 - 根据returnValue确定实际存储的值
*/
function getOptionValue(option: Area) {
if (props.returnValue === 'object') {
return option.id; // object使idchange
} else if (props.returnValue === 'id') {
return option.id;
} else {
return option[props.returnValue as keyof Area] as string;
} }
}
/** /**
* 获取选项值 - 根据returnValue确定实际存储的值 * 获取弹出层容器
*/ */
function getOptionValue(option: Area) { function getParentContainer(node: HTMLElement) {
if (props.returnValue === 'object') { if (props.popContainer) {
return option.id; // object使idchange return setPopContainer(node, props.popContainer);
} else if (props.returnValue === 'id') { } else {
return option.id; if (typeof props.getPopupContainer === 'function') {
return props.getPopupContainer(node);
} else { } else {
return option[props.returnValue as keyof Area] as string; return node?.parentNode;
} }
} }
}
/** /**
* 获取弹出层容器 * 过滤选项 - 禁用前端过滤使用后端搜索
*/ */
function getParentContainer(node: HTMLElement) { function filterOption(_input: string, _option: any) {
if (props.popContainer) { return true; //
return setPopContainer(node, props.popContainer); }
/**
* 确保库区唯一性的辅助函数
*/
function ensureUnique(Areas: Area[], newArea: Area): Area[] {
// id
const filtered = Areas.filter(Area => Area.id !== newArea.id);
return [newArea, ...filtered];
}
/**
* 获取库区数据
*/
const queryData = async (page = 1, keyword = '', isSearch = false) => {
try {
loading.value = true;
const res = await defHttp.get<ResponseData>({
url: '/base/Area/list',
params: {
pageSize: props.pageSize,
pageNo: page,
keyword: keyword,
izActive: props.izActive,
delFlag: props.delFlag
},
});
const records = res.records || [];
if (page === 1 || isSearch) {
//
allAreas.value = records;
Options.value = records;
} else { } else {
if (typeof props.getPopupContainer === 'function') { //
return props.getPopupContainer(node); const newRecords = records.filter(record =>
} else { !allAreas.value.some(Area => Area.id === record.id)
return node?.parentNode; );
allAreas.value = [...allAreas.value, ...newRecords];
Options.value = [...Options.value, ...newRecords];
}
//
isHasData.value = records.length >= props.pageSize;
emit('optionsLoaded', allAreas.value);
} catch (error) {
if (page === 1) {
allAreas.value = [];
Options.value = [];
}
} finally {
loading.value = false;
scrollLoading.value = false;
}
};
async function queryDataById(value: string) {
try {
const res = await queryById({ id: value });
if (res) {
//
const Area = Array.isArray(res) ? res[0] : res;
if (Area) {
// 使ensureUnique
allAreas.value = ensureUnique(allAreas.value, Area);
Options.value = ensureUnique(Options.value, Area);
} }
return Area;
}
} catch (error) {
console.error('查询库区失败:', error);
}
return null;
}
/**
* 根据选项值找到对应的选项对象
*/
function findOptionByValue(value: string): Area | undefined {
if (props.returnValue === 'object' || props.returnValue === 'id') {
return allAreas.value.find((Area) => Area.id === value);
} else {
return allAreas.value.find((Area) => Area[props.returnValue as keyof Area] === value);
}
}
/**
* 获取需要返回的值
*/
function getReturnValue(value: string | string[]) {
if (!value) {
return props.multiple ? [] : undefined;
}
//
if (props.returnValue === 'object') {
if (Array.isArray(value)) {
return value.map((v) => findOptionByValue(v)).filter(Boolean);
} else {
return findOptionByValue(value);
} }
} }
// ID
/** else if (props.returnValue === 'id') {
* 过滤选项 - 禁用前端过滤使用后端搜索 return value;
*/
function filterOption(_input: string, _option: any) {
return true; //
} }
//
/** else {
* 获取库区数据 if (Array.isArray(value)) {
*/ return value.map((v) => {
const fetchAreas = async (page = 1, keyword = '', isSearch = false) => { const option = findOptionByValue(v);
try { return option ? option[props.returnValue as keyof Area] : v;
loading.value = true;
const res = await defHttp.get<ResponseData>({
url: '/base/area/list',
params: {
pageSize: props.pageSize,
pageNo: page,
keyword: keyword,
izActive: props.izActive,
},
}); });
console.log('获取库区数据成功:', res);
const records = res.records || [];
if (page === 1 || isSearch) {
//
allAreas.value = records;
areaOptions.value = records;
} else {
//
allAreas.value = [...allAreas.value, ...records];
areaOptions.value = [...areaOptions.value, ...records];
}
//
isHasData.value = records.length >= props.pageSize;
console.log('是否还有更多数据:', records.length);
emit('optionsLoaded', allAreas.value);
} catch (error) {
console.error('获取库区数据失败:', error);
if (page === 1) {
allAreas.value = [];
areaOptions.value = [];
}
} finally {
loading.value = false;
scrollLoading.value = false;
}
};
/**
* 根据选项值找到对应的选项对象
*/
function findOptionByValue(value: string): Area | undefined {
if (props.returnValue === 'object' || props.returnValue === 'id') {
return allAreas.value.find((item) => item.id === value);
} else { } else {
return allAreas.value.find((item) => item[props.returnValue as keyof Area] === value); const option = findOptionByValue(value);
return option ? option[props.returnValue as keyof Area] : value;
} }
} }
}
/** /**
* 获取需要返回的值 * 搜索处理防抖
*/ */
function getReturnValue(value: string | string[]) { const handleSearch = debounce(function (value: string) {
if (!value) { searchKeyword.value = value;
return props.multiple ? [] : undefined; pageNo.value = 1;
} isHasData.value = true;
// // API
if (props.returnValue === 'object') { queryData(1, value, true);
if (Array.isArray(value)) { }, 300);
return value.map((v) => findOptionByValue(v)).filter(Boolean);
} else {
return findOptionByValue(value);
}
}
// ID
else if (props.returnValue === 'id') {
return value;
}
//
else {
if (Array.isArray(value)) {
return value.map((v) => {
const option = findOptionByValue(v);
return option ? option[props.returnValue as keyof Area] : v;
});
} else {
const option = findOptionByValue(value);
return option ? option[props.returnValue as keyof Area] : value;
}
}
}
/** /**
* 搜索处理防抖 * 处理焦点事件
*/ */
const handleSearch = debounce(function (value: string) { function handleFocus() {
searchKeyword.value = value; //
if (allAreas.value.length === 0 && props.async) {
pageNo.value = 1; pageNo.value = 1;
isHasData.value = true; isHasData.value = true;
queryData(1, '');
}
attrs.onFocus?.();
}
// API /**
fetchAreas(1, value, true); * 处理值变化
}, 300); */
function handleChange(value: string | string[]) {
selectedValue.value = value;
/** //
* 处理焦点事件 const returnValue = getReturnValue(value);
*/ emit('update:value', returnValue);
function handleFocus() { emit('change', returnValue);
// }
if (allAreas.value.length === 0 && props.async) {
pageNo.value = 1; /**
isHasData.value = true; * 滚动加载处理
fetchAreas(1, ''); */
} function handlePopupScroll(e: Event) {
attrs.onFocus?.(); const target = e.target as HTMLElement;
const { scrollTop, scrollHeight, clientHeight } = target;
if (!scrollLoading.value && isHasData.value && scrollTop + clientHeight >= scrollHeight - 10) {
scrollLoading.value = true;
pageNo.value++;
queryData(pageNo.value, searchKeyword.value)
.finally(() => {
scrollLoading.value = false;
})
.catch(() => {
pageNo.value--;
});
}
}
/**
* 根据选中值初始化显示文本
*/
const initSelectValue = async () => {
if (!props.value) {
selectedValue.value = props.multiple ? [] : undefined;
return;
} }
/** //
* 处理值变化 if (props.async && allAreas.value.length === 0) {
*/ await queryData();
function handleChange(value: string | string[]) {
selectedValue.value = value;
//
const returnValue = getReturnValue(value);
console.log('值变化:', returnValue);
emit('update:value', returnValue);
emit('change', returnValue);
} }
/** // returnValue
* 滚动加载处理 let valueIds: string[] = [];
*/
function handlePopupScroll(e: Event) {
const target = e.target as HTMLElement;
const { scrollTop, scrollHeight, clientHeight } = target;
if (!scrollLoading.value && isHasData.value && scrollTop + clientHeight >= scrollHeight - 10) { if (props.returnValue === 'object') {
console.log('滚动加载更多'); // value
scrollLoading.value = true; if (Array.isArray(props.value)) {
pageNo.value++; valueIds = props.value.map((Area: any) => Area.id);
selectedValue.value = valueIds;
fetchAreas(pageNo.value, searchKeyword.value)
.finally(() => {
scrollLoading.value = false;
})
.catch(() => {
pageNo.value--;
});
}
}
/**
* 根据选中值初始化显示文本
*/
const initSelectValue = async () => {
if (!props.value) {
selectedValue.value = props.multiple ? [] : undefined;
return;
}
//
if (props.async && allAreas.value.length === 0) {
await fetchAreas();
}
// returnValue
if (props.returnValue === 'object') {
// value
if (Array.isArray(props.value)) {
selectedValue.value = props.value.map((item: any) => item.id);
} else {
selectedValue.value = (props.value as any).id;
}
} else if (props.returnValue === 'id') {
selectedValue.value = props.value as string | string[];
} else { } else {
// 使 valueIds = [(props.value as any).id];
selectedValue.value = props.value as string | string[]; selectedValue.value = valueIds[0];
} }
}; } else if (props.returnValue === 'id') {
if (Array.isArray(props.value)) {
// value valueIds = props.value as string[];
watch( selectedValue.value = valueIds;
() => props.value, } else {
() => { valueIds = [props.value as string];
initSelectValue(); selectedValue.value = valueIds[0];
},
{ immediate: true }
);
//
onMounted(() => {
if (!props.async) {
fetchAreas();
} }
}); } else {
// 使
selectedValue.value = props.value as string | string[];
// ID
if (Array.isArray(props.value)) {
valueIds = props.value.map((v) => {
const option = allAreas.value.find((Area) => Area[props.returnValue as keyof Area] === v);
return option ? option.id : v;
});
} else {
const option = allAreas.value.find((Area) => Area[props.returnValue as keyof Area] === props.value);
valueIds = option ? [option.id] : [props.value as string];
}
}
return { //
attrs, const missingIds = valueIds.filter((id) => !allAreas.value.some((Area) => Area.id === id));
areaOptions, if (missingIds.length > 0) {
loading, // ID
selectedValue, const queryPromises = missingIds.map((id) => queryDataById(id));
notFoundContent, await Promise.all(queryPromises);
getParentContainer, }
filterOption, };
handleChange,
handleSearch, // value
handleFocus, watch(
getOptionLabel, () => props.value,
getOptionValue, () => {
handlePopupScroll, initSelectValue();
}; },
}, { immediate: true }
}); );
//
onMounted(() => {
if (!props.async) {
queryData();
}
});
return {
attrs,
Options,
loading,
selectedValue,
notFoundContent,
getParentContainer,
filterOption,
handleChange,
handleSearch,
handleFocus,
getOptionLabel,
getOptionValue,
handlePopupScroll,
};
},
});
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -17,8 +17,7 @@
v-bind="attrs" v-bind="attrs"
> >
<template #notFoundContent> <template #notFoundContent>
<a-spin v-if="loading" size="small" />
<span v-else></span>
</template> </template>
<a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)"> <a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)">
{{ getOptionLabel(option) }} {{ getOptionLabel(option) }}
@ -78,6 +77,8 @@
returnValue: propTypes.string.def('id'), returnValue: propTypes.string.def('id'),
// //
izActive: propTypes.number.def(1), izActive: propTypes.number.def(1),
//
delFlag: propTypes.number.def(0),
}, },
emits: ['change', 'update:value', 'optionsLoaded'], emits: ['change', 'update:value', 'optionsLoaded'],
setup(props, { emit }) { setup(props, { emit }) {
@ -97,7 +98,7 @@
// //
const notFoundContent = computed(() => { const notFoundContent = computed(() => {
return loading.value ? undefined : null; return loading.value ? undefined : '暂无数据';
}); });
/** /**
@ -165,6 +166,7 @@
pageNo: page, pageNo: page,
keyword: keyword, keyword: keyword,
izActive: props.izActive, izActive: props.izActive,
delFlag: props.delFlag,
}, },
}); });

View File

@ -16,10 +16,7 @@
:getPopupContainer="getParentContainer" :getPopupContainer="getParentContainer"
v-bind="attrs" v-bind="attrs"
> >
<template #notFoundContent> <template #notFoundContent></template>
<a-spin v-if="loading" size="small" />
<span v-else></span>
</template>
<a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)"> <a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)">
{{ getOptionLabel(option) }} {{ getOptionLabel(option) }}
</a-select-option> </a-select-option>
@ -76,8 +73,14 @@
immediateChange: propTypes.bool.def(false), immediateChange: propTypes.bool.def(false),
// : 'id'() | 'object' | // : 'id'() | 'object' |
returnValue: propTypes.string.def('id'), returnValue: propTypes.string.def('id'),
//
status: propTypes.number.def(0),
// //
izActive: propTypes.number.def(1), izActive: propTypes.number.def(1),
//
delFlag: propTypes.number.def(0),
//ID
areaCode: propTypes.array,
}, },
emits: ['change', 'update:value', 'optionsLoaded'], emits: ['change', 'update:value', 'optionsLoaded'],
setup(props, { emit }) { setup(props, { emit }) {
@ -97,7 +100,7 @@
// //
const notFoundContent = computed(() => { const notFoundContent = computed(() => {
return loading.value ? undefined : null; return loading.value ? undefined : '暂无数据';
}); });
/** /**
@ -147,7 +150,7 @@
*/ */
function ensureUnique(Points: Point[], newPoint: Point): Point[] { function ensureUnique(Points: Point[], newPoint: Point): Point[] {
// id // id
const filtered = Points.filter(Point => Point.id !== newPoint.id); const filtered = Points.filter((Point) => Point.id !== newPoint.id);
return [newPoint, ...filtered]; return [newPoint, ...filtered];
} }
@ -164,7 +167,10 @@
pageSize: props.pageSize, pageSize: props.pageSize,
pageNo: page, pageNo: page,
keyword: keyword, keyword: keyword,
status: props.status,
izActive: props.izActive, izActive: props.izActive,
delFlag: props.delFlag,
areaCode: props.areaCode,
}, },
}); });
@ -176,9 +182,7 @@
Options.value = records; Options.value = records;
} else { } else {
// //
const newRecords = records.filter(record => const newRecords = records.filter((record) => !allPoints.value.some((Point) => Point.id === record.id));
!allPoints.value.some(Point => Point.id === record.id)
);
allPoints.value = [...allPoints.value, ...newRecords]; allPoints.value = [...allPoints.value, ...newRecords];
Options.value = [...Options.value, ...newRecords]; Options.value = [...Options.value, ...newRecords];
} }

View File

@ -21,11 +21,6 @@ export const columns: BasicColumn[] = [
return render.renderTag(text, color); return render.renderTag(text, color);
}, },
}, },
{
title: '库位',
align: "center",
dataIndex: 'pointId_dictText'
},
{ {
title: '是否启用', title: '是否启用',
align: 'center', align: 'center',

View File

@ -10,12 +10,6 @@
<JInput v-model:value="queryParam.stockCode" :placeholder="'请输入容器编码'" :type="JInputTypeEnum.JINPUT_RIGHT_LIKE" /> <JInput v-model:value="queryParam.stockCode" :placeholder="'请输入容器编码'" :type="JInputTypeEnum.JINPUT_RIGHT_LIKE" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6">
<a-form-item name="pointId">
<template #label><span title="库位">库位</span></template>
<PointSelect v-model:value="queryParam.pointId" placeholder="请选择库位" />
</a-form-item>
</a-col>
<a-col :lg="6"> <a-col :lg="6">
<a-form-item name="status"> <a-form-item name="status">
<template #label><span title="状态">状态</span></template> <template #label><span title="状态">状态</span></template>
@ -88,21 +82,16 @@
import { list, deleteOne, batchDelete, saveOrUpdate, getImportUrl, getExportUrl } from './Stock.api'; import { list, deleteOne, batchDelete, saveOrUpdate, getImportUrl, getExportUrl } from './Stock.api';
import StockModal from './components/StockModal.vue'; import StockModal from './components/StockModal.vue';
import SwitchStatus from '/@/views/base/SwitchStatus.vue'; import SwitchStatus from '/@/views/base/SwitchStatus.vue';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
import { getDateByPicker } from '/@/utils'; import { getDateByPicker } from '/@/utils';
import { JDictSelectTag, JSearchSelect } from '@/components/Form'; import { JDictSelectTag, JSearchSelect } from '@/components/Form';
import { JInputTypeEnum } from '@/enums/cpteEnum'; import { JInputTypeEnum } from '@/enums/cpteEnum';
import JInput from '../../../components/Form/src/jeecg/components/JInput.vue'; import JInput from '../../../components/Form/src/jeecg/components/JInput.vue';
import PointSelect from '@/views/base/point/components/PointSelect.vue';
const fieldPickers = reactive({}); const fieldPickers = reactive({});
const formRef = ref(); const formRef = ref();
const queryParam = reactive<any>({}); const queryParam = reactive<any>({});
const toggleSearchStatus = ref<boolean>(false); const toggleSearchStatus = ref<boolean>(false);
const registerModal = ref(); const registerModal = ref();
const userStore = useUserStore();
const { createMessage } = useMessage();
// //
const enhancedColumns = columns.map((col) => { const enhancedColumns = columns.map((col) => {
@ -130,7 +119,7 @@
}); });
//table //table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({ const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: { tableProps: {
title: '容器', title: '容器',
api: list, api: list,
@ -164,7 +153,7 @@
success: handleSuccess, success: handleSuccess,
}, },
}); });
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = const [registerTable, { reload }, { rowSelection, selectedRowKeys }] =
tableContext; tableContext;
const labelCol = reactive({ const labelCol = reactive({
xs: 24, xs: 24,

View File

@ -25,11 +25,6 @@
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24">
<a-form-item label="库位" v-bind="validateInfos.pointId" id="StockForm-pointId" name="pointId">
<PointSelect v-model:value="formData.pointId" />
</a-form-item>
</a-col>
<a-col :span="24"> <a-col :span="24">
<a-form-item label="描述" v-bind="validateInfos.description" id="StockForm-description" name="description"> <a-form-item label="描述" v-bind="validateInfos.description" id="StockForm-description" name="description">
<a-textarea v-model:value="formData.description" :rows="4" placeholder="请输入描述" /> <a-textarea v-model:value="formData.description" :rows="4" placeholder="请输入描述" />
@ -57,7 +52,6 @@
import JSwitch from '@/components/Form/src/jeecg/components/JSwitch.vue'; import JSwitch from '@/components/Form/src/jeecg/components/JSwitch.vue';
import { JDictSelectTag } from '@/components/Form'; import { JDictSelectTag } from '@/components/Form';
import { getTenantId } from '@/utils/auth'; import { getTenantId } from '@/utils/auth';
import PointSelect from '@/views/base/point/components/PointSelect.vue';
const props = defineProps({ const props = defineProps({
formDisabled: { type: Boolean, default: false }, formDisabled: { type: Boolean, default: false },
@ -71,7 +65,6 @@
let tenantId = getTenantId(); let tenantId = getTenantId();
const formData = reactive<Record<string, any>>({ const formData = reactive<Record<string, any>>({
id: '', id: '',
pointId: '',
stockCode: '', stockCode: '',
stockType: 'TRAY', stockType: 'TRAY',
status: 0, status: 0,

View File

@ -17,8 +17,7 @@
v-bind="attrs" v-bind="attrs"
> >
<template #notFoundContent> <template #notFoundContent>
<a-spin v-if="loading" size="small" />
<span v-else></span>
</template> </template>
<a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)"> <a-select-option v-for="option in Options" :key="option.id" :value="getOptionValue(option)">
{{ getOptionLabel(option) }} {{ getOptionLabel(option) }}
@ -75,8 +74,14 @@
immediateChange: propTypes.bool.def(false), immediateChange: propTypes.bool.def(false),
// : 'id'() | 'object' | // : 'id'() | 'object' |
returnValue: propTypes.string.def('id'), returnValue: propTypes.string.def('id'),
//
status: propTypes.number.def(0),
// //
izActive: propTypes.number.def(1), izActive: propTypes.number.def(1),
//
delFlag: propTypes.number.def(0),
//
izScan: propTypes.bool.def(false),
}, },
emits: ['change', 'update:value', 'optionsLoaded'], emits: ['change', 'update:value', 'optionsLoaded'],
setup(props, { emit }) { setup(props, { emit }) {
@ -96,7 +101,7 @@
// //
const notFoundContent = computed(() => { const notFoundContent = computed(() => {
return loading.value ? undefined : null; return loading.value ? undefined : '暂无数据';
}); });
/** /**
@ -146,7 +151,7 @@
*/ */
function ensureUnique(Stocks: Stock[], newStock: Stock): Stock[] { function ensureUnique(Stocks: Stock[], newStock: Stock): Stock[] {
// id // id
const filtered = Stocks.filter(Stock => Stock.id !== newStock.id); const filtered = Stocks.filter((Stock) => Stock.id !== newStock.id);
return [newStock, ...filtered]; return [newStock, ...filtered];
} }
@ -163,11 +168,15 @@
pageSize: props.pageSize, pageSize: props.pageSize,
pageNo: page, pageNo: page,
keyword: keyword, keyword: keyword,
status: props.status,
izActive: props.izActive, izActive: props.izActive,
delFlag: props.delFlag,
izScan: props.izScan,
}, },
}); });
const records = res.records || []; const records = res.records || [];
console.log('res', records)
if (page === 1 || isSearch) { if (page === 1 || isSearch) {
// //
@ -175,9 +184,7 @@
Options.value = records; Options.value = records;
} else { } else {
// //
const newRecords = records.filter(record => const newRecords = records.filter((record) => !allStocks.value.some((Stock) => Stock.id === record.id));
!allStocks.value.some(Stock => Stock.id === record.id)
);
allStocks.value = [...allStocks.value, ...newRecords]; allStocks.value = [...allStocks.value, ...newRecords];
Options.value = [...Options.value, ...newRecords]; Options.value = [...Options.value, ...newRecords];
} }
@ -296,6 +303,7 @@
const returnValue = getReturnValue(value); const returnValue = getReturnValue(value);
emit('update:value', returnValue); emit('update:value', returnValue);
emit('change', returnValue); emit('change', returnValue);
console.log('值变化:', returnValue);
} }
/** /**

View File

@ -13,7 +13,7 @@
<a-col :lg="6"> <a-col :lg="6">
<a-form-item name="pointId"> <a-form-item name="pointId">
<template #label><span title="库位">库位</span></template> <template #label><span title="库位">库位</span></template>
<PointSelect v-model:value="queryParam.pointId" /> <PointSelect v-model:value="queryParam.pointId" :area-code="['CPCCQ', 'MJCCQ']" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6"> <a-col :lg="6">

View File

@ -7,6 +7,12 @@ export const columns: BasicColumn[] = [
dataIndex: 'logType_dictText', dataIndex: 'logType_dictText',
width: 100, width: 100,
}, },
{
title: '业务单号',
align: 'center',
dataIndex: 'businessNo',
width: 130,
},
{ {
title: '物料', title: '物料',
align: 'center', align: 'center',
@ -17,19 +23,19 @@ export const columns: BasicColumn[] = [
title: '原库位', title: '原库位',
align: 'center', align: 'center',
dataIndex: 'fromPointId_dictText', dataIndex: 'fromPointId_dictText',
width: 120, width: 100,
}, },
{ {
title: '目标库位', title: '目标库位',
align: 'center', align: 'center',
dataIndex: 'toPointId_dictText', dataIndex: 'toPointId_dictText',
width: 120, width: 100,
}, },
{ {
title: '容器', title: '容器',
align: 'center', align: 'center',
dataIndex: 'stockId_dictText', dataIndex: 'stockId_dictText',
width: 120, width: 100,
}, },
{ {
title: '变动前数量', title: '变动前数量',
@ -50,15 +56,36 @@ export const columns: BasicColumn[] = [
width: 100, width: 100,
}, },
{ {
title: '变动前分配数量', title: '分配数量',
align: 'center', align: 'center',
dataIndex: 'beforeAllocatedQty', dataIndex: 'beforeAllocatedQty',
width: 120, width: 100,
}, },
{ {
title: '变动后分配数量', title: '分配数量',
align: 'center',
dataIndex: 'allocatedQty',
width: 100,
customRender: ({ record }) => {
// 从记录中获取分配前和分配后的数量
const before = record.beforeAllocatedQty || 0;
const after = record.afterAllocatedQty || 0;
if(record.logType_dictText==='分配'){
if(before>0){
return after - before;
}
return before + after;
}else if(record.logType_dictText==='取消分配'){
return before - after;
}else {
return 0;
}
}
},
{
title: '分配后数量',
align: 'center', align: 'center',
dataIndex: 'afterAllocatedQty', dataIndex: 'afterAllocatedQty',
width: 120, width: 100,
} }
]; ];

View File

@ -6,12 +6,7 @@
<a-row> <a-row>
<a-col :span="24"> <a-col :span="24">
<a-form-item label="托盘" v-bind="validateInfos.stockCode" id="ScanTrayForm-stockCode" name="stockCode"> <a-form-item label="托盘" v-bind="validateInfos.stockCode" id="ScanTrayForm-stockCode" name="stockCode">
<JSearchSelect <StockSelect v-model:value="formData.stockCode" :return-value="'stockCode'" :status="1" :iz-scan="true" />
v-model:value="formData.stockCode"
placeholder="请选择托盘"
dict="base_stock where status=1 and iz_active=1 and del_flag=0,stock_code,stock_code"
allowClear
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24"> <a-col :span="24">
@ -38,6 +33,7 @@
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue'; import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import { JSearchSelect } from '@/components/Form'; import { JSearchSelect } from '@/components/Form';
import { scanTray } from '@/views/conveyorLine/ConveyorLine.api'; import { scanTray } from '@/views/conveyorLine/ConveyorLine.api';
import StockSelect from '@/views/base/stock/components/StockSelect.vue';
const props = defineProps({ const props = defineProps({
formDisabled: { type: Boolean, default: false }, formDisabled: { type: Boolean, default: false },

View File

@ -16,6 +16,7 @@ enum Api {
queryTaskByMainId = '/shipping/pick/queryTaskByMainId', queryTaskByMainId = '/shipping/pick/queryTaskByMainId',
allocatePick = '/shipping/pick/allocatePick', allocatePick = '/shipping/pick/allocatePick',
cancelAllocate = '/shipping/pick/cancelAllocate', cancelAllocate = '/shipping/pick/cancelAllocate',
pickTask = '/shipping/pick/pickTask',
} }
/** /**
@ -123,3 +124,13 @@ export const cancelAllocate = (ids,handleSuccess) => {
handleSuccess(); handleSuccess();
}); });
}; };
/**
*
* @param params
*/
export const pickTask = (ids,handleSuccess) => {
return defHttp.get({ url: Api.pickTask, params: { ids } }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};

View File

@ -37,9 +37,9 @@
</a-row> </a-row>
<a-row :gutter="24"> <a-row :gutter="24">
<a-col :lg="6"> <a-col :lg="6">
<a-form-item name="status"> <a-form-item name="status_MultiString">
<template #label><span title="状态">状态</span></template> <template #label><span title="状态">状态</span></template>
<JDictSelectTag v-model:value="queryParam.status" placeholder="请选择" dictCode="pick_status" mode="multiple" allowClear /> <JSelectMultiple v-model:value="queryParam.status_MultiString" dictCode="pick_status" />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
@ -53,22 +53,34 @@
<a-button <a-button
type="primary" type="primary"
:loading="allocate_loading" :loading="allocate_loading"
:disabled="selectedRowKeys.length === 0"
v-auth="'shipping:data_pick:allocatePick'" v-auth="'shipping:data_pick:allocatePick'"
@click="handleAllocatePick" @click="handleAllocatePick"
preIcon="ant-design:edit-outlined" preIcon="ant-design:edit-outlined"
> >
分配 批量分配
</a-button> </a-button>
<a-button <a-button
type="primary" type="primary"
danger danger
:loading="cancel_loading" :loading="cancel_loading"
:disabled="selectedRowKeys.length === 0"
v-auth="'shipping:data_pick:cancelAllocate'" v-auth="'shipping:data_pick:cancelAllocate'"
@click="handleCancelAllocate" @click="handleCancelAllocate"
preIcon="ant-design:edit-outlined" preIcon="ant-design:edit-outlined"
> >
取消分配 取消分配
</a-button> </a-button>
<a-button
type="primary"
:loading="pick_loading"
:disabled="selectedRowKeys.length === 0"
v-auth="'shipping:data_pick:pickTask'"
@click="handlePick"
preIcon="ant-design:edit-outlined"
>
批量拣货
</a-button>
<a-button type="primary" v-auth="'shipping:data_pick:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> </a-button> <a-button type="primary" v-auth="'shipping:data_pick:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> </a-button>
<j-upload-button type="primary" v-auth="'shipping:data_pick:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls" <j-upload-button type="primary" v-auth="'shipping:data_pick:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls"
>导入 >导入
@ -107,15 +119,17 @@
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
import PickModal from './components/PickModal.vue'; import PickModal from './components/PickModal.vue';
import { columns } from './Pick.data'; import { columns } from './Pick.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, allocatePick, cancelAllocate } from './Pick.api'; import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, allocatePick, cancelAllocate, pickTask } from './Pick.api';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { getDateByPicker } from '/@/utils'; import { getDateByPicker } from '/@/utils';
import { JInputTypeEnum } from '@/enums/cpteEnum'; import { JInputTypeEnum } from '@/enums/cpteEnum';
import { JInput, JDictSelectTag } from '@/components/Form'; import { JInput, JDictSelectTag } from '@/components/Form';
import JRangeDate from '@/components/Form/src/jeecg/components/JRangeDate.vue'; import JRangeDate from '@/components/Form/src/jeecg/components/JRangeDate.vue';
import JSelectMultiple from '/@/components/Form/src/jeecg/components/JSelectMultiple.vue';
const allocate_loading = ref(false); const allocate_loading = ref(false);
const cancel_loading = ref(false); const cancel_loading = ref(false);
const pick_loading = ref(false);
const fieldPickers = reactive({}); const fieldPickers = reactive({});
const formRef = ref(); const formRef = ref();
const queryParam = reactive<any>({}); const queryParam = reactive<any>({});
@ -184,10 +198,6 @@
* 分配事件 * 分配事件
*/ */
async function handleAllocatePick() { async function handleAllocatePick() {
if (selectedRowKeys.value.length === 0) {
return createMessage.error('请选择出库单');
}
// 124 true,false // 124 true,false
const validStatuses = [1, 2, 4]; const validStatuses = [1, 2, 4];
const allValidStatus = selectedRows.value.every((row: any) => validStatuses.includes(row.status)); const allValidStatus = selectedRows.value.every((row: any) => validStatuses.includes(row.status));
@ -214,10 +224,6 @@
* 取消分配 * 取消分配
*/ */
async function handleCancelAllocate() { async function handleCancelAllocate() {
if (selectedRowKeys.value.length === 0) {
return createMessage.error('请选择出库单');
}
// 23 true,false // 23 true,false
const validStatuses = [2, 3]; const validStatuses = [2, 3];
const allValidStatus = selectedRows.value.every((row: any) => validStatuses.includes(row.status)); const allValidStatus = selectedRows.value.every((row: any) => validStatuses.includes(row.status));
@ -240,6 +246,32 @@
} }
} }
/**
* 拣货事件
*/
async function handlePick() {
// 124 true,false
const validStatuses = [2, 3, 4];
const allValidStatus = selectedRows.value.every((row: any) => validStatuses.includes(row.status));
if (!allValidStatus) {
return createMessage.error('【部分分配、已分配、部分拣货】状态的出库单才允许拣货');
}
if (pick_loading.value) {
return;
}
//
pick_loading.value = true;
try {
await pickTask(selectedRowKeys.value, handleSuccess);
} catch (e) {
console.error('拣货失败:', e);
handleSuccess();
} finally {
//
pick_loading.value = false;
}
}
/** /**
* 编辑事件 * 编辑事件
*/ */