vue3前端后端地址可配置方案

news2025/6/9 19:05:44

在开发vue3项目过程中,需要切换不同的服务器部署,代码中配置的服务需要可灵活配置,不随着run npm build把网址打包到代码资源中,不然每次切换都需要重新run npm build。需要一个配置文件可以修改服务地址,而打包的代码资源直接copy就可以了。

记录一下下面的方法

vue3项目跟路径下创建proxy.js, 并且地址写在proxy.js中,挂载到window对象上

window.APP_config = {
    apiBaseURL: 'http://localhost:8080',
    wsBaseURL: 'ws://localhost:8080/ws',
  };

项目的index.html中延迟加载proxy.js文件

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/png" href="/icon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>KXJL Chat OCR</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
    <script src="/proxy.js" defer></script>
  </body>
</html>

main.js中注入,需要监听dom事件,只有dom加载完成才能调用window对象的属性

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/router.js'
import ElementPlus  from 'element-plus'
import 'element-plus/theme-chalk/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import axios from "axios"

var app = createApp(App)

// 使用 DOMContentLoaded 事件确保 DOM 加载完成后再访问 window.APP_config
document.addEventListener('DOMContentLoaded', () => {
    if (window.APP_config) {
      // 注入配置到 Vue 应用中
      app.provide('APP_config', window.APP_config);
    }
  
    for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
        app.component(key, component)
      }
    router.isReady().then(() => {
    if (router.currentRoute.value.path !== '/') {
        router.push('/');
    }
    });
    app.provide('axios', axios)
    app.use(router)
    app.use(ElementPlus)
    app.mount('#app')
  });

其他任何地方可调用

import { ref, onMounted, onUnmounted, inject } from 'vue';
const APP_config = inject('APP_config');
console.log(APP_config)
const ws_url = APP_config.wsBaseURL    
console.log("ws_url", ws_url)

ws的调用

<template lang="">
    <div class="imgocrinfer">
        <div class="input">
        <div class="imgup">
            <el-upload
                class="upload-demo"
                drag
                action=""
                multiple
                :auto-upload="false"
                :on-change="handleFileChange"
                :on-remove="handleRemove"
            >
                <el-icon class="el-icon--upload"><Plus /></el-icon>
                <div class="el-upload__text">点击或拖拽文件到此处上传</div>
                <div class="el-upload__text">支持jpg、png</div>
                <template #tip>
                <div class="el-upload__tip">
                    jpg/png files with a size less than 500kb
                </div>
                </template>
            </el-upload>
        </div>
        <div class="prompt_input">
            <el-input class="textarea" v-model="prompt"  type="textarea"  placeholder="请输入提示词" @input="on_input_change"> </el-input>
        </div>
        <div class="submit">
            <el-button type="primary" plain :icon="Edit" @click="on_click">点击OCR识别</el-button>
        </div>
    </div>
    <div class="imgshow">
            <div>
                <span>图片预览</span>
            </div>
            <div class="imgPreview" ref="imageRef" style=" margin-top:10px">
                <img v-if="previewImage" :src="previewImage" alt="Preview Imag" @load="onImageLoad">
            </div>
    </div>
    <div class="textshow">
        <div>
            <span>文字识别结果</span>
        </div>
        <div style="margin-top:10px">
            <el-input class="textarea"  v-model="text_result"  type="textarea"  placeholder="" > </el-input>
        </div>
    </div>
    
    </div>
</template>

