Forráskód Böngészése

🎨 优化我的修改信息细节问题

快乐的梦鱼 8 órája%!(EXTRA string=)
szülő
commit
97aff37751

+ 239 - 0
src/components/basic/Text.ts

@@ -0,0 +1,239 @@
+import { RandomUtils } from "@imengyu/imengyu-utils";
+import { computed, getCurrentInstance } from "vue";
+import { useTheme, type ThemePaddingMargin } from "../theme/ThemeDefine";
+
+export interface TextProps {
+  /**
+   * 字体颜色。可以是颜色字符串或者在主题中配置的预设名称。
+   */
+  color?: string,
+  /**
+   * 是否允许子节点。如果为 true,则使用view包裹。
+   * @default false
+   */
+  allowChildNode?: boolean,
+  /**
+   * 文字阴影颜色。可以是颜色字符串或者在主题中配置的预设名称。
+   */
+  shadowColor?: string,
+  /**
+   * 文字对齐方式。
+   */
+  textAlign?: 'center'|'left'|'right'|'',
+  /**
+   * 字体预设。可以是主题中预设的一个名称。
+   */
+  fontConfig?: string,
+  /**
+   * 字体大小。可以是实际数值或者在主题中配置的预设名称。
+   */
+  fontSize?: string|number,
+  /**
+   * 字体名称。
+   */
+  fontFamily?: string,
+  /**
+   * 字体样式。
+   */
+  fontStyle?: string,
+  /** 
+   * 字体粗细。
+   */
+  fontWeight?: string|number,
+  /**
+   * 是否是粗体
+   */
+  bold?: boolean,
+  /**
+   * 是否是斜体
+   */
+  italic?: boolean,
+  /**
+   * 是否加下划线
+   */
+  underline?: boolean,
+  /**
+   * 是否加删除线
+   */
+  lineThrough?: boolean,
+  /**
+   * 是否有阴影。
+   */
+  shadow?: boolean,
+  /**
+   * 背景颜色。可以是颜色字符串或者在主题中配置的预设名称。
+   */
+  backgroundColor?: string, 
+  /**
+   * 是否可以选择
+   */
+  selectable?: boolean,
+  /**
+   * word-break
+   * @default undefined
+   */
+  wordBreak?: 'normal'|'break-all'|'keep-all'|'break-word'|undefined;
+  /**
+   * 是否允许换行
+   * @default true
+   */
+  wrap?: boolean,
+  /**
+   * 行数限制
+   */
+  lines?: number,
+  margin?: number|string|number[]|ThemePaddingMargin,
+  padding?: number|string|number[]|ThemePaddingMargin,
+  innerStyle?: object,
+  innerClass?: object|string,
+  /**
+   * 最大宽度
+   */
+  maxWidth?: number|string,
+
+  /**
+   * 自动工具文字长短设置大小,在 autoSize 为 true 时有效。
+   *
+   * 这个是一个小功能,目的是为了在某些情况下(例如金额显示),容器宽度一定但是文字长短不定,
+   * 此时需要自动缩放大小,文字越长,字号越小。
+   *
+   * 计算公式是 (1 - (text.length - minLen) / (maxLen - minLen)) * (maxSize - minSize) + minSize
+   */
+  autoSize?: {
+    /**
+     * 小程序无法获取模板内容,需要额外提供字符串
+     */
+    text?: string,
+    /**
+     * 最长文字长度,用于自动大小公式计算
+     */
+    maxLen: number;
+    /**
+     * 最短文字长度,如果输入文字长度小于这个值,则不会进行自动缩放
+     */
+    minLen: number;
+    /**
+     * 最大文字字号,用于自动大小公式计算
+     */
+    maxSize: number;
+    /**
+     * 最小文字字号,用于自动大小公式计算
+     */
+    minSize: number;
+  },
+  text?: string|number,
+  /**
+   * 是否可以点击
+   */
+  touchable?: boolean,
+
+  forceNoSlotWhenNestForMp?: boolean;
+}
+
+export function useText(
+  props: TextProps,
+  emit: (e: 'click', data: any) => void,
+) {
+  const id = `text-${RandomUtils.genNonDuplicateID(12)}`;
+  const instance = getCurrentInstance();
+  const { resolveThemeColor, resolveThemeSize, getText } = useTheme();
+
+  function getAutoSize() {
+    //自动缩放大小,文字越长,字号越小
+    const autoSizeOption = props.autoSize;
+    if (autoSizeOption) {
+      const text = '' + autoSizeOption.text;
+      if (text.length < autoSizeOption.minLen)
+        return props.fontSize;
+      return (1 - (text.length - autoSizeOption.minLen) / (autoSizeOption.maxLen - autoSizeOption.minLen))
+        * (autoSizeOption.maxSize - autoSizeOption.minSize) + autoSizeOption.minSize;
+    }
+  }
+  function onClick(e: any) {
+    if (props.touchable) {
+      emit("click", e)
+    }
+  }
+
+  const style = computed(() => {
+    const o : Record<string, any> = {
+    }
+    const style = props.fontConfig ? getText(props.fontConfig) : undefined;
+    const color = props.color ?? style?.color as string;
+    const backgroundColor = props.backgroundColor ?? style?.backgroundColor as string;
+    const fontSize = props.fontSize ?? style?.fontSize as string;
+    const fontWeight = props.fontWeight ?? style?.fontWeight as string;
+    const fontFamily = props.fontFamily ?? style?.fontFamily as string;
+    const fontStyle = props.fontStyle ?? style?.fontStyle as string;
+
+    if (color) o.color = resolveThemeColor(color, "text");
+    if (backgroundColor) o.background = resolveThemeColor(backgroundColor);
+    if (fontSize) o.fontSize = resolveThemeSize(fontSize);
+    if (fontWeight) o.fontWeight = fontWeight;
+    if (fontStyle) o.fontStyle = fontStyle;
+    if (!props.wrap) o.whiteSpace = 'nowrap';
+
+    if (fontFamily) o.fontFamily = fontFamily;
+    if (props.textAlign) {
+      o.textAlign = props.textAlign;
+    }
+    if (props.selectable) o.userSelect = props.selectable;
+    if (props.lines) {
+      o.display = '-webkit-box';
+      o['-webkit-line-clamp'] = props.lines;
+      o['-webkit-box-orient'] = 'vertical';
+      o.overflow = 'hidden';
+      o.textOverflow = 'ellipsis';
+    }
+    if (props.bold)
+      o.fontWeight = 'bold';
+    if (props.italic) 
+      o.fontStyle = 'italic';
+    if ((props.underline || props.lineThrough) && !o.textDecoration)
+      o.textDecoration = '';
+    if (props.underline)
+      o.textDecoration += ' underline';
+    if (props.lineThrough)
+      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');
+    }
+    if (props.autoSize) {
+      const s = getAutoSize();
+      if (s)
+        o.fontSize = s + 'rpx';
+    }
+
+    const rs = {
+      ...o,
+      ...props.innerStyle
+    };
+    return rs;
+  })
+
+  async function measureTextWidth() {
+    return await new Promise<number>((resolve) => {
+      uni.createSelectorQuery()
+        // #ifdef MP
+        .in(instance)
+        // #endif
+        .select(`#${id}`)
+        .boundingClientRect((data) => {
+          resolve((data as UniApp.NodeInfo)?.width ?? 0)
+        })
+        .exec();
+    })
+  }
+
+  return {
+    id,
+    style,
+    onClick,
+    measureTextWidth
+  }
+}

