vue里面使用pdfjs-dist+fabric实现pdf电子签章!!!

news2025/2/25 2:07:41

2022.9.6

一、需求

最近领导提了一个新需求:仿照e签宝,实现pdf电子签章!

最终实现效果图

这是做出来的效果图,当然还有很多待修改
在这里插入图片描述

二、思路

然后我就去看了下人家e签宝的操作界面,左侧是印章,右侧是pdf,然后拖拽印章到pdf上面,点击保存,下次打开时显示印章的位置。
思路:我首先想到了拖拽、pdf预览、坐标;分工明确,前端来实现拖拽,pdf预览及把印章信息和坐标传给后端,后端只需要把信息和坐标保存下来就可以了。

三、使用插件

之前实现pdf预览就是通过window.open,打开一个窗口,显示pdf,功能很多,但是和需求不符,需要做的事是把pdf显示出来,同时可以可以拖拽印章到上面去,也不要放大与缩小及其他的功能。百度下了,说是用pdfjs-dist,这个pdf插件可以自定义很多的功能,但是实际用起来,发现好坑。。最后去百度了下,vue实现pdf电子签章, 看有没有现成的,然后还真找到了一个。js处理pdf展示、分页和签章等功能,下载到本地(只许查看index.html文件即可)后发现大佬用的不是vue-cli脚手架,是引用的cdn链接,然后就cv到项目里面了,跟着步骤,安装了pdfjs-dist插件(pdf插件)和fabric插件(专门处理印章的插件)这两个插件,但是项目本地运行后,报错了。。

四、遇到的问题

1.TypeError: Cannot read properties of undefined( reading 'Globalworkeroptions ')

在这里插入图片描述
百思不得其解啊,照着步骤来的啊,为啥呢,然后又回去看了下大佬的代码,发现他的pdf.js不是用的pdfjs-dist,而是引入的pdf的cdn链接
在这里插入图片描述
然后我就在项目的public/index.html下面引入这个链接
在这里插入图片描述
pdf路径则是使用的一个在线的pdf链接,https://www.gjtool.cn/pdfh5/git.pdf,发现可以打开了(样式做了些修改)
在这里插入图片描述
2.Dev Tools failed to load source map: Could not load content for https //mozilla github.ia/pdf js/build/pdf js map: Load canceled due to loadtimeout
开始觉得似乎已经大功告成了,到时候和后端商量下返回数据的格式就完事了的,谁知道还是有问题的。。多次打开关闭pdf后,有时候pdf会不加载出来了。人麻了,然后看了下提示,加载超时了,取消加载。
在这里插入图片描述
明显是cdn链接的问题,那就把pdf.js文件下载到本地呗,本地加载快,应该不会出现加载超时的问题,结果还是有问题。
在这里插入图片描述
唉,真的是服了,使用cdn链接吧,会加载超时,下载到本地引用吧,又会报这么个莫名其妙的问题,然后今天浏览博客时,发现一个兄弟碰到了一样的问题,哈哈,发现还是引入pdf方式的问题

/* 引用cdn链接,可以使用但会加载超时 */ 
// let pdfjsLib = window["pdfjs-dist/build/pdf"];
// pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://mozilla.github.io/pdf.js/build/pdf.worker.js';
/* 下载到本地,看着官方文档引用,报个莫名其妙的错 */
// import pdfjsLib from 'pdfjs-dist';
// pdfjsLib.GlobalWorkerOptions.workerSrc='pdfjs-dist/build/pdf.worker.js';
/* 下载到本地,照着大佬的方式引用,完美! */
let pdfjsLib =require("pdfjs-dist/legacy/build/pdf.js");
import workerSrc from "pdfjs-dist/legacy/build/pdf.worker.entry";
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;

这是大佬的博客链接pdf.js 使用攻略及错误集合

不过这项目的电子签章有些与众不同,用户打开的pdf,是可以自定义的,即用户打开弹窗,在tinymac编辑器里面输入内容,然后切换tab,会立即生成一个pdf,接下来才是用户使用电子签章的过程

以下是电子签章的主要代码,和大佬的index.html的代码差不多,就是做了点修改(ps:目前印章的位置和坐标保存,使用的得本地缓存,便于调试,后期会保存到接口里面!)

代码部分

首先 引入pdfjs-dist插件和fabric插件

npm install pdfjs-dist
npm i fabric --save

html部分

