Vue3水印(Watermark)

news2024/11/23 11:55:40

APIs

参数说明类型默认值必传
width水印的宽度,默认值为 content 自身的宽度numberundefinedfalse
height水印的高度,默认值为 content 自身的高度numberundefinedfalse
rotate水印绘制时,旋转的角度,单位 °number-22false
zIndex追加的水印元素的 z-indexnumber9false
image图片源,建议使用 2 倍或 3 倍图,优先级高于文字stringundefinedfalse
content水印文字内容string | string[]‘’false
color字体颜色string‘rgba(0,0,0,.15)’false
fontSize字体大小,单位pxnumber16false
fontWeight字体粗细‘normal’ | ‘light’ | ‘weight’ | number‘normal’false
fontFamily字体类型string‘sans-serif’false
fontStyle字体样式‘none’ | ‘normal’ | ‘italic’ | ‘oblique’‘normal’false
gap水印之间的间距[number, number][100, 100]false
offset水印距离容器左上角的偏移量,默认为 gap/2[number, number][50, 50]false

效果如下图:在线预览

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建水印组件Watermark.vue

<script setup lang="ts">
import {
  unref,
  shallowRef,
  computed,
  watch,
  onMounted,
  onBeforeUnmount,
  nextTick,
  getCurrentInstance,
  getCurrentScope,
  onScopeDispose
} from 'vue'
import type { CSSProperties } from 'vue'
interface Props {
  width?: number // 水印的宽度,默认值为 content 自身的宽度
  height?: number // 水印的高度,默认值为 content 自身的高度
  rotate?: number // 水印绘制时,旋转的角度,单位 °
  zIndex?: number // 追加的水印元素的 z-index
  image?: string // 图片源,建议使用 2 倍或 3 倍图,优先级高于文字
  content?: string|string[] // 水印文字内容
  color?: string // 字体颜色
  fontSize?: number // 字体大小
  fontWeight?: 'normal'|'light'|'weight'|number // 	字体粗细
  fontFamily?: string // 字体类型
  fontStyle?: 'none'|'normal'|'italic'|'oblique' // 字体样式
  gap?: [number, number] // 水印之间的间距
  offset?: [number, number] // 水印距离容器左上角的偏移量,默认为 gap/2
}
const props = withDefaults(defineProps<Props>(), {
  width: undefined,
  height: undefined,
  rotate: -22,
  zIndex: 9,
  image: undefined,
  content: '',
  color: 'rgba(0,0,0,.15)',
  fontSize: 16,
  fontWeight: 'normal',
  fontFamily: 'sans-serif',
  fontStyle: 'normal',
  gap: () => [100, 100],
  offset: () => [50, 50]
})
/**
 * Base size of the canvas, 1 for parallel layout and 2 for alternate layout
 * Only alternate layout is currently supported
 */
