턴 대상의 원본 이름과 누락값 표시를 분리해 검색 정확도 개선
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
export type CommandOption = {
|
||||
value: string | number;
|
||||
label: string;
|
||||
/** 원본 이름. 없는 소속/부대/수도는 null이며 대체 문구는 화면에서 표시한다. */
|
||||
targetNames?: {
|
||||
name: string;
|
||||
nationName?: string | null;
|
||||
cityName?: string | null;
|
||||
troopName?: string | null;
|
||||
capitalName?: string | null;
|
||||
};
|
||||
color?: string;
|
||||
description?: string;
|
||||
availableNow?: boolean;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, watch, type CSSProperties } from 'vue';
|
||||
import { commandTargetDescription } from '../../utils/commandTargetDescription';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import { buildCommandTargetSearchIndex, matchesCommandTargetSearch } from '../../utils/commandTargetSearch';
|
||||
import { buildCommandOptionSearchIndex, matchesCommandTargetSearch } from '../../utils/commandTargetSearch';
|
||||
import MapViewer from './MapViewer.vue';
|
||||
import NationColorSelect from './NationColorSelect.vue';
|
||||
import { commandArgumentPresentation, resolveCommandArgumentMapTarget } from '../command/commandArgumentPresentation';
|
||||
@@ -82,9 +83,7 @@ const targetIndexes = computed(
|
||||
field.key,
|
||||
optionsFor(field).map((option) => ({
|
||||
option,
|
||||
index: [option.label, option.description ?? '']
|
||||
.filter(Boolean)
|
||||
.flatMap(buildCommandTargetSearchIndex),
|
||||
index: buildCommandOptionSearchIndex(option),
|
||||
})),
|
||||
])
|
||||
)
|
||||
@@ -578,7 +577,7 @@ watch(
|
||||
<div
|
||||
v-if="
|
||||
field.kind === 'select' &&
|
||||
(selectedOptionFor(field)?.description || selectedOptionFor(field)?.color)
|
||||
(commandTargetDescription(commandKey, selectedOptionFor(field)) || selectedOptionFor(field)?.color)
|
||||
"
|
||||
class="option-detail"
|
||||
:class="{ 'assignment-detail': commandKey === 'che_발령' && field.optionSource === 'generals' }"
|
||||
@@ -589,7 +588,7 @@ watch(
|
||||
:style="{ backgroundColor: selectedOptionFor(field)?.color }"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{{ selectedOptionFor(field)?.description }}</span>
|
||||
<span>{{ commandTargetDescription(commandKey, selectedOptionFor(field)) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="searchEnabled && isSearchable(field)"
|
||||
@@ -603,7 +602,7 @@ watch(
|
||||
type="search"
|
||||
inputmode="search"
|
||||
enterkeyhint="done"
|
||||
placeholder="이름·정보 또는 초성"
|
||||
placeholder="이름 또는 초성"
|
||||
autocomplete="off"
|
||||
:spellcheck="false"
|
||||
@keydown.enter.prevent="($event.target as HTMLInputElement).blur()"
|
||||
@@ -653,7 +652,7 @@ watch(
|
||||
<span class="target-state">{{
|
||||
option.availableNow === false ? '현재 불가' : option.availableNow ? '우선 대상' : '대상'
|
||||
}}</span>
|
||||
<small>{{ option.description }}</small>
|
||||
<small>{{ commandTargetDescription(commandKey, option) }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { CommandOption } from '../components/command/types';
|
||||
|
||||
export const commandTargetDescription = (commandKey: string, option?: CommandOption): string => {
|
||||
if (!option) return '';
|
||||
const description = option.description ?? '';
|
||||
// 이전 API는 완성된 description을 주므로 새 원본 필드가 없으면 그대로 표시한다.
|
||||
if (option.targetNames?.troopName === undefined || commandKey === 'che_포상' || commandKey === 'che_몰수') {
|
||||
return description;
|
||||
}
|
||||
const troopName = option.targetNames.troopName ?? (option.troopId ? `#${option.troopId}` : '없음');
|
||||
const leader = option.troopId && option.troopId === option.value ? ' (부대장)' : '';
|
||||
const troop = `탑승 부대 ${troopName}${leader}`;
|
||||
return commandKey === 'che_발령'
|
||||
? [troop, description].filter(Boolean).join('\n')
|
||||
: [description, troop].filter(Boolean).join(' · ');
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { CommandOption } from '../components/command/types';
|
||||
|
||||
// Ref convertSearch초성: 원문, 두벌식 초성, 한글 초성, 두 단계 IME 겹자음 인덱스.
|
||||
const initials = 'ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ';
|
||||
const keyboard = 'rRseEfaqQtTdwWczxvg';
|
||||
@@ -45,3 +47,9 @@ export const matchesCommandTargetSearch = (index: readonly string[], query: stri
|
||||
const normalized = normalize(query);
|
||||
return index.some((text) => text.includes(normalized));
|
||||
};
|
||||
|
||||
// 구 API 응답은 표시 이름까지만 검색한다. description은 결코 검색하지 않는다.
|
||||
export const buildCommandOptionSearchIndex = (option: Pick<CommandOption, 'label' | 'targetNames'>): string[] =>
|
||||
(option.targetNames ? Object.values(option.targetNames) : [option.label])
|
||||
.filter((name): name is string => typeof name === 'string' && Boolean(name.trim()))
|
||||
.flatMap(buildCommandTargetSearchIndex);
|
||||
|
||||
Reference in New Issue
Block a user