+ 11 - 251
src/components/basic/Text.vue

@@ -1,258 +1,26 @@
 <template>
-  <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 -->
-    <!-- #ifndef APP-NVUE -->
-    <slot>{{ text ?? '' }}</slot>
-    <!-- #endif -->
-  </text>
+  <view v-if="(allowChildNode || $slots.default) && !forceNoSlotWhenNestForMp" :id="id" :class="innerClass" :style="style" @click="onClick"><slot>{{ text }}</slot></view>
+  <text v-else :id="id" :class="innerClass" :style="style" @click="onClick">{{ text }}</text>
 </template>
 
 <script lang="ts" setup>
-import { computed, getCurrentInstance } from 'vue';
-import { useTheme, type ThemePaddingMargin } from '../theme/ThemeDefine';
-import { RandomUtils } from '@imengyu/imengyu-utils';
-
-export interface TextProps {
-  /**
-   * 字体颜色。可以是颜色字符串或者在主题中配置的预设名称。
-   */
-  color?: string,
-  /**
-   * 是否允许子节点。如果为 true,则使用view包裹。
-   * @default false
-   */
-  allowChildNode?: boolean,
-  /**
-   * 文字阴影颜色。可以是颜色字符串或者在主题中配置的预设名称。
-   */
-  shadowColor?: string,
-  /**
-   * 文字对齐方式。
-   */
-  textAlign?: 'center'|'left'|'right'|'',
-  /**
-   * 字体预设。可以是主题中预设的一个名称。
-   */
-  fontConfig?: string,
-  /**
-   * 字体大小。可以是实际数值或者在主题中配置的预设名称。
-   */
-  fontSize?: string|number,
-  /**
-   * 字体名称。
-   */
-  fontFamily?: string,
-  /**
-   * 字体样式。
-   */
-  fontStyle?: string,
-  /** 
-   * 字体粗细。
-   */
-  fontWeight?: string|number,
-  /**
-   * 是否是粗体
-   */
-  bold?: boolean,
-  /**
-   * 是否是斜体
-   */
-  italic?: boolean,
-  /**
-   * 是否加下划线
-   */
-  underline?: boolean,
-  /**
-   * 是否加删除线
-   */
-  lineThrough?: boolean,
-  /**
-   * 是否有阴影。
-   */
-  shadow?: boolean,
-  /**
-   * 背景颜色。可以是颜色字符串或者在主题中配置的预设名称。
-   */
-  backgroundColor?: string, 
-  /**
-   * 是否可以选择
-   */
-  selectable?: boolean,
-  /**
-   * word-break
-   * @default undefined
-   */
-  wordBreak?: 'normal'|'break-all'|'keep-all'|'break-word'|undefined;
-  /**
-   * 是否允许换行
-   * @default true
-   */
-  wrap?: boolean,
-  /**
-   * 行数限制
-   */
-  lines?: number,
-  margin?: number|string|number[]|ThemePaddingMargin,
-  padding?: number|string|number[]|ThemePaddingMargin,
-  innerStyle?: object,
-  innerClass?: object|string,
-  /**
-   * 最大宽度
-   */
-  maxWidth?: number|string,
-
-  /**
-   * 自动工具文字长短设置大小,在 autoSize 为 true 时有效。
-   *
-   * 这个是一个小功能,目的是为了在某些情况下(例如金额显示),容器宽度一定但是文字长短不定,
-   * 此时需要自动缩放大小,文字越长,字号越小。
-   *
-   * 计算公式是 (1 - (text.length - minLen) / (maxLen - minLen)) * (maxSize - minSize) + minSize
-   */
-  autoSize?: {
-    /**
-     * 小程序无法获取模板内容,需要额外提供字符串
-     */
-    text?: string,
-    /**
-     * 最长文字长度,用于自动大小公式计算
-     */
-    maxLen: number;
-    /**
-     * 最短文字长度,如果输入文字长度小于这个值,则不会进行自动缩放
-     */
-    minLen: number;
-    /**
-     * 最大文字字号,用于自动大小公式计算
-     */
-    maxSize: number;
-    /**
-     * 最小文字字号,用于自动大小公式计算
-     */
-    minSize: number;
-  },
-  text?: string|number,
-  /**
-   * 是否可以点击
-   */
-  touchable?: boolean,
-}
+import { useText, type TextProps } from './Text';
 
 /**
  * 组件说明:文字封装,支持点击事件,颜色,阴影。
  */
 const props = withDefaults(defineProps<TextProps>(), {
   shadowColor: '#000',
-  bold: false,
-  italic: false,
-  underline: false,
-  lineThrough: false,
-  shadow: false,
-  touchable: false,
   wrap: true,
 });