const BaseSize = 2
const FontGap = 3
// 和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
const containerRef = shallowRef() // ref() 的浅层作用形式
const watermarkRef = shallowRef()
const stopObservation = shallowRef(false)
const gapX = computed(() => props.gap?.[0] ?? 100)
const gapY = computed(() => props.gap?.[1] ?? 100)
const gapXCenter = computed(() => gapX.value / 2)
const gapYCenter = computed(() => gapY.value / 2)
const offsetLeft = computed(() => props.offset?.[0] ?? gapXCenter.value)
const offsetTop = computed(() => props.offset?.[1] ?? gapYCenter.value)
const markStyle = computed(() => {
  const markStyle: CSSProperties = {
    zIndex: props.zIndex ?? 9,
    position: 'absolute',
    left: 0,
    top: 0,
    width: '100%',
    height: '100%',
    pointerEvents: 'none',
    backgroundRepeat: 'repeat'
  }
  /** Calculate the style of the offset */
  let positionLeft = offsetLeft.value - gapXCenter.value
  let positionTop = offsetTop.value - gapYCenter.value
  if (positionLeft > 0) {
    markStyle.left = `${positionLeft}px`
    markStyle.width = `calc(100% - ${positionLeft}px)`
    positionLeft = 0
  }
  if (positionTop > 0) {
    markStyle.top = `${positionTop}px`
    markStyle.height = `calc(100% - ${positionTop}px)`
    positionTop = 0
  }
  markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`

  return markStyle
})
const destroyWatermark = () => {
  if (watermarkRef.value) {
    watermarkRef.value.remove()
    watermarkRef.value = undefined
  }
}
const appendWatermark = (base64Url: string, markWidth: number) => {
  if (containerRef.value && watermarkRef.value) {
    stopObservation.value = true
    watermarkRef.value.setAttribute(
      'style',
      getStyleStr({
        ...markStyle.value,
        backgroundImage: `url('${base64Url}')`,
        backgroundSize: `${(gapX.value + markWidth) * BaseSize}px`
      })
    )
    containerRef.value?.append(watermarkRef.value)
    // Delayed execution
    setTimeout(() => {
      stopObservation.value = false
    })
  }
}
// converting camel-cased strings to be lowercase and link it with Separato
function toLowercaseSeparator(key: string) {
  return key.replace(/([A-Z])/g, '-$1').toLowerCase()
}
function getStyleStr(style: CSSProperties): string {
  return Object.keys(style)
    .map((key: any) => `${toLowercaseSeparator(key)}: ${style[key]};`)
    .join(' ')
}
/*
  Get the width and height of the watermark. The default values are as follows
  Image: [120, 64]; Content: It's calculated by content
*/
const getMarkSize = (ctx: CanvasRenderingContext2D) => {
  let defaultWidth = 120
  let defaultHeight = 64
  const content = props.content
  const image = props.image
  const width = props.width
  const height = props.height
  const fontSize = props.fontSize
  const fontFamily = props.fontFamily
  if (!image && ctx.measureText) {
    ctx.font = `${Number(fontSize)}px ${fontFamily}`
    const contents = Array.isArray(content) ? content : [content]
    const widths = contents.map(item => ctx.measureText(item!).width)
    defaultWidth = Math.ceil(Math.max(...widths))
    defaultHeight = Number(fontSize) * contents.length + (contents.length - 1) * FontGap
  }
  return [width ?? defaultWidth, height ?? defaultHeight] as const
}
// Returns the ratio of the device's physical pixel resolution to the css pixel resolution
function getPixelRatio () {
  return window.devicePixelRatio || 1
}
const fillTexts = (
  ctx: CanvasRenderingContext2D,
  drawX: number,
  drawY: number,
  drawWidth: number,
  drawHeight: number,
) => {
  const ratio = getPixelRatio()
  const content = props.content
  const fontSize = props.fontSize
  const fontWeight = props.fontWeight
  const fontFamily = props.fontFamily
  const fontStyle = props.fontStyle
  const color = props.color
  const mergedFontSize = Number(fontSize) * ratio
  ctx.font = `${fontStyle} normal ${fontWeight} ${mergedFontSize}px/${drawHeight}px ${fontFamily}`
  ctx.fillStyle = color
  ctx.textAlign = 'center'
  ctx.textBaseline = 'top'
  ctx.translate(drawWidth / 2, 0)
  const contents = Array.isArray(content) ? content : [content]
  contents?.forEach((item, index) => {
    ctx.fillText(item ?? '', drawX, drawY + index * (mergedFontSize + FontGap * ratio))
  })
}
const renderWatermark = () => {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  const image = props.image
  const rotate = props.rotate ?? -22

  if (ctx) {
    if (!watermarkRef.value) {
      watermarkRef.value = document.createElement('div')
    }

    const ratio = getPixelRatio()
    const [markWidth, markHeight] = getMarkSize(ctx)
    const canvasWidth = (gapX.value + markWidth) * ratio
    const canvasHeight = (gapY.value + markHeight) * ratio
    canvas.setAttribute('width', `${canvasWidth * BaseSize}px`)
    canvas.setAttribute('height', `${canvasHeight * BaseSize}px`)

    const drawX = (gapX.value * ratio) / 2
    const drawY = (gapY.value * ratio) / 2
    const drawWidth = markWidth * ratio
    const drawHeight = markHeight * ratio
    const rotateX = (drawWidth + gapX.value * ratio) / 2
    const rotateY = (drawHeight + gapY.value * ratio) / 2
    /** Alternate drawing parameters */
    const alternateDrawX = drawX + canvasWidth
    const alternateDrawY = drawY + canvasHeight
    const alternateRotateX = rotateX + canvasWidth
    const alternateRotateY = rotateY + canvasHeight

    ctx.save()
    rotateWatermark(ctx, rotateX, rotateY, rotate)

    if (image) {
      const img = new Image()
      img.onload = () => {
        ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight)
        /** Draw interleaved pictures after rotation */
        ctx.restore()
        rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate)
        ctx.drawImage(img, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
        appendWatermark(canvas.toDataURL(), markWidth)
      }
      img.crossOrigin = 'anonymous'
      img.referrerPolicy = 'no-referrer'
      img.src = image
    } else {
      fillTexts(ctx, drawX, drawY, drawWidth, drawHeight)
      /** Fill the interleaved text after rotation */
      ctx.restore()
      rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate)
      fillTexts(ctx, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
      appendWatermark(canvas.toDataURL(), markWidth)
    }
  }
}
// Rotate with the watermark as the center point
function rotateWatermark(
  ctx: CanvasRenderingContext2D,
  rotateX: number,
  rotateY: number,
  rotate: number
) {
  ctx.translate(rotateX, rotateY)
  ctx.rotate((Math.PI / 180) * Number(rotate))
  ctx.translate(-rotateX, -rotateY)
}
onMounted(() => {
  renderWatermark()
})
watch(
  () => [props],
  () => {
    renderWatermark()
  },
  {
    deep: true, // 强制转成深层侦听器
    flush: 'post' // 在侦听器回调中访问被 Vue 更新之后的 DOM
  },
)
onBeforeUnmount(() => {
  destroyWatermark()
})
// Whether to re-render the watermark
const reRendering = (mutation: MutationRecord, watermarkElement?: HTMLElement) => {
  let flag = false
  // Whether to delete the watermark node
  if (mutation.removedNodes.length) {
    flag = Array.from(mutation.removedNodes).some(node => node === watermarkElement)
  }
  // Whether the watermark dom property value has been modified
  if (mutation.type === 'attributes' && mutation.target === watermarkElement) {
    flag = true
  }
  return flag
}
const onMutate = (mutations: MutationRecord[]) => {
  if (stopObservation.value) {
    return
  }
  mutations.forEach(mutation => {
    if (reRendering(mutation, watermarkRef.value)) {
      destroyWatermark()
      renderWatermark()
    }
  })
}
const defaultWindow = typeof window !== 'undefined' ? window : undefined
type Fn = () => void
function tryOnMounted(fn: Fn, sync = true) {
  if (getCurrentInstance()) onMounted(fn)
  else if (sync) fn()
  else nextTick(fn)
}
function useSupported(callback: () => unknown, sync = false) {
  const isSupported = shallowRef<boolean>()
  const update = () => (isSupported.value = Boolean(callback()))
  update()
  tryOnMounted(update, sync)
  return isSupported
}
function useMutationObserver(
  target: any,
  callback: MutationCallback,
  options: any,
) {
  const { window = defaultWindow, ...mutationOptions } = options
  let observer: MutationObserver | undefined
  const isSupported = useSupported(() => window && 'MutationObserver' in window)

  const cleanup = () => {
    if (observer) {
      observer.disconnect()
      observer = undefined
    }
  }

  const stopWatch = watch(
    () => unref(target),
    el => {
      cleanup()

      if (isSupported.value && window && el) {
        observer = new MutationObserver(callback)
        observer!.observe(el, mutationOptions)
      }
    },
    { immediate: true }
  )

  const stop = () => {
    cleanup()
    stopWatch()
  }

  tryOnScopeDispose(stop)

  return {
    isSupported,
    stop
  }
}
function tryOnScopeDispose(fn: Fn) {
  if (getCurrentScope()) {
    onScopeDispose(fn)
    return true
  }
  return false
}
useMutationObserver(containerRef, onMutate, {
  attributes: true // 观察所有监听的节点属性值的变化
})
</script>
<template>
  <div ref="containerRef" style="position: relative;">
    <slot></slot>
  </div>
</template>

在要使用的页面引入

<script setup lang="ts">
import Watermark from './Watermark.vue'
import { reactive } from 'vue'
const model = reactive({
  content: 'Vue Amazing UI',
  color: 'rgba(0,0,0,.15)',
  fontSize: 16,
  fontWeight: 400,
  zIndex: 9,
  rotate: -22,
  gap: [100, 100] as [number, number],
  offset: []
})
</script>
<template>
  <div>
    <h1>Watermark 水印</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Watermark content="Vue Amazing UI">
      <div style="height: 360px" />
    </Watermark>
    <h2 class="mt30 mb10">多行水印</h2>
    <h3 class="mb10">通过 content 设置 字符串数组 指定多行文字水印内容。</h3>
    <Watermark :content="['Vue Amazing UI', 'Hello World']">
      <div style="height: 400px" />
    </Watermark>
    <h2 class="mt30 mb10">图片水印</h2>
    <h3 class="mb10">通过 image 指定图片地址。为保证图片高清且不被拉伸,请设置 width 和 height, 并上传至少两倍的宽高的 logo 图片地址。</h3>
    <Watermark
      :height="30"
      :width="130"
      image="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*lkAoRbywo0oAAAAAAAAAAAAADrJ8AQ/original">
      <div style="height: 360px" />
    </Watermark>
    <h2 class="mt30 mb10">自定义配置</h2>
    <h3 class="mb10">通过自定义参数配置预览水印效果。</h3>
    <Flex>
      <Watermark v-bind="model">
        <p class="u-paragraph">
          The light-speed iteration of the digital world makes products more complex. However, human
          consciousness and attention resources are limited. Facing this design contradiction, the
          pursuit of natural interaction will be the consistent direction of Ant Design.
        </p>
        <p class="u-paragraph">
          Natural user cognition: According to cognitive psychology, about 80% of external
          information is obtained through visual channels. The most important visual elements in the
          interface design, including layout, colors, illustrations, icons, etc., should fully
          absorb the laws of nature, thereby reducing the user&apos;s cognitive cost and bringing
          authentic and smooth feelings. In some scenarios, opportunely adding other sensory
          channels such as hearing, touch can create a richer and more natural product experience.
        </p>
        <p class="u-paragraph">
          Natural user behavior: In the interaction with the system, the designer should fully
          understand the relationship between users, system roles, and task objectives, and also
          contextually organize system functions and services. At the same time, a series of methods
          such as behavior analysis, artificial intelligence and sensors could be applied to assist
          users to make effective decisions and reduce extra operations of users, to save
          users&apos; mental and physical resources and make human-computer interaction more
          natural.
        </p>
        <img
          style=" position: relative; z-index: 1; width: 100%; max-width: 800px;"
          src="https://cdn.jsdelivr.net/gh/themusecatcher/resources@0.0.3/6.jpg"
          alt="示例图片"
        />
      </Watermark>
      <Flex
        style="
          width: 25%;
          flex-shrink: 0;
          border-left: 1px solid #eee;
          padding-left: 20px;
          margin-left: 20px;
        "
        vertical
        gap="middle"
      >
        <p>Content</p>
        <Input v-model:value="model.content" />
        <p>Color</p>
        <Input v-model:value="model.color" />
        <p>FontSize</p>
        <Slider v-model:value="model.fontSize" :step="1" :min="0" :max="100" />
        <p>FontWeight</p>
        <InputNumber v-model:value="model.fontWeight" :step="100" :min="100" :max="1000" />
        <p>zIndex</p>
        <Slider v-model:value="model.zIndex" :step="1" :min="0" :max="100" />
        <p>Rotate</p>
        <Slider v-model:value="model.rotate" :step="1" :min="-180" :max="180" />
        <p>Gap</p>
        <Space style="display: flex" align="baseline">
          <InputNumber v-model:value="model.gap[0]" placeholder="gapX" />
          <InputNumber v-model:value="model.gap[1]" placeholder="gapY" />
        </Space>
        <p>Offset</p>
        <Space style="display: flex" align="baseline">
          <InputNumber v-model:value="model.offset[0]" placeholder="offsetLeft" />
          <InputNumber v-model:value="model.offset[1]" placeholder="offsetTop" />
        </Space>
      </Flex>
    </Flex>
  </div>
</template>
<style>
.u-paragraph {
  margin-bottom: 1em;
  color: rgba(0, 0, 0, .88);
  word-break: break-word;
  line-height: 1.5714285714285714;
}
</style>

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

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

相关文章

利用数据库的表,生成word文档的表结构注释说明

文章目录 1.场景说明2.解决办法3.生成文档3.1.实现思路3.2.引入Apache POI依赖3.3.获取表及表字段说明Mapper3.4.POI创建文档表格&#xff0c;并填充数据3.5.完整的接口下载代码3.6.效果展示 1.场景说明 在项目中表已经建立好了&#xff0c;但是现在想对外提供一个表的字段的描…

获得文件MD5——校验完整性 window 和 Linux下操作

目录 引出window下获得文件MD5Linux下获得文件MD5单个文件整个目录下所有文件检查MD5 总结 引出 1.Windows 10 自带了一个命令行程序 certutil可以 获取文件的 MD5 值&#xff1b; 2.Linux下md5sum命令获得文件MD5值&#xff1b; window下获得文件MD5 Windows 10 自带了一个命…

python中的简单线性拟合

简单线性回归可以拟合线性关系的数据&#xff0c;一般使用一次函数或二次函数即可。 import numpy as np import matplotlib.pyplot as pltxnp.array([1,2,3,4,5,6,7,8,9,10]) ynp.array([2.5,4.5,4.8,5.5,6.0,7.0,7.8,8.0,9.0,10.0])#一次拟合函数 slope,interceptnp.polyfit…

vs2019 - MFC对话框程序的工程名称不支持下划线命名法

文章目录 vs2019 - MFC对话框程序的工程名称不支持下划线命名法概述笔记备注END vs2019 - MFC对话框程序的工程名称不支持下划线命名法 概述 正在写账单分析程序, 用MFC 对话框. 因为比较习惯下划线命名法, 就在向导中给工程名称起了一个my_test这样的名称(下划线命名法, 小…

亮相史上规模最大高交会,Coremail展现邮件技术创新实力

11月19日&#xff0c;第二十五届中国国际高新技术成果交易会在深圳落下帷幕&#xff0c;作为国内邮件行业引领者&#xff0c;Coremail受邀参展。 展览现场&#xff0c;Coremail邮件解决方案及系列产品受到了众多参观者与业内人士的关注与好评。Coremail XT6邮件系统技术成熟&a…

iOS-打包上架构建版本一直不出现/正在处理/自动消失

iOS开发过程中&#xff0c;打包上架苹果审核是一个不可或缺的环节。说实话&#xff0c;这个问题我遇见两次了&#xff0c;为了让自己长点记性&#xff0c;决定写下来。首先&#xff0c;列举几种情况&#xff1a; 1.iPa包上传至App store后&#xff0c;一个小时内不显示构建版本…

使用Prometheus监控Synology(群辉)

1、简介 在现代的IT环境中&#xff0c;对于服务器和网络设备的监控是至关重要的。Synology&#xff08;群辉&#xff09;作为一种流行的网络存储解决方案&#xff0c;为用户提供了高性能和可靠的存储服务。然而&#xff0c;了解Synology设备的运行状况和性能指标对于确保其正常…

Nodejs+Vue校园餐厅外卖订餐点餐系统 PHP高校食堂 微信小程序_0u4hl 多商家

对于校园订餐小程序将是又一个传统管理到智能化信息管理的改革&#xff0c;对于传统的校园订餐管理&#xff0c;所包括的信息内容比较多&#xff0c;对于用户想要对这些数据进行管理维护需要花费很大的时间信息&#xff0c;而且对于数据的存储比较麻烦&#xff0c;想要查找某一…

为品质加冕 | 喜尔康智家再次斩获大奖

近日&#xff0c;被誉为“家居质量界奥斯卡”的2023年度沸腾质量奖颁奖盛典在福建厦门第三届家居质量大会同期隆重举行。现场重磅揭晓2023年沸腾质量奖测评获奖结果。 今年&#xff0c;喜尔康智能家居再接再厉&#xff0c;从数百家参评企业中脱颖而出&#xff0c;参评的智能坐便…

一起学docker系列之十三使用Dockerfile构建带有Java 8、Vim和Ifconfig功能的CentOS镜像

目录 1 前言2 构建流程3 Dockerfile说明4 解释Dockerfile5 构建Docker镜像6 运行Docker容器7 总结8 参考地址 1 前言 Docker是一个强大的工具&#xff0c;可以创建一致、可移植和隔离的环境。在本指南中&#xff0c;我们将介绍如何创建一个基于CentOS的Docker镜像&#xff0c;…

基于FPGA的五子棋游戏设计

基于FPGA的五子棋游戏设计 本文基于FPGA设计五子棋游戏&#xff0c;使用按键输入&#xff0c;使用VGA接口输出。五子棋的棋具与围棋相同&#xff0c;棋子分为黑白两色&#xff0c;棋盘为1010&#xff0c;棋子放置于棋盘线交叉点上。两人对局&#xff0c;各执一色&#xff0c;轮…

使用docker-compose优雅部署rocketMQ

使用docker-compose优雅部署RocketMQ 随着市场的发展&#xff0c;越来越多的复杂场景出现在我们日常的开发工作中。随之也越来越多的好的工具&#xff0c;也同步出现在程序员的学习范围清单内。好的工具提高产品性能的同时&#xff0c;也带来了很多安装上的问题&#xff0c;do…

31.0/LinkedList/Set/ashSet/ TreeSet/Map/ HashMap/ TreeMap

目录 31.1Linkedlist 31.2Set集合 31.3HashSet集合 31.4添加元素 31.5删除 31.6hashSet的遍历 31.7hashSet的源码 31.8TreeSet集合。 31.1Linkedlist 1.凡是查询源码 &#xff0c;我们都是从类的构造方法入手:/*** Constructs an empty list.*/public LinkedList() {}该…

figma 基础使用——准备阶段

1. 注册账号 2. figma有客户端也有网页端&#xff0c;使用注意同步字体 之后点击下载window installeer 字体 3. 安装 Figma汉化包 通过figma.cool 网站&#xff0c;下载离线的汉化包 之后通过谷歌的扩展程序添加

vulnhub靶机gigachad_vh

下载地址&#xff1a;Gigachad: 1 ~ VulnHub 主机发现 目标166 端口扫描 端口服务扫描 漏洞扫描 这玩意多得离谱 于是我用a重新扫了一遍 先去看web (⊙﹏⊙)离谱&#xff0c;目录扫描&#xff08;之前先去看一下nmap扫到的html&#xff09; 后面扫描的目录奇多&#xff0c;而…

C# 实现微信退款及对帐

目录 需求 基础准备 关键代码 操作界面 ​编辑 退款订单类及方法 退款功能实现 对帐 支付商家后台相关要点 实时交易帐单查询 精确交易帐单查询 小结 需求 在招聘报名系统里&#xff0c;考务费支付是其中一个环节&#xff0c;支付方式很多种&#xff0c;比如银联、…

救命,不会还有体制内姐妹不知道这个神器吧

体制内&#xff0c;每天各种写材料啊啊啊&#xff01;&#xff01;&#xff01;什么用词、结构、形式都要严谨&#xff0c; 有时候憋不出话来说&#xff0c;真的太难了&#xff0c;谁懂啊&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 这个好东西真的救我大…

软工2021上下午第六题(组合模式)

阅读下列说明和Java代码&#xff0c;将应填入&#xff08;n&#xff09;处的字句写在答题纸的对应栏内。 【说明】 层叠菜单是窗口风格的软件系统中经常采用的一种系统功能组织方式。层叠菜单中包含的可能是一个菜单项&#xff08;直接对应某个功能&#xff09;&#xff0c;也可…

大语言模型(LLMs)在 Amazon SageMaker 上的动手实践(一)

本期文章&#xff0c;我们将通过三个动手实验从浅到深地解读和演示大语言模型&#xff08;LLMs&#xff09;&#xff0c;如何结合 Amazon SageMaker 的模型部署、模型编译优化、模型分布式训练等。 实验一&#xff1a;使用 Amazon SageMaker 构建基于开源 GPT-J 模型的对话机器…

Unity3d 灯光阴影开启,法线贴图出现BUG

URP项目打开灯光的阴影后&#xff0c;法线贴图出现BUG 解决方案&#xff1a;按照下图所示调整材质的选项即可