diff --git a/public/assets/home/default-avatar.svg b/public/assets/home/default-avatar.svg
new file mode 100644
index 0000000..efb95f3
--- /dev/null
+++ b/public/assets/home/default-avatar.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/App.vue b/src/App.vue
index 19d3bed..4fbd2d5 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -64,7 +64,11 @@ onMounted(async () => {
-
+
+
+
+
+
@@ -493,16 +497,63 @@ body {
}
}
+/* 页面切换过渡动画 */
+.page-fade-enter-active {
+ transition: opacity 0.25s ease, transform 0.25s ease;
+}
+
+.page-fade-leave-active {
+ transition: opacity 0.15s ease;
+}
+
+.page-fade-enter-from {
+ opacity: 0;
+ transform: translateY(8px);
+}
+
+.page-fade-leave-to {
+ opacity: 0;
+}
+
/* 平滑滚动 */
html {
scroll-behavior: smooth;
}
+/* 自定义滚动条 */
+::-webkit-scrollbar {
+ width: 4px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: rgba(0, 0, 0, 0.15);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: rgba(0, 0, 0, 0.25);
+}
+
/* 选择高亮颜色 */
::selection {
background: rgba(229, 62, 62, 0.2);
color: #e53e3e;
}
+/* 全局 Element Plus 输入框聚焦增强 */
+.el-input__wrapper.is-focus {
+ box-shadow: 0 0 0 1px var(--color-primary, #e53e3e) inset !important;
+}
+
+/* 全局骨架屏动画优化 */
+.el-skeleton.is-animated .el-skeleton__item {
+ background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 37%, #f0f0f0 63%) !important;
+ background-size: 400% 100% !important;
+}
+
diff --git a/src/assets/base.css b/src/assets/base.css
index 8816868..2c0e930 100644
--- a/src/assets/base.css
+++ b/src/assets/base.css
@@ -1,53 +1,51 @@
-/* color palette from */
+/* 精彩猪手 - 设计变量系统 */
:root {
- --vt-c-white: #ffffff;
- --vt-c-white-soft: #f8f8f8;
- --vt-c-white-mute: #f2f2f2;
+ /* 品牌色 */
+ --color-primary: #e53e3e;
+ --color-primary-light: #ff6b6b;
+ --color-primary-dark: #c53030;
+ --color-primary-bg: rgba(229, 62, 62, 0.08);
+ --color-primary-shadow: rgba(229, 62, 62, 0.25);
- --vt-c-black: #181818;
- --vt-c-black-soft: #222222;
- --vt-c-black-mute: #282828;
+ /* 功能色 */
+ --color-success: #48bb78;
+ --color-warning: #ed8936;
+ --color-danger: #f56565;
+ --color-info: #4299e1;
- --vt-c-indigo: #2c3e50;
+ /* 中性色 */
+ --color-text-primary: #1a202c;
+ --color-text-secondary: #4a5568;
+ --color-text-tertiary: #a0aec0;
+ --color-text-placeholder: #cbd5e0;
- --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
- --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
- --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
- --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
+ /* 背景色 */
+ --color-bg-page: #f0f2f5;
+ --color-bg-card: #ffffff;
+ --color-bg-input: #f7fafc;
+ --color-bg-hover: #edf2f7;
- --vt-c-text-light-1: var(--vt-c-indigo);
- --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
- --vt-c-text-dark-1: var(--vt-c-white);
- --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
-}
+ /* 边框色 */
+ --color-border: #e2e8f0;
+ --color-border-light: #edf2f7;
-/* semantic color variables for this project */
-:root {
- --color-background: var(--vt-c-white);
- --color-background-soft: var(--vt-c-white-soft);
- --color-background-mute: var(--vt-c-white-mute);
+ /* 圆角 */
+ --radius-sm: 8px;
+ --radius-md: 12px;
+ --radius-lg: 16px;
+ --radius-xl: 20px;
+ --radius-full: 9999px;
- --color-border: var(--vt-c-divider-light-2);
- --color-border-hover: var(--vt-c-divider-light-1);
+ /* 阴影 */
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+ --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
+ --shadow-xl: 0 12px 40px rgba(0, 0, 0, 0.16);
- --color-heading: var(--vt-c-text-light-1);
- --color-text: var(--vt-c-text-light-1);
-
- --section-gap: 160px;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --color-background: var(--vt-c-black);
- --color-background-soft: var(--vt-c-black-soft);
- --color-background-mute: var(--vt-c-black-mute);
-
- --color-border: var(--vt-c-divider-dark-2);
- --color-border-hover: var(--vt-c-divider-dark-1);
-
- --color-heading: var(--vt-c-text-dark-1);
- --color-text: var(--vt-c-text-dark-2);
- }
+ /* 过渡 */
+ --transition-fast: 0.15s ease;
+ --transition-base: 0.25s ease;
+ --transition-slow: 0.4s ease;
}
*,
@@ -55,28 +53,18 @@
*::after {
box-sizing: border-box;
margin: 0;
- font-weight: normal;
}
body {
min-height: 100vh;
- color: var(--color-text);
- background: var(--color-background);
- transition:
- color 0.5s,
- background-color 0.5s;
+ color: var(--color-text-primary);
+ background: var(--color-bg-page);
line-height: 1.6;
font-family:
- Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
- Oxygen,
- Ubuntu,
- Cantarell,
- 'Fira Sans',
- 'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
diff --git a/src/components/BottomNavigation.vue b/src/components/BottomNavigation.vue
index bac2300..fee7d85 100644
--- a/src/components/BottomNavigation.vue
+++ b/src/components/BottomNavigation.vue
@@ -53,19 +53,22 @@ export default {
/* 底部导航栏 */
.bottom-nav {
height: 70px;
- background: white;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
display: flex;
align-items: center;
justify-content: space-around;
- border-top: 1px solid #f0f0f0;
+ border-top: 1px solid rgba(0, 0, 0, 0.05);
z-index: 1000;
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
+ box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.06);
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
max-width: 850px;
width: 100%;
+ padding: 0 4px;
}
.nav-item {
@@ -74,37 +77,44 @@ export default {
align-items: center;
justify-content: center;
text-decoration: none;
- color: #8a8a8a;
- transition: all 0.3s ease;
- padding: 4px 8px;
+ color: #999;
+ transition: color 0.2s ease, transform 0.15s ease;
+ padding: 6px 12px;
min-width: 60px;
position: relative;
- border-radius: 8px;
+ border-radius: var(--radius-sm, 8px);
cursor: pointer;
+ -webkit-tap-highlight-color: transparent;
}
.nav-item:hover {
- color: #ff6b35;
- background: rgba(255, 107, 53, 0.05);
+ color: var(--color-primary, #e53e3e);
}
.nav-item:hover .nav-icon-img {
- transform: scale(1.05);
+ transform: scale(1.08);
+}
+
+.nav-item:active {
+ transform: scale(0.92);
}
.nav-item.active {
- color: #ff6b35;
- background: rgba(255, 107, 53, 0.1);
+ color: var(--color-primary, #e53e3e);
+}
+
+.nav-item.active .nav-icon {
+ transform: translateY(-1px);
}
.nav-item.active .nav-text {
- color: #ff6b35;
+ color: var(--color-primary, #e53e3e);
font-weight: 600;
}
.nav-icon {
margin-bottom: 4px;
- transition: all 0.3s ease;
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
@@ -116,14 +126,14 @@ export default {
width: 24px;
height: 24px;
object-fit: contain;
- transition: all 0.3s ease;
+ transition: transform 0.2s ease;
}
.nav-text {
font-size: 11px;
font-weight: 500;
text-align: center;
- transition: all 0.3s ease;
+ transition: color 0.2s ease;
line-height: 1;
}
@@ -135,7 +145,7 @@ export default {
}
.nav-item {
- padding: 3px 6px;
+ padding: 4px 8px;
min-width: 55px;
}
@@ -162,7 +172,7 @@ export default {
}
.nav-item {
- padding: 2px 4px;
+ padding: 3px 6px;
min-width: 50px;
}
diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue
deleted file mode 100644
index eff59f1..0000000
--- a/src/components/HelloWorld.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
{{ msg }}
-
- You’ve successfully created a project with
- Vite +
- Vue 3 .
-
-
-
-
-
diff --git a/src/components/TheWelcome.vue b/src/components/TheWelcome.vue
deleted file mode 100644
index fe48afc..0000000
--- a/src/components/TheWelcome.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
-
-
- Documentation
-
- Vue’s
- official documentation
- provides you with all information you need to get started.
-
-
-
-
-
-
- Tooling
-
- This project is served and bundled with
- Vite . The
- recommended IDE setup is
- VSCode
- +
- Vue - Official . If
- you need to test your components and web pages, check out
- Vitest
- and
- Cypress
- /
- Playwright .
-
-
-
- More instructions are available in
- README.md .
-
-
-
-
-
-
- Ecosystem
-
- Get official tools and libraries for your project:
- Pinia ,
- Vue Router ,
- Vue Test Utils , and
- Vue Dev Tools . If
- you need more resources, we suggest paying
- Awesome Vue
- a visit.
-
-
-
-
-
-
- Community
-
- Got stuck? Ask your question on
- Vue Land
- (our official Discord server), or
- StackOverflow . You should also follow the official
- @vuejs.org
- Bluesky account or the
- @vuejs
- X account for latest news in the Vue world.
-
-
-
-
-
-
- Support Vue
-
- As an independent project, Vue relies on community backing for its sustainability. You can help
- us by
- becoming a sponsor .
-
-
diff --git a/src/components/WelcomeItem.vue b/src/components/WelcomeItem.vue
deleted file mode 100644
index 6d7086a..0000000
--- a/src/components/WelcomeItem.vue
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
diff --git a/src/components/icons/IconCommunity.vue b/src/components/icons/IconCommunity.vue
deleted file mode 100644
index 2dc8b05..0000000
--- a/src/components/icons/IconCommunity.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/src/components/icons/IconDocumentation.vue b/src/components/icons/IconDocumentation.vue
deleted file mode 100644
index 6d4791c..0000000
--- a/src/components/icons/IconDocumentation.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/src/components/icons/IconEcosystem.vue b/src/components/icons/IconEcosystem.vue
deleted file mode 100644
index c3a4f07..0000000
--- a/src/components/icons/IconEcosystem.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/src/components/icons/IconSupport.vue b/src/components/icons/IconSupport.vue
deleted file mode 100644
index 7452834..0000000
--- a/src/components/icons/IconSupport.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/src/components/icons/IconTooling.vue b/src/components/icons/IconTooling.vue
deleted file mode 100644
index 660598d..0000000
--- a/src/components/icons/IconTooling.vue
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
diff --git a/src/store/user.js b/src/store/user.js
index b7260fd..d6e55ce 100644
--- a/src/store/user.js
+++ b/src/store/user.js
@@ -25,7 +25,7 @@ export const userStore = reactive({
avatar: userData.userAvatar || userData.avatar || null,
userType: userData.userType || 'trial',
isVip: userData.isVip,
- expireDate: userData.vipExpire || userData.expireDate || '2025-06-30',
+ expireDate: userData.vipExpire || userData.expireDate || null,
registeredAt: userData.createTime || userData.registeredAt || new Date().toISOString(),
status: userData.status !== undefined ? userData.status : 0, // 添加status字段,默认为0 (正常)
stats: {
diff --git a/src/utils/format.js b/src/utils/format.js
new file mode 100644
index 0000000..0f0e4db
--- /dev/null
+++ b/src/utils/format.js
@@ -0,0 +1,75 @@
+/**
+ * 通用格式化工具函数
+ */
+
+/**
+ * 格式化时间为短格式:YY-MM-DD HH:mm
+ * @param {string|number|Date} timestamp
+ * @returns {string}
+ */
+export function formatTime(timestamp) {
+ if (!timestamp) return ''
+ const date = new Date(timestamp)
+ const year = String(date.getFullYear()).slice(-2)
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hours = String(date.getHours()).padStart(2, '0')
+ const minutes = String(date.getMinutes()).padStart(2, '0')
+ return `${year}-${month}-${day} ${hours}:${minutes}`
+}
+
+/**
+ * 格式化时间为完整格式:YYYY-MM-DD HH:mm
+ * @param {string|number|Date} timestamp
+ * @returns {string}
+ */
+export function formatFullTime(timestamp) {
+ if (!timestamp) return ''
+ const date = new Date(timestamp)
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hours = String(date.getHours()).padStart(2, '0')
+ const minutes = String(date.getMinutes()).padStart(2, '0')
+ return `${year}-${month}-${day} ${hours}:${minutes}`
+}
+
+/**
+ * 格式化日期时间为本地化格式:YYYY-MM-DD HH:mm:ss
+ * @param {string|number|Date} dateStr
+ * @returns {string}
+ */
+export function formatDateTime(dateStr) {
+ if (!dateStr) return '-'
+ const date = new Date(dateStr)
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hours = String(date.getHours()).padStart(2, '0')
+ const minutes = String(date.getMinutes()).padStart(2, '0')
+ const seconds = String(date.getSeconds()).padStart(2, '0')
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+
+/**
+ * 格式化日期为短格式:YYYY-MM-DD
+ * @param {string|number|Date} dateStr
+ * @returns {string}
+ */
+export function formatDate(dateStr) {
+ if (!dateStr) return '-'
+ const date = new Date(dateStr)
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ return `${year}-${month}-${day}`
+}
+
+/**
+ * 格式化数字为两位,不足补零
+ * @param {number|string} num
+ * @returns {string}
+ */
+export function padNumber(num) {
+ return String(num).padStart(2, '0')
+}
diff --git a/src/views/DataAnalysis.vue b/src/views/DataAnalysis.vue
index d1153b3..7c7ec7a 100644
--- a/src/views/DataAnalysis.vue
+++ b/src/views/DataAnalysis.vue
@@ -156,13 +156,8 @@