Vue3 运行时解析SFC单文件模板字符串

源码地址:2091124175/OnlinePreview: vue3在线预览模板

需求:后端接口返回了一个vue3单文件模板字符串 需要渲染到当前页面。也就是说需要实时解析。

思路:采用vue官网演练场的方案。核心就是@vue/repl库。

@vue/repl 是一个vue在线编辑器支持实时解析并且渲染 如下图。

​编辑

这里我的需求 是需要把后端返回的字符串通过这个库解析渲染到页面中 不需要这个编辑器。

在开始之前先确定环境

vue: 3.5.13

vite: 5.3.2

node:18.20.0

务必确定一样

接下来开始 使用 vite 创建 一个项目 并且 安装好依赖

sql 复制代码
npm create vue@latest
pnpm install

安装 @vue/repl 库

css 复制代码
 pnpm i @vue/[email protected] 

安装 fflate

css 复制代码
pnpm i [email protected]

创建预览组件

xml 复制代码
Preview.vue

<script setup>
import { Sandbox, useStore } from "@vue/repl";
import { onMounted } from "vue";
import { strFromU8, strToU8, zlibSync } from 'fflate';

function utoa(data) {
  const buffer = strToU8(data);
  const zipped = zlibSync(buffer, { level: 9 });
  const binary = strFromU8(zipped, true);
  return btoa(binary);
}

let props = defineProps(["content"])
// 把内容 hash 化
const hash = utoa(
  JSON.stringify({
    "src/App.vue": props.content,
  })
)
const store = useStore({}, hash);
// onMounted(() => {
//   localStorage.setItem("content", "");
// });

</script>

<template>
  <Sandbox v-if="props.content" :store="store" style="height: 100vh" />
</template>

<style scoped></style>

在app.vue中使用

xml 复制代码
<script setup>
import { ref } from "vue";
import Preview from "./components/Preview.vue";