<script setup>
    import { ref, onMounted, onUnmounted, inject } from 'vue';
    import { Edit } from '@element-plus/icons-vue';

    const text_result = ref("");
    const previewImage = ref('');
    const prompt = ref('');
    const image_base64_str = ref("")
    const imageRef = ref(null);
    const imgWidth = ref("");
    const imgHeight = ref("");
    const maxImageWidth = ref('');
    const maxImageHeight = ref('');
    const ws = ref(null);
    const isConnected = ref(false);

    const APP_config = inject('APP_config');
    console.log(APP_config)
    const ws_url = APP_config.wsBaseURL    
    console.log("ws_url", ws_url)


    const connectWebSocket =() =>{
      ws.value = new WebSocket(ws_url);
      ws.value.onopen = onOpenHandler;
      ws.value.onmessage = (event) => {        
            text_result.value += event.data
            // 如果这是最后一条预期的消息,关闭连接
            if ( event.data.isLastMessage) {
                ws.value.close()
            }
        };

        ws.value.onclose = (event) => {
            isConnected.value = false
            console.log('WebSocket connection closed', event);
            
        };

        ws.value.onerror = (error) => {
            console.error('WebSocket error', error);
        };
    }

    onUnmounted(()=>{
        if (ws.value && ws.value.readyState === WebSocket.OPEN){
            ws.value.close()
        }
    })

    const onOpenHandler = () => {
        isConnected.value = true;
        sendTextAndImage()
    }

    const on_input_change = () =>{
        text_result.value = ""
    }

    // 发送数据
    async function sendTextAndImage() {
        console.log("sendTextAndImage")
        const payload = {
            "text":prompt.value,
            "image_base64_str": image_base64_str.value,
        };

        if (isConnected.value && prompt.value && image_base64_str.value){
            console.log("payload: ", payload)
            ws.value.send(JSON.stringify(payload));
        }
    }

    const handleFileChange = (file, uploadFiles) =>{
        if (uploadFiles.length >= 2){
            uploadFiles.splice(0,uploadFiles.length-1)
        }
        if(file.raw && file.raw.type.startsWith("image/")){
            const reader = new FileReader()
            reader.onload = (e) => {
                previewImage.value = e.target.result
                image_base64_str.value = e.target.result.split(",")[1]
                let img = new Image()
                img.src = e.target.result
                img.onload=()=>{
                    imgWidth.value = img.width
                    imgHeight.value = img.height 
                    if (imgWidth.value > imgHeight.value) {
                        // 如果图片宽度大于高度,则限制宽度
                        maxImageWidth.value = "100%";
                        maxImageHeight.value = "auto";
                    } else {
                        // 否则,限制高度
                        maxImageWidth.value = 'auto';
                        maxImageHeight.value = `100%`;
                    }
                }
            }
                reader.readAsDataURL(file.raw)
        }else{
            ElMessage.error('Please upload an image file.')
        }
    };
    const handleRemove = (file, uploadFiles) =>{    
        if (uploadFiles.length ==0){
            previewImage.value = ""
            prompt.value = ""
            text_result.value = ""
        }
    };

    const onImageLoad = () => {
      // 在图片加载完成后,强制浏览器重新计算尺寸
      // 这可以通过触发一个重排或重绘来实现
        requestAnimationFrame(() => {
            // 强制浏览器重排
            const imgElement = document.querySelector('img')
            imgElement.style.width = maxImageWidth.value;
            imgElement.style.height = maxImageHeight.value
        });
    };

    const on_click = ()=>{
        if ( ! prompt.value && !image_base64_str.value){
            alert("prompt 和 图片不能为空,请重新输入")
        }
        text_result.value = ""
        if (! isConnected.value){
            connectWebSocket()
        }
        // onOpenHandler()
        // prompt.value = ""
        // image_base64_str.value = ""
    }

    defineOptions({
        name: 'imgOcrInference',
    });
</script>

