fix: 下载组件

This commit is contained in:
2023-07-21 03:58:45 +08:00
parent 22a3951222
commit 735170b897
7 changed files with 315 additions and 62 deletions

View File

@ -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

View 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>

View File

@ -1,54 +1,71 @@
<template>
<el-table border :data="fileList">
<el-table-column align="center" :label="label1">
<template #default="{ row }">
<template v-if="row.createdBy">
{{ row.name }}
<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">
{{ row.name }}
</template>
<el-input
v-else v-model.trim="allName[`${row.id}`]" clearable :loading="allLoading[`${row.id}`]"
placeholder="不填则为默认文件名称"
/>
</template>
<el-input
v-else v-model.trim="allName[`${row.id}`]" clearable :loading="allLoading[`${row.id}`]"
placeholder="不填则为默认文件名称"
/>
</template>
</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">
{{ 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)"
@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)">
<template #reference>
<el-button type="danger" :disabled="disabled" link>
删除
</el-table-column>
<el-table-column align="center" :label="label2">
<template #default="{ row, $index }">
<download-link v-if="row.createdBy" :src="row" :file-name="row.name">
{{ row.name }}
</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="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>
删除
</el-button>
</template>
</el-popconfirm>
<template v-else>
<el-button type="primary" :loading="allLoading[`${row.id}`]" link @click="save(row.id)">
保存
</el-button>
<el-button type="danger" :loading="allLoading[`${row.id}`]" link @click="remove($index)">
取消
</el-button>
</template>
</el-popconfirm>
<template v-else>
<el-button type="primary" :loading="allLoading[`${row.id}`]" link @click="save(row.id)">
保存
</el-button>
<el-button type="danger" :loading="allLoading[`${row.id}`]" link @click="remove($index)">
取消
</el-button>
</template>
</template>
</el-table-column>
</el-table>
</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)

View 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: '基本使用',
}

View File

@ -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

View File

@ -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
View File

@ -24,4 +24,9 @@ declare interface FileVO {
*
*/
url?: string
/**
* 创建者
*
*/
createdBy?: string
}