vue 图片上传到腾讯云对象存储组件封装(完善版)

vue 上传图片到腾讯云对象存储

  • 1、 引入cos-js-sdk-v5
  • 2、封装`uploadcos.js`
  • 3、封装图片上传组件、调用上传方法
  • 4、页面使用组件

之前总结过 vue 封装图片上传组件到腾讯云对象存储,后来又加了一些功能,在图片过大时进行压缩,压缩完成之后,再上传到腾讯云对象存储;并且,对上传方法进行了优化,所以重新记录一下。

1、 引入cos-js-sdk-v5

安装 JavaScript SDK

npm install cos-js-sdk-v5

安装成功后会有如下信息:
在这里插入图片描述

2、封装uploadcos.js

新建文件uploadcos.js,封装上传文件方法。

/*** 本文件为腾讯云对象存储相关工具类方法注意:桶的访问权限需要设置指定域名(不然会出现跨域问题),现在设置允许访问的域名是:http://localhost:8080 https://xxx.com.cn/所以本地调试时,需要用http://localhost:8080,不可用其他端口。跨域配置:桶:指定域名 + 指定子账号能上传;外部不能访问,统一通过cdn访问;CDN:设置为无跨域限制---- COD自主诊断工具:https://cloud.tencent.com/login?s_url=https%3A%2F%2Fconsole.cloud.tencent.com%2Fcos5%2Fdiagnose -----*/// https://cloud.tencent.com/document/product/436/11459
import COS from 'cos-js-sdk-v5'
import { Message } from 'element-ui'
import { getCOSSecretKey } from '@/api/index'// 存储桶所在地域
const BUCKET_REGION = 'ap-beijing'
// 使用分片上传阈值10(M)
const SLICE_SIZE = 10const BUCKET_TYPE_CONFIG = {video: 'video-b-123456',image: 'image-b-123456',file: 'file-b-123456'
}const BUCKET_DOMAIN = {video: 'https://abcd-video.xxx.com.cn',image: 'https://abcd-image.xxx.com.cn'
}const FOLDER_PATH_NAME = {// 内容图片ART: {prod: '/art/',test: '/test/art/'},// 日常活动图片ACT: {prod: '/act/',test: '/test/act/'},// 产品图片WARE: {prod: '/ware/',test: '/test/ware/'},// 广告&宣传图片ADV: {prod: '/adv/',test: '/test/adv/'}
}/*** options @param {Object}* sliceSize:使用切片上传阈值 默认10(M)* bucketType:桶类型 video,image,file 三种类型* busiType:业务类型* needLaoding:是否需要loading遮罩层* bucketEnv:桶的环境 测试、生产* bucketName:桶的名称* bucketDomain:桶的域名 用来拼接key* bucketPrefix:自定义桶地址前缀片段* credentials:后台返回凭证信息* keyBackData:后台返回密钥信息 为credentials父级*/
class Cos {constructor(options) {this.bucketEnv =window.msBaseUrl === 'https://xxx.com.cn/' ? 'prod' : 'test'this.bucketType = options?.bucketType || BUCKET_TYPE_CONFIG.filethis.bucketName = BUCKET_TYPE_CONFIG[this.bucketType]this.bucketDomain = BUCKET_DOMAIN[this.bucketType]this.sliceSize = options?.sliceSize || SLICE_SIZEthis.busiType = options?.busiType || 'ART'this.bucketPrefix = FOLDER_PATH_NAME[this.busiType][this.bucketEnv]this.credentials = nullthis.keyBackData = null}/*** 获取密钥* @returns Object*/async getKey () {try {const res = await getCOSSecretKey({bucket: this.bucketName})if (res?.result?.credentials &&Object.keys(res?.result?.credentials).length) {this.keyBackData = res.resultthis.credentials = res.result.credentialsreturn this.credentials}return null} catch (error) {return null}}/*** 生成上传资源名称 6位随机数+uid+文件名*/generateKey (file) {const timeStamp = file.uid ? file.uid : new Date().getTime() + ''const suffix = file.name.split('.')[file.name.split('.').length - 1]return `${this.randomString()}_${timeStamp}.${suffix}`}/*** 获取随机数* @param {*} len* @returns*/randomString (len = 6) {const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789'const maxPos = chars.lengthlet res = ''for (let i = 0; i < len; i++) {res += chars.charAt(Math.floor(Math.random() * maxPos))}return res}/*** 创建COS对象实例* @returns Object*/async getCosInstance () {const getKey = await this.getKey()if (getKey) {const { tmpSecretId, tmpSecretKey, sessionToken } = this.credentialsconst { startTime, expiredTime } = this.keyBackDataconst params = {TmpSecretId: tmpSecretId,TmpSecretKey: tmpSecretKey,SecurityToken: sessionToken,StartTime: startTime,ExpiredTime: expiredTime}const _cos = new COS({getAuthorization: function (options, callback) {callback(params)}})return _cos}return null}/*** 单个文件上传到腾讯云cos* @param {*} file* @returns*/async uploadHandle (file) {const cos = await this.getCosInstance()if (cos) {const KEY = `${this.bucketPrefix}${this.generateKey(file)}`console.log('KEY', KEY)return new Promise((resolve, reject) => {// if (this.needLoading) {//   var loadingInstance = Loading.service({ fullscreen: true })// }cos.uploadFile({Bucket: this.bucketName /* 填入您自己的存储桶,必须字段 */,Region: BUCKET_REGION /* 存储桶所在地域,例如ap-beijing,必须字段 */,Key: KEY /* 存储在桶里的对象键(例如1.jpg,a/b/test.txt),必须字段 */,Body: file /* 必须,上传文件对象,可以是input[type="file"]标签选择本地文件后得到的file对象 */,SliceSize:1024 *1024 *this.sliceSize /* 触发分块上传的阈值,超过5MB使用分块上传,非必须 */,onTaskReady: function (taskId) {/* 非必须 */// console.log(taskId)},onProgress: function (progressData) {/* 非必须 */// console.log(JSON.stringify(progressData))const percent = parseInt(progressData.percent * 10000) / 100const speed =parseInt((progressData.speed / 1024 / 1024) * 100) / 100console.log('进度:' + percent + '%; 速度:' + speed + 'Mb/s;')},onFileFinish: function (err, data, options) {/* 非必须 */console.log(options.Key + '上传' + (err ? '失败' : '完成'))}},(err, data) => {// loadingInstance && loadingInstance.close()if (err) {Message.error(err)reject(err)}const url = `${this.bucketDomain}${KEY}`if (this.bucketType === 'video') {const fileName = file.name || ''const name = fileName.split('.').slice(0, fileName.split('.').length - 1).join('.') // 获取文件名称resolve({ url, name })} else {resolve(url)}})})}}/*** 媒体信息接口*/async getMediaInfoHandle (key) {const cos = await this.getCosInstance()if (cos) {return new Promise((resolve, reject) => {cos.request({Bucket: this.bucketName /* 填入您自己的存储桶,必须字段 */,Region: BUCKET_REGION /* 存储桶所在地域,例如ap-beijing,必须字段 */,Method: 'GET',Key: key /* 存储桶内的媒体文件,必须字段 */,Query: {'ci-process': 'videoinfo' /** 固定值,必须 */}},function (err, data) {if (err) {Message.error(err)reject(err)}resolve(data)})})}}
}export default Cos

3、封装图片上传组件、调用上传方法

新建image-upload.vue封装图片上传组件,调用上传方法:

<template><div class="common-image-upload-cos-container"><div class="image-upload-cos-content" :class="{'limit-num': fileList.length>=limit, 'mini': size === 'small'}"><el-upload ref="upload" :file-list="fileList" list-type="picture-card" action="#" :http-request="uploadImageHandle" v-loading="uploadLoading" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :on-exceed="exceedTips" :on-success="handeSuccess" :before-upload="beforeAvatarUpload" :on-change="onChangeHandle"><i class="el-icon-plus"></i><p class="el-upload__tip" slot="tip" v-if="tips">{{tips}}</p><div slot="file" slot-scope="{file}" class="img-con"><img crossorigin class="el-upload-list__item-thumbnail" :src="file.url" alt=""><span class="el-upload-list__item-actions"><span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)"><i class="el-icon-zoom-in"></i></span><span class="el-upload-list__item-delete" @click="handleRemove(file)" v-if="!disabled"><i class="el-icon-delete"></i></span><span v-if="size === 'small' && !disabled" style="display:block;marginLeft:0px" @click="onChangeHandle(file)"><i class="el-icon-edit"></i></span><span v-if="size !== 'small' && !disabled" @click="onChangeHandle(file)"><i class="el-icon-edit"></i></span></span></div></el-upload></div><div class="img-preview-dialo"><el-dialog :visible.sync="dialogVisibleShow" :append-to-body="append_to_body" :modal-append-to-body="modal_append_to_body"><img style="width:100%" :src="dialogImageUrl" crossorigin alt></el-dialog></div></div>
</template><script>
import Cos from '@/utils/uploadcos'
import ImageCompressor from '@/assets/js/image-compressor.min'
export default {// 上传图片到腾讯云对象存储name: 'ImageUpload',componentName: 'ImageUpload',data () {return {uploadLoading: false,imgWidth: 0,imgHeight: 0,picIndex: -1,dialogImageUrl: '',dialogVisibleShow: false,fileList: [],vmodelType: '',cos: new Cos({busiType: this.busiType,bucketType: this.bucketType})}},props: {// 接收 String, Array类型,默认为 String 类型value: {type: [String, Array],default: ''},tips: {type: String,default: ''},size: {type: String,default: 'medium' // small},limit: {// 限制上传图片张数type: Number,default: 1},limitSize: {// 限制上传图片大小type: Number,default: 10},valueType: {type: String,default: 'String' // Object},bucketType: {type: String,default: 'image'},// 是否校验图片尺寸,默认不校验isCheckPicSize: {type: Boolean,default: false},checkWidth: {type: Number,default: 0 // 图片限制宽度},checkHeight: {type: Number,default: 0 // 图片限制高度},topLimitWidth: {type: Number,default: 0 // 图片限制宽度上限(有时需要校验上传图片宽度在一个范围内)},topLimitHeight: {type: Number,default: 0 // 图片限制高度上限(有时需要校验上传图片高度在一个范围内)},index: {type: Number,default: -1 // 当前图片index,限制可以上传多张时,针对某一张进行操作,需要知道当前的index},limitType: {type: String,default: '' // (限制上传图片格式)传入格式:png,jpg,gif  png,jpg,webp  png,jpg,gif,webp},busiType: {type: String,default: 'ART'},// 禁用开关disabled: {type: Boolean,default: false},isGzip: {// 是否压缩图片,默认不压缩(false);传入 true 时,图片大小大于80KB且不是gif格式时进行压缩type: Boolean,default: false},append_to_body: {type: Boolean,default: false},modal_append_to_body: {type: Boolean,default: true}},components: {},created () {if (this.valueType === 'Object') {this.vmodelType = 'array'}if (this.value) {this.modifyValue()}},watch: {value: {deep: true,handler: function (val, oldVal) {if (val) {this.modifyValue()} else {this.fileList = []}}}},methods: {findItem (uid) {this.fileList.forEach((ele, i) => {if (uid === ele.uid) {this.picIndex = i}})},onChangeHandle (file, fileList) {// console.log('onChangeHandle file, fileList', file, fileList)this.findItem(file.uid)this.$refs.upload.$refs['upload-inner'].handleClick()},handleRemove (file) {// console.log('handleRemove file', file)this.findItem(file.uid)this.fileList.splice(this.picIndex, 1)this.exportImg()},exportImg () {if (this.fileList.length !== 0) {if (this.imgWidth && this.imgHeight) {if (this.valueType === 'Object') {const imgs = this.fileList.map(item => {return {url: item.url,name: item.fileName ? item.fileName : item.name}})this.$emit('input', imgs)this.$emit('imgChange', this.index)} else {if (this.vmodelType === 'array') {const imgs = this.fileList.map(item => {if (item.url) {return item.url}})this.$emit('input', imgs)this.$emit('imgChange', this.index)} else {const resUrl = this.fileList[0].urlthis.$emit('input', resUrl)this.$emit('imgChange', this.index)}}} else {this.$message.error('当前未获取到图片宽高数据,请重新上传图片!')}} else {this.$emit('input', '')this.$emit('imgChange', this.index)}this.picIndex = -1},uploadImageHandle (file) {if (this.vmodelType === 'string') {if (this.picIndex !== -1 && this.fileList.length) {// 如果是单图编辑替换状态,手动删除被替换的文件this.fileList.splice(this.picIndex, 1)}}// console.log('uploadImageHandle file', file)// return// 不需要压缩、图片小于80KB或是gif图,不压缩直接上传;大于80KB,压缩图片后上传const uploadFile = file.fileif ((!this.isGzip) || (uploadFile.size / 1024 < 80) || (uploadFile.type == 'image/gif')) {this.uploadToCos(uploadFile)} else {let file = uploadFileif (!file) {return}var options = {file: file,quality: 0.6,mimeType: 'image/jpeg',maxWidth: 6000,maxHeight: 6000,// width: 1000, // 指定压缩图片宽度// height: 1000, // 指定压缩图片高度minWidth: 10,minHeight: 10,convertSize: Infinity,loose: true,redressOrientation: true,// 压缩前回调beforeCompress: (result) => {console.log('压缩之前图片尺寸大小: ', result.size / 1024)// console.log('mime 类型: ', result.type);},// 压缩成功回调success: (result) => {console.log(result);console.log('压缩之后图片尺寸大小: ', result.size / 1024)console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');this.uploadToCos(result)},// 发生错误error: (msg) => {console.error(msg);this.$message.error(msg)}};/* eslint-disable no-new */new ImageCompressor(options)}},// 上传文件uploadToCos (file) {// console.log('uploadToCos uploadFile', file)// returnthis.uploadLoading = truethis.cos.uploadHandle(file).then((url) => {if (url) {if (this.imgWidth && this.imgHeight) {const resUrl = url + '?width=' + this.imgWidth + '&height=' + this.imgHeightconst obj = { url: resUrl, name: file.name }if (this.picIndex < 0) {this.fileList.push(obj)} else {this.fileList[this.picIndex] = obj}this.exportImg()} else {this.$message.error('当前未获取到图片宽高数据,请重新上传图片!')}} else {this.fileList.splice(this.picIndex, 1)}this.uploadLoading = false})},modifyValue () {if (this.valueType === 'Object') {this.fileList = this.value.map(item => ({url: item.url,name: item.name}))} else {// 判断是否是Stringconst str = this.valueconst res = ((str instanceof String) || (typeof str).toLowerCase() === 'string')if (res === true) {this.vmodelType = 'string'} else {this.vmodelType = 'array'}if (this.vmodelType === 'array') {this.fileList = this.value.map(item => ({ url: item }))} else {this.fileList = [{ url: this.value }]}}},beforeAvatarUpload (file) {const imgType = file.typeconst isLtSize = file.size / 1024 / 1024 < this.limitSizeconst TYPE_ALL = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp']let isType = true// console.log('this.limitType', this.limitType)// console.log('imgType', imgType)if (this.limitType) {const limitTypeArr = this.limitType.split(',')const limutTypeFlagArr = []const IMG_STATUS = {jpg: 'image/jpeg',jpeg: 'image/jpeg',png: 'image/png',gif: 'image/gif',webp: 'image/webp'}limitTypeArr.forEach(item => {if (IMG_STATUS[item]) limutTypeFlagArr.push(IMG_STATUS[item])})if (limutTypeFlagArr.indexOf(imgType) === -1) {isType = falsethis.$message.error(`仅支持上传 ${this.limitType} 格式的图片!`)}} else {// 默认情况,未传入校验类型格式,则默认可以接受全部格式if (TYPE_ALL.indexOf(imgType) === -1) {isType = falsethis.$message.error('仅支持上传 jpg、png、jpeg、webp、gif 格式的图片!')}}if (!isLtSize) {this.$message.error(`上传图片大小不能超过${this.limitSize}MB!`)}if (this.isCheckPicSize === true) {const width = this.checkWidthconst height = this.checkHeightconst topWidth = this.topLimitWidthconst topHeight = this.topLimitHeightconst that = thisconst isSize = new Promise((resolve, reject) => {// console.log('Promise')// window对象,将blob或file读取成一个urlconst _URL = window.URL || window.webkitURLconst img = new Image()img.onload = () => { // image对象的onload事件,当图片加载完成后执行的函数// console.log('img.onload')that.imgWidth = img.widththat.imgHeight = img.heightif (width && height) { // 校验图片的宽度和高度let valid = falseif (topWidth && topHeight) {// 校验图片宽度和高度范围valid = ((width <= img.width) && (img.width <= topWidth)) && ((height <= img.height) && (img.height <= topHeight))} else if (topHeight) {// 校验图片高度范围valid = img.width === width && ((height <= img.height) && (img.height <= topHeight))} else if (topWidth) {// 校验图片宽度范围valid = ((width <= img.width) && (img.width <= topWidth)) && img.height === height} else {// 校验图片宽度、高度固定值valid = img.width === width && height === img.height}valid ? resolve() : reject(new Error('error'))} else if (width) { // 只校验图片的宽度let valid = falseif (topWidth) {// 校验图片宽度范围valid = (width <= img.width) && (img.width <= topWidth)} else {// 校验图片宽度固定值valid = img.width === width}valid ? resolve() : reject(new Error('error'))} if (height) { // 只校验图片的高度let valid = falseif (topHeight) {// 校验图片高度范围valid = (height <= img.height) && (img.height <= topHeight)} else {// 校验图片高度固定值valid = img.height === height}valid ? resolve() : reject(new Error('error'))}}img.src = _URL.createObjectURL(file)}).then(() => {// console.log('then')return file}, () => {// console.log('reject')let text = ''if (width && height) {if (topWidth && topHeight) {text = `图片尺寸限制为:宽度${width}~${topWidth}px,高度${height}~${topHeight}px!`} else if (topHeight) {text = `图片尺寸限制为:宽度${width}px,高度${height}~${topHeight}px!`} else if (topWidth) {text = `图片尺寸限制为:宽度${width}~${topWidth}px,高度${height}px!`} else {text = `图片尺寸限制为:宽度${width}px,高度${height}px!`}} else if (width) {if (topWidth) {text = `图片尺寸限制为:宽度${width}~${topWidth}px!`} else {text = `图片尺寸限制为:宽度${width}px!`}} else if (height) {if (topHeight) {text = `图片尺寸限制为:高度${height}~${topHeight}px!`} else {text = `图片尺寸限制为:高度${height}px!`}}this.$message.error(text)return Promise.reject(new Error('error'))})return isType && isLtSize && isSize} else {// window对象,将blob或file读取成一个urlconst _URL = window.URL || window.webkitURLconst img = new Image()img.onload = () => { // image对象的onload事件,当图片加载完成后执行的函数this.imgWidth = img.widththis.imgHeight = img.height}img.src = _URL.createObjectURL(file)return isType && isLtSize}},handlePictureCardPreview (file) {this.dialogImageUrl = file.urlthis.dialogVisibleShow = true},exceedTips (file, fileList) {this.$message(`最多上传${fileList.length}个文件!`)},handeSuccess (res, file, fileList) {console.log('handeSuccess')}}
}
</script><style lang='less'>
@small-size: 80px;
.common-image-upload-cos-container {.el-dialog {background: transparent;-webkit-box-shadow: none;box-shadow: none;margin-top: 0 !important;width: auto;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);-webkit-transform: translate(-50%, -50%);.el-dialog__header {display: none;}.el-dialog__body {text-align: center;img {width: auto;max-width: 1000px;}}}
}
.image-upload-cos-content&&.limit-num {.el-upload--picture-card {display: none !important;}
}
.image-upload-cos-content&&.mini {.el-upload {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;}.el-upload-list__item {width: @small-size;height: @small-size;text-align: center;/*去除upload组件过渡效果*/transition: none !important;}.el-upload--picture-card {width: @small-size;height: @small-size;line-height: @small-size;text-align: center;}
}
.el-upload-list__item&&.is-success {.img-con {width: 100%;height: 100%;}
}
</style>

