[id_467544084]id_1265953395]
近期在做一个纯前端的简历项目,遇到一个问题难点。
背景:
纯前端(无后端),页面左侧为简历编辑区域,右侧可实时切换简历模板。左侧编辑完成后,右侧的简历预览能实时随数据更改而更新数据。其中最重要的是简历模板可热插拔且能实时切换更新,这是纯前端的实现方案。
整体流程的实现思路是获取简历模板列表,用户可以选择简历模板,然后将其存入 pinia(需使用 pinia-plugin-persistedstate 插件来实现持久化)。在简历预览页面中,通过组件形式导入简历。预览页面需要监听用户切换模板的操作,以便实时热更新简历模板(切换组件)。
首先在 public/template.json 里对模板列表进行定义。
为什么要放在 public 文件夹呢?因为 public 文件夹不会被 vite 进行打包压缩。并且在打包状态或者开发状态运行时,都能够直接通过 /template.json 来访问到对应的 json 文件,从而获取到模板。
[
{
"id": "template111111",
"name": "简洁模板1",
简约的同时又不简单的是简历模板。
"folderPath": "templateA",
"thumbnail": "preview.jpg"
},
{
"id": "template222222",
"name": "简洁模板2",
"description": "简约而不简单的简历模板。",
"folderPath": "templateB",
"thumbnail": "preview.jpg"
},
{
"id": "template33333",
"name": "开发版本",
供开发者参考的是用于开发的模板。
"folderPath": "templateC",
"thumbnail": "preview.jpg"
}
]
获取简历模板列表的工具类
从“../types/template”这个路径中导入名为“Template”的类型。
export 函数 getTemplates 是一个异步函数,它返回一个 Promise 类型的值。 => {
try {
await fetch('/templates.json'); // 使用绝对路径
if (!response.ok) {
throw new Error('无法获取模板列表');
}
return await response.json();
} catch (error) {
在控制台输出错误信息,内容为“获取模板列表失败:”以及具体的错误信息 error 。
return [];
}
};
使用 pinia 来进行持久化存储,以保存用户切换模板的数据。如此一来,当用户下次在浏览器中进入时,所看到的依然是之前选择的模板。
这里使用下面这个插件,开启pinia数据自动持久化
使用 npm 来安装 pinia-plugin-persistedstate 。
在 useTemplateStore.ts 里设置了用户所选择的主题模板,并且还有主题色,不过主题色不在本文章中进行介绍。
开启 pinia 数据自动持久化,persist 为 true 。这样就无需自己另外去写 localStorage 的更新插入等操作,非常方便。
使用“pinia”库的“defineStore”函数进行相关操作。
import { ref } from 'vue'
从“../types/template”这个路径中导入名为“Template”的类型。
定义了一个名为 useTemplateStore 的函数,它使用 defineStore 函数创建了一个名为 'templateStore' 的存储。
const currentTemplate = ref(null)
const themeColor = ref('#203CCB')
函数用于设置主题颜色,参数为一个字符串类型的颜色值。
themeColor.value = color
}
函数用于设置模板,该模板为给定的 Template 类型。
当前的 Template 的值是 template 。
}
返回 { 当前模板, 设置模板, 主题颜色, 设置主题颜色 }
}, {
persist: true // 开启持久化存储
})
上面主要讲的内容有模板切换、持久化以及模板列表的获取。下面开始正式讲述如何实现简历模板的热插拔动态切换,且此过程为纯前端操作。
首先来看项目结构,我将所有简历模板都放置在了 src/template 里。通过这种方式,能够依据模板所在的文件夹名字,直接找到与之对应的 vue 文件,进而引入组件。

