feat: redesign personal space layout
This commit is contained in:
parent
93c53eed32
commit
9bfc6f3903
@ -22,6 +22,37 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="personalization-body" v-if="!loading">
|
||||
<form class="personal-form" @submit.prevent="personalization.save()">
|
||||
<div class="personalization-layout">
|
||||
<nav class="personal-page-tabs" aria-label="个人空间分组切换">
|
||||
<button
|
||||
type="button"
|
||||
class="personal-tab-button"
|
||||
:class="{ active: activeTab === 'preferences' }"
|
||||
:aria-pressed="activeTab === 'preferences'"
|
||||
@click.prevent="setActiveTab('preferences')"
|
||||
>
|
||||
<span>个性化设置</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="personal-tab-button"
|
||||
:class="{ active: activeTab === 'behavior' }"
|
||||
:aria-pressed="activeTab === 'behavior'"
|
||||
@click.prevent="setActiveTab('behavior')"
|
||||
>
|
||||
<span>模型行为</span>
|
||||
</button>
|
||||
</nav>
|
||||
<div class="personalization-content-shell">
|
||||
<div
|
||||
class="personalization-content"
|
||||
@touchstart.passive="handleSwipeStart"
|
||||
@touchend.passive="handleSwipeEnd"
|
||||
>
|
||||
<transition name="personal-page-vertical" mode="out-in">
|
||||
<section v-if="activeTab === 'preferences'" key="preferences" class="personal-page personal-page-pref">
|
||||
<div class="personal-toggle-row">
|
||||
<label class="personal-toggle">
|
||||
<span class="toggle-text">
|
||||
<span class="toggle-title">启用个性化提示</span>
|
||||
@ -37,8 +68,9 @@
|
||||
<span class="switch-slider"></span>
|
||||
</span>
|
||||
</label>
|
||||
<form class="personal-form" @submit.prevent="personalization.save()">
|
||||
</div>
|
||||
<div class="personalization-sections">
|
||||
<div class="personal-left-column">
|
||||
<div class="personal-section personal-info">
|
||||
<label class="personal-field">
|
||||
<span>您希望AI智能体怎么自称?</span>
|
||||
@ -95,6 +127,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="personal-right-column">
|
||||
<div class="personal-section personal-considerations">
|
||||
<div class="personal-field">
|
||||
@ -139,7 +172,7 @@
|
||||
<p class="consideration-limit">最多 {{ maxConsiderations }} 条,可拖动排序</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="personal-form-actions">
|
||||
<div class="personal-form-actions card-aligned">
|
||||
<div class="personal-status-group">
|
||||
<transition name="personal-status-fade">
|
||||
<span class="status success" v-if="status">{{ status }}</span>
|
||||
@ -148,12 +181,93 @@
|
||||
<span class="status error" v-if="error">{{ error }}</span>
|
||||
</transition>
|
||||
</div>
|
||||
<button type="button" class="primary" :disabled="saving" @click="personalization.save()">
|
||||
<button type="submit" class="primary" :disabled="saving">
|
||||
{{ saving ? '保存中...' : '保存设置' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section v-else key="behavior" class="personal-page behavior-page">
|
||||
<div class="behavior-section">
|
||||
<div class="behavior-field">
|
||||
<div class="behavior-field-header">
|
||||
<span class="field-title">思考频率</span>
|
||||
<p class="field-desc">控制连续快速模式运行多少轮后,系统会强制切换到思考模型。</p>
|
||||
</div>
|
||||
<div class="thinking-presets">
|
||||
<button
|
||||
v-for="preset in thinkingPresets"
|
||||
:key="preset.id"
|
||||
type="button"
|
||||
:class="{ active: isPresetActive(preset.value) }"
|
||||
@click.prevent="applyThinkingPreset(preset.value)"
|
||||
>
|
||||
{{ preset.label }}
|
||||
<small>{{ preset.value }} 轮</small>
|
||||
</button>
|
||||
</div>
|
||||
<div class="thinking-input-row">
|
||||
<label>
|
||||
<span>自定义轮数</span>
|
||||
<input
|
||||
type="number"
|
||||
:min="thinkingIntervalRange.min"
|
||||
:max="thinkingIntervalRange.max"
|
||||
:placeholder="`默认 ${thinkingIntervalDefault} 轮`"
|
||||
:value="form.thinking_interval ?? ''"
|
||||
@input="handleThinkingInput"
|
||||
@focus="personalization.clearFeedback()"
|
||||
/>
|
||||
</label>
|
||||
<span class="thinking-hint">范围 {{ thinkingIntervalRange.min }}~{{ thinkingIntervalRange.max }} 轮。</span>
|
||||
<button type="button" class="link-button" @click.prevent="restoreThinkingInterval">
|
||||
恢复默认
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="behavior-field">
|
||||
<div class="behavior-field-header">
|
||||
<span class="field-title">默认禁用工具类别</span>
|
||||
<p class="field-desc">选择后,这些类别在新任务中会保持关闭,也可随时通过快捷菜单临时开启。</p>
|
||||
</div>
|
||||
<div class="tool-category-grid" v-if="toolCategories.length">
|
||||
<label
|
||||
v-for="category in toolCategories"
|
||||
:key="category.id"
|
||||
class="tool-category-chip"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="form.disabled_tool_categories.includes(category.id)"
|
||||
@change="toggleCategory(category.id)"
|
||||
/>
|
||||
<span>{{ category.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="behavior-hint" v-else>暂无可配置的工具类别。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="personal-actions-row">
|
||||
<div class="personal-form-actions card-aligned">
|
||||
<div class="personal-status-group">
|
||||
<transition name="personal-status-fade">
|
||||
<span class="status success" v-if="status">{{ status }}</span>
|
||||
</transition>
|
||||
<transition name="personal-status-fade">
|
||||
<span class="status error" v-if="error">{{ error }}</span>
|
||||
</transition>
|
||||
</div>
|
||||
<button type="submit" class="primary" :disabled="saving">
|
||||
{{ saving ? '保存中...' : '保存设置' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="personalization-loading" v-else>正在加载个性化配置...</div>
|
||||
@ -163,6 +277,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { usePersonalizationStore } from '@/stores/personalization';
|
||||
|
||||
@ -179,7 +294,74 @@ const {
|
||||
status,
|
||||
error,
|
||||
saving,
|
||||
toggleUpdating
|
||||
toggleUpdating,
|
||||
toolCategories,
|
||||
thinkingIntervalDefault,
|
||||
thinkingIntervalRange
|
||||
} = storeToRefs(personalization);
|
||||
|
||||
const activeTab = ref<'preferences' | 'behavior'>('preferences');
|
||||
const swipeState = ref<{ startY: number; active: boolean }>({ startY: 0, active: false });
|
||||
|
||||
const thinkingPresets = [
|
||||
{ id: 'low', label: '低', value: 10 },
|
||||
{ id: 'medium', label: '中', value: 5 },
|
||||
{ id: 'high', label: '高', value: 3 }
|
||||
];
|
||||
|
||||
const setActiveTab = (tab: 'preferences' | 'behavior') => {
|
||||
activeTab.value = tab;
|
||||
};
|
||||
|
||||
const handleSwipeStart = (event: TouchEvent) => {
|
||||
if (!event.touches.length) {
|
||||
return;
|
||||
}
|
||||
swipeState.value = { startY: event.touches[0].clientY, active: true };
|
||||
};
|
||||
|
||||
const handleSwipeEnd = (event: TouchEvent) => {
|
||||
if (!swipeState.value.active || !event.changedTouches.length) {
|
||||
return;
|
||||
}
|
||||
const deltaY = event.changedTouches[0].clientY - swipeState.value.startY;
|
||||
swipeState.value.active = false;
|
||||
if (Math.abs(deltaY) < 60) {
|
||||
return;
|
||||
}
|
||||
if (deltaY < 0) {
|
||||
setActiveTab('behavior');
|
||||
} else {
|
||||
setActiveTab('preferences');
|
||||
}
|
||||
};
|
||||
|
||||
const applyThinkingPreset = (value: number) => {
|
||||
personalization.setThinkingInterval(value);
|
||||
};
|
||||
|
||||
const handleThinkingInput = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.value) {
|
||||
personalization.setThinkingInterval(null);
|
||||
return;
|
||||
}
|
||||
const parsed = Number(target.value);
|
||||
personalization.setThinkingInterval(Number.isNaN(parsed) ? null : parsed);
|
||||
};
|
||||
|
||||
const restoreThinkingInterval = () => {
|
||||
personalization.setThinkingInterval(null);
|
||||
};
|
||||
|
||||
const isPresetActive = (value: number) => {
|
||||
if (form.value.thinking_interval === null || typeof form.value.thinking_interval === 'undefined') {
|
||||
return value === thinkingIntervalDefault.value;
|
||||
}
|
||||
return form.value.thinking_interval === value;
|
||||
};
|
||||
|
||||
const toggleCategory = (categoryId: string) => {
|
||||
personalization.toggleDefaultToolCategory(categoryId);
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
}
|
||||
|
||||
.personal-page-card {
|
||||
width: min(95vw, 860px);
|
||||
width: min(96vw, 1020px);
|
||||
height: calc(100vh - 40px);
|
||||
max-height: 760px;
|
||||
background: #fffaf4;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(118, 103, 84, 0.25);
|
||||
@ -21,7 +23,8 @@
|
||||
padding: 40px;
|
||||
text-align: left;
|
||||
color: var(--claude-text);
|
||||
max-height: calc(100vh - 40px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@ -91,15 +94,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 180px);
|
||||
padding-right: 6px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.personalization-body::-webkit-scrollbar {
|
||||
display: none;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.personal-toggle {
|
||||
@ -168,6 +166,233 @@
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.personal-toggle-row {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.personalization-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 200px minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
align-items: stretch;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personal-page-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(118, 103, 84, 0.2);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
align-self: flex-start;
|
||||
height: auto;
|
||||
max-height: 220px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.personal-tab-button {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
padding: 14px 18px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
background: transparent;
|
||||
color: var(--claude-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.personal-tab-button.active {
|
||||
background: rgba(189, 93, 58, 0.12);
|
||||
color: var(--claude-accent);
|
||||
box-shadow: 0 10px 24px rgba(189, 93, 58, 0.15);
|
||||
}
|
||||
|
||||
.personal-tab-button:focus-visible {
|
||||
outline: 2px solid rgba(189, 93, 58, 0.4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.personalization-content-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personalization-content {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(118, 103, 84, 0.18);
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.personalization-content::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.personalization-content .personal-page {
|
||||
width: 100%;
|
||||
padding: 16px 26px 26px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.personalization-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.personal-page-tabs {
|
||||
flex-direction: row;
|
||||
padding: 14px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.personal-tab-button {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.behavior-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
border: 1px solid rgba(118, 103, 84, 0.18);
|
||||
}
|
||||
|
||||
.behavior-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.behavior-field-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.behavior-field-header .field-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.behavior-field-header .field-desc {
|
||||
color: var(--claude-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.thinking-presets {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.thinking-presets button {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
border: 1px solid rgba(118, 103, 84, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-weight: 600;
|
||||
color: var(--claude-text-secondary);
|
||||
}
|
||||
|
||||
.thinking-presets button.active {
|
||||
border-color: var(--claude-accent);
|
||||
color: var(--claude-accent);
|
||||
background: rgba(118, 103, 84, 0.08);
|
||||
}
|
||||
|
||||
.thinking-presets button small {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.thinking-input-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.thinking-input-row label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.thinking-input-row input {
|
||||
width: 120px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(118, 103, 84, 0.3);
|
||||
}
|
||||
|
||||
.thinking-hint {
|
||||
color: var(--claude-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.behavior-hint {
|
||||
color: var(--claude-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--claude-accent);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tool-category-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tool-category-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 12px;
|
||||
background: rgba(118, 103, 84, 0.08);
|
||||
border: 1px solid rgba(118, 103, 84, 0.2);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tool-category-chip input {
|
||||
accent-color: var(--claude-accent);
|
||||
}
|
||||
|
||||
.global-actions {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* ========================================= */
|
||||
/* 移动端面板入口 */
|
||||
/* ========================================= */
|
||||
@ -507,13 +732,25 @@
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personalization-sections {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
|
||||
gap: 18px;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.personal-left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.personal-left-column .personal-section {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personal-section {
|
||||
@ -532,13 +769,13 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
flex: 1 1 0;
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personal-right-column .personal-section {
|
||||
flex: 1 1 auto;
|
||||
.personal-right-column > .personal-form-actions {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.personal-section.personal-considerations .personal-field {
|
||||
@ -566,11 +803,13 @@
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.personalization-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.personal-right-column {
|
||||
.personal-right-column,
|
||||
.personal-left-column {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
@ -709,6 +948,36 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.personal-form-actions.card-aligned {
|
||||
padding: 0;
|
||||
margin-top: auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.personal-form-actions.card-aligned {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-actions-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.personal-actions-row .personal-form-actions {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
.personal-actions-row .personal-form-actions {
|
||||
max-width: calc(50% - 9px);
|
||||
}
|
||||
}
|
||||
|
||||
.personal-status-group {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
@ -761,6 +1030,21 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.personal-page-vertical-enter-active,
|
||||
.personal-page-vertical-leave-active {
|
||||
transition: opacity 0.35s ease, transform 0.35s ease;
|
||||
}
|
||||
|
||||
.personal-page-vertical-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(26px);
|
||||
}
|
||||
|
||||
.personal-page-vertical-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-26px);
|
||||
}
|
||||
|
||||
.personal-page-fade-enter-active,
|
||||
.personal-page-fade-leave-active {
|
||||
transition: opacity 0.25s ease, backdrop-filter 0.25s ease;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user