小程序canvas2d实现横版全屏和竖版逐字的签名组件(字帖式米字格签名组件)

news2025/3/13 15:53:03

文章标题

  • 01 功能说明
  • 02 效果预览
    • 2.1 横版
    • 2.2 竖版
  • 03 使用方式
  • 04 横向签名组件源码
    • 4.1 html 代码
    • 4.2 业务 Js
    • 4.3 样式 Css
  • 05 竖向签名组件源码
    • 5.1 布局 Html
    • 5.2 业务 Js
    • 5.3 样式 Css

01 功能说明

技术栈:uniapp、vue、canvas 2d

需求

  • 实现横版的全名字帖式米字格签名组件,竖版逐字的字帖式米字格签名组件;
  • 支持配置文字描述、画笔颜色、画笔大小等;
  • 提供 submit 事件,当点击提交按钮时触发,回调参数是canvas转化为图片的地址;

02 效果预览

2.1 横版

横版截图
在这里插入图片描述

2.2 竖版

竖版截图
在这里插入图片描述

03 使用方式

// 使用横向签名------------------------
<template>
  <HorizontalSign signText="赵钱孙" @submit="handleSubmit" /> 
</template>

<script>
import HorizontalSign from '@/components/HorizontalSign.vue';

export default {
  components: { HorizontalSign },
  methods: {
    handleSubmit(imagePath) {
      console.log('--image--', imagePath);
    },
  },
} 
<script> 

// 使用竖向签名------------------------
<template>
  <VerticalSign signText="赵钱孙"  @submit="handleSubmit" />
</template>

<script>
import VerticalSign from '@/components/VerticalSign.vue';

export default {
  components: { VerticalSign },
  methods: {
    handleSubmit(imagePath) {
      console.log('--image--', imagePath);
    },
  },
}  
<script> 

04 横向签名组件源码

4.1 html 代码

<template>
  <view class="wrapping">
    <!-- header 定位后以左上角顺时针旋转 90° -->
    <view class="header-title flex col-center">
      <!-- <text> 签名:</text> -->
      <!-- 预览图片(图片本身是正向的,但由于父元素旋转了90°所以正好能横向观看) -->
      <!-- <image :src="previewImage" mode="aspectFit" class="small-preview" /> -->
      <text class="desc-text">{{ description }}</text>
    </view>
    <!-- 实际保持直立正向 canvas 容器 -->
    <view class="canvas-wrapper">
      <!-- 只展示限制数量文字的米字格,超过配置数量文字则不展示 -->
      <view class="char-group flex-col flex-center" v-if="signText && signText.length <= riceGridLimit">
        <view class="char-box" v-for="(item, index) in signText" :key="index">
          {{ item }}
        </view>
      </view>
      <canvas
        id="signatureCanvas"
        type="2d"
        class="signature-canvas"
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd"
        @touchcancel="handleTouchEnd"
        disable-scroll
      ></canvas>
    </view>
    <!-- footer 定位后以右下角顺时针旋转 90° -->
    <view class="footer-btn flex">
      <view class="action-btn" @click="resetCanvas">重签</view>
      <view class="action-btn submit-btn" @click="handleSubmit">{{ submitText }}</view>
    </view>
    <!--用于绘制并生成旋转为正向签名图片的 canvas 容器-->
    <canvas id="previewCanvas" type="2d" class="preview-canvas"></canvas>
  </view>
</template>

4.2 业务 Js

