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

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>