Skip to content

Commit 65cc1d1

Browse files
committed
feat: add theme style setting
1 parent a2a9299 commit 65cc1d1

11 files changed

Lines changed: 377 additions & 235 deletions

File tree

pkg/constant/constant.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var SiteSiteDefaultValueMap = map[string]string{
1717
"slogan": "A Message Way Hosted Site",
1818
"logo": "<svg t=\"1702547210136\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"1861\" width=\"200\" height=\"200\"><path d=\"M970.091313 224.033616a14.201535 14.201535 0 0 0-14.100687-3.631838c-88.419556-13.429657-142.919111-12.953859-248.242424 23.762747-69.454869 24.217859-109.056 79.873293-135.267556 116.712728-6.692202 9.403475-18.949172 26.625293-23.77309 29.509818-5.70699-1.303273-21.929374-16.036202-31.762101-24.965172-28.626747-25.994343-64.252121-58.353778-106.767516-58.757172-0.312889-0.005172-0.627071-0.005172-0.939959-0.005171-29.62101 0-54.798222 13.434828-72.857859 38.89907-13.222788 18.651798-17.914828 37.388929-18.106182 38.176323a14.31402 14.31402 0 0 0 2.893576 12.515556 14.336 14.336 0 0 0 11.782465 5.095434l0.409858-0.024565c19.192242-1.152 59.141172-3.545212 65.807516 81.857939 4.288646 54.899071 29.742545 108.631919 69.833697 147.419798 45.499475 44.025535 106.237414 67.298263 175.650909 67.298263 98.97503 0 166.222869-24.480323 205.207272-45.015919 43.172202-22.742626 62.621737-45.918384 63.429819-46.898425a14.170505 14.170505 0 0 0 1.529535-15.874586 14.191192 14.191192 0 0 0-14.166626-7.337373c-37.924202 4.443798-92.767677 6.09099-138.666667-11.298909-7.580444-2.872889-13.878303-5.893172-18.959515-8.691071 20.273131-9.964606 51.000889-28.383677 83.621495-59.93503 34.186343-33.065374 37.722505-69.908687 40.838464-102.423273 2.297535-23.919192 4.666182-48.651636 18.500526-73.091879 28.807758-50.883232 66.645333-74.489535 74.560646-79.035475a14.201535 14.201535 0 0 0 12.135434-7.776969 14.193778 14.193778 0 0 0-2.59103-16.484849z\" fill=\"\" p-id=\"1862\"></path><path d=\"M674.834101 716.481939c-33.422222 4.90796-74.989899 16.884364-117.22602-8.102787-34.843152-20.613172-53.056646-20.993293-77.783919-18.49794-0.156444 0.015515-0.312889 0.015515-0.469334 0.034909-0.274101 0.033616-0.548202 0.060768-0.822303 0.094384-4.697212 0.487434-9.65301 1.065374-14.995394 1.62004-6.702545 0.649051-13.414141 1.19596-20.133495 1.634263-75.333818 4.102465-136.860444-5.381172-163.012525-10.404202-49.488162-9.872808-86.57196-23.921778-108.946101-33.970424-59.832889-26.868364-87.080081-56.671677-87.080081-72.989738 0-1.873455 0.384-2.222545 0.787394-2.585858 2.196687-1.974303 10.616242-6.520242 41.302626-6.004364 24.793212 0.418909 56.970343 3.858101 94.308849 7.853253 40.722101 4.348121 86.873212 9.283232 136.348444 11.686788 6.913293 0.333576 13.959758 0.625778 20.93899 0.858505 9.510788 0.307717 17.545051-7.182222 17.868283-16.707233 0.319354-9.535354-7.175758-17.550222-16.705939-17.868282a1386.24 1386.24 0 0 1-20.419233-0.839112c-48.521051-2.358303-94.161455-7.236525-134.434909-11.544565-90.868364-9.712485-139.353212-13.818828-162.333737 6.833131-8.02004 7.21196-12.262141 17.004606-12.262141 28.317737 0 0.672323 0.029737 1.348525 0.065939 2.026021l0.005172 0.100848c-0.045253 0.524929-0.071111 1.039515-0.071111 1.539879 0 41.464242 9.561212 81.661414 28.414707 119.484768 17.493333 35.088808 42.251636 66.663434 73.583192 93.927434 9.169455-6.551273 20.155475-13.604202 32.524929-20.050748 49.737697-25.921939 97.838545-29.339152 139.099798-9.884444 101.590626 47.906909 142.060606 47.837091 206.198949-0.364606 5.70699-4.287354 13.814949-3.140525 18.103596 2.567758 4.289939 5.708283 3.140525 13.813657-2.567757 18.103595-38.425859 28.877576-69.968162 41.279354-105.143596 41.280647-0.484848 0-0.969697-0.002586-1.457132-0.007758-32.760242-0.316768-69.311354-11.381657-126.16404-38.190545-51.905939-24.47903-106.215434 1.008485-139.548444 23.465374 64.611556 47.922424 146.341495 74.101657 232.704 74.101656 117.55701 0 226.204444-48.934788 292.761858-131.362909l0.002586-0.002586c18.79402-14.959192 12.028121-41.362101-23.442101-36.152889z\" fill=\"\" p-id=\"1863\"></path></svg>",
1919
"cookie_exp_days": "1",
20+
"theme_color": "blue",
2021
}
2122