<script>
export default {
  props: {
    description: {
      type: String,
      default: '请使用正楷字体,逐字签写', //  文字描述
    },
    submitText: {
      type: String,
      default: '提交', // 提交按钮文字
    },
    dotSize: {
      type: Number,
      default: 4, // 签名笔大小
    },
    penColor: {
      type: String,
      default: '#000000', // 签名笔颜色
    },
    signText: {
      type: String,
      default: '', // 签名文字
    },
    riceGridLimit: {
      type: Number,
      default: 3, // 米字格展示字数最大限制
    },
  },
  data() {
    return {
      mainCtx: null,
      mainCanvas: null,
      isDrawing: false,
      touchPoints: [],
      signIsMove: false,
      previewImage: '',
      canvasRatio: 1,
    };
  },
  mounted() {
    this.canvasRatio = uni.getWindowInfo().pixelRatio ?? 1;
    this.initCanvas();
  },
  methods: {
    initCanvas() {
      const domItem = uni.createSelectorQuery().in(this).select('#signatureCanvas');
      domItem.fields({ node: true, size: true }).exec((res) => {
        // Canvas 对象
        this.mainCanvas = res[0]?.node;
        // 渲染上下文
        this.mainCtx = this.mainCanvas.getContext('2d');
        // Canvas 画布的实际绘制宽高
        const width = res[0].width;
        const height = res[0].height;
        // 初始化画布大小
        this.mainCanvas.width = width * this.canvasRatio;
        this.mainCanvas.height = height * this.canvasRatio;
        this.mainCtx.scale(this.canvasRatio, this.canvasRatio);
        this.setPen();
      });
    },
    setPen() {
      this.mainCtx.strokeStyle = this.penColor;
      this.mainCtx.lineWidth = this.dotSize;
      this.mainCtx.lineCap = 'round';
      this.mainCtx.lineJoin = 'round';
    },
    handleTouchStart(e) {
      const point = {
        x: e.changedTouches[0].x,
        y: e.changedTouches[0].y,
      };
      this.touchPoints.push(point);
      this.isDrawing = true;
    },
    handleTouchMove(e) {
      if (!this.isDrawing) return;
      const point = {
        x: e.touches[0].x,
        y: e.touches[0].y,
      };
      this.touchPoints.push(point);
      const len = this.touchPoints.length;
      if (len >= 2) {
        const prevPoint = this.touchPoints[len - 2];
        const currentPoint = this.touchPoints[len - 1];
        this.mainCtx.beginPath();
        this.mainCtx.moveTo(prevPoint.x, prevPoint.y);
        this.mainCtx.lineTo(currentPoint.x, currentPoint.y);
        this.mainCtx.stroke();
        this.signIsMove = true;
      }
    },
    handleTouchEnd() {
      this.isDrawing = false;
      this.touchPoints = [];
    },
    resetCanvas() {
      if (!this.signIsMove) {
        return;
      }
      this.mainCtx.clearRect(0, 0, 1000, 1000);
      this.setPen();
      this.touchPoints = [];
      this.previewImage = '';
      this.signIsMove = false;
    },
    async handleSubmit() {
      if (!this.signIsMove) {
        uni.showToast({ title: '请先完成签名', icon: 'none' });
        return;
      }
      try {
        const _this = this;
        uni.canvasToTempFilePath({
          canvas: this.mainCanvas,
          quality: 1,
          fileType: 'png',
          success: (res) => {
            let path = res.tempFilePath;
            _this.handlePreviewImage(path);
          },
          fail: (res) => {
            uni.showToast({ title: '提交失败,请重新尝试', icon: 'none' });
          },
        });
      } catch (err) {
        uni.showToast({ title: '签名失败,请重试', icon: 'none' });
      } finally {
        uni.hideLoading();
      }
    },
    handlePreviewImage(imagePath) {
      const _this = this;
      const previewDom = uni.createSelectorQuery().in(_this).select('#previewCanvas');
      previewDom.fields({ node: true, size: true }).exec((res) => {
        // Canvas 对象
        const canvas = res[0]?.node;
        // 渲染上下文
        const previewCtx = canvas.getContext('2d');
        const image = canvas.createImage();
        image.src = imagePath;
        image.onload = () => {
          let { width, height } = image;
          // 获取图片的宽高初始画布,canvas交换宽高
          canvas.width = height;
          canvas.height = width;
          // 设置白色背景
          previewCtx.fillStyle = '#FFFFFF';
          previewCtx.fillRect(0, 0, height, width);
          // 图片逆时针旋转90度,且换为弧度
          previewCtx.rotate((-90 * Math.PI) / 180);
          // 旋转后调整绘制的位置下移一个宽度的距离
          previewCtx.drawImage(image, -width, 0);
        };
        // 最终导出
        setTimeout(() => {
          uni.canvasToTempFilePath(
            {
              canvas,
              fileType: 'png', // 指定文件类型
              quality: 1, // 最高质量
              success: (res) => {
                _this.previewImage = res.tempFilePath;
                uni.previewImage({ urls: [res.tempFilePath], current: 0 });
                _this.$emit('submit', res.tempFilePath);
              },
              fail: (err) => {
                uni.showToast({ title: '合成失败,请重试', icon: 'none' });
              },
            },
            _this
          );
        }, 300); // 增加最终导出前的延迟
      });
    },
  },
};
</script>

