快乐的梦鱼 3 settimane fa
parent
commit
b896a15a2f

+ 1 - 0
src/components/basic/Cell.vue

@@ -7,6 +7,7 @@
       ...viewStyle,
       ...style,
     }"
+    :disabledOpacity="1"
     :setCursor="false"
     :flex="1" 
     :align="center ? 'center' : 'flex-start'"

+ 15 - 11
src/components/basic/IconUtils.ts

@@ -6,7 +6,7 @@ export type IconItem = {
   rawSvg?: string,
   fontFamily?: string,
 };
-type IconMap = Record<string, IconItem>;
+export type IconMap = Record<string, IconItem>;
 
 
 //图标集
@@ -80,21 +80,25 @@ export const IconUtils = {
    * 加载默认图标。可选从本地或者网络加载。
    * @param urlOrJson 图标URL或者JSON字符串
    */
-  loadDefaultIcons(urlOrJson: string | Record<string, string>) {
+  async loadDefaultIcons(urlOrJson: string | Record<string, string>) : Promise<void> {
     if (typeof urlOrJson === 'object') {
       this.configIconMap(urlOrJson);
       return;
     }
     if (urlOrJson.startsWith('http') || urlOrJson.startsWith('https')) {
-      uni.request({
-        url: urlOrJson,
-        method: 'GET',
-        success: (res) => {
-          this.configIconMap(typeof res.data === 'string' ? JSON.parse(res.data) : res.data);
-        },
-        fail: (err) => {
-          console.error('加载默认图标失败', err);
-        },
+      return await new Promise((resolve, reject) => {
+        uni.request({
+          url: urlOrJson,
+          method: 'GET',
+          success: (res) => {
+            this.configIconMap(typeof res.data === 'string' ? JSON.parse(res.data) : res.data);
+            resolve();
+          },
+          fail: (err) => {
+            console.error('加载默认图标失败', err);
+            reject(err);
+          },
+        });
       });
     } else {
       this.configIconMap(JSON.parse(urlOrJson));

+ 16 - 1
src/components/basic/Text.vue

@@ -1,5 +1,8 @@
 <template>
-  <text :id="id" :class="innerClass" :style="style" @click="onClick">
+  <view v-if="allowChildNode" :id="id" :class="innerClass" :style="style"  @click="onClick">
+    <slot>{{ text }}</slot>
+  </view>
+  <text v-else :id="id" :class="innerClass" :style="style" @click="onClick">
     <!-- #ifdef APP-NVUE -->
     {{ text }}
     <!-- #endif -->
@@ -20,6 +23,11 @@ export interface TextProps {
    */
   color?: string,
   /**
+   * 是否允许子节点。如果为 true,则使用view包裹。
+   * @default false
+   */
+  allowChildNode?: boolean,
+  /**
    * 文字阴影颜色。可以是颜色字符串或者在主题中配置的预设名称。
    */
   shadowColor?: string,
@@ -76,6 +84,11 @@ export interface TextProps {
    */
   selectable?: boolean,
   /**
+   * word-break
+   * @default undefined
+   */
+  wordBreak?: 'normal'|'break-all'|'keep-all'|'break-word'|undefined;
+  /**
    * 是否允许换行
    * @default true
    */
@@ -208,6 +221,8 @@ const style = computed(() => {
     o.textDecoration += ' line-through';
   if (props.maxWidth)
     o.maxWidth = resolveThemeSize(props.maxWidth);
+  if (props.wordBreak)
+    o.wordBreak = props.wordBreak;
 
   if (props.shadow && props.shadowColor) {
     o.textShadow = '1px 1px 2px ' + resolveThemeColor(props.shadowColor, 'text');

+ 49 - 36
src/components/dialog/ActionSheet.vue

@@ -5,44 +5,46 @@
     :closeIcon="false"
     :position="center ? 'center' : 'bottom'"
     :size="center ? themeContext.resolveThemeSize(centerWidth) : 'auto'"
-    round
+    backgroundColor="transparent"
     @close="onCancelClick"
   >
-    <scroll-view 
-      :scroll-y="true"
-      :style="{
-        ...themeStyles.topScroll.value,
-        width: center ? themeContext.resolveThemeSize(centerWidth) : undefined,
-      }"
-    >
-      <slot name="content" :close="onCancelClick">
-        <FlexCol>
+    <slot name="content" :close="onCancelClick">
+      <FlexCol :padding="innerPadding">
+        <FlexCol :innerStyle="themeStyles.topView.value">
           <ActionSheetTitle :title="title" :description="description" />
-          <FlexCol position="relative">
-            <ActionSheetItem
-              v-for="(item, index) in props.actions"
-              :key="item.name"
-              :name="item.name"
-              :bold="item.bold || themeContext.getVar('ActionSheetItemBold', false)"
-              :color="themeContext.resolveThemeColor(item.color || props.textColor || themeContext.resolveThemeColor('ActionSheetItemColor', 'text.content'))"
-              :subname="item.subname"
-              :disabled="item.disabled"
-              @click="onItemClick(item, index)"
-            />
-          </FlexCol>
-          <FlexCol v-if="showCancel" position="relative" :innerStyle="themeStyles.viewCancel.value">
-            <ActionSheetItem
-              :name="props.cancelText || '取消'"
-              :bold="themeContext.getVar('ActionSheetCancelBold', false)"
-              :color="themeContext.resolveThemeColor(props.textColor || themeContext.resolveThemeColor('ActionSheetCancelColor', 'text.content'))"
-              :subname="''"
-              :disabled="false"
-              @click="onCancelClick"
-            />
-          </FlexCol>
+          <scroll-view 
+            :scroll-y="true"
+            :style="{
+              ...themeStyles.topScroll.value,
+              width: center ? themeContext.resolveThemeSize(centerWidth) : undefined,
+            }"
+          >
+            <FlexCol position="relative">
+              <ActionSheetItem
+                v-for="(item, index) in props.actions"
+                :key="item.name"
+                :name="item.name"
+                :bold="item.bold || themeContext.getVar('ActionSheetItemBold', false)"
+                :color="themeContext.resolveThemeColor(item.color || props.textColor || themeContext.resolveThemeColor('ActionSheetItemColor', 'text.content'))"
+                :subname="item.subname"
+                :disabled="item.disabled"
+                @click="onItemClick(item, index)"
+              />
+            </FlexCol>
+          </scroll-view>
         </FlexCol>
-      </slot>
-    </scroll-view>
+        <FlexCol v-if="showCancel" position="relative" :innerStyle="themeStyles.viewCancel.value">
+          <ActionSheetItem
+            :name="props.cancelText || '取消'"
+            :bold="themeContext.getVar('ActionSheetCancelBold', false)"
+            :color="themeContext.resolveThemeColor(props.textColor || themeContext.resolveThemeColor('ActionSheetCancelColor', 'text.content'))"
+            :subname="''"
+            :disabled="false"
+            @click="onCancelClick"
+          />
+        </FlexCol>
+      </FlexCol>
+    </slot>
   </Popup>
 </template>
 
@@ -54,6 +56,7 @@ import ActionSheetTitle from './ActionSheetTitle.vue';
 import FlexCol from '../layout/FlexCol.vue';
 import Popup from './Popup.vue';
 import type { PopupProps } from './Popup.vue';
+import { computed } from 'vue';
 
 export interface ActionSheetProps extends Omit<PopupProps, 'onClose'|'position'|'closeable'|'position'|'size'> {
   /**
@@ -130,15 +133,25 @@ const props = withDefaults(defineProps<ActionSheetProps>(), {
   safeArea: true,
   centerWidth: () => propGetThemeVar('ActionSheetCenterWidth', '600rpx'),
 });
+
+const innerPadding = computed(() => themeContext.getVar('ActionSheetInnerPadding', 20));
+
 const themeContext = useTheme();
 const themeStyles = themeContext.useThemeStyles({
   viewCancel: {
-    backgroundColor: DynamicColor('ActionSheetCancelBackgroundColor', 'light'),
-    paddingTop: DynamicSize('ActionSheetCancelPaddingTop', 20),
+    backgroundColor: DynamicColor('ActionSheetCancelBackgroundColor', 'white'),
+    borderRadius: DynamicSize('ActionSheetCancelBorderRadius', 22),
+    marginTop: DynamicSize('ActionSheetCancelMarginTop', 20),
+    overflow: 'hidden',
   },
   topScroll: {
     maxHeight: DynamicSize('ActionSheetMaxScrollHeight', (screenHeight - 200) + 'px'),
   },
+  topView: {
+    backgroundColor: DynamicColor('ActionSheetCancelBackgroundColor', 'white'),
+    borderRadius: DynamicSize('ActionSheetCancelBorderRadius', 22),
+    overflow: 'hidden',
+  },
 });
 
 function onItemClick(item: ActionSheetItem, index: number) {

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

@@ -115,7 +115,7 @@
           :top="true"
           @close="doClose"
         />
-        <slot />
+        <slot :show="show" />
         <PopupTitle
           v-if="position === 'top'"
           :closeable="closeable"

+ 1 - 0
src/components/display/Card.vue

@@ -104,6 +104,7 @@ const hasTitle = computed(() => props.title || props.desc || props.extra);
     position="relative"
     direction="column"
     :backgroundColor="backgroundColor"
+    :disabledOpacity="1"
     :radius="radius"
     shadow="default"
     overflow="hidden"

+ 1 - 0
src/components/display/NoticeBar.vue

@@ -9,6 +9,7 @@
       ...innerStyle,
       backgroundColor: themeContext.resolveThemeColor(backgroundColor),
     }"
+    :disabledOpacity="1"
     @click="emit('click')"
   >
     <slot name="leftIcon">

+ 1 - 0
src/components/display/Tag.vue

@@ -7,6 +7,7 @@
       ...style,
       ...innerStyle,
     }"
+    :disabledOpacity="1"
     :touchable="touchable"
     @click="emit('click')"
   >

+ 1 - 0
src/components/display/block/TextBlock.vue

@@ -134,6 +134,7 @@ defineProps({
 <template>
   <Touchable
     :touchable="touchable"
+    :disabledOpacity="1"
     justify="space-between"
     align="center"
     direction="row"

+ 1 - 0
src/components/display/block/TextLeftRightBlock.vue

@@ -101,6 +101,7 @@ defineProps({
 <template>
   <Touchable
     :touchable="touchable"
+    :disabledOpacity="1"
     :setCursor="false"
     justify="space-between"
     align="flex-start"

+ 11 - 0
src/components/dynamic/DynamicFormControl.vue

@@ -241,6 +241,16 @@
           v-bind="params"
         />
       </template>
+      <template v-else-if="item.type === 'sign'">
+        <view>
+          <SignatureField
+            ref="itemRef"
+            :modelValue="model"
+            v-bind="params"
+            @update:modelValue="(e: any) => onValueChanged(e)"
+          />
+        </view>
+      </template>
       <ComponentRender v-else
         ref="itemRef"
         :modelValue="model"
@@ -286,6 +296,7 @@ import PickerAddressField from './wrappers/PickerAddressField.vue';
 import Button from '../basic/Button.vue';
 import Alert from '../feedback/Alert.vue';
 import Image from '../basic/Image.vue';
+import SignatureField from '../form/SignatureField.vue';
 import CheckBoxTreeList, { type CheckBoxTreeListProps } from './wrappers/CheckBoxTreeList.vue';
 import { useInjectFormContext, useInjectFormItemContext } from '../form/FormContext';
 

+ 28 - 16
src/components/dynamic/nest/DynamicFormItemContainer.vue

@@ -38,9 +38,9 @@
         </template>
       </DynamicFormItemNormal>
       <!--循环子条目-->
-      <DynamicFormItemContainerFuckMp 
+      <DynamicFormItemContainer 
         v-for="(child, k) in item.children"
-        :key="k"
+        :key="child.name"
         :item="child"
         :name="name+'.'+child.name"
         :rawModel="rawModel"
@@ -52,7 +52,11 @@
         :isLast="k === (item.children?.length || 0) - 1"
         @update:model="(v: unknown) => (model as IDynamicFormObject)[child.name] = v"
         :disabled="disabled || evaluateCallback(item.disabled)"
-      />
+      >
+        <template #formCeil="values">
+          <slot name="formCeil" :data="values.data" />
+        </template>
+      </DynamicFormItemContainer>
     </DynamicFormCheckEmpty>
     <!--对象组-->
     <DynamicFormCheckEmpty 
@@ -66,9 +70,9 @@
       <FormGroup :title="evaluateCallback(item.label)" v-bind="(item.additionalProps as object)">
         <Row v-bind="item.rowProps">
           <!--循环子条目-->
-          <DynamicFormItemContainerFuckMp 
+          <DynamicFormItemContainer 
             v-for="(child, k) in item.children" 
-            :key="k"
+            :key="child.name"
             :item="child"
             :colProps="{ ...item.childrenColProps, ...child.colProps }"
             :name="name+'.'+child.name"
@@ -81,7 +85,11 @@
             :isLast="k === (item.children?.length || 0) - 1"
             @update:model="(v: unknown) => (model as IDynamicFormObject)[child.name] = v"
             :disabled="disabled || evaluateCallback(item.disabled)"
-          />
+          >
+            <template #formCeil="values">
+              <slot name="formCeil" :data="values.data" />
+            </template>
+          </DynamicFormItemContainer>
         </Row>
       </FormGroup>
     </DynamicFormCheckEmpty>
@@ -89,10 +97,10 @@
     <FormGroup v-else-if="item.type === 'flat-group'" :title="evaluateCallback(item.label)" v-bind="(item.additionalProps as object)">
       <Row v-bind="item.rowProps">
         <!--循环子条目-->
-        <DynamicFormItemContainerFuckMp 
+        <DynamicFormItemContainer 
           v-for="(child, k) in item.children" 
           :colProps="{ ...item.childrenColProps, ...child.colProps }"
-          :key="k"
+          :key="child.name"
           :item="child"
           :name="parentName ? `${parentName}.${child.name}` : child.name"
           :rawModel="rawModel"
@@ -108,7 +116,7 @@
           <template #formCeil="values">
             <slot name="formCeil" :data="values.data" />
           </template>
-        </DynamicFormItemContainerFuckMp>
+        </DynamicFormItemContainer>
       </Row>
     </FormGroup>
     <!--扁平普通-->
@@ -125,9 +133,9 @@
       <template #insertion>
         <Row v-bind="item.rowProps">
           <!--循环子条目-->
-          <DynamicFormItemContainerFuckMp 
+          <DynamicFormItemContainer 
             v-for="(child, k) in item.children" 
-            :key="k"
+            :key="child.name"
             :item="child"
             :colProps="{ ...item.childrenColProps, ...child.colProps }"
             :name="parentName ? `${parentName}.${child.name}` : child.name"
@@ -144,7 +152,7 @@
             <template #formCeil="values">
               <slot name="formCeil" :data="values.data" />
             </template>
-          </DynamicFormItemContainerFuckMp>
+          </DynamicFormItemContainer>
         </Row>
       </template>
     </DynamicFormItemNormal>
@@ -190,7 +198,7 @@
               />
             </template>
             <template #child="{ item, pitem, kname, model: child, onUpdateValue, isFirst, isLast }">
-              <DynamicFormItemContainerFuckMp
+              <DynamicFormItemContainer
                 :item="item"
                 :name="kname"
                 :rawModel="rawModel"
@@ -252,7 +260,7 @@
               />
             </template>
             <template #child="{ item, pitem, kname, model: child, onUpdateValue, isFirst, isLast }">
-              <DynamicFormItemContainerFuckMp
+              <DynamicFormItemContainer
                 :item="item"
                 :name="kname"
                 :rawModel="rawModel"
@@ -264,7 +272,11 @@
                 :isLast="isLast"
                 :disabled="disabled || evaluateCallback(item.disabled)"
                 @update:model="(v: unknown) => onUpdateValue(v)"
-              />
+              >
+                <template #formCeil="values">
+                  <slot name="formCeil" :data="values.data" />
+                </template>
+              </DynamicFormItemContainer>
             </template>
           </FormArrayGroup>
         </template>
@@ -301,7 +313,7 @@ import FormArrayGroup from '../group/FormArrayGroup.vue';;
 import Col, { type ColProps } from '@/components/layout/grid/Col.vue';
 import Row from '@/components/layout/grid/Row.vue';
 import DynamicFormCheckEmpty from './DynamicFormCheckEmpty.vue';
-import DynamicFormItemContainerFuckMp from './DynamicFormItemContainerFuckMp.vue';
+import DynamicFormItemContainer from './DynamicFormItemContainer.vue';
 
 /**
  * 动态表单条目包装组件,处理基础类型分支、数据传入、回调处理、事件传递。

+ 0 - 19
src/components/dynamic/nest/DynamicFormItemContainerFuckMp.vue

@@ -1,19 +0,0 @@
-<script setup lang="ts">
-import DynamicFormItemContainer, { type DynamicFormItemContainerProps } from './DynamicFormItemContainer.vue';
-
-defineProps<DynamicFormItemContainerProps>()
-defineEmits(['update:model'])
-defineOptions({
-  options: {
-    virtualHost: true,
-  }
-})
-</script>
-
-<template>
-  <DynamicFormItemContainer v-bind="($props as any)" @update:model="(val) => $emit('update:model', val)">
-    <template #formCeil="values">
-      <slot name="formCeil" :data="values.data" />
-    </template>
-  </DynamicFormItemContainer>
-</template>

+ 1 - 0
src/components/feedback/Alert.vue

@@ -1,6 +1,7 @@
 <template>
   <Touchable 
     direction="row"
+    :disabledOpacity="1"
     :innerClass="['nana-alert', `nana-alert-${type}`]"
     :innerStyle="{
       ...themeStyles.container.value,

+ 7 - 1
src/components/feedback/BubbleBox.vue

@@ -11,7 +11,7 @@
     <slot v-else />
     <SimpleTransition name="bubble-box" :show="showState" :duration="200">
       <template #show="{ classNames }">
-        <view class="nana-bubble-box-popup-mask" @click="hide" />
+        <view v-if="clickOutSideClose" class="nana-bubble-box-popup-mask" @click="hide" />
         <view class="nana-bubble-box-holder-position" @click="hideAndEmitClickOnHolder" />
         <FlexView
           position="absolute"
@@ -166,6 +166,11 @@ export interface BubbleBoxProps {
    */
   arrowOffsetY?: number|string,
   /**
+   * 是否允许点击外部自动关闭
+   * @default true
+   */
+  clickOutSideClose?: boolean,
+  /**
    * 气泡框圆角半径
    * @default 12
    */
@@ -188,6 +193,7 @@ const props = withDefaults(defineProps<BubbleBoxProps>(), {
   position: 'top',
   trigger: 'click',
   direction: 'column',
+  clickOutSideClose: true,
   arrowWidth: () => propGetThemeVar('BubbleBoxArrowWidth', 12),
   items: () => [],
   itemTextColor: () => propGetThemeVar('BubbleBoxItemTextColor', 'text.content'),

+ 3 - 2
src/components/feedback/BubbleTip.vue

@@ -1,6 +1,6 @@
 <template>
   <BubbleBox 
-    ref="followBubbleBoxRef" 
+    ref="followBubbleBoxRef"
     v-bind="props"
     @clickOnHolder="emit('contentClick');hideTip()"
   >
@@ -21,7 +21,7 @@ import FlexRow from '../layout/FlexRow.vue';
 import IconButton, { type IconButtonProps } from '../basic/IconButton.vue';
 import { onMounted, ref, watch } from 'vue';
 
-export interface BubbleTiProps extends BubbleBoxProps {
+export interface BubbleTiProps extends Partial<BubbleBoxProps> {
   show?: boolean;
   content?: string;
   contentTextProps?: TextProps;
@@ -42,6 +42,7 @@ const props = withDefaults(defineProps<BubbleTiProps>(), {
   }),
   backgroundColor: 'rgba(0,0,0,0.9)',
   radius: 10,
+  clickOutSideClose: false
 });
 
 const emit = defineEmits(['contentClick', 'close', 'update:show']);

+ 4 - 2
src/components/form/Field.vue

@@ -1,6 +1,7 @@
 <template>
   <Touchable
     :touchable="touchable || childOnClickListener !== undefined"
+    :disabledOpacity="1"
     :pressedColor="themeContext.resolveThemeColor('FieldPressedColor', 'pressed.white')"
     :innerStyle="{ 
       ...themeStyles.field.value, 
@@ -11,7 +12,6 @@
       ...(error || finalErrorMessage ? errorFieldStyle : {})
     }"
     :setCursor="false"
-    :disabledOpacity="1"
     :direction="labelPosition === 'top' ? 'column' : 'row'"
     :justify="labelPosition === 'top' ? 'flex-start' : 'center'"
     @click="onClick"
@@ -58,7 +58,8 @@
               v-for="(tag, index) in tagSplited"
               :key="index"
               :text="tag"
-              color="primary"
+              scheme="light"
+              type="primary"
               size="small"
               closeable
               @close="onTagDelete(tag)"
@@ -648,6 +649,7 @@ const themeStyles = themeContext.useThemeStyles({
     flex: 1,
     width: 'auto',
     minWidth: '100rpx',
+    fontSize: DynamicSize('FieldFontSize', 28),
     paddingVertical: DynamicSize('FieldInputPaddingVertical', 0),
     paddingHorizontal: DynamicSize('FieldInputPaddingHorizontal', 0),
   },

+ 25 - 5
src/components/form/NumberInput.vue

@@ -20,7 +20,9 @@
         :borderWidth="borderWidth"
         :borderType="borderType"
         :borderColor="borderColor"
+        :backgroundColor="backgroundColor"
         :activeBorderColor="activeBorderColor"
+        :activeBackgroundColor="activeBackgroundColor"
         @click="onBoxClicked(i - 1)"
       />
     </view>
@@ -54,7 +56,13 @@ import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../th
 import { DynamicColor, DynamicSize } from '../theme/ThemeTools';
 import FlexCol from '../layout/FlexCol.vue';
 
-export type NumberInputBorderType = 'underline'|'box';
+/**
+ * * underline 下划线
+ * * box 外边框样式
+ * * flat 仅有背景颜色样式
+ * * activeBorderBox 外边框样式(激活状态)和背景颜色样式
+ */
+export type NumberInputBorderType = 'underline'|'box'|'flat'|'activeBorderBox';
 export interface NumberInputProps {
   /**
    * 数值
@@ -99,8 +107,8 @@ export interface NumberInputProps {
    */
   autoSize?: boolean;
   /**
-   * 格子的边框,默认box
-   * @default 'box'
+   * 格子的边框
+   * @default 'activeBorderBox'
    */
   borderType?: NumberInputBorderType;
   /**
@@ -109,6 +117,11 @@ export interface NumberInputProps {
    */
   borderColor?: string;
   /**
+   * 格子的背景颜色
+   * @default 'background.input'
+   */
+  backgroundColor?: string;
+  /**
    * 格子的边框宽度
    * @default 1.5
    */
@@ -119,6 +132,11 @@ export interface NumberInputProps {
    */
   activeBorderColor?: string;
   /**
+   * 已输入格子的背景颜色
+   * @default 'background.primary'
+   */
+  activeBackgroundColor?: string;
+  /**
    * 格子样式
    */
   boxStyle?: ViewStyle;
@@ -166,11 +184,13 @@ const props = withDefaults(defineProps<NumberInputProps>(), {
   startFocus: false,
   finishHideKeyPad: true,
   showCursur: true,
-  borderType: () => propGetThemeVar('NumberInputBorderType', 'box'),
+  borderType: () => propGetThemeVar('NumberInputBorderType', 'activeBorderBox'),
   borderWidth: () => propGetThemeVar('NumberInputBorderWidth', 4),
-  gutter: () => propGetThemeVar('NumberInputGutter', 4),
   borderColor: () => propGetThemeVar('NumberInputBorderColor', 'border.default'),
+  backgroundColor: () => propGetThemeVar('NumberInputBackgroundColor', 'background.input'),
+  gutter: () => propGetThemeVar('NumberInputGutter', 4),
   activeBorderColor: () => propGetThemeVar('NumberInputActiveBorderColor', 'primary'),
+  activeBackgroundColor: () => propGetThemeVar('NumberInputActiveBackgroundColor', 'background.primary'),
 });
 
 const themeContext = useTheme();

+ 13 - 1
src/components/form/NumberInputBox.vue

@@ -32,7 +32,6 @@
 import { computed } from 'vue';
 import { useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
 import { DynamicColor, DynamicSize, selectStyleType } from '../theme/ThemeTools';
-import FlexCol from '../layout/FlexCol.vue';
 import type { NumberInputBorderType } from './NumberInput.vue';
 import Touchable from '../feedback/Touchable.vue';
 
@@ -51,6 +50,8 @@ export interface NumberInputBoxProps {
   borderType: NumberInputBorderType,
   borderColor: string,
   activeBorderColor: string,
+  backgroundColor: string,
+  activeBackgroundColor: string,
 }
 
 const emit = defineEmits([ 'click' ]);
@@ -94,8 +95,19 @@ const finalBoxStyle = computed(() => selectStyleType<ViewStyle, NumberInputBorde
     borderStyle: 'solid',
     borderWidth: themeContext.resolveSize(props.borderWidth),
     borderColor: themeContext.resolveThemeColor(props.active ? props.activeBorderColor : props.borderColor),
+    backgroundColor: themeContext.resolveThemeColor(props.active ? props.activeBackgroundColor : props.backgroundColor),
+  },
+  activeBorderBox: {
+    backgroundColor: themeContext.resolveThemeColor(props.active ? props.activeBackgroundColor : props.backgroundColor),
+    borderStyle: 'solid',
+    borderWidth: themeContext.resolveSize(props.borderWidth),
+    borderColor: themeContext.resolveThemeColor(props.active ? props.activeBorderColor : 'transparent'),
+  },
+  flat: {
+    backgroundColor: themeContext.resolveThemeColor(props.active ? props.activeBackgroundColor : props.backgroundColor),
   },
   underline: {
+    backgroundColor: themeContext.resolveThemeColor(props.active ? props.activeBackgroundColor : props.backgroundColor),
     borderWidth: themeContext.resolveSize(props.borderWidth),
     borderColor: 'transparent',
     borderBottomWidth: themeContext.resolveSize(props.borderWidth),

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

@@ -1,42 +1,53 @@
 <template>
   <view 
+    :id="containerId"
     class="signature-container"
     :style="containerStyle"
   >
+    <text v-if="placeholder" :style="placeholderStyle">{{ placeholder }}</text>
     <canvas
       class="signature-canvas"
       disable-scroll
       :id="id"
       :canvas-id="id"
       :style="canvasStyle"
-      @touchstart="handleTouchStart"
-      @touchmove="handleTouchMove"
-      @touchend="handleTouchEnd"
-      @touchcancel="handleTouchEnd"
-      @mousedown="handleMouseDown"
-      @mousemove="handleMouseMove"
-      @mouseup="handleMouseUp"
+      :width="canvasWidth"
+      :height="canvasHeight"
+      @touchstart.stop="handleTouchStart"
+      @touchmove.stop="handleTouchMove"
+      @touchend.stop="handleTouchEnd"
+      @touchcancel.stop="handleTouchEnd"
+      @mousedown.stop="handleMouseDown"
+      @mousemove.stop="handleMouseMove"
+      @mouseup.stop ="handleMouseUp"
     ></canvas>
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, type Ref, computed, getCurrentInstance, onBeforeUnmount } from 'vue';
+import { ref, onMounted, type Ref, computed, getCurrentInstance, onBeforeUnmount, nextTick } from 'vue';
 import { propGetThemeVar, useTheme } from '../theme/ThemeDefine';
-import { RandomUtils } from '@imengyu/imengyu-utils';
+import { RandomUtils, waitTimeOut } from '@imengyu/imengyu-utils';
 
 const id = `signatureCanvas${RandomUtils.genNonDuplicateID(10)}`;
+const containerId = `signatureContainer${RandomUtils.genNonDuplicateID(10)}`;
 
 // 定义绘图相关类型
 interface Point { x: number; y: number; }
 interface Line { points: Point[]; color: string; width: number; }
 
 export interface SignatureProps {
+  innerStyle?: object;
+  placeholderStyle?: object;
   backgroundColor?: string;
   lineColor?: string;
   lineWidth?: number;
   round?: boolean;
   border?: boolean;
+  borderStyle?: string;
+  borderWidth?: number;
+  borderColor?: string;
+  placeholder?: string;
 }
 export interface SignatureInstance {
   /** 
@@ -44,6 +55,10 @@ export interface SignatureInstance {
    */
   clear: () => void;
   /** 
+   * 设置签名图片 
+   */
+  setImage: (image: string) => void;
+  /** 
    * 导出签名为图片 
    */
   export: () => Promise<string>;
@@ -55,8 +70,10 @@ const props = withDefaults(defineProps<SignatureProps>(), {
   lineWidth: () => propGetThemeVar('SignatureLineWidth', 3),
   round: () => propGetThemeVar('SignatureRound', true),
   border: () => propGetThemeVar('SignatureBorder', true),
-  borderWidth: () => propGetThemeVar('SignatureBorderWidth', 2),
-  borderColor: () => propGetThemeVar('SignatureBorderColor', 'border.cell'),
+  borderWidth: () => propGetThemeVar('SignatureBorderWidth', 3),
+  borderStyle: () => propGetThemeVar('SignatureBorderStyle', 'dashed'),
+  borderColor: () => propGetThemeVar('SignatureBorderColor', 'border.signature'),
+  placeholder: () => propGetThemeVar('SignaturePlaceholder', '请在虚线框内签名'),
 });
 
 const theme = useTheme();
@@ -66,8 +83,14 @@ const containerStyle = computed(() => ({
   backgroundColor: theme.resolveThemeColor(props.backgroundColor),
   borderRadius: props.round ? theme.resolveThemeSize('SignatureBorderRadius', 12) : '0',
   border: props.border ? 
-    `${theme.resolveThemeSize(props.borderWidth)}px solid ${theme.resolveThemeColor(props.borderColor)}`
-    : 'none'
+    `${theme.resolveThemeSize(props.borderWidth)} ${props.borderStyle} ${theme.resolveThemeColor(props.borderColor)}`
+    : 'none',
+  ...props.innerStyle
+}));
+const placeholderStyle = computed(() => ({
+  fontSize: theme.resolveThemeSize('SignaturePlaceholderFontSize', 26),
+  color: theme.resolveThemeColor('SignaturePlaceholderColor', 'text.second'),
+  ...props.placeholderStyle
 }));
 const canvasStyle = computed(() => ({
   width: `${canvasWidth.value}px`,
@@ -85,27 +108,38 @@ let timer = 0;
 let isDrawing = false;
 let absPos = [0,0];
 let isDirty = true;
+let isCreating = false;
 
 async function initCanvas() {
+  if (isCreating)
+    return;
+  isCreating = true;
+  await waitTimeOut(100);
   // 获取容器尺寸信息
   containerRect.value = await new Promise<UniApp.NodeInfo>((resolve) => {
     uni.createSelectorQuery()
       .in(instance)
-      .select('.signature-container')
+      .select(`#${containerId}`)
       .boundingClientRect(resolve as any)
       .exec();
   });
 
   if (containerRect.value) {
-    canvasWidth.value = containerRect.value.width!;
-    canvasHeight.value = containerRect.value.height!;
+    canvasWidth.value = containerRect.value.width! - 2;
+    canvasHeight.value = containerRect.value.height! - 2;
   }
-
-  // 创建Canvas上下文
-  canvasContext.value = uni.createCanvasContext(id, instance);
-  timer = setInterval(render, 50) as any;
+  if (canvasWidth.value > 0 && canvasHeight.value > 0) {
+    if (!canvasContext.value)
+      // 创建Canvas上下文
+      canvasContext.value = uni.createCanvasContext(id, instance);
+  }
+  isCreating = false;
 }
 function render() {
+  if (canvasWidth.value <= 0 || canvasHeight.value <= 0) {
+    initCanvas();
+    return;
+  }
   if (!canvasContext.value)
     return;
   if (!isDirty)
@@ -142,7 +176,7 @@ function endDrag() {
 }
 
 function handleTouchStart(e: any) {
-  if (!canvasContext.value) return;
+  if (!canvasContext.value) return;   
   const { x, y } = e.touches[0];
   startDrag(x, y);
 }
@@ -233,15 +267,26 @@ async function exportImage(): Promise<string> {
     }, instance);
   });
 }
+function setImage(image: string) {
+  if (!canvasContext.value) return;
+  canvasContext.value.drawImage(image, 0, 0, canvasWidth.value, canvasHeight.value);
+  isDirty = true;
+}
 
-onMounted(initCanvas);
+onMounted(async () => {
+  await nextTick();
+  await initCanvas();
+  timer = setInterval(render, 100) as any;
+});
 onBeforeUnmount(() => {
   clearInterval(timer);
+  timer = 0;
 });
 
 defineExpose<SignatureInstance>({
   clear,
-  export: exportImage
+  export: exportImage,
+  setImage
 });
 </script>
 
@@ -250,6 +295,9 @@ defineExpose<SignatureInstance>({
   width: 100%;
   height: 200px;
   position: relative;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
 }
 .signature-canvas {
   width: 100%;

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

@@ -59,7 +59,7 @@
     <FlexRow v-if="isListStyle" :flex="1" align="center">
       <Width :size="20" />
       <FlexCol :flex="1">
-        <Text :fontSize="26" :text="StringUtils.path.getFileName(item.filePath)" />
+        <Text :fontSize="26" wrap wordBreak="break-all" :text="StringUtils.path.getFileName(item.filePath)" />
         <Text :fontSize="22" :text="item.message" />
         <Height :size="10" /> 
         <Progress :progressColor="selectStyleType(item.state, 'notstart', {

+ 1 - 0
src/components/layout/grid/GridItem.vue

@@ -4,6 +4,7 @@
     :direction="flexDirection"
     :innerStyle="style"
     :touchable="touchable"
+    :disabledOpacity="1"
     :setCursor="false"
     :radius="themeContext.resolveThemeSize(radius)"
     @click="emit('click')"

+ 1 - 0
src/components/theme/Theme.ts

@@ -120,6 +120,7 @@ export const DefaultTheme : ThemeConfig = {
       imageBox: '#dfdfdf',
       cell: '#ffffff',
       bar: '#f8f8f8',
+      input: '#eaeaea',
       box: '#dadada',
       switch: '#dadada',
       notify: '#f8f8f8',

+ 14 - 7
src/components/theme/ThemeDefine.ts

@@ -113,7 +113,7 @@ export function useTheme() {
     }
     return resolveSize(inValue);
   }
-  function resolveThemeColor(inValue?: string, defaultValue?: string) : string|undefined {
+  function resolveThemeColor(inValue?: string, defaultValue?: string) : string|undefined {    
     if (isSpecialColor(inValue))
       return inValue;
     if (inValue === undefined)
@@ -147,17 +147,24 @@ export function useTheme() {
     if (key === undefined)
       return defaultValue;
     let type = '';
-    let keyResolve = getVar(key, key);
-    if (typeof keyResolve === 'string')
-      key = keyResolve;
     if (key.includes('.'))
       [type, key] = key.split('.');
-    if (isSpecialColor(keyResolve))
-      return keyResolve;
     let group = theme.value.colorConfigs[type || 'default'];
     if (!group) 
       group = theme.value.colorConfigs['default'];
-    return group?.[key] ?? defaultValue;
+    if (group?.[key])
+      return group[key];
+    let keyResolve = getVar(key, key);
+    if (keyResolve) {
+      key = keyResolve; 
+      group = theme.value.colorConfigs[type || 'default'];
+      if (!group) 
+        group = theme.value.colorConfigs['default'];
+      if (group?.[key])
+        return group[key];
+    }
+
+    return defaultValue;
   }
   function getSize(key: string, defaultValue?: string|number) {
     if (key === undefined)

+ 1 - 1
src/pages/collect/assessment/argeement-sign.vue

@@ -2,7 +2,7 @@
   <CommonRoot>
     <FlexCol padding="space.lg">
       <SimplePageContentLoader :loader="loader">
-        <template v-if="loader.isFinished.value">
+        <template v-if="loader.isLoaded.value">
           <Result
             v-if="!currentAgreement"
             status="info"

+ 1 - 1
src/pages/collect/assessment/evaluation-form-review.vue

@@ -2,7 +2,7 @@
   <CommonRoot>
     <FlexCol padding="space.lg">
       <SimplePageContentLoader :loader="loader">
-        <template v-if="loader.isFinished.value">
+        <template v-if="loader.isLoaded.value">
           <Result
             v-if="!currentForm"
             status="warning"

+ 1 - 1
src/pages/collect/assessment/evaluation-form.vue

@@ -2,7 +2,7 @@
   <CommonRoot>
     <FlexCol padding="space.lg">
       <SimplePageContentLoader :loader="loader">
-        <template v-if="loader.isFinished.value">
+        <template v-if="loader.isLoaded.value">
           <Result
             v-if="!currentForm"
             status="info"