action:上传地址,必填,直接上传后端接口时填入接口链接;自定义上传方法使用 http-request,所以,填任意字符串即可,没有实际意义。

http-request:覆盖默认上传行为,这里自定义上传行为。

4、页面使用组件

<template><div>    <img-upload v-model="bgImg" :isGzip="true" :size="'small'" :tips="'建议图片宽度为350,JPG、JPGE、PNG 小于5M,仅限上传一张'" :limit="1" :limitSize="5"></img-upload><img-upload v-model="mainImg" tips="宽度750px,高度范围:170px~1334px" :isCheckPicSize="true" :checkWidth="750" :checkHeight="170" :topLimitHeight="1334" :limit="1" size="small"></img-upload> <img-upload v-model="imgArr" :tips="'多图数组'" :limit="6" :limitSize="1"></img-upload><img-upload v-model="imgList" :tips="'多图数组'" :limit="6" :limitSize="1" valueType="Object"></img-upload></div>
</template>
<script>
import ImgUpload from '@/components/image-upload'export default {name: 'demo',components: {ImgUpload},data () {return {bgImg: '',mainImg: '',imgArr: [ 'https://xxx', 'https://xxx']imgList: [{name: '1.jpg',url: 'https://xxx'}]     }    },props: {},watch: {},created () {    },mounted () {},methods: {    }
}
</script>

