前言

在日常的前端开发中,我们经常需要在界面上向用户出示“用户协议”、“隐私政策”和“操作指引”等长文本内容。

如果将大段文字直接写死在 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.tsvite-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 变为 trueAgreementModal 弹窗便会立刻弹起,顺滑过渡出你要向用户展示的长文本。

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 的亮色与暗色模式动态切换》

分类: vue 项目实战 标签: vuemarkdown组件用户协议

评论

暂无评论数据

暂无评论数据

目录