|
@@ -798,6 +798,7 @@ type ExpertItem = {
|
|
|
mobile?: string
|
|
mobile?: string
|
|
|
softwareSkills: number[]
|
|
softwareSkills: number[]
|
|
|
teams?: Array<{ id: number; teamName?: string }>
|
|
teams?: Array<{ id: number; teamName?: string }>
|
|
|
|
|
+ sort?: number // 同梯队同序列内的排序值
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
type ExpertGroup = {
|
|
type ExpertGroup = {
|
|
@@ -813,7 +814,6 @@ const selectedTeam = ref('')
|
|
|
const wordCloudVisible = ref(false)
|
|
const wordCloudVisible = ref(false)
|
|
|
const drawerVisible = ref(false)
|
|
const drawerVisible = ref(false)
|
|
|
const activeExpert = ref<ExpertProfile | null>(null)
|
|
const activeExpert = ref<ExpertProfile | null>(null)
|
|
|
-const expertOrderMap = ref<Record<string, number[]>>({})
|
|
|
|
|
|
|
|
|
|
const resetQueryParams = () => ({
|
|
const resetQueryParams = () => ({
|
|
|
deptId: undefined,
|
|
deptId: undefined,
|
|
@@ -998,18 +998,19 @@ const tableData = computed<TableRow[]>(() => {
|
|
|
)
|
|
)
|
|
|
.join(' ')
|
|
.join(' ')
|
|
|
.toLowerCase()
|
|
.toLowerCase()
|
|
|
|
|
+
|
|
|
const cells: CellItem[] = rankOptions.value.map((option) => {
|
|
const cells: CellItem[] = rankOptions.value.map((option) => {
|
|
|
|
|
+ // 过滤出属于当前序列的专家
|
|
|
const members = group.experts.filter((expert) =>
|
|
const members = group.experts.filter((expert) =>
|
|
|
(expert.expertRank || []).some((rank) => String(rank) === String(option.value))
|
|
(expert.expertRank || []).some((rank) => String(rank) === String(option.value))
|
|
|
)
|
|
)
|
|
|
- const order = expertOrderMap.value[`${group.echelonLabel}-${option.value}`]
|
|
|
|
|
- const orderIndex = new Map((order || []).map((id, order) => [id, order]))
|
|
|
|
|
|
|
|
|
|
- members.sort(
|
|
|
|
|
- (left, right) =>
|
|
|
|
|
- (orderIndex.get(left.id) ?? Number.MAX_SAFE_INTEGER) -
|
|
|
|
|
- (orderIndex.get(right.id) ?? Number.MAX_SAFE_INTEGER)
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ // 同一梯队同一序列内,按 sort 字段升序排序
|
|
|
|
|
+ members.sort((left, right) => {
|
|
|
|
|
+ const leftSort = left.sort ?? Number.MAX_SAFE_INTEGER
|
|
|
|
|
+ const rightSort = right.sort ?? Number.MAX_SAFE_INTEGER
|
|
|
|
|
+ return leftSort - rightSort
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
return { type: 'members', members }
|
|
return { type: 'members', members }
|
|
|
})
|
|
})
|
|
@@ -1029,7 +1030,7 @@ const tableData = computed<TableRow[]>(() => {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 拖拽结束处理:前端完成视觉交换后,调用后端接口持久化顺序
|
|
|
|
|
|
|
+ * 拖拽结束处理:前端完成视觉交换后,调用后端接口持久化排序
|
|
|
*/
|
|
*/
|
|
|
const handleExpertDragEnd = async (
|
|
const handleExpertDragEnd = async (
|
|
|
event: { oldIndex?: number; newIndex?: number },
|
|
event: { oldIndex?: number; newIndex?: number },
|
|
@@ -1037,11 +1038,10 @@ const handleExpertDragEnd = async (
|
|
|
cellIndex: number,
|
|
cellIndex: number,
|
|
|
members: ExpertItem[]
|
|
members: ExpertItem[]
|
|
|
) => {
|
|
) => {
|
|
|
- const oldIndex = event.oldIndex
|
|
|
|
|
- const newIndex = event.newIndex
|
|
|
|
|
|
|
+ const { oldIndex, newIndex } = event
|
|
|
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return
|
|
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return
|
|
|
|
|
|
|
|
- // 1. 还原 SortableJS 已经先完成的“插入式移动”,改为仅交换两个专家
|
|
|
|
|
|
|
+ // 还原成仅交换两个专家(vuedraggable 默认是插入式移动,会打乱其他人的下标)
|
|
|
const originalMembers = [...members]
|
|
const originalMembers = [...members]
|
|
|
const [draggedMember] = originalMembers.splice(newIndex, 1)
|
|
const [draggedMember] = originalMembers.splice(newIndex, 1)
|
|
|
originalMembers.splice(oldIndex, 0, draggedMember)
|
|
originalMembers.splice(oldIndex, 0, draggedMember)
|
|
@@ -1050,36 +1050,25 @@ const handleExpertDragEnd = async (
|
|
|
originalMembers[oldIndex]
|
|
originalMembers[oldIndex]
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
- // 2. 更新本地 members 数组,保证 UI 立即反映交换结果
|
|
|
|
|
- members.splice(0, members.length, ...originalMembers)
|
|
|
|
|
-
|
|
|
|
|
- // 3. 记录当前单元格的顺序映射(用于后续排序参考)
|
|
|
|
|
- const rankValue = rankOptions.value[cellIndex]?.value
|
|
|
|
|
- const orderKey = `${row.teamName}-${rankValue}`
|
|
|
|
|
- expertOrderMap.value[orderKey] = originalMembers.map((member) => member.id)
|
|
|
|
|
-
|
|
|
|
|
- // 4. 调用后端接口,持久化排序结果
|
|
|
|
|
const sourceMember = originalMembers[oldIndex]
|
|
const sourceMember = originalMembers[oldIndex]
|
|
|
const targetMember = originalMembers[newIndex]
|
|
const targetMember = originalMembers[newIndex]
|
|
|
-
|
|
|
|
|
if (!sourceMember || !targetMember) return
|
|
if (!sourceMember || !targetMember) return
|
|
|
|
|
|
|
|
|
|
+ const rankValue = rankOptions.value[cellIndex]?.value
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- // 调用交换接口,传递两个专家的 ID 以及所在梯队、序列信息
|
|
|
|
|
- // 注意:请根据实际后端 API 的参数定义调整以下字段
|
|
|
|
|
await exchangeExpert({
|
|
await exchangeExpert({
|
|
|
sourceId: sourceMember.id,
|
|
sourceId: sourceMember.id,
|
|
|
targetId: targetMember.id,
|
|
targetId: targetMember.id,
|
|
|
- // 可选:传递上下文信息,便于后端定位排序分组
|
|
|
|
|
echelon: row.teamName,
|
|
echelon: row.teamName,
|
|
|
expertRank: rankValue
|
|
expertRank: rankValue
|
|
|
})
|
|
})
|
|
|
-
|
|
|
|
|
ElMessage.success('排序已保存')
|
|
ElMessage.success('排序已保存')
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('排序保存失败:', error)
|
|
console.error('排序保存失败:', error)
|
|
|
ElMessage.error('排序保存失败,请重试')
|
|
ElMessage.error('排序保存失败,请重试')
|
|
|
- // 5. 保存失败时回滚前端顺序(重新拉取列表数据)
|
|
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 无论成功失败都重新拉取,确保前端展示与后端 sort 一致
|
|
|
await getList()
|
|
await getList()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|