-const emit = defineEmits([ 'click' ])
-
-const id = `text-${RandomUtils.genNonDuplicateID(12)}`;
-const instance = getCurrentInstance();
-const { resolveThemeColor, resolveThemeSize, getText } = useTheme();
-
-function getAutoSize() {
-  //自动缩放大小,文字越长,字号越小
-  const autoSizeOption = props.autoSize;
-  if (autoSizeOption) {
-    const text = '' + autoSizeOption.text;
-    if (text.length < autoSizeOption.minLen)
-      return props.fontSize;
-    return (1 - (text.length - autoSizeOption.minLen) / (autoSizeOption.maxLen - autoSizeOption.minLen))
-      * (autoSizeOption.maxSize - autoSizeOption.minSize) + autoSizeOption.minSize;
-  }
-}
-function onClick(e: any) {
-  if (props.touchable) {
-    emit("click", e)
-  }
-}
-
-const style = computed(() => {
-  const o : Record<string, any> = {
-  }
-  const style = props.fontConfig ? getText(props.fontConfig) : undefined;
-  const color = props.color ?? style?.color as string;
-  const backgroundColor = props.backgroundColor ?? style?.backgroundColor as string;
-  const fontSize = props.fontSize ?? style?.fontSize as string;
-  const fontWeight = props.fontWeight ?? style?.fontWeight as string;
-  const fontFamily = props.fontFamily ?? style?.fontFamily as string;
-  const fontStyle = props.fontStyle ?? style?.fontStyle as string;
-
-  if (color) o.color = resolveThemeColor(color, "text");
-  if (backgroundColor) o.background = resolveThemeColor(backgroundColor);
-  if (fontSize) o.fontSize = resolveThemeSize(fontSize);
-  if (fontWeight) o.fontWeight = fontWeight;
-  if (fontStyle) o.fontStyle = fontStyle;
-  if (!props.wrap) o.whiteSpace = 'nowrap';
+const emit = defineEmits([ 'click' ]);
 
-  if (fontFamily) o.fontFamily = fontFamily;
-  if (props.textAlign) {
-    o.textAlign = props.textAlign;
-  }
-  if (props.selectable) o.userSelect = props.selectable;
-  if (props.lines) {
-    o.display = '-webkit-box';
-		o['-webkit-line-clamp'] = props.lines;
-		o['-webkit-box-orient'] = 'vertical';
-		o.overflow = 'hidden';
-    o.textOverflow = 'ellipsis';
-  }
-  if (props.bold)
-    o.fontWeight = 'bold';
-  if (props.italic) 
-    o.fontStyle = 'italic';
-  if ((props.underline || props.lineThrough) && !o.textDecoration)
-    o.textDecoration = '';
-  if (props.underline)
-    o.textDecoration += ' underline';
-  if (props.lineThrough)
-    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');
-  }
-  if (props.autoSize) {
-    const s = getAutoSize();
-    if (s)
-      o.fontSize = s + 'rpx';
-  }
-
-  const rs = {
-    ...o,
-    ...props.innerStyle
-  };
-  return rs;
-})
-
-async function measureTextWidth() {
-  return await new Promise<number>((resolve) => {
-    uni.createSelectorQuery()
-      // #ifdef MP
-      .in(instance)
-      // #endif
-      .select(`#${id}`)
-      .boundingClientRect((data) => {
-        resolve((data as UniApp.NodeInfo)?.width ?? 0)
-      })
-      .exec();
-  })
-}
+const {
+  id,
+  style,
+  measureTextWidth,
+  onClick,
+} = useText(props, emit);
 
 defineExpose({
   measureTextWidth,
@@ -263,12 +31,4 @@ defineOptions({
     styleIsolation: "shared",
   },
 });
-
-</script>
-
-<style lang="scss">
-.nana-text-can-press {
-  cursor: pointer;
-  opacity: 0.8;
-}
-</style>
+</script>

+ 3 - 2
src/components/typography/A.vue

@@ -1,11 +1,12 @@
 <template>
   <Text underline color="text.link" touchable v-bind="$attrs" @click="onClick">
-    <slot />
+    <slot v-if="$slots.default" />
   </Text>
 </template>
 
 <script setup lang="ts">
-import Text, { type TextProps } from '../basic/Text.vue';
+import type { TextProps } from '../basic/Text';
+import Text from '../basic/Text.vue';
 
 export interface AProps extends TextProps {
   /**

+ 15 - 2
src/components/typography/B.vue

@@ -1,9 +1,22 @@
 <template>
-  <Text bold v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  bold: true,
+  wrap: true,
+});
 </script>

+ 14 - 2
src/components/typography/H1.vue

@@ -1,9 +1,21 @@
 <template>
-  <Text fontConfig="h1" v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  fontConfig: 'h1',
+});
 </script>

+ 14 - 2
src/components/typography/H2.vue

@@ -1,9 +1,21 @@
 <template>
-  <Text fontConfig="h2" v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  fontConfig: 'h2',
+});
 </script>

+ 14 - 2
src/components/typography/H3.vue

@@ -1,9 +1,21 @@
 <template>
-  <Text fontConfig="h3" v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  fontConfig: 'h3',
+});
 </script>

+ 14 - 2
src/components/typography/H4.vue

@@ -1,9 +1,21 @@
 <template>
-  <Text fontConfig="h4" v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  fontConfig: 'h4',
+});
 </script>

+ 14 - 2
src/components/typography/H5.vue

@@ -1,9 +1,21 @@
 <template>
-  <Text fontConfig="h5" v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  fontConfig: 'h5',
+});
 </script>

+ 14 - 2
src/components/typography/H6.vue

@@ -1,9 +1,21 @@
 <template>
-  <Text fontConfig="h6" v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  fontConfig: 'h6',
+});
 </script>

+ 15 - 2
src/components/typography/I.vue

@@ -1,9 +1,22 @@
 <template>
-  <Text italic v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  italic: true,
+  wrap: true,
+});
 </script>

+ 10 - 2
src/components/typography/P.vue

@@ -1,11 +1,19 @@
 <template>
-  <Text v-bind="props" fontConfig="p">
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
+  </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
     <slot v-if="$slots.default" />
   </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
-import Text, { type TextProps } from '../basic/Text.vue';
+import type { TextProps } from '../basic/Text';
+import Text from '../basic/Text.vue';
 
 const props = withDefaults(defineProps<Partial<TextProps>>(), {
   wrap: true,

+ 15 - 2
src/components/typography/S.vue

@@ -1,9 +1,22 @@
 <template>
-  <Text lineThrough v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  lineThrough: true,
+  wrap: true,
+});
 </script>

+ 15 - 2
src/components/typography/U.vue

@@ -1,9 +1,22 @@
 <template>
-  <Text underline v-bind="$props">
-    <slot />
+  <!-- #ifdef MP -->
+  <Text v-bind="props" :forceNoSlotWhenNestForMp="!Boolean($slots.default)">
+    <slot v-if="$slots.default"></slot>
   </Text>
+  <!-- #endif -->
+  <!-- #ifndef MP -->
+  <Text v-bind="props">
+    <slot v-if="$slots.default" />
+  </Text>
+  <!-- #endif -->
 </template>
 
 <script setup lang="ts">
+import type { TextProps } from '../basic/Text';
 import Text from '../basic/Text.vue';
+
+const props = withDefaults(defineProps<Partial<TextProps>>(), {
+  underline: true,
+  wrap: true,
+});
 </script>

+ 2 - 0
src/pages.json

@@ -28,6 +28,7 @@
       "path": "pages/user/update/password",
       "style": {
         "navigationBarTitleText": "修改密码",
+        "navigationStyle": "custom",
         "enablePullDownRefresh": false
       }
     },
@@ -35,6 +36,7 @@
       "path": "pages/user/update/profile",
       "style": {
         "navigationBarTitleText": "修改个人信息",
+        "navigationStyle": "custom",
         "enablePullDownRefresh": false
       }
     },

+ 1 - 1
src/pages/user/index.vue

@@ -22,7 +22,7 @@
           :showFailed="false"
           round
         />
-        <H4 v-if="userInfo">{{ userInfo.nickname }}</H4>
+        <H4 v-if="userInfo" :text="userInfo.nickname" />
         <H4 v-else>欢迎登录</H4>
       </FlexRow>
       <Icon icon="arrow-right" size="30" />

+ 24 - 21
src/pages/user/update/password.vue

@@ -1,25 +1,27 @@
 <template>
-  <FlexCol :padding="30">
-    <Form 
-      ref="formRef" 
-      :model="formData" 
-      :rules="rules"
-      :labelFlex="2"
-      :inputFlex="4"
-    >
-      <Field name="oldpassword" type="password" label="当前密码" placeholder="请输入当前密码" :maxlength="20" required />
-      <Field name="newpassword" type="password" label="新密码" placeholder="请输入新密码" :maxlength="20" required />
-      <Field name="confirmPassword" type="password" label="确认新密码" placeholder="请再次输入新密码" :maxlength="20" required />
-      
-      <text class="tips">
-        密码建议:8-20位,包含字母和数字,不要使用过于简单的密码
-      </text>
-    </Form>
-    <Height :height="40" />
-    <Button type="primary" :loading="loading" @click="submitForm" >
-      保存修改
-    </Button>
-  </FlexCol>
+  <CommonTopBanner title="修改密码">
+    <FlexCol :padding="30">
+      <Form 
+        ref="formRef" 
+        :model="formData" 
+        :rules="rules"
+        :labelFlex="2"
+        :inputFlex="4"
+      >
+        <Field name="oldpassword" type="password" label="当前密码" placeholder="请输入当前密码" :maxlength="20" required />
+        <Field name="newpassword" type="password" label="新密码" placeholder="请输入新密码" :maxlength="20" required />
+        <Field name="confirmPassword" type="password" label="确认新密码" placeholder="请再次输入新密码" :maxlength="20" required />
+        
+        <text class="tips">
+          密码建议:8-20位,包含字母和数字,不要使用过于简单的密码
+        </text>
+      </Form>
+      <Height :height="40" />
+      <Button type="primary" :loading="loading" @click="submitForm" >
+        保存修改
+      </Button>
+    </FlexCol>
+  </CommonTopBanner>
 </template>
 
 <script setup lang="ts">
@@ -31,6 +33,7 @@ import Field from '@/components/form/Field.vue';
 import Height from '@/components/layout/space/Height.vue';
 import Button from '@/components/basic/Button.vue';
 import type { Rules } from 'async-validator';
+import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
 
 const formRef = ref<any>(null);
 const loading = ref(false);

+ 44 - 40
src/pages/user/update/profile.vue

@@ -1,39 +1,41 @@
 <template>
-  <FlexCol :padding="30">
-    <Form 
-      ref="formRef"
-      :model="formModel"
-      :rules="rules" 
-      validateTrigger="submit"
-      labelAlign="right"
-      :labelWidth="140"
-    >
-      <!-- 头像 -->
-      <view class="avatar-section">
-        <view class="avatar-container" @click="handleAvatarClick">
-          <image 
-            :src="formModel.avatar || DefaultAvatar" 
-            class="avatar-image" 
-            mode="aspectFill"
-          ></image>
-          <text class="avatar-hint">点击可修改头像</text>
+  <CommonTopBanner title="个人信息">
+    <FlexCol :padding="30">
+      <Form 
+        ref="formRef"
+        :model="formModel"
+        :rules="rules" 
+        validateTrigger="submit"
+        labelAlign="right"
+        :labelWidth="140"
+      >
+        <!-- 头像 -->
+        <view class="avatar-section">
+          <view class="avatar-container" @click="handleAvatarClick">
+            <image 
+              :src="formModel.avatar || DefaultAvatar" 
+              class="avatar-image" 
+              mode="aspectFill"
+            ></image>
+            <text class="avatar-hint">点击可修改头像</text>
+          </view>
         </view>
-      </view>
-
-      <Field name="nickname" label="昵称" placeholder="请输入昵称" required />
-      <Field name="bio" multiline label="个人简介" placeholder="输入个人简介" :inputStyle="{width: '240px'}" />
-    </Form>
-
-    <Height :height="40" />
-
-    <Button type="primary" :loading="loading" @click="submitForm" >
-      保存修改
-    </Button>
-    <Height :height="20" />
-    <Button :plain="true" @click="navTo('/pages/user/update/password')">
-      修改密码
-    </Button>
-  </FlexCol>
+
+        <Field name="nickname" label="昵称" placeholder="请输入昵称" required />
+        <Field name="bio" multiline label="个人简介" placeholder="输入个人简介" :inputStyle="{width: '240px'}" />
+      </Form>
+
+      <Height :height="40" />
+
+      <Button type="primary" :loading="loading" @click="submitForm" >
+        保存修改
+      </Button>
+      <Height :height="20" />
+      <Button :plain="true" @click="navTo('/pages/user/update/password')">
+        修改密码
+      </Button>
+    </FlexCol>
+  </CommonTopBanner>
 </template>
 
 <script setup lang="ts">
@@ -49,6 +51,7 @@ import Height from '@/components/layout/space/Height.vue';
 import type { Rules } from 'async-validator';
 import { navTo } from '@/components/utils/PageAction';
 import { showError } from '@/common/composeabe/ErrorDisplay';
+import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
 
 const DefaultAvatar = 'https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/UserHead.png';
 
@@ -108,7 +111,9 @@ const handleAvatarClick = async () => {
 const updateAvatar = async (avatarUrl: string) => {
   try {
     await UserApi.updateSystemUserInfo({
-      avatar: avatarUrl
+      avatar: avatarUrl,
+      nickname: formModel.value.nickname,
+      bio: formModel.value.bio
     });
     formModel.value.avatar = avatarUrl;
     if (authStore.userInfo) {
@@ -121,6 +126,7 @@ const updateAvatar = async (avatarUrl: string) => {
       duration: 2000
     });
     
+    authStore.refreshUserInfo();
   } catch (error: any) {
     throw new Error(error?.message || '头像更新失败');
   }
@@ -146,19 +152,17 @@ const submitForm = async () => {
   
   try {
     await UserApi.updateSystemUserInfo({
+      avatar: formModel.value.avatar,
       nickname: formModel.value.nickname,
       bio: formModel.value.bio
     });
-    if (authStore.userInfo) {
-      authStore.userInfo.nickname = formModel.value.nickname;
-      authStore.userInfo.avatar = formModel.value.avatar;
-      authStore.userInfo.intro = formModel.value.bio;
-    }
     uni.showToast({
       title: '个人信息更新成功',
       icon: 'success',
       duration: 2000
     });
+    await authStore.refreshUserInfo();
+
     setTimeout(() => {
       uni.navigateBack();
     }, 2000);