commit f80e5c0b5a59e110b89e26e7c5829fe37edb0de9 Author: RhenCloud Date: Sat Dec 6 23:39:15 2025 +0800 feat: 初始化项目 - 初始化 Vue 3 + Vite 项目结构 - 添加核心组件 (Hero, About, Projects, Friends 等) - 配置页面路由和基础样式 - 添加服务端 API 接口 (邮件发送, 好友请求) - 添加项目配置文件 (vercel.json, vite.config.js) diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..facb12f --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +VITE_GITHUB_TOKEN=your-github-token + +# SMTP 服务器地址 +SMTP_HOST=smtp.example.com + +# SMTP 端口(不填写默认465) +SMTP_PORT=465 + +# SMTP 用户名(一般与SENDER_EMAIL相同) +SMTP_USER=your-email@example.com + +# SMTP 密码 +SMTP_PASS=your-email-password + +# 收件邮箱地址 +ADMIN_EMAIL=admin@example.com + +# 发件邮箱地址(一般与SMTP_USER相同) +SENDER_EMAIL=sender@example.com + +# 是否启用SSL +SMTP_SECURE=true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..14aaedd --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# dependencies +node_modules/ +pnpm-lock.yaml +package-lock.json +yarn.lock + +# builds +dist/ +build/ +.out/ +.next/ +.vercel/ +.vite/ + +# env & secrets +.env +.env.* +!.env.example + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +logs/ +*.log + +# editors & system +.vscode/ +.idea/ +.DS_Store +Thumbs.db + +# tests +tests/ +test* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b31524 --- /dev/null +++ b/README.md @@ -0,0 +1,168 @@ +# Cloud Home + +一个基于 Vue 的个人主页模板,内置友链申请、项目展示、友链随机展示、自定义配置,支持 Vercel 部署与邮件通知。 + +## 特性 + +- 🎨 个性化主页:头像、社交链接、技能、站点/项目列表可配置。 +- 🔗 友链模块:支持申请表单、邮件通知、随机顺序展示。 +- 📱 响应式设计:适配桌面与移动端。 +- ⚙️ Serverless 部署支持 + +## 技术栈 + +- 前端:Vue 3 + HTML + CSS +- 构建:Vite +- 部署:Vercel(静态构建 + Serverless Functions) + +## 配置指南 + +### 站点配置文件 (`src/config/siteConfig.ts`) + +本项目的所有静态内容配置均集中在 `src/config/siteConfig.ts` 文件中。 + +```typescript +const siteConfig: SiteConfig = { + profile: { + name: "Example User", // 你的名字 + title: "I'm a software developer.", // 你的简介,可为空 + avatar: "avatar.webp", // 你的头像,可为public目录下的文件或外部链接 + bio: "Hello World", // 你的喜欢的一句话,可为空 + }, + + // 社交链接,预定义的社交链接可在 `src/components/SocialLink.vue` 中查阅 + socialLinks: [ + { name: "GitHub", url: "https://github.com/ExampleUser" }, + { name: "Email", url: "mailto:you@example.com" }, + { name: "Telegram", url: "https://t.me/ExampleUser" }, + { name: "Bilibili", url: "https://space.bilibili.com/1502883335" }, + { name: "Blog", url: "https://blog.example.com" }, + ], + + github: { + username: "ExampleUser", // 你的 GitHub 用户名 + }, + + // 个人介绍卡片 + about: [ + { title: "Example", desc: "Example description", icon: "🧠" }, + { title: "Example", desc: "Example description", icon: "🛠️" }, + { title: "Example", desc: "Example description", icon: "🎬" }, + { title: "Example", desc: "Example description", icon: "🎮" }, + ], + + siteMeta: { + title: "RhenCloud", // 网站标题 + icon: "favicon.ico", // 网站图标,可为public目录下的文件或外部链接 + }, + + // 技能图标展示,详见https://github.com/tandpfun/skill-icons#icons-list + skills: [ + { title: "前端", items: ["css", "html", "javascript", "typescript", "vue"] }, + { title: "后端 / 云", items: ["cpp", "cloudflare", "docker", "java", "mysql", "nodejs", "python", "vercel"] }, + { title: "工具", items: ["ae", "au", "git", "github", "md", "ps", "pr", "vscode"] }, + { title: "操作系统", items: ["arch", "linux", "windows"] }, + ], + + sites: [ + { + name: "Example Site 1", + desc: "Example Site 1", + url: "https://example1.com", + }, + { + name: "Example Site 2", + desc: "Example Site 2", + url: "https://example2.com", + }, + ], + + projects: [ + { name: "Example Project 1", url: "https://github.com/ExampleUser/example-project-1", desc: "Example Project 1" }, + { name: "Example Project 2", url: "https://github.com/ExampleUser/example-project-2", desc: "Example Project 2" }, + ], + + friends: [ + { + name: "Example Site 1", + desc: "Example Site 1", + url: "https://example1.com", + avatar: "https://example1.com/avatar.png", + }, + { + name: "Example Site 2", + desc: "Example Site 2", + url: "https://example2.com", + avatar: "https://example2.com/avatar.png", + }, + ], + + footer: { + beian: "备案号", // 备案号,留空则不显示 + beianLink: "https://beian.miit.gov.cn/", // 备案号链接,一般无需修改 + showHitokoto: true, // 是否显示一言 + hitokotoType: "a&b&c&d&j", // 一言类型,详见 https://developer.hitokoto.cn/sentence/#%E5%8F%A5%E5%AD%90%E7%B1%BB%E5%9E%8B-%E5%8F%82%E6%95%B0 + customHtml: '', // 自定义 HTML 代码,如统计代码等 + }, +}; +``` + +### 其他配置 + +- **404 页面**:修改 `public/404.html` 来自定义 404 错误页面的样式与内容。 +- **友链展示逻辑**:`FriendsSection.vue` 默认使用随机顺序渲染 `siteConfig.friends`,如需固定顺序请修改该组件。 + +## 环境变量(邮件发送) + +在 Vercel 控制台或本地 `.env` 配置: + +- `VITE_GITHUB_TOKEN`: 具有仓库读取权限的 GitHub Token,用于绕过 GitHub API 速率限制。 +- `SMTP_HOST`: 邮件服务器主机名 +- `SMTP_PORT`: 端口(如 465 或 587) +- `SMTP_USER`: 发件人邮箱账号 +- `SMTP_PASS`: 邮箱授权码或密码 +- `MAIL_FROM`: 发件人地址(通常同 SMTP_USER) +- `ADMIN_EMAIL`: 接收通知的邮箱地址 +- `SMTP_SECURE`:是否启用 SSL/TLS 加密(默认 `true`) + +## 本地开发 + +```bash +pnpm install +pnpm dev +``` + +访问 `http://localhost:5173/`。 + +## 构建 + +```bash +pnpm build +``` + +产物输出到 `dist/`。 + +## 部署到 Vercel + +1. 导入仓库到 Vercel。 +2. 设置上文的 SMTP 环境变量。 +3. 保持 `vercel.json` 中 `distDir: "dist"`。 +4. 部署后,静态文件从 `dist/` 提供,API 走 `/api/send-mail`。 + +## API + +- `POST /api/send-mail`:友链申请邮件发送。请求体示例: + +```json +{ + "name": "RhenCloud", + "url": "https://example.com", + "desc": "个人博客", + "email": "you@example.com", + "avatar": "https://example.com/avatar.png" +} +``` + +## 许可 + +MIT License. diff --git a/api/send-mail.ts b/api/send-mail.ts new file mode 100644 index 0000000..0413a3e --- /dev/null +++ b/api/send-mail.ts @@ -0,0 +1,60 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; +import nodemailer from "nodemailer"; + +export default async function handler(req: VercelRequest, res: VercelResponse) { + // CORS 处理 + // res.setHeader("Access-Control-Allow-Origin", "*"); + // res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); + // res.setHeader("Access-Control-Allow-Headers", "Content-Type"); + + // if (req.method === "OPTIONS") { + // return res.status(200).end(); + // } + + // if (req.method === "GET") { + // return res.status(200).json({ status: "ok", message: "use POST to send mail" }); + // } + + // if (req.method !== "POST") { + // return res.status(405).json({ error: "Method Not Allowed" }); + // } + + try { + const data = typeof req.body === "string" ? JSON.parse(req.body) : req.body; + const { name, email, site, desc, avatar } = data || {}; + if (!name || !email || !desc) { + return res.status(400).json({ error: "Missing required fields: name, email, desc" }); + } + + const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: Number(process.env.SMTP_PORT || 465), + secure: Number(process.env.SMTP_PORT || 465) === 465, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS, + }, + }); + + const info = await transporter.sendMail({ + from: process.env.SENDER_EMAIL, + to: process.env.ADMIN_EMAIL, + subject: "友链申请 / 联系表单", + html: ` +