<div id="elesign" class="elesign">
	<el-row>
    <el-col :span="4" style="margin-top:1%;">
      <div class="left-title">我的印章</div>
      <draggable v-model="mainImagelist" :group="{ name: 'itext', pull: 'clone' }" :sort="false" @end="end">
        <transition-group type="transition">
          <li v-for="item in mainImagelist" :key="item" class="item" style="text-align:center;">
            <img :src="item" width="100%;" height="100%" class="imgstyle" />
          </li>
        </transition-group>
      </draggable>
    </el-col>
    <el-col :span="16" style="text-align:center;" class="pCenter">
      <div class="page">
        <!-- <el-button class="btn-outline-dark" @click="zoomIn">-</el-button>
        <span style="color:red;">{{(percentage*100).toFixed(0)+'%'}}</span>
        <el-button class="btn-outline-dark" @click="zoomOut">+</el-button> -->
        <el-button class="btn-outline-dark" @click="prevPage">上一页</el-button>
        <el-button class="btn-outline-dark" @click="nextPage">下一页</el-button>
        <el-button class="btn-outline-dark">{{ pageNum }}/{{ numPages }}</el-button>
        <el-input-number style="margin:0 5px;border-radius:5px;" class="btn-outline-dark"  v-model="pageNum" :min="1" :max="numPages" label="输入页码"></el-input-number>
        <el-button class="btn-outline-dark" @click="cutover">跳转</el-button>
      </div>
      <canvas id="the-canvas" />
      <!-- 盖章部分 -->
      <canvas id="ele-canvas"></canvas>
      <div class="ele-control" style="margin-bottom:2%;">
        <el-button class="btn-outline-dark" @click="removeSignature"> 删除签章</el-button>
        <el-button class="btn-outline-dark" @click="clearSignature"> 清除所有签章</el-button>
        <el-button class="btn-outline-dark" @click="submitSignature">提交所有签章信息</el-button>
      </div>
    </el-col>
    <el-col :span="4" style="margin-top:1%;">
      <div class="left-title">任务信息</div>
      <div style="text-align:center;">
        <div>
          <div class="right-item">
            <div class="right-item-title">文件主题</div>
            <div class="detail-item-desc">{{ taskInfo.title }}</div>
          </div>
          <div class="right-item">
            <div class="right-item-title">发起方</div>
            <div class="detail-item-desc">{{ taskInfo.uname }}</div>
          </div>
          <div class="right-item">
            <div class="right-item-title">截止时间</div>
            <div class="detail-item-desc">{{ taskInfo.endtime }}</div>
          </div>
        </div>
      </div>
    </el-col>
  </el-row>
</div>

js部分

