Vue3图片(Image)

发布时间:2026/8/29 13:10:21

Vue3图片(Image)
本图片预览组件主要包括以下功能展示图片时可设置鼠标悬浮时的预览文本图像无法加载时要显示的描述自定义图像高度和宽度设置图像如何适应容器高度和宽度fill | contain | cover | none | scale-down,参考 object-fit传入单张图像或图像数组设置图像缩放比率设置最大最小缩放比例单张图片预览时左上角展示图片名称水平镜像或垂直镜像可顺时针旋转或逆时针旋转还原图片放大缩小鼠标任意拖动鼠标双击图片还原使用触摸板或鼠标滚轮控制图片缩放多张图片预览时除了单张展示的功能以外可点击左右切换按钮预览多张图片使用键盘上下左右按键进行图片切换设置是否可以循环切换图片可自定义设置以下属性图像地址或图像地址数组src类型string | Array{src: string, alt?: string}默认 undefined图像名称name没有传入图片名时自动从图像地址 src 中读取类型string默认 undefined图像宽度width类型string | number | (string | number)[]单位 px默认 100图像高度height类型string | number | (string | number)[]单位 px默认 100是否禁用图像预览disabled类型boolean默认 false是否显示边框bordered类型boolean默认 true图形如何适应容器高度和宽度fit类型contain | fill | cover | none | scale-down默认 contain参考 object-fit预览文本preview类型string | slot默认 预览自定义预览图片时 img 元素的样式previewImageStyle类型CSSProperties默认 {}Space 组件属性配置spaceProps用于配置多张展示图片时的排列方式类型object默认 {}Spin 组件属性配置spinProps用于配置图片加载中样式类型object默认 {}Spin 组件属性配置previewSpinProps用于配置预览图片加载中样式类型object默认 {}每次缩放比率zoomRatio类型number默认 0.1最小缩放比例minZoomScale类型number默认 0.1最大缩放比例maxZoomScale类型number默认 10缩放移动旋转图片后是否可以双击还原resetOnDbclick类型boolean默认 true是否可以拖动图片draggable类型boolean默认 false是否可以循环切换图片loop类型boolean默认 false相册模式即从一张展示图片点开相册album类型boolean默认 false图片下载配置downloadOptions透传给内置 downloadFile 工具函数的第三个参数 options默认 auto 策略同源 anchor / 跨域 iframe类型{ target?: _self | _blank, strategy?: auto | anchor | iframe }默认值 undefined自定义下载方法customDownload提供时优先于内置 downloadFile用于解决跨域图床下载受限等内置策略无法满足的场景类型(url: string, fileName?: string) void | Promisevoid默认 undefined效果如下图预览时样式正常展示时样式图片加载时样式鼠标悬浮时样式在线预览①创建图片预览组件Image.vue其中引入使用了以下组件和工具函数Vue3加载中SpinVue3间距Space加法 add下载文件 downloadFile获取依赖注入 useInjectscript setup langts import { computed, ref, watchEffect, nextTick } from vue import type { CSSProperties } from vue import Space from components/space import Spin from components/spin import { add, downloadFile, useInject } from components/utils export interface Image { src: string // 图像地址 name?: string // 图像名称 } export interface Props { src?: string | Image[] // 图像地址或图像地址数组 name?: string // 图像名称没有传入图片名时自动从图像地址 src 中读取 width?: string | number | (string | number)[] // 图像宽度单位 px height?: string | number | (string | number)[] // 图像高度单位 px disabled?: boolean // 是否禁用图像预览 bordered?: boolean // 是否显示边框 fit?: contain | fill | cover | none | scale-down // 图片在容器内的的适应类型 preview?: string // 预览文本 string | slot previewImageStyle?: CSSProperties // 自定义预览图片时 img 元素的样式 spaceProps?: object // Space 组件属性配置用于配置多张展示图片时的排列方式 spinProps?: object // Spin 组件属性配置用于配置图片加载中样式 previewSpinProps?: object // Spin 组件属性配置用于配置预览图片加载中样式 zoomRatio?: number // 每次缩放比率 minZoomScale?: number // 最小缩放比例 maxZoomScale?: number // 最大缩放比例 resetOnDbclick?: boolean // 缩放移动旋转图片后是否可以双击还原 draggable?: boolean // 是否可以拖动图片 loop?: boolean // 是否可以循环切换图片 album?: boolean // 是否相册模式即从一张展示图片点开相册 downloadOptions?: { target?: _self | _blank // 打开方式 strategy?: auto | anchor | iframe // 下载策略 } // 图片下载配置透传给内置 downloadFile 的第三个参数 options默认 auto 策略同源 anchor / 跨域 iframe customDownload?: (url: string, fileName?: string) void | Promisevoid // 自定义下载方法提供时优先于内置 downloadFile用于解决跨域图床下载受限等内置策略无法满足的场景 } const props withDefaults(definePropsProps(), { src: undefined, name: undefined, width: 100, height: 100, disabled: false, bordered: true, fit: contain, preview: 预览, previewImageStyle: () ({}), spaceProps: () ({}), spinProps: () ({}), previewSpinProps: () ({}), zoomRatio: 0.1, minZoomScale: 0.1, maxZoomScale: 10, resetOnDbclick: true, draggable: false, loop: false, album: false, downloadOptions: undefined, customDownload: undefined }) const images refImage[]([]) // 图片数组 const previewRef refHTMLElement | null(null) // 预览 DOM 引用 const previewIndex refnumber(0) // 当前预览的图片索引 const showPreview refboolean(false) // 是否显示预览 const imagesRef refHTMLImageElement[]([]) // 图片 DOM 引用 const imagesCompleted refboolean[]([]) // 图片是否加载完成 const previewImagesRef refHTMLImageElement[]([]) // 预览图片 DOM 引用 const previewCompleted refboolean[]([]) // 预览图片是否加载完成 const rotate refnumber(0) // 预览图片旋转角度 const scale refnumber(1) // 缩放比例 const swapX refnumber(1) // 水平镜像数值符号 const swapY refnumber(1) // 垂直镜像数值符号 const sourceX refnumber(0) // 拖动开始时位置 const sourceY refnumber(0) // 拖动开始时位置 const dragX refnumber(0) // 拖动横向距离 const dragY refnumber(0) // 拖动纵向距离 const sourceDragX refnumber(0) // 鼠标按下时图片的X轴偏移量 const sourceDragY refnumber(0) // 鼠标按下时图片的Y轴偏移量 const top refnumber(0) // 图片上边缘距浏览器窗口上边界的距离 const bottom refnumber(0) // 图片下边缘距浏览器窗口上边界的距离 const right refnumber(0) // 图片右边缘距浏览器窗口左边界的距离 const left refnumber(0) // 图片左边缘距浏览器窗口左边界的距离 const viewportWidth refnumber(0) // 视口宽度 const viewportHeight refnumber(0) // 视口高度 const { colorPalettes } useInject(Image) // 主题色注入 const imageAmount computed(() { return images.value.length }) const dragTransitionDuration computed(() { return props.draggable ? 100ms : 200ms }) watchEffect(() { images.value getImages() }) function getImages(): Image[] { if (Array.isArray(props.src)) { return props.src } else { return [ { src: props.src || , name: props.name } ] } } // 图片加载完成 function onImageLoaded(index: number): void { imagesCompleted.value[index] true } // 预览图片加载完成例如相册模式可在预览中切换到未加载完成的图片 function onPreviewLoaded(index: number): void { previewCompleted.value[index] true } // 从地址中提取路径末段作为原始文件名 function getRawNameFromUrl(src: string): string { // 以当前页面地址为 base 解析兼容相对路径解析失败时降级为手工切分 let pathname try { pathname new URL(src, location.href).pathname } catch { pathname src.split(?)[0].split(#)[0] } const segments pathname.split(/) return segments[segments.length - 1] || } // 从图像地址 src 中获取图像名称 // 优先使用显式传入的 name否则从 src 中提取 // 用 URL.pathname 天然剥离查询参数?与哈希#再取末段并做 URL 解码 function getImageName(image: Image): string | undefined { if (image) { if (image.name) { return image.name } const rawName getRawNameFromUrl(image.src) try { return decodeURIComponent(rawName) } catch { // 路径段含非法百分号编码时 decodeURIComponent 会抛异常此时原样返回 return rawName } } } function getImageSize(size: string | number | (string | number)[], index: number): string { if (Array.isArray(size)) { if (typeof size[index] number) { return ${size[index]}px } return size[index] } else { if (typeof size number) { return ${size}px } return size } } // 使用键盘切换图片 function onKeyboard(e: KeyboardEvent): void { if (showPreview.value imageAmount.value 1) { if (e.key ArrowLeft || e.key ArrowUp) { onSwitchLeft() } if (e.key ArrowRight || e.key ArrowDown) { onSwitchRight() } } } // 预览 async function onPreview(index: number): Promisevoid { scale.value 1 rotate.value 0 dragX.value 0 dragY.value 0 showPreview.value true previewIndex.value index await nextTick() previewRef.value?.focus() } defineExpose({ preview: onPreview }) // 关闭 function onClose(): void { showPreview.value false } // 下载 function onDownload(): void { const image images.value[previewIndex.value] // 提供自定义下载方法时优先使用绕开内置下载方法 if (props.customDownload) { props.customDownload(image.src, image.name) return } // 未自定义时走内置 downloadFile透传下载配置 downloadFile(image.src, image.name, props.downloadOptions) } // 放大 function onZoomin(): void { if (scale.value props.zoomRatio props.maxZoomScale) { scale.value props.maxZoomScale } else { scale.value add(scale.value, props.zoomRatio) } } // 缩小 function onZoomout(): void { if (scale.value - props.zoomRatio props.minZoomScale) { scale.value props.minZoomScale } else { scale.value add(scale.value, -props.zoomRatio) } } // 重置图片为初始状态 function onResetOrigin(): void { scale.value 1 swapX.value 1 swapY.value 1 rotate.value 0 dragX.value 0 dragY.value 0 } // 顺时针旋转 function onClockwiseRotate(): void { rotate.value 90 } // 逆时针旋转 function onAnticlockwiseRotate(): void { rotate.value - 90 } // 水平镜像 function onHorizontalMirror(): void { swapX.value * -1 } // 垂直镜像 function onVerticalMirror(): void { swapY.value * -1 } // 鼠标滚轮缩放 function onWheel(e: WheelEvent) { // e.preventDefault() // 禁止浏览器捕获滑动事件 const scrollZoom e.deltaY * props.zoomRatio * 0.1 // 滚轮的纵向滚动量 if (scale.value props.minZoomScale scrollZoom 0) { return } if (scale.value props.maxZoomScale scrollZoom 0) { return } if (scale.value - scrollZoom props.minZoomScale) { scale.value props.minZoomScale } else if (scale.value - scrollZoom props.maxZoomScale) { scale.value props.maxZoomScale } else { scale.value add(scale.value, -scrollZoom) } } function handlePointerDown(e: PointerEvent) { // event.preventDefault() // 消除拖动元素时的阴影 if (!e.target) return const el e.target // 当前点击的元素 const imageRect (el as Element).getBoundingClientRect() top.value imageRect.top // 图片上边缘距浏览器窗口上边界的距离 bottom.value imageRect.bottom // 图片下边缘距浏览器窗口上边界的距离 right.value imageRect.right // 图片右边缘距浏览器窗口左边界的距离 left.value imageRect.left // 图片左边缘距浏览器窗口左边界的距离 viewportWidth.value window.innerWidth // 视口宽度 viewportHeight.value window.innerHeight // 视口高度 sourceX.value e.clientX // 指针按下时相对于视口左边缘的X坐标 sourceY.value e.clientY // 指针按下时相对于视口上边缘的Y坐标 sourceDragX.value dragX.value // 指针按下时图片的X轴偏移量 sourceDragY.value dragY.value // 指针按下时图片的Y轴偏移量 // 捕获指针确保即使移出元素也能继续跟踪 ;(el as HTMLElement).setPointerCapture(e.pointerId) document.addEventListener(pointermove, handlePointerMove) document.addEventListener(pointerup, handlePointerUp) document.addEventListener(pointercancel, handlePointerUp) handlePointerMove(e) } function handlePointerMove(e: PointerEvent) { // e.clientX 返回事件被触发时指针相对于浏览器可视窗口的水平坐标 dragX.value sourceDragX.value e.clientX - sourceX.value dragY.value sourceDragY.value e.clientY - sourceY.value } function handlePointerUp() { if (props.draggable) { if (dragX.value sourceDragX.value viewportWidth.value - right.value) { // 溢出视口右边缘 dragX.value sourceDragX.value viewportWidth.value - right.value } if (dragX.value sourceDragX.value - left.value) { // 溢出视口左边缘 dragX.value sourceDragX.value - left.value } if (dragY.value sourceDragY.value viewportHeight.value - bottom.value) { // 溢出视口下边缘 dragY.value sourceDragY.value viewportHeight.value - bottom.value } if (dragY.value sourceDragY.value - top.value) { // 溢出视口上边缘 dragY.value sourceDragY.value - top.value } } else { dragX.value 0 dragY.value 0 } document.removeEventListener(pointermove, handlePointerMove) document.removeEventListener(pointerup, handlePointerUp) document.removeEventListener(pointercancel, handlePointerUp) } // 切换到上一张 function onSwitchLeft(): void { if (props.loop) { previewIndex.value (previewIndex.value - 1 imageAmount.value) % imageAmount.value } else { if (previewIndex.value 0) { previewIndex.value-- } } onResetOrigin() } // 切换到下一张 function onSwitchRight(): void { if (props.loop) { previewIndex.value (previewIndex.value 1) % imageAmount.value } else { if (previewIndex.value imageAmount.value - 1) { previewIndex.value } } onResetOrigin() } /script template div classimage-wrap :style--image-primary-color: ${colorPalettes[5]}; Space gapsmall v-bindspaceProps div v-for(image, index) in images :keyindex v-show!album || (album index 0) classimage-container :class{ image-bordered: bordered, image-hover-mask: !disabled (imagesCompleted[index] || imagesRef[index]?.naturalWidth) } :stylewidth: ${getImageSize(props.width, index)}; height: ${getImageSize(props.height, index)}; Spin :spinning!(imagesCompleted[index] || imagesRef[index]?.naturalWidth) colorvar(--image-primary-color) indicatordynamic-circle sizesmall v-bindspinProps img refimagesRef classimage-item :styleobject-fit: ${fit}; loadonImageLoaded(index) :srcimage.src :altgetImageName(image) / /Spin div v-if!disabled classimage-mask clickonPreview(index) div classimage-mask-info svg classeye-svg focusablefalse >script setup langts import Image from ./Image.vue import { ref } from vue import type { ImageItem } from vue-amazing-ui const images refImageItem[]([ { src: https://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/1.jpg, name: image-1.jpg }, { src: https://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/2.jpg, name: image-2.jpg }, { src: https://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/3.jpg, name: image-3.jpg }, { src: https://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/4.jpg, name: image-4.jpg }, { src: https://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/5.jpg, name: image-5.jpg } ]) /** * 自定义下载示例用 XHR Blob 下载跨域图床图片 * 内置 downloadFile 的 iframe 策略受图床 X-Frame-Options 限制改用 XHR 取二进制后通过 Blob 触发下载不受该限制 */ function customDownload(url: string, fileName?: string) { // 以二进制流方式请求图片需要图床开启 CORS 才能跨域读取 const xhr new XMLHttpRequest() xhr.open(GET, url, true) xhr.responseType blob xhr.onload () { if (xhr.status 200) { // 将二进制流转为本地 Blob URL再通过 a 标签的 download 属性强制下载blob 同源download 始终生效 const blobUrl URL.createObjectURL(xhr.response) const a document.createElement(a) a.href blobUrl a.download fileName || url.split(?)[0].split(/).pop() || download document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(blobUrl) // 释放 Blob URL避免内存泄漏 } } xhr.send() } /script template div h1{{ $route.name }} {{ $route.meta.title }}/h1 Alert classmt10 mb10 typewarning message关于示例图片的下载功能 show-icon template #description p 示例图片虽以 codecdn.jsdelivr.net/code 为入口但下载时会 301 重定向到 coderaw.githubusercontent.com/code导致默认的「下载」按钮失效原因如下/p p classmb10 mt10 内置 codedownloadFile/code 对跨域地址走 codeiframe/code 策略而 coderaw.githubusercontent.com/code 设置了 codeX-Frame-Options: deny/code浏览器拒绝在 iframe 内加载控制台报 codeRefused to display ... in a frame/code即便不重定向codecdn.jsdelivr.net/code 作为普通 CDN 也不支持 iframe 策略依赖的 coderesponse-content-disposition/code 参数。/p p classmt10 下方的「自定义下载」小节演示了可用的 codeXHR Blob/code 方案以二进制流读取图片需图床开启 CORScode raw.githubusercontent.com/code 返回 codeAccess-Control-Allow-Origin: */code后转 Blob URL再用 codea/code 标签 codedownload/code 属性强制下载不受上述限制。/p /template /Alert h2 classmt30 mb10基本使用/h2 Image srchttps://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/1.jpg / h2 classmt30 mb10多张图片预览/h2 p classmb10 可循环切换图片并支持键盘 (codeleft/code / coderight/code / codeup/code / codedown/code) 按键切换/p Image :srcimages loop / h2 classmt30 mb10禁用预览/h2 Image srchttps://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/1.jpg disabled / h2 classmt30 mb10自定义样式/h2 p classmb10自定义宽高同时图片覆盖容器预览文本设为 codepreview/code/p Image :srcimages :width[100, 200, 100, 200, 100] :space-props{ width: 416 } fitcover loop template #preview p classpreview-txtpreview/p /template /Image h2 classmt30 mb10自定义预览图片样式/h2 Image :srcimages :preview-image-style{ background: #fff, padding: 12px, borderRadius: 8px } loop / h2 classmt30 mb10自定义排列方式 加载中样式/h2 Image :srcimages :space-props{ width: 332, gap: 16 } :spin-props{ tip: loading, indicator: spin-line, color: #fadb14 } loop / h2 classmt30 mb10无边框/h2 Image :srcimages :borderedfalse fitcover loop / h2 classmt30 mb10相册模式/h2 Image :srcimages album loop / h2 classmt30 mb10自定义预览配置/h2 p classmb10更改缩放比率和最大最小缩放比例/p Image :zoom-ratio0.2 :min-zoom-scale0.5 :max-zoom-scale2 srchttps://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/1.jpg / h2 classmt30 mb10下载配置/h2 p classmb10 通过 codedownloadOptions/code 配置内置下载策略codeanchor/code / codeiframe/code与打开方式 code _self/code / code_blank/code。 /p p classmb10 下方示例强制 codeanchor/code 策略并设 codetarget_blank/code跨域下 codedownload/code 属性失效点击后在新窗口打开图片。 /p Image album :srcimages :download-options{ strategy: anchor, target: _blank } loop / h2 classmt30 mb10自定义下载/h2 p classmb10 跨域图床如设置 codeX-Frame-Options/code 拒绝 codeiframe/code 的 CDN下载受限时内置 codedownloadFile/code 可能无法满足可通过 codedownloadOptions/code 调整内置下载策略或通过 codecustomDownload/code 提供完全自定义的下载方法。 /p p classmb10 下方示例演示了用 XHR Blob 绕过codeX-Frame-Options/code 限制下载跨域图床图片 /p Image album :srcimages :custom-downloadcustomDownload loop / /div /template style langless scoped .preview-txt { display: inline-block; font-size: 16px; } /style

相关新闻

Vue3空状态(Empty)

Vue3空状态(Empty)

2026/8/29 13:10:21

可自定义设置以下属性: 自定义描述内容(description),类型:string | slot | null,默认 暂无数据 设置描述文本的样式(descriptionStyle),类型:CSSPropertie…

初识 Free RTOS

初识 Free RTOS

2026/8/29 13:00:18

一、FreeRTOS简介Free RTOS(Real Time Operating System,实时操作系统),顾名思义,一款免费的实时操作系统。作为一种专门设计用于处理实时任务的操作系统。与通用计算机上运行的桌面操作系统(如Windows、Li…

PaddleOCR PP-StructureV3 文档解析实战:5 分钟把复杂 PDF 变成一份 Markdown

PaddleOCR PP-StructureV3 文档解析实战:5 分钟把复杂 PDF 变成一份 Markdown

2026/8/29 13:00:18

PaddleOCR PP-StructureV3 文档解析实战:5 分钟把复杂 PDF 变成一份 Markdown 【免费下载链接】PaddleOCR Turn any PDF or image document into structured data for your AI. A powerful, lightweight OCR toolkit that bridges the gap between images/PDFs and …

AI Coding全景指南:从工具选择到工程化落地实践

AI Coding全景指南:从工具选择到工程化落地实践

2026/8/29 14:10:23

这两年“AI Coding”几乎是从工具圈一路火到了管理层。打开技术社区,满屏都是“AI 辅助编程”“AI Agent 写代码”的话题;打开招聘 JD,不少岗位也把“熟悉 AI 编程工具”写成了加分项。但真正在一线把 AI 用到生产项目里的团队,往…

从“光”到“色”:一文拆解TFT液晶屏如何用晶体管“画”出千万色彩

从“光”到“色”:一文拆解TFT液晶屏如何用晶体管“画”出千万色彩

2026/8/29 14:10:23

1. 从白光到彩色:TFT液晶屏的成像之旅 当你盯着手机屏幕刷短视频时,是否想过这千万种色彩是如何被"画"出来的?这背后是一场光与电的精密共舞。TFT液晶屏的成像过程就像个高效协作的工厂流水线: 背光模组 是发电站&…

基于51单片机的门禁系统设计:从矩阵键盘到EEPROM存储的综合实践

基于51单片机的门禁系统设计:从矩阵键盘到EEPROM存储的综合实践

2026/8/29 14:10:23

1. 项目背景与赛题核心解读 最近在整理一些老项目的资料,翻到了当年参加蓝桥杯第三届单片机国赛的题目——“门禁系统”。这个题目可以说是一个经典的综合应用案例,它不像一些单纯考某个外设驱动的题目,而是把单片机开发中常见的几个核心模块…

Ubuntu 安装docker docker-compose

Ubuntu 安装docker docker-compose

2026/8/29 14:10:23

Docker 通过提供轻量级、可移植且高效的解决方案,简化了软件开发和部署。“docker build”命令是 Docker 镜像创建过程的核心。本文将探讨 Docker 构建命令、用法以及 Docker 构建的优化。Docker 构建有什么作用?Docker build 是一个命令行界面 CLI命令&…

自主行动AI Agent实战:从零搭建自动操作网页的智能体

自主行动AI Agent实战:从零搭建自动操作网页的智能体

2026/8/29 14:10:23

这次我们来看一个 Agent 自主行动的真实案例:为了让用户抢到热门健身课的一个名额,一个 AI Agent 自己完成了一整套网页操作,最终拿到了结果。新闻标题用了 “Rogue AI agent hacks gym” 来形容它,其实多数时候这不是在攻击系统漏…

AI 编码新范式:SHA 绑定 spec 审批,从 issue 到 reviewed PR

AI 编码新范式:SHA 绑定 spec 审批,从 issue 到 reviewed PR

2026/8/29 14:00:23

AgentMachinist 这个项目在 Show HN 上展示的核心信息很短:issue to reviewed PR。从一个 issue 出发,最后得到一个经过审查的 pull request。很多 AI 编程工具现在都能做到“从 issue 生成 PR”,但 reviewed 这个限定词,让它的思…

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

2026/8/27 11:10:02

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

2026/8/29 10:22:10

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

2026/8/28 7:34:42

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

四款热门降AI工具测评:研究生和本科生怎么选?

四款热门降AI工具测评:研究生和本科生怎么选?

2026/8/29 0:09:39

马上要交论文了,最近真的被论文ai率折磨的够呛。 明明查重都没问题了,但是ai率就是居高不下,崩溃了,明明都是我自己写的,天杀的,明明都是我亲生的啊 改来改去,终于给我搞出一套完美的降ai方案…

论文降AI率免费攻略:自查、提示词与工具推荐

论文降AI率免费攻略:自查、提示词与工具推荐

2026/8/29 0:09:39

马上要交论文了,最近真的被论文ai率折磨的够呛。 明明查重都没问题了,但是ai率就是居高不下,崩溃了,明明都是我自己写的,天杀的,明明都是我亲生的啊 改来改去,终于给我搞出一套完美的降ai方案…

北京GEO优化服务商推荐:预算型企业如何选北京GEO优化服务商?

北京GEO优化服务商推荐:预算型企业如何选北京GEO优化服务商?

2026/8/29 0:09:39

前言:预算有限的企业更关心投入能否形成可持续的品牌资产。评估北京GEO优化服务商时,不能只比较单篇内容或单月报价,还要看是否能够把问题词、官网、信源和监测串成完整链路。本期重点放在预算配置、试点范围和交付边界,帮助企业先…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/28 7:35:26

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/28 7:34:51

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/28 7:34:35

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…