vite 全局追加loading

本文介绍了如何使用Vite开发环境中创建一个名为insert-loading的插件,该插件会在Vue组件中动态插入loading效果。同时处理了文件路径的规范化和uni-app的兼容性问题。
import * as fs from 'node:fs';
import path from 'node:path';

import normallize from 'normalize-path';
import type { PluginOption } from 'vite';
import { defaultPagesRE } from './app-provider'

export default function insertLoading() {
  const template = fs.readFileSync(normalizePagePathFromBase('./src/layout/AppProvider.vue'), 'utf-8');
  return <PluginOption>{
    name: 'insert-loading',
    transform(code, id) {
      id = normalizePagePathFromBase(id);
      const regResult = defaultPagesRE.exec(id);
      if (regResult) {
        const oldTemplate = /<template>([\s\S]*)<\/template>/.exec(code);
        const tempString = oldTemplate != null ? oldTemplate[1] : '';
        const newTemplate = template.replace('<!-- template -->', tempString as string);
        code = code.replace(/<template>([\s\S]*)<\/template>/, newTemplate);
      }
      return code;
    },
  };
  function normalizePagePathFromBase(file: string) {
    return normallize(path.relative(process.cwd(), file));
  }
}

<template>
  <view>
    <LoadingPage></LoadingPage>
    <!-- template -->
  </view>
</template>

注意追加的插件在uni之前

plugins: [
    AutoImportTypes(),
    PiniaAutoRefs(),
    AutoImport({
      dts: 'src/auto-imports.d.ts',
      imports: [
        'vue',
        'uni-app',
        'pinia',
        {
          '@/helper/pinia-auto-refs': ['useStore'],
        },
      ],
      exclude: ['createApp'],
      eslintrc: {
        enabled: true,
      },
    }),
    Components({
      extensions: ['vue'],
      dts: 'src/components.d.ts',
    }),
    // 上面写的插件
    insertLoading(),
    // uni支持(兼容性写法,当type为module时,必须要这样写)
    (uniPlugin as any).default(),
    Unocss(),
  ],
