first commit
This commit is contained in:
609
web/src/components/DisplaySettings.vue
Normal file
609
web/src/components/DisplaySettings.vue
Normal file
@@ -0,0 +1,609 @@
|
||||
<template>
|
||||
<div class="display-settings-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="header-info">
|
||||
<h1>显示设置</h1>
|
||||
<p>自定义应用外观和用户体验</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置内容 -->
|
||||
<div class="settings-content">
|
||||
<!-- 主题 -->
|
||||
<n-card title="主题" size="large">
|
||||
<div class="setting-section">
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">主题模式</span>
|
||||
<span class="label-desc">选择应用的主题风格</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-radio-group v-model:value="displaySettings.theme" name="theme">
|
||||
<n-radio-button value="light">
|
||||
<div class="theme-option">
|
||||
<n-icon :component="SunIcon" />
|
||||
<span>浅色</span>
|
||||
</div>
|
||||
</n-radio-button>
|
||||
<n-radio-button value="dark">
|
||||
<div class="theme-option">
|
||||
<n-icon :component="MoonIcon" />
|
||||
<span>深色</span>
|
||||
</div>
|
||||
</n-radio-button>
|
||||
<n-radio-button value="system">
|
||||
<div class="theme-option">
|
||||
<n-icon :component="DeviceDesktopIcon" />
|
||||
<span>系统</span>
|
||||
</div>
|
||||
</n-radio-button>
|
||||
</n-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主题颜色 -->
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">主题颜色</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="color-picker-grid">
|
||||
<div
|
||||
v-for="color in themeColors"
|
||||
:key="color"
|
||||
class="color-option"
|
||||
:class="{ active: displaySettings.primaryColor === color }"
|
||||
:style="{ backgroundColor: color }"
|
||||
@click="displaySettings.primaryColor = color"
|
||||
>
|
||||
<n-icon v-if="displaySettings.primaryColor === color" :component="CheckIcon" color="white" />
|
||||
</div>
|
||||
<!-- 自定义颜色输入 -->
|
||||
<n-input
|
||||
v-model:value="displaySettings.primaryColor"
|
||||
placeholder="#00B96B"
|
||||
size="small"
|
||||
style="width: 80px; margin-left: 8px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 透明窗口 -->
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">透明窗口</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-switch v-model:value="displaySettings.transparentWindow" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 导航栏设置 -->
|
||||
<n-card title="导航栏设置" size="large">
|
||||
<div class="setting-section">
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">导航位置</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-radio-group v-model:value="displaySettings.navPosition" name="navPosition">
|
||||
<n-radio value="left">左侧</n-radio>
|
||||
<n-radio value="top">顶部</n-radio>
|
||||
</n-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 缩放设置 -->
|
||||
<n-card title="缩放设置" size="large">
|
||||
<div class="setting-section">
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">缩放</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="zoom-controls">
|
||||
<n-button size="small" @click="decreaseZoom">-</n-button>
|
||||
<span class="zoom-value">{{ displaySettings.zoomLevel }}%</span>
|
||||
<n-button size="small" @click="increaseZoom">+</n-button>
|
||||
<n-button size="small" @click="resetZoom">
|
||||
<n-icon :component="RefreshIcon" />
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 字体设置 -->
|
||||
<n-card title="字体设置" size="large">
|
||||
<div class="setting-section">
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">全局字体</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-select
|
||||
v-model:value="displaySettings.globalFont"
|
||||
:options="globalFontOptions"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">代码字体</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-select
|
||||
v-model:value="displaySettings.codeFont"
|
||||
:options="codeFontOptions"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 话题设置 -->
|
||||
<n-card title="话题设置" size="large">
|
||||
<div class="setting-section">
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">话题位置</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-radio-group v-model:value="displaySettings.topicPosition" name="topicPosition">
|
||||
<n-radio value="left">左侧</n-radio>
|
||||
<n-radio value="right">右侧</n-radio>
|
||||
</n-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">自动切换到话题</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-switch v-model:value="displaySettings.autoSwitchTopic" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">显示话题时间</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-switch v-model:value="displaySettings.showTopicTime" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">固定话题置顶</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-switch v-model:value="displaySettings.pinnedTopicsTop" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 助手设置 -->
|
||||
<n-card title="助手设置" size="large">
|
||||
<div class="setting-section">
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">
|
||||
<span class="label-text">模型图标类型</span>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<n-radio-group v-model:value="displaySettings.modelIconType" name="modelIconType">
|
||||
<n-radio value="modelIcon">模型图标</n-radio>
|
||||
<n-radio value="emoji">Emoji 表情</n-radio>
|
||||
<n-radio value="none">不显示</n-radio>
|
||||
</n-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 自定义 CSS -->
|
||||
<n-card title="自定义 CSS" size="large">
|
||||
<div class="setting-section">
|
||||
<div class="css-editor">
|
||||
<div class="css-header">
|
||||
<span>/* 这里写自定义 CSS */</span>
|
||||
<a href="#" target="_blank" class="css-help">从 cherryCss.com 获取</a>
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="displaySettings.customCSS"
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
placeholder="/* 在此添加您的自定义CSS样式 */"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, watch, onMounted } from 'vue'
|
||||
import {
|
||||
NCard,
|
||||
NRadioGroup,
|
||||
NRadioButton,
|
||||
NRadio,
|
||||
NSelect,
|
||||
NSwitch,
|
||||
NButton,
|
||||
NIcon,
|
||||
NInput,
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import {
|
||||
Sun as SunIcon,
|
||||
Moon as MoonIcon,
|
||||
DeviceDesktop as DeviceDesktopIcon,
|
||||
Check as CheckIcon,
|
||||
Refresh as RefreshIcon
|
||||
} from '@vicons/tabler'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// 显示设置数据
|
||||
const displaySettings = reactive({
|
||||
// 主题设置
|
||||
theme: 'light' as 'light' | 'dark' | 'system',
|
||||
primaryColor: '#00B96B',
|
||||
transparentWindow: true,
|
||||
|
||||
// 导航栏设置
|
||||
navPosition: 'left' as 'left' | 'top',
|
||||
|
||||
// 缩放设置
|
||||
zoomLevel: 100,
|
||||
|
||||
// 字体设置
|
||||
globalFont: 'default',
|
||||
codeFont: 'default',
|
||||
|
||||
// 话题设置
|
||||
topicPosition: 'left' as 'left' | 'right',
|
||||
autoSwitchTopic: true,
|
||||
showTopicTime: false,
|
||||
pinnedTopicsTop: false,
|
||||
|
||||
// 助手设置
|
||||
modelIconType: 'modelIcon' as 'modelIcon' | 'emoji' | 'none',
|
||||
|
||||
// 自定义 CSS
|
||||
customCSS: ''
|
||||
})
|
||||
|
||||
// 主题颜色选项
|
||||
const themeColors = [
|
||||
'#18a058', // 翠绿
|
||||
'#d03050', // 红色
|
||||
'#2080f0', // 蓝色
|
||||
'#7c3aed', // 紫色
|
||||
'#d946ef', // 品红
|
||||
'#0ea5e9', // 天蓝
|
||||
'#f59e0b', // 橙色
|
||||
'#8b5cf6', // 紫罗兰
|
||||
'#06b6d4', // 青色
|
||||
]
|
||||
|
||||
// 全局字体选项
|
||||
const globalFontOptions = [
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: 'Arial', value: 'Arial' },
|
||||
{ label: 'Helvetica', value: 'Helvetica' },
|
||||
{ label: 'Microsoft YaHei', value: 'Microsoft YaHei' },
|
||||
{ label: 'PingFang SC', value: 'PingFang SC' },
|
||||
{ label: 'Source Han Sans', value: 'Source Han Sans' }
|
||||
]
|
||||
|
||||
// 代码字体选项
|
||||
const codeFontOptions = [
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: 'Monaco', value: 'Monaco' },
|
||||
{ label: 'Menlo', value: 'Menlo' },
|
||||
{ label: 'Consolas', value: 'Consolas' },
|
||||
{ label: 'Source Code Pro', value: 'Source Code Pro' },
|
||||
{ label: 'JetBrains Mono', value: 'JetBrains Mono' },
|
||||
{ label: 'Fira Code', value: 'Fira Code' }
|
||||
]
|
||||
|
||||
// 缩放控制方法
|
||||
const decreaseZoom = () => {
|
||||
if (displaySettings.zoomLevel > 50) {
|
||||
displaySettings.zoomLevel -= 10
|
||||
applySettings()
|
||||
}
|
||||
}
|
||||
|
||||
const increaseZoom = () => {
|
||||
if (displaySettings.zoomLevel < 200) {
|
||||
displaySettings.zoomLevel += 10
|
||||
applySettings()
|
||||
}
|
||||
}
|
||||
|
||||
const resetZoom = () => {
|
||||
displaySettings.zoomLevel = 100
|
||||
applySettings()
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
const saveSettings = () => {
|
||||
try {
|
||||
localStorage.setItem('cherry-display-settings', JSON.stringify(displaySettings))
|
||||
message.success('显示设置已保存')
|
||||
} catch (error) {
|
||||
message.error('保存设置失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 加载设置
|
||||
const loadSettings = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem('cherry-display-settings')
|
||||
if (saved) {
|
||||
const settings = JSON.parse(saved)
|
||||
Object.assign(displaySettings, settings)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载显示设置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用设置
|
||||
const applySettings = () => {
|
||||
const root = document.documentElement
|
||||
|
||||
// 应用主题
|
||||
let actualTheme = displaySettings.theme
|
||||
if (displaySettings.theme === 'system') {
|
||||
actualTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
}
|
||||
root.setAttribute('data-theme', actualTheme)
|
||||
|
||||
// 应用主色调 - 修复颜色应用逻辑
|
||||
root.style.setProperty('--primary-color', displaySettings.primaryColor)
|
||||
root.style.setProperty('--n-color-primary', displaySettings.primaryColor)
|
||||
root.style.setProperty('--n-color-primary-hover', displaySettings.primaryColor + '20')
|
||||
root.style.setProperty('--n-color-primary-pressed', displaySettings.primaryColor + '40')
|
||||
|
||||
// 应用缩放
|
||||
if (typeof document !== 'undefined') {
|
||||
document.body.style.zoom = `${displaySettings.zoomLevel}%`
|
||||
}
|
||||
|
||||
// 应用字体
|
||||
if (displaySettings.globalFont !== 'default') {
|
||||
root.style.setProperty('--font-family', displaySettings.globalFont)
|
||||
} else {
|
||||
root.style.removeProperty('--font-family')
|
||||
}
|
||||
|
||||
if (displaySettings.codeFont !== 'default') {
|
||||
root.style.setProperty('--code-font-family', displaySettings.codeFont)
|
||||
} else {
|
||||
root.style.removeProperty('--code-font-family')
|
||||
}
|
||||
|
||||
// 应用自定义CSS
|
||||
let customStyleElement = document.getElementById('custom-styles')
|
||||
if (!customStyleElement) {
|
||||
customStyleElement = document.createElement('style')
|
||||
customStyleElement.id = 'custom-styles'
|
||||
document.head.appendChild(customStyleElement)
|
||||
}
|
||||
customStyleElement.textContent = displaySettings.customCSS
|
||||
|
||||
// 应用其他样式类
|
||||
root.classList.toggle('transparent-window', displaySettings.transparentWindow)
|
||||
root.classList.toggle('nav-top', displaySettings.navPosition === 'top')
|
||||
root.classList.toggle('topic-right', displaySettings.topicPosition === 'right')
|
||||
|
||||
// 保存设置
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
// 监听设置变化并自动应用
|
||||
watch(displaySettings, () => {
|
||||
applySettings()
|
||||
}, { deep: true })
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadSettings()
|
||||
applySettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.display-settings-page {
|
||||
padding: 32px;
|
||||
background: #f8fafc;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.page-header .header-info h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.page-header .header-info p {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.setting-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.setting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.label-desc {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.setting-control {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.color-picker-grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.color-option {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.color-option:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.color-option.active {
|
||||
border-color: #ffffff;
|
||||
box-shadow: 0 0 0 2px var(--primary-color);
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.zoom-value {
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.css-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.css-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.css-header span {
|
||||
color: #6b7280;
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.css-help {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.css-help:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 深色模式 */
|
||||
[data-theme="dark"] .display-settings-page {
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .page-header .header-info h1 {
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .page-header .header-info p {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .setting-item {
|
||||
border-bottom-color: #334155;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .label-text {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .label-desc {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .zoom-value {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .css-header span {
|
||||
color: #94a3b8;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user