<style lang="scss">
.imgocrinfer{
    display: flex;
    .input{
        width: 14.7vw;
        height: 90vh;
        border:2px solid #ffffff;
        box-shadow: 1px 0 4px #8c9eb11a;
        border-radius: 8px;
        .imgup{
            width: 14.7vw;
            height: 30vh;
            border:2px solid #ffffff;
            box-shadow: 1px 0 4px #8c9eb11a;
            border-radius: 8px;
            .el-upload{
                width: 14.3vw;
            }
        }
        .prompt_input{
            height: 15vh;
            .el-textarea__inner{
                width: 14.3vw;
                resize: none;
                height: 100px;
            }
        }
        .submit{
            width: 14.3vw;
            .el-button {
                width: 14.3vw;
            }
        }
    }
    .imgshow{
        height: 600px;
        width: 29.5vw;
        border:2px solid #ffffff;
        box-shadow: 1px 0 4px #8c9eb11a;
        border-radius: 8px;
        margin-left: 1vw;
        .imgPreview{
            border:2px solid rgb(167, 200, 238);
            height: 58vh;
            border-radius: 8px;
        }
    }

    .textshow{
        width: 19.8vw;
        border:2px solid #ffffff;
        box-shadow: 1px 0 4px #8c9eb11a;
        border-radius: 8px;
        .el-textarea__inner{
            width: 19.8vw;
            height: 58vh;
        }

    }
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2386242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

digitalworld.local: FALL靶场

digitalworld.local: FALL 来自 <digitalworld.local: FALL ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182&#xff0c;靶场IP192.168.23.4 3&…

MySQL---库操作

mysql> create database if not exists kuku3; 1.库操作的语法 create database [if not exists] db_name [create_specification [, create_specification] ...] create_specification: [default] character set charset_name [default] collate collation_name详细解释…

动态规划算法:字符串类问题(2)公共串

0 前言 上节课我们已经讲述了使用动态规划求取回文串长度与数量的方法&#xff08;和本节课关系不大&#xff0c;感兴趣可以去看字符串类问题&#xff08;1&#xff09;回文串&#xff09;&#xff0c;这节课我们继续探索字符串问题中的动态规划问题。 进入本篇文章前&#x…

深度解析Vue项目Webpack打包分包策略 从基础配置到高级优化,全面掌握性能优化核心技巧

深度解析Vue项目Webpack打包分包策略 从基础配置到高级优化&#xff0c;全面掌握性能优化核心技巧 一、分包核心价值与基本原理 1.1 为什么需要分包 首屏加载优化&#xff1a;减少主包体积&#xff0c;提升TTI&#xff08;Time to Interactive&#xff09;缓存利用率提升&am…

spring-boot-starter-data-redis应用详解

一、依赖引入与基础配置 添加依赖 在 pom.xml 中引入 Spring Data Redis 的 Starter 依赖&#xff0c;默认使用 Lettuce 客户端&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis<…

5060显卡驱动PyCUDA开发环境搭建

5060显卡驱动PyCUDA开发环境搭建 本文手把手讲解了RTX5060ti显卡从上手尝试折腾&#xff0c;到在最新Ubuntu LTS版本上CUDA开发环境搭建成功的详细流程。 1.1 开机后Ubuntu2404LTS不识别显卡 1.1.1 显卡硬件规格要求 笔者下单的铭瑄电竞之心&#xff0c;安装规格是PCIe …

redis搭建最小的集群,3主3从

create.sh脚本用于快速部署一个Docker化的Redis集群。首先&#xff0c;脚本创建了一个自定义的Docker网络redis-net&#xff0c;并指定了子网以防止IP变动。接着&#xff0c;脚本设置了宿主机的公网IP&#xff0c;并生成了六个Redis节点的配置文件&#xff0c;每个配置文件都启…

【sylar-webserver】10 HTTP模块

HTTP 解析 这里使用 nodejs/http-parser 提供的 HTTP 解析器。 HTTP 常量定义 HttpMethod HttpStatus /* Request Methods */ #define HTTP_METHOD_MAP(XX) \XX(0, DELETE, DELETE) \XX(1, GET, GET) \XX(2, HEAD, HEAD) …

海康NVR录像回放SDK原始流转FLV视频流:基于Java的流媒体转码(无需安装第三方插件ffmpeg)

wlinker-video-monitor 代码地址&#xff1a;https://gitee.com/wlinker/wlinker-video-monitor 背景与需求 在安防监控、智能楼宇等场景中&#xff0c;海康威视设备作为行业主流硬件&#xff0c;常需要将录像回放功能集成到Web系统中。然而&#xff0c;海康设备的原始视频流…

运维Linux之Ansible详解学习(更新中)

什么是Ansible Ansible 是一款新出现的自动化运维工具&#xff0c;基于 Python 开发。以下是对它的详细介绍&#xff1a; 功能特点&#xff1a;集合了众多运维工具的优点&#xff0c;能实现批量系统配置、批量程序部署、批量运行命令等功能。它是基于模块工作的&#xff0c;本…

深入浅出IIC协议 - 从总线原理到FPGA实战开发 -- 第三篇:Verilog实现I2C Master核

第三篇&#xff1a;Verilog实现I2C Master核 副标题 &#xff1a;从零构建工业级I2C控制器——代码逐行解析与仿真实战 1. 架构设计 1.1 模块分层设计 三层架构 &#xff1a; 层级功能描述关键信号PHY层物理信号驱动与采样sda_oe, scl_oe控制层协议状态机与数据流控制state…

ARM笔记-嵌入式系统基础

第一章 嵌入式系统基础 1.1嵌入式系统简介 1.1.1嵌入式系统定义 嵌入式系统定义&#xff1a; 嵌入式系统是以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可剪裁&#xff0c;对功能、可靠性、成本、体积、功耗等有严格要求的专用计算机系统 ------Any devic…

upload-labs通关笔记-第19关文件上传之条件竞争

系列目录 upload-labs通关笔记-第1关 文件上传之前端绕过&#xff08;3种渗透方法&#xff09; upload-labs通关笔记-第2关 文件上传之MIME绕过-CSDN博客 upload-labs通关笔记-第3关 文件上传之黑名单绕过-CSDN博客 upload-labs通关笔记-第4关 文件上传之.htacess绕过-CSDN…

第5章:任务间通信机制(IPC)全解析

💬 在多线程开发中,线程之间如何协作?如何让一个线程产生数据,另一个线程消费数据?本章聚焦 Zephyr 提供的多种任务间通信机制(IPC)及实战使用技巧。 📚 本章导读 你将学到: Zephyr 提供的常用 IPC 接口:FIFO、消息队列、邮箱、信号量 每种机制适用场景和用法对比…

CAPL自动化-诊断Demo工程

文章目录 前言一、诊断控制面板二、诊断定义三、发送诊断通过类.方法的方式req.SetParameterdiagSetParameter四、SendRequestAndWaitForResponse前言 本文将介绍CANoe的诊断自动化测试,工程可以从CANoe的 Sample Configruration 界面打开,也可以参考下面的路径中打开(以实…

SVN被锁定解决svn is already locked

今天遇到一个问题&#xff0c;svn 在提交代码的时候出现了svn is already locked&#xff0c;解决方案

【深度学习】1. 感知器,MLP, 梯度下降,激活函数,反向传播,链式法则

一、感知机 对于分类问题&#xff0c;我们设定一个映射&#xff0c;将x通过函数f(x)映射到y 1. 感知机的基本结构 感知机&#xff08;Perceptron&#xff09;是最早期的神经网络模型&#xff0c;由 Rosenblatt 在 1958 年提出&#xff0c;是现代神经网络和深度学习模型的雏形…

云原生安全:网络协议TCP详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 &#xff08;注&#xff1a;文末附可视化流程图与专有名词说明表&#xff09; 1. 基础概念 TCP&#xff08;Transmission Control Protocol&#xff09;是…

使用CentOS部署本地DeekSeek

一、查看服务器的操作系统版本 cat /etc/centos-release二、下载并安装ollama 1、ollama下载地址&#xff1a; Releases ollama/ollama GitHubGet up and running with Llama 3.3, DeepSeek-R1, Phi-4, Gemma 3, Mistral Small 3.1 and other large language models. - Re…

LLMs之Qwen:《Qwen3 Technical Report》翻译与解读

LLMs之Qwen&#xff1a;《Qwen3 Technical Report》翻译与解读 导读&#xff1a;Qwen3是Qwen系列最新的大型语言模型&#xff0c;它通过集成思考和非思考模式、引入思考调度机制、扩展多语言支持以及采用强到弱的知识等创新技术&#xff0c;在性能、效率和多语言能力方面都取得…