快乐的梦鱼 преди 1 месец
родител
ревизия
5ca71c3031

+ 2 - 2
src/App.vue

@@ -35,11 +35,11 @@ onLaunch(async () => {
 })
 
 //修改默认主题颜色
-configTheme((theme) => {
+configTheme(false, (theme, defaultDarkTheme) => {
   theme.colorConfigs.default.primary = '#00b66a';
   theme.colorConfigs.pressed.primary = '#00814b';
   theme.colorConfigs.background.primary = '#dcfff0';
-  return theme;
+  return [theme, defaultDarkTheme];
 });
 </script>
 

+ 27 - 6
src/components/basic/Button.vue

@@ -11,7 +11,7 @@
     :pressedColor="finalPressedColor"
     :touchable="touchable && !loading"
     @state="(v) => state = v"
-    @click="emit('click', $event)"
+    @click="(e) => emit('click', e)"
   >
     <slot name="leftIcon">
       <ActivityIndicator 
@@ -58,7 +58,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue';
+import { computed, inject, ref } from 'vue';
 import { useTheme, type ViewStyle } from '../theme/ThemeDefine';
 import { configPadding, DynamicColor, DynamicSize, selectStyleType } from '../theme/ThemeTools';
 import type { IconProps } from './Icon.vue';
@@ -67,6 +67,8 @@ import Text from './Text.vue';
 import ActivityIndicator from './ActivityIndicator.vue';
 import Icon from './Icon.vue';
 import Touchable from '../feedback/Touchable.vue';
+import type { ButtonGroupContext } from './ButtonGroup.vue';
+import { useChildLinkChild } from '../composeabe/ChildItem';
 
 export type ButtomType = 'default'|'primary'|'success'|'warning'|'danger'|'custom'|'text';
 export type ButtomSizeType = 'small'|'medium'|'large'|'larger'|'mini';
@@ -235,8 +237,8 @@ const themeStyles = themeContext.useThemeStyles({
   plainButtonDefault: {
     borderStyle: 'solid',
     borderWidth: DynamicSize('ButtonBorderWidth', 1.5),
-    borderColor: DynamicColor('ButtonPlainDefaultBorderColor', 'border'),
-    color: DynamicColor('ButtonPlainDefaultColor', 'text'),
+    borderColor: DynamicColor('ButtonPlainDefaultBorderColor', 'border.default'),
+    color: DynamicColor('ButtonPlainDefaultColor', 'text.content'),
   },
   plainButtonPrimary: {
     borderStyle: 'solid',
@@ -264,7 +266,7 @@ const themeStyles = themeContext.useThemeStyles({
   },
   lightButtonDefault: {
     backgroundColor: DynamicColor('ButtonLightDefaultBackgroundColor', 'background.button'),
-    color: DynamicColor('ButtonLightDefaultColor', 'text'),
+    color: DynamicColor('ButtonLightDefaultColor', 'text.content'),
   },
   lightButtonPrimary: {
     backgroundColor: DynamicColor('ButtonLightPrimaryBackgroundColor', 'background.primary'),
@@ -380,6 +382,17 @@ const currentStyle = computed(() => {
     borderRadius: props.shape === 'round' ? themeContext.resolveThemeSize(props.radius) : 0,
   };
 
+  if (props.shape === 'round') {
+    if (noLeftRadius.value) {
+      speicalStyle.borderTopLeftRadius = 0;
+      speicalStyle.borderBottomLeftRadius = 0;
+    }
+    if (noRightRadius.value) {
+      speicalStyle.borderTopRightRadius = 0;
+      speicalStyle.borderBottomRightRadius = 0;
+    }
+  }
+
   //自定义状态下的禁用颜色
   if (props.disabledColor && !props.touchable && props.type === 'custom')
     speicalStyle.backgroundColor = themeContext.resolveThemeColor(props.disabledColor);
@@ -395,7 +408,7 @@ const currentStyle = computed(() => {
   //  sizeStyle.paddingHorizontal = `calc(${sizeStyle.paddingHorizontal} + ${themeContext.resolveSize(props.radius / 4)})`;
 
   //内边距样式的强制设置
-  configPadding(speicalStyle, themeContext.theme, props.padding);
+  configPadding(speicalStyle, themeContext.theme.value, props.padding);
 
   return {
     color: (colorStyle).color,
@@ -428,7 +441,15 @@ const finalPressedColor = computed(() => {
     'pressed.' + props.type))
 });
 
+//按钮组的处理
+
+const buttonGroupContext = inject<ButtonGroupContext>('buttonGroupContext', undefined as any);
+const { position } = useChildLinkChild(() => buttonGroupContext?.getPosition());
+
+const noRightRadius = computed(() => buttonGroupContext?.mergeRadius.value && position.value !== buttonGroupContext.length.value - 1);
+const noLeftRadius = computed(() => buttonGroupContext?.mergeRadius.value && position.value !== 0);
 
+const touchable = computed(() => props.touchable !== false && !buttonGroupContext?.groupDisabled.value);
 </script>
 
 <style>

+ 72 - 0
src/components/basic/ButtonGroup.vue

@@ -0,0 +1,72 @@
+<template>
+  <FlexView v-bind="$props">
+    <slot />
+  </FlexView>
+</template>
+
+<script setup lang="ts">
+import FlexView, { type FlexProps } from '../layout/FlexView.vue';
+import { useChildLinkParent } from '../composeabe/ChildItem';
+import { computed, provide, ref, toRef, type ComputedRef, type Ref } from 'vue';
+
+export interface ButtonGroupProp extends FlexProps {
+  /**
+   * 是否禁用按钮组下的全部按钮
+   * @default false
+   */
+  disabled?: boolean;
+  /**
+   * 按钮组间距。为0时会自动合并按钮,使其圆角自动合并
+   * @default 0
+   */
+  gap?: number;
+}
+
+defineOptions({
+  options: {
+    styleIsolation: "shared",
+    virtualHost: true,
+  }
+})
+
+const props = withDefaults(defineProps<ButtonGroupProp>(), {
+  disabled: false,
+  gap: 0,
+});
+
+const mergeRadius = computed(() => {
+  return props.gap === 0;
+});
+
+const length = ref(0);
+
+const {
+  getPosition,
+  getLength,
+  resetCounter,
+} = useChildLinkParent({
+  onLengthChanged() {
+    length.value = getLength();
+  },
+});
+
+export interface ButtonGroupContext {
+  groupDisabled: Ref<boolean>;
+  mergeRadius: ComputedRef<boolean>;
+  length: typeof length;
+  resetCounter: typeof resetCounter;
+  getPosition: typeof getPosition;
+}
+
+provide<ButtonGroupContext>('buttonGroupContext', {
+  groupDisabled: toRef(props.disabled),
+  mergeRadius,
+  length,
+  getPosition,
+  resetCounter,
+});
+</script>
+
+<style>
+</style>
+

+ 17 - 8
src/components/composeabe/ChildItem.ts

@@ -1,20 +1,20 @@
-import { computed, onMounted, onUpdated, ref } from "vue";
+import { computed, nextTick, onMounted, onUpdated, ref } from "vue";
 
-export function useChildLinkParent(options: {
-  getPositionExtra?: (index: number) => any,
+export function useChildLinkParent<T extends Record<string, any> = {}>(options: {
+  getPositionExtra?: (index: number) => T,
   onClean?: () => void,
+  onLengthChanged?: () => void,
 }) {
 
   let currentIndex = 0;
   let length = 0;
 
-
-  function getPosition() : Record<string, any> {
+  function getPosition() : { index: number } & Partial<T> {
     const index = currentIndex++;
     length++;
     return {
       index,
-      ...options.getPositionExtra?.(index)
+      ...(options.getPositionExtra?.(index) || {}) as T
     }
   }
   function resetCounter() {
@@ -24,10 +24,16 @@ export function useChildLinkParent(options: {
   onMounted(() => {
     resetCounter();
     options.onClean?.();
+    nextTick(() => {
+      options.onLengthChanged?.();
+    })
   });
   onUpdated(() => {
     resetCounter();
     options.onClean?.();
+    nextTick(() => {
+      options.onLengthChanged?.();
+    })
   });
 
   return {
@@ -37,9 +43,12 @@ export function useChildLinkParent(options: {
   }
 }
 
-export function useChildLinkChild(getPosition: () => any) {
+export function useChildLinkChild(getPosition?: () => {
+  index: number;
+  [key: string]: unknown;
+}|undefined) {
 
-  const position = computed(() => getPosition());
+  const position = computed(() => getPosition?.()?.index ?? -1 );
 
   onMounted(() => {
     position.value;

Файловите разлики са ограничени, защото са твърде много
+ 2 - 0
src/components/data/DefaultIcon.json


+ 2 - 6
src/components/demo/DemoBlock.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import Text from '../basic/Text.vue';
 import DemoTitle from './DemoTitle.vue';
 
 defineProps({	
@@ -13,7 +14,7 @@ defineProps({
   <view :class="['nana-demo-block',flat?'flat':'']">
     <view class="header">
       <DemoTitle v-if="title" :title="title" :desc="desc" :small="smallTitle" />
-      <text class="sub-title" v-if="desc">{{ desc }}</text>
+      <Text v-if="desc" color="text.second" :text="desc" />
     </view>
     <slot />
   </view>
@@ -38,9 +39,4 @@ defineProps({
 .nana-demo-block .header {
   margin: 0 0 20rpx 0;
 }
-.nana-demo-block .sub-title {
-  font-size: 28rpx;
-  color: #888;
-  margin-bottom: 20rpx;
-}
 </style>

+ 24 - 34
src/components/demo/DemoPage.vue

@@ -1,56 +1,46 @@
 <script setup lang="ts">
+import Text from '../basic/Text.vue';
 import CommonRoot from '../dialog/CommonRoot.vue';
+import FlexCol from '../layout/FlexCol.vue';
+import StatusBarSpace from '../layout/space/StatusBarSpace.vue';
+import NavBar from '../nav/NavBar.vue';
 
-const props = defineProps({	
-  title: String,
-  desc: String
-})
+const props = defineProps<{	
+  title?: string,
+  desc?: string,
+  isHome?: boolean,
+}>();
 
-if (props.title) {
-  uni.setNavigationBarTitle({
-    title: props.title,
-  })
-}
 </script>
 
 <template>
   <CommonRoot>
-    <view class="nana-demo-page">
-      <view class="header">
-        <text class="title">{{ title }}</text>
-        <text v-if="desc" class="desc">{{ desc }}</text>
+    <FlexCol innerClass="nana-demo-page" backgroundColor="background.page">
+      <StatusBarSpace />
+      <NavBar :title="title" leftButton="back" :showLeftButton="!isHome" />
+      <view v-if="title || desc" class="header">
+        <Text color="primary" fontConfig="h3" :text="title" />
+        <Text v-if="desc" color="text.second" :text="desc" />
       </view>
       <slot />
-    </view>
+    </FlexCol>
   </CommonRoot>
 </template>
 
-<style>
+<style lang="scss">
 .nana-demo-page {
-  display: flex;
-  flex-direction: column;
-  background-color: #efefef;
   /* #ifdef H5 */
   min-height: calc(100vh - 44px - env(safe-area-inset-top));
   /* #endif */
   /* #ifndef H5 */
   min-height: calc(100vh - 44px);
   /* #endif */
-}
-.nana-demo-page > .header {
-  display: flex;
-  flex-direction: column;
-  margin: 40rpx 40rpx;
-  flex-shrink: 0;
-}
-.nana-demo-page > .header .title {
-  font-size: 45rpx;
-  font-weight: bold;
-  color: #0079db;
-}
-.nana-demo-page > .header .desc {
-  font-size: 28rpx;
-  color: #888;
-  margin-top: 15rpx;
+
+  & > .header {
+    display: flex;
+    flex-direction: column;
+    margin: 40rpx 40rpx;
+    flex-shrink: 0;
+  }
 }
 </style>

+ 3 - 1
src/components/demo/DemoTitle.vue

@@ -3,11 +3,13 @@
     <view v-if="!small" class="line">
       <view class="line2"></view>
     </view>
-    <text class="title">{{ title }}</text>
+    <Text fontConfig="h4" color="text.content" :text="title" />
   </view>
 </template>
 
 <script setup lang="ts">
+import Text from '../basic/Text.vue';
+
 defineProps({	
   title: String	,
   small: Boolean,

+ 3 - 3
src/components/display/CollapseItem.vue

@@ -46,14 +46,14 @@
         </template>
       </Cell>
     </slot>
-    <CollapseBox :open="state" :anim-duration="context.animDuration.value" :name="name || position.value">
+    <CollapseBox :open="state" :anim-duration="context.animDuration.value" :name="'' + (name || position)">
       <slot />
     </CollapseBox>
   </FlexView>
 </template>
 
 <script setup lang="ts">
-import { computed, inject, provide, toRef, type Ref } from 'vue';
+import { computed, inject } from 'vue';
 import FlexView from '../layout/FlexView.vue';
 import type { CollapseContext } from './Collapse.vue';
 import Cell from '../basic/Cell.vue';
@@ -105,5 +105,5 @@ const context = inject<CollapseContext>('CollapseContext')!;
 const id = computed(() => props.name || position.value);
 const state = computed(() => context.activeName.value.includes(id.value));
 
-const { position } = useChildLinkChild(() => context.getPosition(props.name));
+const { position } = useChildLinkChild(() => ({ index: context.getPosition(props.name) }));
 </script>

+ 3 - 3
src/components/form/Field.vue

@@ -382,7 +382,7 @@ export interface FieldProps {
   errorFieldStyle?: ViewStyle;
   /**
    * 错误时的文字颜色
-   * @default Color.danger
+   * @default danger
    */
   errorTextColor?: string;
 
@@ -496,11 +496,11 @@ const emit = defineEmits([ 'update:modelValue', 'click', 'blur', 'focus', 'clear
 
 const props = withDefaults(defineProps<FieldProps>(), {
   label: '',
-  labelColor: () => propGetThemeVar('FieldLabelColor', propGetFormContext()?.fieldProps.value?.labelColor ?? 'text'),
+  labelColor: () => propGetThemeVar('FieldLabelColor', propGetFormContext()?.fieldProps.value?.labelColor ?? 'text.content'),
   labelDisableColor: () => propGetThemeVar('FieldLabelDisableColor', propGetFormContext()?.fieldProps.value?.labelDisableColor ?? 'grey'),
   labelFlex: () => propGetThemeVar('FieldLabelFlex', propGetFormContext()?.labelFlex.value)!,
   inputDisableColor: () => propGetThemeVar('FieldInputDisableColor', propGetFormContext()?.fieldProps.value?.inputDisableColor ?? 'grey'),
-  inputColor: () => propGetThemeVar('FieldInputColor', propGetFormContext()?.fieldProps.value?.inputColor ?? 'text'),
+  inputColor: () => propGetThemeVar('FieldInputColor', propGetFormContext()?.fieldProps.value?.inputColor ?? 'text.content'),
   inputFlex: () => propGetThemeVar('FieldInputFlex', propGetFormContext()?.inputFlex.value ?? 5),
   placeholderTextColor: () => propGetThemeVar('FieldPlaceholderTextColor', propGetFormContext()?.fieldProps.value?.placeholderTextColor ?? 'text.second'),
   errorTextColor: () => propGetThemeVar('FieldErrorTextColor', propGetFormContext()?.fieldProps.value?.errorTextColor ?? 'danger'),

+ 2 - 2
src/components/form/Slider.vue

@@ -83,7 +83,7 @@ export interface SliderProps {
   size?: string|number;
   /**
    * 开关点的颜色
-   * @default white
+   * @default lightButton
    */
   dotColor?: string;
   /**
@@ -127,7 +127,7 @@ const props = withDefaults(defineProps<SliderProps>(), {
   modelValue: 50,
   activeColor: () => propGetThemeVar('SliderActiveColor', 'primary'),
   inactiveColor: () => propGetThemeVar('SliderInactiveColor', 'background.switch'),
-  dotColor: () => propGetThemeVar('SliderDotColor', 'white'),
+  dotColor: () => propGetThemeVar('SliderDotColor', 'lightButton'),
   dotSize: () => propGetThemeVar('SliderDotSize', 40),
   disabled: false,
   direction: 'horizontal',

+ 2 - 2
src/components/form/Switch.vue

@@ -80,7 +80,7 @@ export interface SwitchProps {
   inverseColor?: string;
   /**
    * 开关点的颜色
-   * @default white
+   * @default lightButton
    */
   dotColor?: string;
   /**
@@ -113,7 +113,7 @@ const props = withDefaults(defineProps<SwitchProps>(), {
   native: false,
   color: () => propGetThemeVar('SwitchColor', 'primary'),
   inverseColor: () => propGetThemeVar('SwitchInverseColor', 'background.switch'),
-  dotColor: () => propGetThemeVar('SwitchDotColor', 'white'),
+  dotColor: () => propGetThemeVar('SwitchDotColor', 'lightButton'),
   loading: false,
   disabled: false,
   impactFeedback: () => propGetThemeVar('SwitchImpactFeedback', true),

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

@@ -378,7 +378,7 @@ function onUploadPress() {
       //#endif
       //#ifndef MP
       chooseLocal();
-      ///#endif
+      //#endif
 
       function chooseLocal() {
         switch (props.chooseType) {

+ 2 - 2
src/components/layout/BaseView.ts

@@ -29,9 +29,9 @@ export function useBaseViewStyleBuilder(props: FlexProps) {
     }
 
     //内边距样式
-    configPadding(obj, themeContext.theme, props.padding as any);
+    configPadding(obj, themeContext.theme.value, props.padding as any);
     //外边距样式
-    configMargin(obj, themeContext.theme, props.margin as any);
+    configMargin(obj, themeContext.theme.value, props.margin as any);
 
     if (obj.paddingVertical) {
       if (obj.paddingTop === undefined)

+ 2 - 0
src/components/nav/NavBar.vue

@@ -104,6 +104,7 @@ export interface NavBarProps {
   rightButton?: NavBarButtonTypes,
   /**
    * 是否显示右侧按钮
+   * @default true
    */
   showRightButton?: boolean;
   /**
@@ -117,6 +118,7 @@ export interface NavBarProps {
   rightPillSpaceForce?: number,
   /**
    * 是否显示左侧按钮
+   * @default true
    */
   showLeftButton?: boolean;
   /**

+ 7 - 0
src/components/nav/SegmentedControl.vue

@@ -7,6 +7,7 @@
       :plain="true"
       :maxLen="(values?.length ?? 0) - 1"
       :radius="themeContext.resolveSize(radius)!"
+      :textColor="themeContext.resolveThemeColor(textColor)"
       :activeColor="themeContext.resolveThemeColor(tintColor)"
       :activeTextColor="themeContext.resolveThemeColor(activeTextColor)"
       :normalColor="plain ? 
@@ -73,6 +74,11 @@ export interface SegmentedControlProps {
    */
   tintColor?: string | undefined;
   /**
+   * 条目文字颜色。
+   * @default text.content
+   */
+  textColor?: string | undefined;
+  /**
    * 条目激活时的文字颜色。
    * @default white
    */
@@ -94,6 +100,7 @@ const props = withDefaults(defineProps<SegmentedControlProps>(), {
   fill: true,
   plain: false,
   selectedIndex: 0,
+  textColor: () => propGetThemeVar('SegmentedControlTextColor', 'text.content'),
   tintColor: () => propGetThemeVar('SegmentedControlTintColor', 'primary'),
   activeTextColor: () => propGetThemeVar('SegmentedControlActiveTextColor', 'white'),
   radius: 10,

+ 2 - 1
src/components/nav/SegmentedControlItem.vue

@@ -22,7 +22,7 @@
   >
     <text :style="{
       ...itemTextStyle,
-      color: active ? activeTextColor : undefined,
+      color: active ? activeTextColor : textColor,
     }">{{ label }}</Text>
   </Touchable>
 </template>
@@ -40,6 +40,7 @@ export interface SegmentedControlItemProps {
   maxLen: number,
   activeColor?: string,
   normalColor?: string,
+  textColor?: string,
   activeTextColor?: string,
   pressedColor?: string,
   itemStyle: ViewStyle,

+ 5 - 1
src/components/nav/Tabs.vue

@@ -339,12 +339,14 @@ function onTabClick(index: number) {
 
 <style lang="scss">
 .nana-tabs {
+  position: relative;
   display: flex;
   flex-direction: row;
-  position: relative;
+  flex-wrap: nowrap;
   flex-shrink: 0;
   flex-grow: 0;
   height: auto;
+  width: fit-content;
   overflow: hidden;
 
   .tab-item {
@@ -352,6 +354,8 @@ function onTabClick(index: number) {
     padding-top: 20rpx;
     padding-bottom: 30rpx;
     box-sizing: content-box;
+    flex-shrink: 0;
+    flex-grow: 0;
   }
   .tab-item-text {
     font-size: 15px;

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

@@ -1,5 +1,6 @@
 import type { ThemeConfig } from "./ThemeDefine";
 
+/** 默认主题配置 */
 export const DefaultTheme : ThemeConfig = {
   varOverrides: {
     spaceSize: {
@@ -25,6 +26,7 @@ export const DefaultTheme : ThemeConfig = {
     default: {
       default: 'transparent',
       button: '#dddddd',
+      lightButton: '#ffffff',
       primary: '#007AFF',
       secondary: '#0462C7',
       success: '#4CAF50',
@@ -141,4 +143,75 @@ export const DefaultTheme : ThemeConfig = {
       fontSize: '24rpx',
     },
   }
+}
+
+/** 默认暗黑主题配置 */
+export const DefaultDarkTheme = {
+  ...DefaultTheme,
+  colorConfigs: {
+    ...DefaultTheme.colorConfigs,
+    default: {
+      ...DefaultTheme.colorConfigs.default,
+      button: '#666666',
+      lightButton: '#333333',
+      notice: '#000000',
+      light: '#3e3e3e',
+      dark: '#eeeeee',
+      white: '#000000',
+      black: '#ffffff',
+      grey: '#666666',
+      lightGrey: '#3d3d3d',
+      darkGrey: '#727272',
+      skeleton: 'rgba(255,255,255,0.1)',
+    },
+    pressed: {
+      ...DefaultTheme.colorConfigs.pressed,
+      default: 'rgba(255,255,255,0.1)',
+      white: '#181818',
+      black: '#999999',
+      grey: '#555555',
+      notice: '#222222',
+    },
+    mask: {
+      ...DefaultTheme.colorConfigs.mask,
+      default: 'rgba(0, 0, 0, 0.5)',
+      white: 'rgba(250, 250, 250, 0.9)',
+      primary: 'rgba(9, 96, 172, 0.3)',
+      info: 'rgba(33, 150, 243, 0.3)',
+      success: 'rgba(39, 137, 80, 0.4)',
+      warning: 'rgba(221, 135, 0, 0.3)',
+      danger: 'rgba(215, 9, 32, 0.3)',
+    },
+    background: {
+      ...DefaultTheme.colorConfigs.background,
+      page: '#1a1a1a',
+      imageBox: '#202020',
+      cell: '#000000',
+      bar: '#1a1a1a',
+      box: '#252525',
+      switch: '#252525',
+      notify: '#1a1a1a',
+      toast: 'rgba(0, 0, 0, 0.95)',
+      mask: 'rgba(0, 0, 0, 0.8)',
+      primary: 'rgba(0, 122, 255, 0.2)',
+      success: 'rgba(76, 175, 80, 0.2)',
+      warning: 'rgba(255, 193, 7, 0.2)',
+      danger: 'rgba(244, 67, 54, 0.2)',
+      info: 'rgba(33, 150, 243, 0.2)',
+    },
+    text: {
+      ...DefaultTheme.colorConfigs.text,
+      title: '#ffffff',
+      light: '#000000',
+      content: '#cccccc',
+      second: '#666666',
+    },
+    border: {
+      ...DefaultTheme.colorConfigs.border,
+      input: '#252525',
+      default: '#333333',
+      cell: '#333333',
+      light: '#444444',
+    },
+  },
 }

+ 88 - 33
src/components/theme/ThemeDefine.ts

@@ -1,5 +1,5 @@
-import { computed, inject, provide, type ComputedRef } from "vue";
-import { DefaultTheme } from "./Theme";
+import { computed, inject, provide, ref, shallowRef, type ComputedRef, type Ref } from "vue";
+import { DefaultDarkTheme, DefaultTheme } from "./Theme";
 import type { DynamicVarType } from "./ThemeTools";
 import { ObjectUtils } from "@imengyu/imengyu-utils";
 
@@ -24,6 +24,10 @@ export interface ThemeConfig {
   textConfigs: Record<string, Record<string, string|object>>,
 }
 
+function popInjectTheme() : ThemeConfig {
+  return inject<Ref<ThemeConfig>>(ThemeKey)?.value ?? DefaultTheme;
+}
+
 /**
  * 在PropDefault回调中使用主题默认值的函数
  * @param key 
@@ -31,7 +35,7 @@ export interface ThemeConfig {
  * @returns 
  */
 export function propGetThemeVar<T>(key: string, defaultValue?: T) : T {
-  const theme = (inject(ThemeKey, DefaultTheme) as ThemeConfig);
+  const theme = popInjectTheme();
   return theme?.varOverrides[key] ?? defaultValue;
 }
 /**
@@ -40,33 +44,40 @@ export function propGetThemeVar<T>(key: string, defaultValue?: T) : T {
  * @returns 回调函数的返回值
  */
 export function propGetThemeVar2(cb: (getVar: (key: string, defaultValue: any) => any, theme: ThemeConfig) => any) {
-  const theme = (inject(ThemeKey, DefaultTheme) as ThemeConfig);
+  const theme = popInjectTheme();
   return cb((key, defaultValue) => getVar(theme, key, defaultValue), theme);
 }
 
 
-export function provideTheme(theme: ThemeConfig) {
-  provide(ThemeKey, theme);
-}
 export function provideSomeThemeColor(record: Record<string, any>) {
-  const v = {
-    ...DefaultTheme,
-    colorConfigs: {
-      ...DefaultTheme.colorConfigs,
-      ...record
-    }
-  } as ThemeConfig;
-  provide(ThemeKey, v);
+ const topTheme = inject<Ref<ThemeConfig>>(ThemeKey);
+  const newTheme = computed(() => {
+    const topThemeValue = topTheme?.value ?? DefaultTheme;
+    const v = {
+      ...topThemeValue,
+      colorConfigs: {
+        ...topThemeValue.colorConfigs,
+        ...record
+      }
+    } as ThemeConfig;
+    return v;
+  });
+  provide(ThemeKey, newTheme);
 }
 export function provideSomeThemeVar(record: Record<string, any>) {
-  const v = {
-    ...DefaultTheme,
-    varOverrides: {
-      ...DefaultTheme.varOverrides,
-      ...record
-    }
-  } as ThemeConfig;
-  provide(ThemeKey, v);
+  const topTheme = inject<Ref<ThemeConfig>>(ThemeKey);
+  const newTheme = computed(() => {
+    const topThemeValue = topTheme?.value ?? DefaultTheme;
+    const v = {
+      ...topThemeValue,
+      varOverrides: {
+        ...topThemeValue.varOverrides,
+        ...record
+      }
+    } as ThemeConfig;
+    return v;
+  });
+  provide(ThemeKey, newTheme);
 }
 
 export function getVar<T>(theme : ThemeConfig, key: string, defaultValue: T) : T {
@@ -77,7 +88,8 @@ export function getVar<T>(theme : ThemeConfig, key: string, defaultValue: T) : T
  * 主题的组合代码
  */
 export function useTheme() {
-  const theme = inject(ThemeKey, DefaultTheme) as ThemeConfig;
+  const topTheme = inject<Ref<ThemeConfig>>(ThemeKey);
+  const theme = computed(() => topTheme?.value ?? DefaultTheme);
 
   function resolveThemeSize(inValue?: string|number, defaultValue?: string|number) : string|undefined {    
     const preResolve = resolveSize(inValue);
@@ -131,9 +143,9 @@ export function useTheme() {
     key = getVar(key, key);
     if (key.includes('.'))
       [type, key] = key.split('.');
-    let group = theme.colorConfigs[type || 'default'];
+    let group = theme.value.colorConfigs[type || 'default'];
     if (!group) 
-      group = theme.colorConfigs['default'];
+      group = theme.value.colorConfigs['default'];
     return group?.[key] ?? defaultValue;
   }
   function getSize(key: string, defaultValue?: string|number) {
@@ -144,14 +156,14 @@ export function useTheme() {
       [type, key] = key.split('.');
     let v: any = undefined;
     if (type) {
-      const group = theme.varOverrides[type];
+      const group = theme.value.varOverrides[type];
       v = group?.[key];
     } else
-      v = theme.varOverrides[key];
+      v = theme.value.varOverrides[key];
     return resolveSize(v ?? defaultValue);
   }
   function getText(key: string, defaultValue: Record<string, string>) {
-    return theme.textConfigs[key]?? defaultValue;
+    return theme.value.textConfigs[key]?? defaultValue;
   }
   function getVar<T>(key: string, defaultValue: T) : T {
     let rs = undefined;
@@ -159,10 +171,10 @@ export function useTheme() {
     if (key.includes('.'))
       [type, key] = key.split('.');
     if (type) {
-      const group = theme.varOverrides[type];
+      const group = theme.value.varOverrides[type];
       rs = group?.[key];
     } else
-      rs = theme.varOverrides[key];
+      rs = theme.value.varOverrides[key];
     return rs ?? defaultValue;
   } 
 
@@ -278,7 +290,50 @@ export interface ThemePaddingMargin {
 /**
  * 配置主题变量,建议在App.vue中调用
  * @param cb 回调函数,参数为默认主题配置,返回值为新的主题配置
+ * @returns 主题配置对象,返回currentTheme为当前主题,可以修改为其他主题对象,但请注意主题对象为shallowRef,
+ * 直接修改主题对象的属性不会触发主题更新,需要调用currentTheme.value = newTheme新的对象来更新主题。
+ */
+export function configTheme(
+  /** 
+   * 是否自动匹配系统深色主题
+   * @default true
+   */
+  autoMatchSystemDark?: boolean, 
+  /** 
+   * 回调函数,用于配置修改主题,参数为默认主题配置,返回值为新的主题配置
+   * @default undefined
+   * @returns 可以返回新的主题对象或者是传入的对象,第一个为亮色主题,第二个为深色主题
+   */
+  cb?: (defaultTheme: ThemeConfig, defaultDarkTheme: ThemeConfig) => [ThemeConfig,ThemeConfig]
+) {
+  let defaultTheme = ObjectUtils.clone(DefaultTheme);
+  let defaultDarkTheme = ObjectUtils.clone(DefaultDarkTheme);
+  const [theme, darkTheme] = cb?.(defaultTheme, defaultDarkTheme) ?? [defaultTheme, defaultDarkTheme];
+  const currentSystemDark = ref(uni.getSystemInfoSync().theme === 'dark');
+
+  console.log(uni.getSystemInfoSync());
+  
+  const currentTheme = shallowRef(currentSystemDark.value ? darkTheme : theme);
+  provide(ThemeKey, currentTheme);
+
+  if (autoMatchSystemDark !== false) {
+    // 监听系统主题变化
+    uni.onThemeChange((res) => {
+      currentSystemDark.value = res.theme === 'dark';
+      currentTheme.value = currentSystemDark.value ? darkTheme : theme;
+    });
+  }
+
+  return {
+    currentTheme,
+  }
+}
+/**
+ * 克隆默认主题,用于自定义主题
+ * @param cb 回调函数,参数为默认主题配置,返回值为新的主题配置
+ * @param dark 是否为深色主题,默认false为亮色主题
+ * @returns 新的主题配置对象
  */
-export function configTheme(cb: (defaultTheme: ThemeConfig) => ThemeConfig) {
-  provide(ThemeKey, cb(ObjectUtils.clone(DefaultTheme)));
+export function cloneTheme(dark?: boolean, cb?: (defaultTheme: ThemeConfig) => ThemeConfig) {
+  return cb?.(ObjectUtils.clone(dark ? DefaultDarkTheme : DefaultTheme)) ?? ObjectUtils.clone(dark ? DefaultDarkTheme : DefaultTheme);
 }

+ 1 - 1
src/pages/dig/admin/preview.vue

@@ -9,7 +9,7 @@
     />
 
     <FlexCol v-if="overviewLoader.content.value">
-      <SubTitle :title="overviewLoader.content.value.villageName || ''" />
+      <SubTitle :title="overviewLoader.content.value.villageName || querys.villageName" />
       <IntroBlock 
         small
         :descItems="[

+ 1 - 1
src/pages/dig/components/CollectModuleList.vue

@@ -75,7 +75,7 @@ async function loadList() {
           const formDefine = collectModuleInternalName ? getVillageInfoForm(collectModuleInternalName, -1) : undefined;
           return {
             ...item,
-            extra: item.total >= 0 ? `已采编 ${item.total}` : '',
+            extra: authStore.isAdmin && item.total >= 0 ? `已采编 ${item.total}` : '',
             enable: canCollectCatalog(item.id),
             catalogItem: item,
             goForm: collectModuleInternalName ? [ 

+ 12 - 2
src/pages/dig/components/TaskList.vue

@@ -2,6 +2,7 @@
 import Button from '@/components/basic/Button.vue';
 import Icon from '@/components/basic/Icon.vue';
 import Text from '@/components/basic/Text.vue';
+import Touchable from '@/components/feedback/Touchable.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import Height from '@/components/layout/space/Height.vue';
@@ -41,7 +42,16 @@ defineProps({
 </script>
 
 <template>
-  <FlexRow :padding="25" backgroundColor="white" :radius="20" justify="space-between" align="center">
+  <Touchable 
+    :padding="25" 
+    :radius="20" 
+    :touchable="button && enable"
+    direction="row"
+    backgroundColor="white" 
+    justify="space-between" 
+    align="center"
+    @click="$emit('click')"
+  >
     <FlexRow align="center">
       <FlexCol center radius="50%" backgroundColor="#efefef" :padding="20">
         <Icon :icon="icon || 'help-filling'" color="primary" type="material" :size="50" />
@@ -65,5 +75,5 @@ defineProps({
         :text="enable ? goButtonText : '未开放'"
       />  
     </FlexRow>
-  </FlexRow>
+  </Touchable>
 </template>

+ 7 - 6
src/pages/dig/index.vue

@@ -18,7 +18,7 @@
             buttonClick: () => {},
           }"
         >
-          <FlexCol>
+          <FlexCol :gap="10">
             <FlexRow 
               v-for="item in villageListLoader.content.value"
               :key="item.id"
@@ -37,11 +37,11 @@
                 />
                 <H3>{{ item.villageName }}</H3>
               </FlexRow>
-              <FlexRow align="center" :gap="10">
-                <Button v-if="authStore.isAdmin" type="primary" size="small" @click="goManagePage(item)">管理</Button>
-                <Button v-if="authStore.isAdmin" size="small" @click="goPreviewDigPage(item)">预览</Button>
-                <Button type="default" size="small" icon="edit-filling" @click="goSubmitDigPage(item)">采编</Button>
-              </FlexRow>
+              <ButtonGroup direction="row" align="center" :gap="0">
+                <Button v-if="authStore.isAdmin" icon="edit-filling" size="small" @click="goManagePage(item)">管理</Button>
+                <Button v-if="authStore.isAdmin" size="small" icon="browse" @click="goPreviewDigPage(item)">预览</Button>
+                <Button type="primary" size="small" icon="edit-filling" @click="goSubmitDigPage(item)">采编</Button>
+              </ButtonGroup>
             </FlexRow>
           </FlexCol>
         </SimplePageContentLoader>
@@ -81,6 +81,7 @@ import FlexRow from '@/components/layout/FlexRow.vue';
 import H3 from '@/components/typography/H3.vue';
 import Text from '@/components/basic/Text.vue';
 import Height from '@/components/layout/space/Height.vue';
+import ButtonGroup from '@/components/basic/ButtonGroup.vue';
 
 const authStore = useAuthStore();
 const collectStore = useCollectStore();