Zkit工具站

This commit is contained in:
zpooi
2025-12-03 23:25:57 +08:00
commit 105bedc0ec
67 changed files with 17487 additions and 0 deletions

168
Zkit-C/src/App.vue Normal file
View File

@@ -0,0 +1,168 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { RouterView } from 'vue-router'
import AppHeader from './components/AppHeader.vue'
import AppFooter from './components/AppFooter.vue'
import LoginModal from './components/LoginModal.vue'
import AnnouncementModal from './components/AnnouncementModal.vue'
// --- 主题切换逻辑 ---
const isDark = ref(false)
// 执行切换操作
const applyTheme = (dark) => {
isDark.value = dark
if (dark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
// 按钮点击事件
const toggleTheme = () => {
applyTheme(!isDark.value)
}
// --- 回到顶部逻辑 ---
const showBackTop = ref(false)
const handleScroll = () => {
showBackTop.value = window.scrollY > 200
}
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
// 初始化:根据时间判断 (19:00 - 06:00为暗色)
onMounted(() => {
const hour = new Date().getHours()
const isNight = hour >= 19 || hour < 6
applyTheme(isNight)
// 添加滚动监听
window.addEventListener('scroll', handleScroll)
})
onUnmounted(() => {
// 移除滚动监听
window.removeEventListener('scroll', handleScroll)
})
</script>
<template>
<AppHeader />
<!-- 路由出口 -->
<RouterView />
<AppFooter />
<LoginModal />
<AnnouncementModal />
<!-- 全局主题切换按钮 -->
<div class="theme-switch-btn" @click="toggleTheme" :title="isDark ? '切换到亮色' : '切换到暗色'">
<i v-if="isDark" class="fa-solid fa-moon"></i>
<i v-else class="fa-solid fa-sun"></i>
</div>
<!-- 全局回到顶部按钮 -->
<Transition name="fade">
<div
v-show="showBackTop"
class="back-to-top"
@click="scrollToTop"
title="回到顶部"
>
<i class="fa-solid fa-arrow-up"></i>
</div>
</Transition>
</template>
<style>
/* 引入修改后的main.css */
@import './assets/main.css';
/* 切换按钮样式 */
.theme-switch-btn {
position: fixed;
bottom: 100px; /* 位于返回顶部按钮上方 */
right: 40px;
width: 50px;
height: 50px;
background-color: var(--bg-card);
border: 1px solid var(--border-color);
color: var(--text-main);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
box-shadow: var(--shadow-md);
cursor: pointer;
z-index: 999;
transition: all 0.3s ease;
}
.theme-switch-btn:hover {
transform: rotate(15deg) scale(1.1);
border-color: var(--primary);
color: var(--primary);
}
/* 回到顶部按钮样式 */
.back-to-top {
position: fixed;
bottom: 40px;
right: 40px;
width: 50px;
height: 50px;
background-color: var(--primary);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
box-shadow: 0 10px 15px -3px rgba(37, 99, 235, 0.3);
cursor: pointer;
z-index: 999;
transition: all 0.3s ease;
}
.back-to-top:hover {
transform: translateY(-5px);
background-color: var(--primary-dark);
box-shadow: 0 15px 20px -3px rgba(37, 99, 235, 0.4);
}
/* Vue 过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(20px);
}
@media (max-width: 768px) {
.theme-switch-btn {
right: 20px;
bottom: 80px;
width: 45px;
height: 45px;
}
.back-to-top {
bottom: 20px;
right: 20px;
width: 45px;
height: 45px;
}
}
</style>

306
Zkit-C/src/assets/main.css Normal file
View File

@@ -0,0 +1,306 @@
/* src/assets/main.css */
/* 1. 定义变量 (Light 默认) */
:root {
/* 核心色调 */
--primary: #2563eb;
--primary-dark: #1d4ed8;
--accent: #f59e0b;
/* 背景色 */
--bg-body: #f8fafc; /* 页面大背景 */
--bg-card: #ffffff; /* 卡片/弹窗背景 */
--bg-header: rgba(255, 255, 255, 0.9); /* 导航栏毛玻璃 */
--bg-input: #ffffff; /* 输入框背景 */
--bg-ad: #f1f5f9; /* 广告位背景 */
/* 文本色 */
--text-main: #0f172a;
--text-sub: #64748b;
--text-placeholder: #94a3b8;
--text-sub-dim:#94a3b8;
/* 边框与装饰 */
--border-color: #e2e8f0;
--border-hover: #bfdbfe;
--dot-color: #cbd5e1; /* Hero区域的点阵颜色 */
--shadow-sm: 0 4px 6px rgba(0, 0, 0, 0.02);
--shadow-md: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
--radius: 12px;
}
/* 2. 定义暗色模式变量 (Dark) - 挂载在 html.dark 上 */
html.dark {
--primary: #3b82f6; /* 暗色下蓝色稍微亮一点 */
--primary-dark: #2563eb;
--bg-body: #0f172a; /* 深蓝背景 */
--bg-card: #1e293b; /* 卡片深色 */
--bg-header: rgba(30, 41, 59, 0.9);
--bg-input: #1e293b;
--bg-ad: #1e293b;
--text-main: #f1f5f9; /* 亮白字 */
--text-sub: #94a3b8; /* 灰字 */
--text-placeholder: #475569;
--text-sub-dim: #e2e8f0;
--border-color: #334155;
--border-hover: #3b82f6;
--dot-color: #334155; /* 点阵变暗 */
--shadow-sm: 0 4px 6px rgba(0, 0, 0, 0.3);
--shadow-md: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
}
/* 3. 全局基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/* 增加颜色过渡,让切换平滑 */
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
body {
background: var(--bg-body);
color: var(--text-main);
overflow-x: hidden;
}
a { text-decoration: none; color: inherit; transition: 0.2s; cursor: pointer; }
ul { list-style: none; }
/* 通用组件 */
.container { max-width: 1200px; margin: 0 auto; padding: 0 1.5rem; }
.btn-login {
padding: 8px 20px;
background: var(--bg-card); /* 修改:使用变量 */
color: var(--primary);
border: 1px solid var(--border-hover);
border-radius: 50px;
font-weight: 600;
cursor: pointer;
}
.btn-login:hover { background: var(--bg-body); }
.btn-cta {
background: var(--primary);
color: white; /* 按钮文字保持白色 */
padding: 12px 30px;
border-radius: 50px;
font-weight: bold;
display: inline-block;
box-shadow: 0 10px 20px rgba(37, 99, 235, 0.2);
cursor: pointer;
border: none;
}
.btn-cta:hover { background: var(--primary-dark); transform: translateY(-2px); }
/* 导航栏 */
header {
background: var(--bg-header); /* 修改:使用变量 */
backdrop-filter: blur(10px);
position: sticky;
top: 0;
z-index: 100;
border-bottom: 1px solid var(--border-color); /* 修改 */
}
.nav-inner {
max-width: 1200px;
margin: 0 auto;
height: 70px;
padding: 0 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.brand { font-size: 1.4rem; font-weight: 800; color: var(--primary); display: flex; align-items: center; gap: 8px; }
.nav-menu a { margin-left: 24px; color: var(--text-sub); font-weight: 500; font-size: 0.95rem; }
.nav-menu a:hover { color: var(--primary); }
/* Hero 区域 */
.hero-wrapper {
position: relative;
padding: 6rem 1rem 5rem;
text-align: center;
/* 修改:使用变量处理点阵图 */
background-image: radial-gradient(var(--dot-color) 1.5px, transparent 1.5px);
background-size: 24px 24px;
background-color: var(--bg-body); /* 修改 */
border-bottom: 1px solid var(--border-color);
}
.hero-title {
font-size: 3rem;
font-weight: 900;
line-height: 1.2;
margin-bottom: 1.5rem;
color: var(--text-main); /* 修改 */
}
.hero-title span { color: var(--primary); }
.hero-sub { font-size: 1.1rem; color: var(--text-sub); max-width: 600px; margin: 0 auto 2.5rem; }
/* 搜索框 */
.search-container { max-width: 600px; margin: 0 auto; position: relative; z-index: 10; }
.search-input {
width: 100%;
padding: 1.2rem 1.5rem 1.2rem 3.5rem;
border-radius: 50px;
border: 1px solid var(--border-color); /* 修改 */
font-size: 1rem;
background: var(--bg-input); /* 修改 */
color: var(--text-main); /* 修改 */
box-shadow: var(--shadow-md); /* 修改 */
outline: none;
transition: 0.3s;
}
.search-input:focus { border-color: var(--primary); }
.search-icon { position: absolute; left: 1.5rem; top: 50%; transform: translateY(-50%); color: var(--text-sub); }
/* 统计栏 */
.stats-bar {
max-width: 1000px;
margin: -40px auto 4rem;
position: relative;
z-index: 20;
background: var(--bg-card); /* 修改 */
border-radius: 16px;
padding: 2rem;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-color);
}
.stat-item { text-align: center; }
.stat-item:last-child { border-right: none; }
.stat-num { font-size: 1.8rem; font-weight: 800; margin-bottom: 5px; color: var(--text-main); }
.stat-desc { font-size: 0.9rem; color: var(--text-sub); }
/* 卡片 */
.section-header { display: flex; justify-content: space-between; align-items: center; margin: 3rem 0 1.5rem; }
.section-header h2 { color: var(--text-main); }
.tools-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1.5rem; }
.card {
background: var(--bg-card); /* 修改 */
padding: 1.5rem;
border-radius: var(--radius);
border: 1px solid var(--border-color);
box-shadow: var(--shadow-sm);
transition: 0.3s;
position: relative;
display: block;
cursor: pointer;
}
.card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-md);
border-color: var(--border-hover);
}
.card-icon {
width: 48px; height: 48px;
background: var(--bg-body); /* 修改 */
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 1.25rem;
margin-bottom: 1rem;
color: var(--text-sub);
transition: 0.3s;
}
.card:hover .card-icon { background: var(--primary); color: white; }
.card h3 { color: var(--text-main); margin-bottom: 0.5rem; } /* 确保标题颜色 */
.card p { color: var(--text-sub); font-size: 0.9rem; } /* 确保描述颜色 */
/* 广告位 */
.ad-banner {
width: 100%; height: 100px;
background: var(--bg-ad); /* 修改 */
border: 2px dashed var(--border-color);
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
color: var(--text-sub);
margin: 2rem 0;
}
/* ---------- 全局暗黑主题样式优化 ---------- */
/* 图标背景色 - Light Mode */
.icon-bg-blue { background: #eff6ff; color: #2563eb; }
.icon-bg-indigo { background: #eef2ff; color: #4f46e5; }
.icon-bg-rose { background: #fff1f2; color: #e11d48; }
.icon-bg-cyan { background: #ecfeff; color: #0891b2; }
.icon-bg-emerald { background: #ecfdf5; color: #059669; }
.icon-bg-amber { background: #fffbeb; color: #d97706; }
.icon-bg-slate { background: #f1f5f9; color: #475569; }
.icon-bg-violet { background: #f5f3ff; color: #7c3aed; }
.icon-bg-fuchsia { background: #fdf4ff; color: #c026d3; }
.icon-bg-orange { background: #fff7ed; color: #ea580c; }
.icon-bg-teal { background: #f0fdfa; color: #0d9488; }
.icon-bg-gray { background: #f3f4f6; color: #4b5563; }
.icon-bg-red { background: #fef2f2; color: #ef4444; }
/* 图标背景色 - Dark Mode */
html.dark .icon-bg-blue { background: rgba(37, 99, 235, 0.15); color: #60a5fa; }
html.dark .icon-bg-indigo { background: rgba(79, 70, 229, 0.15); color: #818cf8; }
html.dark .icon-bg-rose { background: rgba(225, 29, 72, 0.15); color: #f43f5e; }
html.dark .icon-bg-cyan { background: rgba(8, 145, 178, 0.15); color: #22d3ee; }
html.dark .icon-bg-emerald { background: rgba(5, 150, 105, 0.15); color: #34d399; }
html.dark .icon-bg-amber { background: rgba(217, 119, 6, 0.15); color: #fbbf24; }
html.dark .icon-bg-slate { background: rgba(51, 65, 85, 0.5); color: #94a3b8; }
html.dark .icon-bg-violet { background: rgba(124, 58, 237, 0.15); color: #a78bfa; }
html.dark .icon-bg-fuchsia { background: rgba(192, 38, 211, 0.15); color: #e879f9; }
html.dark .icon-bg-orange { background: rgba(234, 88, 12, 0.15); color: #fb923c; }
html.dark .icon-bg-teal { background: rgba(13, 148, 136, 0.15); color: #2dd4bf; }
html.dark .icon-bg-gray { background: rgba(51, 65, 85, 0.5); color: #cbd5e1; }
html.dark .icon-bg-red { background: rgba(239, 68, 68, 0.15); color: #f87171; }
/* VIP 徽章 - Light Mode */
.vip-badge {
background: #fff7ed;
color: #d97706;
transition: background-color 0.3s, color 0.3s;
}
/* VIP 徽章 - Dark Mode */
html.dark .vip-badge {
background: rgba(217, 119, 6, 0.2);
color: #fbbf24;
}
/* 工具标题暗色优化 */
html.dark .tool-title {
color: #e2e8f0;
}
/* 图标容器暗色优化 */
html.dark .icon-wrapper {
filter: brightness(0.92) saturate(0.92);
}
/* 联系客服卡片 - Dark Mode */
html.dark .contact-card {
background: rgba(37, 99, 235, 0.08);
border-color: rgba(37, 99, 235, 0.3);
}
html.dark .contact-icon {
background: var(--bg-card);
}
html.dark .ad-banner {
background: var(--bg-card);
border-color: var(--text-placeholder);
}
/* 响应式 */
@media (max-width: 768px) {
.hero-title { font-size: 2rem; }
.nav-menu { display: none; }
.stats-bar { grid-template-columns: 1fr; gap: 2rem; margin-top: 2rem; }
}

View File

@@ -0,0 +1,517 @@
<script setup>
import { ref, onMounted } from 'vue'
const visible = ref(false)
const STORAGE_KEY = 'has_seen_announcement_v5' // 唯一标识如果要发新公告改一下这个名字比如v2即可再次弹出
onMounted(() => {
// 核心逻辑:检查 localStorage 中是否有记录
const hasSeen = localStorage.getItem(STORAGE_KEY)
// 如果没有记录null说明是首次访问设置为显示
if (!hasSeen) {
// 延迟 0.5 秒弹出,体验更好
setTimeout(() => {
visible.value = true
}, 500)
}
})
const handleClose = () => {
visible.value = false
// 核心逻辑:点击关闭后,在本地存储写入记录
localStorage.setItem(STORAGE_KEY, 'true')
}
</script>
<template>
<Transition name="modal-fade">
<div v-if="visible" class="modal-overlay" @click.self="handleClose">
<div class="modal-content">
<!-- 1. 顶部装饰区 (增加SVG背景纹理) -->
<div class="modal-header-bg">
<svg class="bg-pattern" width="100%" height="100%">
<defs>
<pattern id="pattern-circles" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="2" cy="2" r="1" fill="rgba(255,255,255,0.3)" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#pattern-circles)" />
</svg>
<!-- 浮动的装饰圆圈 -->
<div class="circle-decor c1"></div>
<div class="circle-decor c2"></div>
</div>
<!-- 悬浮图标容器 -->
<div class="icon-wrapper-outer">
<div class="icon-wrapper">
<!-- 使用 SVG 替代 Emoji -->
<svg class="main-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg>
</div>
<!-- 呼吸光环 -->
<div class="icon-glow"></div>
</div>
<!-- 关闭按钮 (移入卡片内部右上角) -->
<button class="close-btn" @click="handleClose" title="关闭">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
<!-- 文本内容区域 -->
<div class="modal-body">
<h3 class="modal-title">全新版本 <span class="gradient-text">V2.0</span> 已发布</h3>
<p class="intro-text">为了提升您的工作效率我们带来了一些令人兴奋的改进</p>
<ul class="feature-list">
<li style="--delay: 0.1s">
<div class="icon-box green">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"></polyline></svg>
</div>
<div class="text-box">
<span class="title">PDF 工具箱</span>
<span class="desc">支持一键合并拆分与压缩</span>
</div>
</li>
<li style="--delay: 0.2s">
<div class="icon-box blue">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</div>
<div class="text-box">
<span class="title">安全升级</span>
<span class="desc">全新的 MD5 加密算法速度提升 50%</span>
</div>
</li>
<li style="--delay: 0.3s">
<div class="icon-box purple">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2.69l5.74 5.88-5.74 5.88-5.74-5.88z"></path><path d="M2 12l10 10 10-10"></path></svg>
</div>
<div class="text-box">
<span class="title">体验优化</span>
<span class="desc">修复移动端显示 bug更加丝滑</span>
</div>
</li>
</ul>
</div>
<!-- 底部 -->
<div class="modal-footer">
<p class="footer-note">💖 祝您使用愉快</p>
<button class="confirm-btn" @click="handleClose">
<span>立即体验</span>
<svg class="arrow-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
<!-- 按钮光效 -->
<div class="btn-shine"></div>
</button>
</div>
</div>
</div>
</Transition>
</template>
<style scoped>
/* --- 遮罩层 (磨砂质感) --- */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(30, 30, 35, 0.6);
backdrop-filter: blur(12px); /* 强力毛玻璃 */
-webkit-backdrop-filter: blur(12px);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
padding: 20px;
}
/* --- 模态框主体 --- */
.modal-content {
background: var(--bg-card);
width: 100%;
max-width: 400px;
border-radius: 28px;
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.1); /* 极细内描边 */
position: relative;
overflow: hidden;
animation: jelly-pop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
/* --- 顶部装饰背景 --- */
.modal-header-bg {
height: 120px;
background: linear-gradient(135deg, var(--primary) 0%, #2c3e50 100%);
position: relative;
overflow: hidden;
}
.bg-pattern {
opacity: 0.3;
position: absolute;
top: 0;
left: 0;
}
/* 装饰性浮动圆圈 */
.circle-decor {
position: absolute;
border-radius: 50%;
background: rgba(255,255,255,0.1);
}
.c1 { width: 100px; height: 100px; top: -20px; right: -20px; }
.c2 { width: 60px; height: 60px; bottom: 20px; left: 10%; }
/* --- 悬浮图标 --- */
.icon-wrapper-outer {
position: absolute;
top: 80px; /* 120px header - 40px (half icon height) */
left: 50%;
transform: translateX(-50%);
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
.icon-wrapper {
width: 72px;
height: 72px;
background: var(--bg-card);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
position: relative;
z-index: 2;
color: var(--primary);
}
.main-icon {
width: 32px;
height: 32px;
animation: swing 2s ease-in-out infinite; /* 铃铛摇摆动画 */
}
.icon-glow {
position: absolute;
width: 100%;
height: 100%;
background: var(--primary);
border-radius: 50%;
opacity: 0.3;
z-index: 1;
animation: pulse-glow 2s infinite;
}
/* --- 关闭按钮 --- */
.close-btn {
position: absolute;
top: 15px;
right: 15px;
background: rgba(0,0,0,0.2);
width: 30px;
height: 30px;
border-radius: 50%;
border: none;
color: rgba(255,255,255,0.8);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
z-index: 20;
}
.close-btn:hover {
background: rgba(255,255,255,0.25);
transform: rotate(90deg);
color: white;
}
/* --- 内容区域 --- */
.modal-body {
padding: 50px 28px 10px; /* Top padding for icon space */
text-align: center;
}
.modal-title {
margin: 0 0 10px;
font-size: 1.5rem;
color: var(--text-main);
font-weight: 800;
letter-spacing: -0.5px;
}
.gradient-text {
background: linear-gradient(120deg, var(--primary), #3b82f6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.intro-text {
color: var(--text-sub);
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 24px;
}
/* --- 特性列表 (卡片式) --- */
.feature-list {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
.feature-list li {
display: flex;
align-items: center;
padding: 12px;
margin-bottom: 12px;
border-radius: 16px;
background: var(--bg-body);
border: 1px solid transparent;
transition: all 0.2s ease;
/* 动画初始状态 */
opacity: 0;
animation: slide-in-up 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
animation-delay: var(--delay);
}
.feature-list li:hover {
background: var(--bg-card);
border-color: var(--border-hover);
transform: translateY(-2px);
box-shadow: var(--shadow-sm);
}
/* 图标盒子 */
.icon-box {
width: 40px;
height: 40px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 14px;
flex-shrink: 0;
}
.icon-box svg { width: 20px; height: 20px; }
.icon-box.green {
background: rgba(66, 184, 131, 0.15);
color: var(--primary);
}
.icon-box.blue {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.icon-box.purple {
background: rgba(139, 92, 246, 0.15);
color: #8b5cf6;
}
.text-box {
display: flex;
flex-direction: column;
}
.text-box .title {
font-weight: 700;
font-size: 0.95rem;
color: var(--text-main);
margin-bottom: 2px;
}
.text-box .desc {
font-size: 0.8rem;
color: var(--text-sub);
}
/* --- 底部 --- */
.modal-footer {
padding: 10px 28px 30px;
text-align: center;
}
.footer-note {
font-size: 0.85rem;
color: var(--text-sub);
margin-bottom: 15px;
font-weight: 500;
opacity: 0;
animation: fade-in 0.5s ease forwards 0.5s;
}
/* 按钮美化 */
.confirm-btn {
position: relative;
width: 100%;
background: var(--primary);
color: white;
border: none;
padding: 16px;
border-radius: 14px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
overflow: hidden;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
}
.confirm-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(37, 99, 235, 0.4);
background: var(--primary-dark);
}
.arrow-icon {
width: 18px;
height: 18px;
transition: transform 0.2s;
}
.confirm-btn:hover .arrow-icon {
transform: translateX(4px);
}
/* 按钮光泽扫光动画 */
.btn-shine {
position: absolute;
top: 0;
left: -100%;
width: 50%;
height: 100%;
background: linear-gradient(
to right,
rgba(255,255,255,0) 0%,
rgba(255,255,255,0.2) 50%,
rgba(255,255,255,0) 100%
);
transform: skewX(-25deg);
animation: shine 3s infinite;
}
/* --- 动画关键帧 --- */
@keyframes jelly-pop {
0% { transform: scale(0.9); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes slide-in-up {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fade-in {
to { opacity: 1; }
}
@keyframes pulse-glow {
0% { transform: scale(1); opacity: 0.3; }
50% { transform: scale(1.4); opacity: 0; }
100% { transform: scale(1); opacity: 0; }
}
@keyframes swing {
0%, 100% { transform: rotate(0deg); }
20% { transform: rotate(15deg); }
40% { transform: rotate(-10deg); }
60% { transform: rotate(5deg); }
80% { transform: rotate(-5deg); }
}
@keyframes shine {
0% { left: -100%; }
20% { left: 200%; }
100% { left: 200%; }
}
/* Vue Transition: Fade */
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.3s ease;
}
.modal-fade-enter-from,
.modal-fade-leave-to {
opacity: 0;
}
/* --- 暗黑模式适配 --- */
:global(.dark) .modal-content {
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.05);
}
:global(.dark) .feature-list li {
background: var(--bg-body);
border: 1px solid var(--border-color);
}
:global(.dark) .feature-list li:hover {
background: var(--bg-card);
border-color: var(--border-hover);
box-shadow: var(--shadow-md);
}
:global(.dark) .icon-box.green {
background: rgba(66, 184, 131, 0.2);
}
:global(.dark) .icon-box.blue {
background: rgba(59, 130, 246, 0.2);
}
:global(.dark) .icon-box.purple {
background: rgba(139, 92, 246, 0.2);
}
:global(.dark) .close-btn {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.8);
}
:global(.dark) .close-btn:hover {
background: rgba(255, 255, 255, 0.2);
color: white;
}
/* --- 响应式设计 --- */
@media (max-width: 480px) {
.modal-content {
max-width: 350px;
}
.modal-body {
padding: 50px 20px 10px;
}
.modal-footer {
padding: 10px 20px 30px;
}
.feature-list li {
padding: 10px;
}
.modal-title {
font-size: 1.3rem;
}
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<footer class="site-footer">
<div class="container">
<p>&copy; 2023 OmniKit Tool Station. All rights reserved.</p>
</div>
</footer>
</template>
<style scoped>
.site-footer {
background: var(--bg-card);
border-top: 1px solid var(--border-color);
color: var(--text-sub);
padding: 3rem 0;
margin-top: 4rem;
text-align: center;
transition: background 0.3s, border-color 0.3s, color 0.3s;
}
</style>

View File

@@ -0,0 +1,228 @@
<script setup>
import { useUserStore } from '@/stores/user'
import { useRouter, useRoute } from 'vue-router'
import { computed, ref, watch } from 'vue'
const userStore = useUserStore()
const router = useRouter()
const route = useRoute()
// --- 移动端菜单状态 ---
const isMenuOpen = ref(false)
const toggleMenu = () => {
isMenuOpen.value = !isMenuOpen.value
document.body.style.overflow = isMenuOpen.value ? 'hidden' : ''
}
const closeMenu = () => {
isMenuOpen.value = false
document.body.style.overflow = ''
}
watch(() => route.fullPath, closeMenu)
// --- 返回按钮逻辑 ---
const backConfig = computed(() => {
if (route.path === '/') return null
const previousPath = router.options.history.state.back
if (typeof previousPath === 'string' && previousPath.includes('/all-tools')) {
return { text: '返回工具库', path: '/all-tools' }
}
return { text: '返回首页', path: '/' }
})
const handleAuth = () => {
closeMenu()
if (userStore.isLoggedIn) {
if(confirm('退出登录?')) userStore.logout()
} else {
userStore.openModal()
}
}
</script>
<template>
<header class="site-header">
<div class="container nav-inner">
<div class="header-left">
<button class="hamburger-btn" @click="toggleMenu" :class="{ 'active': isMenuOpen }">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</button>
<router-link to="/" class="brand desktop-brand">
<div class="logo-icon">
<i class="fa-solid fa-cube" style="color: var(--primary);"></i>
</div>
<span class="brand-text">ZKit</span>
</router-link>
<transition name="fade">
<router-link
v-if="backConfig"
:to="backConfig.path"
class="btn-capsule btn-back"
>
<i class="fa-solid fa-angle-left"></i>
<span class="back-text">{{ backConfig.text }}</span>
</router-link>
</transition>
</div>
<div class="header-center-mobile">
<router-link to="/" class="brand mobile-brand">
<div class="logo-icon">
<i class="fa-solid fa-cube" style="color: var(--primary);"></i>
</div>
<span class="brand-text">ZKit</span>
</router-link>
</div>
<nav class="header-right">
<div class="desktop-links">
<router-link to="/all-tools" active-class="active-link" class="nav-link">全部工具</router-link>
<router-link to="/faq" active-class="active-link" class="nav-link">常见问题</router-link>
</div>
<button class="btn-capsule btn-login" @click="handleAuth" :class="{ 'is-vip': userStore.isVip }">
<i v-if="userStore.isVip" class="fa-solid fa-crown vip-icon"></i>
<span class="login-text">
{{ userStore.isLoggedIn ? (userStore.isVip ? 'VIP' : '我的') : '登录' }}
</span>
</button>
</nav>
</div>
<transition name="slide-down">
<div v-if="isMenuOpen" class="mobile-menu">
<div class="mobile-menu-inner">
<router-link to="/" class="mobile-link" active-class="active-mobile">首页</router-link>
<router-link to="/all-tools" class="mobile-link" active-class="active-mobile">全部工具</router-link>
<router-link to="/faq" class="mobile-link" active-class="active-mobile">常见问题</router-link>
</div>
</div>
</transition>
</header>
</template>
<style scoped>
/* --- 基础布局 --- */
.site-header {
background: var(--bg-header);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 100;
transition: all 0.3s;
}
.nav-inner {
height: 72px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative; /* 用于绝对定位中间的Logo */
}
.header-left, .header-right {
display: flex;
align-items: center;
gap: 16px;
z-index: 102; /* 保证按钮在Logo图层之上 */
}
/* --- Logo 样式 --- */
.brand { display: flex; gap: 8px; align-items: center; text-decoration: none; }
.brand-text { font-size: 1.5rem; font-weight: 900; color: var(--text-main); letter-spacing: -0.5px; }
/* 默认显示桌面Logo隐藏移动端Logo */
.desktop-brand { display: flex; }
.header-center-mobile { display: none; }
/* --- 按钮通用 --- */
.btn-capsule {
display: inline-flex; align-items: center; gap: 6px; font-size: 0.9rem; font-weight: 600; padding: 8px 18px; border-radius: 99px; text-decoration: none; transition: all 0.2s ease; border: 1px solid transparent; cursor: pointer; line-height: 1; white-space: nowrap;
}
.btn-back { color: var(--text-sub); border-color: var(--border-color); background: transparent; }
.btn-back:hover { color: var(--text-main); border-color: var(--border-hover); background: var(--bg-body); }
.btn-login { color: var(--primary); border-color: var(--primary); background: transparent; }
.btn-login:hover { background: var(--bg-body); transform: translateY(-1px); }
.btn-login.is-vip { color: #d97706; border-color: #fbbf24; }
/* --- 导航链接 --- */
.desktop-links { display: flex; gap: 24px; margin-right: 8px; }
.nav-link { color: var(--text-sub); font-size: 0.95rem; font-weight: 500; text-decoration: none; transition: color 0.2s; }
.nav-link:hover { color: var(--primary); }
/* --- 汉堡包按钮 --- */
.hamburger-btn {
display: none; /* 默认隐藏 */
flex-direction: column;
justify-content: center;
gap: 5px;
width: 32px;
height: 32px;
background: transparent;
border: none;
cursor: pointer;
padding: 0;
z-index: 103;
}
.hamburger-btn .bar {
width: 20px; height: 2px; background-color: var(--text-main); border-radius: 2px; transition: all 0.3s;
}
/* 汉堡包动画 */
.hamburger-btn.active .bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.hamburger-btn.active .bar:nth-child(2) { opacity: 0; }
.hamburger-btn.active .bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
/* --- 移动端菜单 --- */
.mobile-menu {
position: fixed; top: 72px; left: 0; width: 100%; height: calc(100vh - 72px);
background: var(--bg-header); backdrop-filter: blur(15px); z-index: 101;
border-top: 1px solid var(--border-color); overflow-y: auto;
}
.mobile-menu-inner { padding: 24px; display: flex; flex-direction: column; gap: 12px; }
.mobile-link { font-size: 1.1rem; font-weight: 600; color: var(--text-main); padding: 16px; border-radius: 8px; text-decoration: none; background: var(--bg-body); }
.mobile-link.active-mobile { color: var(--primary); border: 1px solid var(--primary); }
/* 动画 */
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
.slide-down-enter-active, .slide-down-leave-active { transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
.slide-down-enter-from, .slide-down-leave-to { opacity: 0; transform: translateY(-10px); }
/* --- 响应式适配 (Max Width 640px) --- */
@media (max-width: 640px) {
/* 1. 布局调整 */
.nav-inner { padding: 0 15px; }
/* 左侧显示汉堡包隐藏桌面Logo */
.hamburger-btn { display: flex; }
.desktop-brand { display: none; }
/* 中间:绝对定位显示 Logo */
.header-center-mobile {
display: block;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
}
/* 右侧:隐藏链接,调整登录按钮 */
.desktop-links { display: none; }
.btn-capsule { padding: 6px 12px; font-size: 0.85rem; }
/* 返回按钮隐藏文字只留箭头防止挤压Logo */
.back-text { display: none; }
.btn-back { padding: 6px 10px; border: none; }
.btn-back i { font-size: 1.1rem; }
/* 调整间距 */
.header-left { gap: 8px; }
}
</style>

View File

@@ -0,0 +1,282 @@
<script setup>
import { ref, computed } from 'vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 表单数据
const phone = ref('')
const password = ref('')
const confirmPassword = ref('')
// 当前模式: 'login' | 'register' | 'forgot'
const authType = ref('login')
// 标题和按钮文字的计算属性
const config = computed(() => {
switch (authType.value) {
case 'register':
return { title: '创建账号', subtitle: '注册以享受更多会员权益', btn: '立即注册' }
case 'forgot':
return { title: '重置密码', subtitle: '请输入手机号以接收验证码', btn: '发送重置链接' }
default:
return { title: '欢迎回来', subtitle: '登录以保存您的使用记录', btn: '立即登录' }
}
})
// 处理提交
const handleSubmit = () => {
if (!phone.value) return alert('请输入手机号')
if (authType.value === 'login') {
userStore.login(phone.value)
} else if (authType.value === 'register') {
if (password.value !== confirmPassword.value) return alert('两次输入的密码不一致')
alert('注册逻辑待接入 API')
authType.value = 'login'
} else {
alert('重置密码逻辑待接入 API')
authType.value = 'login'
}
}
// 关闭弹窗时重置状态
const handleClose = () => {
userStore.closeModal()
setTimeout(() => {
authType.value = 'login'
phone.value = ''
password.value = ''
confirmPassword.value = ''
}, 300)
}
</script>
<template>
<div v-if="userStore.showLoginModal" class="modal-overlay" @click.self="handleClose">
<div class="modal-box">
<button class="close-btn" @click="handleClose">
<i class="fa-solid fa-xmark"></i>
</button>
<div class="modal-header">
<h2>{{ config.title }}</h2>
<p>{{ config.subtitle }}</p>
</div>
<div class="form-group">
<div class="input-wrapper">
<i class="fa-solid fa-mobile-screen input-icon"></i>
<input v-model="phone" type="text" class="modal-input" placeholder="手机号">
</div>
<div class="input-wrapper" v-if="authType !== 'forgot'">
<i class="fa-solid fa-lock input-icon"></i>
<input v-model="password" type="password" class="modal-input" placeholder="密码">
</div>
<div class="input-wrapper" v-if="authType === 'register'">
<i class="fa-solid fa-shield-halved input-icon"></i>
<input v-model="confirmPassword" type="password" class="modal-input" placeholder="确认密码">
</div>
<div class="form-options" v-if="authType === 'login'">
<a @click="authType = 'forgot'" class="link-text">忘记密码?</a>
</div>
</div>
<button class="btn-submit" @click="handleSubmit">
{{ config.btn }} <i class="fa-solid fa-arrow-right"></i>
</button>
<div class="modal-footer">
<span v-if="authType === 'login'">
还没有账号? <a @click="authType = 'register'">去注册</a>
</span>
<span v-else>
已有账号? <a @click="authType = 'login'">去登录</a>
</span>
</div>
</div>
</div>
</template>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
}
.modal-box {
background: var(--bg-card);
width: 380px;
max-width: 90%;
padding: 40px 30px 30px;
border-radius: var(--radius);
box-shadow: var(--shadow-md);
position: relative;
display: flex;
flex-direction: column;
gap: 20px;
animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.close-btn {
position: absolute;
top: 20px;
right: 20px;
border: none;
background: transparent;
font-size: 1.2rem;
color: var(--text-sub);
cursor: pointer;
transition: all 0.2s;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
background: var(--bg-body);
color: var(--text-main);
}
.modal-header h2 {
margin: 0;
font-size: 1.75rem;
color: var(--text-main);
font-weight: 700;
}
.modal-header p {
margin: 8px 0 0;
color: var(--text-sub);
font-size: 0.95rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 16px;
}
.input-wrapper {
display: flex;
align-items: center;
width: 100%;
height: 52px;
background: var(--bg-input);
border: 2px solid var(--border-color);
border-radius: var(--radius);
padding: 0 16px;
transition: all 0.2s;
box-sizing: border-box;
}
.input-icon {
font-size: 1rem;
color: var(--text-placeholder);
margin-right: 12px;
flex-shrink: 0;
display: flex;
align-items: center;
height: 100%;
}
.modal-input {
flex: 1;
width: 100%;
height: 100%;
border: none !important;
background: transparent !important;
outline: none !important;
padding: 0 !important;
margin: 0 !important;
font-size: 1rem;
color: var(--text-main);
vertical-align: middle;
}
.input-wrapper:focus-within {
background: var(--bg-card);
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.input-wrapper:focus-within .input-icon {
color: var(--primary);
}
.form-options {
text-align: right;
}
.link-text {
font-size: 0.85rem;
color: var(--text-sub);
cursor: pointer;
text-decoration: none;
transition: color 0.2s;
}
.link-text:hover {
color: var(--primary);
}
.btn-submit {
width: 100%;
padding: 14px;
border: none;
border-radius: var(--radius);
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: white;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.2s;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.btn-submit:hover {
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
}
.btn-submit:active {
transform: scale(0.98);
}
.modal-footer {
text-align: center;
font-size: 0.9rem;
color: var(--text-sub);
margin-top: 10px;
}
.modal-footer a {
color: var(--primary);
font-weight: 600;
cursor: pointer;
margin-left: 4px;
}
.modal-footer a:hover {
text-decoration: underline;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px) scale(0.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
</style>

View File

@@ -0,0 +1,163 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({
title: String,
desc: String,
icon: String,
isVip: Boolean,
// 传入 main.css 中定义的颜色名,例如 'blue', 'purple', 'orange'
colorTheme: {
type: String,
default: 'blue'
}
})
// 动态生成符合 main.css 规范的类名,例如 "icon-bg-blue"
// 这样就能利用全局 CSS 中定义的 Dark Mode 适配逻辑
const iconThemeClass = computed(() => `icon-bg-${props.colorTheme}`)
</script>
<template>
<div class="tool-card">
<!-- VIP 标签 (使用 main.css 的基础样式并增强定位) -->
<div v-if="isVip" class="vip-badge card-badge">
<i class="fa-solid fa-crown"></i> VIP
</div>
<div class="card-content">
<!-- 图标容器: 结合全局颜色类和局部布局类 -->
<div class="icon-box" :class="iconThemeClass">
<i :class="icon"></i>
</div>
<!-- 文本区域 -->
<div class="text-area">
<h3 class="card-title">
{{ title }}
<!-- 悬停出现的箭头 -->
<span class="arrow-icon"></span>
</h3>
<p class="card-desc">{{ desc }}</p>
</div>
</div>
</div>
</template>
<style scoped>
/*
注意:这里不再定义颜色变量,而是直接使用 main.css 中的变量
这样可以确保切换 html.dark 时组件自动变色
*/
.tool-card {
/* 使用全局变量 */
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius); /* 使用全局圆角 */
box-shadow: var(--shadow-sm);
padding: 1.5rem;
position: relative;
cursor: pointer;
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
/* 动效配置 */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.3s ease,
border-color 0.3s ease;
}
/* 悬停状态 */
.tool-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-md);
border-color: var(--border-hover);
}
/* 图标容器样式 */
.icon-box {
width: 52px;
height: 52px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 1.2rem;
/* 颜色由 main.css 的 .icon-bg-xxx 类控制,这里只控制动效 */
transition: transform 0.4s ease;
}
/* 悬停时图标微动 */
.tool-card:hover .icon-box {
transform: scale(1.1) rotate(3deg);
}
/* 标题样式 */
.card-title {
font-size: 1.15rem;
font-weight: 700;
color: var(--text-main); /* 适配暗黑模式 */
margin-bottom: 0.6rem;
display: flex;
align-items: center;
justify-content: space-between;
transition: color 0.3s;
}
/* 悬停时标题高亮 - 使用全局主色 */
.tool-card:hover .card-title {
color: var(--primary);
}
/* 描述文字 */
.card-desc {
font-size: 0.9rem;
color: var(--text-sub); /* 适配暗黑模式 */
line-height: 1.6;
/* 多行文本截断 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/*
VIP 徽章定位调整
颜色样式已在 main.css 的 .vip-badge 中定义
这里只处理它在卡片中的位置
*/
.card-badge {
position: absolute;
top: 12px;
right: 12px;
padding: 4px 10px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 800;
display: flex;
align-items: center;
gap: 4px;
z-index: 10;
}
/* 箭头图标 */
.arrow-icon {
font-size: 1rem;
opacity: 0;
transform: translateX(-10px);
transition: all 0.3s ease;
color: var(--primary); /* 使用全局主色 */
}
.tool-card:hover .arrow-icon {
opacity: 1;
transform: translateX(0);
}
</style>

14
Zkit-C/src/main.js Normal file
View File

@@ -0,0 +1,14 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css' // 引入全局样式
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

View File

@@ -0,0 +1,26 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// 路由懒加载
const Md5View = () => import('../views/Md5View.vue')
const PdfView = () => import('../views/PdfView.vue')
const AllToolsView = () => import('../views/AllToolsView.vue') // 新增
const FaqView = () => import('../views/FaqView.vue') // 新增
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', name: 'home', component: HomeView },
{ path: '/tool/md5', name: 'md5', component: Md5View },
{ path: '/tool/pdf', name: 'pdf', component: PdfView },
// 新增路由
{ path: '/all-tools', name: 'all-tools', component: AllToolsView },
{ path: '/faq', name: 'faq', component: FaqView }
],
scrollBehavior() {
return { top: 0 }
}
})
export default router

43
Zkit-C/src/stores/user.js Normal file
View File

@@ -0,0 +1,43 @@
// src/stores/user.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
// 状态 State
const user = ref(localStorage.getItem('omni_user') || null)
const isVip = ref(localStorage.getItem('omni_vip') === 'true')
const showLoginModal = ref(false) // 控制全局登录弹窗显示
// 计算属性 Getters
const isLoggedIn = computed(() => !!user.value)
// 动作 Actions
function login(username) {
user.value = username
localStorage.setItem('omni_user', username)
localStorage.setItem('omni_vip', 'false')
showLoginModal.value = false
}
function logout() {
user.value = null
isVip.value = false
localStorage.removeItem('omni_user')
localStorage.removeItem('omni_vip')
}
function upgradeVip() {
isVip.value = true
localStorage.setItem('omni_vip', 'true')
}
function openModal() {
showLoginModal.value = true
}
function closeModal() {
showLoginModal.value = false
}
return { user, isVip, isLoggedIn, showLoginModal, login, logout, upgradeVip, openModal, closeModal }
})

View File

@@ -0,0 +1,552 @@
<script setup>
import { useRouter } from 'vue-router'
import { ref, computed } from 'vue'
const router = useRouter()
// 1. 定义分类配置
const categories = [
{ id: 'all', name: '全部', icon: 'fa-solid fa-layer-group' },
{ id: 'dev', name: '开发', icon: 'fa-solid fa-code' },
{ id: 'media', name: '媒体', icon: 'fa-solid fa-photo-film' },
{ id: 'crypto', name: '加密', icon: 'fa-solid fa-shield-halved' },
{ id: 'life', name: '生活', icon: 'fa-solid fa-mug-hot' },
]
// 当前选中的分类 ID
const activeCategory = ref('all')
// 2. 工具数据
const rawTools = [
{ title: 'PDF 转 Word', desc: '精准保持排版,一键转换', icon: 'fa-solid fa-file-word', color: 'blue', vip: true, path: '/tool/pdf', category: 'media' },
{ title: '图片压缩', desc: '智能压缩,保持清晰度', icon: 'fa-solid fa-image', color: 'rose', vip: false, category: 'media' },
{ title: '图片去底', desc: 'AI 自动识别主体抠图', icon: 'fa-solid fa-wand-magic-sparkles', color: 'fuchsia', vip: false, category: 'media' },
{ title: '视频转 GIF', desc: '截取片段生成表情包', icon: 'fa-solid fa-film', color: 'orange', vip: true, category: 'media' },
{ title: '二维码生成', desc: '自定义颜色、Logo嵌入', icon: 'fa-solid fa-qrcode', color: 'gray', vip: false, category: 'media' },
{ title: 'SQL 转 ER', desc: '一键生成数据库关系图', icon: 'fa-solid fa-database', color: 'cyan', vip: true, category: 'dev' },
{ title: 'JSON 格式化', desc: '校验、高亮、折叠代码', icon: 'fa-solid fa-code', color: 'emerald', vip: false, category: 'dev' },
{ title: '正则测试', desc: '实时匹配,包含常用公式', icon: 'fa-solid fa-check-double', color: 'violet', vip: false, category: 'dev' },
{ title: 'Unix 时间戳', desc: '各时区时间快速转换', icon: 'fa-solid fa-clock', color: 'amber', vip: false, category: 'dev' },
{ title: 'MD5 加密', desc: '不可逆加密,支持多种盐值', icon: 'fa-solid fa-lock', color: 'indigo', vip: false, path: '/tool/md5', category: 'crypto' },
{ title: 'Base64 转码', desc: '文件与字符串互转', icon: 'fa-solid fa-font', color: 'slate', vip: false, category: 'crypto' },
{ title: '随机密码', desc: '自定义长度与字符集', icon: 'fa-solid fa-key', color: 'teal', vip: false, category: 'crypto' },
]
// 广告数据池
const adPool = [
{ title: '云服务器', desc: '新用户首年99元', tag: '广告', icon: 'fa-solid fa-server', btnText: '抢购' },
{ title: 'AI 写作', desc: '自动生成文案', tag: '广告', icon: 'fa-solid fa-robot', btnText: '试用' },
]
// 3. 计算逻辑
const displayList = computed(() => {
let tools = activeCategory.value === 'all' ? rawTools : rawTools.filter(t => t.category === activeCategory.value)
const list = []
let adIndex = 0
tools.forEach((tool, index) => {
list.push({ type: 'tool', ...tool })
// 每 6 个工具插一个广告
if (index > 0 && (index + 1) % 6 === 0) {
const ad = adPool[adIndex % adPool.length]
list.push({ type: 'ad', ...ad })
adIndex++
}
})
return list
})
const getColorClass = (color) => `icon-bg-${color}`
const handleToolClick = (item) => { if (item.path) router.push(item.path) }
const setCategory = (id) => {
activeCategory.value = id
if(window.innerWidth < 768) {
const offset = document.querySelector('.main-layout').offsetTop - 140
window.scrollTo({ top: Math.max(0, offset), behavior: 'smooth' })
} else {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}
</script>
<template>
<div class="page-container">
<div class="content-wrapper">
<div class="header-area">
<span class="header-subtitle">ZKit Tools</span>
<h1>全能在线工具库</h1>
<p>收录 <strong>{{ rawTools.length }}+</strong> 款开发者与设计师必备工具</p>
</div>
<div class="main-layout">
<aside class="category-sidebar">
<div class="sidebar-track">
<div
v-for="cat in categories"
:key="cat.id"
class="nav-item"
:class="{ active: activeCategory === cat.id }"
@click="setCategory(cat.id)"
>
<i :class="cat.icon"></i>
<span>{{ cat.name }}</span>
</div>
</div>
</aside>
<main class="tools-section">
<transition name="fade" mode="out-in">
<div v-if="displayList.length === 0" class="empty-state">
<i class="fa-solid fa-box-open"></i>
<p>该分类下暂无工具</p>
</div>
<div v-else class="tools-grid" :key="activeCategory">
<div
v-for="(item, index) in displayList"
:key="index"
class="card-wrapper"
:style="{ animationDelay: `${index * 0.03}s` }"
>
<!-- 普通工具卡片 -->
<div
v-if="item.type === 'tool'"
class="card tool-card"
@click="handleToolClick(item)"
>
<div v-if="item.vip" class="vip-badge">VIP</div>
<div class="card-content">
<div class="icon-wrapper" :class="getColorClass(item.color)">
<i :class="item.icon"></i>
</div>
<div class="text-content">
<h3 class="tool-title">{{ item.title }}</h3>
<p class="tool-desc">{{ item.desc }}</p>
</div>
</div>
<!-- 动态箭头 -->
<div class="arrow-indicator">
<i class="fa-solid fa-arrow-right"></i>
</div>
<div class="hover-overlay"></div>
</div>
<!-- 广告卡片已统一为箭头样式 -->
<div v-else class="card ad-card">
<!-- 广告标签 (类似VIP标签) -->
<div class="ad-badge">{{ item.tag }}</div>
<div class="card-content">
<!-- 广告图标背景使用与工具卡片相同的样式 -->
<div class="icon-wrapper" :class="getColorClass('blue')">
<i :class="item.icon"></i>
</div>
<div class="text-content">
<h3 class="tool-title">{{ item.title }}</h3>
<p class="tool-desc">{{ item.desc }}</p>
</div>
</div>
<!-- 统一的动态箭头 (代替原按钮) -->
<div class="arrow-indicator">
<i class="fa-solid fa-arrow-right"></i>
</div>
<div class="hover-overlay"></div>
</div>
</div>
</div>
</transition>
</main>
</div>
</div>
</div>
</template>
<style scoped>
/* ---------- 基础变量适配 ---------- */
:root {
--sidebar-width: 200px;
}
.page-container {
background: var(--bg-body);
min-height: 100vh;
padding: 2rem 20px;
color: var(--text-sub);
}
.content-wrapper {
max-width: 1200px;
margin: 0 auto;
}
/* ---------- 头部区域 ---------- */
.header-area {
text-align: center;
margin-bottom: 3rem;
animation: fadeInDown 0.6s ease-out;
}
.header-subtitle {
display: inline-block;
font-size: 0.75rem;
font-weight: 700;
color: var(--primary);
background: rgba(37, 99, 235, 0.1);
padding: 4px 10px;
border-radius: 99px;
margin-bottom: 0.8rem;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.header-area h1 {
font-size: 2.2rem;
color: var(--text-main);
font-weight: 800;
margin: 0 0 0.5rem 0;
letter-spacing: -1px;
}
.header-area p {
color: var(--text-sub);
font-size: 1rem;
}
/* ---------- 布局结构 ---------- */
.main-layout {
display: flex;
gap: 32px;
align-items: flex-start;
position: relative;
}
/* ---------- 侧边栏 / 导航 ---------- */
.category-sidebar {
width: var(--sidebar-width);
flex-shrink: 0;
position: sticky;
top: 90px;
}
.sidebar-track {
background: var(--bg-card);
border-radius: 12px;
padding: 8px;
border: 1px solid var(--border-color);
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
display: flex; align-items: center; gap: 10px;
padding: 10px 14px;
border-radius: 8px;
cursor: pointer;
color: var(--text-sub);
font-weight: 500;
font-size: 0.95rem;
transition: all 0.2s;
}
.nav-item:hover {
background: var(--bg-body);
color: var(--text-main);
}
.nav-item.active {
background: var(--primary);
color: #fff;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
}
.nav-item i { width: 20px; text-align: center; }
/* ---------- 右侧网格 ---------- */
.tools-section { flex: 1; min-width: 0; }
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
}
.card-wrapper {
animation: fadeInUp 0.5s forwards;
opacity: 0;
}
/* ---------- 卡片通用 ---------- */
.card {
background: var(--bg-card);
border-radius: 16px;
padding: 20px;
height: 100%;
position: relative;
border: 1px solid var(--border-color);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
cursor: pointer;
overflow: hidden;
display: flex;
flex-direction: column;
}
.card:hover {
transform: translateY(-4px);
border-color: var(--border-hover);
box-shadow: var(--shadow-md);
}
/* Tool & Ad Card 内容通用布局 */
.card-content {
display: flex;
flex-direction: column;
gap: 16px;
height: 100%;
}
/* 图标容器 */
.icon-wrapper {
width: 48px; height: 48px; border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 1.3rem; flex-shrink: 0;
transition: transform 0.3s;
}
.card:hover .icon-wrapper { transform: scale(1.1); }
/* 文字 */
.tool-title {
font-size: 1.05rem; font-weight: 700; color: var(--text-main);
margin: 0 0 6px 0;
line-height: 1.3;
}
.tool-desc {
font-size: 0.85rem; color: var(--text-sub); margin: 0;
line-height: 1.5;
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
}
/* 统一箭头动画样式 */
.arrow-indicator {
position: absolute;
bottom: 20px;
right: 20px;
color: var(--primary);
opacity: 0; /* 默认隐藏 */
transform: translateX(-10px);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
font-size: 1rem;
}
/* 悬停显现 */
.card:hover .arrow-indicator {
opacity: 1;
transform: translateX(0);
}
/* 标签样式 (VIP & 广告) */
.vip-badge, .ad-badge {
position: absolute; top: 12px; right: 12px;
font-size: 0.6rem; font-weight: 800; padding: 2px 6px; border-radius: 4px;
z-index: 2;
}
.vip-badge {
background: #fffbeb; color: #b45309; border: 1px solid #fde68a;
}
.ad-badge {
background: var(--primary); color: #fff; opacity: 0.8;
}
/* Ad Card 样式 - 完全匹配工具卡片,只保留虚线边框作为区分 */
.ad-card {
/* 使用与普通卡片相同的背景和边框变量 */
background: var(--bg-card);
border: 1px dashed var(--border-color);
}
/* 广告图标使用与工具卡片相同的样式,无需特殊处理 */
/* 移除原有的 .ad-icon-bg 样式 */
/* 动画 */
@keyframes fadeInDown { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
/* =========================================
📱 移动端布局终极修复 - 适配夜间模式
========================================= */
@media (max-width: 768px) {
.page-container {
padding: 1rem 12px;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.content-wrapper { width: 100%; margin: 0; }
/* 头部区域 */
.header-area {
display: block;
text-align: center;
margin-bottom: 1.5rem;
padding: 0 4px;
}
.header-subtitle {
display: inline-block;
font-size: 0.65rem;
padding: 3px 8px;
margin-bottom: 8px;
}
.header-area h1 {
font-size: 1.8rem;
margin-bottom: 0.5rem;
line-height: 1.2;
}
.header-area p {
font-size: 0.9rem;
opacity: 0.9;
max-width: 90%;
margin: 0 auto;
}
/* 布局重置 */
.main-layout {
display: block;
width: 100%;
margin: 0;
gap: 0;
}
/* 侧边栏 - 适配夜间模式 */
.category-sidebar {
width: 100% !important;
position: sticky;
top: 0;
z-index: 10;
margin: 0 0 12px 0;
padding: 8px 4px;
/* 使用CSS变量适配夜间模式 */
background: var(--bg-body);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border-color);
}
.sidebar-track {
display: flex;
flex-direction: row;
overflow-x: auto;
gap: 8px;
padding: 0 4px;
border: none;
background: transparent;
scrollbar-width: none;
}
.sidebar-track::-webkit-scrollbar { display: none; }
.nav-item {
flex-shrink: 0;
padding: 6px 14px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 99px;
font-size: 0.85rem;
margin: 0;
color: var(--text-sub);
}
.nav-item.active {
background: var(--primary);
color: #fff;
border-color: var(--primary);
}
.tools-section { width: 100% !important; display: block; }
/* 网格布局 */
.tools-grid {
display: grid;
width: 100%;
grid-template-columns: 1fr 1fr;
gap: 10px;
box-sizing: border-box;
}
/* 卡片样式统一 (工具 & 广告现在完全一样) */
.card {
padding: 14px;
width: 100%;
box-sizing: border-box;
/* 因为没有了大按钮,高度统一为 155px 即可 */
height: 155px;
display: flex;
flex-direction: column;
align-items: flex-start;
text-align: left;
justify-content: space-between;
}
.card-content {
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
}
.icon-wrapper {
width: 40px; height: 40px;
font-size: 1rem;
border-radius: 10px;
margin: 0;
}
.text-content { width: 100%; }
.tool-title {
font-size: 0.9rem;
margin-bottom: 4px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
width: 100%;
color: var(--text-main);
}
.tool-desc {
font-size: 0.75rem;
line-height: 1.4;
color: var(--text-sub);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
height: 2.8em;
padding-right: 14px;
}
/* 箭头适配移动端:始终显示,但颜色淡 */
.arrow-indicator {
opacity: 1;
transform: none;
bottom: 12px;
right: 12px;
font-size: 0.8rem;
color: var(--text-sub); /* 使用主题变量 */
}
.vip-badge, .ad-badge { top: 10px; right: 10px; font-size: 0.6rem; padding: 1px 5px; }
}
</style>

View File

@@ -0,0 +1,215 @@
<script setup>
import { ref } from 'vue'
const faqs = ref([
{ q: 'OmniKit 需要下载安装吗?', a: '不需要。OmniKit 是完全基于浏览器的云端工具箱,您只需打开网页即可在 Windows、Mac 或手机上使用所有工具。', open: true },
{ q: '我的文件安全吗?会泄露吗?', a: '非常安全。对于图片压缩、MD5 计算等工具,我们采用 WebAssembly 技术在您本地浏览器处理,文件从未上传至服务器。对于 PDF 转换等必须上传的工具,文件会在处理完成 1 小时后从服务器物理删除。', open: false },
{ q: '如何开通 VIP 会员?', a: '点击右上角的“登录”按钮,注册或登录账号后,在个人中心选择“升级 VIP”。支持微信和支付宝支付开通后即时生效。', open: false },
{ q: 'VIP 有什么特权?', a: '1. 解锁 PDF 转 Word、视频处理等高算力工具2. 移除全站所有广告3. 优先获得极速服务器资源4. 专属客服支持。', open: false },
{ q: '如果转换失败怎么办?', a: '如果遇到文件转换失败,通常是因为文件加密或损坏。您可以尝试重新上传,或者通过底部的“联系我们”发送邮件反馈,我们的人工客服会帮您处理。', open: false },
])
const toggle = (index) => {
faqs.value[index].open = !faqs.value[index].open
}
// 客服点击事件
const handleContact = () => {
// 这里可以换成跳转到工单系统,或者弹出邮箱
alert('客服邮箱support@omnikit.com\n工作时间周一至周五 9:00 - 18:00')
}
</script>
<template>
<div class="page-container">
<div class="container faq-wrapper">
<h1 class="page-title">常见问题解答</h1>
<p class="page-sub">这里列出了用户最关心的问题希望能帮到您</p>
<!-- 问题列表 -->
<div class="faq-list">
<div
v-for="(item, index) in faqs"
:key="index"
class="faq-item"
:class="{ active: item.open }"
@click="toggle(index)"
>
<div class="faq-question">
<span>{{ item.q }}</span>
<i class="fa-solid" :class="item.open ? 'fa-chevron-up' : 'fa-chevron-down'"></i>
</div>
<div class="faq-answer" v-show="item.open">
{{ item.a }}
</div>
</div>
</div>
<!-- 新增联系客服模块 -->
<div class="contact-card">
<div class="contact-icon">
<i class="fa-solid fa-headset"></i>
</div>
<div class="contact-content">
<h3>没有找到想要的答案</h3>
<p>如果您遇到技术问题或有商务合作意向欢迎随时联系我们</p>
</div>
<button class="btn-contact" @click="handleContact">联系客服</button>
</div>
<!-- 页面底部广告 -->
<div class="ad-banner">
<i class="fa-brands fa-google"></i> 广告位相关服务推荐
</div>
</div>
</div>
</template>
<style scoped>
/* ---------- 基础 ---------- */
.page-container {
padding: 4rem 0;
min-height: 80vh;
background: var(--bg-body); /* 变量 */
}
.faq-wrapper {
max-width: 800px;
margin: 0 auto;
}
.page-title {
text-align: center;
font-size: 2rem;
color: var(--text-main); /* 变量 */
margin-bottom: 0.5rem;
font-weight: 800;
}
.page-sub {
text-align: center;
color: var(--text-sub); /* 变量 */
margin-bottom: 3rem;
}
/* ---------- FAQ 列表 ---------- */
.faq-item {
border-bottom: 1px solid var(--border-color); /* 变量 */
margin-bottom: 1rem;
}
.faq-question {
padding: 1.2rem 0;
font-weight: 600;
font-size: 1.1rem;
color: var(--text-main); /* 变量 */
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: color 0.2s;
}
.faq-question:hover {
color: var(--primary); /* 变量 */
}
.faq-answer {
padding-bottom: 1.5rem;
color: var(--text-sub); /* 变量 */
line-height: 1.7;
font-size: 0.95rem;
animation: slideDown 0.2s ease-out;
}
.faq-item.active .faq-question {
color: var(--primary); /* 变量 */
}
/* ---------- 联系客服卡片 ---------- */
.contact-card {
background: var(--bg-card);
border: 1px solid var(--border-hover);
border-radius: var(--radius);
padding: 2rem;
margin-top: 3rem;
display: flex;
align-items: center;
gap: 20px;
transition: background 0.3s, border 0.3s;
}
.contact-icon {
width: 50px;
height: 50px;
background: var(--bg-body);
color: var(--primary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
flex-shrink: 0;
transition: background 0.3s;
}
.contact-content {
flex: 1;
}
.contact-content h3 {
margin: 0 0 5px 0;
color: var(--text-main); /* 变量 */
font-size: 1.1rem;
font-weight: 700;
}
.contact-content p {
margin: 0;
color: var(--text-sub); /* 变量 */
font-size: 0.95rem;
}
.btn-contact {
background: var(--primary); /* 变量 */
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
white-space: nowrap;
}
.btn-contact:hover {
background: var(--primary-dark); /* 变量 */
}
/* ---------- 底部广告位 ---------- */
.ad-banner {
margin-top: 2rem;
height: 100px;
background: var(--bg-ad);
border: 2px dashed var(--border-color);
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-placeholder);
transition: background 0.3s, border 0.3s;
}
/* ---------- 动画 ---------- */
@keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
/* ---------- 移动端 ---------- */
@media (max-width: 640px) {
.contact-card {
flex-direction: column;
text-align: center;
padding: 1.5rem;
}
.contact-content p {
margin-bottom: 1rem;
}
.btn-contact {
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,384 @@
<script setup>
import { useRouter } from 'vue-router'
import ToolCard from '@/components/ToolCard.vue'
const router = useRouter()
</script>
<template>
<div class="home-container">
<!-- 1. Hero 区域 -->
<div class="hero-wrapper">
<h1 class="hero-title">
瞬间解决繁琐工作<br>
<span>让效率触手可及</span>
</h1>
<p class="hero-desc">集成 50+ 款开发者设计师及办公必备的在线工具无需下载安全隐私</p>
<div class="search-box">
<i class="fa-solid fa-magnifying-glass"></i>
<input placeholder="搜索工具,例如: PDF 转 Word, MD5, 正则..." />
</div>
<div class="hot-tags">
<span class="label">热门搜索:</span>
<div class="tag-list">
<span class="tag">PDF转换</span>
<span class="tag">SQL格式化</span>
<span class="tag">颜色提取</span>
<span class="tag">Base64</span>
</div>
</div>
</div>
<!-- 2. 统计栏 (悬浮) -->
<div class="stats-container container">
<div class="stats-bar">
<div class="stat-item">
<span>50+</span> <small>免费在线工具</small>
</div>
<div class="stat-item">
<span>0s</span> <small>无需安装等待</small>
</div>
<div class="stat-item">
<span>100%</span> <small>本地处理更安全</small>
</div>
</div>
</div>
<!-- 3. 主内容区 -->
<div class="container content-area">
<div class="ad-banner-top">
<i class="fa-brands fa-google"></i> &nbsp; Google AdSense 顶部横幅 (Leaderboard)
</div>
<!-- 第一部分热门工具 -->
<div class="section-header">
<h2 class="section-title">
<i class="fa-solid fa-fire"></i> 热门工具
</h2>
<router-link to="/all-tools" active-class="active-link" class="view-all">查看全部 <i class="fa-solid fa-arrow-right"></i></router-link>
</div>
<div class="tools-grid">
<ToolCard
title="PDF 转 Word"
desc="精准保留原文档格式,一键转换为可编辑 Word。"
icon="fa-solid fa-file-word"
bgClass="bg-blue"
:isVip="true"
@click="router.push('/tool/pdf')"
/>
<ToolCard
title="MD5 加密"
desc="不可逆加密算法,支持批量生成与校验。"
icon="fa-solid fa-lock"
bgClass="bg-gray"
@click="router.push('/tool/md5')"
/>
<ToolCard
title="图片压缩"
desc="智能压缩 PNG/JPG 图片体积,画质几乎无损。"
icon="fa-solid fa-image"
bgClass="bg-gray"
/>
<ToolCard
title="图片去底"
desc="AI 自动识别主体3秒钟完成一键抠图。"
icon="fa-solid fa-wand-magic-sparkles"
bgClass="bg-gray"
/>
</div>
<div class="ad-banner-middle">
<i class="fa-solid fa-ad"></i> 列表中间广告位 (Banner)
</div>
<!-- 第二部分开发人员 -->
<div class="section-header">
<h2 class="section-title">
<i class="fa-solid fa-code" style="color: var(--primary);"></i> 开发人员
</h2>
</div>
<div class="tools-grid">
<ToolCard
title="SQL 转 ER 图"
desc="粘贴建表语句,自动生成数据库关系图表。"
icon="fa-solid fa-database"
bgClass="bg-gray"
:isVip="true"
/>
<ToolCard
title="JSON 格式化"
desc="校验、美化、压缩 JSON 数据,开发必备。"
icon="fa-solid fa-brackets-curly"
bgClass="bg-gray"
/>
<ToolCard
title="Unix 时间戳"
desc="时间戳与北京时间互转,支持毫秒级。"
icon="fa-solid fa-clock"
bgClass="bg-gray"
/>
<ToolCard
title="Crontab 表达式"
desc="Linux 定时任务可视化生成器,无需记忆语法。"
icon="fa-solid fa-terminal"
bgClass="bg-gray"
/>
</div>
</div>
</div>
</template>
<style scoped>
/* 动画定义 */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* --- 英雄区域 --- */
.hero-wrapper {
text-align: center;
padding: 5rem 1rem 7rem;
background-image: radial-gradient(var(--dot-color) 1px, transparent 1px);
background-size: 20px 20px;
background-color: var(--bg-body);
transition: background-color 0.3s ease;
}
.hero-title {
font-size: 3.2rem;
font-weight: 900;
color: var(--text-main);
margin-bottom: 1.5rem;
line-height: 1.3;
letter-spacing: -1px;
animation: fadeInDown 0.8s ease-out;
}
.hero-title span {
color: var(--primary);
display: block;
margin-top: 5px;
}
.hero-desc {
color: var(--text-sub);
margin-bottom: 2.5rem;
font-size: 1.15rem;
animation: fadeInDown 0.8s ease-out 0.1s backwards;
}
/* --- 搜索框 --- */
.search-box {
max-width: 600px;
margin: 0 auto;
position: relative;
animation: fadeInDown 0.8s ease-out 0.2s backwards;
}
.search-box input {
width: 100%;
padding: 18px 25px 18px 55px;
border-radius: 50px;
background: var(--bg-input);
color: var(--text-main);
border: 1px solid var(--border-color);
outline: none;
font-size: 1.05rem;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.05);
transition: 0.3s;
}
.search-box input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(37,99,235,0.15);
}
.search-box input::placeholder {
color: var(--text-sub);
opacity: 0.7;
}
.search-box i {
position: absolute;
left: 25px;
top: 50%;
transform: translateY(-50%);
color: var(--text-sub);
font-size: 1.2rem;
}
/* --- 热门标签 --- */
.hot-tags {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-top: 25px;
color: var(--text-sub);
font-size: 0.95rem;
animation: fadeInDown 0.8s ease-out 0.3s backwards;
}
.tag-list {
display: flex;
gap: 10px;
}
.tag {
background: var(--bg-card);
border: 1px solid var(--border-color);
padding: 6px 16px;
border-radius: 8px;
color: var(--text-sub);
cursor: pointer;
transition: all 0.2s;
}
.tag:hover {
color: var(--primary);
border-color: var(--border-hover);
transform: translateY(-1px);
}
/* --- 统计栏(悬浮) --- */
.stats-container {
position: relative;
margin-top: -40px;
z-index: 10;
animation: fadeInDown 0.8s ease-out 0.4s backwards;
}
.stats-bar {
background: var(--bg-card);
border-radius: 16px;
padding: 2rem;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-color);
display: flex;
justify-content: space-around;
transition: background 0.3s ease;
}
.stat-item {
text-align: center;
display: flex;
flex-direction: column;
}
.stat-item span {
font-size: 1.8rem;
font-weight: 800;
color: var(--primary);
margin-bottom: 5px;
}
.stat-item:nth-child(2) span {
color: #10b981;
}
.stat-item:nth-child(3) span {
color: var(--accent);
}
.stat-item small {
color: var(--text-sub);
font-size: 0.9rem;
}
/* --- 主内容区 --- */
.content-area {
padding-bottom: 4rem;
margin-top: 2rem;
}
.ad-banner-top {
margin-top: 2rem;
margin-bottom: 1rem;
height: 100px;
background: var(--bg-ad);
border: 2px dashed var(--border-color);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-sub);
font-size: 0.9rem;
}
.ad-banner-middle {
margin: 4rem 0;
padding: 2rem;
background: var(--bg-ad);
border: 2px dashed var(--border-color);
border-radius: 12px;
color: var(--text-sub);
text-align: center;
font-size: 0.9rem;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
margin-top: 2rem;
}
.section-title {
font-size: 1.5rem;
color: var(--text-main);
display: flex;
align-items: center;
gap: 10px;
}
.section-title i {
color: #ef4444;
}
.view-all {
color: var(--primary);
font-size: 0.9rem;
}
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
gap: 1.5rem;
}
@media (max-width: 768px) {
.stats-bar {
flex-direction: column;
gap: 2rem;
}
.hero-title {
font-size: 2.2rem;
}
.hot-tags {
flex-direction: column;
gap: 10px;
}
}
</style>

View File

@@ -0,0 +1,76 @@
<script setup>
import { ref } from 'vue'
import { useUserStore } from '@/stores/user'
import md5 from 'blueimp-md5'
const userStore = useUserStore()
const input = ref('')
const result = ref('')
const doEncrypt = () => {
// 权限控制:未登录则弹窗
if (!userStore.isLoggedIn) {
userStore.openModal()
return
}
if (!input.value) return alert('请输入内容')
result.value = md5(input.value).toUpperCase()
}
</script>
<template>
<div class="container layout-grid">
<main>
<div class="ad-banner">内容页顶部广告</div>
<div class="tool-content">
<h1>MD5 在线加密</h1>
<p class="tool-desc">输入文本生成 32 位哈希值</p>
<textarea v-model="input" class="tool-textarea"></textarea>
<button class="btn-full" @click="doEncrypt">生成 MD5</button>
<div v-if="result" class="result-box">
<strong>结果</strong> {{ result }}
</div>
</div>
</main>
<aside>
<div class="ad-sidebar">侧边栏广告</div>
<div class="ad-sidebar ad-sidebar-tall">长条广告</div>
</aside>
</div>
</template>
<style scoped>
.tool-desc {
color: var(--text-sub);
margin-bottom: 20px;
}
.tool-textarea {
width: 100%;
height: 120px;
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 8px;
background: var(--bg-input);
color: var(--text-main);
font-family: inherit;
resize: vertical;
}
.result-box {
margin-top: 20px;
background: var(--bg-body);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--border-color);
}
.ad-sidebar-tall {
height: 400px;
}
</style>

View File

@@ -0,0 +1,131 @@
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const handleUpgrade = () => {
if(confirm('确认支付 9.9元 开通 VIP')) {
userStore.upgradeVip()
alert('支付成功!')
}
}
</script>
<template>
<div class="container layout-grid">
<main>
<div class="ad-banner">VIP 页顶部广告</div>
<div class="tool-content tool-locked">
<!-- 真实内容 (根据权限决定是否模糊) -->
<div :class="{ 'content-blur': !userStore.isLoggedIn || !userStore.isVip }">
<h1>PDF Word</h1>
<div class="upload-area">
<i class="fa-solid fa-cloud-upload-alt upload-icon"></i>
<p>点击上传 PDF 文件</p>
</div>
<button class="btn-full">开始转换</button>
</div>
<!-- 遮罩层 -->
<div v-if="!userStore.isLoggedIn" class="modal-overlay lock-overlay">
<div class="lock-content">
<i class="fa-solid fa-lock lock-icon"></i>
<h3>请登录后使用</h3>
<button class="btn-login" @click="userStore.openModal()">立即登录</button>
</div>
</div>
<div v-else-if="!userStore.isVip" class="modal-overlay lock-overlay">
<div class="lock-content">
<i class="fa-solid fa-crown vip-icon"></i>
<h3>会员专享功能</h3>
<button class="btn-full btn-upgrade" @click="handleUpgrade">升级 VIP (¥9.9)</button>
</div>
</div>
</div>
</main>
<aside>
<div class="vip-promo">
<h3>升级 VIP</h3>
<p>解锁所有高级功能去除广告</p>
</div>
</aside>
</div>
</template>
<style scoped>
.tool-locked {
position: relative;
overflow: hidden;
}
.content-blur {
filter: blur(5px);
pointer-events: none;
}
.upload-area {
border: 2px dashed var(--border-color);
padding: 50px;
text-align: center;
margin-top: 30px;
border-radius: 12px;
background: var(--bg-body);
}
.upload-icon {
font-size: 3rem;
color: var(--text-placeholder);
}
.lock-overlay {
position: absolute;
}
.lock-content {
text-align: center;
}
.lock-icon {
font-size: 3rem;
color: var(--text-sub);
}
.vip-icon {
font-size: 3rem;
color: #f59e0b;
}
.btn-upgrade {
background: #f59e0b;
}
.vip-promo {
background: #fff7ed;
padding: 20px;
border-radius: 12px;
border: 1px solid #fed7aa;
}
.vip-promo h3 {
color: #d97706;
}
.vip-promo p {
font-size: 0.9rem;
color: #d97706;
margin-top: 10px;
}
html.dark .vip-promo {
background: rgba(217, 119, 6, 0.1);
border-color: rgba(217, 119, 6, 0.3);
}
html.dark .vip-promo h3,
html.dark .vip-promo p {
color: #fbbf24;
}
</style>