import {fabric} from 'fabric';
let pdfjsLib =require("pdfjs-dist/legacy/build/pdf.js");
import workerSrc from "pdfjs-dist/legacy/build/pdf.worker.entry";
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
import draggable from "vuedraggable";
export default {
  components: {draggable},
  data() {
    return {
      //pdf预览
      pdfUrl: '',
      pdfDoc: null,
      numPages: 1,
      pageNum: 1,
      scale: 2.2,
      pageRendering: false,
      pageNumPending: null,
      sealUrl: '',
      signUrl: '',
      canvas: null,
      ctx: null,
      canvasEle: null,
      whDatas: null,
      mainImagelist: [],
      taskInfo: {},
    }
  },
  computed: {
      hasSigna() {
        return this.canvasEle && this.canvasEle.getObjects()[0] ? true : false;
      },
  },
  created(){
    var that = this;
    that.mainImagelist = [require('./sign.png'),require('./seal.png')];
    that.taskInfo = {'title':'测试盖章', uname:'张三', endtime:'2021-09-01 17:59:59'};
  },
  methods: {
    //pdf预览
    // zoomIn() {
    //   console.log("缩小");
    //   if(this.scale<=0.5){
    //     this.$message.error("已经显示最小比例")
    //   }else{
    //     this.scale-=0.1;
    //     this.percentage-=0.1;
    //     this.renderPage(this.pageNum);
    //     this.renderFabric();
    //   }
    // },
    // zoomOut() {
    //   console.log("放大")
    //   if(this.scale>=2.2){
    //     this.$message.error('已经显示最大比例')
    //   }else{
    //     this.scale+=0.1;
    //     this.percentage+=0.1;
    //     this.renderPage(this.pageNum);
    //     this.renderFabric();
    //   }
    // },
    renderPage(num) {
      let _this = this;
      this.pageRendering = true;
      return this.pdfDoc.getPage(num).then((page) => {
        let viewport = page.getViewport({ scale: _this.scale });//设置视口大小
        _this.canvas.height = viewport.height;
        _this.canvas.width = viewport.width;
        
        // Render PDF page into canvas context
        let renderContext = {
          canvasContext: _this.ctx,
          viewport: viewport,
        };
        let renderTask = page.render(renderContext);
        // Wait for rendering to finish
        renderTask.promise.then(() => {
          _this.pageRendering = false;
          if (_this.pageNumPending !== null) {
            // New page rendering is pending
            this.renderPage(_this.pageNumPending);
            _this.pageNumPending = null;
          }
        });
      });
    },
    queueRenderPage(num) {
      if (this.pageRendering) {
        this.pageNumPending = num;
      } else {
        this.renderPage(num);
      }
    },
    prevPage() {
      this.confirmSignature();
      if (this.pageNum <= 1) {
        return;
      }
      this.pageNum--;
    },
    nextPage() {
      this.confirmSignature();
      if (this.pageNum >= this.numPages) {
        return;
      }
      this.pageNum++;
    },
    cutover() {
      this.confirmSignature();
    },
    //渲染pdf,到时还会盖章信息,在渲染时,同时显示出来,不应该在切换页码时才显示印章信息
    showpdf(pdfUrl) {
      let caches = JSON.parse(localStorage.getItem('signs')); //获取缓存字符串后转换为对象
      console.log(caches);
      if(caches == null) return false;
      let datas = caches[this.pageNum];
      if(datas != null && datas != undefined) {
        for (let index in datas) {
          this.addSeal(datas[index].sealUrl, datas[index].left, datas[index].top, datas[index].index);
        }
      }
      this.canvas = document.getElementById("the-canvas");
      this.ctx = this.canvas.getContext("2d");
      pdfjsLib.getDocument({url:pdfUrl, rangeChunkSize:65536, disableAutoFetch:false}).promise.then((pdfDoc_) => {
        this.pdfDoc = pdfDoc_;
        this.numPages = this.pdfDoc.numPages;
        this.renderPage(this.pageNum).then(() => {
          this.renderPdf({
            width: this.canvas.width,
            height: this.canvas.height,
          });
        });
        this.commonSign(this.pageNum, true);
      });
    },
    /**
   *  盖章部分开始
   */
    // 设置绘图区域宽高
    renderPdf(data) {
      this.whDatas = data;
      // document.querySelector("#elesign").style.width = data.width + "px";
    },
    // 生成绘图区域
    renderFabric() {
      let canvaEle = document.querySelector("#ele-canvas");
      let pCenter=document.querySelector(".pCenter");
      canvaEle.width = pCenter.clientWidth;
      // canvaEle.height = (this.whDatas.height)*(this.scale);
      canvaEle.height = this.whDatas.height;

      this.canvasEle = new fabric.Canvas(canvaEle);
      let container = document.querySelector(".canvas-container");
      container.style.position = "absolute";
      container.style.top = "50px";
      // container.style.left = "30%";
    },
    // 相关事件操作哟
    canvasEvents() {
      // 拖拽边界 不能将图片拖拽到绘图区域外
      this.canvasEle.on("object:moving", function (e) {
        var obj = e.target;
        // if object is too big ignore
        if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
          return;
        }
        obj.setCoords();
        // top-left  corner
        if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
          obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
          obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
        }
        // bot-right corner
        if(obj.getBoundingRect().top+obj.getBoundingRect().height  > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width  > obj.canvas.width){
          obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
          obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
        }
      });
    },
    // 添加公章
    addSeal(sealUrl, left, top, index) {
      fabric.Image.fromURL(
        sealUrl,
        (oImg) => {
          oImg.set({
            left: left,
            top: top,
            // angle: 10,
            scaleX: 0.8,
            scaleY: 0.8,
            index:index,
          });
            // oImg.scale(0.5); //图片缩小一
          this.canvasEle.add(oImg);
        }
      );
    },
    // 删除签章
    removeSignature() {
      this.canvasEle.remove(this.canvasEle.getActiveObject())
    },
    //翻页展示盖章信息
    commonSign(pageNum, isFirst = false) {
      if(isFirst == false) this.canvasEle.remove(this.canvasEle.clear()); //清空页面所有签章
      let caches = JSON.parse(localStorage.getItem('signs')); //获取缓存字符串后转换为对象
      console.log(caches);
      if(caches == null) return false;
      let datas = caches[this.pageNum];
      if(datas != null && datas != undefined) {
        for (let index in datas) {
          this.addSeal(datas[index].sealUrl, datas[index].left, datas[index].top, datas[index].index);
        }
      }
    },
    //确认签章位置并保存到缓存
    confirmSignature() {
      let data = this.canvasEle.getObjects(); //获取当前页面内的所有签章信息
      let caches = JSON.parse(localStorage.getItem('signs')); //获取缓存字符串后转换为对象

      let signDatas = {}; //存储当前页的所有签章信息
      let i = 0;
      // let sealUrl = '';
      for(var val of data) {
        signDatas[i] =  {
          width: val.width,
          height: val.height,
          top: val.top,
          left: val.left,
          angle: val.angle,
          translateX: val.translateX,
          translateY: val.translateY,
          scaleX: val.scaleX,
          scaleY: val.scaleY,
          pageNum: this.pageNum,
          sealUrl: this.mainImagelist[val.index],
          index:val.index
        }
        i++;
      }
      if(caches == null) {
          caches = {};
          caches[this.pageNum] = signDatas;
      } else {
          caches[this.pageNum] = signDatas;
      }
      localStorage.setItem('signs', JSON.stringify(caches)); //对象转字符串后存储到缓存
    },
    //提交数据
    submitSignature() {
        this.confirmSignature();
        let caches = localStorage.getItem('signs');
        console.log(JSON.parse(caches));
        return false
    },
    //清空数据
    clearSignature() {
      this.canvasEle.remove(this.canvasEle.clear()); //清空页面所有签章
      localStorage.removeItem('signs'); //清除缓存
    },
    end(e){
   this.addSeal(this.mainImagelist[e.newDraggableIndex], e.originalEvent.layerX, e.originalEvent.layerY, e.newDraggableIndex)
    },
    //设置PDF预览区域高度
    setPdfArea(){
       this.pdfUrl = 'https://www.gjtool.cn/pdfh5/git.pdf';
       this.pdfurl=res.data.data.pdfurl;
       this.$nextTick(() => {
         this.showpdf(this.pdfUrl);//接口返回的应该还有盖章信息,不只是pdf
       });
    },
  },
  watch: {
    whDatas: {
      handler() {
        const loading = this.$loading({
          lock: true,
          text: 'Loading',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        });
        if (this.whDatas) {
          console.log(this.whDatas)
          loading.close();
          this.renderFabric();
          this.canvasEvents();
          let eleCanvas=document.querySelector("#ele-canvas");
          eleCanvas.style="border:1px solid #5ea6ef";
        }
      },
    },
    pageNum: function() {
      this.commonSign(this.pageNum);
      this.queueRenderPage(this.pageNum);
    }
  }
},

