747 lines
20 KiB
Vue
747 lines
20 KiB
Vue
<!-- =============================================================================
|
||
// 企微IT智能服务台 — 快速回复组件(v5.3 终版 · 三层渐进导航)
|
||
// =============================================================================
|
||
// 导航结构:L1(7分类网格) → L2(chip子分类) → L3(条目列表) → Enter填入
|
||
// 键盘操作:Alt+1~7(L1) → 数字(L2/L3) → Enter填入 → ←/Backspace返回 → /搜索
|
||
// ============================================================================= -->
|
||
|
||
<template>
|
||
<div class="qr-panel">
|
||
<!-- ================================================================ -->
|
||
<!-- 搜索栏(置顶) -->
|
||
<!-- ================================================================ -->
|
||
<div class="qr-search">
|
||
<el-input
|
||
ref="searchRef"
|
||
v-model="searchQuery"
|
||
placeholder="搜索快速回复 / Alt+目录数字"
|
||
size="small"
|
||
clearable
|
||
:prefix-icon="SearchIcon"
|
||
class="qr-search-input"
|
||
@keydown.stop
|
||
/>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- 面包屑导航 -->
|
||
<!-- ================================================================ -->
|
||
<div class="qr-breadcrumb">
|
||
<span v-if="navState.l1Index >= 0" class="bc-back" @click="goBack">
|
||
← 返回
|
||
</span>
|
||
<template v-if="navState.l1Index >= 0">
|
||
<span
|
||
class="bc-item"
|
||
:class="{ 'bc-active': navState.l2Index < 0 }"
|
||
@click="resetToL1"
|
||
>{{ catName(navState.l1Index) }}</span>
|
||
<template v-if="navState.l2Index >= 0">
|
||
<span class="bc-sep">›</span>
|
||
<span class="bc-item bc-active">{{ subName(navState.l1Index, navState.l2Index) }}</span>
|
||
</template>
|
||
</template>
|
||
<span class="bc-placeholder" v-else>选择一个分类开始浏览</span>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- L1 一级分类:7列网格,按钮上下排列,强制一行 -->
|
||
<!-- ================================================================ -->
|
||
<div v-show="showL1" class="qr-l1-grid">
|
||
<button
|
||
v-for="(cat, i) in qrData"
|
||
:key="i"
|
||
class="qr-l1-btn"
|
||
:class="{ active: navState.l1Index === i }"
|
||
@click="selectL1(i)"
|
||
>
|
||
<span class="l1-num">{{ i + 1 }}</span>
|
||
<span class="l1-name">{{ cat.name }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- L2 二级子分类:chip 横向流式 -->
|
||
<!-- ================================================================ -->
|
||
<div v-show="showL2" class="qr-l2-row">
|
||
<button
|
||
v-for="(sub, i) in currentSubs"
|
||
:key="i"
|
||
class="qr-l2-chip"
|
||
:class="{ selected: navState.l2Index === i }"
|
||
@click="selectL2(i)"
|
||
>
|
||
<span class="l2-num">{{ i + 1 }}</span>
|
||
<span>{{ sub.name }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- L3 回复列表 -->
|
||
<!-- ================================================================ -->
|
||
<div v-show="showL3" class="qr-l3-scroll">
|
||
<div v-if="filteredItems.length === 0" class="qr-empty">
|
||
{{ searchQuery ? '无匹配结果' : '暂无回复模板' }}
|
||
</div>
|
||
<div v-else class="qr-l3-list">
|
||
<div
|
||
v-for="(item, i) in filteredItems"
|
||
:key="i"
|
||
class="qr-l3-item"
|
||
:class="{ selected: navState.selectedIndex === i }"
|
||
:ref="(el) => { if (el) itemRefs[i] = el as HTMLElement }"
|
||
@click="selectL3(i)"
|
||
@mouseenter="navState.selectedIndex = i"
|
||
>
|
||
<span class="qr-l3-num">{{ i + 1 }}</span>
|
||
<div class="qr-l3-body">
|
||
<div class="qr-l3-title">{{ item.title }}</div>
|
||
<div class="qr-l3-content">{{ item.content }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- 选中预览条 -->
|
||
<!-- ================================================================ -->
|
||
<div v-if="selectedPreview" class="qr-selected-bar" @click="fillSelected">
|
||
<span class="qr-selected-label">已选:</span>
|
||
<span class="qr-selected-text">{{ selectedPreview }}</span>
|
||
<span class="qr-selected-enter">Enter ↵ 填入</span>
|
||
</div>
|
||
|
||
<!-- ================================================================ -->
|
||
<!-- 底部键盘指南 -->
|
||
<!-- ================================================================ -->
|
||
<div class="qr-keyboard-guide">
|
||
<span><kbd>Alt+1-7</kbd> 一级</span>
|
||
<span class="qr-guide-sep">|</span>
|
||
<span><kbd>数字</kbd> 选子项</span>
|
||
<span class="qr-guide-sep">|</span>
|
||
<span><kbd>Enter</kbd> 填入</span>
|
||
<span class="qr-guide-sep">|</span>
|
||
<span><kbd>←</kbd> 返回</span>
|
||
<span class="qr-guide-sep">|</span>
|
||
<span><kbd>/</kbd> 搜索</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ==================================================================== -->
|
||
<!-- Script -->
|
||
<!-- ==================================================================== -->
|
||
<script setup lang="ts">
|
||
import { ref, computed, watch, nextTick, onMounted } from 'vue'
|
||
import { Search as SearchIcon } from '@element-plus/icons-vue'
|
||
import { useConversationStore } from '@/stores/conversation'
|
||
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'
|
||
import { qrData, type QrCategory, type QrItem } from '@/data/qrData'
|
||
|
||
// ========================================================================
|
||
// Interface
|
||
// ========================================================================
|
||
|
||
interface Emits {
|
||
(e: 'use-template', content: string): void
|
||
}
|
||
|
||
const emit = defineEmits<Emits>()
|
||
|
||
// ========================================================================
|
||
// State
|
||
// ========================================================================
|
||
|
||
const conversationStore = useConversationStore()
|
||
const searchRef = ref<InstanceType<typeof import('element-plus')['ElInput']> | null>(null)
|
||
const itemRefs = ref<Record<number, HTMLElement>>({})
|
||
|
||
/** 搜索关键词 */
|
||
const searchQuery = ref('')
|
||
|
||
/**
|
||
* 导航状态机
|
||
* l1Index: -1 = 初始/L1选择中; >=0 = 已选L1分类
|
||
* l2Index: -1 = L2选择中; >=0 = 已选L2子分类,展示L3
|
||
* selectedIndex: L3列表中的选中索引
|
||
*/
|
||
interface NavState {
|
||
l1Index: number
|
||
l2Index: number
|
||
selectedIndex: number
|
||
}
|
||
const navState = ref<NavState>({
|
||
l1Index: -1,
|
||
l2Index: -1,
|
||
selectedIndex: 0,
|
||
})
|
||
|
||
// ========================================================================
|
||
// Computed
|
||
// ========================================================================
|
||
|
||
/** 是否显示 L1 网格 */
|
||
const showL1 = computed(() => navState.value.l1Index < 0)
|
||
|
||
/** 是否显示 L2 chip 行 */
|
||
const showL2 = computed(() => navState.value.l1Index >= 0 && navState.value.l2Index < 0)
|
||
|
||
/** 是否显示 L3 列表 */
|
||
const showL3 = computed(() => navState.value.l1Index >= 0 && navState.value.l2Index >= 0)
|
||
|
||
/** 当前 L1 分类 */
|
||
const currentCategory = computed<QrCategory | null>(() => {
|
||
if (navState.value.l1Index < 0) return null
|
||
return qrData[navState.value.l1Index] ?? null
|
||
})
|
||
|
||
/** 当前 L2 子分类列表 */
|
||
const currentSubs = computed(() => {
|
||
const cat = currentCategory.value
|
||
return cat ? cat.subs : []
|
||
})
|
||
|
||
/** 当前 L3 条目列表 */
|
||
const currentItems = computed(() => {
|
||
const cat = currentCategory.value
|
||
if (!cat || navState.value.l2Index < 0) return []
|
||
const sub = cat.subs[navState.value.l2Index]
|
||
return sub ? sub.items : []
|
||
})
|
||
|
||
/** 搜索过滤后的 L3 条目 */
|
||
const filteredItems = computed<QrItem[]>(() => {
|
||
const items = currentItems.value
|
||
if (!searchQuery.value.trim()) return items
|
||
const q = searchQuery.value.trim().toLowerCase()
|
||
return items.filter(
|
||
(item) =>
|
||
item.title.toLowerCase().includes(q) ||
|
||
item.content.toLowerCase().includes(q)
|
||
)
|
||
})
|
||
|
||
/** 当前选中的条目文案预览 */
|
||
const selectedPreview = computed(() => {
|
||
if (!showL3.value) return null
|
||
const items = filteredItems.value
|
||
if (items.length === 0) return null
|
||
const idx = navState.value.selectedIndex
|
||
if (idx < 0 || idx >= items.length) return null
|
||
return items[idx].title
|
||
})
|
||
|
||
// ========================================================================
|
||
// Helpers
|
||
// ========================================================================
|
||
|
||
function catName(index: number): string {
|
||
return qrData[index]?.name ?? ''
|
||
}
|
||
|
||
function subName(l1: number, l2: number): string {
|
||
return qrData[l1]?.subs[l2]?.name ?? ''
|
||
}
|
||
|
||
// ========================================================================
|
||
// Navigation Methods
|
||
// ========================================================================
|
||
|
||
/** 选择 L1 分类 → 进入 L2 */
|
||
function selectL1(index: number): void {
|
||
if (index < 0 || index >= qrData.length) return
|
||
navState.value = { l1Index: index, l2Index: -1, selectedIndex: 0 }
|
||
searchQuery.value = ''
|
||
}
|
||
|
||
/** 选择 L2 子分类 → 进入 L3 */
|
||
function selectL2(index: number): void {
|
||
const cat = currentCategory.value
|
||
if (!cat || index < 0 || index >= cat.subs.length) return
|
||
navState.value.l2Index = index
|
||
navState.value.selectedIndex = 0
|
||
searchQuery.value = ''
|
||
nextTick(() => scrollToSelected())
|
||
}
|
||
|
||
/** 点击 L3 条目 → 直接填入 */
|
||
function selectL3(index: number): void {
|
||
const items = filteredItems.value
|
||
if (index < 0 || index >= items.length) return
|
||
fillContent(items[index].content)
|
||
}
|
||
|
||
/** 将选中条目填入输入框 */
|
||
function fillSelected(): void {
|
||
const items = filteredItems.value
|
||
const idx = navState.value.selectedIndex
|
||
if (idx < 0 || idx >= items.length) return
|
||
fillContent(items[idx].content)
|
||
}
|
||
|
||
/** 填入内容(含变量替换) */
|
||
function fillContent(content: string): void {
|
||
const conv = conversationStore.currentConversation
|
||
const variables: Record<string, string> = {}
|
||
if (conv) {
|
||
variables.employee_name = conv.employee_name || ''
|
||
variables.department = conv.department || ''
|
||
variables.position = conv.position || ''
|
||
}
|
||
|
||
let result = content
|
||
for (const [key, value] of Object.entries(variables)) {
|
||
result = result.replaceAll(`{${key}}`, value)
|
||
}
|
||
|
||
emit('use-template', result)
|
||
}
|
||
|
||
/** 返回上一级 */
|
||
function goBack(): void {
|
||
if (navState.value.l2Index >= 0) {
|
||
// L3 → L2
|
||
navState.value.l2Index = -1
|
||
navState.value.selectedIndex = 0
|
||
} else if (navState.value.l1Index >= 0) {
|
||
// L2 → L1
|
||
navState.value.l1Index = -1
|
||
navState.value.selectedIndex = 0
|
||
}
|
||
}
|
||
|
||
/** 回到 L1(点击面包屑中的 L1) */
|
||
function resetToL1(): void {
|
||
if (navState.value.l2Index >= 0) {
|
||
navState.value.l2Index = -1
|
||
navState.value.selectedIndex = 0
|
||
}
|
||
}
|
||
|
||
/** 滚动选中项到视图 */
|
||
function scrollToSelected(): void {
|
||
nextTick(() => {
|
||
const el = itemRefs.value[navState.value.selectedIndex]
|
||
if (el) {
|
||
el.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
||
}
|
||
})
|
||
}
|
||
|
||
// ========================================================================
|
||
// Keyboard Navigation (数字键选择)
|
||
// ========================================================================
|
||
|
||
function handleDigitKey(digit: number): void {
|
||
if (showL1.value) {
|
||
// L1: 数字键选择分类
|
||
if (digit >= 1 && digit <= qrData.length) {
|
||
selectL1(digit - 1)
|
||
}
|
||
} else if (showL2.value) {
|
||
// L2: 数字键选择子分类
|
||
const subs = currentSubs.value
|
||
if (digit >= 1 && digit <= subs.length) {
|
||
selectL2(digit - 1)
|
||
}
|
||
} else if (showL3.value) {
|
||
// L3: 数字键选择条目
|
||
const items = filteredItems.value
|
||
if (digit >= 1 && digit <= items.length) {
|
||
selectL3(digit - 1)
|
||
}
|
||
}
|
||
}
|
||
|
||
function navigateUpDown(direction: 'up' | 'down'): void {
|
||
if (showL3.value) {
|
||
const len = filteredItems.value.length
|
||
if (len === 0) return
|
||
if (direction === 'up') {
|
||
navState.value.selectedIndex =
|
||
navState.value.selectedIndex > 0 ? navState.value.selectedIndex - 1 : len - 1
|
||
} else {
|
||
navState.value.selectedIndex =
|
||
navState.value.selectedIndex < len - 1 ? navState.value.selectedIndex + 1 : 0
|
||
}
|
||
scrollToSelected()
|
||
}
|
||
}
|
||
|
||
function confirmSelection(): void {
|
||
if (showL3.value) {
|
||
fillSelected()
|
||
}
|
||
}
|
||
|
||
function focusSearch(): void {
|
||
searchRef.value?.focus()
|
||
}
|
||
|
||
// ========================================================================
|
||
// Keyboard Shortcuts Registration
|
||
// ========================================================================
|
||
|
||
// 注册 Alt+1~7 分类切换
|
||
function handleCategoryShortcut(index: number): void {
|
||
if (index >= 0 && index < qrData.length) {
|
||
selectL1(index)
|
||
}
|
||
}
|
||
|
||
// 注册全局键盘快捷键
|
||
useKeyboardShortcuts({
|
||
onQuickReplyCategory: handleCategoryShortcut,
|
||
onQuickReplyDigit: handleDigitKey,
|
||
onQuickReplyBack: goBack,
|
||
onQuickReplyNavigate: navigateUpDown,
|
||
onQuickReplyConfirm: confirmSelection,
|
||
onFocusSearch: focusSearch,
|
||
})
|
||
|
||
// ========================================================================
|
||
// Watchers — 搜索词变化后重置选中
|
||
// ========================================================================
|
||
|
||
watch(searchQuery, () => {
|
||
navState.value.selectedIndex = 0
|
||
})
|
||
|
||
// ========================================================================
|
||
// Lifecycle — 初始进入默认展示 L1
|
||
// ========================================================================
|
||
|
||
onMounted(() => {
|
||
navState.value = { l1Index: -1, l2Index: -1, selectedIndex: 0 }
|
||
})
|
||
</script>
|
||
|
||
<!-- ==================================================================== -->
|
||
<!-- Styles -->
|
||
<!-- ==================================================================== -->
|
||
<style scoped>
|
||
.qr-panel {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ---- 搜索栏 ---- */
|
||
.qr-search {
|
||
padding: 6px 10px;
|
||
flex-shrink: 0;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
.qr-search-input {
|
||
width: 100%;
|
||
}
|
||
.qr-search-input :deep(.el-input__wrapper) {
|
||
background-color: var(--bg-tertiary);
|
||
border-radius: var(--radius-md);
|
||
box-shadow: none !important;
|
||
border: 1px solid var(--border-light);
|
||
font-size: 12px;
|
||
}
|
||
.qr-search-input :deep(.el-input__wrapper:hover) {
|
||
border-color: var(--accent);
|
||
}
|
||
.qr-search-input :deep(.el-input__wrapper.is-focus) {
|
||
border-color: var(--accent);
|
||
box-shadow: 0 0 0 1px var(--accent) !important;
|
||
}
|
||
.qr-search-input :deep(.el-input__prefix-inner) {
|
||
color: var(--text-tertiary);
|
||
}
|
||
|
||
/* ---- 面包屑 ---- */
|
||
.qr-breadcrumb {
|
||
padding: 5px 10px;
|
||
flex-shrink: 0;
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex-wrap: wrap;
|
||
border-bottom: 1px solid var(--border-light);
|
||
background: var(--bg-tertiary);
|
||
min-height: 26px;
|
||
}
|
||
.bc-back {
|
||
cursor: pointer;
|
||
color: var(--accent);
|
||
font-weight: 500;
|
||
padding: 1px 6px;
|
||
border-radius: 3px;
|
||
transition: 0.2s;
|
||
}
|
||
.bc-back:hover {
|
||
background: var(--accent-soft);
|
||
}
|
||
.bc-sep {
|
||
color: var(--text-placeholder);
|
||
}
|
||
.bc-item {
|
||
color: var(--text-secondary);
|
||
transition: 0.2s;
|
||
cursor: default;
|
||
}
|
||
.bc-item.bc-active {
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
}
|
||
.bc-item:not(.bc-active) {
|
||
cursor: pointer;
|
||
}
|
||
.bc-item:not(.bc-active):hover {
|
||
color: var(--accent);
|
||
}
|
||
.bc-placeholder {
|
||
color: var(--text-tertiary);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* ---- L1 一级分类:7列网格,强制一行,按钮内上下排列 ---- */
|
||
.qr-l1-grid {
|
||
padding: 4px 6px 6px;
|
||
flex-shrink: 0;
|
||
border-bottom: 1px solid var(--border-light);
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 4px;
|
||
}
|
||
.qr-l1-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 2px;
|
||
padding: 5px 2px;
|
||
border-radius: var(--radius-sm);
|
||
cursor: pointer;
|
||
transition: 0.15s;
|
||
font-size: 10px;
|
||
color: var(--text-secondary);
|
||
background: transparent;
|
||
border: 1px solid var(--border-light);
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
.qr-l1-btn:hover {
|
||
background: var(--bg-hover);
|
||
color: var(--text-primary);
|
||
}
|
||
.qr-l1-btn.active {
|
||
background: var(--accent-soft);
|
||
color: var(--accent);
|
||
border-color: var(--accent);
|
||
font-weight: 600;
|
||
}
|
||
.l1-num {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 4px;
|
||
background: var(--bg-tertiary);
|
||
color: var(--text-tertiary);
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
border: 1px solid var(--border-light);
|
||
}
|
||
.qr-l1-btn.active .l1-num {
|
||
background: var(--accent);
|
||
color: var(--bg-secondary);
|
||
border-color: var(--accent);
|
||
}
|
||
.l1-name {
|
||
font-size: 10px;
|
||
line-height: 1.2;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* ---- L2 二级子分类 chip ---- */
|
||
.qr-l2-row {
|
||
padding: 5px 8px;
|
||
flex-shrink: 0;
|
||
border-bottom: 1px solid var(--border-light);
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
}
|
||
.qr-l2-chip {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
padding: 3px 8px;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
transition: 0.15s;
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
background: var(--bg-tertiary);
|
||
border: 1px solid var(--border-light);
|
||
white-space: nowrap;
|
||
}
|
||
.qr-l2-chip:hover {
|
||
background: var(--bg-hover);
|
||
color: var(--text-primary);
|
||
border-color: var(--border-light);
|
||
}
|
||
.qr-l2-chip.selected {
|
||
background: var(--accent-soft);
|
||
color: var(--accent);
|
||
border-color: var(--accent);
|
||
font-weight: 600;
|
||
}
|
||
.l2-num {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
background: var(--bg-hover);
|
||
color: var(--text-tertiary);
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
}
|
||
.qr-l2-chip.selected .l2-num {
|
||
background: var(--accent);
|
||
color: var(--bg-secondary);
|
||
}
|
||
|
||
/* ---- L3 列表 ---- */
|
||
.qr-l3-scroll {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
min-height: 0;
|
||
}
|
||
.qr-empty {
|
||
text-align: center;
|
||
padding: 24px 12px;
|
||
color: var(--text-tertiary);
|
||
font-size: 12px;
|
||
}
|
||
.qr-l3-list {
|
||
padding: 2px 0;
|
||
}
|
||
.qr-l3-item {
|
||
display: flex;
|
||
gap: 6px;
|
||
padding: 7px 10px;
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
border-left: 3px solid transparent;
|
||
}
|
||
.qr-l3-item:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
.qr-l3-item.selected {
|
||
background: var(--accent-soft);
|
||
border-left-color: var(--accent);
|
||
}
|
||
.qr-l3-num {
|
||
flex-shrink: 0;
|
||
width: 16px;
|
||
text-align: right;
|
||
font-size: 10px;
|
||
color: var(--text-tertiary);
|
||
margin-top: 1px;
|
||
}
|
||
.qr-l3-body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
.qr-l3-title {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--text-primary);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.qr-l3-content {
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
margin-top: 2px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* ---- 选中预览条 ---- */
|
||
.qr-selected-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 5px 10px;
|
||
border-top: 1px solid var(--border-light);
|
||
background: var(--bg-accent-soft);
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
transition: background 0.15s;
|
||
}
|
||
.qr-selected-bar:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
.qr-selected-label {
|
||
color: var(--text-tertiary);
|
||
flex-shrink: 0;
|
||
}
|
||
.qr-selected-text {
|
||
flex: 1;
|
||
color: var(--accent);
|
||
font-weight: 500;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.qr-selected-enter {
|
||
color: var(--text-placeholder);
|
||
font-size: 10px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ---- 键盘指南 ---- */
|
||
.qr-keyboard-guide {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
padding: 4px 8px;
|
||
border-top: 1px solid var(--border-light);
|
||
background: var(--bg-secondary);
|
||
flex-shrink: 0;
|
||
font-size: 10px;
|
||
color: var(--text-tertiary);
|
||
flex-wrap: wrap;
|
||
}
|
||
.qr-keyboard-guide kbd {
|
||
display: inline-block;
|
||
padding: 0 3px;
|
||
font-size: 9px;
|
||
font-family: inherit;
|
||
background: var(--bg-tertiary);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 2px;
|
||
color: var(--text-secondary);
|
||
line-height: 1.4;
|
||
}
|
||
.qr-guide-sep {
|
||
color: var(--text-placeholder);
|
||
}
|
||
</style>
|