// 这里是你接口回来的模板
let content = ref(`
<template>
  <div class="prank-page">
    <div class="prank-container">
      <h1>⚠ 警告 ⚠</h1>
      <p class="message">您的电脑已被检测到感染了 127 种病毒!</p >
      <p class="fake-scan">正在扫描系统... <span class="percentage">{{ scanProgress }}%</span></p >
      <div class="fake-progress">
        <div class="progress-bar" :style="{ width: scanProgress + '%' }"></div>
      </div>
      
      <p class="urgent-message">立即点击下方按钮修复您的系统!</p >
      
      <button 
        ref="runawayBtn"
        @mouseover="moveButton"
        @click="activatePrank"
        class="prank-btn"
        :style="{ left: btnPosition.x + 'px', top: btnPosition.y + 'px' }"
      >
        {{ btnText }}
      </button>
      
      <div v-if="prankActivated" class="prank-effect">
        <p class="effect-text">哈哈!被骗了吧! 😜</p >
        <button @click="resetPrank" class="reset-btn">再来一次</button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
// 恶作剧状态
const prankActivated = ref(false)
const btnText = ref('立即修复')
const scanProgress = ref(0)
const runawayBtn = ref(null)
const btnPosition = ref({ x: 0, y: 0 })

// 初始化按钮位置
onMounted(() => {
  if (runawayBtn.value) {
    btnPosition.value = {
      x: (window.innerWidth - runawayBtn.value.offsetWidth) / 2,
      y: (window.innerHeight - runawayBtn.value.offsetHeight) / 2 + 100
    }
  }
  
  // 模拟扫描进度
  const interval = setInterval(() => {
    if (scanProgress.value < 100) {
      scanProgress.value += Math.floor(Math.random() * 5) + 1
      if (scanProgress.value > 100) scanProgress.value = 100
    } else {
      clearInterval(interval)
    }
  }, 300)
})

// 移动按钮位置
const moveButton = () => {
  if (prankActivated.value) return
  
  const maxX = window.innerWidth - runawayBtn.value.offsetWidth - 20
  const maxY = window.innerHeight - runawayBtn.value.offsetHeight - 20
  
  btnPosition.value = {
    x: Math.random() * maxX,
    y: Math.random() * maxY
  }
  
  btnText.value = ['点不到', '来抓我', '嘿嘿', '差一点'][Math.floor(Math.random() * 4)]
}

// 激活恶作剧效果
const activatePrank = () => {
  prankActivated.value = true
  btnText.value = '被骗了吧!'
}

// 重置恶作剧
const resetPrank = () => {
  prankActivated.value = false
  scanProgress.value = 0
  btnText.value = '立即修复'
  
  // 重新开始扫描动画
  const interval = setInterval(() => {
    if (scanProgress.value < 100) {
      scanProgress.value += Math.floor(Math.random() * 5) + 1
      if (scanProgress.value > 100) scanProgress.value = 100
    } else {
      clearInterval(interval)
    }
  }, 300)
}
<\/script>

<style scoped>
.prank-page {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #1a1a1a;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: Arial, sans-serif;
  user-select: none;
}

.prank-container {
  text-align: center;
  max-width: 500px;
  padding: 2rem;
  background-color: #2a2a2a;
  border-radius: 10px;
  box-shadow: 0 0 20px rgba(255, 0, 0, 0.3);
}

h1 {
  color: #ff5555;
  font-size: 2.5rem;
  margin-bottom: 1.5rem;
}

.message {
  font-size: 1.2rem;
  margin-bottom: 2rem;
  color: #ff9999;
}

.fake-scan {
  margin-bottom: 0.5rem;
  color: #aaa;
}

.percentage {
  color: #4CAF50;
  font-weight: bold;
}

.fake-progress {
  width: 100%;
  height: 20px;
  background-color: #333;
  border-radius: 10px;
  margin-bottom: 2rem;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  background-color: #4CAF50;
  transition: width 0.3s ease;
}

.urgent-message {
  color: #ff5555;
  font-size: 1.3rem;
  margin-bottom: 2rem;
  font-weight: bold;
}

.prank-btn {
  position: absolute;
  padding: 15px 30px;
  background-color: #ff5555;
  color: white;
  border: none;
  border-radius: 5px;
  font-size: 1.2rem;
  cursor: pointer;
  transition: all 0.2s ease;
}

.prank-btn:hover {
  background-color: #ff3333;
  transform: scale(1.05);
}

.prank-effect {
  margin-top: 2rem;
  animation: shake 0.5s;
}

.effect-text {
  font-size: 2rem;
  color: #4CAF50;
  margin-bottom: 1.5rem;
}

.reset-btn {
  padding: 10px 20px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 5px;
  font-size: 1rem;
  cursor: pointer;
}

.reset-btn:hover {
  background-color: #45a049;
}

@keyframes shake {
  0% { transform: translateX(0); }
  25% { transform: translateX(-10px); }
  50% { transform: translateX(10px); }
  75% { transform: translateX(-10px); }
  100% { transform: translateX(0); }
}
</style>
`);
</script>

<template>
  <div style="height: 100vh; width: 100vw">
    <Preview :content="content" />
  </div>
</template>

<style scoped>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
</style>

问题解决

来自:Vue3 运行时解析SFC单文件模板字符串-CSDN博客

相关推荐
幼儿园技术家5 分钟前
微信小程序/H5 调起确认收款界面
前端
微笑边缘的金元宝10 分钟前
Echarts柱状图斜线环纹(图形的贴花图案)
前端·javascript·echarts
wuxiguala15 分钟前
【web考试系统的设计】
前端
独立开阀者_FwtCoder1 小时前
CSS view():JavaScript 滚动动画的终结
前端·javascript·vue.js
咖啡教室1 小时前
用markdown语法制作一个好看的网址导航页面(markdown-web-nav)
前端·javascript·markdown
独立开阀者_FwtCoder1 小时前
Vue 团队“王炸”新作!又一打包工具发布!
前端·javascript·vue.js
天天扭码1 小时前
一分钟解决“3.无重复字符的最长字串问题”(最优解)
前端·javascript·算法
独立开阀者_FwtCoder1 小时前
Promise 引入全新 API!效率提升 300%!
前端·javascript·后端
陈明勇2 小时前
三句话搞定周末出行攻略!我用 AI 生成一日游可视化页面,还能秒上线!
前端·人工智能·mcp
_一条咸鱼_2 小时前
Vue 样式深入剖析:从基础到源码级理解(十)
前端·javascript·面试