<template> <div class="main"> <div> <div class="trend-content"> <div class="chart-placeholder"> <!-- 加载中提示 --> <div v-if="isLoading" class="loading"> <p>正在加载中...</p> </div> <!-- 后端返回内容渲染(支持Markdown格式) --> <div v-else class="strategy-content" v-html="talkStrategy"></div> </div> </div> </div> </div> </template> <script lang='ts'> import { reactive, toRefs, onMounted, nextTick } from 'vue' // import { useRoute } from 'vue-router' import { marked } from 'marked' // 导入WebSocket工具函数 import { getWsUrl } from './utils/webscoket'; // 定义类型接口,规范数据结构 interface WsResponse { answer?: string; is_stop?: boolean; [key: string]: any; } export default { name: 'TrendDrawerPage', setup() { const data = reactive({ currentRow: null as CurrentRow | null, socket: null as WebSocket | null, wsMessage: null as string | null, isLoading: false, talkStrategy: "", }); // Markdown格式化函数 const formatMarkdown = (content: string): string => { if (!content) return "<p>暂无分析结果</p>"; // 使用marked库解析Markdown(更规范的处理方式) // 自定义marked渲染器来优化样式 const renderer = new marked.Renderer(); // 自定义标题渲染 - 修复方法签名 renderer.heading = ({ text, depth }: { text: string; depth: number }) => { const headingClass = `md-heading md-heading-${depth}`; return `<h${depth} class="${headingClass}">${text}</h${depth}>`; }; // 自定义段落渲染 renderer.paragraph = (text) => { return `<p class="md-paragraph">${text}</p>`; }; // 自定义列表渲染 renderer.list = (body, ordered, start) => { const tag = ordered ? "ol" : "ul"; return `<${tag} class="md-list"${ start ? ` start="${start}"` : "" }>${body}</${tag}>`; }; renderer.listitem = (text) => { return `<li class="md-list-item">${text}</li>`; }; // 自定义分隔线 renderer.hr = () => '<hr class="md-divider">'; // 配置marked选项 marked.setOptions({ renderer: renderer, breaks: true, gfm: true, }); // 解析Markdown return marked(content); }; // 发送分析请求 const sendAnalysisRequest = () => { if (!data.socket || data.socket.readyState !== WebSocket.OPEN) { console.error("WebSocket 未连接"); return; } const requestData = { task_type: data.currentRow?.type, person_name: data.currentRow?.intervieweeName, talk_reason: data.currentRow?.thyy, timestamp: data.currentRow?.startTime, context: "", question: "", }; try { const jsonStr = JSON.stringify(requestData); data.socket.send(jsonStr); } catch (error) { console.error("JSON序列化失败:", error); data.talkStrategy = `<p>数据发送失败:${(error as Error).message}</p>`; data.isLoading = false; } }; // 抽屉关闭回调 const onDrawerClose = (done: () => void) => { // 关闭WebSocket连接 if (data.socket) { data.socket.close(1000, "用户关闭抽屉"); data.socket = null; } done(); // 确认关闭抽屉 }; // 初始化趋势分析 const trend = async () => { data.currentRow = { id: -1, type: '历史趋势', intervieweeName: '测试51号', thyy: '耳目谈话', startTime: '1751253194000', }; data.isLoading = true; data.talkStrategy = ""; // 关闭现有连接 if (data.socket) { data.socket.close(); data.socket = null; } // 等待DOM更新 await nextTick(); if (!data.currentRow?.id) { console.error("缺少任务ID"); data.isLoading = false; data.talkStrategy = "<p>数据错误:缺少任务ID</p>"; return; } // 构建WebSocket URL(结合环境变量) let wsUrl = ''; if (import.meta.env.VITE_APP_ENV === 'development') { // 开发环境使用代理路径 wsUrl = `/tendency/ws/ws/talkAnalysis/${data.currentRow.id}`; // wsUrl = `ws://192.168.3.148:8000/ws/talkAnalysis/${data.currentRow.id}`; } else { wsUrl = getWsUrl(`/ws/talkAnalysis/${data.currentRow.id}`); } console.log('WebSocket URL:', wsUrl); try { data.socket = new WebSocket(wsUrl); let accumulatedAnswer = ""; data.socket.onopen = () => { console.log("WebSocket连接成功"); sendAnalysisRequest(); }; data.socket.onmessage = (event) => { data.isLoading = false; try { const res: WsResponse = typeof event.data === "string" ? JSON.parse(event.data) : event.data; if (res.answer) { accumulatedAnswer += res.answer; data.talkStrategy = formatMarkdown(accumulatedAnswer); } if (res.is_stop) { data.socket?.close(); data.socket = null; console.log("流式输出结束"); } } catch (error) { console.error("消息解析错误:", error); data.talkStrategy = `<p>数据解析失败:${(error as Error).message}</p>`; } }; data.socket.onerror = (error) => { console.error("WebSocket错误:", error); data.talkStrategy = "<p>连接失败,请重试</p>"; data.isLoading = false; }; data.socket.onclose = (event) => { if (data.isLoading) data.isLoading = false; console.log(`WebSocket关闭:${event.code} - ${event.reason}`); }; } catch (error) { console.error("创建WebSocket失败:", error); data.isLoading = false; data.talkStrategy = `<p>连接创建失败:${(error as Error).message}</p>`; } }; // 组件挂载时初始化 onMounted(() => { // trend(); }); return { ...toRefs(data), onDrawerClose, }; }, }; </script> <style scoped> .trend-content { height: calc(100% - 40px); min-height: 300px; overflow: auto; padding: 10px 0; } .chart-placeholder { padding: 16px; min-height: 200px; } .loading { display: flex; align-items: center; justify-content: center; height: 200px; color: #666; } .strategy-content { line-height: 1.8; color: #333; } .empty提示 { text-align: center; padding: 50px 0; color: #999; } /* Markdown渲染样式 */ .md-heading { margin: 1.2em 0 0.5em; font-weight: 600; } .md-heading-1 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; } .md-heading-2 { font-size: 1.3em; } .md-heading-3 { font-size: 1.1em; } .md-paragraph { margin: 0.8em 0; } .md-list { margin: 0.8em 0; padding-left: 1.5em; } .md-list-item { margin: 0.3em 0; } .md-divider { margin: 1em 0; border: 0; border-top: 1px solid #eee; } </style>基于提供的代码进行修复 亲亲
最新发布
08-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值