4.3 样式 Css

<style scoped>
.wrapping {
  position: relative;
  padding: 20rpx;
  margin: 20rpx;
  background-color: #fff;
  box-sizing: border-box;
}

.header-title {
  position: absolute;
  right: 20rpx;
  top: 20rpx;
  height: 50rpx;
  z-index: 1000;
  transform-origin: top left;
  transform: translateX(100%) rotate(90deg);
  font-size: 32rpx;
  color: #333;
}

.desc-text {
  color: #969799;
}

.small-preview {
  width: 100rpx;
  height: 50rpx;
  border-bottom: 1px solid #333;
}

.canvas-wrapper {
  position: relative;
  margin: auto;
  width: 60%;
  height: 80vh;
  background: #f7f8fa;
}

.char-group {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
  z-index: 1;
  gap: 20rpx;
}

.char-box {
  padding: 36rpx;
  width: 30vw;
  height: 30vw;
  transform: rotate(90deg);
  font-size: 30vw;
  line-height: 30vw;
  text-align: center;
  color: #eeeeee;
  /* 使用虚线边框框住字体 */
  /* border: 1px dashed #ccc; */
  /* 使用米字格照片当背景图 */
  background: url('https://img1.baidu.com/it/u=2622499137,3527900847&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500') no-repeat;
  background-size: 100%;
  text-shadow: 1px 1px black, -1px -1px black, 1px -1px black, -1px 1px black;
}

.signature-canvas {
  position: relative;
  width: 100%;
  height: 100%;
  z-index: 2;
}

.footer-btn {
  position: absolute;
  left: 20rpx;
  bottom: 20rpx;
  transform-origin: bottom right;
  transform: translateX(-100%) rotate(90deg);
  z-index: 1000;
  gap: 32rpx;
}

.action-btn {
  text-align: center;
  width: 200rpx;
  height: 96rpx;
  border-radius: 100rpx;
  font-size: 32rpx;
  line-height: 96rpx;
  color: #3874f6;
  border: 2rpx solid #3874f6;
  background: #fff;
}

.submit-btn {
  color: #fff;
  border: 2rpx solid #3874f6;
  background: #3874f6;
}

.preview-canvas {
  visibility: hidden;
  position: fixed;
  /* 将画布移出展示区域 */
  top: 100vh;
  left: 100vw;
  opacity: 0;
  z-index: 0;
}
</style>

05 竖向签名组件源码

5.1 布局 Html

<template>
  <view class="signature-container">
    <view class="desc-text">{{ description }}</view>
    <view class="signature-area">
      <view class="canvas-wrapper">
        <!-- 逐字展示文字 -->
        <view class="char-box" v-if="signText && currentCharIndex < signText.length">
          {{ signText[currentCharIndex] }}
        </view>
        <canvas
          id="signatureCanvas"
          class="signature-canvas"
          type="2d"
          @touchstart="handleTouchStart"
          @touchmove="handleTouchMove"
          @touchend="handleTouchEnd"
          @touchcancel="handleTouchEnd"
          disable-scroll
        ></canvas>
      </view>
      <view class="action-box">
        <view class="action-btn" v-if="currentCharIndex > 0" @click="prevChar">上一字</view>
        <view class="action-btn" @click="resetCanvas">清空画板</view>
        <view class="action-btn" v-if="currentCharIndex < signText.length" @click="nextChar">
          {{ currentCharIndex < signText.length - 1 ? '下一字' : '确认' }}
        </view>
      </view>
    </view>

    <view class="preview-title">逐字预览</view>
    <view class="preview-content">
      <image v-for="(img, index) in previewImages" :key="index" :src="img" mode="aspectFit" class="preview-char" />
    </view>

    <view class="action-box">
      <view class="action-btn submit-btn" @click="resetAllRecord">全部重签</view>
      <view class="action-btn submit-btn" @click="handleSubmit">{{ submitText }}</view>
    </view>

    <!--用于拼接合并为完整签名图片的 canvas 容器-->
    <canvas id="previewCanvas" type="2d" class="preview-canvas"></canvas>
  </view>
