快乐的梦鱼 1 hónapja
szülő
commit
078e18c636

+ 19 - 6
src/common/components/upload/AliOssUploadCo.ts

@@ -39,7 +39,7 @@ function hmacSha1(message: string, key: string) {
   }
 }
 
-function uploadOSS(uploadPath: string, filePath: string, onProgress?: (progress: number) => void) {
+function uploadOSS(uploadPath: string, filePath: string, cancelHandler: { cancel: () => void }, onProgress?: (progress: number) => void) {
   return new Promise<string>((resolve, reject) => {
 
     const key = uploadPath;
@@ -53,7 +53,7 @@ function uploadOSS(uploadPath: string, filePath: string, onProgress?: (progress:
       success_action_status: '200'
     };
 
-    uni.uploadFile({
+    const task = uni.uploadFile({
       url: `https://${client.bucket}.${client.region}.aliyuncs.com`,
       filePath,
       name: 'file',
@@ -65,16 +65,25 @@ function uploadOSS(uploadPath: string, filePath: string, onProgress?: (progress:
           reject(new Error('上传失败' + res.statusCode))
         }
       },
-      fail: reject
-    }).onProgressUpdate(({ progress }) => onProgress?.(progress))
+      fail: reject,
+      complete: () => {}
+    })
+    task.onProgressUpdate(({ progress }) => onProgress?.(progress));
+
+    cancelHandler.cancel = () => {
+      task.abort();
+    };
   })
 }
 
 export function useAliOssUploadCo(subPath: string) {
   return (action: UploaderAction) => {
     const name = StringUtils.path.getFileName(action.item.filePath);
-    const uploadPath = `${subPath}/${name.split('.')[0]}-${RandomUtils.genNonDuplicateID(26)}.${StringUtils.path.getFileExt(name)}`;      
-    uploadOSS(uploadPath, action.item.filePath, (progress) => {
+    const uploadPath = `${subPath}/${name.split('.')[0]}-${RandomUtils.genNonDuplicateID(26)}.${StringUtils.path.getFileExt(name)}`;  
+    const cancelHandler: { cancel: () => void } = {
+      cancel: () => {},
+    };    
+    uploadOSS(uploadPath, action.item.filePath, cancelHandler,(progress) => {
       action.onProgress?.(progress)
     }).then((res) => {  
       action.onFinish?.({
@@ -84,6 +93,10 @@ export function useAliOssUploadCo(subPath: string) {
     }).catch((err) => {
       action.onError?.(err);
     });
+    return () => {
+      //取消上传
+      cancelHandler.cancel();
+    };
   }
 }
 

+ 3 - 0
src/common/components/upload/ImageUploadCo.ts

@@ -14,6 +14,9 @@ export function useImageSimpleUploadCo(additionData?: Record<string, any>) {
       }).catch((err) => {
         action.onError?.(err);
       })
+    return () => {
+      //取消上传.暂不处理
+    };
   }
 }
 

+ 2 - 2
src/components/basic/Cell.vue

@@ -36,10 +36,10 @@
           </FlexRow>
           <FlexCol :innerStyle="leftViewStyle">
             <slot name="title">
-              <Text v-if="title" :innerStyle="{ ...titleStyle, ...textStyle} ">{{ title }}</Text>
+              <Text v-if="title" :innerStyle="{ ...titleStyle, ...textStyle} " :text="title" />
             </slot>
             <slot name="label">
-              <Text v-if="label" :innerStyle="{ ...labelStyle, ...textStyle} ">{{ label }}</Text>
+              <Text v-if="label" :innerStyle="{ ...labelStyle, ...textStyle} " :text="label" />
             </slot>
           </FlexCol>
         </slot>

+ 1 - 1
src/components/dialog/PopupTitle.vue

@@ -66,7 +66,7 @@ defineOptions({
       v-if="closeable === true && closeIcon"
       :size="closeIconSize || theme.resolveThemeSize('PopupCloseIconSize', 25)"
     />
-    <Text>{{ title }}</Text>
+    <Text :text="title" />
     <IconButton 
       v-if="closeable === true && closeIcon"
       :icon="(closeIcon as string) || theme.resolveThemeSize('PopupCloseIconName', 'close')!"

+ 4 - 5
src/components/display/block/TextBlock.vue

@@ -141,7 +141,7 @@ defineProps({
     @click="$emit('click')"
   >
     <slot name="prefix">
-      <Text v-if="prefix" class="nana-text-prefix" v-bind="prefixProps">{{ prefix }}</Text>
+      <Text v-if="prefix" class="nana-text-prefix" v-bind="prefixProps" :text="prefix" />
     </slot>
     <slot>
       <FlexCol v-if="text2" class="nana-text">
@@ -171,12 +171,11 @@ defineProps({
           wrap ? 'wrap' : '',
         ]" 
         :v-bind="textProps"
-      >
-        {{ text }}
-      </Text>
+        :text="text"
+      />
     </slot>
     <slot name="suffix">
-      <Text class="nana-text-suffix" v-bind="suffixProps">{{ suffix }}</Text>
+      <Text class="nana-text-suffix" v-bind="suffixProps" :text="suffix" />
     </slot>
   </Touchable>
 </template>

+ 10 - 0
src/components/dynamic/DynamicForm.ts

@@ -249,6 +249,12 @@ export interface IDynamicFormRef {
    */
   getFormItemControlRef: <T>(key: string) => T;
   /**
+   * 获取指定类型的所有表单项组件的 Ref
+   * @param type 组件类型
+   * @returns 
+   */
+  getFormItemControlRefsByType: <T>(type: string) => T[];
+  /**
    * 触发提交。同 getFormRef().submit() 。
    * @returns 
    */
@@ -314,7 +320,11 @@ export function configDefaultDynamicFormOptions(options: Omit<IDynamicFormOption
 export type IEvaluateCallback = <T>(val: T | IDynamicFormItemCallback<T>) => T;
 export type IDynamicFormMessageCenterCallback = (messageName: string, data: unknown) => void;
 
+export type IDynamicFormWidgetRef = () => unknown;
+
 export interface IDynamicFormMessageCenter {
   addInstance: (name: string, fn: IDynamicFormMessageCenterCallback) => void,
+  addWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => void,
+  removeWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => void,
   removeInstance: (name: string) => void,
 }

+ 22 - 6
src/components/dynamic/DynamicForm.vue

@@ -19,14 +19,15 @@
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, provide, ref, toRef, toRefs, type PropType } from 'vue';
+import { computed, onMounted, provide, ref, shallowRef, toRef, toRefs, type PropType } from 'vue';
 import Form, { type FormInstance } from '../form/Form.vue';
 import { 
   type IDynamicFormOptions, type IDynamicFormItem, type IDynamicFormRef, 
   type IDynamicFormObject, defaultDynamicFormOptions, 
   type IDynamicFormMessageCenter,
   type IDynamicFormMessageCenterCallback,
-  MESSAGE_RELOAD
+  MESSAGE_RELOAD,
+  type IDynamicFormWidgetRef
 } from '.';
 import DynamicFormRoot from './nest/DynamicFormRoot.vue';
 
@@ -72,19 +73,33 @@ provide('rawModel', model);
 provide('globalParams', toRef(props, 'globalParams'));
 provide('finalOptions', finalOptions);
 
+
 const formEditor = ref<FormInstance>();
-const widgetsRefMap = ref(new Map<string,() => unknown>());
+const widgetsRefMap = new Map<string, IDynamicFormWidgetRef>();
+const widgetsRefTypesMap = new Map<string, IDynamicFormWidgetRef[]>();
 const messageCenterMap = new Map<string, IDynamicFormMessageCenterCallback>();
-  
-provide('widgetsRefMap', widgetsRefMap);
+
 provide('messageCenter', {
   addInstance: (name: string, fn: IDynamicFormMessageCenterCallback) => messageCenterMap.set(name, fn),
   removeInstance: (name: string) => messageCenterMap.delete(name),
+  addWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
+    const refs = widgetsRefTypesMap.get(name) || [];
+    refs.push(ref);
+    widgetsRefTypesMap.set(type, refs);
+  },
+  removeWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
+    const refs = widgetsRefTypesMap.get(name) || [];
+    widgetsRefTypesMap.set(type, refs.filter((r) => r !== ref));
+  },
 } as IDynamicFormMessageCenter);
 
 //获取组件引用
 function getFormItemControlRef(key: string) {
-  return widgetsRefMap.value.get(key)?.();
+  return widgetsRefMap.get(key)?.();
+}
+//获取组件引用组
+function getFormItemControlRefsByType(type: string) {
+  return (widgetsRefTypesMap.get(type) || []).map((ref) => ref());
 }
 
 //通过路径访问
@@ -192,6 +207,7 @@ const formRef : IDynamicFormRef = {
       throw new Error('Form instance is not create.');
     return formEditor.value
   },
+  getFormItemControlRefsByType: getFormItemControlRefsByType as any,
   getFormItemControlRef: getFormItemControlRef as any,
   submit() { return this.getFormRef().validate(); },
   validate() { return this.getFormRef().validate(); },

+ 4 - 4
src/components/dynamic/DynamicFormControl.vue

@@ -257,7 +257,7 @@
 
 <script setup lang="ts">
 import { computed, inject, onBeforeUnmount, onMounted, ref, type PropType, type Ref } from 'vue';
-import type { IDynamicFormItem, IDynamicFormItemCallback, IDynamicFormObject, IDynamicFormOptions, IDynamicFormRef } from '.';
+import type { IDynamicFormItem, IDynamicFormItemCallback, IDynamicFormMessageCenter, IDynamicFormObject, IDynamicFormOptions, IDynamicFormRef } from '.';
 import Field from '../form/Field.vue';
 import Stepper from '../form/Stepper.vue';
 import NaPickerField, { type PickerFieldProps } from '../form/PickerField.vue';
@@ -417,7 +417,7 @@ const data = computed<FormCeilProps>(() => {
 })
 
 const itemRef = ref();
-const widgetsRefMap = inject<Ref<Map<string, unknown>>>('widgetsRefMap');
+const messageCenter = inject<IDynamicFormMessageCenter>('messageCenter');
  
 function onValueChanged(v: any) {
   props.item.watch?.(props.model, v, props.rawModel, getComponentRef());
@@ -431,11 +431,11 @@ function getComponentRef() {
 
 onMounted(() => {
   props.item.mounted?.(props.model, props.rawModel, getComponentRef());
-  widgetsRefMap?.value?.set(props.item.name, getComponentRef);
+  messageCenter?.addWidgetRef(props.item.name, props.item.type ?? '', getComponentRef);
 })
 onBeforeUnmount(() => {
   props.item.beforeUnmount?.(props.model, props.rawModel, getComponentRef()); 
-  widgetsRefMap?.value?.delete(props.item.name);
+  messageCenter?.removeWidgetRef(props.item.name, props.item.type ?? '', getComponentRef);
 })
 
 defineOptions({

+ 4 - 2
src/components/feedback/Result.vue

@@ -8,14 +8,16 @@
       :color="themeVars.ResultTitleColor"
       :bold="themeVars.ResultTitleBold"
       align="center"
-    >{{ title }}</Text>
+      :text="title"
+    />
     <Height :size="themeVars.ResultDescriptionMarginTop" />
     <Text
       :fontSize="themeVars.ResultDescriptionFontSize"
       :color="themeVars.ResultDescriptionColor"
       :bold="themeVars.ResultDescriptionBold"
       align="center"
-    >{{ description }}</Text>
+      :text="description"
+    />
     <slot />
   </FlexCol>
 </template>

+ 1 - 1
src/components/feedback/ShareSheetButtons.vue

@@ -14,7 +14,7 @@
       :buttonStyle="itemStyle"
       @click="emit('click', item)"
     >
-      <Text v-if="item.title" :innerStyle="itemTextStyle">{{ item.title }}</Text>
+      <Text v-if="item.title" :innerStyle="itemTextStyle" :text="item.title" />
     </IconButton>
   </FlexRow>
 </template>

+ 1 - 1
src/components/form/Calendar.vue

@@ -35,7 +35,7 @@
           <template #item="{ item }">
             <FlexCol position="relative">
               <FlexRow justify="center">
-                <Text>{{ item.title }}</Text>
+                <Text :text="item.title" />
               </FlexRow>
               <FlexRow innerClass="nana-calendar-days" wrap justify="flex-start">
                 <template

+ 4 - 0
src/components/form/Uploader.ts

@@ -31,6 +31,10 @@ export interface UploaderItem {
    * 当前上传进度,0-100
    */
   progress?: number;
+  /**
+   * 取消上传回调
+   */
+  cancelUpload?: () => void;
 }
 export interface UploaderAction {
   /**

+ 71 - 23
src/components/form/Uploader.vue

@@ -2,11 +2,13 @@
   <FlexCol>
     <Toast ref="toast" />
     <DialogRoot ref="dialog" />
+    <!-- #ifndef MP -->
     <slot 
       name="uploader" 
       :onClick="onUploadPress"
       :items="currentUpladList"
     >
+    <!-- #endif -->
       <FlexView
         v-if="showUpload"
         :direction="props.listType === 'grid' ? 'row' : 'column'"
@@ -17,6 +19,7 @@
           v-for="(item, index) in currentUpladList"
           :key="index"
         >
+          <!-- #ifndef MP -->
           <slot 
             name="uploadItem"
             :index="index"
@@ -32,6 +35,7 @@
             :defaultSource="props.itemDefaultSource"
               
           >
+          <!-- #endif -->
             <UploaderListItem
               :item="item"
               :showDelete="showDelete"
@@ -45,7 +49,9 @@
               @click="() => onItemPress(item)"
               @delete="() => onItemDeletePress(item)"
             />
+          <!-- #ifndef MP -->
           </slot>
+          <!-- #endif -->
         </template>
         <slot v-if="currentUpladList.length < maxUploadCount && !disabled" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
           <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
@@ -57,12 +63,15 @@
       <slot v-else name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
         <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
       </slot>
+      
+    <!-- #ifndef MP -->
     </slot>
+    <!-- #endif -->
   </FlexCol>
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue';
+import { reactive, ref, watch } from 'vue';
 import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
 import type { ToastInstance } from '../feedback/Toast.vue';
 import Toast from '../feedback/Toast.vue';
@@ -72,7 +81,7 @@ import UploaderListItem from './UploaderListItem.vue';
 import FlexView from '../layout/FlexView.vue';
 import FlexCol from '../layout/FlexCol.vue';
 import type { UploaderAction, UploaderItem } from './Uploader';
-import { LogUtils } from '@imengyu/imengyu-utils';
+import { Debounce, LogUtils } from '@imengyu/imengyu-utils';
 import Text from '../basic/Text.vue';
 import { actionSheet } from '../dialog/CommonRoot';
 
@@ -180,7 +189,7 @@ export interface UploaderProps {
    * 上传处理。不提供则无法上传
    * @required true
    */
-  upload: (item: UploaderAction) => void;
+  upload: (item: UploaderAction) => (() => void);
   /**
    * 自定义选择文件组件,你可以调用自己的文件选择器。默认调用 ImagePicker 选择文件.
    */
@@ -288,7 +297,7 @@ const props = withDefaults(defineProps<UploaderProps>(), {
   itemSize: () => propGetThemeVar('UploaderItemSize', { width: 750 / 4 - 15, height: 750 / 4 - 15 }),
 });
 
-const currentUpladList = ref<UploaderItem[]>(props.intitalItems || []);
+const currentUpladList = ref<UploaderItem[]>(props.intitalItems?.concat() || []);
 
 //上传按钮点击
 function onUploadPress() {
@@ -323,7 +332,9 @@ function onUploadPress() {
             size: item.size,
             state: 'notstart',
             isImage,
-          } as UploaderItem
+            message: '',
+            progress: 0,
+          } as UploaderItem;
         }))
       }
       //#ifdef MP
@@ -333,9 +344,11 @@ function onUploadPress() {
           actions: [
             {
               name: '从相册选择',
+              subname: '从相机立即拍摄或者相册中选择照片/视频',
             },
             {
               name: '从微信聊天中选择',
+              subname: '可在录音机或者WPS文档分享给文件传输助手\n然后选择任意文档文件',
             },
           ],
           onSelect(index, name) {
@@ -348,9 +361,11 @@ function onUploadPress() {
               type: props.chooseType || 'all',
               count: props.maxUploadCount - currentUpladList.value.length,
               success: (res) => {
+                LogUtils.printLog(TAG, 'info', 'chooseMessageFile', res);
                 handleFiles(res.tempFiles as { path: string; size: number; }[])
               },
               fail: (e) => {
+                LogUtils.printLog(TAG, 'error', 'chooseMessageFile', e);
                 reject(e);
               }
             });
@@ -389,8 +404,8 @@ function onUploadPress() {
     });
 
   items
-    .then((res) => {
-      if (props.maxFileSize > 0)
+    .then((res) => { 
+      if (props.maxFileSize > 0) {
         res = res.filter((item) => {
           if (item.size && item.size > props.maxFileSize) {
             props.onOverSize?.(item);
@@ -398,11 +413,24 @@ function onUploadPress() {
           }
           return true;
         });
+        if (res.length === 0) {
+          toast.value?.text('您选择的文件过大,请重新选择!');
+          return;
+        }
+      }
+
+      res = res.map((item) => {
+        item.state = 'notstart';
+        item.message = '';
+        item.progress = 0;
+        return reactive(item);
+      });
+
       //添加条目
       currentUpladList.value = props.maxUploadCount > 1 ? currentUpladList.value.concat(res) : res;
       //自动上传
       if (props.uploadWhenAdded)
-        startUploadMulitItem(res);
+        startUploadMulitItem(currentUpladList.value);
     })
     .catch((e) => console.warn('PickImage failed', e));
 }
@@ -450,21 +478,26 @@ function onItemDeletePress(item: UploaderItem) {
     });
 }
 //更新列表条目
-function updateListItem(item: UploaderItem) {/* 
-  currentUpladList.value = ((prev) => {
-    const newList = prev.concat();
-    const index = prev.findIndex((k) => k.filePath === item.filePath);
-    index >= 0 ? newList[index] = { ...item } : newList.push(item);
-    return newList;
-  })(currentUpladList.value); */
+function updateListItem(item: UploaderItem) {
   emit('updateList', currentUpladList.value);
 }
 //删除列表条目
 function deleteListItem(item: UploaderItem) {
   currentUpladList.value = currentUpladList.value.filter((k) => k.filePath !== item.filePath);
+
+  //如果正在上传,先取消上传
+  if (item.state === 'uploading') {
+    item.cancelUpload?.();
+    item.state = 'fail';
+  }
 }
+
+
+
 //开始上传条目
 function startUploadItem(item: UploaderItem) {
+  if (item.state === 'uploading')
+    return;
   return new Promise<void>((resolve, reject) => {
     if (item.state === 'success') {
       resolve();
@@ -472,7 +505,14 @@ function startUploadItem(item: UploaderItem) {
     }
     LogUtils.printLog(TAG, 'message', `调用上传文件 ${item.filePath}`);
 
-    props.upload({
+    const updateProgressDebounce = new Debounce<number>(400, (precent) => {
+      item.state = 'uploading';
+      item.message = precent ? `上传中 ${precent}%` : '上传中...';
+      item.progress = precent;
+      updateListItem(item);
+    });
+
+    item.cancelUpload = props.upload({
       item,
       onError(error) {
         item.state = 'fail';
@@ -482,6 +522,7 @@ function startUploadItem(item: UploaderItem) {
         LogUtils.printLog(TAG, 'error', `上传文件 ${item.filePath} 失败,错误信息:${error}`);
       },
       onFinish(result, message) {
+        updateProgressDebounce.cancel();
         item.state = 'success';
         item.message = message || '上传完成';
         item.progress = 100;
@@ -493,10 +534,7 @@ function startUploadItem(item: UploaderItem) {
         LogUtils.printLog(TAG, 'success', `上传文件 ${item.filePath} 成功,上传路径:${result.uploadedUrl}`);
       },
       onProgress(precent) {
-        item.state = 'uploading';
-        item.message = precent ? `${precent}%` : '上传中...';
-        item.progress = precent;
-        updateListItem(item);
+        updateProgressDebounce.executeWithDelay(200, precent);
       },
       onStart(message) {
         item.state = 'uploading';
@@ -523,11 +561,21 @@ defineExpose<UploaderInstance>({
   startUploadAll() {
     return startUploadMulitItem(currentUpladList.value);
   },
-  startUpload(item) {
-    return startUploadItem(item);
+  async startUpload(item) {
+    return await startUploadItem(item);
   },
   setList(list) {
-    currentUpladList.value = list;
+    const needRemoveItems = [] as string[];
+    for (const item of currentUpladList.value) {
+      if (list.findIndex(k => k.filePath === item.filePath) === -1)
+        needRemoveItems.push(item.filePath);
+    }
+    for (const filePath of needRemoveItems)
+      currentUpladList.value.splice(currentUpladList.value.findIndex(k => k.filePath === filePath), 1);
+    list.forEach(item => {
+      if (currentUpladList.value.findIndex(k => k.filePath === item.filePath) === -1)
+        currentUpladList.value.push(item);
+    });
   },
   getList() {
     return currentUpladList.value;

+ 5 - 6
src/components/form/UploaderListItem.vue

@@ -18,7 +18,6 @@
     center
     @click="emit('click')" 
   >
-
     <template v-if="!isListStyle">
       <IconButton 
         v-if="showDelete"
@@ -32,14 +31,14 @@
         backgroundColor: themeContext.resolveThemeColor('UploaderListItemUploadingBackgroundColor', 'mask.primary'),
       }">
         <ActivityIndicator :color="itemMaskTextColor" :size="loadingSize" />
-        <Text :color="itemMaskTextColor" :style="itemMaskTextStyle">{{item.message}}</Text>
+        <Text :color="itemMaskTextColor" :style="itemMaskTextStyle" :text="item.message" />
       </FlexView>
       <FlexView v-if="item.state === 'fail'" :innerStyle="{
         ...itemMaskStyle,
         backgroundColor: themeContext.resolveThemeColor('UploaderListItemUploadingBackgroundColor', 'mask.danger'),
       }">
         <Icon icon="error" :color="itemMaskTextColor" :size="iconSize" />
-        <Text :color="itemMaskTextColor" :style="itemMaskTextStyle">{{item.message}}</Text>
+        <Text :color="itemMaskTextColor" :style="itemMaskTextStyle" :text="item.message" />
       </FlexView>
     </template>
 
@@ -60,8 +59,8 @@
     <FlexRow v-if="isListStyle" :flex="1" align="center">
       <Width :size="20" />
       <FlexCol :flex="1">
-        <Text :fontSize="26">{{ StringUtils.path.getFileName(item.filePath) }}</Text>
-        <Text :fontSize="22">{{ item.message }}</Text>
+        <Text :fontSize="26" :text="StringUtils.path.getFileName(item.filePath)" />
+        <Text :fontSize="22" :text="item.message" />
         <Height :size="10" /> 
         <Progress :progressColor="selectStyleType(item.state, 'notstart', {
           notstart: 'primary',
@@ -217,7 +216,7 @@ const fileIcon = computed(() => {
     case 'wav': 
     case 'wma': 
     case 'ogg': 
-    case 'flac': 
+    case 'flac':
       return IconAudio;
     case 'default': 
       return IconDefault;

+ 1 - 1
src/components/keyboard/NumberKeyBoardInner.vue

@@ -3,7 +3,7 @@
     <!-- 标题  -->
     <FlexRow v-if="title" :padding="[ 8, 0 ]" justify="space-between" align="center">
       <FlexRow :width="150" />
-      <Text :innerStyle="themeStyles.title.value">{{ title }}</Text>
+      <Text :innerStyle="themeStyles.title.value" :text="title" />
       <FlexRow :width="150">
         <Button
           v-if="showCloseButton"

+ 1 - 1
src/components/keyboard/NumberKeyBoardKey.vue

@@ -8,7 +8,7 @@
     @click="emit('click', action, text)"
   >
     <Icon v-if="icon" :icon="text" :innerStyle="context.keyTextStyle.value" />
-    <Text v-else :innerStyle="action === 'finish' ? context.keyTextStyleFinish.value : context.keyTextStyle.value">{{ text }}</Text>
+    <Text v-else :innerStyle="action === 'finish' ? context.keyTextStyleFinish.value : context.keyTextStyle.value" :text="text" />
   </Touchable>
   <view
     v-else

+ 1 - 1
src/components/keyboard/PlateKeyBoardKey.vue

@@ -8,7 +8,7 @@
     @click="emit('click', text, action)"
   >
     <Icon v-if="icon" :icon="text" :innerStyle="context.keyTextStyle.value" />
-    <Text v-else :innerStyle="context.keyTextStyle.value">{{ text }}</Text>
+    <Text v-else :innerStyle="context.keyTextStyle.value" :text="text" />
   </Touchable>
   <view
     v-else

+ 1 - 1
src/components/nav/Pagination.vue

@@ -10,7 +10,7 @@
       />
     </slot>
     <slot v-if="simple" name="simple" :text="`${currentPage + 1}/${props.pageCount}`"">
-      <text :style="themeStyles.simpleText.value">{{ `${currentPage + 1}/${props.pageCount}` }}</Text>
+      <text :style="themeStyles.simpleText.value">{{ `${currentPage + 1}/${props.pageCount}` }}</text>
     </slot>
     <template v-else>
       <template  v-for="index in items" :key="index">

+ 0 - 1
src/pages/dig/admin/volunteer.vue

@@ -129,7 +129,6 @@ const formDefine : IDynamicFormOptions = {
           label: '头像', name: 'image', type: 'uploader',
           additionalProps: {
             single: true,
-
             maxFileSize: 1024 * 1024 * 10,
             upload: useAliOssUploadCo('xiangyuan/volunteer/images')
           } as UploaderFieldProps,

+ 15 - 1
src/pages/dig/forms/common.vue

@@ -32,7 +32,7 @@ import { getVillageInfoForm, getVillageInfoFormIds, type SingleForm } from './fo
 import { showError } from '@/common/composeabe/ErrorDisplay';
 import { backAndCallOnPageBack } from '@/components/utils/PageAction';
 import { toast } from '@/components/utils/DialogAction';
-import { confirm } from '@/components/dialog/CommonRoot';
+import { alert, confirm } from '@/components/dialog/CommonRoot';
 import { Debounce, ObjectUtils, RequestApiError, waitTimeOut } from '@imengyu/imengyu-utils';
 import VillageInfoApi, { CommonInfoModel } from '@/api/inhert/VillageInfoApi';
 import DynamicForm from '@/components/dynamic/DynamicForm.vue';
@@ -44,6 +44,8 @@ import Height from '@/components/layout/space/Height.vue';
 import XBarSpace from '@/components/layout/space/XBarSpace.vue';
 import type { IDynamicFormOptions, IDynamicFormRef } from '@/components/dynamic';
 import Alert from '@/components/feedback/Alert.vue';
+import type { UploaderInstance } from '@/components/form/Uploader.vue';
+import type { UploaderFieldInstance } from '@/components/form/UploaderField.vue';
 
 const loading = ref(false);
 const subTitle = ref('');
@@ -167,6 +169,18 @@ async function submit() {
   }
   try {
     loading.value = true;
+
+    // 检查是否有上传中的文件
+    const controls = formRef.value.getFormItemControlRefsByType<UploaderFieldInstance>('uploader');
+    if (controls.filter(c => c.getUploaderRef()?.isAnyUploading()).length > 0) {
+      await alert({
+        content: '您有未上传完成的文件,请等待上传完成',
+        icon: 'prompt',
+      });
+      return;
+    }
+
+    // 提交表单数据
     await waitTimeOut(800);
     await VillageInfoApi.updateInfo(
       collectStore.getCollectModuleId(querys.value.subType),

+ 4 - 4
src/pages/dig/forms/data/common.ts

@@ -96,7 +96,7 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             defaultValue: '',
             additionalProps: {
               upload: useAliOssUploadCo('xiangyuan/common'),
-              maxFileSize: 1024 * 1024 * 20,
+              maxFileSize: 1024 * 1024 * 20, // 20MB
               maxUploadCount: 20,
             } as UploaderFieldProps,
             rules: [],
@@ -111,7 +111,7 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             defaultValue: '',
             additionalProps: {
               upload: useAliOssUploadCo('xiangyuan/video'),
-              maxFileSize: 1024 * 1024 * 100,
+              maxFileSize: 1024 * 1024 * 1000, // 1000MB
               single: true,
               chooseType: 'video',
             } as UploaderFieldProps,
@@ -152,7 +152,7 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             defaultValue: '',
             additionalProps: {
               upload: useAliOssUploadCo('xiangyuan/archives'),
-              maxFileSize: 1024 * 1024 * 20,
+              maxFileSize: 1024 * 1024 * 200, // 200MB
               chooseType: '',
               single: true,
             } as UploaderFieldProps,
@@ -168,7 +168,7 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             defaultValue: '',
             additionalProps: {
               upload: useAliOssUploadCo('xiangyuan/annex'),
-              maxFileSize: 1024 * 1024 * 20,
+              maxFileSize: 1024 * 1024 * 1000, // 1000MB
               maxUploadCount: 20,
               chooseType: '',
             } as UploaderFieldProps,

+ 2 - 2
src/pages/dig/forms/data/cultural.ts

@@ -171,11 +171,11 @@ export const villageInfoCulture : GroupForm = {
           additionalProps: {
             upload: useAliOssUploadCo('xiangyuan/cultural/video'),
             chooseType: '',
-            maxFileSize: 1024 * 1024 * 20,
+            maxFileSize: 1024 * 1024 * 1000, // 1000MB
             single: true,
           } as UploaderFieldProps,
           formProps: {
-            extraMessage: '您可以上传已经录制好的口述历史视频/录音,也可以点击下方按钮录制新的音频。但小程序中录音时长最长10分钟,如需更长时间,请使用系统相机拍摄。',
+            extraMessage: '您可以上传已经录制好的口述历史视频/录音,也可以点击下方按钮录制新的音频。但小程序中录音时长最长10分钟,如需更长时间,请使用系统相机或者录音机拍摄。',
           } as FieldProps,
         },
         {

+ 1 - 1
src/pages/dig/forms/data/specker.ts

@@ -112,7 +112,7 @@ export const villageInfoSpeakerForm : SingleForm = [CommonInfoModel, (r) => ({
           defaultValue: '',
           additionalProps: {
             upload: useAliOssUploadCo('xiangyuan/speaker/audio'),
-            maxFileSize: 1024 * 1024 * 50,
+            maxFileSize: 1024 * 1024 * 500, // 500MB
             maxUploadCount: 1,
             accept: 'audio/*',
           } as UploaderFieldProps,