|
|
- <template>
- <view class="l-upload" ref="uploadRef" :style="styles">
- <view class="l-upload__item" v-for="(file, index) in customFiles" :style="[itemStyle]" :key="file.url">
- <!-- #ifndef APP || WEB -->
- <view class="l-upload__item-inner" :style="[innerStyle]">
- <!-- #endif -->
- <slot name="file" :file="file" :index="index">
- <image class="l-upload__image" v-if="file.type == 'image'" :src="file.url" :data-file="file"
- :mode="imageFit" @click="onProofTap(index)" />
- <video class="l-upload__image" v-if="file.type == 'video'" :src="file.url" :data-file="file"
- :autoplay="false" objectFit="contain" @click="onFileClick(index)" />
- <view class="l-upload__progress-mask" v-if="file.status != null && file.status != 'done'" :data-file="file" @click="onFileClick(index)">
- <template v-if="file.status == 'loading'">
- <l-loading class="l-upload__progress-loading" size="24px" color="white" />
- <text class="l-upload__progress-text" v-if="file.percent != null">{{file.percent}}%</text>
- <text class="l-upload__progress-text" v-else>{{loadingText}}</text>
- </template>
- <l-icon v-else class="l-upload__progress-icon"
- :name="file.status == 'reload' ? 'refresh' : 'close-circle'" size="48rpx" aria-hidden />
- <text v-if="file.status == 'reload' || file.status == 'failed'" class="l-upload__progress-text">
- {{file.status == 'reload' ? reloadText : failedText}}
- </text>
- </view>
- <view class="l-upload__delete-btn" aria-role="button" aria-label="删除" :data-index="index"
- @click="onDelete(index)">
- <l-icon name="close" size="16px" color="#fff" />
- </view>
- </slot>
- <!-- #ifndef APP || WEB -->
- </view>
- <!-- #endif -->
- </view>
- <!-- #ifdef APP || WEB -->
- <view class="l-upload__item l-upload__item--add"
- :class="{'l-upload__item--disabled':disabled}"
- v-show="!multiple ? customFiles.length == 0: true"
- :style="[itemStyle, addBgColor!=null ? {background: addBgColor}: '']"
- aria-label="上传"
- @click="onAddTap">
- <slot>
- <l-icon class="l-upload__add-icon" :size="uploadIconSize" :name="uploadIcon" />
- </slot>
- </view>
- <!-- #endif -->
- <!-- #ifndef APP || WEB -->
- <view class="l-upload__item l-upload__item--add"
- :class="{'l-upload__item--disabled':disabled}"
- v-show="!multiple ? customFiles.length == 0: true"
- :style="[itemStyle]"
- aria-label="上传"
- @click="onAddTap">
- <view class="l-upload__item-inner" :style="[innerStyle, addBgColor!=null ? {background: addBgColor}: '']">
- <slot>
- <l-icon class="l-upload__add-icon" :size="uploadIconSize" :name="uploadIcon" />
- </slot>
- </view>
- </view>
- <!-- #endif -->
- </view>
- </template>
- <script lang="uts" setup>
- import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
- import { chooseFiles } from './utils'
- import { UploadProps, UploadFile, ChooseFileOptions } from './type';
-
- defineSlots<{
- file(props : { file : UploadFile, index: number }) : any,
- }>()
- const emits = defineEmits(['fail', 'remove', 'success', 'click', 'add', 'update:modelValue'])
- const props = withDefaults(defineProps<UploadProps>(), {
- imageFit: 'aspectFill',
- disablePreview: false,
- autoUpload: false,
- multiple: true,
- max: 0,
- disabled: false,
- mediaType: 'image',
- sizeType: ['original', 'compressed'],
- sourceType: ['album', 'camera'],
- uploadIcon: 'camera',
- loadingText: '上传中...',
- reloadText: '重新上传',
- failedText: '上传失败',
- mode: 'grid'
- })
-
- const transformFiles = (it : any) : UploadFile => {
- // #ifdef APP-ANDROID
- if(it instanceof UploadFile) {
- return it
- }
- // #endif
-
- // #ifndef APP-ANDROID || APP-IOS
- const file:UTSJSONObject = {...it}
- // #endif
- // #ifdef APP-ANDROID || APP-IOS
- const file = UTSJSONObject.assign({}, it as UTSJSONObject) //it as UTSJSONObject
- // #endif
- return {
- url: file.getString('url') ?? '',
- path: file.getString('path'),
- name: file.getString('name'),
- thumb: file.getString('thumb'),
- size: file.getNumber('size'),
- type: file.getString('type'),
- percent: file.getNumber('percent'),
- status: file.getString('status') ?? 'done',
- } as UploadFile
- }
- const customFiles = ref<UploadFile[]>((props.modelValue ?? props.defaultFiles)?.map(transformFiles) ?? []);
-
- const listWidth = ref(0);
- const uploadRef = ref<UniElement | null>(null);
- const styles = computed(() : Map<string, any>=> {
- const style = new Map<string, any>();
- const gutter = unitConvert(props.gutter ?? 8) / 2 * -1
- style.set('margin-left', `${gutter}px`)
- style.set('margin-right', `${gutter}px`)
- style.set('margin-top', `${gutter}px`)
- return style
- })
- const itemStyle = computed(() : Map<string, any> => {
- const style = new Map<string, any>();
-
- // const gridWidth = unitConvert(props.gridWidth ?? 80)
- const gutter = unitConvert(props.gutter ?? 8) / 2
- let column = props.column ?? 4;
-
- if(props.gridWidth != null || props.column != null) {
- // #ifdef APP || WEB
- const width = listWidth.value / column - gutter * 2.0275;// ios 计算精度导致不均分
- style.set('width', props.gridWidth ?? `${width}px`)
- // #endif
- // #ifndef APP || WEB
- style.set('width', props.gridWidth ?? `${100 / column}%`)
- // #endif
- }
-
- // #ifdef APP || WEB
- if(props.gridHeight != null){
- style.set('height', props.gridHeight!)
- }
- style.set('margin', `${gutter}px`)
- if(props.gridBgColor != null){
- style.set('background', props.gridBgColor!)
- }
- // #endif
- return style
- })
- // #ifndef APP || WEB
- const innerStyle = computed((): Map<string, any> => {
- const style = new Map<string, any>();
-
- const gutter = unitConvert(props.gutter ?? 8) / 2
- style.set('margin', `${gutter}px`)
- if(props.gridBgColor != null){
- style.set('background', props.gridBgColor!)
- }
- if(props.gridHeight != null){
- style.set('height', props.gridHeight!)
- }
- return style
- })
- // #endif
-
- const onFileClick = (index : number) => {
- const file = customFiles.value[index]
- emits('click', { file })
- }
- const onProofTap = (index : number) => {
- onFileClick(index);
- if (props.disablePreview) return
- uni.previewImage({
- urls: customFiles.value.filter((file) : boolean => file.percent != -1).map((file) : string => file.url),
- current: index
- });
- }
- const onDelete = (index : number) => {
- const delFile = customFiles.value[index]
- customFiles.value = customFiles.value.filter((file, i) : boolean => index != i)
- emits('remove', { index, file: delFile })
- }
-
- let last:number// = 0
- const upload = (files: UploadFile[]) => {
- if(!props.autoUpload || props.action == null || props.action!.length < 5) return
- if(props.action == 'uniCloud') {
- let uploadImgs:Promise<UniCloudUploadFileResult>[] = [];
- files.forEach((file, index) =>{
- // props.beforeRead(file).then((res)=>{})
-
- const promise = new Promise<UniCloudUploadFileResult>((resolve, reject) =>{
- uniCloud.uploadFile({
- filePath: file.url,
- cloudPath: file.name!.substring(file.name!.lastIndexOf('.')),
- onUploadProgress: (res)=>{
- file.status = 'loading'
- file.percent = Math.floor(res.loaded / res.total * 100)
- },
- }).then(res=>{
- file.path = res.fileID;
- file.status = 'done'
- resolve(res)
- }).catch(err=>{
- file.status = 'failed'
- reject(err)
- })
- })
- uploadImgs.push(promise as Promise<UniCloudUploadFileResult>)
- })
-
- Promise.all(uploadImgs).then(res =>{
- emits('success', res)
- }).catch(err => {
- emits('fail', err)
- })
- } else {
- let uploadImgs:Promise<UploadFileSuccess>[] = [];
- files.forEach((file, index) =>{
- const promise = new Promise<UploadFileSuccess>((resolve, reject) =>{
- const task = uni.uploadFile({
- url: props.action!,
- filePath: file.url,
- name: file.name,
- formData: props.formData,
- header: props.headers,
- success: (res) => {
- file.status = 'done'
- if(res.statusCode == 200) {
- if(typeof res.data == 'string') {
- try{
- const data = JSON.parse<UTSJSONObject>(res.data);
- const url = data?.getString('url');
- if(url != null) {
- file.path = url
- }
- }catch(e){
- //TODO handle the exception
- }
- }
- }
- resolve(res)
- },
- fail(err) {
- file.status = 'failed'
- reject(err)
- }
- });
- task.onProgressUpdate((res) => {
- file.status = 'loading'
- file.percent = res.progress
- });
- })
- uploadImgs.push(promise as Promise<UploadFileSuccess>)
- })
- Promise.all(uploadImgs).then(res =>{
- emits('success', res)
- }).catch(err => {
- emits('fail', err)
- })
- }
- }
-
- const customLimit = computed(() : number => props.max == 0 ? 20 : props.max - customFiles.value.length)
- const onAddTap = () => {
- if (props.disabled) return;
- chooseFiles({
- mediaType: props.mediaType,
- count: customLimit.value,
- sourceType: props.sourceType,
- sizeType: props.sizeType,
- sizeLimit: props.sizeLimit
- } as ChooseFileOptions).then((files) =>{
- last = customFiles.value.length
- customFiles.value = customFiles.value.concat(files)
- // @ts-ignore
- const _files = customFiles.value.filter((it, i):boolean => i > last - 1)
- upload(_files)
- emits('add', _files)
- })
- }
-
-
-
-
- const stop = watch(customFiles, (v : UploadFile[]) => {
- emits('update:modelValue', v)
- })
-
- const stopValue = watch(():(UTSJSONObject[] | null) => props.modelValue, (v: UTSJSONObject[]|null)=>{
- if(v != null && v.length != customFiles.value.length){
- customFiles.value = v.map(transformFiles)
- }
- })
-
- defineExpose({
- remove: onDelete
- })
-
- // #ifdef APP || WEB
- const resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
- listWidth.value = entries[0].target.getBoundingClientRect().width
- })
-
- onMounted(() => {
- nextTick(() => {
- listWidth.value = uploadRef.value?.getBoundingClientRect().width ?? 0;
- resizeObserver.observe(uploadRef.value!)
- })
- })
- // #endif
-
- onUnmounted(() => {
- stop()
- // #ifdef APP
- resizeObserver.disconnect()
- // #endif
- })
- </script>
- <style lang="scss">
- @import './index';
- </style>
|