</template>

5.2 业务 Js

<script>
export default {
  props: {
    description: {
      type: String,
      default: '请使用正楷字体,逐字签写', // 文字描述
    },
    submitText: {
      type: String,
      default: '提交', // 提交按钮文字
    },
    dotSize: {
      type: Number,
      default: 4, // 签名笔大小
    },
    penColor: {
      type: String,
      default: '#000000', // 签名笔颜色
    },
    signText: {
      type: String,
      default: '', // 签名文字
    },
  },
  data() {
    return {
      mainCtx: null,
      mainCanvas: null,
      isDrawing: false,
      touchPoints: [],
      allTouchPoints: [],
      signIsMove: false,
      currentCharIndex: 0,
      canvasRatio: 1,
      previewImages: [],
    };
  },
  mounted() {
    this.canvasRatio = uni.getWindowInfo().pixelRatio ?? 1;
    this.initCanvas();
  },
  methods: {
    initCanvas() {
      const domItem = uni.createSelectorQuery().in(this).select('#signatureCanvas');
      domItem.fields({ node: true, size: true }).exec((res) => {
        // Canvas 对象
        this.mainCanvas = res[0]?.node;
        // 渲染上下文
        this.mainCtx = this.mainCanvas.getContext('2d');
        // Canvas 画布的实际绘制宽高
        const width = res[0].width;
        const height = res[0].height;
        // 初始化画布大小
        this.mainCanvas.width = width * this.canvasRatio;
        this.mainCanvas.height = height * this.canvasRatio;
        this.mainCtx.scale(this.canvasRatio, this.canvasRatio);
        this.setPen();
      });
    },
    setPen() {
      this.mainCtx.strokeStyle = this.penColor;
      this.mainCtx.lineWidth = this.dotSize;
      this.mainCtx.lineCap = 'round';
      this.mainCtx.lineJoin = 'round';
    },
    handleTouchStart(e) {
      const point = {
        x: e.changedTouches[0].x,
        y: e.changedTouches[0].y,
      };
      this.touchPoints.push(point);
      this.allTouchPoints.push(point);
      this.isDrawing = true;
    },
    handleTouchMove(e) {
      if (!this.isDrawing) return;
      const point = {
        x: e.touches[0].x,
        y: e.touches[0].y,
      };
      this.touchPoints.push(point);
      this.allTouchPoints.push(point);
      const len = this.touchPoints.length;
      if (len >= 2) {
        const prevPoint = this.touchPoints[len - 2];
        const currentPoint = this.touchPoints[len - 1];
        this.mainCtx.beginPath();
        this.mainCtx.moveTo(prevPoint.x, prevPoint.y);
        this.mainCtx.lineTo(currentPoint.x, currentPoint.y);
        this.mainCtx.stroke();
        this.signIsMove = true;
      }
    },
    handleTouchEnd() {
      this.isDrawing = false;
      this.touchPoints = [];
    },
    getRectangle(points) {
      // 计算每个字符的实际大小
      let minX = Number.POSITIVE_INFINITY;
      let minY = Number.POSITIVE_INFINITY;
      let maxX = Number.NEGATIVE_INFINITY;
      let maxY = Number.NEGATIVE_INFINITY;
      for (let point of points) {
        minX = Math.min(minX, point.x);
        minY = Math.min(minY, point.y);
        maxX = Math.max(maxX, point.x);
        maxY = Math.max(maxY, point.y);
      }
      return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
    },
    prevChar() {
      if (this.previewImages.length > 0) {
        this.previewImages.pop();
        this.currentCharIndex--;
        this.resetCanvas();
      }
    },
    nextChar() {
      if (!this.signIsMove) {
        uni.showToast({ title: '请先完成签名', icon: 'none' });
        return;
      }
      try {
        const { x, y, width, height } = this.getRectangle(this.allTouchPoints);
        const offset = 10;
        const _this = this;
        uni.canvasToTempFilePath(
          {
            canvas: this.mainCanvas,
            x: x - offset,
            y: y - offset,
            width: width + offset * 2,
            height: height + offset * 2,
            success: (res) => {
              _this.previewImages.push(res.tempFilePath);
              _this.currentCharIndex++;
              _this.resetCanvas();
            },
            fail: () => {
              uni.showToast({ title: '提交失败,请重新尝试', icon: 'none' });
            },
          },
          _this
        );
      } catch (err) {
        uni.showToast({ title: '保存失败,请重试', icon: 'none' });
      }
    },
    resetCanvas() {
      this.mainCtx.clearRect(0, 0, 1000, 1000);
      this.setPen();
      this.touchPoints = [];
      this.allTouchPoints = [];
      this.signIsMove = false;
    },
    resetAllRecord() {
      this.previewImages = [];
      this.currentCharIndex = 0;
      this.resetCanvas();
    },
    async handleSubmit() {
      if (this.previewImages.length <= 0) {
        uni.showToast({ title: '请至少签写一个字', icon: 'none' });
        return;
      }
      try {
        this.handlePreviewImage();
      } catch (err) {
        uni.showToast({ title: '合成失败,请重试', icon: 'none' });
      }
    },
    handlePreviewImage() {
      const _this = this;
      const previewDom = uni.createSelectorQuery().in(_this).select('#previewCanvas');
      previewDom.fields({ node: true, size: true }).exec((res) => {
        // Canvas 对象
        const canvas = res[0]?.node;
        // 渲染上下文
        const previewCtx = canvas.getContext('2d');
        // 计算总宽度和单个字的尺寸
        const charWidth = 300 / this.previewImages.length;
        const charHeight = 300 / this.previewImages.length;
        const totalWidth = charWidth * this.previewImages.length;
        // 设置白色背景
        previewCtx.fillStyle = '#FFFFFF';
        previewCtx.fillRect(0, 0, totalWidth, charHeight);
        // 按顺序绘制每个图片
        for (let i = 0; i < this.previewImages.length; i++) {
          const image = canvas.createImage();
          image.src = this.previewImages[i];
          image.onload = () => {
            const x = i * charWidth;
            // 绘制当前图片
            previewCtx.drawImage(image, x, 0, charWidth, charHeight);
          };
        }
        // 最终导出
        setTimeout(() => {
          uni.canvasToTempFilePath(
            {
              canvas,
              x: 0,
              y: 0,
              width: totalWidth,
              height: charHeight,
              fileType: 'png', // 指定文件类型
              quality: 1, // 最高质量
              success: (res) => {
                uni.previewImage({ urls: [res.tempFilePath], current: 0 });
                _this.$emit('submit', res.tempFilePath);
              },
              fail: (err) => {
                uni.showToast({ title: '合成失败,请重试', icon: 'none' });
              },
            },
            _this
          );
        }, 300); // 增加最终导出前的延迟
      });
    },
  },
};
</script>

