属性排序需求
需求背景如下:
- 需要实现属性的拖拽排序;
- 支持同级子项排序,但禁止跨容器拖拽。
例如:A 容器内的项不能拖到 B 容器,只能在 A 容器内部调整顺序(B 容器同理)。
示意图:

说明:规格配置 不能拖拽到 行业属性 容器,行业属性 也不能拖拽到 规格配置 容器。
实现思路
将拖拽排序封装为独立组件 AttrDragItem,由父组件传入数据并接收更新结果。组件同时支持:
- 扁平列表排序;
- 树状结构(带子集)排序。
vue
<!-- 父组件 -->
<script setup lang="ts">
import { ref } from "vue";
import attrDragItem from "./attrDragItem.vue";
const test = ref(
Array.from({ length: 7 }, (_, i) => ({
id: i + 1,
name: `C00${i + 1}${
["白色", "黑色", "红色", "绿色", "蓝色", "黄色", "紫色"][i]
}`,
elements: [],
}))
);
const test2 = ref([
{
id: 1,
name: "电机型号",
elements: [
{
id: 1,
name: "M2/V2",
},
{
id: 2,
name: "米家",
},
],
},
]);
</script>
<template>
<h3>颜色配置</h3>
<attrDragItem
:data="test"
:is-tree="false"
@update:data="(newData) => (test = newData)"
/>
<h3>规格配置</h3>
<attrDragItem
:data="test2"
:is-tree="true"
@update:data="(newData) => (test2 = newData)"
/>
<!-- .... 其他属性配置 -->
</template>
attrDragItem 组件核心参数如下:
data:排序数据(数组);is-tree:是否为树状结构,默认false;@update:data:数据更新事件,返回最新排序结果。
vue
<!-- 子组件 attrDragItem.vue -->
<script setup lang="ts">
defineOptions({
name: "AttrDragItem",
});
import VabDraggable from "vuedraggable";
const props = defineProps<{
// 数据
data: TAny[];
// 是否是树结构
isTree: boolean;
}>();
const emit = defineEmits<{
"update:data": [value: any[]];
}>();
const dataList = computed({
get: () => props.data,
set: (value) => emit("update:data", value),
});
const dragOptions = computed(() => {
return {
animation: 600,
disabled: false,
ghostClass: "ghost",
};
});
// 处理子集元素更新(树结构)
const handleElementsUpdate = (item: TAny, newElements: TAny[]) => {
const index = dataList.value.findIndex((i) => i.id === item.id);
if (index !== -1) {
const newData = [...dataList.value];
newData[index] = { ...item, elements: newElements };
emit("update:data", newData);
}
};
</script>
<template>
<vab-draggable
v-model="dataList"
v-bind="dragOptions"
item-key="id"
style="display: flex; flex-wrap: wrap; justify-content: flex-start"
:style="{ flexDirection: props.isTree ? 'column' : 'row' }"
>
<template #item="{ element: item }">
<div>
<div :class="['drag-item-container', props.isTree ? 'mb' : '']">
<vab-icon v-if="props.isTree" icon="draggable" />
<div :class="[props.isTree ? 'tree-item' : 'drag-item']">
{{ item.name }}
</div>
</div>
<template v-if="props.isTree">
<!-- 递归调用子组件 -->
<attrDragItem
:data="item.elements"
:is-tree="false"
@update:data="
(newElements) => handleElementsUpdate(item, newElements)
"
/>
</template>
</div>
</template>
</vab-draggable>
</template>
<style lang="scss" scoped>
.drag-item-container {
display: flex;
align-items: center;
color: #333;
&.mb {
margin-bottom: 12px;
}
.drag-item {
padding: 6px 12px;
background-color: rgba(238, 238, 238, 1);
border-radius: 50px;
margin-bottom: 8px;
margin-right: 8px;
cursor: move;
}
}
</style>
实现要点
- 禁止跨容器拖拽
每个容器使用独立数据源与组件实例;如需更严格限制,可为不同容器设置不同group。 - 树结构递归渲染
当isTree = true时递归渲染子节点,并通过handleElementsUpdate回传子集变更。 - 排序结果持久化
推荐在拖拽结束事件(如@end)触发后端保存接口,避免拖拽过程中频繁请求。
相关文章
执行价格弹窗:表头 / 表体合并与可配置表格
业务需要在「执行价格」类弹窗中展示多块价格配置(基础、颜色、规格、行业等)。每块区域具备:
GridView 宫格加载渲染优化
系统首页 GridView 宫格模块接口耗时不高,但首次进入总耗时接近 22s。复盘耗时分层定位过程、代码层面的瓶颈(约 2000 行、100 个 tab 重复节点)、优化手段与最终指标对比。
新商家系统性能优化实践
商家系统新版本上线后,团队持续针对构建效率与用户体验做了一轮系统性优化。本文记录核心方案与落地结果,供后续版本复用。
老系统升级与兼容改造实践
公司原有业务系统采用 Node + jQuery 架构,长期运行后暴露出典型遗留系统问题:
键盘弹起导致底部被顶起问题(H5 适配)
在移动端 H5 页面中,当用户聚焦 input、textarea 等可编辑元素、软键盘弹出时,常见表现包括:
中英文输入法下搜索触发冲突复盘
在“边输入边查询”场景中,很多实现会直接在 input 事件中请求接口。 但在中文输入法(IME)场景下,如果不处理输入合成过程,容易出现“未完成输入就提前触发查询”的问题。
Series
team documents
8 / 22