css部分

<style scoped>

/*pdf部分*/

.pCenter{
  overflow-x: hidden;
}
#the-canvas{
  margin-top:10px;
}

html:fullscreen {
    background: white;
}
.elesign {
    display: flex;
    flex: 1;
    flex-direction: column;
    position: relative;
    /* padding-left: 180px; */
    margin: auto;
    /* width:600px; */
}
.page {
    text-align:center;
    margin:0 auto;
    margin-top: 1%;
}
#ele-canvas {
    /* border: 1px solid #5ea6ef; */
    overflow: hidden;
}
.ele-control {
    text-align: center;
    margin-top: 3%;
}
#page-input {
    width: 7%;
}

@keyframes ani-demo-spin {
    from { transform: rotate(0deg);}
    50% { transform: rotate(180deg);}
    to { transform: rotate(360deg);}
}
/* .loadingclass{
    position: absolute;
    top:30%;
    left:49%;
    z-index: 99;
} */
.left {
    position: absolute;
    top: 42px;
    left: -5px;
    padding: 5px 5px;
    /*border: 1px solid #eee;*/
    /*border-radius: 4px;*/
}
.left-title {
    text-align:center;
    padding-bottom: 10px;
    border-bottom: 1px solid #eee;
}
li {
    list-style-type:none;
    padding: 10px;
}
.imgstyle{
    vertical-align: middle;
    width: 130px;
    border: solid 1px #e8eef2;
    background-image: url("tuo.png");
    background-repeat:no-repeat;
}
.right {
    position: absolute;
    top: 7px;
    right: -177px;
    margin-top: 34px;
    padding-top: 10px;
    padding-bottom: 20px;
    width: 152px;
    /*border: 1px solid #eee;*/
    /*border-radius: 4px;*/
}
.right-item {
    margin-bottom: 15px;
    margin-left: 10px;
}
.right-item-title {
    color: #777;
    height: 20px;
    line-height: 20px;
    font-size: 12px;
    font-weight: 400;
    text-align: left !important;
}
.detail-item-desc {
    color: #333;
    line-height: 20px;
    width: 100%;
    font-size: 12px;
    display: inline-block;
    text-align: left;
}
.btn-outline-dark {
    color: #0f1531;
    background-color: transparent;
    background-image: none;
    border:1px solid #3e4b5b;
}