上传大小小于 80M 的图片,过程中打印进度信息如下:
在这里插入图片描述

上传大小大于 80M 的图片,过程中打印进度信息如下:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[React]面向组件编程

1. 定义组件 - 函数式定义&#xff08;简单组件&#xff09;&#xff0c;使用function定义 import React from react; import ReactDOM from react-dom/client;function App() {return (<button onClick{handleClick}>click</button> // 直接把方法handleClick赋…

Elasticsearch-01篇(单机版简单安装)

Elasticsearch-01篇&#xff08;单机版简单安装&#xff09; 1. 前言1.1 关于 Elastic Stack 2. Elasticsearch 的安装&#xff08;Linux&#xff09;2.1 准备工作2.1.1 下载2.1.2 解压&#xff08;启动不能用root&#xff0c;所以最好此处换个用户&#xff09; 2.2 修改相应的…

OpenStack(T版)——网络(Neutron)服务介绍与安装

文章目录 OpenStack(T版)——网络(Neutron)服务介绍与安装安装和配置(controller)准备(1)创建数据库(2)加载admin user的环境变量(3)创建服务凭证 配置Neutron网络服务组件(1)安装软件(2)配置服务器组件(3)配置Layer 2 (ML2)plug-in模块(4)配置桥接代理(5)配置内核(6)配置DHCP代…

