editor 富文本编辑器上传图片,视频功能更新

This commit is contained in:
ktianc 2023-08-08 11:08:30 +08:00
parent cf6a60ad3a
commit 6360dff110
10 changed files with 103 additions and 19 deletions

View File

@ -2,7 +2,7 @@
import { ElCard, ElButton } from 'element-plus' import { ElCard, ElButton } from 'element-plus'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { ref, onMounted, defineEmits } from 'vue' import { ref, onMounted } from 'vue'
import { Sticky } from '@/components/Sticky' import { Sticky } from '@/components/Sticky'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n() const { t } = useI18n()
@ -15,9 +15,11 @@ defineProps({
title: propTypes.string.def(''), title: propTypes.string.def(''),
message: propTypes.string.def('') message: propTypes.string.def('')
}) })
const emit = defineEmits(['back']) const emit = defineEmits(['back'])
const offset = ref(85) const offset = ref(85)
const contentDetailWrap = ref() const contentDetailWrap = ref()
onMounted(() => { onMounted(() => {
offset.value = contentDetailWrap.value.getBoundingClientRect().top offset.value = contentDetailWrap.value.getBoundingClientRect().top
}) })

View File

@ -6,6 +6,9 @@ import { propTypes } from '@/utils/propTypes'
import { isNumber } from '@/utils/is' import { isNumber } from '@/utils/is'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale' import { useLocaleStore } from '@/store/modules/locale'
import { uploadImageToOSS, uploadVideoToOSS } from '@/api/vadmin/system/files'
// editor https://www.wangeditor.com/v5/getting-started.html
const localeStore = useLocaleStore() const localeStore = useLocaleStore()
@ -79,7 +82,48 @@ const editorConfig = computed((): IEditorConfig => {
}, },
autoFocus: false, autoFocus: false,
scroll: true, scroll: true,
uploadImgShowBase64: true uploadImgShowBase64: true,
MENU_CONF: {
uploadImage: {
//
// https://www.wangeditor.com/v5/menu-config.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%8A%E4%BC%A0
async customUpload(file: File, insertFn: InsertImageType) {
//
if (!['image/jpeg', 'image/gif', 'image/png'].includes(file.type)) {
return ElMessage.error(`${file.name}上传失败:上传图片只能是 JPG/GIF/PNG/ 格式!`)
}
if (!(file.size / 1024 / 1024 < 2)) {
return ElMessage.error(`${file.name}上传失败:上传图片大小不能超过 2MB!`)
}
// url
const formData = new FormData()
formData.append('file', file)
formData.append('path', 'editor/image')
const res = await uploadImageToOSS(formData)
insertFn(res.data, '', res.data)
}
},
uploadVideo: {
//
// https://www.wangeditor.com/v5/menu-config.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8A%9F%E8%83%BD-1
async customUpload(file: File, insertFn: InsertVideoType) {
//
if (!['video/mp4', 'video/mpeg'].includes(file.type)) {
return ElMessage.error(`${file.name}上传失败:上传视频只能是 mp4/mpeg/ 格式!`)
}
if (!(file.size / 1024 / 1024 < 5)) {
return ElMessage.error(`${file.name}上传失败:上传视频大小不能超过 5MB!`)
}
// url
const formData = new FormData()
formData.append('file', file)
formData.append('path', 'editor/video')
const res = await uploadVideoToOSS(formData)
insertFn(res.data, '')
}
}
}
}, },
props.editorConfig || {} props.editorConfig || {}
) )

View File

