7 changed files with 372 additions and 291 deletions
@ -0,0 +1,163 @@ |
|||
<script lang="ts" setup> |
|||
import type { PropType } from 'vue' |
|||
import { computed, onMounted, ref, watch } from 'vue' |
|||
import { Transfer } from 'ant-design-vue' |
|||
import type { TransferItem } from 'ant-design-vue' |
|||
import { pointListApi } from '@/api/alert/model/select' |
|||
import { buildPointKeyFromInfo, buildPointTitle, parsePointKey } from './pointTransferHelper' |
|||
|
|||
interface PointTransferItem extends TransferItem { |
|||
key: string |
|||
title: string |
|||
} |
|||
|
|||
const props = defineProps({ |
|||
selectedKeys: { |
|||
type: Array as PropType<string[]>, |
|||
default: () => [], |
|||
}, |
|||
listStyle: { |
|||
type: Object as PropType<Record<string, string>>, |
|||
default: () => ({ |
|||
width: '500px', |
|||
height: '450px', |
|||
}), |
|||
}, |
|||
titles: { |
|||
type: Array as PropType<[string, string]>, |
|||
default: () => ['可选测点', '已选测点'], |
|||
}, |
|||
showSearch: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
prefillPoints: { |
|||
type: Array as PropType<Array<Partial<ReturnType<typeof parsePointKey>>>>, |
|||
default: () => [], |
|||
}, |
|||
}) |
|||
|
|||
const emit = defineEmits<{ |
|||
(e: 'update:selectedKeys', value: string[]): void |
|||
(e: 'change', value: ReturnType<typeof parsePointKey>[]): void |
|||
}>() |
|||
|
|||
const dataSource = ref<PointTransferItem[]>([]) |
|||
const targetKeys = ref<string[]>([...props.selectedKeys]) |
|||
|
|||
const dataMap = computed(() => new Map(dataSource.value.map(item => [item.key, item]))) |
|||
const prefilledItems = computed<PointTransferItem[]>(() => { |
|||
return (props.prefillPoints || []).map((info) => { |
|||
const key = buildPointKeyFromInfo(info) |
|||
const parsed = parsePointKey(key) |
|||
return { |
|||
key, |
|||
title: buildPointTitle(parsed, key), |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
function mergeUnique(items: PointTransferItem[]) { |
|||
const map = new Map<string, PointTransferItem>() |
|||
items.forEach((item) => { |
|||
if (item.key === undefined || item.key === null) |
|||
return |
|||
const key = String(item.key) |
|||
if (!map.has(key)) |
|||
map.set(key, { ...item, key }) |
|||
}) |
|||
dataSource.value = Array.from(map.values()) |
|||
} |
|||
|
|||
function ensureSelectedVisible(keys: string[]) { |
|||
mergeUnique(prefilledItems.value) |
|||
const missing: PointTransferItem[] = [] |
|||
keys.forEach((key) => { |
|||
const k = String(key) |
|||
if (!dataMap.value.has(k)) { |
|||
const info = parsePointKey(k) |
|||
missing.push({ |
|||
key: k, |
|||
title: buildPointTitle(info, k), |
|||
}) |
|||
} |
|||
}) |
|||
if (missing.length) |
|||
mergeUnique([...dataSource.value, ...missing]) |
|||
} |
|||
|
|||
async function fetchPoints(keyword = '') { |
|||
try { |
|||
const res = await pointListApi({ keyword }) |
|||
const fetched = Array.isArray(res) |
|||
? res.map((item: any) => { |
|||
const key = item?.id |
|||
const info = parsePointKey(key) |
|||
return { |
|||
key: String(key), |
|||
title: item?.name || buildPointTitle(info, String(key)), |
|||
} |
|||
}) |
|||
: [] |
|||
const selectedItems = targetKeys.value.map((key) => { |
|||
const saved = dataMap.value.get(key) |
|||
if (saved) |
|||
return saved |
|||
const info = parsePointKey(key) |
|||
return { key, title: buildPointTitle(info, key) } |
|||
}) |
|||
mergeUnique([...selectedItems, ...fetched]) |
|||
} |
|||
catch (error) { |
|||
console.error('获取测点列表失败', error) |
|||
} |
|||
} |
|||
|
|||
watch( |
|||
() => props.selectedKeys, |
|||
(val) => { |
|||
targetKeys.value = [...val] |
|||
ensureSelectedVisible(val) |
|||
}, |
|||
{ immediate: true }, |
|||
) |
|||
|
|||
function handleChange(nextTargetKeys: string[]) { |
|||
targetKeys.value = nextTargetKeys |
|||
emit('update:selectedKeys', nextTargetKeys) |
|||
emit('change', nextTargetKeys.map(key => parsePointKey(key))) |
|||
} |
|||
|
|||
function handleSearch(direction: 'left' | 'right', value: string) { |
|||
if (direction === 'left') |
|||
fetchPoints(value) |
|||
} |
|||
|
|||
function filterOption(inputValue: string, option?: PointTransferItem) { |
|||
if (!option?.title) |
|||
return false |
|||
return option.title.toLowerCase().includes((inputValue || '').toLowerCase()) |
|||
} |
|||
|
|||
function renderItem(item: PointTransferItem) { |
|||
return item.title || (item as any).label || item.key |
|||
} |
|||
|
|||
onMounted(() => { |
|||
fetchPoints() |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<Transfer |
|||
:target-keys="targetKeys" |
|||
:data-source="dataSource" |
|||
:titles="titles" |
|||
:list-style="listStyle" |
|||
:show-search="showSearch" |
|||
:filter-option="filterOption" |
|||
:render="renderItem" |
|||
@search="handleSearch" |
|||
@change="handleChange" |
|||
/> |
|||
</template> |
|||
@ -0,0 +1,26 @@ |
|||
export interface PointKeyInfo { |
|||
description: string |
|||
pointId: string |
|||
unit?: string |
|||
Lower?: string |
|||
Upper?: string |
|||
} |
|||
|
|||
export function parsePointKey(key: string | number | undefined | null): PointKeyInfo { |
|||
const safeKey = key !== undefined && key !== null ? String(key) : '' |
|||
const [description = '', pointId = '', unit = '', Lower = '', Upper = ''] = safeKey.split('|') |
|||
return { description, pointId, unit, Lower, Upper } |
|||
} |
|||
|
|||
export function buildPointKeyFromInfo(info: Partial<PointKeyInfo>): string { |
|||
const { description = '', pointId = '', unit = '', Lower = '', Upper = '' } = info || {} |
|||
return [description, pointId, unit, Lower, Upper].join('|') |
|||
} |
|||
|
|||
export function buildPointTitle(info: PointKeyInfo, fallback: string): string { |
|||
if (info.description && info.pointId) |
|||
return `${info.description} (${info.pointId})` |
|||
if (info.description) |
|||
return info.description |
|||
return fallback |
|||
} |
|||
Loading…
Reference in new issue