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