提交 7926c8ae authored 作者: 王鹏飞's avatar 王鹏飞

chore: 优化上传

上级 07b6e8b2
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
"element-plus": "^2.8.7", "element-plus": "^2.8.7",
"eventsource-parser": "^3.0.2", "eventsource-parser": "^3.0.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"file-type": "^21.1.0",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
...@@ -138,6 +139,16 @@ ...@@ -138,6 +139,16 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@borewit/text-codec": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
"integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@bufbuild/protobuf": { "node_modules/@bufbuild/protobuf": {
"version": "2.10.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.0.tgz", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.0.tgz",
...@@ -1584,6 +1595,36 @@ ...@@ -1584,6 +1595,36 @@
"vue": "^3.0.0" "vue": "^3.0.0"
} }
}, },
"node_modules/@tokenizer/inflate": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.3.1.tgz",
"integrity": "sha512-4oeoZEBQdLdt5WmP/hx1KZ6D3/Oid/0cUb2nk4F0pTDAWy+KCH3/EnAkZF/bvckWo8I33EqBm01lIPgmgc8rCA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.1",
"fflate": "^0.8.2",
"token-types": "^6.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/inflate/node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@tsconfig/node20": { "node_modules/@tsconfig/node20": {
"version": "20.1.4", "version": "20.1.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz",
...@@ -4717,6 +4758,24 @@ ...@@ -4717,6 +4758,24 @@
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
}, },
"node_modules/file-type": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.0.tgz",
"integrity": "sha512-boU4EHmP3JXkwDo4uhyBhTt5pPstxB6eEXKJBu2yu2l7aAMMm7QQYQEzssJmKReZYrFdFOJS8koVo6bXIBGDqA==",
"license": "MIT",
"dependencies": {
"@tokenizer/inflate": "^0.3.1",
"strtok3": "^10.3.1",
"token-types": "^6.0.0",
"uint8array-extras": "^1.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/file-uri-to-path": { "node_modules/file-uri-to-path": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
...@@ -8189,6 +8248,22 @@ ...@@ -8189,6 +8248,22 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/strtok3": {
"version": "10.3.4",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
"integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
...@@ -8534,6 +8609,24 @@ ...@@ -8534,6 +8609,24 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/token-types": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz",
"integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==",
"license": "MIT",
"dependencies": {
"@borewit/text-codec": "^0.1.0",
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz",
...@@ -8695,6 +8788,18 @@ ...@@ -8695,6 +8788,18 @@
"source-map": "~0.6.1" "source-map": "~0.6.1"
} }
}, },
"node_modules/uint8array-extras": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
"element-plus": "^2.8.7", "element-plus": "^2.8.7",
"eventsource-parser": "^3.0.2", "eventsource-parser": "^3.0.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"file-type": "^21.1.0",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
......
...@@ -13,6 +13,7 @@ const handleRefresh = () => { ...@@ -13,6 +13,7 @@ const handleRefresh = () => {
} }
const listParams = reactive({ name: '', live_commodity_type_id: '', live_commodity_title: '' }) const listParams = reactive({ name: '', live_commodity_type_id: '', live_commodity_title: '' })
const listLength = ref(0)
// 列表配置 // 列表配置
const listOptions = computed(() => { const listOptions = computed(() => {
return { return {
...@@ -24,6 +25,10 @@ const listOptions = computed(() => { ...@@ -24,6 +25,10 @@ const listOptions = computed(() => {
params.live_commodity_type_id = listParams.live_commodity_type_id params.live_commodity_type_id = listParams.live_commodity_type_id
return params return params
}, },
callback(data) {
listLength.value = data.list.length
return data
},
}, },
filters: [ filters: [
{ label: '直播主题品类', prop: 'live_commodity_type_id', slots: 'filter-category' }, { label: '直播主题品类', prop: 'live_commodity_type_id', slots: 'filter-category' },
...@@ -77,7 +82,7 @@ const handleRemove = async (row) => { ...@@ -77,7 +82,7 @@ const handleRemove = async (row) => {
<AppCard title="直播话术管理"> <AppCard title="直播话术管理">
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-button type="primary" @click="handleAdd">添加话术</el-button> <el-button type="primary" @click="handleAdd" v-if="!listLength">添加话术</el-button>
</template> </template>
<template #filter-category> <template #filter-category>
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory> <LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
......
...@@ -89,7 +89,10 @@ const { ...@@ -89,7 +89,10 @@ const {
onRecord: async (blob) => { onRecord: async (blob) => {
if (props.isLocalUpload) return if (props.isLocalUpload) return
if (isOSSUpload) { if (isOSSUpload) {
ossResult = await appendUpload(blob, ossResult?.nextAppendPosition, `${fileName.value}.mp4`) ossResult = await appendUpload(blob, {
name: `live/${ssoId}/${fileName.value}.mp4`,
position: ossResult?.nextAppendPosition,
})
fileUrl.value = ossResult.url fileUrl.value = ossResult.url
} else { } else {
const base64Data = await readBlobAsBase64(blob) const base64Data = await readBlobAsBase64(blob)
......
...@@ -50,7 +50,8 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }: ...@@ -50,7 +50,8 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }:
// 初始化MediaRecorder // 初始化MediaRecorder
const initializeMediaRecorder = () => { const initializeMediaRecorder = () => {
if (!stream.value) return if (!stream.value) return
mediaRecorder = new MediaRecorder(stream.value, { mimeType: 'video/webm' }) const mimeType = MediaRecorder.isTypeSupported('video/mp4') ? 'video/mp4' : 'video/webm'
mediaRecorder = new MediaRecorder(stream.value, { mimeType })
mediaRecorder.ondataavailable = handleDataAvailable mediaRecorder.ondataavailable = handleDataAvailable
mediaRecorder.onstart = handleStart mediaRecorder.onstart = handleStart
mediaRecorder.onstop = handleStop mediaRecorder.onstop = handleStop
...@@ -93,7 +94,7 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }: ...@@ -93,7 +94,7 @@ export function useLive({ enabledUserMedia = true, onStart, onRecord, onStop }:
const start = () => { const start = () => {
if (!mediaRecorder) initializeMediaRecorder() if (!mediaRecorder) initializeMediaRecorder()
recordedChunks.value = [] recordedChunks.value = []
mediaRecorder?.start(5000) // 每100ms触发一次dataavailable事件 mediaRecorder?.start(1000 * 10) // 每100ms触发一次dataavailable事件
} }
// 停止录制 // 停止录制
......
<script setup> <script setup>
import AppEditor from '@/components/base/AppEditor.vue' import AppEditor from '@/components/base/AppEditor.vue'
import { useFileDialog } from '@vueuse/core' import { useFileDialog } from '@vueuse/core'
import { upload } from '@/utils/upload' import { multipartUpload } from '@/utils/oss'
import { Delete } from '@element-plus/icons-vue' import { Delete } from '@element-plus/icons-vue'
const form = defineModel('form', { const form = defineModel('form', {
...@@ -29,6 +29,7 @@ const emit = defineEmits(['save']) ...@@ -29,6 +29,7 @@ const emit = defineEmits(['save'])
const { open, onChange } = useFileDialog({ const { open, onChange } = useFileDialog({
accept: 'image/*,video/*', accept: 'image/*,video/*',
multiple: false,
}) })
const currentItem = computed(() => { const currentItem = computed(() => {
...@@ -46,16 +47,20 @@ const handleUpload = async () => { ...@@ -46,16 +47,20 @@ const handleUpload = async () => {
} }
} }
const isLoading = ref(false) const isLoading = ref(false)
const percentage = ref(0)
onChange(async (files) => { onChange(async (files) => {
if (!files) return if (!files) return
const [file] = files const [file] = files
isLoading.value = true isLoading.value = true
const res = await upload(file) const res = await multipartUpload(file, {
progress: (progress) => {
percentage.value = Math.round(progress * 100)
},
})
if (currentItem.value.isImage) { if (currentItem.value.isImage) {
form.value[key.value] = form.value[key.value] ? [...form.value[key.value], res] : [res] form.value[key.value] = form.value[key.value] ? [...form.value[key.value], res.url] : [res.url]
} else { } else {
form.value[key.value] = res form.value[key.value] = res.url
} }
isLoading.value = false isLoading.value = false
}) })
...@@ -73,6 +78,7 @@ const handleDelete = (url) => { ...@@ -73,6 +78,7 @@ const handleDelete = (url) => {
<div style="background: #ecf2f7; padding: 40px; border-radius: 20px; min-height: 200px"> <div style="background: #ecf2f7; padding: 40px; border-radius: 20px; min-height: 200px">
<template v-if="item.isImage || item.isVideo"> <template v-if="item.isImage || item.isVideo">
<el-button type="primary" round @click="handleUpload" :loading="isLoading">点击上传</el-button> <el-button type="primary" round @click="handleUpload" :loading="isLoading">点击上传</el-button>
<el-progress :percentage="percentage" v-if="isLoading" />
<div v-if="item.isImage" class="upload-tips"> <div v-if="item.isImage" class="upload-tips">
<p>图片格式为png/jpg/jpeg,图片大小不能超过2M</p> <p>图片格式为png/jpg/jpeg,图片大小不能超过2M</p>
</div> </div>
......
import OSS from 'ali-oss' import OSS from 'ali-oss'
import md5 from 'blueimp-md5' import md5 from 'blueimp-md5'
import { fileTypeFromBlob } from 'file-type'
import { getToken } from '@/api/base' import { getToken } from '@/api/base'
export const prefix = 'upload/saas-dml-live/'
let store: OSS | null = null let store: OSS | null = null
export async function getTokenInfo() { export async function getTokenInfo() {
...@@ -30,33 +29,35 @@ export async function createStore() { ...@@ -30,33 +29,35 @@ export async function createStore() {
})) }))
} }
export function generateObjectName(file: Blob | File, objectName?: string) { export async function parseParams(file: Blob | File, options: any = {}) {
if (objectName) return `${prefix}${objectName}` const { name, prefix = 'upload/saas-dml-pro/', ...rest } = options
let fileType = 'png' let objectName: string
if (file instanceof File && file.name) { if (name) {
const matches = file.name.match(/\.(\w+)$/) objectName = `${prefix}${name}`
if (matches) { } else {
fileType = matches[1] const result = await fileTypeFromBlob(file)
} objectName = `${prefix}${md5(new Date().getTime() + Math.random().toString(36).slice(-8))}.${result?.ext}`
} else if (file.type) {
const mimeType = file.type.split('/').pop()
if (mimeType) {
fileType = mimeType
}
} }
return `${prefix}${md5(new Date().getTime() + Math.random().toString(36).slice(-8))}.${fileType}` return { name: objectName, file, options: rest }
} }
// 上传 // 上传
export async function putUpload(file: Blob | File, objectName?: string) { export async function putUpload(file: Blob | File, options?: any) {
const store = await createStore() const store = await createStore()
return store.put(generateObjectName(file, objectName), file) const params = await parseParams(file, options)
return store.put(params.name, params.file, params.options)
} }
// 追加上传 // 追加上传
export async function appendUpload(file: Blob | File, position = '', objectName?: string) { export async function appendUpload(file: Blob | File, options?: any) {
const store = await createStore()
const params = await parseParams(file, options)
return store.append(params.name, params.file, params.options)
}
export async function multipartUpload(file: Blob | File, options?: any) {
const store = await createStore() const store = await createStore()
let options = {} const params = await parseParams(file, options)
if (position) options = { position } const res: any = await store.multipartUpload(params.name, params.file, params.options)
return store.append(generateObjectName(file, objectName), file, options) return { url: `https://webapp-pub.ezijing.com/${res.name}`, ...res }
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论