mirror of
https://github.com/keven1024/015.git
synced 2026-05-30 08:59:34 +00:00
refactor(front): update FileShareDrawer and FileUploadField components to handle multiple files and improve file type checks
This commit is contained in:
@@ -1,120 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
LucideShare,
|
||||
LucideImage,
|
||||
LucideBot,
|
||||
LucideLanguages,
|
||||
LucideFileText,
|
||||
LucideImageMinus,
|
||||
LucideArrowRightLeft,
|
||||
LucideImagePlus,
|
||||
LucideAudioLines,
|
||||
LucideListMusic,
|
||||
} from "lucide-vue-next";
|
||||
import { cx } from "class-variance-authority";
|
||||
import { isObject } from "lodash-es";
|
||||
import showDrawer from "@/lib/showDrawer";
|
||||
import FileShareHandle from "@/components/Preprocessing/FileShareHandle.vue";
|
||||
const { t } = useI18n();
|
||||
LucideShare,
|
||||
LucideImage,
|
||||
LucideBot,
|
||||
LucideLanguages,
|
||||
LucideFileText,
|
||||
LucideImageMinus,
|
||||
LucideArrowRightLeft,
|
||||
LucideImagePlus,
|
||||
LucideAudioLines,
|
||||
LucideListMusic,
|
||||
} from 'lucide-vue-next'
|
||||
import { cx } from 'class-variance-authority'
|
||||
import { isObject } from 'lodash-es'
|
||||
import showDrawer from '@/lib/showDrawer'
|
||||
import FileShareHandle from '@/components/Preprocessing/FileShareHandle.vue'
|
||||
const { t } = useI18n()
|
||||
const props = defineProps<{
|
||||
hide: () => void;
|
||||
file: File;
|
||||
onFileHandle: ({ type, config }: { type: string; config: any }) => void;
|
||||
}>();
|
||||
hide: () => void
|
||||
file: File[]
|
||||
onFileHandle: ({ type, config }: { type: string; config: any }) => void
|
||||
}>()
|
||||
|
||||
const isImage = computed(() => props.file.type.startsWith("image/"));
|
||||
const isVideo = computed(() => props.file.type.startsWith("video/"));
|
||||
const isAudio = computed(() => props.file.type.startsWith("audio/"));
|
||||
const isMedia = computed(() => isImage.value || isVideo.value || isAudio.value);
|
||||
const isImage = computed(() => props.file.every((r) => r?.type?.startsWith('image/')))
|
||||
const isVideo = computed(() => props.file.every((r) => r?.type?.startsWith('video/')))
|
||||
const isAudio = computed(() => props.file.every((r) => r?.type?.startsWith('audio/')))
|
||||
const isMedia = computed(() => isImage.value || isVideo.value || isAudio.value)
|
||||
|
||||
const isPDF = computed(() => props.file.type.startsWith("application/pdf"));
|
||||
const isDOC = computed(() => props.file.type.startsWith("application/msword"));
|
||||
const isXLS = computed(() =>
|
||||
props.file.type.startsWith("application/vnd.ms-excel"),
|
||||
);
|
||||
const isPPT = computed(() =>
|
||||
props.file.type.startsWith("application/vnd.ms-powerpoint"),
|
||||
);
|
||||
const isDocument = computed(
|
||||
() => isPDF.value || isDOC.value || isXLS.value || isPPT.value,
|
||||
);
|
||||
const isPDF = computed(() => props.file.every((r) => r?.type?.startsWith('application/pdf')))
|
||||
const isDOC = computed(() => props.file.every((r) => r?.type?.startsWith('application/msword')))
|
||||
const isXLS = computed(() => props.file.every((r) => r?.type?.startsWith('application/vnd.ms-excel')))
|
||||
const isPPT = computed(() => props.file.every((r) => r?.type?.startsWith('application/vnd.ms-powerpoint')))
|
||||
const isDocument = computed(() => isPDF.value || isDOC.value || isXLS.value || isPPT.value)
|
||||
const actions = [
|
||||
{
|
||||
label: t("file.handleType.file-share"),
|
||||
icon: LucideShare,
|
||||
className: "bg-green-300",
|
||||
onClick: () => {
|
||||
showDrawer({
|
||||
render: ({ hide }) => h(FileShareHandle, { ...props, hide }),
|
||||
});
|
||||
{
|
||||
label: t('file.handleType.file-share'),
|
||||
icon: LucideShare,
|
||||
className: 'bg-green-300',
|
||||
onClick: () => {
|
||||
showDrawer({
|
||||
render: ({ hide }) => h(FileShareHandle, { ...props, hide }),
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
isImage.value && {
|
||||
label: t("file.handleType.file-image-compress"),
|
||||
icon: LucideImageMinus,
|
||||
className: "bg-red-300",
|
||||
onClick: () => {
|
||||
props.onFileHandle({ type: "file-image-compress", config: {} });
|
||||
isImage.value && {
|
||||
label: t('file.handleType.file-image-compress'),
|
||||
icon: LucideImageMinus,
|
||||
className: 'bg-red-300',
|
||||
onClick: () => {
|
||||
props.onFileHandle({ type: 'file-image-compress', config: {} })
|
||||
},
|
||||
},
|
||||
},
|
||||
// isImage.value && {
|
||||
// label: '图片翻译', icon: LucideLanguages, className: 'bg-orange-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// isImage.value && {
|
||||
// label: '图片超分', icon: LucideImagePlus, className: 'bg-cyan-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// (isAudio.value || isVideo.value) && {
|
||||
// label: '转文本', icon: LucideListMusic, className: 'bg-cyan-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// isAudio.value && {
|
||||
// label: '语音克隆', icon: LucideAudioLines, className: 'bg-cyan-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// (isDocument.value || isMedia.value) && {
|
||||
// label: '格式转换', icon: LucideArrowRightLeft, className: 'bg-purple-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// isImage.value && {
|
||||
// label: '图片翻译', icon: LucideLanguages, className: 'bg-orange-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// isImage.value && {
|
||||
// label: '图片超分', icon: LucideImagePlus, className: 'bg-cyan-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// (isAudio.value || isVideo.value) && {
|
||||
// label: '转文本', icon: LucideListMusic, className: 'bg-cyan-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// isAudio.value && {
|
||||
// label: '语音克隆', icon: LucideAudioLines, className: 'bg-cyan-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
// (isDocument.value || isMedia.value) && {
|
||||
// label: '格式转换', icon: LucideArrowRightLeft, className: 'bg-purple-300', onClick: () => {
|
||||
// console.log('复制链接')
|
||||
// }
|
||||
// },
|
||||
]?.filter(isObject) as {
|
||||
label: string;
|
||||
icon: any;
|
||||
className: string;
|
||||
onClick: () => void;
|
||||
}[];
|
||||
label: string
|
||||
icon: any
|
||||
className: string
|
||||
onClick: () => void
|
||||
}[]
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col gap-5 p-5">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
v-for="item in actions"
|
||||
:key="item.label"
|
||||
class="flex flex-col items-center gap-2 max-w-20"
|
||||
@click="
|
||||
() => {
|
||||
props?.hide();
|
||||
item?.onClick();
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:class="
|
||||
cx(
|
||||
'size-14 flex justify-center items-center rounded-full mx-3',
|
||||
item?.className,
|
||||
)
|
||||
"
|
||||
>
|
||||
<component :is="item?.icon" />
|
||||
<div class="flex flex-col gap-5 p-5">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
v-for="item in actions"
|
||||
:key="item.label"
|
||||
class="flex flex-col items-center gap-2 max-w-20"
|
||||
@click="
|
||||
() => {
|
||||
props?.hide()
|
||||
item?.onClick()
|
||||
}
|
||||
"
|
||||
>
|
||||
<div :class="cx('size-14 flex justify-center items-center rounded-full mx-3', item?.className)">
|
||||
<component :is="item?.icon" />
|
||||
</div>
|
||||
<div class="text-xs truncate w-full text-center">{{ item?.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs truncate w-full text-center">{{ item?.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,42 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import FileUpload from "@/components/FileUpload.vue";
|
||||
import { cx } from "class-variance-authority";
|
||||
import type { RuleExpression } from "vee-validate";
|
||||
import FileUpload from '@/components/FileUpload.vue'
|
||||
import { cx } from 'class-variance-authority'
|
||||
import type { RuleExpression } from 'vee-validate'
|
||||
import Button from '../ui/button/Button.vue'
|
||||
import { PlusIcon } from 'lucide-vue-next'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
rules?: RuleExpression<File>;
|
||||
}>();
|
||||
const { value, setValue } = useField<File>(props?.name, props?.rules);
|
||||
const { t } = useI18n();
|
||||
name: string
|
||||
rules?: RuleExpression<File[]>
|
||||
}>()
|
||||
const { value, setValue } = useField<File[]>(props?.name, props?.rules)
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FileUpload
|
||||
@onChange="
|
||||
(file) => {
|
||||
setValue(file);
|
||||
}
|
||||
"
|
||||
v-slot="{ isOverDropZone }"
|
||||
>
|
||||
<div
|
||||
:class="
|
||||
cx(
|
||||
'bg-white/50 rounded-md p-2 w-full h-40 flex flex-col items-center justify-center border border-dashed border-black/20 cursor-pointer text-gray-500 gap-3',
|
||||
isOverDropZone && '!bg-green-100/50 ',
|
||||
)
|
||||
"
|
||||
<FileUpload
|
||||
@onChange="
|
||||
(file) => {
|
||||
// 这里没hash,我们姑且认为name和size,type都一样的为同一个文件
|
||||
setValue([...(value?.filter((r) => r?.name !== file?.name || r?.type !== file?.type || r?.size !== file?.size) || []), file])
|
||||
}
|
||||
"
|
||||
v-slot="{ isOverDropZone }"
|
||||
>
|
||||
<template v-if="!!value">
|
||||
<FilePreviewView :value="value" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<LucideUpload class="size-10" />
|
||||
<div class="text-sm">
|
||||
{{ t("file.uploadFilePlaceholder") }}
|
||||
<div
|
||||
:class="
|
||||
cx(
|
||||
'bg-white/50 rounded-md p-2 w-full min-h-40 flex flex-col items-center justify-center border border-dashed border-black/20 cursor-pointer text-gray-500 gap-3 transition-all duration-300',
|
||||
isOverDropZone && '!bg-green-100/50 '
|
||||
)
|
||||
"
|
||||
>
|
||||
<template v-if="!isEmpty(value)">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-5 w-full">
|
||||
<div v-for="item in value" :key="item?.name" class="flex flex-col items-center justify-center relative">
|
||||
<div class="absolute top-0 right-0">
|
||||
<Button
|
||||
class="size-5 p-0 bg-red-500/20 hover:bg-red-500/60 text-red-500 hover:text-white"
|
||||
@click="
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
setValue(
|
||||
value?.filter((r) => r?.name !== item?.name || r?.type !== item?.type || r?.size !== item?.size) || []
|
||||
)
|
||||
}
|
||||
"
|
||||
>
|
||||
<LucideX class="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<FilePreviewView :value="item" />
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center opacity-50 gap-1">
|
||||
<div class="size-16 flex justify-center items-center rounded-xl bg-white/80">
|
||||
<PlusIcon class="size-7" />
|
||||
</div>
|
||||
<div class="mb-3">添加更多</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<LucideUpload class="size-10" />
|
||||
<div class="text-sm">
|
||||
{{ t('file.uploadFilePlaceholder') }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</FileUpload>
|
||||
</FileUpload>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import SwitchField from '../Field/SwitchField.vue';
|
||||
import InputField from '../Field/InputField.vue';
|
||||
import SelectField from '../Field/SelectField.vue';
|
||||
import FormButton from '../Field/FormButton.vue';
|
||||
import SwitchField from '../Field/SwitchField.vue'
|
||||
import InputField from '../Field/InputField.vue'
|
||||
import SelectField from '../Field/SelectField.vue'
|
||||
import FormButton from '../Field/FormButton.vue'
|
||||
const props = defineProps<{
|
||||
hide: () => void
|
||||
file: File
|
||||
onFileHandle: ({ type, config }: { type: string, config: any }) => void
|
||||
file: File[]
|
||||
onFileHandle: ({ type, config }: { type: string; config: any }) => void
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -16,51 +15,74 @@ const props = defineProps<{
|
||||
<div class="flex flex-col gap-3">
|
||||
<h2 class="text-lg font-bold">分享选项</h2>
|
||||
<div class="flex flex-row items-center gap-2 text-sm">
|
||||
|
||||
<SelectField name="download_nums" label="下载次数" :options="[
|
||||
{ label: '1次下载', value: 1 },
|
||||
{ label: '2次下载', value: 2 },
|
||||
{ label: '3次下载', value: 3 },
|
||||
{ label: '5次下载', value: 5 },
|
||||
{ label: '10次下载', value: 10 },
|
||||
]" />
|
||||
<SelectField
|
||||
name="download_nums"
|
||||
label="下载次数"
|
||||
:options="[
|
||||
{ label: '1次下载', value: 1 },
|
||||
{ label: '2次下载', value: 2 },
|
||||
{ label: '3次下载', value: 3 },
|
||||
{ label: '5次下载', value: 5 },
|
||||
{ label: '10次下载', value: 10 },
|
||||
]"
|
||||
/>
|
||||
或
|
||||
<SelectField name="expire_time" label="过期时间" :options="[
|
||||
{ label: '5分钟', value: 5 },
|
||||
{ label: '1小时', value: 60 },
|
||||
{ label: '1天', value: 1440 },
|
||||
{ label: '3天', value: 4320 },
|
||||
]" />
|
||||
<SelectField
|
||||
name="expire_time"
|
||||
label="过期时间"
|
||||
:options="[
|
||||
{ label: '5分钟', value: 5 },
|
||||
{ label: '1小时', value: 60 },
|
||||
{ label: '1天', value: 1440 },
|
||||
{ label: '3天', value: 4320 },
|
||||
]"
|
||||
/>
|
||||
后过期
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex flex-row gap-3 min-h-9">
|
||||
<SwitchField name="has_pickup_code" label="取件码" :rules="((value: boolean) => {
|
||||
if (!!value) {
|
||||
setFieldValue('has_password', false)
|
||||
}
|
||||
return true
|
||||
})" />
|
||||
<SwitchField
|
||||
name="has_pickup_code"
|
||||
label="取件码"
|
||||
:rules="
|
||||
(value: boolean) => {
|
||||
if (!!value) {
|
||||
setFieldValue('has_password', false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row gap-3 min-h-9">
|
||||
<SwitchField name="has_password" label="密码保护" :rules="((value: boolean) => {
|
||||
if (!!value) {
|
||||
setFieldValue('has_pickup_code', false)
|
||||
}
|
||||
return true
|
||||
})" />
|
||||
<SwitchField
|
||||
name="has_password"
|
||||
label="密码保护"
|
||||
:rules="
|
||||
(value: boolean) => {
|
||||
if (!!value) {
|
||||
setFieldValue('has_pickup_code', false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
"
|
||||
/>
|
||||
<InputField v-if="!!values.has_password" name="password" placeholder="请输入密码" rules="required" />
|
||||
</div>
|
||||
<div class="flex flex-row gap-3 min-h-9">
|
||||
<SwitchField name="has_notify" label="下载通知" />
|
||||
<InputField v-if="!!values.has_notify" name="notify_email" placeholder="请输入邮箱"
|
||||
rules="required" />
|
||||
<InputField v-if="!!values.has_notify" name="notify_email" placeholder="请输入邮箱" rules="required" />
|
||||
</div>
|
||||
</div>
|
||||
<FormButton @click="async (form) => {
|
||||
onFileHandle({ type: 'file-share', config: values })
|
||||
hide()
|
||||
}">提交</FormButton>
|
||||
<FormButton
|
||||
@click="
|
||||
async (form) => {
|
||||
onFileHandle({ type: 'file-share', config: values })
|
||||
hide()
|
||||
}
|
||||
"
|
||||
>提交</FormButton
|
||||
>
|
||||
</div>
|
||||
</VeeForm>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user