JS知识点汇总(七)--数据类型

1. JavaScript中的简单数据类型有哪些&#xff1f; 1、概述 JS 中有六种简单数据类型&#xff1a;undefined、null、boolean、string、number、symbol ES10中的新特性 BigInt (任意精度整数)&#xff0c;目前还处于stage-4阶段&#xff0c;不出意外即将成为js的第七种基本数据…

多元函数微分

1-7 8&#xff0c;梯度 多元函数梯度&#xff1a; 方向导数是梯度在L方向上的投影 梯度方向是f增长最快的方向 9&#xff0c;极值点处若存在偏导&#xff0c;则该点为驻点&#xff0c;该点处的各偏导值为0 10&#xff0c; 二阶偏导和极值 二元函数&#xff1a; 多元函数的…

企业电子招投标采购系统之项目说明和开发类型源码

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…

哪个爬虫库用的最多?

在Python中&#xff0c;最常用的爬虫库是requests和BeautifulSoup。requests库用于发送HTTP请求和处理响应&#xff0c;而BeautifulSoup库用于解析HTML文档。这两个库通常结合使用&#xff0c;用于爬取网页内容并提取所需的数据。其他常用的爬虫库还包括Scrapy、Selenium等。 常…

kkfileview部署使用

1.gitee下载源码 kkFileView: 使用spring boot打造文件文档在线预览项目解决方案&#xff0c;支持doc、docx、ppt、pptx、wps、xls、xlsx、zip、rar、ofd、xmind、bpmn 、eml 、epub、3ds、dwg、psd 、mp4、mp3以及众多类文本类型文件在线预览 2.去掉cad 3.替换水印字体为免费…

构建交互式数据框架:使用Gradio的Dataframe模块

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

视频解说小程序看点小程序搭建上线,流量主对接实现广告收益

什么是视频解说小程序&#xff1f; 影视剪辑和解说&#xff0c;我们都知道有这类的抖音号&#xff0c;这时候就用到我们小程序了&#xff0c;流量主产生了收益。把视频解说上传到小程序&#xff0c;设置为广告观看&#xff0c;这样引导用户去小程序看&#xff0c;就产生一个广告…

配置鼠标右键菜单功能 :一键csv转excel

配置右键菜单功能 &#xff1a;一键csv转excel 无需点开文件&#xff0c;双击即可以生成新的excel文件 步骤&#xff1a; 1、配置Python&#xff0c;安装依赖库 pip install openpyxl pip install pandas2、创建Python文件 csv_to_excel.py # -*- coding:utf-8 -*- impor…

基于Java+SSM+Vue的高校校园点餐系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…