agent-Specialization/static/src/components/personalization/tabs/PreferencesTab.vue
JOJO f5bfab4056 feat(settings): 个人空间重构为紧凑窄版布局,完善移动端适配
- 卡片窄化至 648px、圆角 14px、侧栏 184px,控件行高紧凑化
- 4429 行主文件拆分为 13 个 tab 子组件 + 共享样式表
- 移动端(≤760px)改为 iOS 设置式「列表→子页」导航
- 修复内容区缺 flex:1 导致设置行无法占满
- 修复交流语气行 label 被 grid auto 列挤压成竖排
- 修复移动端弹窗顶栏被主界面顶栏(z-index 1500)遮挡
- 窄屏禁用选项 hover/active 视觉态与 tab 切换过渡动画
- 修复上下文图标 28 viewBox/1.7 描边导致的视觉偏小偏细
2026-08-29 21:58:53 +08:00

277 lines
14 KiB
Vue

<script setup lang="ts">
import { inject } from 'vue';
import FancyCheck from '@/components/common/FancyCheck.vue';
defineOptions({ name: 'PreferencesTab' });
/**
* 共享上下文由 PersonalizationDrawer.vue 通过 provide 注入。
* 解构出的名称与主文件 script 顶层绑定一致,模板可直接引用。
*/
const ctx = inject<Record<string, any>>('personalizationDrawer')!;
const {
personalization,
form,
tonePresets,
toggleUpdating,
activeDropdown,
toggleDropdown,
floatingMenuStyle,
activeTheme,
communicationStyleLabel,
conversationContinuityLabel,
currentBlockDisplayMode,
stackedHideBorders,
handleStackedHideBordersChange,
minimalExpandHeightLimited,
handleMinimalExpandHeightLimitedChange,
selectCommunicationStyle,
selectConversationContinuity
} = ctx;
</script>
<template>
<section
class="settings-page"
data-tutorial="personal-page-preferences"
>
<label class="settings-toggle-row">
<span class="settings-row-copy">
<span class="settings-row-title">{{ $t('personalization.preferencesTitle') }}</span>
<span class="settings-row-desc">{{ $t('personalization.preferencesDesc') }}</span>
</span>
<input
type="checkbox"
:checked="form.enabled"
:disabled="toggleUpdating"
@change="personalization.toggleEnabled()"
/>
<FancyCheck :checked="form.enabled" />
</label>
<label class="settings-input-row">
<span class="settings-row-title">{{ $t('personalization.selfIdentifyTitle') }}</span>
<input
type="text"
:value="form.self_identify"
maxlength="20"
:placeholder="$t('personalization.selfIdentifyPlaceholder')"
@input="
personalization.updateField({
key: 'self_identify',
value: $event.target.value
})
"
@focus="personalization.clearFeedback()"
/>
</label>
<label class="settings-input-row">
<span class="settings-row-title">{{ $t('personalization.userNameTitle') }}</span>
<input
type="text"
:value="form.user_name"
maxlength="20"
:placeholder="$t('personalization.userNamePlaceholder')"
@input="
personalization.updateField({
key: 'user_name',
value: $event.target.value
})
"
@focus="personalization.clearFeedback()"
/>
</label>
<label class="settings-input-row">
<span class="settings-row-title">{{ $t('personalization.professionTitle') }}</span>
<input
type="text"
:value="form.profession"
maxlength="20"
:placeholder="$t('personalization.professionPlaceholder')"
@input="
personalization.updateField({
key: 'profession',
value: $event.target.value
})
"
@focus="personalization.clearFeedback()"
/>
</label>
<div class="settings-input-row stackable">
<span class="settings-row-title">{{ $t('personalization.toneTitle') }}</span>
<div class="settings-input-stack">
<input
type="text"
:value="form.tone"
maxlength="20"
:placeholder="$t('personalization.tonePlaceholder')"
@input="
personalization.updateField({
key: 'tone',
value: $event.target.value
})
"
@focus="personalization.clearFeedback()"
/>
<div class="settings-chip-row">
<button
v-for="preset in tonePresets"
:key="preset"
type="button"
@click.prevent="personalization.applyTonePreset(preset)"
>
{{ preset }}
</button>
</div>
</div>
</div>
<div class="settings-textarea-row">
<div class="settings-row-copy">
<span class="settings-row-title">{{ $t('personalization.considerationsTitle') }}</span>
<span class="settings-row-desc">{{ $t('personalization.considerationsDesc') }}</span>
</div>
<textarea
:value="form.considerations"
rows="6"
maxlength="2000"
:placeholder="$t('personalization.considerationsPlaceholder')"
@input="personalization.updateConsiderations($event.target.value)"
@focus="personalization.clearFeedback()"
></textarea>
</div>
<label
v-if="currentBlockDisplayMode === 'stacked'"
class="settings-toggle-row"
>
<span class="settings-row-copy">
<span class="settings-row-title">{{ $t('personalization.hideBlockBordersTitle') }}</span>
<span class="settings-row-desc">{{ $t('personalization.hideBlockBordersDesc') }}</span>
</span>
<input
type="checkbox"
:checked="stackedHideBorders"
@change="handleStackedHideBordersChange($event)"
/>
<FancyCheck :checked="stackedHideBorders" />
</label>
<label
v-if="currentBlockDisplayMode === 'minimal'"
class="settings-toggle-row"
>
<span class="settings-row-copy">
<span class="settings-row-title">{{ $t('personalization.minimalExpandHeightTitle') }}</span>
<span class="settings-row-desc">{{ $t('personalization.minimalExpandHeightDesc') }}</span>
</span>
<input
type="checkbox"
:checked="minimalExpandHeightLimited"
@change="handleMinimalExpandHeightLimitedChange($event)"
/>
<FancyCheck :checked="minimalExpandHeightLimited" />
</label>
<div class="settings-select-row">
<span class="settings-row-copy">
<span class="settings-row-title">{{ $t('personalization.communicationStyleTitle') }}</span>
<span class="settings-row-desc">{{ $t('personalization.communicationStyleDesc') }}</span>
</span>
<div
class="settings-select-wrap"
:class="{ open: activeDropdown === 'communication' }"
@click.stop
>
<button
type="button"
class="settings-select-button"
@click="toggleDropdown('communication')"
>
{{ communicationStyleLabel }}
<span class="select-chevron" aria-hidden="true"></span>
</button>
<div
:class="['settings-floating-menu', { dark: activeTheme === 'dark' }]"
:style="activeDropdown ? floatingMenuStyle : undefined"
>
<button
type="button"
class="settings-menu-option"
:class="{ selected: form.communication_style === 'default' }"
@click="selectCommunicationStyle('default')"
>
<strong>{{ $t('personalization.communicationDefault') }}</strong><span>{{ $t('personalization.communicationDefaultDesc') }}</span
><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
</button>
<button
type="button"
class="settings-menu-option"
:class="{ selected: form.communication_style === 'human_like' }"
@click="selectCommunicationStyle('human_like')"
>
<strong>{{ $t('personalization.communicationHumanLike') }}</strong><span>{{ $t('personalization.communicationHumanLikeDesc') }}</span
><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
</button>
<button
type="button"
class="settings-menu-option"
:class="{ selected: form.communication_style === 'auto' }"
@click="selectCommunicationStyle('auto')"
>
<strong>{{ $t('personalization.communicationAuto') }}</strong><span>{{ $t('personalization.communicationAutoDesc') }}</span
><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
</button>
</div>
</div>
</div>
<div class="settings-select-row">
<span class="settings-row-copy">
<span class="settings-row-title">{{ $t('personalization.conversationContinuityTitle') }}</span>
<span class="settings-row-desc">{{ $t('personalization.conversationContinuityDesc') }}</span>
</span>
<div
class="settings-select-wrap"
:class="{ open: activeDropdown === 'conversation-continuity' }"
@click.stop
>
<button
type="button"
class="settings-select-button"
@click="toggleDropdown('conversation-continuity')"
>
{{ conversationContinuityLabel }}
<span class="select-chevron" aria-hidden="true"></span>
</button>
<div
:class="['settings-floating-menu', { dark: activeTheme === 'dark' }]"
:style="activeDropdown ? floatingMenuStyle : undefined"
>
<button
type="button"
class="settings-menu-option"
:class="{ selected: form.conversation_continuity === 'high' }"
@click="selectConversationContinuity('high')"
>
<strong>{{ $t('personalization.continuityHigh') }}</strong><span>{{ $t('personalization.continuityHighDesc') }}</span
><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
</button>
<button
type="button"
class="settings-menu-option"
:class="{ selected: form.conversation_continuity === 'medium' }"
@click="selectConversationContinuity('medium')"
>
<strong>{{ $t('personalization.continuityMedium') }}</strong><span>{{ $t('personalization.continuityMediumDesc') }}</span
><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
</button>
<button
type="button"
class="settings-menu-option"
:class="{ selected: form.conversation_continuity === 'low' }"
@click="selectConversationContinuity('low')"
>
<strong>{{ $t('personalization.continuityLow') }}</strong><span>{{ $t('personalization.continuityLowDesc') }}</span
><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
</button>
</div>
</div>
</div>
</section>
</template>