为实现动态切换,需在预览页面中一次性将 template 目录里的所有简历加载进来。
// 动态导入所有模板组件
const templateModules = import.meta.glob('../../../template/**/index.vue'); 这句话无需改写,因为其表意清晰且用词简洁,难以在不改变原意的情况下进行大幅度改写。如果一定要进行改写,可改为:“将 import.meta.glob('../../../template/**/index.vue') 赋值给 templateModules 这个常量。” 但这样的改写在意思表达上与原句基本一致,只是表述方式略有不同。
解释
import.meta.glob 是 Vite 所提供的一种特殊导入功能,它主要是用来实现模块的动态导入。
主要功能:
1. 批量导入 :
- 使用 glob 模式匹配多个文件
= **/ 表示匹配任意深度的子目录
这里会匹配 template 目录下的所有 index.vue 文件。
2. 生成导入函数映射 :
// templateModules 实际上是这样的结构
{
从'../../../template/templateA/index.vue'这个路径导入组件,具体是通过调用 import 函数来实现的,导入的目标就是'../../../template/templateA/index.vue'这个路径所对应的组件。
通过以下方式引入文件:import('../../../template/templateB/index.vue'),该文件的路径为../../../template/templateB/index.vue
// ... 更多模板
}
3. 按需加载 :
- 每个匹配的文件会生成一个动态导入函数
- 这些函数只有在被调用时才会真正加载对应的模块
- 有助于提高应用的初始加载性能
4. 优势:
- 实现代码分割
- 减小主包体积
- 支持动态加载
- 提高首屏加载速度
页面初始化时,首先判断是否有用户的默认模板,如果有就进行加载;如果没有用户的默认模板,那么就加载默认的第一个模板。
// 获取并初始化模板列表
onMounted(async () => {
try {
获取模板的操作会被赋值给 templates.value ,即 templates.value 等于 await getTemplates() 的结果
// 如果 Pinia 中有已选中的模板,则恢复
如果 templateStore 中有当前模板,那么就会执行相应的操作。如果 templateStore 中没有当前模板,就不会执行相应的操作。
selectedTemplateId 的值等于 templateStore.currentTemplate 的 id 。
如果 templates.value 的长度大于 0 ,那么就执行以下操作;如果 templates.value 的长度不大于 0 ,则不执行任何操作;这里主要是对 templates.value 的长度进行判断,以决定是否执行后续的相关操作。
// 否则默认选中第一个模板
selectedTemplateId 的值等于 templates 的值中的第一个元素的 id 。
templateStore 执行了设置模板的操作,设置的模板为 templates.value[0]。
}
loadCurrentTemplate();
} catch (error) {
console.error('获取模板列表失败:', error);
}
});
loadCurrentTemplate()函数是整个切换模板的核心代码,它的作用是能够实时地将对应的组件渲染到页面之上。
// 加载当前选中的模板组件
定义一个名为 loadCurrentTemplate 的函数,该函数的功能是进行一些操作。
// 首先定义获取到当前选择的模板
模板存储中的当前模板为选中的模板。
在进行判断时,利用可选链操作符?. 以保证 folderPath 是存在的。
为了防止在 selectedTemplate 为 null 或 undefined 的情况下报错。
如果 selectedTemplate 的 folderPath 存在的话。
所选模板的文件夹路径是 folderName 。
if (!folderName) {
控制台输出错误信息:“模板路径错误:”以及 selectedTemplate.folderPath 这个选定模板的文件夹路径。
return;
}
const importPath = 路径 + 文件夹名 + 文件名;
templateModules 是由 import.meta.glob 生成的一种导入函数映射。
// 根据路径获取对应的动态导入函数
从 templateModules 中获取名为 importPath 的导入函数,将其赋值给 importFunc 。
if (importFunc) {
// 异步加载组件
currentComponent 的值等于 defineAsyncComponent,该函数会导入一个由 importFunc() 返回的 Promise 作为组件。);
} else {
控制台输出错误信息,内容为未找到路径为指定的 importPath 的组件。
}
}
};
另外,页面中需要使用component挂载组件
补充:defineAsyncComponent()
Vue 3 提供了 defineAsyncComponent()这个异步组件加载函数,它可用于按需进行动态加载组件,这样做能够优化性能,还能减少初始加载的时间。
基本用法:
从“vue”中导入“defineAsyncComponent”这个函数。
定义了一个异步组件 AsyncComponent,它通过导入 './MyComponent.vue' 来实现异步加载。
作用:
最后完成了整个模板的动态切换。
总结是通过 import.meta.glob 来批量导入文件夹下的.vue 文件。在用户切换模板时,会从 templateModules 中按需加载与之对应的模块。