mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
frontend: add dark mode support
This commit is contained in:
+13
-2
@@ -5,14 +5,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from 'vue'
|
import { onMounted, computed } from 'vue'
|
||||||
import { ConfigProvider as AConfigProvider } from 'ant-design-vue'
|
import { ConfigProvider as AConfigProvider } from 'ant-design-vue'
|
||||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import antdTheme from './antd-theme'
|
import getAntdTheme from './antd-theme'
|
||||||
|
import { useTheme, initTheme, watchSystemTheme } from '@/composables/useTheme'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
// 初始化主题(全局)
|
||||||
|
initTheme()
|
||||||
|
watchSystemTheme()
|
||||||
|
|
||||||
|
// 使用主题
|
||||||
|
const { isDark } = useTheme()
|
||||||
|
|
||||||
|
// 动态生成 Ant Design 主题
|
||||||
|
const antdTheme = computed(() => getAntdTheme(isDark.value))
|
||||||
|
|
||||||
// 应用启动时验证 Token
|
// 应用启动时验证 Token
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (authStore.isAuthenticated) {
|
if (authStore.isAuthenticated) {
|
||||||
|
|||||||
+116
-8
@@ -1,14 +1,18 @@
|
|||||||
|
import { theme } from 'ant-design-vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ant Design Vue 主题配置
|
* Ant Design Vue 主题配置
|
||||||
* 匹配现有 Material Design 3 色彩系统
|
* 匹配现有 Material Design 3 色彩系统
|
||||||
|
* @param {boolean} isDark - 是否为暗黑模式
|
||||||
*/
|
*/
|
||||||
export default {
|
export default function getAntdTheme(isDark = false) {
|
||||||
|
return {
|
||||||
token: {
|
token: {
|
||||||
// 主色调 - 绿色(与 MD3 primary 保持一致)
|
// 主色调 - 绿色(与 MD3 primary 保持一致)
|
||||||
colorPrimary: '#4caf50',
|
colorPrimary: isDark ? '#81c784' : '#4caf50',
|
||||||
|
|
||||||
// 成功色
|
// 成功色
|
||||||
colorSuccess: '#4caf50',
|
colorSuccess: isDark ? '#81c784' : '#4caf50',
|
||||||
|
|
||||||
// 警告色
|
// 警告色
|
||||||
colorWarning: '#ff9800',
|
colorWarning: '#ff9800',
|
||||||
@@ -17,7 +21,27 @@ export default {
|
|||||||
colorError: '#f56c6c',
|
colorError: '#f56c6c',
|
||||||
|
|
||||||
// 信息色 - 蓝色(与 MD3 secondary 保持一致)
|
// 信息色 - 蓝色(与 MD3 secondary 保持一致)
|
||||||
colorInfo: '#2196f3',
|
colorInfo: isDark ? '#64b5f6' : '#2196f3',
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
colorBgBase: isDark ? '#121212' : '#ffffff',
|
||||||
|
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
colorBgElevated: isDark ? '#2c2c2e' : '#ffffff',
|
||||||
|
colorBgLayout: isDark ? '#121212' : '#fafafa',
|
||||||
|
colorBgSpotlight: isDark ? '#2c2c2e' : '#ffffff',
|
||||||
|
|
||||||
|
// 文字色
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorTextSecondary: isDark ? '#a0a0a3' : '#64748b',
|
||||||
|
colorTextTertiary: isDark ? '#808083' : '#94a3b8',
|
||||||
|
colorTextQuaternary: isDark ? '#606063' : '#cbd5e1',
|
||||||
|
|
||||||
|
// 边框色
|
||||||
|
colorBorder: isDark ? '#3a3a3c' : '#e5e7eb',
|
||||||
|
colorBorderSecondary: isDark ? '#2c2c2e' : '#f3f4f6',
|
||||||
|
|
||||||
|
// 分割线颜色
|
||||||
|
colorSplit: isDark ? '#3a3a3c' : '#e5e7eb',
|
||||||
|
|
||||||
// 边框圆角 - 与 Material Design 3 一致
|
// 边框圆角 - 与 Material Design 3 一致
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
@@ -26,21 +50,31 @@ export default {
|
|||||||
fontFamily: "'Inter', 'Segoe UI', 'Roboto', system-ui, -apple-system, sans-serif",
|
fontFamily: "'Inter', 'Segoe UI', 'Roboto', system-ui, -apple-system, sans-serif",
|
||||||
|
|
||||||
// 链接色
|
// 链接色
|
||||||
colorLink: '#2196f3',
|
colorLink: isDark ? '#64b5f6' : '#2196f3',
|
||||||
|
colorLinkHover: isDark ? '#90caf9' : '#1976d2',
|
||||||
|
colorLinkActive: isDark ? '#42a5f5' : '#1565c0',
|
||||||
|
|
||||||
// 字体大小
|
// 字体大小
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
||||||
// 行高
|
// 行高
|
||||||
lineHeight: 1.5715,
|
lineHeight: 1.5715,
|
||||||
|
|
||||||
|
// 控制组件高度
|
||||||
|
controlHeight: 40,
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
// Card 组件定制
|
// Card 组件定制
|
||||||
Card: {
|
Card: {
|
||||||
borderRadiusLG: 16,
|
borderRadiusLG: 16,
|
||||||
boxShadowTertiary: '0 1px 3px 1px rgba(0, 0, 0, 0.08)',
|
boxShadowTertiary: isDark
|
||||||
|
? '0 1px 3px 1px rgba(0, 0, 0, 0.5)'
|
||||||
|
: '0 1px 3px 1px rgba(0, 0, 0, 0.08)',
|
||||||
paddingLG: 24,
|
paddingLG: 24,
|
||||||
|
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
colorBorderSecondary: isDark ? '#3a3a3c' : '#f0f0f0',
|
||||||
|
colorTextHeading: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Button 组件定制
|
// Button 组件定制
|
||||||
@@ -48,30 +82,104 @@ export default {
|
|||||||
borderRadius: 24, // 圆角按钮,类似 MD3
|
borderRadius: 24, // 圆角按钮,类似 MD3
|
||||||
controlHeight: 40,
|
controlHeight: 40,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorBgContainer: isDark ? '#2c2c2e' : '#ffffff',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Input 组件定制
|
// Input 组件定制
|
||||||
Input: {
|
Input: {
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
controlHeight: 40,
|
controlHeight: 40,
|
||||||
|
colorBgContainer: isDark ? '#2c2c2e' : '#ffffff',
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorTextPlaceholder: isDark ? '#808083' : '#94a3b8',
|
||||||
|
colorBorder: isDark ? '#3a3a3c' : '#e5e7eb',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Select 组件定制
|
||||||
|
Select: {
|
||||||
|
borderRadius: 12,
|
||||||
|
controlHeight: 40,
|
||||||
|
colorBgContainer: isDark ? '#2c2c2e' : '#ffffff',
|
||||||
|
colorBgElevated: isDark ? '#2c2c2e' : '#ffffff',
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorTextPlaceholder: isDark ? '#808083' : '#94a3b8',
|
||||||
|
colorBorder: isDark ? '#3a3a3c' : '#e5e7eb',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Modal 组件定制
|
// Modal 组件定制
|
||||||
Modal: {
|
Modal: {
|
||||||
borderRadiusLG: 16,
|
borderRadiusLG: 16,
|
||||||
|
colorBgElevated: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorTextHeading: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Table 组件定制
|
// Table 组件定制
|
||||||
Table: {
|
Table: {
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
colorFillAlter: isDark ? '#2c2c2e' : '#f5f7fa',
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorTextHeading: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorBorderSecondary: isDark ? '#3a3a3c' : '#f0f0f0',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Tabs 组件定制
|
// Tabs 组件定制
|
||||||
Tabs: {
|
Tabs: {
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Menu 组件定制
|
||||||
|
Menu: {
|
||||||
|
colorItemBg: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
colorItemBgHover: isDark ? '#2c2c2e' : '#f5f7fa',
|
||||||
|
colorItemBgSelected: isDark ? '#2c2c2e' : '#e8f5e9',
|
||||||
|
colorItemText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorItemTextSelected: isDark ? '#81c784' : '#4caf50',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dropdown 组件定制
|
||||||
|
Dropdown: {
|
||||||
|
colorBgElevated: isDark ? '#2c2c2e' : '#ffffff',
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Descriptions 组件定制
|
||||||
|
Descriptions: {
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorTextSecondary: isDark ? '#a0a0a3' : '#64748b',
|
||||||
|
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
colorFillAlter: isDark ? '#2c2c2e' : '#f5f7fa',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Alert 组件定制
|
||||||
|
Alert: {
|
||||||
|
borderRadiusLG: 12,
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Drawer 组件定制
|
||||||
|
Drawer: {
|
||||||
|
colorBgElevated: isDark ? '#1c1c1e' : '#ffffff',
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Form 组件定制
|
||||||
|
Form: {
|
||||||
|
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
colorTextHeading: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Empty 组件定制
|
||||||
|
Empty: {
|
||||||
|
colorTextDescription: isDark ? '#a0a0a3' : '#94a3b8',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// 算法配置
|
// 算法配置 - 使用 Ant Design 内置的暗黑算法
|
||||||
algorithm: [],
|
algorithm: isDark ? [theme.darkAlgorithm] : [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ onMounted(() => {
|
|||||||
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
|
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .layout-container {
|
||||||
|
background: linear-gradient(135deg, #1a1a1a 0%, #0f0f0f 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sticky top-0 z-50 glass-effect border-b border-gray-200/50 shadow-md3-2">
|
<div class="sticky top-0 z-50 glass-effect border-b border-gray-200/50 dark:border-gray-700/50 shadow-md3-2">
|
||||||
<nav class="max-w-7xl mx-auto px-6 py-4">
|
<nav class="max-w-7xl mx-auto px-6 py-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<!-- Logo and Brand -->
|
<!-- Logo and Brand -->
|
||||||
@@ -23,8 +23,8 @@
|
|||||||
:class="[
|
:class="[
|
||||||
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary-100 text-primary-700'
|
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400'
|
||||||
: 'text-gray-700 hover:bg-gray-100'
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
@@ -44,8 +44,8 @@
|
|||||||
:class="[
|
:class="[
|
||||||
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary-100 text-primary-700'
|
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400'
|
||||||
: 'text-gray-700 hover:bg-gray-100'
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
:class="[
|
:class="[
|
||||||
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary-100 text-primary-700'
|
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400'
|
||||||
: 'text-gray-700 hover:bg-gray-100'
|
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
<a
|
<a
|
||||||
:class="[
|
:class="[
|
||||||
'px-4 py-2 rounded-full font-medium transition-all flex items-center space-x-2 cursor-pointer',
|
'px-4 py-2 rounded-full font-medium transition-all flex items-center space-x-2 cursor-pointer',
|
||||||
isAdminPath ? 'bg-secondary-100 text-secondary-700' : 'text-gray-700 hover:bg-gray-100'
|
isAdminPath ? 'bg-secondary-100 dark:bg-secondary-900/30 text-secondary-700 dark:text-secondary-400' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<SettingOutlined />
|
<SettingOutlined />
|
||||||
@@ -117,43 +117,57 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Menu & Mobile Hamburger -->
|
<!-- User Menu & Mobile Hamburger -->
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-2 md:space-x-4">
|
||||||
<!-- Token Status Indicator (Desktop) -->
|
<!-- Token Status Indicator (Desktop & Mobile) -->
|
||||||
<a-tooltip v-if="!isMobile && showTokenStatus" :title="tokenStatusTooltip">
|
<a-tooltip v-if="showTokenStatus" :title="tokenStatusTooltip">
|
||||||
<div
|
<div
|
||||||
class="px-3 py-1.5 rounded-full cursor-pointer transition-all hover:bg-gray-100 flex items-center space-x-2"
|
class="px-2 md:px-3 py-1.5 rounded-full cursor-pointer transition-all hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center space-x-1 md:space-x-2"
|
||||||
@click="handleTokenStatusClick"
|
@click="handleTokenStatusClick"
|
||||||
>
|
>
|
||||||
<a-badge :status="tokenBadgeStatus" />
|
<a-badge :status="tokenBadgeStatus" />
|
||||||
<ClockCircleOutlined :class="tokenIconClass" />
|
<ClockCircleOutlined :class="[tokenIconClass, 'text-sm md:text-base']" />
|
||||||
<span class="text-sm">{{ tokenBadgeText }}</span>
|
<span class="text-xs md:text-sm hidden sm:inline">{{ tokenBadgeText }}</span>
|
||||||
<!-- 过期时显示刷新按钮 -->
|
<!-- 过期时显示刷新按钮(响应式设计) -->
|
||||||
<a-button
|
<a-button
|
||||||
v-if="remainingMinutes !== null && remainingMinutes < 0"
|
v-if="remainingMinutes !== null && remainingMinutes < 0"
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
|
class="!text-xs !px-2 md:!px-3"
|
||||||
@click.stop="handleRefreshToken"
|
@click.stop="handleRefreshToken"
|
||||||
>
|
>
|
||||||
刷新
|
<span class="hidden sm:inline">刷新</span>
|
||||||
|
<ReloadOutlined class="sm:hidden" />
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|
||||||
|
<!-- Theme Toggle Button -->
|
||||||
|
<a-tooltip :title="isDark ? '切换到亮色模式' : '切换到暗黑模式'">
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
class="!p-2 !flex !items-center !justify-center hover:!bg-gray-100 dark:hover:!bg-gray-700 transition-all"
|
||||||
|
@click="toggleTheme"
|
||||||
|
>
|
||||||
|
<BulbFilled v-if="isDark" class="text-lg text-yellow-400" />
|
||||||
|
<BulbOutlined v-else class="text-lg text-gray-700 dark:text-gray-300" />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
<!-- Desktop User Menu -->
|
<!-- Desktop User Menu -->
|
||||||
<a-dropdown v-if="!isMobile" :trigger="['hover']">
|
<a-dropdown v-if="!isMobile" :trigger="['hover']">
|
||||||
<a class="flex items-center space-x-3 px-4 py-2 rounded-full hover:bg-gray-100 transition-all cursor-pointer">
|
<a class="flex items-center space-x-3 px-4 py-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all cursor-pointer">
|
||||||
<a-avatar :style="{ backgroundColor: '#f56a00' }">
|
<a-avatar :style="{ backgroundColor: '#f56a00' }">
|
||||||
{{ userInitial }}
|
{{ userInitial }}
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
<span class="hidden md:block font-medium text-gray-700">{{ authStore.user?.alias || '用户' }}</span>
|
<span class="hidden md:block font-medium text-gray-700 dark:text-gray-200">{{ authStore.user?.alias || '用户' }}</span>
|
||||||
<DownOutlined class="text-xs text-gray-500" />
|
<DownOutlined class="text-xs text-gray-500 dark:text-gray-400" />
|
||||||
</a>
|
</a>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item key="info" disabled>
|
<a-menu-item key="info" disabled>
|
||||||
<div class="px-2 py-1">
|
<div class="px-2 py-1">
|
||||||
<p class="text-sm font-medium text-gray-900">{{ authStore.user?.alias }}</p>
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ authStore.user?.alias }}</p>
|
||||||
<p class="text-xs text-gray-500 mt-1">{{ authStore.isAdmin ? '管理员' : '普通用户' }}</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ authStore.isAdmin ? '管理员' : '普通用户' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-divider />
|
<a-menu-divider />
|
||||||
@@ -176,7 +190,7 @@
|
|||||||
@click="drawerVisible = true"
|
@click="drawerVisible = true"
|
||||||
class="!p-2"
|
class="!p-2"
|
||||||
>
|
>
|
||||||
<MenuOutlined class="text-xl" />
|
<MenuOutlined class="text-xl text-gray-700 dark:text-gray-300" />
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -277,6 +291,7 @@ import { useAuthStore } from '@/stores/auth'
|
|||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useTokenMonitor } from '@/composables/useTokenMonitor'
|
import { useTokenMonitor } from '@/composables/useTokenMonitor'
|
||||||
import { useBreakpoint } from '@/composables/useBreakpoint'
|
import { useBreakpoint } from '@/composables/useBreakpoint'
|
||||||
|
import { useTheme } from '@/composables/useTheme'
|
||||||
import { Modal, message } from 'ant-design-vue'
|
import { Modal, message } from 'ant-design-vue'
|
||||||
import QRCodeModal from './QRCodeModal.vue'
|
import QRCodeModal from './QRCodeModal.vue'
|
||||||
import {
|
import {
|
||||||
@@ -293,6 +308,9 @@ import {
|
|||||||
DownOutlined,
|
DownOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
|
BulbOutlined,
|
||||||
|
BulbFilled,
|
||||||
|
ReloadOutlined,
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -301,6 +319,7 @@ const authStore = useAuthStore()
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { isMobile } = useBreakpoint()
|
const { isMobile } = useBreakpoint()
|
||||||
const { getRemainingMinutes, tokenStatus } = useTokenMonitor()
|
const { getRemainingMinutes, tokenStatus } = useTokenMonitor()
|
||||||
|
const { isDark, toggleTheme } = useTheme()
|
||||||
|
|
||||||
const drawerVisible = ref(false)
|
const drawerVisible = ref(false)
|
||||||
const qrcodeModalVisible = ref(false)
|
const qrcodeModalVisible = ref(false)
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const THEME_STORAGE_KEY = 'checkin-app-theme'
|
||||||
|
|
||||||
|
// 全局主题状态(单例模式)
|
||||||
|
const theme = ref('light')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用主题到 DOM
|
||||||
|
*/
|
||||||
|
const applyTheme = (newTheme) => {
|
||||||
|
const html = document.documentElement
|
||||||
|
|
||||||
|
if (newTheme === 'dark') {
|
||||||
|
html.classList.add('dark')
|
||||||
|
} else {
|
||||||
|
html.classList.remove('dark')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化主题
|
||||||
|
* 优先级: localStorage > 系统偏好 > 默认亮色
|
||||||
|
*/
|
||||||
|
export const initTheme = () => {
|
||||||
|
// 1. 尝试从 localStorage 读取
|
||||||
|
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY)
|
||||||
|
if (savedTheme === 'light' || savedTheme === 'dark') {
|
||||||
|
theme.value = savedTheme
|
||||||
|
applyTheme(savedTheme)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检测系统偏好
|
||||||
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
|
theme.value = 'dark'
|
||||||
|
applyTheme('dark')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 默认亮色
|
||||||
|
theme.value = 'light'
|
||||||
|
applyTheme('light')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听系统主题变化
|
||||||
|
*/
|
||||||
|
export const watchSystemTheme = () => {
|
||||||
|
if (!window.matchMedia) return
|
||||||
|
|
||||||
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
// 仅在用户未手动设置主题时才跟随系统
|
||||||
|
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY)
|
||||||
|
if (!savedTheme) {
|
||||||
|
const systemTheme = e.matches ? 'dark' : 'light'
|
||||||
|
theme.value = systemTheme
|
||||||
|
applyTheme(systemTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaQuery.addEventListener('change', handleChange)
|
||||||
|
|
||||||
|
// 返回清理函数
|
||||||
|
return () => mediaQuery.removeEventListener('change', handleChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题管理 Composable
|
||||||
|
* 支持亮色/暗色模式切换,并持久化到 localStorage
|
||||||
|
*/
|
||||||
|
export function useTheme() {
|
||||||
|
/**
|
||||||
|
* 切换主题
|
||||||
|
*/
|
||||||
|
const toggleTheme = () => {
|
||||||
|
const newTheme = theme.value === 'light' ? 'dark' : 'light'
|
||||||
|
theme.value = newTheme
|
||||||
|
applyTheme(newTheme)
|
||||||
|
localStorage.setItem(THEME_STORAGE_KEY, newTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置指定主题
|
||||||
|
*/
|
||||||
|
const setTheme = (newTheme) => {
|
||||||
|
if (newTheme !== 'light' && newTheme !== 'dark') {
|
||||||
|
console.warn(`Invalid theme: ${newTheme}. Using 'light' instead.`)
|
||||||
|
newTheme = 'light'
|
||||||
|
}
|
||||||
|
|
||||||
|
theme.value = newTheme
|
||||||
|
applyTheme(newTheme)
|
||||||
|
localStorage.setItem(THEME_STORAGE_KEY, newTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
toggleTheme,
|
||||||
|
setTheme,
|
||||||
|
isDark: computed(() => theme.value === 'dark'),
|
||||||
|
isLight: computed(() => theme.value === 'light')
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-10
@@ -4,9 +4,6 @@ import { createPinia } from 'pinia'
|
|||||||
// Ant Design Vue
|
// Ant Design Vue
|
||||||
import Antd from 'ant-design-vue'
|
import Antd from 'ant-design-vue'
|
||||||
import 'ant-design-vue/dist/reset.css'
|
import 'ant-design-vue/dist/reset.css'
|
||||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
|
||||||
import { ConfigProvider } from 'ant-design-vue'
|
|
||||||
import antdTheme from './antd-theme'
|
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
@@ -18,13 +15,7 @@ const pinia = createPinia()
|
|||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
// Ant Design Vue with custom theme
|
// Ant Design Vue
|
||||||
app.use(Antd)
|
app.use(Antd)
|
||||||
|
|
||||||
// Configure Ant Design globally
|
|
||||||
app.config.globalProperties.$antdConfig = {
|
|
||||||
theme: antdTheme,
|
|
||||||
locale: zhCN,
|
|
||||||
}
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
+363
-16
@@ -14,7 +14,7 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
|
|
||||||
/* Material Design 3 color tokens */
|
/* Material Design 3 color tokens - Light Mode */
|
||||||
--md-sys-color-primary: #4caf50;
|
--md-sys-color-primary: #4caf50;
|
||||||
--md-sys-color-on-primary: #ffffff;
|
--md-sys-color-on-primary: #ffffff;
|
||||||
--md-sys-color-secondary: #2196f3;
|
--md-sys-color-secondary: #2196f3;
|
||||||
@@ -25,6 +25,21 @@
|
|||||||
--md-sys-color-on-background: #1c1b1f;
|
--md-sys-color-on-background: #1c1b1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark Mode Colors */
|
||||||
|
.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
|
||||||
|
/* Material Design 3 color tokens - Dark Mode */
|
||||||
|
--md-sys-color-primary: #81c784;
|
||||||
|
--md-sys-color-on-primary: #1b5e20;
|
||||||
|
--md-sys-color-secondary: #64b5f6;
|
||||||
|
--md-sys-color-on-secondary: #0d47a1;
|
||||||
|
--md-sys-color-surface: #1c1c1e;
|
||||||
|
--md-sys-color-on-surface: #e5e5e7;
|
||||||
|
--md-sys-color-background: #121212;
|
||||||
|
--md-sys-color-on-background: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -41,6 +56,11 @@
|
|||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body {
|
||||||
|
background: linear-gradient(135deg, #1a1a1a 0%, #0f0f0f 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
@@ -53,7 +73,7 @@
|
|||||||
@layer components {
|
@layer components {
|
||||||
/* Material Design 3 Card */
|
/* Material Design 3 Card */
|
||||||
.md3-card {
|
.md3-card {
|
||||||
@apply bg-white rounded-md3 shadow-md3-2 overflow-hidden transition-all duration-300;
|
@apply bg-white dark:bg-gray-800 rounded-md3 shadow-md3-2 overflow-hidden transition-all duration-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md3-card:hover {
|
.md3-card:hover {
|
||||||
@@ -61,7 +81,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.md3-card-elevated {
|
.md3-card-elevated {
|
||||||
@apply bg-white rounded-md3-lg shadow-md3-3;
|
@apply bg-white dark:bg-gray-800 rounded-md3-lg shadow-md3-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Material Design 3 Button */
|
/* Material Design 3 Button */
|
||||||
@@ -71,25 +91,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.md3-button-filled {
|
.md3-button-filled {
|
||||||
@apply md3-button bg-primary-600 text-white hover:bg-primary-700 hover:shadow-md3-2;
|
@apply md3-button bg-primary-600 dark:bg-primary-500 text-white hover:bg-primary-700 dark:hover:bg-primary-600 hover:shadow-md3-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md3-button-outlined {
|
.md3-button-outlined {
|
||||||
@apply md3-button border-2 border-primary-600 text-primary-600 hover:bg-primary-50;
|
@apply md3-button border-2 border-primary-600 dark:border-primary-400 text-primary-600 dark:text-primary-400 hover:bg-primary-50 dark:hover:bg-gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md3-button-text {
|
.md3-button-text {
|
||||||
@apply md3-button text-primary-600 hover:bg-primary-50;
|
@apply md3-button text-primary-600 dark:text-primary-400 hover:bg-primary-50 dark:hover:bg-gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fluent Design elements */
|
/* Fluent Design elements */
|
||||||
.fluent-card {
|
.fluent-card {
|
||||||
@apply bg-white/80 backdrop-blur-xl rounded-lg border border-gray-200/50 shadow-lg;
|
@apply bg-white/80 dark:bg-gray-800/80 backdrop-blur-xl rounded-lg border border-gray-200/50 dark:border-gray-700/50 shadow-lg;
|
||||||
@apply transition-all duration-300 hover:shadow-xl hover:border-gray-300/50;
|
@apply transition-all duration-300 hover:shadow-xl hover:border-gray-300/50 dark:hover:border-gray-600/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fluent-acrylic {
|
.fluent-acrylic {
|
||||||
@apply bg-white/70 backdrop-blur-2xl backdrop-saturate-150;
|
@apply bg-white/70 dark:bg-gray-800/70 backdrop-blur-2xl backdrop-saturate-150;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status badges */
|
/* Status badges */
|
||||||
@@ -98,24 +118,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-success {
|
.status-success {
|
||||||
@apply status-badge bg-green-100 text-green-800;
|
@apply status-badge bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-warning {
|
.status-warning {
|
||||||
@apply status-badge bg-yellow-100 text-yellow-800;
|
@apply status-badge bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-error {
|
.status-error {
|
||||||
@apply status-badge bg-red-100 text-red-800;
|
@apply status-badge bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-info {
|
.status-info {
|
||||||
@apply status-badge bg-blue-100 text-blue-800;
|
@apply status-badge bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loading skeleton */
|
/* Loading skeleton */
|
||||||
.skeleton {
|
.skeleton {
|
||||||
@apply animate-pulse bg-gray-200 rounded;
|
@apply animate-pulse bg-gray-200 dark:bg-gray-700 rounded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,11 +146,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.glass-effect {
|
.glass-effect {
|
||||||
@apply bg-white/60 backdrop-blur-md backdrop-saturate-150;
|
@apply bg-white/60 dark:bg-gray-800/60 backdrop-blur-md backdrop-saturate-150;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gradient {
|
.text-gradient {
|
||||||
@apply bg-gradient-to-r from-primary-600 to-secondary-600 bg-clip-text text-transparent;
|
@apply bg-gradient-to-r from-primary-600 to-secondary-600 dark:from-primary-400 dark:to-secondary-400 bg-clip-text text-transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +254,12 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-thead > tr > th {
|
||||||
|
background: #2c2c2e;
|
||||||
|
color: #e5e5e7;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ant Design Tabs */
|
/* Ant Design Tabs */
|
||||||
.ant-tabs {
|
.ant-tabs {
|
||||||
color: var(--md-sys-color-on-surface);
|
color: var(--md-sys-color-on-surface);
|
||||||
@@ -264,15 +290,34 @@
|
|||||||
background: #f5f7fa;
|
background: #f5f7fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .ant-descriptions-bordered .ant-descriptions-item-label {
|
||||||
|
background: #2c2c2e;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-descriptions-item-label,
|
||||||
|
.dark .ant-descriptions-item-content {
|
||||||
|
color: #e5e5e7;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ant Design Statistic */
|
/* Ant Design Statistic */
|
||||||
.ant-statistic-title {
|
.ant-statistic-title {
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .ant-statistic-title {
|
||||||
|
color: #a0a0a3;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-statistic-content {
|
.ant-statistic-content {
|
||||||
color: var(--md-sys-color-on-surface);
|
color: var(--md-sys-color-on-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .ant-statistic-content {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ant Design Drawer */
|
/* Ant Design Drawer */
|
||||||
.ant-drawer-content {
|
.ant-drawer-content {
|
||||||
border-radius: 16px 0 0 16px;
|
border-radius: 16px 0 0 16px;
|
||||||
@@ -287,6 +332,308 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode support for common elements */
|
||||||
|
.dark .ant-card {
|
||||||
|
background: #1c1c1e;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-card-head {
|
||||||
|
color: #e5e5e7;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-card-body {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-selector {
|
||||||
|
background: #2c2c2e !important;
|
||||||
|
border-color: #3a3a3c !important;
|
||||||
|
color: #e5e5e7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-selection-item {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-arrow {
|
||||||
|
color: #a0a0a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-input {
|
||||||
|
background: #2c2c2e;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-input::placeholder {
|
||||||
|
color: #808083;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-content {
|
||||||
|
background: #1c1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-header {
|
||||||
|
background: #1c1c1e;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-title {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-modal-body {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table {
|
||||||
|
background: #1c1c1e;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-tbody > tr > td {
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-table-tbody > tr:hover > td {
|
||||||
|
background: #2c2c2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-pagination-item {
|
||||||
|
background: #2c2c2e;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-pagination-item a {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-pagination-item:hover {
|
||||||
|
border-color: #81c784;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-pagination-item:hover a {
|
||||||
|
color: #81c784;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-empty-description {
|
||||||
|
color: #a0a0a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-form-item-label > label {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-checkbox-wrapper {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-radio-wrapper {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-divider {
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown 暗黑模式 */
|
||||||
|
.dark .ant-dropdown-menu {
|
||||||
|
background: #2c2c2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-dropdown-menu-item {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-dropdown-menu-item:hover {
|
||||||
|
background: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用文本颜色 - 暗黑模式 */
|
||||||
|
.dark p,
|
||||||
|
.dark span,
|
||||||
|
.dark div {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .hint,
|
||||||
|
.dark .card-header {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select dropdown 暗黑模式 */
|
||||||
|
.dark .ant-select-dropdown {
|
||||||
|
background: #2c2c2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-item {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-item:hover {
|
||||||
|
background: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-item-option-selected {
|
||||||
|
background: #2c2c2e;
|
||||||
|
color: #81c784;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drawer 暗黑模式 */
|
||||||
|
.dark .ant-drawer-content {
|
||||||
|
background: #1c1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-drawer-header {
|
||||||
|
background: #1c1c1e;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-drawer-title {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-drawer-body {
|
||||||
|
background: #1c1c1e;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用文本和标签颜色 */
|
||||||
|
.dark .ant-tag {
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn-text {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-btn-text:hover {
|
||||||
|
background: #2c2c2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-skeleton-content .ant-skeleton-title {
|
||||||
|
background: linear-gradient(90deg, #2c2c2e 25%, #3a3a3c 37%, #2c2c2e 63%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-skeleton-content .ant-skeleton-paragraph > li {
|
||||||
|
background: linear-gradient(90deg, #2c2c2e 25%, #3a3a3c 37%, #2c2c2e 63%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message 和 Notification */
|
||||||
|
.dark .ant-message-notice-content {
|
||||||
|
background: #2c2c2e;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-notification-notice {
|
||||||
|
background: #2c2c2e;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spin loading */
|
||||||
|
.dark .ant-spin-dot-item {
|
||||||
|
background: #81c784;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip */
|
||||||
|
.ant-tooltip-inner {
|
||||||
|
background: rgba(255, 255, 255, 0.95) !important;
|
||||||
|
color: #1c1b1f !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
min-height: 32px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tooltip-inner {
|
||||||
|
background: rgba(50, 50, 50, 0.95) !important;
|
||||||
|
color: #e5e5e7 !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tooltip-arrow-content {
|
||||||
|
background: rgba(255, 255, 255, 0.95) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .ant-tooltip-arrow-content {
|
||||||
|
background: rgba(50, 50, 50, 0.95) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用标题和文本颜色 */
|
||||||
|
.dark h1,
|
||||||
|
.dark h2,
|
||||||
|
.dark h3,
|
||||||
|
.dark h4,
|
||||||
|
.dark h5,
|
||||||
|
.dark h6 {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用容器和组件类 */
|
||||||
|
.dark .card-header,
|
||||||
|
.dark .hint,
|
||||||
|
.dark .label {
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .hint {
|
||||||
|
color: #a0a0a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 亮色模式下的通用样式 */
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1c1b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Material Design 3 卡片 - 暗黑模式统一样式 */
|
||||||
|
.dark .md3-card,
|
||||||
|
.dark .md3-card-elevated {
|
||||||
|
background: #1c1c1e;
|
||||||
|
border-color: #3a3a3c;
|
||||||
|
color: #e5e5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fluent Design 卡片 - 暗黑模式 */
|
||||||
|
.dark .fluent-card {
|
||||||
|
background: rgba(28, 28, 30, 0.8);
|
||||||
|
border-color: rgba(58, 58, 60, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用按钮对齐 */
|
||||||
|
.ant-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn .anticon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ant Design Pagination */
|
/* Ant Design Pagination */
|
||||||
.ant-pagination-item-active {
|
.ant-pagination-item-active {
|
||||||
border-color: var(--md-sys-color-primary);
|
border-color: var(--md-sys-color-primary);
|
||||||
|
|||||||
@@ -335,13 +335,6 @@ onMounted(async () => {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
.loading-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
@@ -357,37 +350,8 @@ onMounted(async () => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hint {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: #909399;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-check-in {
|
.last-check-in {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-card {
|
|
||||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 修复按钮图标对齐 */
|
|
||||||
:deep(.ant-btn) {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-btn .anticon) {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
<Layout>
|
<Layout>
|
||||||
<div class="settings-view">
|
<div class="settings-view">
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<h1 class="text-3xl font-bold text-gray-800 mb-6">个人设置</h1>
|
<h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 mb-6">个人设置</h1>
|
||||||
|
|
||||||
<!-- 基本信息卡片 -->
|
<!-- 基本信息卡片 -->
|
||||||
<div class="md3-card p-6 mb-6">
|
<div class="md3-card p-6 mb-6">
|
||||||
<h2 class="text-xl font-bold text-gray-800 mb-4 flex items-center">
|
<h2 class="text-xl font-bold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
|
||||||
<UserOutlined class="mr-2" />
|
<UserOutlined class="mr-2" />
|
||||||
基本信息
|
基本信息
|
||||||
</h2>
|
</h2>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
<!-- 修改邮箱 -->
|
<!-- 修改邮箱 -->
|
||||||
<div class="md3-card p-6 mb-6">
|
<div class="md3-card p-6 mb-6">
|
||||||
<h2 class="text-xl font-bold text-gray-800 mb-4 flex items-center">
|
<h2 class="text-xl font-bold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
|
||||||
<EditOutlined class="mr-2" />
|
<EditOutlined class="mr-2" />
|
||||||
修改个人信息
|
修改个人信息
|
||||||
</h2>
|
</h2>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
<!-- 设置/修改密码 -->
|
<!-- 设置/修改密码 -->
|
||||||
<div class="md3-card p-6">
|
<div class="md3-card p-6">
|
||||||
<h2 class="text-xl font-bold text-gray-800 mb-4 flex items-center">
|
<h2 class="text-xl font-bold text-gray-800 dark:text-gray-100 mb-4 flex items-center">
|
||||||
<KeyOutlined class="mr-2" />
|
<KeyOutlined class="mr-2" />
|
||||||
{{ hasPassword ? '修改密码' : '设置密码' }}
|
{{ hasPassword ? '修改密码' : '设置密码' }}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -296,14 +296,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.md3-card {
|
.settings-view {
|
||||||
background: white;
|
min-height: 100%;
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.08);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md3-card:hover {
|
|
||||||
box-shadow: 0 4px 8px 3px rgba(0, 0, 0, 0.10);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-4xl font-bold text-gradient mb-2">任务管理</h1>
|
<h1 class="text-4xl font-bold text-gradient mb-2">任务管理</h1>
|
||||||
<p class="text-gray-600">管理您的自动打卡任务</p>
|
<p class="text-gray-600 dark:text-gray-400">管理您的自动打卡任务</p>
|
||||||
</div>
|
</div>
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
<div class="fluent-card p-6 animate-slide-up">
|
<div class="fluent-card p-6 animate-slide-up">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 mb-1">总任务数</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">总任务数</p>
|
||||||
<p class="text-3xl font-bold text-primary-600">{{ taskStore.taskStats.total }}</p>
|
<p class="text-3xl font-bold text-primary-600 dark:text-primary-400">{{ taskStore.taskStats.total }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-12 h-12 bg-primary-100 rounded-md3 flex items-center justify-center">
|
<div class="w-12 h-12 bg-primary-100 dark:bg-primary-900/30 rounded-md3 flex items-center justify-center">
|
||||||
<FileTextOutlined class="text-2xl text-primary-600" />
|
<FileTextOutlined class="text-2xl text-primary-600 dark:text-primary-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,11 +42,11 @@
|
|||||||
<div class="fluent-card p-6 animate-slide-up" style="animation-delay: 0.1s">
|
<div class="fluent-card p-6 animate-slide-up" style="animation-delay: 0.1s">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 mb-1">启用中</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">启用中</p>
|
||||||
<p class="text-3xl font-bold text-green-600">{{ taskStore.taskStats.active }}</p>
|
<p class="text-3xl font-bold text-green-600 dark:text-green-400">{{ taskStore.taskStats.active }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-12 h-12 bg-green-100 rounded-md3 flex items-center justify-center">
|
<div class="w-12 h-12 bg-green-100 dark:bg-green-900/30 rounded-md3 flex items-center justify-center">
|
||||||
<CheckCircleOutlined class="text-2xl text-green-600" />
|
<CheckCircleOutlined class="text-2xl text-green-600 dark:text-green-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,11 +56,11 @@
|
|||||||
<div class="fluent-card p-6 animate-slide-up" style="animation-delay: 0.2s">
|
<div class="fluent-card p-6 animate-slide-up" style="animation-delay: 0.2s">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 mb-1">已禁用</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">已禁用</p>
|
||||||
<p class="text-3xl font-bold text-gray-600">{{ taskStore.taskStats.inactive }}</p>
|
<p class="text-3xl font-bold text-gray-600 dark:text-gray-300">{{ taskStore.taskStats.inactive }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-12 h-12 bg-gray-100 rounded-md3 flex items-center justify-center">
|
<div class="w-12 h-12 bg-gray-100 dark:bg-gray-700 rounded-md3 flex items-center justify-center">
|
||||||
<StopOutlined class="text-2xl text-gray-600" />
|
<StopOutlined class="text-2xl text-gray-600 dark:text-gray-300" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
|
darkMode: 'class', // 启用 class 模式的暗黑模式
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
|||||||
Reference in New Issue
Block a user