refactor: 更新UI 样式

This commit is contained in:
ViperEkura 2026-03-27 15:49:14 +08:00
parent f4cb991ed7
commit 41a4d997fd
9 changed files with 123 additions and 212 deletions

View File

@ -30,7 +30,7 @@
:project-name="currentProject.name"
/>
</div>
<div v-else class="explorer-body explorer-empty">
<div v-else class="explorer-body empty-state">
<span v-html="icons.folderLg" style="color: var(--text-tertiary); opacity: 0.5;" />
<p>当前对话未关联项目</p>
</div>
@ -653,17 +653,7 @@ onMounted(() => {
display: flex;
}
.explorer-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
color: var(--text-tertiary);
font-size: 14px;
}
/* modal-overlay, modal-content, btn-icon, form-group, modal-footer, btn-secondary, btn-primary now in global.css */
/* explorer-empty now uses global .empty-state */
.create-modal {
background: var(--bg-primary);

View File

@ -15,15 +15,15 @@
</div>
</div>
<div v-if="loadingTree" class="explorer-loading">
<div v-if="loadingTree" class="empty-state">
<span class="spinner" v-html="icons.spinnerMd" />
</div>
<div v-else-if="treeItems.length === 0" class="explorer-empty">
<div v-else-if="treeItems.length === 0" class="empty-state">
空项目
</div>
<div v-else class="tree-container">
<div v-else class="scrollbar-thin" style="flex: 1; padding: 4px 0;">
<FileTreeItem
v-for="item in treeItems"
:key="item.path"
@ -80,7 +80,7 @@
</div>
<!-- Empty state -->
<div v-else class="viewer-placeholder">
<div v-else class="empty-state">
<span v-html="icons.fileLg" style="color: var(--text-tertiary); opacity: 0.5;" />
<span>选择文件以预览</span>
</div>
@ -311,31 +311,6 @@ onUnmounted(() => {
flex-shrink: 0;
}
.tree-container {
flex: 1;
overflow-y: auto;
padding: 4px 0;
}
.tree-container::-webkit-scrollbar {
width: 4px;
}
.tree-container::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 2px;
}
.explorer-loading,
.explorer-empty {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
color: var(--text-tertiary);
font-size: 13px;
}
/* -- Viewer -- */
.file-viewer {
flex: 1;
@ -400,17 +375,6 @@ onUnmounted(() => {
font-size: 13px;
}
.viewer-placeholder {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
color: var(--text-tertiary);
font-size: 13px;
}
/* -- Code pane -- */
.code-pane {
flex: 1;

View File

@ -30,13 +30,13 @@
<div class="message-footer">
<span class="token-count" v-if="tokenCount">{{ tokenCount }} tokens</span>
<span class="message-time">{{ formatTime(createdAt) }}</span>
<button v-if="role === 'assistant'" class="btn-regenerate" @click="$emit('regenerate')" title="重新生成">
<button v-if="role === 'assistant'" class="ghost-btn success" @click="$emit('regenerate')" title="重新生成">
<span v-html="icons.regenerate" />
</button>
<button v-if="role === 'assistant'" class="btn-copy" @click="copyContent" title="复制">
<button v-if="role === 'assistant'" class="ghost-btn accent" @click="copyContent" title="复制">
<span v-html="icons.copy" />
</button>
<button v-if="deletable" class="btn-delete-msg" @click="$emit('delete')" title="删除">
<button v-if="deletable" class="ghost-btn danger" @click="$emit('delete')" title="删除">
<span v-html="icons.trash" />
</button>
</div>
@ -140,32 +140,6 @@ function copyContent() {
color: var(--text-tertiary);
}
.btn-regenerate,
.btn-copy,
.btn-delete-msg {
background: none;
border: none;
color: var(--text-tertiary);
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.15s;
display: flex;
align-items: center;
}
.btn-regenerate:hover {
color: var(--success-color);
background: var(--success-bg);
}
.btn-copy:hover {
color: var(--accent-primary);
background: var(--accent-primary-light);
}
.btn-delete-msg:hover {
color: var(--danger-color);
background: var(--danger-bg);
}
</style>

View File

@ -5,7 +5,7 @@
<div v-for="(file, index) in uploadedFiles" :key="index" class="file-item">
<span class="file-icon">{{ getFileIcon(file.extension) }}</span>
<span class="file-name">{{ file.name }}</span>
<button class="btn-remove-file" @click="removeFile(index)" title="移除">
<button class="ghost-btn danger btn-remove-file" @click="removeFile(index)" title="移除">
<span v-html="icons.close" />
</button>
</div>
@ -213,25 +213,11 @@ defineExpose({ focus })
}
.btn-remove-file {
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border: none;
background: transparent;
color: var(--text-tertiary);
cursor: pointer;
border-radius: 4px;
transition: all 0.15s;
padding: 0;
}
.btn-remove-file:hover {
background: var(--danger-bg);
color: var(--danger-color);
}
.input-container {
display: flex;
flex-direction: column;

View File

@ -1,6 +1,6 @@
<template>
<Teleport to="body">
<Transition name="modal-fade">
<Transition name="fade">
<div v-if="state.visible" class="modal-overlay" @click.self="onCancel" @keydown.escape="onCancel">
<div class="modal-dialog" role="dialog" :aria-modal="true">
<h3 class="modal-title">{{ state.title }}</h3>
@ -128,13 +128,6 @@ watch(() => state.visible, (v) => {
opacity: 0.85;
}
/* Transitions */
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.2s;
}
.modal-fade-enter-from,
.modal-fade-leave-to {
opacity: 0;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="settings-panel">
<div class="settings-header">
<div class="settings-title">
<div class="panel-header">
<div class="panel-title">
<span v-html="icons.settings" />
<h4>会话设置</h4>
</div>
@ -215,37 +215,7 @@ onMounted(loadModels)
flex-direction: column;
}
.settings-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.settings-title {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-primary);
}
.settings-title svg {
color: var(--text-tertiary);
}
.settings-title h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
/* tab styles now in global.css */
/* panel-header, panel-title, header-actions now in global.css */
.settings-body {
flex: 1;

View File

@ -15,21 +15,21 @@
<span class="project-name">{{ group.name }}</span>
<span class="conv-count">{{ group.conversations.length }}</span>
<button
class="btn-group-action"
class="ghost-btn"
title="新建对话"
@click.stop="$emit('createInProject', { id: group.id, name: group.name })"
>
<span v-html="icons.plus" />
</button>
<button
class="btn-group-action"
class="ghost-btn"
title="浏览文件"
@click.stop="$emit('browseProject', { id: group.id, name: group.name })"
>
<span v-html="icons.folder" />
</button>
<button
class="btn-group-action btn-delete-project"
class="ghost-btn danger btn-delete-project"
title="删除项目"
@click.stop="$emit('deleteProject', { id: group.id, name: group.name })"
>
@ -50,7 +50,7 @@
<span>{{ conv.message_count || 0 }} 条消息 · {{ formatTime(conv.updated_at) }}</span>
</div>
</div>
<button class="btn-delete" @click.stop="$emit('delete', conv.id)" title="删除">
<button class="ghost-btn danger btn-delete" @click.stop="$emit('delete', conv.id)" title="删除">
<span v-html="icons.trash" />
</button>
</div>
@ -64,7 +64,7 @@
<span class="standalone-icon" v-html="icons.chat" />
<span class="conv-count">{{ groupedData.standalone.length }}</span>
<button
class="btn-group-action"
class="ghost-btn"
title="新建对话"
@click.stop="$emit('createInProject', { id: null, name: null })"
>
@ -87,7 +87,7 @@
<span>{{ conv.message_count || 0 }} 条消息 · {{ formatTime(conv.updated_at) }}</span>
</div>
</div>
<button class="btn-delete" @click.stop="$emit('delete', conv.id)" title="删除">
<button class="ghost-btn danger btn-delete" @click.stop="$emit('delete', conv.id)" title="删除">
<span v-html="icons.trash" />
</button>
</div>
@ -229,10 +229,6 @@ function onScroll(e) {
padding: 0 16px 16px;
}
.conversation-list::-webkit-scrollbar {
width: 4px;
}
.conversation-list::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb-sidebar);
border-radius: 2px;
@ -297,16 +293,8 @@ function onScroll(e) {
}
.btn-group-action {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
background: none;
color: var(--text-tertiary);
cursor: pointer;
border-radius: 4px;
flex-shrink: 0;
transition: all 0.15s;
opacity: 0.5;
@ -316,15 +304,10 @@ function onScroll(e) {
opacity: 1;
}
.btn-group-action:hover {
color: var(--accent-primary);
background: var(--accent-primary-light);
opacity: 1;
}
.btn-delete-project:hover {
color: var(--danger-color);
background: var(--danger-bg);
opacity: 1;
}
.conversation-item {
@ -369,24 +352,12 @@ function onScroll(e) {
.btn-delete {
opacity: 0;
background: none;
border: none;
color: var(--text-tertiary);
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.15s;
flex-shrink: 0;
}
.conversation-item:hover .btn-delete {
opacity: 1;
}
.btn-delete:hover {
color: var(--danger-color);
background: var(--danger-bg);
}
.loading-more,
.empty-hint {
@ -407,16 +378,13 @@ function onScroll(e) {
}
.btn-footer {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
border: none;
background: none;
color: var(--text-tertiary);
cursor: pointer;
border-radius: 8px;
transition: all 0.15s;
}

View File

@ -1,7 +1,7 @@
<template>
<div class="stats-panel">
<div class="stats-header">
<div class="stats-title">
<div class="panel-header">
<div class="panel-title">
<span v-html="icons.stats" />
<h4>使用统计</h4>
</div>
@ -324,37 +324,7 @@ onMounted(loadStats)
padding: 0;
}
.stats-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.stats-title {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-primary);
}
.stats-title svg {
color: var(--text-tertiary);
}
.stats-title h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
/* tab styles now in global.css */
/* panel-header, panel-title, header-actions now in global.css */
.stats-loading {
display: flex;

View File

@ -751,5 +751,101 @@ input[type="range"]::-moz-range-thumb {
color: var(--danger-color);
}
/* ============ Ghost Button (base for small icon buttons) ============ */
.ghost-btn {
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
color: var(--text-tertiary);
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.15s;
}
.ghost-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.ghost-btn.danger:hover {
background: var(--danger-bg);
color: var(--danger-color);
}
.ghost-btn.accent:hover {
background: var(--accent-primary-light);
color: var(--accent-primary);
}
.ghost-btn.success:hover {
background: var(--success-bg);
color: var(--success-color);
}
/* ============ Empty / Loading State ============ */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
gap: 12px;
color: var(--text-tertiary);
font-size: 13px;
}
/* ============ Thin Scrollbar (4px) ============ */
.scrollbar-thin {
overflow-y: auto;
}
.scrollbar-thin::-webkit-scrollbar {
width: 4px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 2px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: transparent;
}
/* ============ Panel Header (title + actions) ============ */
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.panel-title {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-primary);
}
.panel-title svg {
color: var(--text-tertiary);
}
.panel-title h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}