5.3 样式 Css

<style scoped>
.signature-container {
  padding: 0 20rpx 40rpx 20rpx;
  background-color: #f5f5f5;
  box-sizing: border-box;
}

.signature-area {
  padding: 50rpx;
  background-color: #fff;
  box-sizing: border-box;
}

.desc-text {
  padding: 20rpx 0;
  font-size: 32rpx;
  color: #333;
  text-align: center;
  box-sizing: border-box;
}

.canvas-wrapper {
  position: relative;
  width: 100%;
  /* 保持宽高比 */
  aspect-ratio: 1;
  /* height: 600rpx; */
  background: #fff;
  /* 使用虚线边框框住字体 */
  /* border: 1px dashed #ccc; */
  /* 使用米字格照片当背景图 */
  background: url('https://img1.baidu.com/it/u=2622499137,3527900847&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500') no-repeat;
  background-size: 100%;
}

.char-box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 400rpx;
  text-shadow: 1px 1px black, -1px -1px black, 1px -1px black, -1px 1px black;
  color: #eeeeee;
  pointer-events: none;
  user-select: none;
  z-index: 1;
}

.signature-canvas {
  position: relative;
  width: 100%;
  height: 100%;
  z-index: 2;
}

.action-box {
  display: flex;
  margin-top: 32rpx;
  gap: 20rpx;
}

