性能优化和样式更新
This commit is contained in:
@@ -45,6 +45,8 @@ const selectedFolderId = computed({
|
|||||||
// 保存预设时是否同时标记为收藏
|
// 保存预设时是否同时标记为收藏
|
||||||
const saveAsFavorite = ref(false);
|
const saveAsFavorite = ref(false);
|
||||||
const viewMode = ref<'compact' | 'detail'>('compact');
|
const viewMode = ref<'compact' | 'detail'>('compact');
|
||||||
|
// 左侧光标所在的 token 序号,用于在右侧映射中定位高亮
|
||||||
|
const activeTokenIndex = ref<number | null>(null);
|
||||||
const showPresetDropdown = ref(false);
|
const showPresetDropdown = ref(false);
|
||||||
const showTranslationPopup = ref(false);
|
const showTranslationPopup = ref(false);
|
||||||
const translationTargetToken = ref<string | null>(null);
|
const translationTargetToken = ref<string | null>(null);
|
||||||
@@ -829,6 +831,7 @@ const unmappedTokens = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function isUnmapped(key: string): boolean {
|
function isUnmapped(key: string): boolean {
|
||||||
|
void store.mappingVersion; // 追踪映射变更以触发响应式刷新
|
||||||
const { core } = parseDetailedToken(key);
|
const { core } = parseDetailedToken(key);
|
||||||
const tag = store.getTagByKey(core);
|
const tag = store.getTagByKey(core);
|
||||||
return !tag || !tag.translation?.[selectedLang.value];
|
return !tag || !tag.translation?.[selectedLang.value];
|
||||||
@@ -842,6 +845,7 @@ function handleApplyTranslation(results: { key: string; trans: string }[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayTrans(key: string): string {
|
function displayTrans(key: string): string {
|
||||||
|
void store.mappingVersion; // 追踪映射变更以触发响应式刷新
|
||||||
const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(key);
|
const { core, weight, wrappers, prefix, suffix } = parseDetailedToken(key);
|
||||||
const tag = store.getTagByKey(core);
|
const tag = store.getTagByKey(core);
|
||||||
const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? core;
|
const translatedCore = tag?.translation?.[selectedLang.value] ?? tag?.key ?? core;
|
||||||
@@ -870,11 +874,13 @@ function isRemoveDisabled(token: string): boolean {
|
|||||||
:get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
|
:get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
|
||||||
@update-suggestions="updateSuggestionsFromText" @copy="copyLeft" @replace-cn-comma="replaceCnComma"
|
@update-suggestions="updateSuggestionsFromText" @copy="copyLeft" @replace-cn-comma="replaceCnComma"
|
||||||
@format-prompt="formatPrompt" @unify-priority="unifyPriorityStyle" @toggle-underscore="toggleUnderscoreSpace"
|
@format-prompt="formatPrompt" @unify-priority="unifyPriorityStyle" @toggle-underscore="toggleUnderscoreSpace"
|
||||||
@add-tag="handleAddTag" @drag-tag-start="handleQuickAddDragStart" @drag-tag-end="handleQuickAddDragEnd" />
|
@add-tag="handleAddTag" @drag-tag-start="handleQuickAddDragStart" @drag-tag-end="handleQuickAddDragEnd"
|
||||||
|
@locate-token="(i) => activeTokenIndex = i >= 0 ? i : null" />
|
||||||
|
|
||||||
<TokenMappingPanel ref="tokenMappingRef" :tokens="tokens" :selected-lang="selectedLang"
|
<TokenMappingPanel ref="tokenMappingRef" :tokens="tokens" :selected-lang="selectedLang"
|
||||||
v-model:view-mode="viewMode" :dragging-index="draggingIndex" :over-index="overIndex" :insert-side="insertSide"
|
v-model:view-mode="viewMode" :dragging-index="draggingIndex" :over-index="overIndex" :insert-side="insertSide"
|
||||||
:is-dragging="isDragging" :edit-suggestions="editSuggestions" :numeric-mode="numericMode"
|
:is-dragging="isDragging" :edit-suggestions="editSuggestions" :numeric-mode="numericMode"
|
||||||
|
:active-index="activeTokenIndex"
|
||||||
:display-trans="displayTrans" :is-unmapped="isUnmapped" :get-token-wrapper-info="getTokenWrapperInfo"
|
:display-trans="displayTrans" :is-unmapped="isUnmapped" :get-token-wrapper-info="getTokenWrapperInfo"
|
||||||
:has-weight-suffix="hasWeightSuffix" :get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
|
:has-weight-suffix="hasWeightSuffix" :get-suggestions="(prefix, limit) => store.getSuggestions(prefix, limit)"
|
||||||
@pointer-down="onPointerDown" @panel-dragover="handlePanelDragOver" @panel-dragleave="handlePanelDragLeave"
|
@pointer-down="onPointerDown" @panel-dragover="handlePanelDragOver" @panel-dragleave="handlePanelDragLeave"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, nextTick } from 'vue';
|
import { ref, computed, nextTick } from 'vue';
|
||||||
import { splitTokens, parseDetailedToken, constructToken } from '../../stores/promptStore';
|
import { splitTokens, parseDetailedToken, constructToken, normalizeSymbols } from '../../stores/promptStore';
|
||||||
import PromptQuickAdd from '../PromptQuickAdd.vue';
|
import PromptQuickAdd from '../PromptQuickAdd.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -26,6 +26,7 @@ const emit = defineEmits<{
|
|||||||
'add-tag': [tag: string];
|
'add-tag': [tag: string];
|
||||||
'drag-tag-start': [tag: string];
|
'drag-tag-start': [tag: string];
|
||||||
'drag-tag-end': [];
|
'drag-tag-end': [];
|
||||||
|
'locate-token': [index: number];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const inputEl = ref<HTMLTextAreaElement | null>(null);
|
const inputEl = ref<HTMLTextAreaElement | null>(null);
|
||||||
@@ -154,9 +155,41 @@ async function applySuggestion(s: string) {
|
|||||||
emit('update-suggestions');
|
emit('update-suggestions');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSuggestions() {
|
// 计算与 splitTokens 对齐的非空 token 区间(用于光标定位)
|
||||||
// 通知父组件更新建议
|
function computeTokenRanges(txt: string): { start: number; end: number }[] {
|
||||||
|
const ranges: { start: number; end: number }[] = [];
|
||||||
|
let depth = 0;
|
||||||
|
let segStart = 0;
|
||||||
|
for (let i = 0; i < txt.length; i++) {
|
||||||
|
const c = txt[i]!;
|
||||||
|
if (c === '(' || c === '[' || c === '{' || c === '<') depth++;
|
||||||
|
else if (c === ')' || c === ']' || c === '}' || c === '>') depth = Math.max(0, depth - 1);
|
||||||
|
if ((c === ',' || c === '\n') && depth === 0) {
|
||||||
|
ranges.push({ start: segStart, end: i });
|
||||||
|
segStart = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges.push({ start: segStart, end: txt.length });
|
||||||
|
return ranges.filter(r => txt.slice(r.start, r.end).trim().length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据光标位置定位对应的 token 序号,通知父组件高亮右侧映射
|
||||||
|
function emitLocate() {
|
||||||
|
const el = inputEl.value;
|
||||||
|
if (!el) return;
|
||||||
|
const norm = normalizeSymbols(props.text);
|
||||||
|
const pos = el.selectionStart ?? norm.length;
|
||||||
|
const ranges = computeTokenRanges(norm);
|
||||||
|
if (!ranges.length) { emit('locate-token', -1); return; }
|
||||||
|
let idx = ranges.findIndex(r => pos >= r.start && pos <= r.end);
|
||||||
|
if (idx === -1) idx = ranges.findIndex(r => pos <= r.end);
|
||||||
|
if (idx === -1) idx = ranges.length - 1;
|
||||||
|
emit('locate-token', idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCursorActivity() {
|
||||||
emit('update-suggestions');
|
emit('update-suggestions');
|
||||||
|
emitLocate();
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@@ -172,8 +205,8 @@ defineExpose({
|
|||||||
class="pe-input"
|
class="pe-input"
|
||||||
v-model="localText"
|
v-model="localText"
|
||||||
@keydown="onKeyDown"
|
@keydown="onKeyDown"
|
||||||
@click="updateSuggestions"
|
@click="onCursorActivity"
|
||||||
@keyup="updateSuggestions"
|
@keyup="onCursorActivity"
|
||||||
placeholder="例如:1girl, aaa, bbb, ccc"
|
placeholder="例如:1girl, aaa, bbb, ccc"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="pe-input-actions">
|
<div class="pe-input-actions">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, nextTick } from 'vue';
|
import { ref, computed, nextTick, watch } from 'vue';
|
||||||
import { parseDetailedToken, constructToken } from '../../stores/promptStore';
|
import { parseDetailedToken, constructToken } from '../../stores/promptStore';
|
||||||
import type { LangCode } from '../../types';
|
import type { LangCode } from '../../types';
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ const props = defineProps<{
|
|||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
editSuggestions: string[];
|
editSuggestions: string[];
|
||||||
numericMode: boolean;
|
numericMode: boolean;
|
||||||
|
activeIndex: number | null;
|
||||||
displayTrans: (key: string) => string;
|
displayTrans: (key: string) => string;
|
||||||
isUnmapped: (key: string) => boolean;
|
isUnmapped: (key: string) => boolean;
|
||||||
getTokenWrapperInfo: (token: string) => { wrapperCount: number };
|
getTokenWrapperInfo: (token: string) => { wrapperCount: number };
|
||||||
@@ -54,6 +55,31 @@ const localViewMode = computed({
|
|||||||
set: (v: 'compact' | 'detail') => emit('update:viewMode', v),
|
set: (v: 'compact' | 'detail') => emit('update:viewMode', v),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 每个词条的展示数据只计算一次,避免模板中重复调用 displayTrans / isUnmapped
|
||||||
|
const rows = computed(() => props.tokens.map((key) => ({
|
||||||
|
key,
|
||||||
|
trans: props.displayTrans(key),
|
||||||
|
unmapped: props.isUnmapped(key),
|
||||||
|
removeDisabled: isRemoveDisabled(key),
|
||||||
|
})));
|
||||||
|
|
||||||
|
// 左侧点击提示词 → 右侧映射定位并平滑滚动到视图中央(仅在出现滚动条时滚动)
|
||||||
|
watch(() => props.activeIndex, (idx) => {
|
||||||
|
if (idx == null || idx < 0) return;
|
||||||
|
nextTick(() => {
|
||||||
|
const container = dragContainer.value;
|
||||||
|
if (!container) return;
|
||||||
|
const el = container.querySelector<HTMLElement>(`[data-index="${idx}"]`);
|
||||||
|
if (!el) return;
|
||||||
|
const scroller = container.closest('.pe-right-pane') as HTMLElement | null;
|
||||||
|
if (!scroller || scroller.scrollHeight <= scroller.clientHeight) return;
|
||||||
|
const erect = el.getBoundingClientRect();
|
||||||
|
const srect = scroller.getBoundingClientRect();
|
||||||
|
const delta = (erect.top - srect.top) - (srect.height / 2 - erect.height / 2);
|
||||||
|
scroller.scrollBy({ top: delta, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function beginEdit(i: number) {
|
function beginEdit(i: number) {
|
||||||
editingIndex.value = i;
|
editingIndex.value = i;
|
||||||
editingValue.value = props.tokens[i] ?? '';
|
editingValue.value = props.tokens[i] ?? '';
|
||||||
@@ -204,33 +230,49 @@ defineExpose({
|
|||||||
<!-- 精简视图 -->
|
<!-- 精简视图 -->
|
||||||
<div class="pe-tokens-compact" v-if="viewMode === 'compact'">
|
<div class="pe-tokens-compact" v-if="viewMode === 'compact'">
|
||||||
<div
|
<div
|
||||||
v-for="(k,i) in tokens"
|
v-for="(row,i) in rows"
|
||||||
:key="k + '_' + i"
|
:key="row.key + '_' + i"
|
||||||
:data-index="i"
|
:data-index="i"
|
||||||
:class="{
|
:class="{
|
||||||
'dragging': draggingIndex === i,
|
'dragging': draggingIndex === i,
|
||||||
'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
|
'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
|
||||||
'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
|
'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
|
||||||
'editing': editingIndex === i
|
'editing': editingIndex === i,
|
||||||
|
'is-active': activeIndex === i && draggingIndex !== i
|
||||||
}"
|
}"
|
||||||
class="pe-token-compact"
|
class="pe-token-compact"
|
||||||
@pointerdown="emit('pointer-down', i, $event)"
|
@pointerdown="emit('pointer-down', i, $event)"
|
||||||
@dblclick="beginEdit(i)"
|
@dblclick="beginEdit(i)"
|
||||||
:title="`${k} → ${displayTrans(k)}`"
|
:title="`${row.key} → ${row.trans}`"
|
||||||
>
|
>
|
||||||
<span class="pe-handle-compact">⋮⋮</span>
|
<span class="pe-handle-compact">⋮⋮</span>
|
||||||
<div v-if="editingIndex === i" class="pe-edit-inline">
|
<div v-if="editingIndex === i" class="pe-edit-inline">
|
||||||
<input
|
<div class="pe-edit-row">
|
||||||
ref="editEl"
|
<input
|
||||||
class="pe-edit-input"
|
ref="editEl"
|
||||||
v-model="editingValue"
|
class="pe-edit-input"
|
||||||
@keydown="onEditKeyDown"
|
v-model="editingValue"
|
||||||
@keydown.enter.stop.prevent="commitEdit"
|
@keydown="onEditKeyDown"
|
||||||
@keydown.esc.stop.prevent="cancelEdit"
|
@keydown.enter.stop.prevent="commitEdit"
|
||||||
@click="updateEditSuggestions"
|
@keydown.esc.stop.prevent="cancelEdit"
|
||||||
@keyup="updateEditSuggestions"
|
@click="updateEditSuggestions"
|
||||||
placeholder="编辑提示词"
|
@keyup="updateEditSuggestions"
|
||||||
/>
|
placeholder="编辑提示词"
|
||||||
|
/>
|
||||||
|
<div class="pe-edit-actions">
|
||||||
|
<button @click="commitEdit" class="pe-edit-save-btn" title="保存">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polyline points="20 6 9 17 4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button @click="cancelEdit" class="pe-edit-cancel-btn" title="取消">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ul class="pe-edit-suggest" v-if="editSuggestions.length">
|
<ul class="pe-edit-suggest" v-if="editSuggestions.length">
|
||||||
<li
|
<li
|
||||||
v-for="s in editSuggestions"
|
v-for="s in editSuggestions"
|
||||||
@@ -239,25 +281,12 @@ defineExpose({
|
|||||||
@click="applyEditSuggestion(s)"
|
@click="applyEditSuggestion(s)"
|
||||||
>{{ s }}</li>
|
>{{ s }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="pe-edit-actions">
|
|
||||||
<button @click="commitEdit" class="pe-edit-save-btn" title="保存">
|
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<polyline points="20 6 9 17 4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button @click="cancelEdit" class="pe-edit-cancel-btn" title="取消">
|
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<line x1="18" y1="6" x2="6" y2="18" stroke="currentColor" stroke-width="2"/>
|
|
||||||
<line x1="6" y1="6" x2="18" y2="18" stroke="currentColor" stroke-width="2"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="pe-token-content">
|
<div v-else class="pe-token-content">
|
||||||
<span class="pe-key-compact">{{ k }}</span>
|
<span class="pe-key-compact">{{ row.key }}</span>
|
||||||
<span class="pe-arrow-compact">→</span>
|
<span class="pe-arrow-compact">→</span>
|
||||||
<span class="pe-trans-compact" :class="{ unmapped: isUnmapped(k) }">
|
<span class="pe-trans-compact" :class="{ unmapped: row.unmapped }">
|
||||||
{{ displayTrans(k) }}
|
{{ row.trans }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pe-token-controls-compact">
|
<div class="pe-token-controls-compact">
|
||||||
@@ -272,7 +301,7 @@ defineExpose({
|
|||||||
@click="emit('remove-wrapper', i)"
|
@click="emit('remove-wrapper', i)"
|
||||||
class="pe-remove-wrapper-btn"
|
class="pe-remove-wrapper-btn"
|
||||||
:title="numericMode ? '降低权重' : '移除优先级'"
|
:title="numericMode ? '降低权重' : '移除优先级'"
|
||||||
:disabled="isRemoveDisabled(k)"
|
:disabled="row.removeDisabled"
|
||||||
>
|
>
|
||||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||||
@@ -292,14 +321,15 @@ defineExpose({
|
|||||||
<!-- 详细视图 -->
|
<!-- 详细视图 -->
|
||||||
<div class="pe-tokens-detail" v-else>
|
<div class="pe-tokens-detail" v-else>
|
||||||
<div
|
<div
|
||||||
v-for="(k,i) in tokens"
|
v-for="(row,i) in rows"
|
||||||
:key="k + '_' + i"
|
:key="row.key + '_' + i"
|
||||||
:data-index="i"
|
:data-index="i"
|
||||||
:class="{
|
:class="{
|
||||||
'dragging': draggingIndex === i,
|
'dragging': draggingIndex === i,
|
||||||
'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
|
'insert-before': overIndex === i && insertSide === 'before' && draggingIndex !== i,
|
||||||
'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
|
'insert-after': overIndex === i && insertSide === 'after' && draggingIndex !== i,
|
||||||
'editing': editingIndex === i
|
'editing': editingIndex === i,
|
||||||
|
'is-active': activeIndex === i && draggingIndex !== i
|
||||||
}"
|
}"
|
||||||
class="pe-token-detail"
|
class="pe-token-detail"
|
||||||
@pointerdown="emit('pointer-down', i, $event)"
|
@pointerdown="emit('pointer-down', i, $event)"
|
||||||
@@ -307,12 +337,12 @@ defineExpose({
|
|||||||
<div class="pe-token-header">
|
<div class="pe-token-header">
|
||||||
<span class="pe-handle-detail">⋮⋮</span>
|
<span class="pe-handle-detail">⋮⋮</span>
|
||||||
<div class="pe-token-main" @dblclick="beginEdit(i)">
|
<div class="pe-token-main" @dblclick="beginEdit(i)">
|
||||||
<span class="pe-key-detail">{{ k }}</span>
|
<span class="pe-key-detail">{{ row.key }}</span>
|
||||||
<span class="pe-arrow-detail">→</span>
|
<span class="pe-arrow-detail">→</span>
|
||||||
<span class="pe-trans-detail" :class="{ unmapped: isUnmapped(k) }">{{ displayTrans(k) }}</span>
|
<span class="pe-trans-detail" :class="{ unmapped: row.unmapped }">{{ row.trans }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pe-token-controls">
|
<div class="pe-token-controls">
|
||||||
<button v-if="isUnmapped(k)" class="pe-add-map-btn" @click="emit('show-add-map', i)" title="添加映射">
|
<button v-if="row.unmapped" class="pe-add-map-btn" @click="emit('show-add-map', i)" title="添加映射">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" stroke-width="2"/>
|
<line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" stroke-width="2"/>
|
||||||
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
|
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2"/>
|
||||||
@@ -329,7 +359,7 @@ defineExpose({
|
|||||||
@click="emit('remove-wrapper', i)"
|
@click="emit('remove-wrapper', i)"
|
||||||
class="pe-remove-wrapper-detail-btn"
|
class="pe-remove-wrapper-detail-btn"
|
||||||
:title="numericMode ? '降低权重' : '移除优先级'"
|
:title="numericMode ? '降低权重' : '移除优先级'"
|
||||||
:disabled="isRemoveDisabled(k)"
|
:disabled="row.removeDisabled"
|
||||||
>
|
>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
<path d="M16 3h3v3M8 3H5v3m0 12v3h3m8 0h3v-3" stroke="currentColor" stroke-width="2" fill="none"/>
|
||||||
@@ -489,6 +519,10 @@ defineExpose({
|
|||||||
border-color: var(--color-accent);
|
border-color: var(--color-accent);
|
||||||
box-shadow: 0 0 0 3px var(--color-accent-light);
|
box-shadow: 0 0 0 3px var(--color-accent-light);
|
||||||
background-color: var(--color-bg-primary);
|
background-color: var(--color-bg-primary);
|
||||||
|
/* 编辑时整行展开,给输入框与候选词留足空间,避免被压缩 */
|
||||||
|
width: 100%;
|
||||||
|
flex-basis: 100%;
|
||||||
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pe-token-compact.editing .pe-handle-compact,
|
.pe-token-compact.editing .pe-handle-compact,
|
||||||
@@ -499,10 +533,19 @@ defineExpose({
|
|||||||
|
|
||||||
.pe-edit-inline {
|
.pe-edit-inline {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-edit-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pe-edit-input {
|
.pe-edit-input {
|
||||||
@@ -918,6 +961,8 @@ defineExpose({
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
max-height: 7.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pe-edit-suggest li {
|
.pe-edit-suggest li {
|
||||||
@@ -991,6 +1036,34 @@ defineExpose({
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 左侧点击定位时的高亮(提升)效果 */
|
||||||
|
.pe-token-compact.is-active,
|
||||||
|
.pe-token-detail.is-active {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
box-shadow: 0 0 0 2px var(--color-accent-light), var(--shadow-md);
|
||||||
|
background-color: var(--color-accent-light);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-token-compact.is-active {
|
||||||
|
transform: translateY(-1px) scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-token-detail.is-active {
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pe-token-compact.is-active:not(.editing),
|
||||||
|
.pe-token-detail.is-active:not(.editing) {
|
||||||
|
animation: locatePulse 0.7s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes locatePulse {
|
||||||
|
0% { box-shadow: 0 0 0 0 var(--color-accent); }
|
||||||
|
60% { box-shadow: 0 0 0 6px transparent; }
|
||||||
|
100% { box-shadow: 0 0 0 2px var(--color-accent-light), var(--shadow-md); }
|
||||||
|
}
|
||||||
|
|
||||||
.pe-token-compact,
|
.pe-token-compact,
|
||||||
.pe-token-detail {
|
.pe-token-detail {
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ export const usePromptStore = defineStore('promptStore', {
|
|||||||
// 编辑器内的目标文件夹选择:跨页面切换保留,刷新后重置为默认(不参与持久化)
|
// 编辑器内的目标文件夹选择:跨页面切换保留,刷新后重置为默认(不参与持久化)
|
||||||
editorSelectedFolderId: '' as string,
|
editorSelectedFolderId: '' as string,
|
||||||
editorFolderInitialized: false,
|
editorFolderInitialized: false,
|
||||||
|
// 映射变更计数:用于驱动依赖翻译结果的视图(如右侧映射)的响应式刷新
|
||||||
|
mappingVersion: 0,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
categories: (s) => s.dataset?.categories ?? [],
|
categories: (s) => s.dataset?.categories ?? [],
|
||||||
@@ -537,11 +539,13 @@ export const usePromptStore = defineStore('promptStore', {
|
|||||||
if (exist) {
|
if (exist) {
|
||||||
if (!exist.translation) exist.translation = {};
|
if (!exist.translation) exist.translation = {};
|
||||||
exist.translation[lang] = val;
|
exist.translation[lang] = val;
|
||||||
|
this.mappingVersion++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const grp = this.ensureCustomGroup();
|
const grp = this.ensureCustomGroup();
|
||||||
grp.tags.push({ key, translation: { en: key, [lang]: val } });
|
grp.tags.push({ key, translation: { en: key, [lang]: val } });
|
||||||
rebuildTagIndex(this.dataset);
|
rebuildTagIndex(this.dataset);
|
||||||
|
this.mappingVersion++;
|
||||||
},
|
},
|
||||||
ensureCustomGroup(): PromptGroup {
|
ensureCustomGroup(): PromptGroup {
|
||||||
const catName = 'Custom';
|
const catName = 'Custom';
|
||||||
|
|||||||
Reference in New Issue
Block a user