feat: highlight avatar names with unique colors in event panel
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
import { computed, ref, watch, nextTick } from 'vue'
|
||||
import { useWorldStore } from '../../stores/world'
|
||||
import { NSelect } from 'naive-ui'
|
||||
import { highlightAvatarNames, buildAvatarColorMap } from '../../utils/eventHelper'
|
||||
import type { GameEvent } from '../../types/core'
|
||||
|
||||
const worldStore = useWorldStore()
|
||||
const filterValue = ref('all')
|
||||
@@ -67,6 +69,15 @@ const emptyEventMessage = computed(() => (
|
||||
function formatEventDate(event: { year: number; month: number }) {
|
||||
return `${event.year}年${event.month}月`
|
||||
}
|
||||
|
||||
// 构建角色名 -> 颜色映射表。
|
||||
const avatarColorMap = computed(() => buildAvatarColorMap(worldStore.avatarList))
|
||||
|
||||
// 渲染事件内容,高亮角色名。
|
||||
function renderEventContent(event: GameEvent): string {
|
||||
const text = event.content || event.text || ''
|
||||
return highlightAvatarNames(text, avatarColorMap.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -84,7 +95,7 @@ function formatEventDate(event: { year: number; month: number }) {
|
||||
<div v-else class="event-list" ref="eventListRef">
|
||||
<div v-for="event in filteredEvents" :key="event.id" class="event-item">
|
||||
<div class="event-date">{{ formatEventDate(event) }}</div>
|
||||
<div class="event-content">{{ event.content || event.text }}</div>
|
||||
<div class="event-content" v-html="renderEventContent(event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -58,3 +58,60 @@ export function mergeAndSortEvents(existingEvents: GameEvent[], newEvents: GameE
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色 ID 哈希生成一致的 HSL 颜色。
|
||||
*/
|
||||
export function avatarIdToColor(id: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < id.length; i++) {
|
||||
hash = ((hash << 5) - hash) + id.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
const hue = Math.abs(hash) % 360;
|
||||
return `hsl(${hue}, 70%, 65%)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色列表构建 name -> color 映射表。
|
||||
*/
|
||||
export function buildAvatarColorMap(
|
||||
avatars: Array<{ id: string; name?: string }>
|
||||
): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
for (const av of avatars) {
|
||||
if (av.name) {
|
||||
map.set(av.name, avatarIdToColor(av.id));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
const HTML_ESCAPE_MAP: Record<string, string> = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
/**
|
||||
* 高亮文本中的角色名,返回 HTML 字符串。
|
||||
*/
|
||||
export function highlightAvatarNames(
|
||||
text: string,
|
||||
colorMap: Map<string, string>
|
||||
): string {
|
||||
if (!text || colorMap.size === 0) return text;
|
||||
|
||||
// 按名字长度倒序排列,避免部分匹配(如 "张三" 匹配到 "张三丰")。
|
||||
const names = [...colorMap.keys()].sort((a, b) => b.length - a.length);
|
||||
|
||||
let result = text;
|
||||
for (const name of names) {
|
||||
const color = colorMap.get(name)!;
|
||||
const escaped = name.replace(/[&<>"']/g, c => HTML_ESCAPE_MAP[c] || c);
|
||||
result = result.replaceAll(name, `<span style="color:${color}">${escaped}</span>`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user