.btn-outline-dark:hover {
    color: #fff;
    background-color: #3e4b5b;
    border-color: #3e4b5b;
}

2022.9.13修改
使用的时候,发现在pdf第一页添加的印章,下次再打开时,不在显示,本地缓存的也是显示{},所以琢磨了下,应该是每次打开pdf页面重置了,代码做了以下修改

在这里插入图片描述
将选中部分改为以下代码
在这里插入图片描述

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

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

相关文章

Vue 原理整理

目录 1. 组件化基础>&#xff08;MVVM模型&#xff09; 2. Vue的响应式原理 3.为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty&#xff1f; 4.computed 的实现原理 5. computed 和 watch 有什么区别及运用场景? 6. Vue 中的 key 到底有什么用&#xff…

VUE中watch的详细使用教程

1、watch是什么&#xff1f; watch:是vue中常用的侦听器&#xff08;监听器&#xff09;&#xff0c;用来监听数据的变化 2、watch的使用方式如下 watch: { 这里写你在data中定义的变量名或别处方法名: { handler(数据改变后新的值, 数据改变之前旧的值) { 这里写你拿到变化值后…

uniApp实现热更新

热更新 热更新是开发中常见且常用的一种软件版本控制的方式&#xff0c;在uniapp进行使用热更新将软件实现更新操作 思路: 服务器中存储着最新版本号&#xff0c;前端进行查询可以在首次进入应用时进行请求版本号进行一个匹对如果版本号一致则不提示&#xff0c;反之则提示进行…

Layui的layer.confirm弹框用法,很详细

Layui的弹框用法一、官网示例二、自定义一些样式1、自定义标题2、给提示框内容加上图标3、定义按钮的位置4、自定义关闭按钮5、自定义遮罩6、自定义点击弹框外遮罩关闭弹框7、定义唯一弹框8、自定义弹框出场动画9、关闭弹框动画10、设置弹框可以最大或者最小化11、设置弹框固定…

前端react axios 发送post请求fastapi响应报错422 (Unprocessable Entity)

post请求fastapi响应报错:422 (Unprocessable Entity) 最近在学习和使用fastapi的时候遇到了一个问题,就是发送了post请求后服务器端回应:422 (Unprocessable Entity), 具体含义: HTTP 422 状态码表示服务器理解请求实体的内容类型&#xff0c;并且请求实体的语法是正确的&am…

CSS盒子居中的6种方法!

大家好, 我是菜鸟,今天给大家带来几种css盒子居中的方法! 1.flex布局设置居中 常见的一种方式就是使用flex布局设置居中。 利用弹性布局(flex)&#xff0c;实现水平居中&#xff0c;其中justify-content 用于设置弹性盒子元素在主轴&#xff08;横轴&#xff09;方向上的对…

如何成功安装webpack

按照B站里的视频总是报错&#xff0c;于是去CSDN里尝试了多种方法&#xff0c;也不知道是哪种方法有效了&#xff0c;叠加了多种方法&#xff0c;都记下来。 做换行变色的例子时不会变色。因为jQuery包属于es6语法&#xff0c;并不是所有浏览器都能支持es6语法。因此&#xff0…

resetFields重置初始值不生效的原因

问题 最近在做项目的时候, dialog组件回调close里面 一般我都会加个resetFields 重置初始值和校验 其他地方都没问题, 在table组件里面出问题了, 后来经过监听vue tools, 查看到resetFields后, formData没变, 最后也找到原因了. 解决 1、用 dialog【新增】、【修改】数据 2、…

uniapp 项目搭建

1、uni-app 简介 uni-app 是一个使用 Vue.js 开发所有前端应用的框架。开发者编写一套代码&#xff0c;可发布到 iOS、Android、H5、以及各种小程序&#xff08;微信/支付宝/百度/头条/QQ/钉钉/淘宝&#xff09;、快应用等多个平台。 详细的 uni-app 官方文档&#xff0c;请翻…

一个简单的网页设计HTML5作业

