mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 14:06:28 +00:00
frontend: add dark mode support
This commit is contained in:
+13
-2
@@ -5,14 +5,25 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { onMounted, computed } from 'vue'
|
||||
import { ConfigProvider as AConfigProvider } from 'ant-design-vue'
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
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()
|
||||
|
||||
// 初始化主题(全局)
|
||||
initTheme()
|
||||
watchSystemTheme()
|
||||
|
||||
// 使用主题
|
||||
const { isDark } = useTheme()
|
||||
|
||||
// 动态生成 Ant Design 主题
|
||||
const antdTheme = computed(() => getAntdTheme(isDark.value))
|
||||
|
||||
// 应用启动时验证 Token
|
||||
onMounted(async () => {
|
||||
if (authStore.isAuthenticated) {
|
||||
|
||||
+162
-54
@@ -1,77 +1,185 @@
|
||||
import { theme } from 'ant-design-vue'
|
||||
|
||||
/**
|
||||
* Ant Design Vue 主题配置
|
||||
* 匹配现有 Material Design 3 色彩系统
|
||||
* @param {boolean} isDark - 是否为暗黑模式
|
||||
*/
|
||||
export default {
|
||||
token: {
|
||||
// 主色调 - 绿色(与 MD3 primary 保持一致)
|
||||
colorPrimary: '#4caf50',
|
||||
export default function getAntdTheme(isDark = false) {
|
||||
return {
|
||||
token: {
|
||||
// 主色调 - 绿色(与 MD3 primary 保持一致)
|
||||
colorPrimary: isDark ? '#81c784' : '#4caf50',
|
||||
|
||||
// 成功色
|
||||
colorSuccess: '#4caf50',
|
||||
// 成功色
|
||||
colorSuccess: isDark ? '#81c784' : '#4caf50',
|
||||
|
||||
// 警告色
|
||||
colorWarning: '#ff9800',
|
||||
// 警告色
|
||||
colorWarning: '#ff9800',
|
||||
|
||||
// 错误色
|
||||
colorError: '#f56c6c',
|
||||
// 错误色
|
||||
colorError: '#f56c6c',
|
||||
|
||||
// 信息色 - 蓝色(与 MD3 secondary 保持一致)
|
||||
colorInfo: '#2196f3',
|
||||
// 信息色 - 蓝色(与 MD3 secondary 保持一致)
|
||||
colorInfo: isDark ? '#64b5f6' : '#2196f3',
|
||||
|
||||
// 边框圆角 - 与 Material Design 3 一致
|
||||
borderRadius: 12,
|
||||
// 背景色
|
||||
colorBgBase: isDark ? '#121212' : '#ffffff',
|
||||
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||
colorBgElevated: isDark ? '#2c2c2e' : '#ffffff',
|
||||
colorBgLayout: isDark ? '#121212' : '#fafafa',
|
||||
colorBgSpotlight: isDark ? '#2c2c2e' : '#ffffff',
|
||||
|
||||
// 字体家族
|
||||
fontFamily: "'Inter', 'Segoe UI', 'Roboto', system-ui, -apple-system, sans-serif",
|
||||
// 文字色
|
||||
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||
colorTextSecondary: isDark ? '#a0a0a3' : '#64748b',
|
||||
colorTextTertiary: isDark ? '#808083' : '#94a3b8',
|
||||
colorTextQuaternary: isDark ? '#606063' : '#cbd5e1',
|
||||
|
||||
// 链接色
|
||||
colorLink: '#2196f3',
|
||||
// 边框色
|
||||
colorBorder: isDark ? '#3a3a3c' : '#e5e7eb',
|
||||
colorBorderSecondary: isDark ? '#2c2c2e' : '#f3f4f6',
|
||||
|
||||
// 字体大小
|
||||
fontSize: 14,
|
||||
// 分割线颜色
|
||||
colorSplit: isDark ? '#3a3a3c' : '#e5e7eb',
|
||||
|
||||
// 行高
|
||||
lineHeight: 1.5715,
|
||||
},
|
||||
// 边框圆角 - 与 Material Design 3 一致
|
||||
borderRadius: 12,
|
||||
|
||||
components: {
|
||||
// Card 组件定制
|
||||
Card: {
|
||||
borderRadiusLG: 16,
|
||||
boxShadowTertiary: '0 1px 3px 1px rgba(0, 0, 0, 0.08)',
|
||||
paddingLG: 24,
|
||||
},
|
||||
// 字体家族
|
||||
fontFamily: "'Inter', 'Segoe UI', 'Roboto', system-ui, -apple-system, sans-serif",
|
||||
|
||||
// Button 组件定制
|
||||
Button: {
|
||||
borderRadius: 24, // 圆角按钮,类似 MD3
|
||||
controlHeight: 40,
|
||||
// 链接色
|
||||
colorLink: isDark ? '#64b5f6' : '#2196f3',
|
||||
colorLinkHover: isDark ? '#90caf9' : '#1976d2',
|
||||
colorLinkActive: isDark ? '#42a5f5' : '#1565c0',
|
||||
|
||||
// 字体大小
|
||||
fontSize: 14,
|
||||
},
|
||||
|
||||
// Input 组件定制
|
||||
Input: {
|
||||
borderRadius: 12,
|
||||
// 行高
|
||||
lineHeight: 1.5715,
|
||||
|
||||
// 控制组件高度
|
||||
controlHeight: 40,
|
||||
},
|
||||
|
||||
// Modal 组件定制
|
||||
Modal: {
|
||||
borderRadiusLG: 16,
|
||||
components: {
|
||||
// Card 组件定制
|
||||
Card: {
|
||||
borderRadiusLG: 16,
|
||||
boxShadowTertiary: isDark
|
||||
? '0 1px 3px 1px rgba(0, 0, 0, 0.5)'
|
||||
: '0 1px 3px 1px rgba(0, 0, 0, 0.08)',
|
||||
paddingLG: 24,
|
||||
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||
colorBorderSecondary: isDark ? '#3a3a3c' : '#f0f0f0',
|
||||
colorTextHeading: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||
},
|
||||
|
||||
// Button 组件定制
|
||||
Button: {
|
||||
borderRadius: 24, // 圆角按钮,类似 MD3
|
||||
controlHeight: 40,
|
||||
fontSize: 14,
|
||||
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||
colorBgContainer: isDark ? '#2c2c2e' : '#ffffff',
|
||||
},
|
||||
|
||||
// Input 组件定制
|
||||
Input: {
|
||||
borderRadius: 12,
|
||||
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: {
|
||||
borderRadiusLG: 16,
|
||||
colorBgElevated: isDark ? '#1c1c1e' : '#ffffff',
|
||||
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||
colorTextHeading: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||
},
|
||||
|
||||
// Table 组件定制
|
||||
Table: {
|
||||
borderRadius: 12,
|
||||
colorBgContainer: isDark ? '#1c1c1e' : '#ffffff',
|
||||
colorFillAlter: isDark ? '#2c2c2e' : '#f5f7fa',
|
||||
colorText: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||
colorTextHeading: isDark ? '#e5e5e7' : '#1c1b1f',
|
||||
colorBorderSecondary: isDark ? '#3a3a3c' : '#f0f0f0',
|
||||
},
|
||||
|
||||
// Tabs 组件定制
|
||||
Tabs: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
|
||||
// Table 组件定制
|
||||
Table: {
|
||||
borderRadius: 12,
|
||||
},
|
||||
|
||||
// Tabs 组件定制
|
||||
Tabs: {
|
||||
borderRadius: 12,
|
||||
},
|
||||
},
|
||||
|
||||
// 算法配置
|
||||
algorithm: [],
|
||||
// 算法配置 - 使用 Ant Design 内置的暗黑算法
|
||||
algorithm: isDark ? [theme.darkAlgorithm] : [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ onMounted(() => {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
|
||||
}
|
||||
|
||||
.dark .layout-container {
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #0f0f0f 100%);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- Logo and Brand -->
|
||||
@@ -23,8 +23,8 @@
|
||||
:class="[
|
||||
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
||||
isActive
|
||||
? 'bg-primary-100 text-primary-700'
|
||||
: 'text-gray-700 hover:bg-gray-100'
|
||||
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
@@ -44,8 +44,8 @@
|
||||
:class="[
|
||||
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
||||
isActive
|
||||
? 'bg-primary-100 text-primary-700'
|
||||
: 'text-gray-700 hover:bg-gray-100'
|
||||
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
@@ -65,8 +65,8 @@
|
||||
:class="[
|
||||
'px-4 py-2 rounded-full font-medium transition-all cursor-pointer',
|
||||
isActive
|
||||
? 'bg-primary-100 text-primary-700'
|
||||
: 'text-gray-700 hover:bg-gray-100'
|
||||
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
@@ -81,7 +81,7 @@
|
||||
<a
|
||||
:class="[
|
||||
'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 />
|
||||
@@ -117,43 +117,57 @@
|
||||
</div>
|
||||
|
||||
<!-- User Menu & Mobile Hamburger -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Token Status Indicator (Desktop) -->
|
||||
<a-tooltip v-if="!isMobile && showTokenStatus" :title="tokenStatusTooltip">
|
||||
<div class="flex items-center space-x-2 md:space-x-4">
|
||||
<!-- Token Status Indicator (Desktop & Mobile) -->
|
||||
<a-tooltip v-if="showTokenStatus" :title="tokenStatusTooltip">
|
||||
<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"
|
||||
>
|
||||
<a-badge :status="tokenBadgeStatus" />
|
||||
<ClockCircleOutlined :class="tokenIconClass" />
|
||||
<span class="text-sm">{{ tokenBadgeText }}</span>
|
||||
<!-- 过期时显示刷新按钮 -->
|
||||
<ClockCircleOutlined :class="[tokenIconClass, 'text-sm md:text-base']" />
|
||||
<span class="text-xs md:text-sm hidden sm:inline">{{ tokenBadgeText }}</span>
|
||||
<!-- 过期时显示刷新按钮(响应式设计) -->
|
||||
<a-button
|
||||
v-if="remainingMinutes !== null && remainingMinutes < 0"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="!text-xs !px-2 md:!px-3"
|
||||
@click.stop="handleRefreshToken"
|
||||
>
|
||||
刷新
|
||||
<span class="hidden sm:inline">刷新</span>
|
||||
<ReloadOutlined class="sm:hidden" />
|
||||
</a-button>
|
||||
</div>
|
||||
</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 -->
|
||||
<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' }">
|
||||
{{ userInitial }}
|
||||
</a-avatar>
|
||||
<span class="hidden md:block font-medium text-gray-700">{{ authStore.user?.alias || '用户' }}</span>
|
||||
<DownOutlined class="text-xs text-gray-500" />
|
||||
<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 dark:text-gray-400" />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="info" disabled>
|
||||
<div class="px-2 py-1">
|
||||
<p class="text-sm font-medium text-gray-900">{{ authStore.user?.alias }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ authStore.isAdmin ? '管理员' : '普通用户' }}</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 dark:text-gray-400 mt-1">{{ authStore.isAdmin ? '管理员' : '普通用户' }}</p>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
@@ -176,7 +190,7 @@
|
||||
@click="drawerVisible = true"
|
||||
class="!p-2"
|
||||
>
|
||||
<MenuOutlined class="text-xl" />
|
||||
<MenuOutlined class="text-xl text-gray-700 dark:text-gray-300" />
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -277,6 +291,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useTokenMonitor } from '@/composables/useTokenMonitor'
|
||||
import { useBreakpoint } from '@/composables/useBreakpoint'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { Modal, message } from 'ant-design-vue'
|
||||
import QRCodeModal from './QRCodeModal.vue'
|
||||
import {
|
||||
@@ -293,6 +308,9 @@ import {
|
||||
DownOutlined,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
BulbOutlined,
|
||||
BulbFilled,
|
||||
ReloadOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -301,6 +319,7 @@ const authStore = useAuthStore()
|
||||
const userStore = useUserStore()
|
||||
const { isMobile } = useBreakpoint()
|
||||
const { getRemainingMinutes, tokenStatus } = useTokenMonitor()
|
||||
const { isDark, toggleTheme } = useTheme()
|
||||
|
||||
const drawerVisible = 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
|
||||
import Antd from 'ant-design-vue'
|
||||
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 router from './router'
|
||||
@@ -18,13 +15,7 @@ const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
|
||||
// Ant Design Vue with custom theme
|
||||
// Ant Design Vue
|
||||
app.use(Antd)
|
||||
|
||||
// Configure Ant Design globally
|
||||
app.config.globalProperties.$antdConfig = {
|
||||
theme: antdTheme,
|
||||
locale: zhCN,
|
||||
}
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
+363
-16
@@ -14,7 +14,7 @@
|
||||
font-weight: 400;
|
||||
color-scheme: light;
|
||||
|
||||
/* Material Design 3 color tokens */
|
||||
/* Material Design 3 color tokens - Light Mode */
|
||||
--md-sys-color-primary: #4caf50;
|
||||
--md-sys-color-on-primary: #ffffff;
|
||||
--md-sys-color-secondary: #2196f3;
|
||||
@@ -25,6 +25,21 @@
|
||||
--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;
|
||||
padding: 0;
|
||||
@@ -41,6 +56,11 @@
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-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 {
|
||||
@@ -53,7 +73,7 @@
|
||||
@layer components {
|
||||
/* Material Design 3 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 {
|
||||
@@ -61,7 +81,7 @@
|
||||
}
|
||||
|
||||
.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 */
|
||||
@@ -71,25 +91,25 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
@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 {
|
||||
@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-card {
|
||||
@apply bg-white/80 backdrop-blur-xl rounded-lg border border-gray-200/50 shadow-lg;
|
||||
@apply transition-all duration-300 hover:shadow-xl hover:border-gray-300/50;
|
||||
@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 dark:hover:border-gray-600/50;
|
||||
}
|
||||
|
||||
.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 */
|
||||
@@ -98,24 +118,24 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 */
|
||||
.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 {
|
||||
@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 {
|
||||
@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;
|
||||
}
|
||||
|
||||
.dark .ant-table-thead > tr > th {
|
||||
background: #2c2c2e;
|
||||
color: #e5e5e7;
|
||||
border-color: #3a3a3c;
|
||||
}
|
||||
|
||||
/* Ant Design Tabs */
|
||||
.ant-tabs {
|
||||
color: var(--md-sys-color-on-surface);
|
||||
@@ -264,15 +290,34 @@
|
||||
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-statistic-title {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.dark .ant-statistic-title {
|
||||
color: #a0a0a3;
|
||||
}
|
||||
|
||||
.ant-statistic-content {
|
||||
color: var(--md-sys-color-on-surface);
|
||||
}
|
||||
|
||||
.dark .ant-statistic-content {
|
||||
color: #e5e5e7;
|
||||
}
|
||||
|
||||
/* Ant Design Drawer */
|
||||
.ant-drawer-content {
|
||||
border-radius: 16px 0 0 16px;
|
||||
@@ -287,6 +332,308 @@
|
||||
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-pagination-item-active {
|
||||
border-color: var(--md-sys-color-primary);
|
||||
|
||||
@@ -335,13 +335,6 @@ onMounted(async () => {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -357,37 +350,8 @@ onMounted(async () => {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-bottom: 20px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.last-check-in {
|
||||
width: 100%;
|
||||
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>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<Layout>
|
||||
<div class="settings-view">
|
||||
<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">
|
||||
<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" />
|
||||
基本信息
|
||||
</h2>
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<!-- 修改邮箱 -->
|
||||
<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" />
|
||||
修改个人信息
|
||||
</h2>
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<!-- 设置/修改密码 -->
|
||||
<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" />
|
||||
{{ hasPassword ? '修改密码' : '设置密码' }}
|
||||
</h2>
|
||||
@@ -296,14 +296,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.md3-card {
|
||||
background: white;
|
||||
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);
|
||||
.settings-view {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<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>
|
||||
<a-button
|
||||
type="primary"
|
||||
@@ -28,11 +28,11 @@
|
||||
<div class="fluent-card p-6 animate-slide-up">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 mb-1">总任务数</p>
|
||||
<p class="text-3xl font-bold text-primary-600">{{ taskStore.taskStats.total }}</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">总任务数</p>
|
||||
<p class="text-3xl font-bold text-primary-600 dark:text-primary-400">{{ taskStore.taskStats.total }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-primary-100 rounded-md3 flex items-center justify-center">
|
||||
<FileTextOutlined class="text-2xl text-primary-600" />
|
||||
<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 dark:text-primary-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,11 +42,11 @@
|
||||
<div class="fluent-card p-6 animate-slide-up" style="animation-delay: 0.1s">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 mb-1">启用中</p>
|
||||
<p class="text-3xl font-bold text-green-600">{{ taskStore.taskStats.active }}</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">启用中</p>
|
||||
<p class="text-3xl font-bold text-green-600 dark:text-green-400">{{ taskStore.taskStats.active }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-green-100 rounded-md3 flex items-center justify-center">
|
||||
<CheckCircleOutlined class="text-2xl text-green-600" />
|
||||
<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 dark:text-green-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,11 +56,11 @@
|
||||
<div class="fluent-card p-6 animate-slide-up" style="animation-delay: 0.2s">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 mb-1">已禁用</p>
|
||||
<p class="text-3xl font-bold text-gray-600">{{ taskStore.taskStats.inactive }}</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1">已禁用</p>
|
||||
<p class="text-3xl font-bold text-gray-600 dark:text-gray-300">{{ taskStore.taskStats.inactive }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-gray-100 rounded-md3 flex items-center justify-center">
|
||||
<StopOutlined class="text-2xl text-gray-600" />
|
||||
<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 dark:text-gray-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: 'class', // 启用 class 模式的暗黑模式
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
|
||||
Reference in New Issue
Block a user