2223
// 日志清理自定义

service/settings_service/settings.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package settings_service
33
import (
44
"errors"
55
"fmt"
6-
"strconv"
7-
"github.com/robfig/cron/v3"
86
"message-nest/models"
97
"message-nest/pkg/app"
108
"message-nest/pkg/constant"
119
"message-nest/pkg/util"
1210
"message-nest/service/cron_service"
11+
"strconv"
12+
13+
"github.com/robfig/cron/v3"
1314
)
1415

1516
type UserSettings struct {
@@ -38,7 +39,7 @@ func (us *UserSettings) GetUserSetting(section string) (map[string]string, error
3839
return GetSiteConfigCache(), nil
3940
}
4041
}
41-
42+
4243
result := make(map[string]string)
4344
settings, err := models.GetSettingBySection(section)
4445
if err != nil {
@@ -47,12 +48,12 @@ func (us *UserSettings) GetUserSetting(section string) (map[string]string, error
4748
for _, setting := range settings {
4849
result[setting.Key] = setting.Value
4950
}
50-
51+
5152
// 如果是site_config,更新缓存
5253
if section == constant.SiteSettingSectionName {
5354
SetSiteConfigCache(result)
5455
}
55-
56+
5657
// 版本信息单独获取
5758
if section == constant.AboutSectionName {
5859
result = constant.LatestVersion
@@ -127,6 +128,7 @@ type SiteConfig struct {
127128
Slogan string `json:"slogan" validate:"omitempty,min=1,max=50" label:"网站slogan"`
128129
Logo string `json:"logo" validate:"omitempty,min=1" label:"logo"`
129130
CookieExpDays string `json:"cookie_exp_days" validate:"omitempty,numeric,min=1,max=365" label:"cookie过期天数"`
131+
ThemeColor string `json:"theme_color" validate:"omitempty,max=20" label:"主题颜色"`
130132
}
131133

132134
type LogConfig struct {
@@ -152,15 +154,15 @@ func GetCookieExpDays() int {
152154
}
153155
}
154156
}
155-
157+
156158
// 从数据库获取
157159
setting, _ := models.GetSettingByKey(constant.SiteSettingSectionName, "cookie_exp_days")
158160
if setting.ID > 0 && setting.Value != "" {
159161
if days, err := strconv.Atoi(setting.Value); err == nil && days > 0 {
160162
return days
161163
}
162164
}
163-
165+
164166
// 返回默认值
165167
return 1
166168
}
@@ -173,6 +175,7 @@ func (us *UserSettings) ValidateDiffSetting(section string, data map[string]stri
173175
config.Slogan = data["slogan"]
174176
config.Logo = data["logo"]
175177
config.CookieExpDays = data["cookie_exp_days"]
178+
config.ThemeColor = data["theme_color"]
176179
_, errStr := app.CommonPlaygroundValid(config)
177180
return errStr
178181
}

web/src/components/Index.vue

Lines changed: 77 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { ref, computed, watch, onMounted } from 'vue'
3+
import { applyTheme, getStoredTheme } from '@/util/theme'
34
// 使用 defineAsyncComponent 导入没有默认导出的组件
45
// const Dashboard = defineAsyncComponent(() => import("@/components/pages/dashboard/Dashboard.vue"))
56
// const SendLogs = defineAsyncComponent(() => import("@/components/pages/sendLogs/SendLogs.vue"))
@@ -56,7 +57,7 @@ const applyThemeFromPreference = () => {
5657
} else {
5758
document.documentElement.classList.remove('dark')
5859
}
59-
try { localStorage.setItem('themePreference', themePreference.value) } catch {}
60+
try { localStorage.setItem('themePreference', themePreference.value) } catch { }
6061
}
6162
6263
const toggleTheme = () => {
@@ -91,23 +92,23 @@ const updateUserAccount = () => {
9192
}
9293
9394
// 更新favicon
94-
const updateFavicon = (logoSvg: string) => {
95-
if (logoSvg) {
96-
// 查找现有的favicon链接
97-
let link = document.querySelector("link[rel*='icon']") as HTMLLinkElement
98-
if (!link) {
99-
// 如果不存在,创建新的favicon链接
100-
link = document.createElement('link')
101-
link.rel = 'icon'
102-
document.head.appendChild(link)
103-
}
104-
// 将SVG转换为data URL
105-
const svgBlob = new Blob([logoSvg], { type: 'image/svg+xml' })
106-
const svgUrl = URL.createObjectURL(svgBlob)
107-
link.href = svgUrl
108-
link.type = 'image/svg+xml'
95+
const updateFavicon = (logoSvg: string) => {
96+
if (logoSvg) {
97+
// 查找现有的favicon链接
98+
let link = document.querySelector("link[rel*='icon']") as HTMLLinkElement
99+
if (!link) {
100+
// 如果不存在,创建新的favicon链接
101+
link = document.createElement('link')
102+
link.rel = 'icon'
103+
document.head.appendChild(link)
109104
}
105+
// 将SVG转换为data URL
106+
const svgBlob = new Blob([logoSvg], { type: 'image/svg+xml' })
107+
const svgUrl = URL.createObjectURL(svgBlob)
108+
link.href = svgUrl
109+
link.type = 'image/svg+xml'
110110
}
111+
}
111112
112113
// 获取本地配置
113114
const getLocalConfig = () => {
@@ -127,7 +128,11 @@ const getLocalConfig = () => {
127128
if (localConfig.logo) {
128129
updateFavicon(localConfig.logo)
129130
}
130-
131+
// 更新主题色
132+
if (localConfig.theme_color) {
133+
applyTheme(localConfig.theme_color)
134+
}
135+
131136
}
132137
} catch (error) {
133138
console.error('获取本地配置失败:', error)
@@ -152,7 +157,11 @@ const getLatestConfig = async () => {
152157
if (latestConfig.logo) {
153158
updateFavicon(latestConfig.logo)
154159
}
155-
160+
// 更新主题色
161+
if (latestConfig.theme_color) {
162+
applyTheme(latestConfig.theme_color)
163+
}
164+
156165
}
157166
} catch (error) {
158167
console.error('获取最新配置失败:', error)
@@ -181,6 +190,8 @@ const logout = () => {
181190
182191
// 监听localStorage变化
183192
onMounted(() => {
193+
// 应用存储的主题色
194+
applyTheme(getStoredTheme())
184195
// 初始化主题并监听系统主题变化
185196
applyThemeFromPreference()
186197
try {
@@ -193,21 +204,21 @@ onMounted(() => {
193204
media.addEventListener('change', handleSystemChange)
194205
} else if ((media as any).addListener) {
195206
// 兼容旧浏览器
196-
;(media as any).addListener(handleSystemChange)
207+
; (media as any).addListener(handleSystemChange)
197208
}
198-
} catch {}
209+
} catch { }
199210
200211
// 初始化用户账号信息
201212
updateUserAccount();
202-
213+
203214
// 初始化配置信息
204215
getLocalConfig();
205-
216+
206217
// 如果已认证,获取最新配置
207218
if (isAuthenticated.value) {
208219
getLatestConfig();
209220
}
210-
221+
211222
// 定期检查token状态
212223
const checkAuth = () => {
213224
const wasAuthenticated = isAuthenticated.value;
@@ -228,7 +239,7 @@ onMounted(() => {
228239
window.addEventListener('storage', checkAuth);
229240
// 定期检查(处理同一页面内的变化)
230241
const interval = setInterval(checkAuth, 1000);
231-
242+
232243
// 点击外部关闭用户菜单
233244
const handleClickOutside = (event: Event) => {
234245
const target = event.target as Element;
@@ -237,7 +248,7 @@ onMounted(() => {
237248
}
238249
};
239250
document.addEventListener('click', handleClickOutside);
240-
251+
241252
// 清理函数
242253
return () => {
243254
window.removeEventListener('storage', checkAuth);
@@ -262,7 +273,7 @@ const activeTab = ref('Dashboard');
262273
// 处理标签点击事件,跳转到对应路由
263274
const handleTabClick = (tab: TabRoute) => {
264275
activeTab.value = tab.name;
265-
276+
266277
267278
// 使用 Vue Router 进行导航,保持单页应用状态
268279
// 对于根路径,直接导航;对于其他路径,使用相对路径
@@ -331,13 +342,13 @@ const siteTitle = computed(() => {
331342
<button v-for="tab in tabRoutes" :key="tab.name" @click="handleTabClick(tab)" :class="[
332343
'relative py-1.5 px-2 text-sm font-medium transition-all duration-200 rounded-md whitespace-nowrap',
333344
activeTab === tab.name
334-
? 'text-blue-600 bg-blue-50 dark:text-blue-400 dark:bg-blue-400/10'
335-
: 'text-gray-600 hover:text-blue-600 hover:bg-gray-50 dark:text-gray-300 dark:hover:text-blue-400 dark:hover:bg-white/5'
345+
? 'text-brand bg-brand/10 dark:text-brand dark:bg-brand/10'
346+
: 'text-gray-600 hover:text-brand hover:bg-gray-50 dark:text-gray-300 dark:hover:text-brand dark:hover:bg-white/5'
336347
]">
337348
{{ tab.name }}
338349
<!-- 活动状态指示器 -->
339350
<span v-if="activeTab === tab.name"
340-
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-1 h-1 bg-blue-600 rounded-full"></span>
351+
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-1 h-1 bg-brand rounded-full"></span>
341352
</button>
342353
</div>
343354

@@ -347,21 +358,24 @@ const siteTitle = computed(() => {
347358
<TooltipProvider>
348359
<Tooltip>
349360
<TooltipTrigger>
350-
<button @click="toggleTheme" class="hidden md:inline-flex p-2 rounded-md text-gray-600 hover:text-blue-600 hover:bg-gray-50 transition-colors dark:text-gray-300 dark:hover:text-blue-400 dark:hover:bg-white/5">
361+
<button @click="toggleTheme"
362+
class="hidden md:inline-flex p-2 rounded-md text-gray-600 hover:text-brand hover:bg-gray-50 transition-colors dark:text-gray-300 dark:hover:text-brand dark:hover:bg-white/5">
351363
<MonitorIcon v-if="themePreference === 'system'" class="w-5 h-5" />
352-
<svg v-else-if="theme === 'dark'" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
353-
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
364+
<svg v-else-if="theme === 'dark'" class="w-5 h-5" viewBox="0 0 24 24" fill="none"
365+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
366+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
354367
</svg>
355-
<svg v-else class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
356-
<circle cx="12" cy="12" r="5"/>
357-
<line x1="12" y1="1" x2="12" y2="3"/>
358-
<line x1="12" y1="21" x2="12" y2="23"/>
359-
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
360-
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
361-
<line x1="1" y1="12" x2="3" y2="12"/>
362-
<line x1="21" y1="12" x2="23" y2="12"/>
363-
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
364-
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
368+
<svg v-else class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
369+
stroke-linecap="round" stroke-linejoin="round">
370+
<circle cx="12" cy="12" r="5" />
371+
<line x1="12" y1="1" x2="12" y2="3" />
372+
<line x1="12" y1="21" x2="12" y2="23" />
373+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
374+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
375+
<line x1="1" y1="12" x2="3" y2="12" />
376+
<line x1="21" y1="12" x2="23" y2="12" />
377+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
378+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
365379
</svg>
366380
</button>
367381
</TooltipTrigger>
@@ -372,34 +386,42 @@ const siteTitle = computed(() => {
372386
</TooltipProvider>
373387
<!-- 用户账号下拉菜单 -->
374388
<div class="relative user-menu-container">
375-
<button @click="toggleUserMenu" class="flex items-center space-x-2 p-2 rounded-md hover:bg-muted transition-colors dark:hover:bg-white/5">
376-
<div class="w-8 h-8 bg-blue-100 dark:bg-muted border border-border rounded-full flex items-center justify-center">
377-
<svg class="w-4 h-4 text-blue-600 dark:text-blue-300" fill="currentColor" viewBox="0 0 20 20">
378-
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd" />
389+
<button @click="toggleUserMenu"
390+
class="flex items-center space-x-2 p-2 rounded-md hover:bg-muted transition-colors dark:hover:bg-white/5">
391+
<div
392+
class="w-8 h-8 bg-brand/10 dark:bg-muted border border-border rounded-full flex items-center justify-center">
393+
<svg class="w-4 h-4 text-brand dark:text-brand" fill="currentColor" viewBox="0 0 20 20">
394+
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
395+
clip-rule="evenodd" />
379396
</svg>
380397
</div>
381398
<span class="hidden sm:block text-sm font-medium text-foreground">{{ userAccount }}</span>
382-
<svg class="w-4 h-4 text-gray-400 dark:text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
399+
<svg class="w-4 h-4 text-gray-400 dark:text-muted-foreground" fill="none" stroke="currentColor"
400+
viewBox="0 0 24 24">
383401
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
384402
</svg>
385403
</button>
386-
404+
387405
<!-- 下拉菜单 -->
388-
<div v-if="isUserMenuOpen" class="absolute right-0 mt-2 w-52 bg-card text-foreground rounded-md shadow-lg border border-border z-50">
406+
<div v-if="isUserMenuOpen"
407+
class="absolute right-0 mt-2 w-52 bg-card text-foreground rounded-md shadow-lg border border-border z-50">
389408
<div class="py-1">
390409
<div class="px-4 py-2 text-sm text-muted-foreground border-b border-border">
391410
<div class="font-medium text-foreground">{{ userAccount }}</div>
392411
<!-- <div class="text-xs">当前登录账号</div> -->
393412
</div>
394413
<!-- 移动端主题切换入口 -->
395-
<button @click="toggleTheme" class="md:hidden w-full text-left px-4 py-2 text-sm hover:bg-muted dark:hover:bg-white/5 flex items-center justify-between">
414+
<button @click="toggleTheme"
415+
class="md:hidden w-full text-left px-4 py-2 text-sm hover:bg-muted dark:hover:bg-white/5 flex items-center justify-between">
396416
<span class="truncate">外观</span>
397417
<span class="text-xs text-muted-foreground flex-shrink-0">{{ themeLabel }}</span>
398418
</button>
399-
<button @click="logout" class="w-full text-left px-4 py-2 text-sm text-destructive hover:bg-muted dark:hover:bg-white/5 transition-colors">
419+
<button @click="logout"
420+
class="w-full text-left px-4 py-2 text-sm text-destructive hover:bg-muted dark:hover:bg-white/5 transition-colors">
400421
<div class="flex items-center space-x-2">
401422
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
402-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
423+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
424+
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
403425
</svg>
404426
<span>退出登录</span>
405427
</div>
@@ -410,7 +432,7 @@ const siteTitle = computed(() => {
410432

411433
<!-- 移动端菜单按钮 -->
412434
<button @click="toggleMobileMenu"
413-
class="md:hidden p-2 rounded-md text-gray-600 hover:text-blue-600 hover:bg-gray-50 transition-colors">
435+
class="md:hidden p-2 rounded-md text-gray-600 hover:text-brand hover:bg-gray-50 transition-colors">
414436
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
415437
<path v-if="!isMobileMenuOpen" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
416438
d="M4 6h16M4 12h16M4 18h16" />
@@ -427,8 +449,8 @@ const siteTitle = computed(() => {
427449
:class="[
428450
'text-left py-3 px-4 text-sm font-medium transition-all duration-200 rounded-md',
429451
activeTab === tab.name
430-
? 'text-blue-600 bg-blue-50 dark:text-blue-400 dark:bg-blue-400/10'
431-
: 'text-muted-foreground hover:text-blue-600 hover:bg-muted dark:hover:bg-white/5'
452+
? 'text-brand bg-brand/10 dark:text-brand dark:bg-brand/10'
453+
: 'text-muted-foreground hover:text-brand hover:bg-muted dark:hover:bg-white/5'
432454
]">
433455
{{ tab.name }}
434456
</button>

0 commit comments

Comments
 (0)