前言&#xff1a; HTML5是Web中核心语言HTML的规范&#xff0c;用户使用任何手段进行网页浏览时看到的内容原本都是HTML格式的&#xff0c;在浏览器中通过一些技术处理将其转换成为了可识别的信息。HTML5在从前HTML4.01的基础上进行了一定的改进&#xff0c;虽然技术人员在开发…

如何在vue项目中使用rem布局

如何在vue项目中使用rem布局 场景&#xff1a; 在我们开发移动端项目中&#xff0c;实现手机页面布局&#xff0c;需要使用rem布局&#xff0c;那么该如何使用呢&#xff1f; 本文就详细的讲解了在vue项目中使用rem布局的整个过程&#xff0c;可以仔细阅读哦&#xff01; 方法&…

对象 和 json 互转 四种方式 json-lib、Gson、FastJson、Jackson

文章目录一、 json-lib二、 Google的Gson1.简介2. 配置步骤1. MAVEN 依赖引入2. gsonUtil 工具类3. 排除不要序列化的熟悉注解类 Exclude三. 阿里巴巴的FastJson1.简介2.配置步骤1.引入maven2. 配置 CustomFastjsonConfig3. 测试4. 开源的Jackson简介&#xff1a;Jackson配置Ob…

手把手教你入门Vue,猴子都能看懂的教程

目录标题一、Vue简介二、模板语法2.1 插值语法2.2 指令语法三、数据绑定3.1 单向数据绑定3.2 双向数据绑定四、写法区别4.1 el写法4.2 data写法五、MVVM模型六、数据代理七、事件处理7.1 基本使用7.2 修饰符7.3 键盘事件7.3.1 系统提供7.3.2 原始key值7.3.3 系统修饰键八、计算…

【Node.js】一文带你开发博客项目之接口(处理请求、搭建开发环境、开发路由)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿 offer&#xff08;秋招&#xff09; &#x1f947;推荐学习&…

微信小程序实现文字长按复制、一键复制功能

一、不引入外部组件的实现方式 <!-- index.wxml --> <view><!-- 长按复制 --><view bindlongtap"copyText" data-key"{{item.cdkey}}">{{ item.cdkey }}</view><text bindlongtap"copyText" data-key"{{i…

【Web实战-Tomcat-Servlet-Thymeleaf -JDBC-MySQL】浏览器页面显示数据库数据(水果库存系统)

&#x1f947;作者 .29. 的✔博客主页✔ &#x1f947;记录JavaWeb学习的专栏&#xff1a;Web专栏 &#x1f947;拼搏起来吧&#xff0c;未来会给你开出一个无法拒绝的条件… 您的点赞&#xff0c;收藏以及关注是对作者最大的鼓励喔 ~~ 客户端展示库存数据前言一、Thymeleaf - …

每天一个CSS小特效,文字闪烁——【钢铁侠:爱你三千遍】

文章目录前言效果图HTML篇CSS篇1. 盒子模型设置2. 动画设置完整代码前言 我是前端小刘不怕牛牛&#xff0c;love you 3000&#xff0c;愿你能遇到值得的人。 今天分享一个唯美的文字闪烁CSS特效 希望大家能喜欢 效果图 HTML篇 代码&#xff1a; <div class"main"…

uniapp中获取dom元素的方法,更改dom元素颜色(遇坑记录)

前言 最近写uniapp&#xff0c;遇到一个需要获取到页面中dom元素&#xff0c;我第一反应是使用this.$ refs进行获取&#xff0c;于是我开心的使用this.$refs写了很多代码&#xff0c;使用h5调试的过程中没有发现任何问题&#xff0c;但后来真机调试的时候发现在app端无效&…

npm安装报错(npm ERR! code EPERM npm ERR! syscall mkdir npm ERR! path C:\Program Files\nodejs\node_ca...)

使用npm安装时候报错了 根据网上经验解决方法&#xff1a; 1.删除.npmrc文件 该文件在&#xff1a;C:\Users{账户}\下的.npmrc文件&#xff0c; 一般这种类型的都是默认被隐藏&#xff0c;一定要选择将隐藏取消掉 删掉即可。 注意&#xff1a;当前方式确实是最有效的操作&…

猿创征文|如何在HbuilderX中运行Vue

第一步&#xff1a;安装Node.js 这里不加赘述&#xff0c;是傻瓜式安装 第二步&#xff1a;找到安装的Node在哪里 如果找不到可以打开cmd 输入 where node 第三步&#xff1a;打开HbuilderX 点击工具 选择设置 如图&#xff1a; 选择运行配置 ②③的位置是需要填写的位置 使用我…