635 lines
16 KiB
Vue
635 lines
16 KiB
Vue
<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)
|
|
|
|
// 应用主色调 - 修复颜色应用逻辑
|
|
const primaryColor = displaySettings.primaryColor
|
|
root.style.setProperty('--primary-color', primaryColor)
|
|
|
|
// Naive UI 主题变量
|
|
root.style.setProperty('--n-color-primary', primaryColor)
|
|
root.style.setProperty('--n-color-primary-hover', primaryColor + 'CC') // 80% 透明度
|
|
root.style.setProperty('--n-color-primary-pressed', primaryColor + '99') // 60% 透明度
|
|
root.style.setProperty('--n-color-primary-suppl', primaryColor)
|
|
|
|
// 计算颜色变体
|
|
const hexToRgb = (hex: string) => {
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
return result ? {
|
|
r: parseInt(result[1], 16),
|
|
g: parseInt(result[2], 16),
|
|
b: parseInt(result[3], 16)
|
|
} : null
|
|
}
|
|
|
|
const rgb = hexToRgb(primaryColor)
|
|
if (rgb) {
|
|
root.style.setProperty('--n-color-primary-hover', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.8)`)
|
|
root.style.setProperty('--n-color-primary-pressed', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.9)`)
|
|
root.style.setProperty('--n-border-color-primary', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.3)`)
|
|
}
|
|
|
|
// 应用缩放
|
|
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()
|
|
// 触发主题更新事件
|
|
window.dispatchEvent(new CustomEvent('theme-color-changed', {
|
|
detail: displaySettings.primaryColor
|
|
}))
|
|
}, { 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>
|