如何在 Vite 项目中优雅地展示用户协议?(Markdown 转 Vue 组件方案)
前言
在日常的前端开发中,我们经常需要在界面上向用户出示“用户协议”、“隐私政策”和“操作指引”等长文本内容。
如果将大段文字直接写死在 Vue 模板里或者存放到变量中,不仅代码会变得非常臃肿,而且极不利于后续的更新与维护(比如法务或运营要求修改个别字眼时,我们在成堆的代码里找起来非常头疼)。
一种优雅且主流的解决方案是:独立维护纯文本的 Markdown(.md)格式的协议文件,然后借助构建工具的支持,直接将它们转换为 Vue 组件在弹窗中引入并渲染。
本文将手把手带你了解如何利用 unplugin-vue-markdown 这一 Vite 插件,结合大家常用的 Element Plus UI 框架,在项目中快速实现协议的优雅展示。
1. 安装核心依赖
首先,我们需要安装能让 Vite 将 Markdown 解析为 Vue 组件的插件。在终端中运行以下命令:
npm install unplugin-vue-markdown -D
# 或者使用 pnpm / yarn注:该插件依赖于 @vitejs/plugin-vue,对于常规的 Vite+Vue3 项目通常已经默认安装好该核心依赖
2. 配置 Vite 插件
安装完成后,我们需要修改项目的构建配置。打开项目根目录下的 vite.config.ts 文件,进行如下调整:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// 引入 Markdown 插件
import Markdown from "unplugin-vue-markdown/vite";
export default defineConfig({
plugins: [
vue({
// 核心配置 1:让 Vue 的解析器同时处理 .vue 和 .md 后缀的文件
include: [/\.vue$/, /\.md$/]
}),
// 核心配置 2:注册 Markdown 解析器
Markdown({}),
]
});解决 TypeScript 文件声明报错
如果你现在直接引入md文件,大概率会遇到找不到模块“xxxx.md”或其相应的类型声明。的报错提示。
为什么会这样? 因为 TypeScript 默认只认识 .ts、.js 等代码文件,它并不理解 .md 文件是什么,也不知道它导出来的是什么东西。虽然 Vite 能正常打包,但编辑器里的红线实在影响心情和代码规范。
如何解决? 非常简单,我们只需要“告诉” TypeScript,所有的 .md 文件都可以当作普通的 Vue 组件来对待。
在项目的 src 目录下,找到(或新建)一个类型声明文件,通常叫 env.d.ts、vite-env.d.ts 或专门新建一个 markdown.d.ts。在里面添加如下声明:
// src/env.d.ts 或 src/vite-env.d.ts
declare module "*.md" {
import type { ComponentOptions } from "vue";
const Component: ComponentOptions;
export default Component;
}保存后,你会发现编辑器里的红灯报错神奇地消失了!
完成这一步后,你的项目中就可以像引入 Vue 组件那样直接引入 Markdown 文件了。
3. 引入 Markdown 的两种方式:静态与异步
在准备好诸如 user-agreement.md 这样的 Markdown 协议文件后,我们有两种主要方式将它引入到我们的 Vue 页面中。
方式一:直接静态导入(适合轻便短小的文本)
import UserAgreement from "@/assets/agreements/user-agreement.md";优点:代码最直接,使用起来和普通组件完全一样。 缺点:会将文件的长文本直接打包进主 JS 文件里。如果协议特别长,会导致首屏加载变慢。
方式二:异步动态导入(强烈推荐,适合长篇协议)
这种方式利用了 Vue 3 的 defineAsyncComponent API,实现按需加载。
import { defineAsyncComponent } from 'vue';
const UserAgreementAsync = defineAsyncComponent(() =>
import("@/assets/agreements/user-agreement.md")
);优点:首屏不会加载这部分文字,只有在用户真正打开弹窗时,浏览器才会去单独请求下载这份协议文件,大大优化了页面包体积和加载速度。
实战建议:针对“用户协议”这类动辄几千字的长篇大论,强烈推荐使用方式二进行异步加载。
4. 封装协议展示弹窗 (基于 Element Plus)
协议往往需要在一个弹窗中向用户展示。下面我们使用 Element Plus 的 el-dialog 来封装一个专门用于阅读协议的展示组件AgreementModal.vue
<!-- components/AgreementModal.vue -->
<template>
<el-dialog
v-model="visible"
title="用户服务协议"
width="50%"
center
>
<!-- 协议文字展示区域 -->
<div class="agreement-content">
<!--
使用 Suspense 优雅处理异步加载过程:
当 md 文件还在利用网络下载时,先展示 fallback 里面的 loading 效果;
下载完毕后,展示 default 里面的实际 Markdown 组件。
-->
<Suspense>
<!-- 默认展示内容(下载完后展示对应内容) -->
<template #default>
<component :is="AgreementContent" />
</template>
<!-- 加载中的提示(正在下载文件时展示的地方) -->
<template #fallback>
<div class="loading-box" v-loading="true" element-loading-text="协议加载中...全速冲刺!"></div>
</template>
</Suspense>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
// 使用 defineModel 双向绑定,让父组件可以轻松控制弹窗的显隐
const visible = defineModel({ type: Boolean, required: true });
// 使用异步方式按需引入 Markdown 协议文件
const AgreementContent = defineAsyncComponent(() =>
import("@/assets/agreements/user-agreement.md")
);
</script>
<style scoped>
.agreement-content {
/* 限制弹窗内容区域的高度,内容超出则出现滚动条,方便用户向下阅读 */
height: 400px;
overflow-y: auto;
padding: 0 10px;
}
.loading-box {
height: 100%;
width: 100%;
}
</style>5. 项目中的完整调用示例
现在,我们有了一个非常好用的只用于展示的弹窗组件。接下来我们在一个常见的“登录注册页面”里,将它引入并使用:
<!-- views/LoginView.vue -->
<template>
<div class="login-container">
<div class="form-box">
<h2>欢迎登录</h2>
<!-- 协议勾选提示区域 -->
<div class="agreement-tips">
<el-checkbox v-model="isCheck">
已阅读并同意
</el-checkbox>
<!-- 点击高亮文字,改变 showAgreement 变量的值,弹出协议框 -->
<a class="primary-link" @click="showAgreement = true">
《用户服务协议》
</a>
</div>
<el-button type="primary" class="login-btn">登录</el-button>
</div>
<!-- 挂载我们刚刚封装的弹窗组件,用 v-model 绑定状态 -->
<AgreementModal v-model="showAgreement" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import AgreementModal from '@/components/AgreementModal.vue';
// 记录复选框是否勾选
const isCheck = ref(false);
// 控制协议弹窗显示隐藏的简单变量
const showAgreement = ref(false);
</script>
<style scoped>
.primary-link {
color: #409eff;
cursor: pointer;
text-decoration: none;
}
.primary-link:hover {
opacity: 0.8;
}
/* 其他外层布局样式略 */
</style>只要点击蓝色的《用户服务协议》,控制显示的变量 showAgreement 变为 true,AgreementModal 弹窗便会立刻弹起,顺滑过渡出你要向用户展示的长文本。
6. 进阶:如何美化 Markdown 渲染后的样式?
经过以上的步骤,你的 Markdown 已经成功渲染在页面上了!
但是,你会发现文字可能挤在一起,缺少层次感,图片和表格也没什么高级感。
为什么会这样?
因为 unplugin-vue-markdown 插件在底层仅仅是把你的 md 文件解析成了类似下面这样的普通 HTML 标签骨架,并且最外层通常会被包裹在一个固定的 CSS Class 内(插件默认设定为 .markdown-body):
<div class="markdown-body">
<h1>用户服务协议</h1>
<p>欢迎您使用本应用!</p>
<h2>第一条 隐私条款</h2>
</div>原生的 HTML 标签(如 h1, p, table)是没有经过“精修”的新手默认样式的。你需要在项目中额外引入 CSS 去装饰它们。
业界推荐的解决方案:
为了不重复造轮子(自己从头手写所有的间距和字号排版非常繁琐),业界的标准做法是直接引入现成的 Markdown 第三方 CSS 主题包。
在这里非常推荐使用 Github 官方的经典样式包:github-markdown-css。
步骤一:安装样式包
npm install github-markdown-css
# 或者使用 pnpm / yarn步骤二:在弹窗组件中引入,并运用到外层容器上
修改刚才的 AgreementModal.vue:
<template>
<el-dialog ...>
<!-- 我们给内容层增加 markdown-body 的类名,这是应用 github 样式的触发锚点 -->
<div class="agreement-content markdown-body">
<Suspense>
...
</Suspense>
</div>
</el-dialog>
</template>
<script setup>
// 在组件代码上方,或者直接在项目的 main.ts 中全局引入样式表
import 'github-markdown-css/github-markdown.css';
</script>
仅仅是多了两行代码,你的协议内容立刻就会变得和在 Github 或掘金等技术社区上看文章一样排版规整、段落分明、极其舒适了!如果你有定制化的主题颜色等要求,也可以利用 .markdown-body 这个 class 在自己的代码里手写 CSS 针对性覆盖重写。
如果你的项目是支持亮色和暗色模式的,这个样式库也是支持的,你可以阅读我之前的文章:《在 Vue 项目中实现 github-markdown-css 的亮色与暗色模式动态切换》。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据