|
|
@@ -6,7 +6,7 @@
|
|
|
<slot
|
|
|
name="uploader"
|
|
|
:onClick="onUploadPress"
|
|
|
- :items="finalUploadList"
|
|
|
+ :items="currentUpladList"
|
|
|
>
|
|
|
<!-- #endif -->
|
|
|
<FlexView
|
|
|
@@ -16,45 +16,41 @@
|
|
|
:innerStyle="(props.itemListStyle as ViewStyle)"
|
|
|
>
|
|
|
<template
|
|
|
- v-for="(item, index) in finalUploadList"
|
|
|
- :key="item.isTitle ? `title-${item.filePath}-${index}` : `${item.filePath}-${index}`"
|
|
|
+ v-for="(item, index) in currentUpladList"
|
|
|
+ :key="index"
|
|
|
>
|
|
|
- <Text v-if="item.isTitle" bold :text="item.filePath" />
|
|
|
- <template v-else>
|
|
|
- <!-- #ifndef MP -->
|
|
|
- <slot
|
|
|
- name="uploadItem"
|
|
|
- :index="index"
|
|
|
+ <!-- #ifndef MP -->
|
|
|
+ <slot
|
|
|
+ name="uploadItem"
|
|
|
+ :index="index"
|
|
|
+ :item="item"
|
|
|
+ :onClick="() => onItemPress(item)"
|
|
|
+ :onDeleteClick="() => onItemDeletePress(item)"
|
|
|
+ :style="props.itemStyle"
|
|
|
+ :imageStyle="props.itemImageStyle"
|
|
|
+ :itemMaskStyle="props.itemMaskStyle"
|
|
|
+ :itemMaskTextStyle="props.itemMaskTextStyle"
|
|
|
+ :itemSize="itemSize"
|
|
|
+ :showDelete="showDelete"
|
|
|
+ :defaultSource="props.itemDefaultSource"
|
|
|
+ >
|
|
|
+ <!-- #endif -->
|
|
|
+ <UploaderListItem
|
|
|
:item="item"
|
|
|
- :onClick="() => onItemPress(item)"
|
|
|
- :onDeleteClick="() => onItemDeletePress(item)"
|
|
|
- :style="props.itemStyle"
|
|
|
- :imageStyle="props.itemImageStyle"
|
|
|
- :itemMaskStyle="props.itemMaskStyle"
|
|
|
- :itemMaskTextStyle="props.itemMaskTextStyle"
|
|
|
+ :showDelete="showDelete && !disabled && !readonly"
|
|
|
+ :isListStyle="props.listType === 'list'"
|
|
|
+ :style="itemStyle"
|
|
|
+ :imageStyle="itemImageStyle"
|
|
|
+ :itemMaskStyle="itemMaskStyle"
|
|
|
+ :itemMaskTextStyle="itemMaskTextStyle"
|
|
|
+ :defaultSource="itemDefaultSource"
|
|
|
:itemSize="itemSize"
|
|
|
- :showDelete="showDelete"
|
|
|
- :defaultSource="props.itemDefaultSource"
|
|
|
- >
|
|
|
- <!-- #endif -->
|
|
|
- <UploaderListItem
|
|
|
- :item="item"
|
|
|
- :showDelete="showDelete && !disabled && !readonly"
|
|
|
- :isListStyle="props.listType === 'list'"
|
|
|
- :style="itemStyle"
|
|
|
- :imageStyle="itemImageStyle"
|
|
|
- :itemMaskStyle="itemMaskStyle"
|
|
|
- :itemMaskTextStyle="itemMaskTextStyle"
|
|
|
- :defaultSource="itemDefaultSource"
|
|
|
- :itemSize="itemSize"
|
|
|
- :itemExtraButtons="itemExtraButtons"
|
|
|
- @click="() => onItemPress(item)"
|
|
|
- @delete="() => onItemDeletePress(item)"
|
|
|
- />
|
|
|
- <!-- #ifndef MP -->
|
|
|
- </slot>
|
|
|
- <!-- #endif -->
|
|
|
- </template>
|
|
|
+ @click="() => onItemPress(item)"
|
|
|
+ @delete="() => onItemDeletePress(item)"
|
|
|
+ />
|
|
|
+ <!-- #ifndef MP -->
|
|
|
+ </slot>
|
|
|
+ <!-- #endif -->
|
|
|
</template>
|
|
|
<slot v-if="currentUpladList.length < maxUploadCount && !disabled && !readonly" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
|
|
|
<UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
|
|
|
@@ -74,19 +70,19 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { computed, reactive, ref } from 'vue';
|
|
|
+import { reactive, ref, watch } from 'vue';
|
|
|
import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
|
|
|
-import { Debounce, LogUtils, StringUtils } from '@imengyu/imengyu-utils';
|
|
|
-import { actionSheet } from '../dialog/CommonRoot';
|
|
|
import type { ToastInstance } from '../feedback/Toast.vue';
|
|
|
-import type { UploaderAction, UploaderItem } from './Uploader';
|
|
|
import Toast from '../feedback/Toast.vue';
|
|
|
-import Text from '../basic/Text.vue';
|
|
|
import DialogRoot, { type DialogAlertRoot } from '../dialog/DialogRoot.vue';
|
|
|
import UploaderListAddItem from './UploaderListAddItem.vue';
|
|
|
import UploaderListItem from './UploaderListItem.vue';
|
|
|
import FlexView from '../layout/FlexView.vue';
|
|
|
import FlexCol from '../layout/FlexCol.vue';
|
|
|
+import type { UploaderAction, UploaderItem } from './Uploader';
|
|
|
+import { Debounce, LogUtils } from '@imengyu/imengyu-utils';
|
|
|
+import Text from '../basic/Text.vue';
|
|
|
+import { actionSheet } from '../dialog/CommonRoot';
|
|
|
|
|
|
const themeContext = useTheme();
|
|
|
const TAG = 'Uploader';
|
|
|
@@ -171,14 +167,6 @@ export interface UploaderProps {
|
|
|
*/
|
|
|
itemMaskStyle?: ViewStyle;
|
|
|
/**
|
|
|
- * 条目的自定义额外按钮,仅当listType为list时有效
|
|
|
- * @default []
|
|
|
- */
|
|
|
- itemExtraButtons?: {
|
|
|
- icon: string;
|
|
|
- onClick: (item: UploaderItem) => void;
|
|
|
- }[];
|
|
|
- /**
|
|
|
* 初始列表中的条目
|
|
|
* @default []
|
|
|
*/
|
|
|
@@ -195,11 +183,6 @@ export interface UploaderProps {
|
|
|
* @default true
|
|
|
*/
|
|
|
formMessage?: boolean;
|
|
|
- /**
|
|
|
- * 列表是否按类型分组,仅当listType为list时有效
|
|
|
- * @default false
|
|
|
- */
|
|
|
- groupType?: boolean;
|
|
|
|
|
|
/**
|
|
|
* 上传处理。不提供则无法上传
|
|
|
@@ -302,79 +285,11 @@ const props = withDefaults(defineProps<UploaderProps>(), {
|
|
|
uploadQueueMode: 'all',
|
|
|
listType: 'grid',
|
|
|
chooseType: 'image',
|
|
|
- groupType: false,
|
|
|
itemSize: () => propGetThemeVar('UploaderItemSize', { width: 750 / 4 - 15, height: 750 / 4 - 15 }),
|
|
|
});
|
|
|
|
|
|
const currentUpladList = ref<UploaderItem[]>(props.intitalItems?.concat() || []);
|
|
|
|
|
|
-type UploadDisplayKind = 'image' | 'video' | 'audio' | 'document' | 'other';
|
|
|
-
|
|
|
-const UPLOAD_GROUP_META: { kind: UploadDisplayKind; label: string }[] = [
|
|
|
- { kind: 'image', label: '图片' },
|
|
|
- { kind: 'video', label: '视频' },
|
|
|
- { kind: 'audio', label: '音频' },
|
|
|
- { kind: 'document', label: '文档' },
|
|
|
- { kind: 'other', label: '其他' },
|
|
|
-];
|
|
|
-
|
|
|
-function isImagePath(path: string) {
|
|
|
- return path.match(/\.(jpg|jpeg|png|gif|webp)$/i) !== null;
|
|
|
-}
|
|
|
-
|
|
|
-function getUploadItemDisplayKind(item: UploaderItem): UploadDisplayKind {
|
|
|
- if (item.isTitle)
|
|
|
- return 'other';
|
|
|
- const path = item.previewPath || item.uploadedPath || item.filePath;
|
|
|
- if (item.isImage || isImagePath(path))
|
|
|
- return 'image';
|
|
|
- if (/\.(mp4|m4v|mov|webm|avi|wmv|flv|mkv|3gp|3g2|ts)$/i.test(path))
|
|
|
- return 'video';
|
|
|
- if (/\.(mp3|wav|m4a|aac|ogg|opus|flac|wma|amr|aiff|aif)$/i.test(path))
|
|
|
- return 'audio';
|
|
|
- if (/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|rtf|csv|md)$/i.test(path))
|
|
|
- return 'document';
|
|
|
- return 'other';
|
|
|
-}
|
|
|
-
|
|
|
-function makeUploadTitleItem(label: string): UploaderItem {
|
|
|
- return {
|
|
|
- name: label,
|
|
|
- filePath: label,
|
|
|
- isTitle: true,
|
|
|
- state: 'success',
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-/** 列表模式 + groupType:按文件类型插入分组标题行(isTitle) */
|
|
|
-function buildGroupedUploadList(items: UploaderItem[]): UploaderItem[] {
|
|
|
- const files = items.filter((i) => !i.isTitle);
|
|
|
- if (files.length === 0)
|
|
|
- return [];
|
|
|
- const buckets = new Map<UploadDisplayKind, UploaderItem[]>();
|
|
|
- for (const { kind } of UPLOAD_GROUP_META)
|
|
|
- buckets.set(kind, []);
|
|
|
- for (const item of files) {
|
|
|
- const kind = getUploadItemDisplayKind(item);
|
|
|
- buckets.get(kind)!.push(item);
|
|
|
- }
|
|
|
- const out: UploaderItem[] = [];
|
|
|
- for (const { kind, label } of UPLOAD_GROUP_META) {
|
|
|
- const group = buckets.get(kind)!;
|
|
|
- if (group.length === 0)
|
|
|
- continue;
|
|
|
- out.push(makeUploadTitleItem(label));
|
|
|
- out.push(...group);
|
|
|
- }
|
|
|
- return out;
|
|
|
-}
|
|
|
-
|
|
|
-const finalUploadList = computed(() => {
|
|
|
- if (props.groupType && props.listType === 'list')
|
|
|
- return buildGroupedUploadList(currentUpladList.value);
|
|
|
- return currentUpladList.value;
|
|
|
-});
|
|
|
-
|
|
|
//上传按钮点击
|
|
|
function onUploadPress() {
|
|
|
if (props.disabled || props.readonly)
|
|
|
@@ -391,7 +306,6 @@ function onUploadPress() {
|
|
|
function handleFiles(res: {
|
|
|
path: string;
|
|
|
size: number;
|
|
|
- name: string;
|
|
|
}[]) {
|
|
|
resolve(res.map((item) => {
|
|
|
let isImage = typeof (item as any).type === 'string' ? (item as any).type.startsWith('image/') : false;
|
|
|
@@ -399,7 +313,6 @@ function onUploadPress() {
|
|
|
isImage = isImagePath(item.path);
|
|
|
}
|
|
|
return {
|
|
|
- name: item.name,
|
|
|
filePath: item.path,
|
|
|
previewPath: item.path,
|
|
|
size: item.size,
|
|
|
@@ -432,11 +345,11 @@ function onUploadPress() {
|
|
|
chooseLocal();
|
|
|
} else if (index === 1) {
|
|
|
uni.chooseMessageFile({
|
|
|
- type: props.chooseType === 'file' ? 'all' :(props.chooseType || 'all'),
|
|
|
+ type: props.chooseType || 'all',
|
|
|
count: props.maxUploadCount - currentUpladList.value.length,
|
|
|
success: (res) => {
|
|
|
LogUtils.printLog(TAG, 'info', 'chooseMessageFile', res);
|
|
|
- handleFiles(res.tempFiles as { path: string; name: string; size: number; }[])
|
|
|
+ handleFiles(res.tempFiles as { path: string; size: number; }[])
|
|
|
},
|
|
|
fail: (e) => {
|
|
|
LogUtils.printLog(TAG, 'error', 'chooseMessageFile', e);
|
|
|
@@ -459,29 +372,18 @@ function onUploadPress() {
|
|
|
uni.chooseVideo().then((res) => handleFiles([
|
|
|
{
|
|
|
path: res.tempFilePath,
|
|
|
- name: res.name || StringUtils.path.getFileName(res.tempFilePath) || '',
|
|
|
size: res.size,
|
|
|
}
|
|
|
])).catch(reject);
|
|
|
break;
|
|
|
- //#ifdef H5
|
|
|
case 'file':
|
|
|
- uni.chooseFile().then((res) => handleFiles((res.tempFiles as any []).map((item) => ({
|
|
|
- path: item.path,
|
|
|
- name: item.name || StringUtils.path.getFileName(item.path) || '',
|
|
|
- size: item.size,
|
|
|
- })))).catch(reject);
|
|
|
+ uni.chooseFile().then((res) => handleFiles(res.tempFiles as { path: string; size: number; }[])).catch(reject);
|
|
|
break;
|
|
|
- //#endif
|
|
|
default:
|
|
|
case 'image':
|
|
|
uni.chooseImage({
|
|
|
count: props.maxUploadCount - currentUpladList.value.length,
|
|
|
- }).then((res) => handleFiles((res.tempFiles as any[]).map((item) => ({
|
|
|
- path: item.path,
|
|
|
- name: item.name || StringUtils.path.getFileName(item.path) || '',
|
|
|
- size: item.size,
|
|
|
- })))).catch(reject);
|
|
|
+ }).then((res) => handleFiles(res.tempFiles as { path: string; size: number; }[])).catch(reject);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
@@ -585,16 +487,11 @@ function deleteListItem(item: UploaderItem) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function formatError(error: unknown) {
|
|
|
- if (error instanceof Error)
|
|
|
- return error.message;
|
|
|
- if (typeof error === 'string')
|
|
|
- return error;
|
|
|
- if (typeof error === 'object')
|
|
|
- return JSON.stringify(error);
|
|
|
- return '' + error;
|
|
|
+function isImagePath(path: string) {
|
|
|
+ return path.match(/\.(jpg|jpeg|png|gif|webp)$/) !== null;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
//开始上传条目
|
|
|
function startUploadItem(item: UploaderItem) {
|
|
|
if (item.state === 'uploading')
|
|
|
@@ -617,7 +514,7 @@ function startUploadItem(item: UploaderItem) {
|
|
|
item,
|
|
|
onError(error) {
|
|
|
item.state = 'fail';
|
|
|
- item.message = formatError(error) || '上传失败';
|
|
|
+ item.message = ('' + error) || '上传失败';
|
|
|
updateListItem(item);
|
|
|
reject(error);
|
|
|
LogUtils.printLog(TAG, 'error', `上传文件 ${item.filePath} 失败,错误信息:${error}`);
|