Vue3个人网站模板源码拆解:从工程化到部署
发布时间:2026/9/16 9:31:55来源:尧图网络
简介这是一套基于Vue 3开发的个人网站模板源码兼容Vue系列多个版本适合前端初学者、开发者以及需要快速完成网站类课程大作业的高校学生。模板内置五个页面网站首页、个人工具、个人日志、个人相册、给我留言并支持自定义导航菜单、轮播图、表单、表格、自定义标签等常用功能模块页面结构与样式通俗易懂可直接在模板上修改文字和图片就能应用无需从零搭建。资源包共2000个文件以1653个JavaScript文件为主体包含Vue组件、依赖库与构建工具脚本另有187个JSON配置文件、152个Markdown说明文档、7个TXT及1个HTML入口文件压缩包整体约111.16MB目录结构清晰便于按模块查阅和二次开发。目前已有1481人学习下载。使用VS Code打开项目并执行npm run dev即可本地预览既适合作为个人网站快速上线的基础模板也可当作Vue 3工程化学习的参考案例。1. 从VUE3个人网站模板源码开始先想清楚模板给谁用VUE3实现个人网站模板源码这类标题在开源社区和资源站里很常见但多数人下载后只做两件事改掉站点标题、换掉头像然后再也不碰。真正值得关注的不是模板本身有多好看而是Vue3给个人网站模板带来的工程化结构单文件组件让导航、作品集、页脚各归其位Composition API把配置与逻辑拆成可组合的模块响应式状态让主题切换和内容过滤不再依赖手写DOM操作。本文按工程初始化→组件实现→数据层组织→构建部署这条线把一份个人网站模板从零到上线的关键路径走一遍。适合想自己搭作品集网站的开发者也适合准备从Vue2迁移到Vue3的团队参考。2. VUE3工程初始化与环境配置让模板源码先跑起来2.1 create-vue 与 Vite 两条脚手架路径的选择2.1.1 两条路径的命令与对比# 路径 A官方 create-vue 脚手架 npm create vuelatest my-site # 交互式选择 Router / Pinia / ESLint / Prettier # 路径 BVite 官方模板 npm create vitelatest my-site -- --template vue-ts路径B比A少了很多交互选项生成的工程也不带vue-router和pinia依赖。路径A生成的src目录里直接有views、components、router、stores四个基础目录对一个要分发给其他人的模板源码来说省去了一轮目录整理。下面的表格列出两者差异对比项create-vuecreate vite vue-ts路由与状态管理可选自动接入需手动安装工程目录骨架已按views/components分层只有最小结构ESLint/Prettier提供选项需另行添加适合场景模板分发、多人维护原型快速验证模板源码通常要给别人二次修改所以工程结构完整比生成速度快更重要。实际上直接用create-vue然后删掉demo页面比用空白Vite模板自己搭目录要省力。2.1.2 为什么模板源码推荐create-vue用create-vue初始化的项目自带router/index.ts和stores/这两个文件在个人网站模板里几乎必然要用。路由可以控制单页切换store里可以放站点配置和主题状态。自己从零搭当然也可以安装vue-router、建目录、写入口但这些重复劳动对模板这件事没有增量价值反而增加了模板使用者第一次运行时出错的概率。提示npm create vuelatest命令中的latest不要省略否则在某些npm版本下会拉到旧版create-vue。生成后直接npm install npm run dev验证环境是否正常。2.2 模板目录怎么拆才利于二次开发个人网站和后台管理系统常说的vue3后台管理系统模板的目录差异很大。后台系统的核心是布局和权限个人网站的核心是内容和展示所以目录设计应该让改内容的人和改逻辑的人互不干扰。我一般使用的结构如下src/ ├── config/ # 站点配置昵称、简介、社交链接、作品列表 ├── layouts/ # 布局组件顶栏、页脚、移动端抽屉 ├── views/ # 路由页面Home、Portfolio、About ├── components/ # 可复用组件Tabs、Card、BackTop ├── composables/ # 组合式函数useTheme、useFetch ├── router/index.ts ├── stores/site.ts └── styles/ # 全局样式、CSS变量、断点变量每个目录的职责用一句话就能说清config放数据views放页面components放可复用片段composables放逻辑。模板使用者拿到手后90%的修改发生在config和views两个目录里剩下的人改components和styles。如果一开始就把所有代码堆在App.vue里那不如直接发静态HTML没必要用Vue3。2.3 用环境变量处理模板的部署环境差异2.3.1 .env文件与VITE_前缀模板要能在本地、测试服务器、生产环境都跑起来环境变量是必须处理的。Vue3项目只暴露以VITE_开头的变量# .env.development VITE_API_BASE/api VITE_SITE_URLhttp://localhost:5173 # .env.production VITE_API_BASE/api VITE_SITE_URLhttps://example.com// src/config/env.ts export const env { apiBase: import.meta.env.VITE_API_BASE, siteUrl: import.meta.env.VITE_SITE_URL, isProd: import.meta.env.PROD }import.meta.env.VITE_API_BASE由Vite在构建时静态替换不能使用动态键访问import.meta.env[key]这一点与旧版process.env.VUE_APP_*不同。如果模板需要部署在服务器子路径比如https://example.com/blog/还要在vite.config.ts里设置base: /blog/否则构建后的JS和CSS全部404。2.4 模板字符串在站点配置里的轻量应用个人网站里存在大量固定前缀可变后缀的字符串比如社交链接、og:image地址、RSS地址。不引入模板语言库的前提下用模板字符串做轻量拼接已经足够// src/config/site.ts export const site { baseUrl: import.meta.env.VITE_SITE_URL, nickname: 程序员的记事本, get ogImage() { return ${this.baseUrl}/assets/og-cover.png }, get rssUrl() { return /feed.xml }, social: [ { name: GitHub, url: https://github.com/你的用户名 }, { name: 微博, url: https://weibo.com/你的用户名 } ], works: [] }使用get访问器是为了让ogImage跟随baseUrl变化如果后续支持后台切换域名这个值会自动更新不需要额外加同步逻辑。模板字符串的另一个用途是构造数据埋点地址把页面名和来源拼进URL参数避免手工拼接出错。3. 导航、tabs与作品集卡片个人网站模板的核心组件3.1 tabs标签页样式定制从样式类到路由联动个人网站模板里tabs最常出现在两个形态一个是页面内切换作品分类、文章列表另一个是全站导航项的高亮。前者用ref控制后者用router-link加active-class。实际项目中混用的情况很多。!-- components/TabsGroup.vue -- script setup langts import { ref } from vue const props defineProps{ items: { label: string; value: string }[] modelValue?: string }() const emit defineEmits([change]) const active ref(props.modelValue ?? props.items[0]?.value) function select(v: string) { active.value v emit(change, v) } /script template div classtabs roletablist button v-foritem in items :keyitem.value typebutton classtabs__item :class{ tabs__item--active: active item.value } clickselect(item.value) {{ item.label }} /button /div /template style scoped .tabs { display: flex; gap: 8px; border-bottom: 1px solid var(--border-color); } .tabs__item { padding: 8px 16px; background: transparent; border: none; border-bottom: 2px solid transparent; cursor: pointer; color: var(--text-secondary); transition: color 0.2s, border-color 0.2s; } .tabs__item--active { color: var(--text-primary); border-bottom-color: var(--accent-color); } /style这个组件的关键参数是items和modelValueitems定义每个tab的显示文字和值modelValue支持外部初始化默认选中项。用ref保存选中状态在按钮点击时调用emit(change)把值交还给父组件。样式方面下划线效果用border-bottom实现background: transparent是为了避免和页面背景冲突。如果tabs需要在路由切换后保持选中状态可以把active的初始值改成useRoute().query.tab这样别人分享带?tabtech的链接时页面能直接定位到对应分类。这也是模板源码和一次性页面的区别要照顾可分享、可刷新的场景。提示如果使用UI组件库Element Plus、Naive UI的tabs样式定制反而复杂多数个人网站模板只用原生按钮加CSS就能达到同样效果还能减少打包体积。3.2 作品集展示区卡片网格与分类过滤个人作品集网站是为个人网站模板导流的重要场景。作品集区域最常见的需求是展示项目封面、标题、标签支持按分类过滤。用Vue3实现时过滤逻辑放在computed里比放在方法里更符合响应式心智!-- views/Portfolio.vue 节选 -- script setup langts import { ref, computed } from vue import PortfolioCard from /components/PortfolioCard.vue import { site } from /config/site const activeCat ref(全部) const cats [全部, ...new Set(site.works.map(w w.category))] const filteredWorks computed(() activeCat.value 全部 ? site.works : site.works.filter(w w.category activeCat.value) ) /script template section classworks-grid PortfolioCard v-forwork in filteredWorks :keywork.title :titlework.title :coverwork.cover :tagswork.tags :linkwork.link / /section /templatecomputed在这里起了两个作用一是filteredWorks不会在每次渲染时重新执行过滤性能上有保障二是当activeCat被tabs组件修改时这个计算属性自动更新卡片网格不需要额外的事件驱动。cats用new Set去重避免在模板里写复杂表达式。如果作品数量超过50条考虑再加分页或无限滚动否则一次性渲染图片和卡片足够。PortfolioCard组件本身不关心数据从哪来只接收props。这种数据在父级展示在子级的结构让模板使用者想调整作品顺序时只需要改config/site.ts里的数组不需要改组件代码。3.3 响应式页面设计模板的断点与流体布局响应式页面设计模板是检索个人网站模板的高频词具体落实在三个地方断点变量、栅格系统和字体字号。下面是一份适合个人网站的断点定义断点容器最大宽度适用设备480px以下100%小屏手机768px100%平板竖屏1024px960px桌面1280px1200px宽屏桌面:root { --breakpoint-sm: 480px; --breakpoint-md: 768px; --breakpoint-lg: 1024px; --breakpoint-xl: 1280px; --container-max: 1200px; } .works-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; } media (min-width: 768px) { .works-grid { gap: 32px; } }把断点放进CSS变量而不是散落各处的媒体查询里模板的二开者修改断点时只需要动styles/variables.css一个文件。minmax(280px, 1fr)配合auto-fill实现每行自动填充卡片当容器宽度不足以塞下一张280px卡片时grid自动换行这就是响应式模板最基础也最常用的一招。4. 状态管理与computed模板的数据层该放哪4.1 用组合式函数把配置变成响应式状态个人网站模板的数据层和后台管理系统不同不需要复杂的权限流和用户会话核心需求是把站点配置变成响应式的、可被组件共享的状态。在Vue3里我一般用composables模块实现而不是全局store。// composables/useSiteConfig.ts import { computed, ref } from vue import { site } from /config/site const title ref(site.title) const nickname ref(site.nickname) export function useSiteConfig() { const setTitle (v: string) { title.value v document.title v } const pageTitle computed(() ${title.value} · ${nickname.value}) return { title, nickname, setTitle, pageTitle } }关键差异在于模块顶层声明的ref只会在首次import时初始化一次因此多个组件调用useSiteConfig()拿到的是同一个title引用修改一个组件里的值其他组件同步更新。如果ref放在useSiteConfig函数内部每次调用都会生成新状态那它和普通函数就没有区别了。这一点是Composition API和Vue2的data()最显著的不同。从Vue2迁移的角度看这套写法用composables函数替代了mixin。mixin的缺陷在于来源不透明——组件里出现的每个属性都要去翻mixin定义才能确认从哪来组合式函数把状态和操作显式地包在一个函数里模板源码的阅读者看import语句就知道哪些逻辑被引入。4.2 computed的边界缓存计算与副作用分离computed在个人网站模板里的典型应用是过滤作品、计算阅读时长、拼接分享链接、统计标签云。但是computed不是万能的它只适合根据已有响应式数据计算出新值不应该在computed里写异步操作或修改其他状态。// composables/useWorks.ts import { computed, ref } from vue import { site } from /config/site const keyword ref() const category ref(全部) export const filteredWorks computed(() { const kw keyword.value.trim().toLowerCase() return site.works.filter(w { const matchCategory category.value 全部 || w.category category.value const matchKeyword !kw || w.title.toLowerCase().includes(kw) || w.tags.some(t t.toLowerCase().includes(kw)) return matchCategory matchKeyword }) })这个computed合并了关键词搜索与分类过滤两个输入条件任意变化都会触发重新计算。参数语义keyword是受控的输入框绑定值category由tabs组件更新。toLowerCase()处理大小写trim()避免用户输入空格导致过滤结果为空。computed与watch的边界有一个简单判断标准想得到一个值时用computed想在值变化后执行某个动作时用watch。在模板源码里watch最常出现的地方是路由变化后滚动到顶部、主题切换后写入localStorage。场景用computed用watch根据关键词过滤作品是否路由变化后回到页面顶部否是根据分类切换动态页标题是是防抖后提交搜索词否是4.3 数据请求与错误处理的封装个人网站的后端一般很轻可能一个JSON文件、一个CMS接口、一个评论系统。封装一个泛型请求函数可以避免每个页面重复写try-catch、loading和abort逻辑。// composables/useFetch.ts import { ref } from vue export async function useFetchT(url: string, timeout 10000) { const data refT | null(null) const error refError | null(null) const loading ref(true) const controller new AbortController() const timer setTimeout(() controller.abort(), timeout) try { const res await fetch(url, { signal: controller.signal, headers: { Accept: application/json } }) if (!res.ok) throw new Error(HTTP ${res.status}) data.value await res.json() as T } catch (e) { error.value e as Error } finally { clearTimeout(timer) loading.value false } return { data, error, loading, controller } }三个返回值的设计data、error、loading是页面渲染时需要的状态controller是给调用方取消请求用的。模板里可以这样用const { data, error, loading } await useFetchWork[](/api/works.json)在有严格TS约束的模板里泛型T让后端返回的数据结构拥有类型提示。如果接口返回格式相对复杂用T定义整个响应壳再用另一个类型定义业务体。超时参数timeout默认10秒对个人网站这种低流量、弱依赖接口的场景足够局域网部署可适当调大到20秒。4.4 配置即内容让非开发者能改模板个人网站模板的典型使用者可能是设计师或文案他们不熟悉组件语法但改配置文件的意愿和能力是有的。把内容集中在config/site.ts里并用TypeScript接口约束字段是让模板可交付的关键。例如作品项的字段定义// config/types.ts export interface Work { title: string description: string category: Project | Writing | Design cover: string link?: string tags: string[] date: string }字段下方的tsdoc注释也要写清楚每个字段的取值比如link不填时卡片不跳转。模板在严苛的TS检查下别人改配置时如果漏写了tagsIDE会直接报错这比运行时白屏更友好。对不想用TS的模板用JSDoc也可以达到类似效果但Vue3的defineProps对TS的支持更自然所以新模板尽量用script setup langts。5. Nginx部署与按需加载验证模板上线的最后一段路模板源码最终要被部署到服务器。Vue3项目默认使用HTML5 History路由如果直接配置nginx指向dist目录下的index.html进入子路由后刷新页面nginx会按路径去找对应的静态文件找不到就返回404。解决办法是在location里补一段try_files。# nginx 配置关键段 server { listen 80; server_name example.com; root /var/www/my-site/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /assets/ { expires 30d; add_header Cache-Control public, immutable; gzip_static on; } }说明一下参数try_files $uri $uri/ /index.html的意思是先找实际文件再找目录都没有就回退到index.html由前端路由接管。location /assets/后面加expires 30d是因为Vite构建时会对带hash的静态资源生成唯一文件名文件名变了浏览器自然请求新版本所以可以放心缓存。gzip_static on需要预先把dist目录里的js/css用gzip压缩成.gz文件构建时可以用vite-plugin-compression自动生成。使用Hash路由createWebHashHistory可以免去nginx配置但URL会带上#/在分享链接和SEO上都不占优。模板源码我推荐History加try_files的组合代价只是一次nginx配置。部署后需要验证模板是否真正生效。在浏览器Network面板里看两个东西一是/assets/下的JS文件是否按访问的路由拆分加载二是状态码是否为200而非304导致缓存失效。如果想要页面级代码分割在路由里使用动态导入// router/index.ts const Portfolio () import(/views/Portfolio.vue) const About () import(/views/About.vue)()动态导入让Vite在构建时为每个页面生成单独的chunk。用npm run build构建后打开dist目录能看到assets/下除了主入口外还有以页面命名的Portfolio-xxx.js。局域网环境验证时首次访问首页只加载首页相关chunk点击作品集导航后新增请求Portfolio对应的chunk这就是按需加载生效。修改了config/site.ts后重新构建新产物文件名带新的hashnginx配置了immutable后浏览器会紧跟着新请求无需手动清缓存。产品上线前的最后一步把Network面板里的chunk列表和路由表对照一遍确认每个视图对应唯一chunk多余的就从路由的静态导入改回动态导入。本文还有配套的精品资源点击获取
网站建设高端定制企业官网