fix: 下载组件
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
* @Author: zhaojinfeng 121016171@qq.com
|
||||
* @Date: 2023-07-21 01:21:34
|
||||
* @LastEditors: zhaojinfeng 121016171@qq.com
|
||||
* @LastEditTime: 2023-07-21 01:51:17
|
||||
* @LastEditTime: 2023-07-21 03:57:13
|
||||
* @FilePath: \vue3\packages\download-link\index.vue
|
||||
* @Description: 现在组件
|
||||
*
|
||||
@ -79,8 +79,11 @@ function download() {
|
||||
downloadByRequest(props.src)
|
||||
break
|
||||
case 'object':
|
||||
if (typeof props.src?.url === 'string')
|
||||
downloadByRequest(props.src.url)
|
||||
if (typeof props.src?.url === 'string') {
|
||||
url = props.src.url
|
||||
type = 'success'
|
||||
saveAs(props.src.url, props.src.name)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
112
packages/upload-single-file/index.vue
Normal file
112
packages/upload-single-file/index.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<!--
|
||||
* @Author: zhaojinfeng 121016171@qq.com
|
||||
* @Date: 2023-07-21 00:37:46
|
||||
* @LastEditors: zhaojinfeng 121016171@qq.com
|
||||
* @LastEditTime: 2023-07-21 03:31:04
|
||||
* @FilePath: \vue3\packages\upload-single-file\index.vue
|
||||
* @Description:
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div v-loading="loading" @click="startUpload">
|
||||
<slot :loading="loading">
|
||||
<el-button type="primary" plain :text="text">
|
||||
点击上传
|
||||
</el-button>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="ThUploadSingleFile">
|
||||
const props = withDefaults(defineProps<{
|
||||
/** 文件id */
|
||||
modelValue?: string | number
|
||||
/** 自动上传 */
|
||||
autoUpload?: boolean
|
||||
/** 可接受的文件类型 */
|
||||
accept?: string
|
||||
disabled?: boolean
|
||||
/** 激活默认按钮的text属性 */
|
||||
text?: boolean
|
||||
/** 文件上传的请求函数 */
|
||||
request?: (formData: FormData) => Promise<FileVO>
|
||||
/** 自定义的上传文件名称,不带后缀名 */
|
||||
fileName?: string
|
||||
/** 最大上传大小,单位Mb */
|
||||
maxsize?: number
|
||||
}>(), {
|
||||
modelValue: '',
|
||||
autoUpload: false,
|
||||
accept: '',
|
||||
disabled: false,
|
||||
maxsize: 20,
|
||||
text: false,
|
||||
request: () => {
|
||||
throw new Error('请定义接口请求函数')
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', id?: number): void
|
||||
(event: 'onChange', file: File): void
|
||||
(event: 'onSuccess', file: FileVO): void
|
||||
(event: 'onError', error: Error): void
|
||||
}>()
|
||||
|
||||
let loading = $ref(false)
|
||||
|
||||
const { open, files, onChange } = useFileDialog({
|
||||
accept: props.accept,
|
||||
reset: true,
|
||||
})
|
||||
function startUpload() {
|
||||
if (!props.disabled)
|
||||
open()
|
||||
}
|
||||
|
||||
async function upload() {
|
||||
const file = files.value?.item(0)
|
||||
if (!file) {
|
||||
const err = '请选择上传文件'
|
||||
ElMessage.error(err)
|
||||
emit('onError', new Error(err))
|
||||
return
|
||||
}
|
||||
if (file.size > props.maxsize * 1024 * 1024) {
|
||||
const err = `文件大小不能超过${props.maxsize}Mb`
|
||||
ElMessage.error(err)
|
||||
emit('onError', new Error(err))
|
||||
return
|
||||
}
|
||||
loading = true
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
if (props.fileName)
|
||||
formData.append('name', props.fileName)
|
||||
|
||||
const res = await props.request(formData)
|
||||
emit('update:modelValue', res.id)
|
||||
emit('onSuccess', res)
|
||||
}
|
||||
catch (err: any) {
|
||||
emit('onError', err)
|
||||
}
|
||||
finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onChange((param) => {
|
||||
const file = param.item(0)
|
||||
emit('onChange', file)
|
||||
if (props.autoUpload)
|
||||
upload()
|
||||
})
|
||||
|
||||
watch(() => props.autoUpload,
|
||||
(autoupload) => {
|
||||
if (autoupload)
|
||||
upload()
|
||||
})
|
||||
</script>
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<el-table border :data="fileList">
|
||||
<div w-full>
|
||||
<el-table border :data="fileList" w-full>
|
||||
<el-table-column align="center" :label="label1">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.createdBy">
|
||||
@ -13,20 +14,32 @@
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="label2">
|
||||
<template #default="{ row, $index }">
|
||||
<upload-file-link v-if="row.createdBy" :file-id="row.id" :file-name="row.name">
|
||||
<download-link v-if="row.createdBy" :src="row" :file-name="row.name">
|
||||
{{ row.name }}
|
||||
</upload-file-link>
|
||||
<upload-file-single
|
||||
v-else :accept="accept" :auto-upload="false" :index-key="`${row.id}`" :max-size="20"
|
||||
:file-name="allName[`${row.id}`]" :loading="allLoading[`${row.id}`]"
|
||||
@on-change="$event => onChange($event, row.id)" @on-success="$event => onSuccess($event, row, $index)"
|
||||
</download-link>
|
||||
<upload-single-file
|
||||
v-else
|
||||
:accept="accept"
|
||||
:auto-upload="allLoading[`${row.id}`]"
|
||||
:max-size="20"
|
||||
:file-name="allName[`${row.id}`]"
|
||||
:request="request"
|
||||
@on-change="$event => onChange($event, row.id)"
|
||||
@on-success="$event => onSuccess($event, row, $index)"
|
||||
@on-error="onError(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="label3" width="200px">
|
||||
<template #default="{ row, $index }">
|
||||
<el-popconfirm v-if="row.createdBy" :title="popconfirm" :disabled="disabled" @confirm="remove($index)">
|
||||
<el-popconfirm v-if="allowReplace" :title="replacePopconfirm" :disabled="disabled" @confirm="remove($index)">
|
||||
<template #reference>
|
||||
<el-button type="danger" :disabled="disabled" link>
|
||||
重新上传
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-popconfirm v-if="allowDelete && row.createdBy" :title="deletePopconfirm" :disabled="disabled" @confirm="remove($index)">
|
||||
<template #reference>
|
||||
<el-button type="danger" :disabled="disabled" link>
|
||||
删除
|
||||
@ -44,11 +57,15 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="!hideAdd" w-full text-center>
|
||||
<el-button :disabled="disabled" type="primary" plain @click="add">
|
||||
{{ addText }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="ThUploadTable">
|
||||
import type { UploadFile } from 'element-plus'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
/** 上传格式 */
|
||||
accept?: string
|
||||
@ -58,21 +75,41 @@ const props = withDefaults(defineProps<{
|
||||
label2?: string
|
||||
/** 列3的文字 */
|
||||
label3?: string
|
||||
disabled?: boolean
|
||||
/** 文件列表,支持v-model */
|
||||
fileList?: FileVO[]
|
||||
/** 删除按钮提示文字 */
|
||||
popconfirm?: string
|
||||
/** 再次上传确认的提示文字 */
|
||||
replacePopconfirm?: string
|
||||
/** 删除确认的提示文字 */
|
||||
deletePopconfirm?: string
|
||||
/** 新增按钮的提示文字 */
|
||||
addText?: string
|
||||
/** 允许替换,用再次上传覆盖之前已上传的文件 */
|
||||
allowReplace?: boolean
|
||||
/** 允许删除已上传的文件 */
|
||||
allowDelete?: boolean
|
||||
/** 表格禁用,支持v-model */
|
||||
disabled?: boolean
|
||||
/** 隐藏新增按钮 */
|
||||
hideAdd?: boolean
|
||||
/** 文件上传的请求函数 */
|
||||
request?: (formData: FormData) => Promise<FileVO>
|
||||
}>(), {
|
||||
accept: '.jpeg,.jpg,.png,.bmp',
|
||||
addText: '新增一行',
|
||||
allowDelete: true,
|
||||
allowReplace: false,
|
||||
disabled: false,
|
||||
hideAdd: false,
|
||||
label1: '文件名称',
|
||||
label2: '图片上传',
|
||||
label3: '操作',
|
||||
fileList: () => [],
|
||||
popconfirm: '是否删除本行?',
|
||||
replacePopconfirm: '是否重新本行文件?',
|
||||
deletePopconfirm: '是否删除本行?',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:disabled', disabled: boolean): void
|
||||
(event: 'update:fileList', fileList?: FileVO[]): void
|
||||
}>()
|
||||
|
||||
@ -84,6 +121,7 @@ function onSuccess(file: FileVO, row: FileVO, index: number) {
|
||||
const indexKey = `${row.id}`
|
||||
const newList = [...props.fileList.slice(0, index), file, ...props.fileList.slice(index + 1)]
|
||||
emit('update:fileList', newList)
|
||||
ElMessage.success('上传成功')
|
||||
allLoading[indexKey] = false
|
||||
}
|
||||
|
||||
@ -92,13 +130,10 @@ function onError(id: number) {
|
||||
allLoading[indexKey] = false
|
||||
}
|
||||
|
||||
function onChange(uploadFile: UploadFile, id: number) {
|
||||
// 自动设置文件名
|
||||
function onChange(uploadFile: File, id: number) {
|
||||
const indexKey = `${id}`
|
||||
|
||||
if (uploadFile.status !== 'ready') {
|
||||
allLoading[indexKey] = false
|
||||
return
|
||||
}
|
||||
if (allName[indexKey])
|
||||
return
|
||||
|
||||
@ -115,6 +150,13 @@ function save(id: number) {
|
||||
allLoading[indexKey] = true
|
||||
}
|
||||
|
||||
function add() {
|
||||
const id = Date.now() // 生成一个id
|
||||
allName[id] = ''
|
||||
allLoading[id] = false
|
||||
emit('update:fileList', [...props.fileList, { id }])
|
||||
}
|
||||
|
||||
function remove(index: number) {
|
||||
const newList = [...props.fileList.slice(0, index), ...props.fileList.slice(index + 1)]
|
||||
emit('update:fileList', newList)
|
||||
|
36
stories/UploadSingleFile.stories.ts
Normal file
36
stories/UploadSingleFile.stories.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author: zhaojinfeng 121016171@qq.com
|
||||
* @Date: 2023-07-21 00:37:46
|
||||
* @LastEditors: zhaojinfeng 121016171@qq.com
|
||||
* @LastEditTime: 2023-07-21 02:56:04
|
||||
* @FilePath: \vue3\stories\UploadSingleFile.stories.ts
|
||||
* @Description:
|
||||
*
|
||||
*/
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
import ThUploadSingleFile from '../packages/upload-single-file/index.vue'
|
||||
import avatar from './assets/avatar.jpeg'
|
||||
|
||||
const meta = {
|
||||
title: '表单组件/UploadSingleFile',
|
||||
component: ThUploadSingleFile,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
modelValue: '',
|
||||
autoUpload: false,
|
||||
accept: '',
|
||||
disabled: false,
|
||||
text: false,
|
||||
request: () => Promise.resolve({
|
||||
id: 0,
|
||||
url: avatar,
|
||||
}),
|
||||
},
|
||||
} satisfies Meta<typeof ThUploadSingleFile>
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Base: Story = {
|
||||
name: '基本使用',
|
||||
}
|
@ -2,28 +2,79 @@
|
||||
* @Author: zhaojinfeng 121016171@qq.com
|
||||
* @Date: 2023-07-18 12:23:37
|
||||
* @LastEditors: zhaojinfeng 121016171@qq.com
|
||||
* @LastEditTime: 2023-07-18 16:11:39
|
||||
* @LastEditTime: 2023-07-21 03:50:54
|
||||
* @FilePath: \vue3\stories\UploadTable.stories.ts
|
||||
* @Description:
|
||||
*
|
||||
*/
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
import ThUploadTable from '../packages/upload-table/index.vue'
|
||||
import avatar from './assets/avatar.jpeg'
|
||||
|
||||
// 模拟2秒钟的接口请求
|
||||
function request(formData: FormData) {
|
||||
let name = '佚名文件'
|
||||
let url = avatar
|
||||
const fileName = formData.get('name')
|
||||
const file = formData.get('file')
|
||||
if (typeof fileName === 'string') {
|
||||
name = fileName
|
||||
}
|
||||
else if (typeof file !== 'string' && file) {
|
||||
name = file.name
|
||||
url = URL.createObjectURL(file)
|
||||
}
|
||||
return new Promise<FileVO>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
id: Date.now(),
|
||||
name,
|
||||
url,
|
||||
createdBy: 'user',
|
||||
})
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: '表单组件/UploadTable',
|
||||
component: ThUploadTable,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
accept: '.jpeg,.jpg,.png,.bmp',
|
||||
addText: '新增一行',
|
||||
allowDelete: true,
|
||||
allowReplace: false,
|
||||
disabled: false,
|
||||
hideAdd: false,
|
||||
label1: '文件名称',
|
||||
label2: '图片上传',
|
||||
label3: '操作',
|
||||
fileList: [],
|
||||
replacePopconfirm: '是否重新本行文件?',
|
||||
deletePopconfirm: '是否删除本行?',
|
||||
request,
|
||||
},
|
||||
argTypes: {
|
||||
label1: { control: 'select', options: ['small', 'medium', 'large'] },
|
||||
render: args => ({
|
||||
components: { ThUploadTable },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `<th-upload-table
|
||||
v-model:file-list="args.fileList"
|
||||
v-model:disabled="args.disabled"
|
||||
:accept="args.accept"
|
||||
:add-text="args.addText"
|
||||
:allow-delete="args.allowDelete"
|
||||
:allow-replace="args.allowReplace"
|
||||
:hide-add="args.hideAdd"
|
||||
:label1="args.label1"
|
||||
:label2="args.label2"
|
||||
:label3="args.label3"
|
||||
:replace-popconfirm="args.replacePopconfirm"
|
||||
:delete-popconfirm="args.deletePopconfirm"
|
||||
:request="args.request"
|
||||
/>`,
|
||||
}),
|
||||
} satisfies Meta<typeof ThUploadTable>
|
||||
export default meta
|
||||
|
||||
|
8
types/components.d.ts
vendored
8
types/components.d.ts
vendored
@ -7,14 +7,14 @@ export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Button: typeof import('./../packages/button/index.vue')['default']
|
||||
DownloadLink: typeof import('./../packages/download-link/index.vue')['default']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
@ -26,6 +26,10 @@ declare module 'vue' {
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SelectTableModal: typeof import('./../packages/select-table-modal/index.vue')['default']
|
||||
UploadAvatar: typeof import('./../packages/upload-avatar/index.vue')['default']
|
||||
UploadSingleFile: typeof import('./../packages/upload-single-file/index.vue')['default']
|
||||
UploadTable: typeof import('./../packages/upload-table/index.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
5
types/file.d.ts
vendored
5
types/file.d.ts
vendored
@ -24,4 +24,9 @@ declare interface FileVO {
|
||||
*
|
||||
*/
|
||||
url?: string
|
||||
/**
|
||||
* 创建者
|
||||
*
|
||||
*/
|
||||
createdBy?: string
|
||||
}
|
Reference in New Issue
Block a user