.action-btn {
  flex: 1;
  text-align: center;
  padding: 16rpx 30rpx;
  font-size: 28rpx;
  color: #3874f6;
  border: 2rpx solid #3874f6;
  border-radius: 80rpx;
  box-sizing: border-box;
}

.submit-btn {
  background: #3874f6;
  color: #fff;
}

.preview-title {
  margin-top: 32rpx;
  width: 100%;
  text-align: center;
  font-size: 28rpx;
  color: #666;
}

.preview-content {
  display: flex;
  flex-wrap: wrap;
  margin-top: 20rpx;
  background-color: #fff;
  padding: 20rpx 20rpx 0 20rpx;
  min-height: 190rpx;
  box-sizing: border-box;
}

.preview-char {
  width: 150rpx;
  height: 150rpx;
  margin-right: 19rpx;
  margin-bottom: 20rpx;
}

.preview-canvas {
  position: fixed;
  left: -2000px;
  width: 300px;
  height: 300px;
}
</style>

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

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

相关文章

MoE演变过程

MoE演变过程 1 MoE1.1 BasicMoE1.2 SparseMoE1.2.1 实现 1.3 Shared Expert SparseMoE 1 MoE 参考&#xff1a;https://huggingface.co/blog/zh/moe 1.1 BasicMoE 用router给出各专家的权重&#xff0c;然后让输入过每一个专家&#xff0c;然后做加权求和。 1.2 SparseMoE …

【实战项目】BP神经网络识别人脸朝向----MATLAB实现

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

【1.8w字深入解析】从依赖地狱到依赖天堂:pnpm 如何革新前端包管理?

目录 前言npm 的诞生与发展嵌套依赖模型存在的问题npm3架构与yarnYarn 的诞生与局限Yarn 的诞生背景Yarn 仍然存在的问题 何为幽灵依赖依赖结构的不确定性pnpm王牌登场 -- 网状平铺结构安装包速度快依赖管理软链接 和 硬链接 机制 幽灵依赖产生的根本原因包管理工具的依赖解析机…

137,【4】 buuctf web [SCTF2019]Flag Shop

进入靶场 都点击看看 发现点击work会增加&#xffe5; 但肯定不能一直点下去 抓包看看 这看起来是一个 JWT&#xff08;JSON Web Token&#xff09;字符串。JWT 通常由三部分组成&#xff0c;通过点&#xff08;.&#xff09;分隔&#xff0c;分别是头部&#xff08;Header&…

【c++】c++内存管理

目录 c和c的内存分布回顾C语言动态管理内存的方式malloccallocreallocfree C动态管理内存的方式new和deleteoperator new和operator delete定位new c和c的内存分布 回顾C语言动态管理内存的方式 malloc void* malloc (size_t size);malloc可以在堆上开辟指定内存的空间&#…

EtherNet/IP转Modbus TCP:新能源风电监控与分析实用案例

EtherNet/IP转Modbus TCP&#xff1a;新能源风电监控与分析实用案例 一、案例背景 在某新能源汽车电池生产线上&#xff0c;需要将采用EtherNet/IP协议的电池检测设备与采用ProfiNet协议的生产线控制系统进行集成&#xff0c;以实现对电池生产过程的全面监控和数据采集。 二、…

数字电路-基础逻辑门实验

基础逻辑门是数字电路设计的核心元件&#xff0c;它们执行的是基本的逻辑运算。通过这些基本运算&#xff0c;可以构建出更为复杂的逻辑功能。常见的基础逻辑门包括与门&#xff08;AND&#xff09;、或门&#xff08;OR&#xff09;、非门&#xff08;NOT&#xff09;、异或门…

国产编辑器EverEdit - 如虎添翼的功能:快速选择

1 快速选择 1.1 应用场景 快速选择适用于批量选择和修改的场景&#xff0c;比如&#xff1a;变量改名。 1.2 使用方法 1.2.1 逐项快速选择 将光标放置在单词前或单词中&#xff0c;选择主菜单查找 -> 快速选择 -> 快速选择或使用快捷键Ctrl D 注&#xff1a;光标放…