@ -173,7 +173,7 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
// 如果为 false则说明是点击按钮则获取当前选择行数据进行删除 // 如果为 false则说明是点击按钮则获取当前选择行数据进行删除
delListApi: async ( delListApi: async (
multiple: boolean, multiple: boolean,
ids: string[] | number[] | number = [], ids: string[] | number[] | number | string = [],
message = true message = true
) => { ) => {
const tableRef = await getTable() const tableRef = await getTable()

3
kinit-admin/src/types/editor.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
type InsertImageType = (url: string, alt: string, href: string) => void
type InsertVideoType = (url: string, poster: string = '') => void

View File

@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
""" """
系统版本 系统版本
""" """
VERSION = "1.10.3" VERSION = "1.10.4"
"""安全警告: 不要在生产中打开调试运行!""" """安全警告: 不要在生产中打开调试运行!"""
DEBUG = True DEBUG = True

View File

@ -282,8 +282,6 @@ class UserDal(DalBase):
更新当前用户头像 更新当前用户头像
""" """
result = await AliyunOSS(BucketConf(**settings.ALIYUN_OSS)).upload_image("avatar", file) result = await AliyunOSS(BucketConf(**settings.ALIYUN_OSS)).upload_image("avatar", file)
if not result:
raise CustomException(msg="上传失败", code=status.HTTP_ERROR)
user.avatar = result user.avatar = result
await self.flush(user) await self.flush(user)
return result return result

View File

@ -113,8 +113,18 @@ async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())):
@app.post("/upload/image/to/oss", summary="上传图片到阿里云OSS") @app.post("/upload/image/to/oss", summary="上传图片到阿里云OSS")
async def upload_image_to_oss(file: UploadFile, path: str = Form(...)): async def upload_image_to_oss(file: UploadFile, path: str = Form(...)):
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(path, file) result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(path, file)
if not result: return SuccessResponse(result)
return ErrorResponse(msg="上传失败")
@app.post("/upload/video/to/oss", summary="上传视频到阿里云OSS")
async def upload_video_to_oss(file: UploadFile, path: str = Form(...)):
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_video(path, file)
return SuccessResponse(result)
@app.post("/upload/file/to/oss", summary="上传文件到阿里云OSS")
async def upload_file_to_oss(file: UploadFile, path: str = Form(...)):
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_file(path, file)
return SuccessResponse(result) return SuccessResponse(result)

View File