名称:${name}

+

邮箱:${email}

+

站点:${site || "未填写"}

+

描述:${desc}

+

头像:${avatar || "未填写"}

+

时间:${new Date().toISOString()}

+ `, + }); + + return res.status(200).json({ + message: "Mail sent", + id: info.messageId, + }); + } catch (err) { + return res.status(500).json({ error: "Send mail failed", detail: (err as Error).message }); + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..153041f --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + + Cloud Home + + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b6f91af --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "cloud-home", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@vercel/node": "^5.5.15", + "express": "^5.2.1", + "he": "^1.2.0", + "nodemailer": "^7.0.11", + "vue": "^3.5.25", + "vue-router": "^4.6.3" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^24.10.1", + "@types/nodemailer": "^7.0.4", + "@vitejs/plugin-vue": "^6.0.2", + "typescript": "^5.9.3", + "vite": "^7.2.6", + "vue-tsc": "^3.1.6" + } +} \ No newline at end of file diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..6c825fc --- /dev/null +++ b/public/404.html @@ -0,0 +1,80 @@ + + + + + + + 404 - Not Found + + + + +
+

404

+

页面不见了,或已被移除。

+ 返回首页 +
+ + + \ No newline at end of file diff --git a/public/avatar.webp b/public/avatar.webp new file mode 100644 index 0000000..1d61fad Binary files /dev/null and b/public/avatar.webp differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..0358a03 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/components/AboutSection.vue b/src/components/AboutSection.vue new file mode 100644 index 0000000..79d6c39 --- /dev/null +++ b/src/components/AboutSection.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/components/FooterSection.vue b/src/components/FooterSection.vue new file mode 100644 index 0000000..9d30654 --- /dev/null +++ b/src/components/FooterSection.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/components/FriendsSection.vue b/src/components/FriendsSection.vue new file mode 100644 index 0000000..58fc43e --- /dev/null +++ b/src/components/FriendsSection.vue @@ -0,0 +1,311 @@ + + + + + diff --git a/src/components/GithubSection.vue b/src/components/GithubSection.vue new file mode 100644 index 0000000..b8caa2a --- /dev/null +++ b/src/components/GithubSection.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/components/HeroSection.vue b/src/components/HeroSection.vue new file mode 100644 index 0000000..6a6a98e --- /dev/null +++ b/src/components/HeroSection.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/components/PageSwitcher.vue b/src/components/PageSwitcher.vue new file mode 100644 index 0000000..824f05f --- /dev/null +++ b/src/components/PageSwitcher.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/ProjectsSection.vue b/src/components/ProjectsSection.vue new file mode 100644 index 0000000..19c2057 --- /dev/null +++ b/src/components/ProjectsSection.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/components/SitesSection.vue b/src/components/SitesSection.vue new file mode 100644 index 0000000..a3ed6c6 --- /dev/null +++ b/src/components/SitesSection.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/components/SkillsSection.vue b/src/components/SkillsSection.vue new file mode 100644 index 0000000..b332a20 --- /dev/null +++ b/src/components/SkillsSection.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/components/SocialLinks.vue b/src/components/SocialLinks.vue new file mode 100644 index 0000000..65596ae --- /dev/null +++ b/src/components/SocialLinks.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/config/siteConfig.ts b/src/config/siteConfig.ts new file mode 100644 index 0000000..b75a145 --- /dev/null +++ b/src/config/siteConfig.ts @@ -0,0 +1,144 @@ +interface SiteConfig { + profile: { + name: string; + title: string; + avatar: string; + bio: string; + }; + socialLinks: Array<{ + name: string; + url: string; + }>; + github: { + username: string; + }; + about: Array<{ + title: string; + desc: string; + icon: string; + }>; + siteMeta: { + title: string; + icon: string; + }; + skills: Array<{ + title: string; + items: string[]; + }>; + sites: Array<{ + name: string; + desc: string; + url: string; + }>; + projects: Array<{ + name: string; + url: string; + desc: string; + }>; + friends: Array<{ + name: string; + desc: string; + url: string; + avatar: string; + }>; + footer: { + beian: string; + beianLink: string; + showHitokoto: boolean; + hitokotoType: string; + customHtml: string; + }; +} + +const siteConfig: SiteConfig = { + profile: { + name: "RhenCloud", + title: "I'm RhenCloud.", + avatar: "avatar.webp", // public/avatar.webp + bio: "趁世界还未重启之前 约一次爱恋", + }, + + socialLinks: [ + { name: "GitHub", url: "https://github.com/RhenCloud" }, + { name: "Email", url: "mailto:i@rhen.cloud" }, + { name: "Telegram", url: "https://t.me/RhenCloud" }, + { name: "Bilibili", url: "https://space.bilibili.com/1502883335" }, + { name: "Blog", url: "https://blog.rhen.cloud" }, + ], + + github: { + username: "RhenCloud", + }, + + about: [ + { title: "Pro-LGBT", desc: "我相信性别多样性是人们应有的自由和权利。", icon: "🧠" }, + { title: "Developer", desc: "专注后端 / 云原生,热爱自动化与高可用。", icon: "🛠️" }, + { title: "Anime Fan", desc: "二次元爱好者,享受故事与想象力。", icon: "🎬" }, + { title: "Just For Fun", desc: "我喜欢尝试新鲜事物,折腾小众技术", icon: "🎮" }, + ], + + siteMeta: { + title: "RhenCloud", + icon: "favicon.ico", // public/favicon.ico + }, + + skills: [ + { title: "前端", items: ["css", "html", "javascript", "typescript", "vue"] }, + { title: "后端 / 云", items: ["cpp", "cloudflare", "docker", "java", "mysql", "nodejs", "python", "vercel"] }, + { title: "工具", items: ["ae", "au", "git", "github", "md", "ps", "pr", "vscode"] }, + { title: "操作系统", items: ["arch", "linux", "windows"] }, + ], + + sites: [ + { + name: "个人主页", + desc: "个人主页", + url: "https://rhen.cloud", + }, + { + name: "我的博客", + desc: "分享与记录", + url: "https://blog.rhen.cloud", + }, + { + name: "来视奸我", + desc: "使用Sleepy项目搭建的视奸网站", + url: "https://sleepy.rhen.cloud", + }, + { + name: "网站监控", + desc: "网站运行状态监控", + url: "https://status.rhen.cloud", + }, + ], + + projects: [ + { name: "Cloud Home", url: "https://github.com/RhenCloud/cloud-home", desc: "个人主页模板" }, + { name: "ILP", url: "https://github.com/RhenCloud/ILP", desc: "跨平台、多网站、模块化的小说下载器" }, + { name: "ILP-C++", url: "https://github.com/RhenCloud/ILP-Cpp", desc: "跨平台、多网站、模块化的小说下载器" }, + ], + friends: [ + { + name: "wuxian", + desc: "wuxian's web", + url: "https://www.alxian.cn", + avatar: "https://www.alxian.cn/_next/image?url=%2Fimages%2Favatar.jpg&w=256&q=75", + }, + { + name: "鈴奈咲桜のBlog", + desc: "一个普普通通的Blog", + url: "https://blog.sakura.ink", + avatar: "https://q2.qlogo.cn/headimg_dl?dst_uin=2731443459&spec=5", + }, + ], + + footer: { + beian: "津ICP备2025039003号-1", + beianLink: "https://beian.miit.gov.cn/", + showHitokoto: true, + hitokotoType: "a&b&c&d&j", + customHtml: '© 2025 RhenCloud', + }, +}; + +export default siteConfig; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..c8c4d1e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,6 @@ +import { createApp } from "vue"; +import App from "./App.vue"; +import router from "./router"; +import "./styles.css"; + +createApp(App).use(router).mount("#app"); diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..7319b7d --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,19 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router"; +import Home from "../views/Home.vue"; +import About from "../views/About.vue"; +import Sites from "../views/Sites.vue"; +import Projects from "../views/Projects.vue"; +import Friends from "../views/Friends.vue"; + +const routes: RouteRecordRaw[] = [ + { path: "/", name: "Home", component: Home, meta: { order: 0, label: "首页" } }, + { path: "/about", name: "About", component: About, meta: { order: 1, label: "关于" } }, + { path: "/sites", name: "Sites", component: Sites, meta: { order: 2, label: "网站" } }, + { path: "/projects", name: "Projects", component: Projects, meta: { order: 3, label: "项目" } }, + { path: "/friends", name: "Friends", component: Friends, meta: { order: 4, label: "友链" } }, +]; + +export default createRouter({ + history: createWebHistory(), + routes, +}); diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..68d91ef --- /dev/null +++ b/src/styles.css @@ -0,0 +1,92 @@ +:root { + color-scheme: light dark; + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: #0f1629; + color: #e8eefc; +} + +body { + margin: 0; + min-height: 100vh; + background: radial-gradient(circle at 20% 20%, #1b2b4b, #0f1629); +} + +a { + color: #7cc1ff; + text-decoration: none; +} + +.page { + max-width: 960px; + margin: 0 auto; + padding: 32px 16px 48px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.card { + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 16px; + padding: 18px 20px; + backdrop-filter: blur(8px); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25); +} + +.hero { + display: grid; + grid-template-columns: 96px 1fr; + gap: 16px; + align-items: center; +} + +.avatar { + width: 96px; + height: 96px; + border-radius: 50%; + object-fit: cover; + border: 2px solid rgba(255, 255, 255, 0.2); +} + +h1, +h2, +h3 { + margin: 0 0 8px 0; +} + +p { + margin: 6px 0; +} + +.muted { + color: #a8b3cf; + font-size: 0.95rem; +} + +.chips { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.chips a { + padding: 6px 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); +} + +.list { + list-style: none; + padding: 0; + margin: 0; +} + +.list li { + margin-bottom: 8px; +} + +.footer { + text-align: center; + margin-top: 8px; +} diff --git a/src/views/About.vue b/src/views/About.vue new file mode 100644 index 0000000..99df128 --- /dev/null +++ b/src/views/About.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/views/Friends.vue b/src/views/Friends.vue new file mode 100644 index 0000000..cdbd8a4 --- /dev/null +++ b/src/views/Friends.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/views/Home.vue b/src/views/Home.vue new file mode 100644 index 0000000..ce69ab9 --- /dev/null +++ b/src/views/Home.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/src/views/Projects.vue b/src/views/Projects.vue new file mode 100644 index 0000000..c313fb3 --- /dev/null +++ b/src/views/Projects.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/views/Sites.vue b/src/views/Sites.vue new file mode 100644 index 0000000..f06bb17 --- /dev/null +++ b/src/views/Sites.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..68af4be --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module "*.vue" { + import type { DefineComponent } from "vue"; + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5bd2acc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..2f699d8 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..624634b --- /dev/null +++ b/vercel.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "framework": "vite", + "routes": [ + { + "src": "/api/(.*)", + "dest": "/api/$1" + } + ] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..c5c15df --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; + +export default defineConfig({ + plugins: [vue()], + server: { port: 5173 }, +});