国内外网络安全政策动态(2025年1月)

▶︎ 1.国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》 1月3日&#xff0c;国家互联网信息办公室发布《个人信息出境个人信息保护认证办法&#xff08;征求意见稿&#xff09;》。根据《意见稿》&#xff0c;个人信息出境个…

68页PDF | 数据安全总体解决方案:从数据管理方法论到落地实践的全方位指南(附下载)

一、前言 这份报告旨在应对数字化转型过程中数据安全面临的挑战&#xff0c;并提供全面的管理与技术体系建设框架。报告首先分析了数字化社会的发展背景&#xff0c;强调了数据安全在国家安全层面的重要性&#xff0c;并指出数据安全风险的来源和防护措施。接着&#xff0c;报…

AI大模型的文本流如何持续吐到前端,实时通信的技术 SSE(Server-Sent Events) 认知

写在前面 没接触过 SSE&#xff08;Server-Sent Events&#xff09;&#xff0c;AI大模型出来之后&#xff0c;一直以为文本流是用 WebSocket 做的偶然看到返回到报文格式是 text/event-stream,所以简单认知&#xff0c;整理笔记博文内容涉及 SSE 认知&#xff0c;以及对应的 D…

Electron:使用electron-react-boilerplate创建一个react + electron的项目

使用 electron-react-boilerplate git clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name cd your-project-name npm install npm start 安装不成功 在根目录加上 .npmrc文件 内容为 electron_…

Spring Boot三:Springboot自动装配原理

精心整理了最新的面试资料&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 原理初探 pom.xml 核心依赖在父工程中 spring-boot-dependencies所有的jar包都在这里管理 我们在写或者引入一些依赖的时候&#xff0c;不需要指定版本 启动器 <…

2024 年 CSDN 博客之星年度评选:技术创作与影响力的碰撞(统计时间2025-02-17 11:06:06)

摘要&#xff1a;在技术的海洋里&#xff0c;每一位博主都像是一座独特的灯塔&#xff0c;用自己创作的光芒照亮他人前行的道路。2024 年 CSDN 博客之星年度评选活动&#xff0c;正是对这些灯塔的一次盛大检阅&#xff0c;让我们看到了众多优秀博主在技术创作领域的卓越表现以及…

Java零基础入门笔记:(3)程序控制

前言 本笔记是学习狂神的java教程&#xff0c;建议配合视频&#xff0c;学习体验更佳。 【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili Scanner对象 之前我们学的基本语法中我们并没有实现程序和人的交互&#xff0c;但是Java给我们提供了这样一个工具类&…

后端生成二维码,前端请求接口生成二维码并展示,且多个参数后边的参数没有正常传输问题处理

一、后端代码 1、controller GetMapping("/generateQRCode/{url}")ApiOperation(value "生成url链接二维码",notes "生成url链接二维码")public JsonResult<NewsQRCodeVo> generateQRCode(PathVariable String url,HttpServletRespons…

(8/100)每日小游戏平台系列

项目地址位于&#xff1a;小游戏导航 新增一个打地鼠游戏&#xff01; 打地鼠&#xff08;Whack-a-Mole&#xff09;是一款经典的休闲游戏&#xff0c;玩家需要点击随机出现的地鼠&#xff0c;以获取分数。游戏时间有限&#xff0c;玩家需要在规定时间内尽可能多地击中地鼠&am…

[Python人工智能] 五十.PyTorch入门 (5)快速搭建神经网络及模型保存

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解PyTorch构建分类神经网络。这篇文章将介绍如何利用PyTorch快速构建神经网络,之前的代码比较复杂,通过自定义Net类实现,本文通过Torch函数定义神经网络。前面我们的Python人工智能主要以Tens…

SpringBoot+Vue+数据可视化的动漫妆造服务平台(程序+论文+讲解+安装+调试+售后等)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在当今数字化高速发展的时代&#xff0c;动漫产业迎来了前所未有的繁荣&#xff0c;动漫…

基于web的留守儿童网站的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…