@ -11,7 +11,9 @@ from fastapi import UploadFile
from pydantic import BaseModel from pydantic import BaseModel
import oss2 # 安装依赖库pip install oss2 import oss2 # 安装依赖库pip install oss2
from oss2.models import PutObjectResult from oss2.models import PutObjectResult
from core.exception import CustomException
from core.logger import logger from core.logger import logger
from utils import status
from utils.file.compress.cpressJPG import compress_jpg_png from utils.file.compress.cpressJPG import compress_jpg_png
from utils.file.file_manage import FileManage from utils.file.file_manage import FileManage
from utils.file.file_base import FileBase from utils.file.file_base import FileBase
@ -45,15 +47,19 @@ class AliyunOSS(FileBase):
self.bucket = oss2.Bucket(auth, bucket.endpoint, bucket.bucket) self.bucket = oss2.Bucket(auth, bucket.endpoint, bucket.bucket)
self.baseUrl = bucket.baseUrl self.baseUrl = bucket.baseUrl
async def upload_image(self, path: str, file: UploadFile, compress: bool = False) -> str: async def upload_image(self, path: str, file: UploadFile, compress: bool = False, max_size: int = 10) -> str:
""" """
上传图片 上传图片
:param path: path由包含文件后缀不包含Bucket名称组成的Object完整路径例如abc/efg/123.jpg :param path: path由包含文件后缀不包含Bucket名称组成的Object完整路径例如abc/efg/123.jpg
:param file: 文件对象 :param file: 文件对象
:param compress: 是否压缩该文件 :param compress: 是否压缩该文件
:param max_size: 图片文件最大值单位 MB默认 10MB
:return: 上传后的文件oss链接 :return: 上传后的文件oss链接
""" """
# 验证图片类型
await self.validate_file(file, max_size, self.IMAGE_ACCEPT)
# 生成文件路径
path = self.generate_path(path, file.filename) path = self.generate_path(path, file.filename)
if compress: if compress:
# 压缩图片 # 压缩图片
@ -63,14 +69,23 @@ class AliyunOSS(FileBase):
file_data = f.read() file_data = f.read()
else: else:
file_data = await file.read() file_data = await file.read()
result = self.bucket.put_object(path, file_data) return await self.__upload_file_to_oss(path, file_data)
assert isinstance(result, PutObjectResult)
if result.status != 200: async def upload_video(self, path: str, file: UploadFile, max_size: int = 100) -> str:
logger.error(f"图片上传到OSS失败状态码{result.status}") """
print("图片上传路径", path) 上传视频
print(f"图片上传到OSS失败状态码{result.status}")
return "" :param path: path由包含文件后缀不包含Bucket名称组成的Object完整路径例如abc/efg/123.jpg
return self.baseUrl + path :param file: 文件对象
:param max_size: 视频文件最大值单位 MB默认 100MB
:return: 上传后的文件oss链接
"""
# 验证图片类型
await self.validate_file(file, max_size, self.VIDEO_ACCEPT)
# 生成文件路径
path = self.generate_path(path, file.filename)
file_data = await file.read()
return await self.__upload_file_to_oss(path, file_data)
async def upload_file(self, path: str, file: UploadFile) -> str: async def upload_file(self, path: str, file: UploadFile) -> str:
""" """
@ -82,9 +97,19 @@ class AliyunOSS(FileBase):
""" """
path = self.generate_path(path, file.filename) path = self.generate_path(path, file.filename)
file_data = await file.read() file_data = await file.read()
return await self.__upload_file_to_oss(path, file_data)
async def __upload_file_to_oss(self, path: str, file_data: bytes) -> str:
"""
上传文件到OSS
:param path: path由包含文件后缀不包含Bucket名称组成的Object完整路径例如abc/efg/123.jpg
:param file_data: 文件数据
:return: 上传后的文件oss链接
"""
result = self.bucket.put_object(path, file_data) result = self.bucket.put_object(path, file_data)
assert isinstance(result, PutObjectResult) assert isinstance(result, PutObjectResult)
if result.status != 200: if result.status != 200:
logger.error(f"文件上传到OSS失败状态码{result.status}") logger.error(f"文件上传到OSS失败状态码{result.status}")
return "" raise CustomException("上传文件失败", code=status.HTTP_ERROR)
return self.baseUrl + path return self.baseUrl + path

View File

@ -17,7 +17,7 @@ from utils import status
class FileBase: class FileBase:
IMAGE_ACCEPT = ["image/png", "image/jpeg", "image/gif", "image/x-icon"] IMAGE_ACCEPT = ["image/png", "image/jpeg", "image/gif", "image/x-icon"]
VIDEO_ACCEPT = ["audio/mp4", "video/mp4", "video/mpeg"] VIDEO_ACCEPT = ["video/mp4", "video/mpeg"]
ALL_ACCEPT = [*IMAGE_ACCEPT, *VIDEO_ACCEPT] ALL_ACCEPT = [*IMAGE_ACCEPT, *VIDEO_ACCEPT]
@classmethod @classmethod

View File

@ -16,10 +16,12 @@ https://api.ip138.com/ip/?ip=58.16.180.3&datatype=jsonp&token=cc87f3c77747bccbaa
aiohttp 异步请求文档https://docs.aiohttp.org/en/stable/client_quickstart.html aiohttp 异步请求文档https://docs.aiohttp.org/en/stable/client_quickstart.html
""" """
from aiohttp import TCPConnector from aiohttp import TCPConnector
from application.settings import IP_PARSE_TOKEN, IP_PARSE_ENABLE from application.settings import IP_PARSE_TOKEN, IP_PARSE_ENABLE
import aiohttp import aiohttp
from core.logger import logger from core.logger import logger
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional
class IPLocationOut(BaseModel): class IPLocationOut(BaseModel):