Bläddra i källkod

📦 首页7 选择城市

快乐的梦鱼 2 veckor sedan
förälder
incheckning
07b880f3ce
89 ändrade filer med 1245 tillägg och 7019 borttagningar
  1. 14 0
      .cursor/rules/web-scraping-devtools-first.mdc
  2. 1 1
      src/App.vue
  3. 129 0
      src/api/light/FollowVillageApi.ts
  4. 35 3
      src/api/map/MapApi.ts
  5. 11 1
      src/common/components/RequireLogin.vue
  6. 0 32
      src/common/components/SimplePageListLoader.vue
  7. 1 1
      src/common/components/parts/Box.vue
  8. 1 1
      src/common/components/parts/Box2LineImageRightShadow.vue
  9. 3 3
      src/common/components/parts/Box2LineLargeImageUserShadow.vue
  10. 0 54
      src/common/composeabe/SimplePageContentLoader.ts
  11. 5 5
      src/common/config/Theme.ts
  12. 6 6
      src/components/basic/Button.vue
  13. 1 1
      src/components/basic/Cell.vue
  14. 0 0
      src/components/composeabe/LoadQuerys.ts
  15. 0 0
      src/components/composeabe/MemoryTimeOut.ts
  16. 20 0
      src/components/composeabe/StorageVar.ts
  17. 4 3
      src/common/composeabe/LoaderCommon.ts
  18. 14 13
      src/common/composeabe/SimpleDataLoader.ts
  19. 22 15
      src/common/composeabe/SimplePageListLoader.ts
  20. 2 1
      src/components/display/Avatar.vue
  21. 2 2
      src/components/display/PreviewItem.vue
  22. 2 2
      src/components/display/title/SubTitle.vue
  23. 2 2
      src/components/feedback/Alert.vue
  24. 7 7
      src/components/layout/BaseView.ts
  25. 15 15
      src/components/layout/FlexView.vue
  26. 2 0
      src/components/layout/space/SafeAreaPadding.vue
  27. 1 1
      src/components/layout/space/StatusBarSpace.vue
  28. 27 6
      src/components/list/FixedVirtualList.vue
  29. 5 10
      src/components/list/IndexList.vue
  30. 8 8
      src/common/components/SimplePageContentLoader.vue
  31. 35 0
      src/components/loader/SimplePageListLoader.vue
  32. 10 10
      src/components/theme/Theme.ts
  33. 13 10
      src/components/theme/ThemeDefine.ts
  34. 11 47
      src/components/theme/ThemeTools.ts
  35. 71 0
      src/components/thirdPart/pinyinUtil.d.ts
  36. 364 0
      src/components/thirdPart/pinyinUtil.js
  37. 8 0
      src/components/thirdPart/pinyin_dict_firstletter.js
  38. 9 0
      src/components/utils/PingyinUtils.ts
  39. 1 0
      src/env.d.ts
  40. 1 4
      src/pages.json
  41. 1 1
      src/pages/article/common/CommonContent.ts
  42. 9 9
      src/pages/article/common/CommonListPage.vue
  43. 1 1
      src/pages/article/common/list.vue
  44. 7 8
      src/pages/article/details.vue
  45. 1 1
      src/pages/article/web/ewebview.vue
  46. 20 5
      src/pages/components/LightMap.vue
  47. 1 1
      src/pages/dig/admin/components/VolunteerItem.vue
  48. 9 9
      src/pages/dig/admin/index.vue
  49. 4 4
      src/pages/dig/admin/preview.vue
  50. 6 6
      src/pages/dig/admin/review.vue
  51. 1 1
      src/pages/dig/admin/volunteer.vue
  52. 1 1
      src/pages/dig/components/CollectModuleList.vue
  53. 1 1
      src/pages/dig/components/TaskList.vue
  54. 4 4
      src/pages/dig/details.vue
  55. 1 1
      src/pages/dig/forms/common.vue
  56. 1 1
      src/pages/dig/forms/composeable/TaskEntryForm.ts
  57. 9 9
      src/pages/dig/forms/list.vue
  58. 12 12
      src/pages/dig/forms/submits.vue
  59. 1 1
      src/pages/dig/forms/task.vue
  60. 8 8
      src/pages/dig/index.vue
  61. 1 1
      src/pages/dig/sharereg/bind-wx.vue
  62. 1 1
      src/pages/dig/sharereg/share-reg-page.vue
  63. 2 2
      src/pages/home/about/about.vue
  64. 2 2
      src/pages/home/about/contract.vue
  65. 60 0
      src/pages/home/components/CitySelect.vue
  66. 4 1
      src/pages/home/components/VillageMiniMap.vue
  67. 42 0
      src/pages/home/components/VillageMyFollow.vue
  68. 2 2
      src/pages/home/components/VillageRankList.vue
  69. 1 1
      src/pages/home/components/VillageUserRankList.vue
  70. 7 8
      src/pages/home/discover/details.vue
  71. 4 4
      src/pages/home/discover/index.vue
  72. 1 1
      src/pages/home/discover/list.vue
  73. 112 12
      src/pages/home/index.vue
  74. 8 8
      src/pages/home/light/details.vue
  75. 9 4
      src/pages/home/light/submit-map.vue
  76. 1 1
      src/pages/home/light/submit.vue
  77. 9 9
      src/pages/home/village/details.vue
  78. 1 1
      src/pages/home/village/index.vue
  79. 10 10
      src/pages/home/village/introd/card.vue
  80. 3 3
      src/pages/home/village/introd/tree.vue
  81. 1 1
      src/pages/index.vue
  82. 1 1
      src/pages/test/blank.vue
  83. 1 1
      src/pages/user/index.vue
  84. 1 1
      src/pages/user/login.vue
  85. 0 49
      src/wxcomponents/cax/cax.js
  86. 0 4
      src/wxcomponents/cax/cax.json
  87. 0 12
      src/wxcomponents/cax/cax.wxml
  88. 0 0
      src/wxcomponents/cax/cax.wxss
  89. 0 6531
      src/wxcomponents/cax/index.js

+ 14 - 0
.cursor/rules/web-scraping-devtools-first.mdc

@@ -0,0 +1,14 @@
+---
+description: 网页抓取/页面检查优先用 Browser DevTools MCP
+alwaysApply: true
+---
+
+# 网页抓取优先使用 Browser DevTools MCP
+
+当任务需要**打开网页、抓取页面内容、定位元素、复现交互、查看网络请求**时,优先使用 Browser DevTools MCP,而不是直接用普通网页抓取/猜测选择器。
+
+- **结构优先**:导航到页面后,先用 ARIA 快照理解页面结构与可交互元素(必要时再用 AX tree)。
+- **交互前必快照**:任何点击/输入/选择/滚动等交互之前,先获取快照拿到真实 ref/selector,禁止凭截图或臆测 CSS 选择器。
+- **按需取内容**:需要正文/表格/接口参数时,再获取 HTML(可按 selector 范围化);只有在需要验证视觉效果时才截图。
+- **批量执行**:多步操作(填表+提交+等待+再抓取)尽量用批量执行能力合并成一次调用,减少往返与不确定性。
+

+ 1 - 1
src/App.vue

@@ -9,7 +9,7 @@ import { getCurrentPageUrl, navTo } from './components/utils/PageAction';
 import { onError, onLaunch } from '@dcloudio/uni-app'
 import { RequestApiConfig, RequestApiError } from '@imengyu/imengyu-utils';
 import ApiCofig from './common/config/ApiCofig';
-import MemoryTimeOut from './common/composeabe/MemoryTimeOut';
+import MemoryTimeOut from './components/composeabe/MemoryTimeOut';
 import BugReporter from './common/BugReporter';
 
 const authStore = useAuthStore();

+ 129 - 0
src/api/light/FollowVillageApi.ts

@@ -0,0 +1,129 @@
+import { DataModel, transformArrayDataModel } from '@imengyu/js-request-transform';
+import { AppServerRequestModule } from '../RequestModules';
+import { transformSomeToArray } from '../Utils';
+import { VillageListItem } from './LightVillageApi';
+
+export class FollowedUserListItem extends DataModel<FollowedUserListItem> {
+  constructor() {
+    super(FollowedUserListItem, '关注用户');
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+      userId: { clientSide: 'number', serverSide: 'number' },
+    };
+    this._convertKeyType = (key) => {
+      if (key.endsWith('timeText')) return undefined;
+      if (key.endsWith('time')) {
+        return {
+          clientSide: 'date',
+          serverSide: 'number',
+        };
+      }
+      return undefined;
+    };
+  }
+
+  id!: number;
+  nickname = '';
+  username = '';
+  avatar = '';
+  mobile = '';
+  gender = 0 as number | null;
+  birthday = null as string | null;
+  userId = 0 as number | null;
+  followtime = '' as string | null;
+  prevtime = null as Date | null;
+  logintime = null as Date | null;
+  jointime = null as Date | null;
+  prevtimeText = '' as string | null;
+  logintimeText = '' as string | null;
+  jointimeText = '' as string | null;
+}
+
+export class FollowVillageApi extends AppServerRequestModule<DataModel> {
+  constructor() {
+    super();
+  }
+
+  async followVillage(villageId: number) {
+    const res = await this.post<null>('/village/village/follow', '关注村社', {
+      village_id: villageId,
+    });
+    return res.requireData();
+  }
+
+  async unfollowVillage(villageId: number) {
+    const res = await this.post<null>('/village/village/unfollow', '取消关注村社', {
+      village_id: villageId,
+    });
+    return res.requireData();
+  }
+
+  async getFollowVillageList(options?: {
+    page?: number;
+    pageSize?: number;
+    keywords?: string;
+    region?: number;
+    userId?: number;
+  }) {
+    const res = await this.post<{
+      total: number;
+      per_page?: number;
+      current_page?: number;
+      last_page?: number;
+      data: any[];
+    }>('/village/village/followVillage', '关注村社列表', {
+      page: options?.page,
+      pageSize: options?.pageSize,
+      keywords: options?.keywords,
+      region: options?.region,
+      user_id: options?.userId,
+    });
+
+    const data = res.requireData();
+    return {
+      total: data.total ?? 0,
+      list: transformArrayDataModel<VillageListItem>(
+        VillageListItem,
+        transformSomeToArray(data.data),
+        '关注村社',
+        true,
+      ),
+    };
+  }
+
+  async getFollowedUsers(options: {
+    villageId: number;
+    page?: number;
+    pageSize?: number;
+    keywords?: string;
+    region?: number;
+  }) {
+    const res = await this.post<{
+      total: number;
+      per_page?: number;
+      current_page?: number;
+      last_page?: number;
+      data: any[];
+    }>('/village/village/followedUsers', '关注用户列表', {
+      village_id: options.villageId,
+      page: options.page,
+      pageSize: options.pageSize,
+      keywords: options.keywords,
+      region: options.region,
+    });
+
+    const data = res.requireData();
+    return {
+      total: data.total ?? 0,
+      list: transformArrayDataModel<FollowedUserListItem>(
+        FollowedUserListItem,
+        transformSomeToArray(data.data),
+        '关注用户',
+        true,
+      ),
+    };
+  }
+}
+
+export default new FollowVillageApi();

+ 35 - 3
src/api/map/MapApi.ts

@@ -25,6 +25,19 @@ export class Poi extends DataModel<Poi> {
   cityname = '';
 }
 
+export interface CityItem {
+  first_letter: string;
+  code: string;
+  name: string;
+  /**
+   * 0:省
+   * 1:市
+   * 2:区
+   */
+  type: number;
+  children?: CityItem[];
+}
+
 export class MapApi extends MapServerRequestModule<DataModel> {
 
   constructor() {
@@ -64,14 +77,33 @@ export class MapApi extends MapServerRequestModule<DataModel> {
       .then(res => (res.data as any).regeocode.formattedAddress as string)
       .catch(e => { throw e });
   }
-  loadCityData() {
+  /**
+   * 简单获取地区编码
+   *
+   * 匹配方式:
+   * 1. 如果匹配到单个地区数据,则直接返回
+   * 2. 如果匹配到多个地区数据,则需要根据省份和城市名称来区分
+   * @param name 地区名称
+   * @param province 省份名称,可选,当出现同名地区时,需要省份名称来区分
+   * @param city 城市名称,可选,当出现同名地区时,需要城市名称来区分
+   */
+  simpleGetRegionCode(name: string, province?: string, city?: string) {
+    return this.get<string>(`https://update-server1.imengyu.top/content/map/simpleGetRegionCode`, `简单获取地区编码`, {
+      name,
+      province,
+      city,
+    })
+      .then(res => res.requireData() as string)
+      .catch(e => { throw e });
+  }
+  loadCityData(full = false) : Promise<CityItem[]> {
     return new Promise((resolve, reject) => {
       uni.request({
-        url: 'https://mn.wenlvti.net/app_static/xiangyuan/data/ChinaCityData.slim.json',
+        url: `https://mn.wenlvti.net/app_static/xiangyuan/data/ChinaCityData${full ? '' : '.slim'}.json`,
         method: 'GET',
         success(result) {
           if (result.statusCode === 200) {
-            resolve(result.data);
+            resolve(result.data as CityItem[]);
           } else {
             reject(new Error(`请求失败,状态码:${result.statusCode}`));
           }

+ 11 - 1
src/common/components/RequireLogin.vue

@@ -14,11 +14,15 @@ import Text from '@/components/basic/Text.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import { navTo } from '@/components/utils/PageAction';
 import { useAuthStore } from '@/store/auth';
-import { computed } from 'vue';
+import { computed, watch } from 'vue';
 
 const authStore = useAuthStore();
 const isLogged = computed(() => authStore.isLogged);
 
+const emit = defineEmits<{
+  (e: 'loginSuccess'): void;
+}>();
+
 defineProps({	
   unLoginMessage : {
     type: String,
@@ -26,6 +30,12 @@ defineProps({
   },
 })
 
+watch(isLogged, (newVal) => {
+  if (newVal) {
+    emit('loginSuccess');
+  }
+})
+
 function goLogin() {
   navTo('/pages/user/login');
 }

+ 0 - 32
src/common/components/SimplePageListLoader.vue

@@ -1,32 +0,0 @@
-<template>
-  <slot />
-  <Loadmore
-    v-if="loader.loadStatus.value == 'loading' 
-      || (loader.loadStatus.value == 'nomore' && !$slots.empty)" 
-    :status="loader.loadStatus.value" 
-  />
-  <slot v-else-if="loader.loadStatus.value == 'empty' && $slots.empty" name="empty" />
-  <Loadmore 
-    v-else-if="loader.loadStatus.value == 'error'"
-    status="loadmore" 
-    :loadmoreText="loader.loadError.value" 
-    @loadmore="handleRetry" 
-  />
-</template>
-
-<script setup lang="ts">
-import type { PropType } from 'vue';
-import type { ISimplePageListLoader } from '../composeabe/SimplePageListLoader';
-import Loadmore from '@/components/display/loading/Loadmore.vue';
-
-const props = defineProps({	
-  loader: {
-    type: Object as PropType<ISimplePageListLoader<any, any>>,
-    default: null,
-  },
-})
-
-function handleRetry() {
-  props.loader.loadData();
-}
-</script>

+ 1 - 1
src/common/components/parts/Box.vue

@@ -5,7 +5,7 @@
       backDropFilter: 'blur(10px)',
     }"
     :padding="padding ? [35,25] : 0" 
-    radius="radius.large" 
+    radius="radius.lg" 
     shadow="light"
   >
     <SubTitle v-if="title" :title="title" :showMore="showMore" @moreClicked="emit('moreClicked')">

+ 1 - 1
src/common/components/parts/Box2LineImageRightShadow.vue

@@ -15,7 +15,7 @@
       <Image 
         :width="wideImage ? 250 : 150"
         :height="150"
-        radius="radius.small"
+        radius="radius.sm"
         :flexShrink="0"
         :src="image"
         mode="aspectFill"

+ 3 - 3
src/common/components/parts/Box2LineLargeImageUserShadow.vue

@@ -4,7 +4,7 @@
     :flexShrink="fixSize ? 0 : undefined"
     :flexGrow="fixSize ? undefined : 1"
     :gap="10"
-    radius="radius.medium"
+    radius="radius.md"
     overflow="hidden"
     position="relative"
     backgroundColor="white"
@@ -23,7 +23,7 @@
       v-if="image" 
       :height="300" 
       width="100%"
-      radius="radius.medium"
+      radius="radius.md"
       :src="image" 
       mode="aspectFill" 
     />
@@ -51,7 +51,7 @@
       :top="0"
       :right="0"
       :margin="15"
-      radius="radius.large" 
+      radius="radius.lg" 
       :padding="15"
       backgroundColor="background.primary" 
     >

+ 0 - 54
src/common/composeabe/SimplePageContentLoader.ts

@@ -1,54 +0,0 @@
-import { onMounted, ref, type Ref } from "vue";
-import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
-
-export interface ISimplePageContentLoader<T, P> extends ILoaderCommon<P> {
-  content: Ref<T|null>;
-}
-
-/**
- * 说明:
- * * 简单页面内容加载器组合式代码。
- * @param loader 数据加载函数
- * @param options 选项
- */
-export function useSimplePageContentLoader<T, P = any>(
-  loader: (params?: P) => Promise<T>,
-  options?: {
-    immediate?: boolean;
-  }
-)  : ISimplePageContentLoader<T, P>
- {
-
-  const content = ref<T|null>(null) as Ref<T|null>;
-  const loadStatus = ref<LoaderLoadType>('loading');
-  const loadError = ref('');
-
-  let lastParams: P | undefined;
-
-  async function loadData(params?: P) {
-    if (params)
-      lastParams = params;
-    loadStatus.value = 'loading';
-    try {
-      const res = (await loader(params ?? lastParams)) as T;
-      content.value = res;
-      loadStatus.value = 'finished';
-      loadError.value = '';
-    } catch(e) {
-      loadError.value = '' + e;
-      loadStatus.value = 'error';
-    }
-  }
-
-  onMounted(() => {
-    if (options?.immediate)
-      loadData();
-  });
-
-  return {
-    content,
-    loadStatus,
-    loadError,
-    loadData,
-  }
-}

+ 5 - 5
src/common/config/Theme.ts

@@ -15,11 +15,11 @@ export function configAppTheme() {
     theme.colorConfigs.text.second = '#95755a';
     theme.colorConfigs.text.titleLight = '#55989a';
 
-    theme.varOverrides.radius.smaller = '5rpx';
-    theme.varOverrides.radius.small = '10rpx';
-    theme.varOverrides.radius.medium = '20rpx';
-    theme.varOverrides.radius.large = '30rpx';
-    theme.varOverrides.radius.larger = '50rpx';
+    theme.varOverrides.radius.xs = '5rpx';
+    theme.varOverrides.radius.sm = '10rpx';
+    theme.varOverrides.radius.md = '20rpx';
+    theme.varOverrides.radius.lg = '30rpx';
+    theme.varOverrides.radius.lgr = '50rpx';
 
     theme.textConfigs.primaryTitle = {
       color: 'text.title',

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

@@ -221,11 +221,11 @@ const props = withDefaults(defineProps<ButtonProp>(), {
 });
 
 const FonstSizes = computed(() => ({
-  mini: themeContext.resolveThemeSize('ButtonMiniFonstSize', 'fontSize.mini'),
-  small: themeContext.resolveThemeSize('ButtonSmallFonstSize', 'fontSize.small'),
-  medium: themeContext.resolveThemeSize('ButtonMediumFonstSize', 'fontSize.medium'),
-  large: themeContext.resolveThemeSize('ButtonLargeFonstSize', 'fontSize.large'),
-  larger: themeContext.resolveThemeSize('ButtonLargerFonstSize', 'fontSize.larger'),
+  mini: themeContext.resolveThemeSize('ButtonMiniFonstSize', 'fontSize.xs'),
+  small: themeContext.resolveThemeSize('ButtonSmallFonstSize', 'fontSize.sm'),
+  medium: themeContext.resolveThemeSize('ButtonMediumFonstSize', 'fontSize.md'),
+  large: themeContext.resolveThemeSize('ButtonLargeFonstSize', 'fontSize.lg'),
+  larger: themeContext.resolveThemeSize('ButtonLargerFonstSize', 'fontSize.xl'),
 }));
 const themeVars = themeContext.getVars({
   ButtonBorderWidth: 1.5,
@@ -406,7 +406,7 @@ const currentStyle = computed(() => {
   //  sizeStyle.paddingHorizontal = `calc(${sizeStyle.paddingHorizontal} + ${themeContext.resolveSize(props.radius / 4)})`;
 
   //内边距样式的强制设置
-  configPadding(speicalStyle, themeContext.theme.value, props.padding);
+  configPadding(speicalStyle, themeContext, props.padding);
 
   return {
     color: (colorStyle).color,

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

@@ -305,7 +305,7 @@ const style = computed(() => {
   }
 
   //内边距样式的强制设置
-  configPadding(styleObj, theme.theme.value, props.padding);
+  configPadding(styleObj, theme, props.padding);
 
   //边框设置
   if (props.topBorder)

src/common/composeabe/LoadQuerys.ts → src/components/composeabe/LoadQuerys.ts


src/common/composeabe/MemoryTimeOut.ts → src/components/composeabe/MemoryTimeOut.ts


+ 20 - 0
src/components/composeabe/StorageVar.ts

@@ -0,0 +1,20 @@
+import { onMounted, ref, watch } from "vue";
+
+export function useStorageVar<T>(key: string, defaultValue: T) {
+  const value = ref<T>(defaultValue);
+
+  watch(value, (newValue) => {
+    uni.setStorageSync(key, JSON.stringify(newValue));
+  })
+
+  onMounted(() => {
+    const storageValue = uni.getStorageSync(key);
+    if (storageValue) {
+      value.value = JSON.parse(storageValue);
+    }
+  })
+
+  return {
+    value,
+  }
+}

+ 4 - 3
src/common/composeabe/LoaderCommon.ts

@@ -8,7 +8,8 @@ export type LoaderLoadType = 'loading' | 'finished' | 'nomore' | 'error' | 'empt
  * @param P 加载参数类型
  */
 export interface ILoaderCommon<P> {
-  loadError: Ref<string>;
-  loadStatus: Ref<LoaderLoadType>;
-  loadData: (params?: P, refresh?: boolean) => Promise<void>;
+  error: Ref<string>;
+  status: Ref<LoaderLoadType>;
+  load: (refresh?: boolean, params?: P) => Promise<void>;
+  reload: () => Promise<void>;
 }

+ 14 - 13
src/common/composeabe/SimpleDataLoader.ts

@@ -23,15 +23,15 @@ export function useSimpleDataLoader<T, P = any>(
  {
 
   const content = ref<T|null>(null) as Ref<T|null>;
-  const loadStatus = ref<LoaderLoadType>('loading');
-  const loadError = ref('');
+  const status = ref<LoaderLoadType>('loading');
+  const error = ref('');
 
   let lastParams: P | undefined;
 
-  async function loadData(params?: P) {
+  async function load(refresh?: boolean, params?: P) {
     if (params)
       lastParams = params;
-    loadStatus.value = 'loading';
+    status.value = 'loading';
     if (showGlobalLoading)
       uni.showLoading({ title: '加载中...' });
 
@@ -39,13 +39,13 @@ export function useSimpleDataLoader<T, P = any>(
       const res = (await loader(params ?? lastParams)) as T;
       content.value = res;
       if (Array.isArray(res) && emptyIfArrayEmpty && (res as any[]).length === 0)
-        loadStatus.value = 'nomore';
+        status.value = 'nomore';
       else
-        loadStatus.value = 'finished';
-      loadError.value = '';
+        status.value = 'finished';
+      error.value = '';
     } catch(e) {
-      loadError.value = '' + e;
-      loadStatus.value = 'error';
+      error.value = '' + e;
+      status.value = 'error';
       console.log(e);
       
     } finally {
@@ -57,16 +57,17 @@ export function useSimpleDataLoader<T, P = any>(
   onMounted(() => {
     if (loadWhenMounted) {
       setTimeout(() => {
-        loadData();
+        load(false);
       }, (0.5 + Math.random()) * 500);
     }
   })
 
   return {
     content,
-    loadStatus,
-    loadError,
-    loadData,
+    status,
+    error,
+    load,
+    reload: () => load(true),
     getLastParams: () => lastParams,
   }
 }

+ 22 - 15
src/common/composeabe/SimplePageListLoader.ts

@@ -1,5 +1,5 @@
 import { onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
-import { ref, type Ref } from "vue";
+import { onMounted, ref, type Ref } from "vue";
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
 
 export interface ISimplePageListLoader<T, P> extends ILoaderCommon<P> {
@@ -18,12 +18,13 @@ export interface ISimplePageListLoader<T, P> extends ILoaderCommon<P> {
 export function useSimplePageListLoader<T, P = any>(
   pageSize: number, 
   loader: (page: number, pageSize: number, params?: P) => Promise<{ list: T[], total: number }>,
+  loadWhenMounted = true,
   showGlobalLoading = false,
 )  : ISimplePageListLoader<T, P>
 {
   
-  const loadStatus = ref<LoaderLoadType>('loading');
-  const loadError = ref('');
+  const status = ref<LoaderLoadType>('loading');
+  const error = ref('');
   const page = ref(0);
   const total = ref(0);
   const list = ref<T[]>([]) as Ref<T[]>;
@@ -31,7 +32,7 @@ export function useSimplePageListLoader<T, P = any>(
   let lastParams: P | undefined;
   let loading = false;
 
-  async function loadData(params?: P, refresh: boolean = false) {
+  async function load(refresh: boolean = false, params?: P) {
     if (loading) 
       return;
     if (params)
@@ -41,7 +42,7 @@ export function useSimplePageListLoader<T, P = any>(
       list.value = []; 
     }
     page.value++;
-    loadStatus.value = 'loading';
+    status.value = 'loading';
     loading = true;
     if (showGlobalLoading)
       uni.showLoading({ title: '加载中...' });
@@ -50,12 +51,12 @@ export function useSimplePageListLoader<T, P = any>(
       const res = (await loader(page.value, pageSize, lastParams));
       list.value = list.value.concat(res.list as T[]);
       total.value = res.total;
-      loadStatus.value = res.list.length > 0 ? 'finished' : (list.value.length > 0 ? 'nomore' : 'empty');
-      loadError.value = '';
+      status.value = res.list.length > 0 ? 'finished' : (list.value.length > 0 ? 'nomore' : 'empty');
+      error.value = '';
       loading = false;
     } catch(e) {
-      loadError.value = '' + e;
-      loadStatus.value = 'error';
+      error.value = '' + e;
+      status.value = 'error';
       loading = false;
     } finally {
       if (showGlobalLoading)
@@ -64,24 +65,30 @@ export function useSimplePageListLoader<T, P = any>(
   }
 
   onPullDownRefresh(() => {
-    loadData(lastParams, true).then(() => {
+    load(true, lastParams).then(() => {
       uni.stopPullDownRefresh();
     }).catch(() => {
       uni.stopPullDownRefresh();
     });
   });
   onReachBottom(() => {
-    if (loadStatus.value == 'nomore')
+    if (status.value == 'nomore')
       return;
-    loadData(lastParams, false);
+    load(false, lastParams);
   });
 
+  onMounted(() => {
+    if (loadWhenMounted)
+      load(false, lastParams);
+  })
+
   return {
     list,
     total,
     page,
-    loadStatus,
-    loadError,
-    loadData,
+    status,
+    error,
+    load,
+    reload: () => load(true),
   }
 }

+ 2 - 1
src/components/display/Avatar.vue

@@ -15,6 +15,7 @@
       }"
     />
     <text 
+      class="text"
       v-else 
       :style="{ 
         width: themeContext.resolveThemeSize(size),
@@ -124,7 +125,7 @@ defineEmits([ 'click' ]);
   overflow: hidden;
   flex-shrink: 0;
 
-  text {
+  .text {
     display: block;
     text-align: center;
     user-select: none;

+ 2 - 2
src/components/display/PreviewItem.vue

@@ -23,7 +23,7 @@
           :key="k" 
           :width="100" 
           :height="100"
-          radius="radius.small"
+          radius="radius.sm"
           round
           v-bind="(valueProps as ImageProps)" 
           :src="item"
@@ -34,7 +34,7 @@
         v-else
         :width="100"
         :height="100"
-        radius="radius.small"
+        radius="radius.sm"
         round
         v-bind="(valueProps as ImageProps)"
         :src="value"

+ 2 - 2
src/components/display/title/SubTitle.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { useTheme, type ThemePaddingMargin, type ThemePaddingMarginProp } from '@/components/theme/ThemeDefine';
+import { useTheme, type ThemePaddingOrMarginType } from '@/components/theme/ThemeDefine';
 import Icon from '@/components/basic/Icon.vue';
 import Text, { type TextProps } from '@/components/basic/Text.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
@@ -13,7 +13,7 @@ const props = withDefaults(defineProps<{
   showMore?: boolean,
   badgeColor?: string,
   backgroundColor?: string,
-  padding?: ThemePaddingMarginProp,
+  padding?: ThemePaddingOrMarginType,
   titleProps?: TextProps,
   badgeStyle?: object,
 }>(), {

+ 2 - 2
src/components/feedback/Alert.vue

@@ -54,7 +54,7 @@
         <Text 
           v-if="message"
           :text="message"
-          :fontSize="themeContext.resolveThemeSize('AlertTitleFontSize', 'fontSize.medium')"
+          :fontSize="themeContext.resolveThemeSize('AlertTitleFontSize', 'fontSize.md')"
           :fontWeight="themeContext.getVar('AlertTitleFontWeight', 'bold')"
           :color="color"
           :innerStyle="themeStyles.message.value"
@@ -65,7 +65,7 @@
         <Text 
           v-if="description"
           :text="description"
-          :fontSize="themeContext.resolveThemeSize('AlertMessageFontSize', 'fontSize.small')"
+          :fontSize="themeContext.resolveThemeSize('AlertMessageFontSize', 'fontSize.sm')"
           :color="color"
           :lines="lines"
           :innerStyle="{

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

@@ -1,5 +1,5 @@
 import { computed } from "vue";
-import { useTheme } from "../theme/ThemeDefine";
+import { useTheme, type ThemePaddingMargin } from "../theme/ThemeDefine";
 import type { FlexProps } from "./FlexView.vue";
 import { configMargin, configPadding } from "../theme/ThemeTools";
 import { ObjectUtils } from "@imengyu/imengyu-utils";
@@ -36,9 +36,9 @@ export function useBaseViewStyleBuilder(props: FlexProps) {
     }
 
     //内边距样式
-    configPadding(obj, themeContext.theme.value, props.padding as any);
+    configPadding(obj, themeContext, props.padding);
     //外边距样式
-    configMargin(obj, themeContext.theme.value, props.margin as any);
+    configMargin(obj, themeContext, props.margin);
 
     if (props.innerStyle)
       ObjectUtils.cloneValuesToObject(props.innerStyle, obj);
@@ -48,24 +48,24 @@ export function useBaseViewStyleBuilder(props: FlexProps) {
         obj.paddingTop = obj.paddingVertical;
       if (obj.paddingBottom === undefined)
         obj.paddingBottom = obj.paddingVertical;
-      obj.paddingVertical = undefined;
+      delete obj.paddingVertical;
     }
     if (obj.paddingHorizontal) {
       if (obj.paddingLeft === undefined)
         obj.paddingLeft = obj.paddingHorizontal;
       if (obj.paddingRight === undefined)
         obj.paddingRight = obj.paddingHorizontal;
-      obj.paddingHorizontal = undefined;
+      delete obj.paddingHorizontal;
     }
     if (obj.marginVertical) {
       obj.marginTop = obj.marginVertical;
       obj.marginBottom = obj.marginVertical;
-      obj.marginVertical = undefined;
+      delete obj.marginVertical;
     }
     if (obj.marginHorizontal) {
       obj.marginLeft = obj.marginHorizontal;
       obj.marginRight = obj.marginHorizontal;
-      obj.marginHorizontal = undefined;
+      delete obj.marginHorizontal;
     }
 
     if (props.inset) {

+ 15 - 15
src/components/layout/FlexView.vue

@@ -20,15 +20,15 @@
  * 组件说明:Flex组件,用于一些布局中快速写容器,是一系列盒子的基础组件。
  */
 import { computed, getCurrentInstance } from 'vue';
-import { type ThemePaddingMargin } from '../theme/ThemeDefine';
 import { RandomUtils } from '@imengyu/imengyu-utils';
-import { useBaseViewStyleBuilder } from './BaseView';
+import { useBaseViewStyleBuilder, type ThemePaddingOrMarginType, type ThemeSizeType } from './BaseView';
 
 export type FlexDirection = "row"|"column"|'row-reverse'|'column-reverse';
 export type FlexJustifyType =  'flex-start' | 'flex-end' | 'center' |'space-between' |'space-around' |'space-evenly';
 export type FlexAlignType = "stretch"|'center'|'start'|'end'|'flex-start' | 'flex-end' | 'center';
 export type StateType = 'default' | 'active' | 'pressed';
 
+
 export interface FlexProps {
   innerId?: string,
   /**
@@ -74,7 +74,7 @@ export interface FlexProps {
   /**
    * flexBasis 参数
    */
-  flexBasis?: number|string,
+  flexBasis?: ThemeSizeType,
   /**
    * flexGrow 参数
    */
@@ -86,31 +86,31 @@ export interface FlexProps {
   /**
    * 内边距参数(支持数字或数组)
    */
-  padding?: number|Array<number>|ThemePaddingMargin,
+  padding?: ThemePaddingOrMarginType,
   /**
    * 外边距参数(支持数字或数组)
    */
-  margin?: number|Array<number>|ThemePaddingMargin,
+  margin?: ThemePaddingOrMarginType,
   /**
    * 位置参数
    */
-  top?: number|string,
-  right?: number|string,
-  bottom?: number|string,
-  left?: number|string,
+  top?: ThemeSizeType,
+  right?: ThemeSizeType,
+  bottom?: ThemeSizeType,
+  left?: ThemeSizeType,
   /**
    * 设置元素与其父元素之间的距离(支持数字或数组,等同于 top, right, bottom, left,
    * 但优先级比它们低),
    */
-  inset?: (number|string)[]|string|number,
+  inset?: ThemePaddingOrMarginType,
   /**
    * 圆角
    */
-  radius?: number|string,
+  radius?: ThemeSizeType,
   /**
    * 间距
    */
-  gap?: number|number[]|string,
+  gap?: ThemeSizeType|ThemeSizeType[],
   /**
    * 背景颜色
    */
@@ -130,7 +130,7 @@ export interface FlexProps {
   /**
    * 边框宽度
    */
-  borderWidth?: number|string,
+  borderWidth?: ThemeSizeType,
   /**
    * 边框样式
    */
@@ -138,11 +138,11 @@ export interface FlexProps {
   /**
    * 宽度
    */
-  width?: number|string,
+  width?: ThemeSizeType,
   /**
    * 高度
    */
-  height?: number|string,
+  height?: ThemeSizeType,
   overflow?: 'visible'|'hidden'|'scroll'|'auto'
   /**
    * 层级

+ 2 - 0
src/components/layout/space/SafeAreaPadding.vue

@@ -1,5 +1,7 @@
 <template>
   <view :style="{
+    position: 'relative',
+    boxSizing: 'border-box',
     width: '100%',
     height: '100%',
     paddingTop: props.top ? `${safeAreaInsets.top}px` : undefined,

+ 1 - 1
src/components/layout/space/StatusBarSpace.vue

@@ -54,7 +54,7 @@ export interface StatusBarSpaceProps {
 }
 
 const props = withDefaults(defineProps<StatusBarSpaceProps>(), {
-  backgroundColor: 'white',
+  backgroundColor: 'transparent',
   extendsBackgroundColorHeight: 0,
   includeStatus: true,
   includeHeader: false,

+ 27 - 6
src/components/list/FixedVirtualList.vue

@@ -8,12 +8,11 @@
       class="virtual-list-scroll"
       :scroll-y="direction === 'vertical'"
       :scroll-x="direction === 'horizontal'"
-      :scroll-left="$attrs.scrollLeft"
-      :scroll-anchoring="$attrs.scrollAnchoring"
-      :scroll-top="$attrs.scrollTop"
-      :scroll-into-view="$attrs.scrollIntoView"
-      :scroll-with-animation="$attrs.scrollWithAnimation"
-      :show-scrollbar="$attrs.showScrollbar"
+      :scroll-left="scrollLeft"
+      :scroll-top="scrollTop"
+      :scroll-into-view="scrollIntoView"
+      :scroll-with-animation="scrollWithAnimation"
+      :show-scrollbar="showScrollbar"
       @scroll="handleScroll"
     >
       <slot name="prefix" />
@@ -82,6 +81,26 @@ const props = defineProps({
     default: 'vertical',
     validator: (val: string) => ['vertical', 'horizontal'].includes(val)
   },
+  scrollLeft: {
+    type: Number,
+    default: 0
+  },
+  scrollTop: {
+    type: Number,
+    default: 0
+  },
+  scrollIntoView: {
+    type: String,
+    default: ''
+  },
+  scrollWithAnimation: {
+    type: Boolean,
+    default: false
+  },
+  showScrollbar: {
+    type: Boolean,
+    default: false
+  },
   /**
    * 缓冲区大小(可视区域外额外渲染的条目数量)
    * @default 5
@@ -184,6 +203,8 @@ const visibleItems = computed(() => {
 // 占位容器样式(总滚动区域)
 const placeholderStyle = computed(() => {
   const size = `${props.data.length * props.itemSize}px`;
+  console.log('size', size);
+  
   return props.direction === 'vertical' 
     ? { height: size, width: '100%' }
     : { width: size, height: '100%' };

+ 5 - 10
src/components/list/IndexList.vue

@@ -242,16 +242,11 @@ const activeIndex = ref(-1);
 const scrollCurrentValue = ref(0);
 
 function handleScroll(e: any) {
-  if (scrollCurrentValue.value != e.detail.scrollTop) {
-    scrollCurrentValue.value = e.detail.scrollTop;
-    if (!flushLock) {
-      const index = Math.floor(scrollCurrentValue.value / props.itemHeight);
-      for (let i = index; i >= 0; i--) {
-        if (groupedData.value.list[i].isHeader) {
-          activeIndex.value = groupedData.value.list[i].headerIndex || -1;
-          break;
-        }
-      }
+  const index = Math.floor(e.detail.scrollTop / props.itemHeight);
+  for (let i = index; i >= 0; i--) {
+    if (groupedData.value.list[i].isHeader) {
+      activeIndex.value = groupedData.value.list[i].headerIndex ?? -1;
+      break;
     }
   }
 }

+ 8 - 8
src/common/components/SimplePageContentLoader.vue

@@ -1,24 +1,24 @@
 <template>
   <view
-    v-if="loader?.loadStatus.value == 'loading'"
+    v-if="loader?.status.value == 'loading'"
     class="loader-view center"
   >
     <LoadingPage loadingText="加载中" textSize="18" />
   </view>
   <view
-    v-else-if="loader?.loadStatus.value == 'error'"
+    v-else-if="loader?.status.value == 'error'"
     class="loader-view"
   >
     <Empty
       image="error"
-      :description="loader.loadError.value"
+      :description="loader.error.value"
     >
       <Height :height="20" />
       <Button type="primary" text="刷新" @click="handleRetry" />
     </Empty>
   </view> 
   <view
-    v-if="showEmpty || loader?.loadStatus.value == 'nomore'"
+    v-if="showEmpty || loader?.status.value == 'nomore'"
     class="loader-view"
   >
     <Empty
@@ -48,15 +48,15 @@
 
 <script setup lang="ts">
 import { onMounted, ref, type PropType } from 'vue';
-import type { ISimplePageContentLoader } from '../composeabe/SimplePageContentLoader';
 import Empty from '@/components/feedback/Empty.vue';
 import Button from '@/components/basic/Button.vue';
 import LoadingPage from '@/components/display/loading/LoadingPage.vue';
 import Height from '@/components/layout/space/Height.vue';
+import type { ILoaderCommon } from '../composeabe/loader/LoaderCommon';
 
 const props = defineProps({	
   loader: {
-    type: Object as PropType<ISimplePageContentLoader<any, any>>,
+    type: Object as PropType<ILoaderCommon<any>>,
     default: null,
   },
   lazy: {
@@ -91,13 +91,13 @@ onMounted(() => {
 });
 
 function handleRetry() {
-  props.loader.loadData(undefined);
+  props.loader.reload();
 }
 function handleLoad() {
   if (loaded.value) 
     return;
   loaded.value = true;
-  props.loader.loadData(undefined);
+  props.loader.reload();
 }
 </script>
 

+ 35 - 0
src/components/loader/SimplePageListLoader.vue

@@ -0,0 +1,35 @@
+<template>
+  <slot />
+  <Loadmore
+    v-if="loader.status.value == 'loading' 
+      || (loader.status.value == 'nomore' && !$slots.empty)" 
+    :status="loader.status.value" 
+  />
+  <slot v-else-if="loader.status.value == 'empty'" name="empty">
+    <Empty description="暂无数据" />
+  </slot>
+  <Loadmore 
+    v-else-if="loader.status.value == 'error'"
+    status="loadmore" 
+    :loadmoreText="loader.error.value" 
+    @loadmore="handleRetry" 
+  />
+</template>
+
+<script setup lang="ts">
+import type { PropType } from 'vue';
+import type { ISimplePageListLoader } from '../composeabe/loader/SimplePageListLoader';
+import Loadmore from '@/components/display/loading/Loadmore.vue';
+import Empty from '../feedback/Empty.vue';
+
+const props = defineProps({	
+  loader: {
+    type: Object as PropType<ISimplePageListLoader<any, any>>,
+    default: null,
+  },
+})
+
+function handleRetry() {
+  props.loader.reload();
+}
+</script>

+ 10 - 10
src/components/theme/Theme.ts

@@ -15,11 +15,11 @@ export const DefaultTheme : ThemeConfig = {
       '5xl': '80rpx',
     },
     fontSize: {
-      mini: 22,
-      small: 26,
-      medium: 30,
-      large: 38,
-      larger: 46,
+      xs: 22,
+      sm: 26,
+      md: 30,
+      lg: 38,
+      xl: 46,
     },
     gap: {
       xs: '2rpx',
@@ -43,11 +43,11 @@ export const DefaultTheme : ThemeConfig = {
       none: 'none',
     },
     radius: {
-      smaller: '5rpx',
-      small: '10rpx',
-      medium: '20rpx',
-      large: '30rpx',
-      larger: '50rpx',
+      xs: '5rpx',
+      sm: '10rpx',
+      md: '20rpx',
+      lg: '30rpx',
+      xl: '50rpx',
     },
   },
   colorConfigs: {

+ 13 - 10
src/components/theme/ThemeDefine.ts

@@ -18,6 +18,9 @@ export function configDefaultSizeUnit(unit: string) {
 export type ViewStyle = Record<string, any>
 export type TextStyle = Record<string, any>
 
+export type ThemeSizeType = number|string;
+export type ThemePaddingOrMarginType = number|Array<number>|ThemePaddingMargin|string|string[];
+
 export interface ThemeConfig {
   varOverrides: Record<string, any>,
   colorConfigs: Record<string, Record<string, string>>,
@@ -91,7 +94,7 @@ export function useTheme() {
   const topTheme = inject<Ref<ThemeConfig>>(ThemeKey);
   const theme = computed(() => topTheme?.value ?? DefaultTheme);
 
-  function resolveThemeSize(inValue?: string|number, defaultValue?: string|number) : string|undefined {    
+  function resolveThemeSize(inValue?: ThemeSizeType, defaultValue?: ThemeSizeType) : string|undefined {    
     const preResolve = resolveSize(inValue);
     if (preResolve !== undefined)
       return preResolve;    
@@ -243,12 +246,14 @@ export function useTheme() {
   }
 }
 
-function isNumbrSize(inValue: string|number|undefined) {
+export type ThemeContext = ReturnType<typeof useTheme>;
+
+function isNumbrSize(inValue: ThemeSizeType|undefined) {
   if (inValue == undefined)
     return false;
   return typeof inValue === 'number' || !isNaN(Number(inValue));
 }
-function isRealSize(inValue: string|number|undefined) {
+function isRealSize(inValue: ThemeSizeType|undefined) {
   if (inValue == undefined)
     return false;
   return typeof inValue === 'number' || 
@@ -264,7 +269,7 @@ function isRealSize(inValue: string|number|undefined) {
  * @param themeType 
  * @returns 
  */
-export function resolveSize(inValue: string|number|undefined) : string|undefined {
+export function resolveSize(inValue: ThemeSizeType|undefined) : string|undefined {
   if (inValue == undefined)
     return undefined;
   if (isNumbrSize(inValue))
@@ -282,13 +287,11 @@ export function resolveSize(inValue: string|number|undefined) : string|undefined
   return undefined;
 }
 
-export type ThemePaddingMarginProp = number | number[] | ThemePaddingMargin
-
 export interface ThemePaddingMargin {
-  l?: number,
-  r?: number,
-  t?: number,
-  b?: number,
+  l?: number|string,
+  r?: number|string,
+  t?: number|string,
+  b?: number|string,
 }
 
 /**

+ 11 - 47
src/components/theme/ThemeTools.ts

@@ -1,4 +1,4 @@
-import { resolveSize, type ThemeConfig, type ThemePaddingMargin } from "./ThemeDefine";
+import { resolveSize, type ThemeConfig, type ThemeContext, type ThemePaddingOrMarginType } from "./ThemeDefine";
 
 export type StringSize = number | string;
 
@@ -24,63 +24,27 @@ export function rpx2px(rpx: number) {
   return rpx / 750 * screenWidth;
 } 
 
-export function configThemePaddingMarginOneSide(input: string|number|number[]|ThemePaddingMargin|undefined, controlType: 'vertical'|'horizontal', controlValue: number) : ThemePaddingMargin {
-  
-  const result = { l: 0, r: 0, t: 0, b: 0 };
-  if (!input) {
-    if (controlType == 'horizontal')
-      result.l = result.r = controlValue;
-    else
-      result.t = result.b = controlValue;
-    return result;
-  }
-
-  if (typeof input === 'number') {
-    result.l += input;
-    result.r += input;
-    result.t += input;
-    result.b += input;
-  } else if (input instanceof Array) {
-    if (input.length === 2) {
-      result.t += input[0];
-      result.b += input[0];
-      result.l += input[1];
-      result.r += input[1];
-    } else if (input.length === 4) {
-      result.t += input[0];
-      result.r += input[1];
-      result.b += input[2];
-      result.l += input[3];
-    }
-  } else if (typeof input === 'object') {
-    result.t += input.t || 0;
-    result.r += input.r || 0;
-    result.b += input.b || 0;
-    result.l += (input.l || 0);
-  }
-  return result;
-}
-export function configMargin(style: Record<string, any>, theme: ThemeConfig, input: string|number|number[]|ThemePaddingMargin|undefined) {
+export function configMargin(style: Record<string, any>, theme: ThemeContext, input: ThemePaddingOrMarginType|undefined) {
   configMarginOrStyle('margin', style, theme, input);
 }
-export function configPadding(style: Record<string, any>, theme: ThemeConfig, input: string|number|number[]|ThemePaddingMargin|undefined) {
+export function configPadding(style: Record<string, any>, theme: ThemeContext, input: ThemePaddingOrMarginType|undefined) {
   configMarginOrStyle('padding', style, theme, input);
 }
-function configMarginOrStyle(name: string, style: Record<string, any>, theme: ThemeConfig, input: string|number|number[]|ThemePaddingMargin|undefined) {
+function configMarginOrStyle(name: string, style: Record<string, any>, theme: ThemeContext, input: ThemePaddingOrMarginType|undefined) {
   if (input === undefined)
     return;
-  if (typeof input === 'number') {
-    style[name] = resolveSize(input);
-  } else if (input instanceof Array) {
+  if (input instanceof Array) {
     style[name] = undefined;
     if (input.length === 2) {
-      style[name] = `${resolveSize(input[0] ?? 0)} ${resolveSize(input[1] ?? 0)}`;
+      style[name] = `${theme.resolveThemeSize(input[0] ?? 0)} ${theme.resolveThemeSize(input[1] ?? 0)}`;
     } else if (input.length === 4) {
-      style[name] = `${resolveSize(input[0] ?? 0)} ${resolveSize(input[1] ?? 0)} ${resolveSize(input[2] ?? 0)} ${resolveSize(input[3] ?? 0)}`;
+      style[name] = `${theme.resolveThemeSize(input[0] ?? 0)} ${theme.resolveThemeSize(input[1] ?? 0)} ${theme.resolveThemeSize(input[2] ?? 0)} ${theme.resolveThemeSize(input[3] ?? 0)}`;
     }
   } else if (typeof input === 'object') {
-    style[name] = `${resolveSize(input.t ?? 0)} ${resolveSize(input.r ?? 0)} ${resolveSize(input.b ?? 0)} ${resolveSize(input.l ?? 0)}`;
-  }
+    style[name] = `${theme.resolveThemeSize(input.t ?? 0)} ${theme.resolveThemeSize(input.r ?? 0)} ${theme.resolveThemeSize(input.b ?? 0)} ${theme.resolveThemeSize(input.l ?? 0)}`;
+  } else {
+    style[name] = theme.resolveThemeSize(input);
+  } 
 }
 
 export interface DynamicVarType {

+ 71 - 0
src/components/thirdPart/pinyinUtil.d.ts

@@ -0,0 +1,71 @@
+export type PinyinGetPinyinResult<Polyphone extends boolean | undefined> =
+  Polyphone extends true ? string[] : string;
+
+export type PinyinFirstLetterResult<Polyphone extends boolean | undefined> =
+  Polyphone extends true ? string[] : string;
+
+export interface PinyinUtilFirstLetterDict {
+  /** 长度约 20902 的首字母字符串,索引为 (unicode - 19968) */
+  all: string;
+  /** key 为汉字 unicode,value 为首字母候选(例如 "ZC") */
+  polyphone: Record<number, string>;
+}
+
+export interface PinyinUtilDict {
+  /** 汉字 -> 带声调拼音(多音字用空格分隔,例如 "da tai") */
+  withtone?: Record<string, string>;
+  /** 汉字 -> 不带声调拼音(不支持多音字) */
+  notone?: Record<string, string>;
+  /** 拼音(无声调) -> 汉字集合字符串 */
+  py2hz?: Record<string, string>;
+  /** 首字母字典 */
+  firstletter?: PinyinUtilFirstLetterDict;
+  [key: string]: unknown;
+}
+
+export interface PinyinUtil {
+  /** 解析各种字典文件(需在本文件之前导入字典脚本) */
+  parseDict(): void;
+
+  /**
+   * 根据汉字获取拼音;遇到非汉字会原样保留。
+   * - `withtone` 默认 `true`
+   * - `polyphone` 默认 `false`
+   *
+   * 当 `polyphone=true` 时,可能返回所有组合的数组。
+   */
+  getPinyin<Polyphone extends boolean | undefined = false>(
+    chinese: string,
+    splitter?: string,
+    withtone?: boolean,
+    polyphone?: Polyphone
+  ): PinyinGetPinyinResult<Polyphone>;
+
+  /**
+   * 获取汉字的拼音首字母;遇到非汉字会原样保留。
+   *
+   * 当 `polyphone=true` 时,返回所有可能组合的数组。
+   */
+  getFirstLetter<Polyphone extends boolean | undefined = false>(
+    str: string,
+    polyphone?: Polyphone
+  ): PinyinFirstLetterResult<Polyphone>;
+
+  /** 拼音转汉字(只支持单个拼音音节),返回匹配汉字集合字符串 */
+  getHanzi(pinyin: string): string;
+
+  /** 获取某个汉字的同音字(当前实现未完全可靠) */
+  getSameVoiceWord(hz: string, sameTone?: boolean): string;
+
+  /** 去除拼音中的声调,例如 "xiǎo míng" -> "xiao ming" */
+  removeTone(pinyin: string): string;
+
+  /** 数字声调转标点声调,例如 "xiao3" -> "xiǎo" */
+  getTone(pinyinWithoutTone: string): string;
+
+  /** 内部字典(由 `parseDict()` 填充) */
+  dict: PinyinUtilDict;
+}
+
+declare const pinyinUtil: PinyinUtil;
+export default pinyinUtil;

+ 364 - 0
src/components/thirdPart/pinyinUtil.js

@@ -0,0 +1,364 @@
+
+/**
+ * 汉字与拼音互转工具,根据导入的字典文件的不同支持不同
+ * 对于多音字目前只是将所有可能的组合输出,准确识别多音字需要完善的词库,而词库文件往往比字库还要大,所以不太适合web环境。
+ * @start 2016-09-26
+ * @last 2016-09-29
+ */
+
+var toneMap = 
+{
+  "ā": "a1",
+  "á": "a2",
+  "ǎ": "a3",
+  "à": "a4",
+  "ō": "o1",
+  "ó": "o2",
+  "ǒ": "o3",
+  "ò": "o4",
+  "ē": "e1",
+  "é": "e2",
+  "ě": "e3",
+  "è": "e4",
+  "ī": "i1",
+  "í": "i2",
+  "ǐ": "i3",
+  "ì": "i4",
+  "ū": "u1",
+  "ú": "u2",
+  "ǔ": "u3",
+  "ù": "u4",
+  "ü": "v0",
+  "ǖ": "v1",
+  "ǘ": "v2",
+  "ǚ": "v3",
+  "ǜ": "v4",
+  "ń": "n2",
+  "ň": "n3",
+  "": "m2"
+};
+
+var dict = {}; // 存储所有字典数据
+var pinyinUtil =
+{
+  /**
+   * 解析各种字典文件,所需的字典文件必须在本JS之前导入
+   */
+  parseDict: function()
+  {
+    // 如果导入了 pinyin_dict_firstletter.js
+    if(globalThis.pinyin_dict_firstletter)
+    {
+      dict.firstletter = pinyin_dict_firstletter;
+    }
+    // 如果导入了 pinyin_dict_notone.js
+    if(globalThis.pinyin_dict_notone)
+    {
+      dict.notone = {};
+      dict.py2hz = pinyin_dict_notone; // 拼音转汉字
+      for(var i in pinyin_dict_notone)
+      {
+        var temp = pinyin_dict_notone[i];
+        for(var j=0, len=temp.length; j<len; j++)
+        {
+          if(!dict.notone[temp[j]]) dict.notone[temp[j]] = i; // 不考虑多音字
+        }
+      }
+    }
+    // 如果导入了 pinyin_dict_withtone.js
+    if(globalThis.pinyin_dict_withtone)
+    {
+      dict.withtone = {}; // 汉字与拼音映射,多音字用空格分开,类似这种结构:{'大': 'da tai'}
+      var temp = pinyin_dict_withtone.split(',');
+      for(var i=0, len = temp.length; i<len; i++)
+      {
+        // 这段代码耗时28毫秒左右,对性能影响不大,所以一次性处理完毕
+        dict.withtone[String.fromCharCode(i + 19968)] = temp[i]; // 这里先不进行split(' '),因为一次性循环2万次split比较消耗性能
+      }
+
+      // 拼音 -> 汉字
+      if(globalThis.pinyin_dict_notone)
+      {
+        // 对于拼音转汉字,我们优先使用pinyin_dict_notone字典文件
+        // 因为这个字典文件不包含生僻字,且已按照汉字使用频率排序
+        dict.py2hz = pinyin_dict_notone; // 拼音转汉字
+      }
+      else
+      {
+        // 将字典文件解析成拼音->汉字的结构
+        // 与先分割后逐个去掉声调相比,先一次性全部去掉声调然后再分割速度至少快了3倍,前者大约需要120毫秒,后者大约只需要30毫秒(Chrome下)
+        var notone = pinyinUtil.removeTone(pinyin_dict_withtone).split(',');
+        var py2hz = {}, py, hz;
+        for(var i=0, len = notone.length; i<len; i++)
+        {
+          hz = String.fromCharCode(i + 19968); // 汉字
+          py = notone[i].split(' '); // 去掉了声调的拼音数组
+          for(var j=0; j<py.length; j++)
+          {
+            py2hz[py[j]] = (py2hz[py[j]] || '') + hz;
+          }
+        }
+        dict.py2hz = py2hz;
+      }
+    }
+  },
+  /**
+   * 根据汉字获取拼音,如果不是汉字直接返回原字符
+   * @param chinese 要转换的汉字
+   * @param splitter 分隔字符,默认用空格分隔
+   * @param withtone 返回结果是否包含声调,默认是
+   * @param polyphone 是否支持多音字,默认否
+   */
+  getPinyin: function(chinese, splitter, withtone, polyphone)
+  {
+    if(!chinese || /^ +$/g.test(chinese)) return '';
+    splitter = splitter == undefined ? ' ' : splitter;
+    withtone = withtone == undefined ? true : withtone;
+    polyphone = polyphone == undefined ? false : polyphone;
+    var result = [];
+    if(dict.withtone) // 优先使用带声调的字典文件
+    {
+      var noChinese = '';
+      for (var i=0, len = chinese.length; i < len; i++)
+      {
+        var pinyin = dict.withtone[chinese[i]];
+        if(pinyin)
+        {
+          // 如果不需要多音字,默认返回第一个拼音,后面的直接忽略
+          // 所以这对数据字典有一定要求,常见字的拼音必须放在最前面
+          if(!polyphone) pinyin = pinyin.replace(/ .*$/g, '');
+          if(!withtone) pinyin = this.removeTone(pinyin); // 如果不需要声调
+          //空格,把noChinese作为一个词插入
+          noChinese && ( result.push( noChinese), noChinese = '' );
+          result.push( pinyin ); 
+        }
+        else if ( !chinese[i] || /^ +$/g.test(chinese[i]) ){
+          //空格,把noChinese作为一个词插入
+          noChinese && ( result.push( noChinese), noChinese = '' );
+        }
+        else{
+          noChinese += chinese[i];
+        }
+      }
+      if ( noChinese ){
+        result.push( noChinese);
+        noChinese = '';
+      }
+    }
+    else if(dict.notone) // 使用没有声调的字典文件
+    {
+      if(withtone) console.warn('pinyin_dict_notone 字典文件不支持声调!');
+      if(polyphone) console.warn('pinyin_dict_notone 字典文件不支持多音字!');
+      var noChinese = '';
+      for (var i=0, len = chinese.length; i < len; i++)
+      {
+        var temp = chinese.charAt(i),
+          pinyin = dict.notone[temp];
+        if ( pinyin ){ //插入拼音
+          //空格,把noChinese作为一个词插入
+          noChinese && ( result.push( noChinese), noChinese = '' );
+          result.push( pinyin );
+        }
+        else if ( !temp || /^ +$/g.test(temp) ){
+          //空格,插入之前的非中文字符
+          noChinese && ( result.push( noChinese), noChinese = '' );
+        }
+        else {
+          //非空格,关联到noChinese中
+          noChinese += temp;
+        }
+      }
+
+      if ( noChinese ){
+        result.push( noChinese );
+        noChinese = '';
+      }
+    }
+    else
+    {
+      throw '抱歉,未找到合适的拼音字典文件!';
+    }
+    if(!polyphone) return result.join(splitter);
+    else
+    {
+      if(globalThis.pinyin_dict_polyphone) return parsePolyphone(chinese, result, splitter, withtone);
+      else return handlePolyphone(result, ' ', splitter);
+    }
+  },
+  /**
+   * 获取汉字的拼音首字母
+   * @param str 汉字字符串,如果遇到非汉字则原样返回
+   * @param polyphone 是否支持多音字,默认false,如果为true,会返回所有可能的组合数组
+   */
+  getFirstLetter: function(str, polyphone)
+  {
+    polyphone = polyphone == undefined ? false : polyphone;
+    if(!str || /^ +$/g.test(str)) return '';
+    if(dict.firstletter) // 使用首字母字典文件
+    {
+      var result = [];
+      for(var i=0; i<str.length; i++)
+      {
+        var unicode = str.charCodeAt(i);
+        var ch = str.charAt(i);
+        if(unicode >= 19968 && unicode <= 40869)
+        {
+          ch = dict.firstletter.all.charAt(unicode-19968);
+          if(polyphone) ch = dict.firstletter.polyphone[unicode] || ch;
+        }
+        result.push(ch);
+      }
+      if(!polyphone) return result.join(''); // 如果不用管多音字,直接将数组拼接成字符串
+      else return handlePolyphone(result, '', ''); // 处理多音字,此时的result类似于:['D', 'ZC', 'F']
+    }
+    else
+    {
+      var py = this.getPinyin(str, ' ', false, polyphone);
+      py = py instanceof Array ? py : [py];
+      var result = [];
+      for(var i=0; i<py.length; i++)
+      {
+        result.push(py[i].replace(/(^| )(\w)\w*/g, function(m,$1,$2){return $2.toUpperCase();}));
+      }
+      if(!polyphone) return result[0];
+      else return simpleUnique(result);
+    }
+  },
+  /**
+   * 拼音转汉字,只支持单个汉字,返回所有匹配的汉字组合
+   * @param pinyin 单个汉字的拼音,可以包含声调
+   */
+  getHanzi: function(pinyin)
+  {
+    if(!dict.py2hz)
+    {
+      throw '抱歉,未找到合适的拼音字典文件!';
+    }
+    return dict.py2hz[this.removeTone(pinyin)] || '';
+  },
+  /**
+   * 获取某个汉字的同音字,本方法暂时有问题,待完善
+   * @param hz 单个汉字
+   * @param sameTone 是否获取同音同声调的汉字,必须传进来的拼音带声调才支持,默认false
+   */
+  getSameVoiceWord: function(hz, sameTone)
+  {
+    sameTone = sameTone || false
+    return this.getHanzi(this.getPinyin(hz, ' ', false))
+  },
+  /**
+   * 去除拼音中的声调,比如将 xiǎo míng tóng xué 转换成 xiao ming tong xue
+   * @param pinyin 需要转换的拼音
+   */
+  removeTone: function(pinyin)
+  {
+    return pinyin.replace(/[āáǎàōóǒòēéěèīíǐìūúǔùüǖǘǚǜńň]/g, function(m){ return toneMap[m][0]; });
+  },
+  /**
+   * 将数组拼音转换成真正的带标点的拼音
+   * @param pinyinWithoutTone 类似 xu2e这样的带数字的拼音
+   */
+  getTone: function(pinyinWithoutTone)
+  {
+    var newToneMap = {};
+    for(var i in toneMap) newToneMap[toneMap[i]] = i;
+    return (pinyinWithoutTone || '').replace(/[a-z]\d/g, function(m) {
+      return newToneMap[m] || m;
+    });
+  }
+};
+
+
+/**
+ * 处理多音字,将类似['D', 'ZC', 'F']转换成['DZF', 'DCF']
+ * 或者将 ['chang zhang', 'cheng'] 转换成 ['chang cheng', 'zhang cheng']
+ */
+function handlePolyphone(array, splitter, joinChar)
+{
+  splitter = splitter || '';
+  var result = [''], temp = [];
+  for(var i=0; i<array.length; i++)
+  {
+    temp = [];
+    var t = array[i].split(splitter);
+    for(var j=0; j<t.length; j++)
+    {
+      for(var k=0; k<result.length; k++)
+        temp.push(result[k] + (result[k]?joinChar:'') + t[j]);
+    }
+    result = temp;
+  }
+  return simpleUnique(result);
+}
+
+/**
+ * 根据词库找出多音字正确的读音
+ * 这里只是非常简单的实现,效率和效果都有一些问题
+ * 推荐使用第三方分词工具先对句子进行分词,然后再匹配多音字
+ * @param chinese 需要转换的汉字
+ * @param result 初步匹配出来的包含多个发音的拼音结果
+ * @param splitter 返回结果拼接字符
+ */
+function parsePolyphone(chinese, result, splitter, withtone)
+{
+  var poly = globalThis.pinyin_dict_polyphone;
+  var max = 7; // 最多只考虑7个汉字的多音字词,虽然词库里面有10个字的,但是数量非常少,为了整体效率暂时忽略之
+  var temp = poly[chinese];
+  if(temp) // 如果直接找到了结果
+  {
+    temp = temp.split(' ');
+    for(var i=0; i<temp.length; i++)
+    {
+      result[i] = temp[i] || result[i];
+      if(!withtone) result[i] = pinyinUtil.removeTone(result[i]);
+    }
+    return result.join(splitter);
+  }
+  for(var i=0; i<chinese.length; i++)
+  {
+    temp = '';
+    for(var j=0; j<max && (i+j)<chinese.length; j++)
+    {
+      if(!/^[\u2E80-\u9FFF]+$/.test(chinese[i+j])) break; // 如果碰到非汉字直接停止本次查找
+      temp += chinese[i+j];
+      var res = poly[temp];
+      if(res) // 如果找到了多音字词语
+      {
+        res = res.split(' ');
+        for(var k=0; k<=j; k++)
+        {
+          if(res[k]) result[i+k] = withtone ? res[k] : pinyinUtil.removeTone(res[k]);
+        }
+        break;
+      }
+    }
+  }
+  // 最后这一步是为了防止出现词库里面也没有包含的多音字词语
+  for(var i=0; i<result.length; i++)
+  {
+    result[i] = result[i].replace(/ .*$/g, '');
+  }
+  return result.join(splitter);
+}
+
+// 简单数组去重
+function simpleUnique(array)
+{
+  var result = [];
+  var hash = {};
+  for(var i=0; i<array.length; i++)
+  {
+    var key = (typeof array[i]) + array[i];
+    if(!hash[key])
+    {
+      result.push(array[i]);
+      hash[key] = true;
+    }
+  }
+  return result;
+}
+
+pinyinUtil.parseDict();
+pinyinUtil.dict = dict;
+
+export default pinyinUtil;

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 8 - 0
src/components/thirdPart/pinyin_dict_firstletter.js


+ 9 - 0
src/components/utils/PingyinUtils.ts

@@ -0,0 +1,9 @@
+import '../thirdPart/pinyin_dict_firstletter'
+import pinyinUtil from '../thirdPart/pinyinUtil';
+
+export default {
+  getFirstLetter(char: string) : string {
+    const res = pinyinUtil.getFirstLetter(char);
+    return res;
+  }
+}

+ 1 - 0
src/env.d.ts

@@ -1,6 +1,7 @@
 /// <reference types="vite/client" />
 
 declare module 'cax';
+declare module 'pinyinjs';
 
 declare module '*.vue' {
   import { DefineComponent } from 'vue'

+ 1 - 4
src/pages.json

@@ -247,10 +247,7 @@
     "navigationBarTextStyle": "white",
     "navigationBarTitleText": "uni-app",
     "navigationBarBackgroundColor": "#e19579",
-    "backgroundColor": "#fef2e8",
-    "usingComponents": {
-      "cax": "/wxcomponents/cax/cax"
-    }
+    "backgroundColor": "#fef2e8"
   },
   "uniIdRouter": {}
 }

+ 1 - 1
src/pages/article/common/CommonContent.ts

@@ -1,5 +1,5 @@
 import CommonContent, { GetContentListItem, GetContentListParams } from "@/api/CommonContent";
-import { useSimpleDataLoader, type ISimpleDataLoader } from "@/common/composeabe/SimpleDataLoader";
+import { useSimpleDataLoader, type ISimpleDataLoader } from "@/components/composeabe/loader/SimpleDataLoader";
 import { navTo } from "@/components/utils/PageAction";
 
 export interface IHomePageMiniCommonListGoMoreAndGoDetail {

+ 9 - 9
src/pages/article/common/CommonListPage.vue

@@ -104,9 +104,9 @@
 
 <script setup lang="ts">
 import { computed, nextTick, onMounted, ref, watch, type PropType } from 'vue';
-import { useSimplePageListLoader } from '@/common/composeabe/SimplePageListLoader';
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
 import { navTo } from '@/components/utils/PageAction';
-import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
 import Box2LineLargeImageUserShadow from '@/common/components/parts/Box2LineLargeImageUserShadow.vue';
 import Box2LineImageRightShadow from '@/common/components/parts/Box2LineImageRightShadow.vue';
 import SimpleDropDownPicker, { type SimpleDropDownPickerItem } from '@/common/components/SimpleDropDownPicker.vue';
@@ -273,7 +273,7 @@ watch(() => props.intitalSearch, (newValue) => {
 
 function handleChangeDropDownValue(index: number, value: number) {
   dropDownValues.value[index] = value;
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 }
 function handleTabClick(e: any) {
   nextTick(() => {
@@ -281,11 +281,11 @@ function handleTabClick(e: any) {
       props.tabs[tabCurrentIndex.value].jump?.();
       return;
     }
-    listLoader.loadData(undefined, true);
+    listLoader.reload();
   })
 }
 function doSearch() {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 }
 function goDetails(item: any, id: number) {
   if (props.detailsPage == 'custom') {
@@ -321,7 +321,7 @@ function loadDropDownValues() {
 }
 
 watch(tabCurrentIndex, () => {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 });
 watch(() => props.startTabIndex, () => {
   if (props.startTabIndex) {
@@ -330,12 +330,12 @@ watch(() => props.startTabIndex, () => {
 });
 watch(() => props.dropDownNames.length, () => {
   loadDropDownValues();
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 });
 
 defineExpose({
   load: () => {
-    listLoader.loadData(undefined, true);
+    listLoader.reload();
   },
 })
 
@@ -346,6 +346,6 @@ onMounted(() => {
     uni.setNavigationBarTitle({ title: props.title, })
   loadDropDownValues();
   if (props.loadMounted)
-    listLoader.loadData(undefined, true);
+    listLoader.reload();
 });
 </script>

+ 1 - 1
src/pages/article/common/list.vue

@@ -14,7 +14,7 @@
 </template>
 
 <script setup lang="ts">
-import { useLoadQuerys, stringDotNumbersToNumbers } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys, stringDotNumbersToNumbers } from '@/components/composeabe/LoadQuerys';
 import CommonListPage from './CommonListPage.vue';
 import CommonContent, { GetContentListParams } from '@/api/CommonContent';
 import { redirectTo } from '@/components/utils/PageAction';

+ 7 - 8
src/pages/article/details.vue

@@ -17,7 +17,7 @@
                 :src="item" 
                 width="100%"
                 :height="500"
-                radius="radius.medium"
+                radius="radius.md"
                 mode="aspectFill"
                 touchable
                 @click="onPreviewImage(key)"
@@ -26,7 +26,7 @@
           </swiper>
           <Image 
             v-else-if="loader.content.value.image"
-            radius="radius.medium"
+            radius="radius.md"
             :src="loader.content.value.image"
             mode="widthFix"
             width="100%"
@@ -80,15 +80,14 @@
 
 <script setup lang="ts">
 import { computed } from "vue";
-import { useSimpleDataLoader } from "@/common/composeabe/SimpleDataLoader";
-import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
+import { useSimpleDataLoader } from "@/components/composeabe/loader/SimpleDataLoader";
 import { useSwiperImagePreview } from "@/common/composeabe/SwiperImagePreview";
-import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
+import { useLoadQuerys } from "@/components/composeabe/LoadQuerys";
 import { navTo } from "@/components/utils/PageAction";
 import { onShareTimeline, onShareAppMessage } from "@dcloudio/uni-app";
 import { DataDateUtils } from "@imengyu/js-request-transform";
 import type { GetContentDetailItem } from "@/api/CommonContent";
-import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
+import SimplePageContentLoader from "@/components/loader/SimplePageContentLoader.vue";
 import Parse from "@/components/display/parse/Parse.vue";
 import CommonContent, { GetContentListParams } from "@/api/CommonContent";
 import Box2LineImageRightShadow from "@/common/components/parts/Box2LineImageRightShadow.vue";
@@ -100,7 +99,7 @@ import Text from "@/components/basic/Text.vue";
 import H2 from "@/components/typography/H2.vue";
 import FlexRow from "@/components/layout/FlexRow.vue";
 
-const loader = useSimplePageContentLoader<
+const loader = useSimpleDataLoader<
   GetContentDetailItem, 
   { id: number }
 >(async (params) => {
@@ -146,7 +145,7 @@ const { querys } = useLoadQuerys({
   mainBodyColumnId: 0,
   mainBodyId: 0,
   modelId: 0,
-}, (t) => loader.loadData(t));
+}, (t) => loader.load(false, t));
 
 function getPageShareData() {
   if (!loader.content.value)

+ 1 - 1
src/pages/article/web/ewebview.vue

@@ -6,7 +6,7 @@
 </template>
 
 <script setup lang="ts">
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { ref } from 'vue';
 
 const finalUrl = ref('')

+ 20 - 5
src/pages/components/LightMap.vue

@@ -1,5 +1,12 @@
 <template>
-  <div class="light-map" :class="{ 'full': full }">
+  <div 
+    class="light-map"
+    :class="{ 
+      'full': full ,
+      'small': small
+    }" 
+  >
+    <slot />
     <map 
       id="prevMap"
       map-id="prevMap"
@@ -33,7 +40,7 @@
 
 <script setup lang="ts">
 import { getCurrentInstance, onMounted, ref, watch } from 'vue';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { geoJsonToWechatMapShapes } from '@/common/utils/geoJsonToWechatMap';
 import LightVillageApi, { VillageListItem } from '@/api/light/LightVillageApi';
 import MapApi from '@/api/map/MapApi';
@@ -60,6 +67,7 @@ const emit = defineEmits(['getedCurrentLonlat','update:isLightMode']);
 const props = defineProps<{
   startLonlat?: { longitude: number, latitude: number } | undefined;
   isLightMode?: boolean;
+  small?: boolean;
   full?: boolean;
 }>();
 
@@ -166,12 +174,12 @@ function onMarkerTap(e: any) {
 function onSelectedRegion(regionId: number) {
   selectedRegion.value = regionId;
   nextNeedAutoFocus.value = true;
-  mapLoader.loadData(undefined, true);
+  mapLoader.reload();
 }
 
 function setCurrentRegion(regionName: string) {
   selectedRegion.value = regionLoader.content.value?.find(p => p.name == regionName)?.id || undefined;
-  mapLoader.loadData(undefined, true);
+  mapLoader.reload();
 }
 
 function getCurrentLonlat() {
@@ -236,7 +244,7 @@ onMounted(async () => {
     });
   }
 
-  await regionLoader.loadData(undefined, true);
+  await regionLoader.reload();
 
   if (!props.startLonlat) {
     const res = await LightVillageApi.getIpAddress();
@@ -277,6 +285,13 @@ onMounted(async () => {
       height: 100vh;
     }
   }
+  &.small {
+    height: 340rpx;
+    border-radius: 20rpx;
+    .light-map-map {
+      height: 340rpx;
+    }
+  }
 
   .light-map-map {
     width: 100%;

+ 1 - 1
src/pages/dig/admin/components/VolunteerItem.vue

@@ -2,7 +2,7 @@
   <FlexRow
     :key="item.id" 
     backgroundColor="white" 
-    radius="radius.medium"
+    radius="radius.md"
     justify="space-between"
     align="center"
     :padding="20"

+ 9 - 9
src/pages/dig/admin/index.vue

@@ -9,8 +9,8 @@
           @search="search"
         />
         <ButtonGroup>
-          <NButton radius="radius.larger" size="small" @click="navTo('review', { villageId: querys.villageId })">待审核志愿者</NButton>
-          <NButton radius="radius.larger" size="small" type="primary" @click="newData">+ 新增</NButton>
+          <NButton radius="radius.lgr" size="small" @click="navTo('review', { villageId: querys.villageId })">待审核志愿者</NButton>
+          <NButton radius="radius.lgr" size="small" type="primary" @click="newData">+ 新增</NButton>
         </ButtonGroup>
       </FlexRow>
       <FlexCol :gap="20" :margin="[20,0,0,0]">
@@ -61,7 +61,7 @@
                 },
               ]"
             >
-              <NButton type="primary" icon="edit-filling" radius="radius.larger" />
+              <NButton type="primary" icon="edit-filling" radius="radius.lgr" />
             </BubbleBox>
           </template>
         </VolunteerItem>
@@ -80,12 +80,12 @@
 <script setup lang="ts">
 import { ref } from 'vue';
 import { navTo } from '@/components/utils/PageAction';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
-import { useSimplePageListLoader } from '@/common/composeabe/SimplePageListLoader';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
 import { onShareAppMessage } from '@dcloudio/uni-app';
 import { alert, confirm, toast } from '@/components/dialog/CommonRoot';
 import VillageApi, { VolunteerInfo } from '@/api/inhert/VillageApi';
-import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
 import NButton from '@/components/basic/Button.vue';
 import Icon from '@/components/basic/Icon.vue';
 import Text from '@/components/basic/Text.vue';
@@ -108,7 +108,7 @@ const { querys } = useLoadQuerys({
   villageId: 0,
   villageVolunteerId: 0,
 }, () => {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 });
 
 const searchText = ref('');
@@ -159,7 +159,7 @@ function doDeleteUser(item: VolunteerInfo) {
   }).then((res) => {
     if (res) {
       VillageApi.deleteVolunteer(item.id, querys.value.villageId).then(() => {
-        listLoader.loadData(undefined, true);
+        listLoader.reload();
         toast({ content: '删除成功' })
       }).catch((e) => {
         alert({
@@ -172,7 +172,7 @@ function doDeleteUser(item: VolunteerInfo) {
   });
 }
 function search() {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 }
 
 onShareAppMessage(() => ({

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

@@ -2,7 +2,7 @@
   <FlexCol :padding="30" :gap="20">
     <ImageSwiper
       :images="imageList"
-      radius="radius.medium"
+      radius="radius.md"
       round
       mode="widthFix"
       :width="690" 
@@ -42,7 +42,7 @@
 
     <FlexCol :margin="[20, 0, 30, 0]">
       <SubTitle title="地理位置" />
-      <FlexCol radius="radius.medium" backgroundColor="white" :margin="[20, 0, 0, 0]">
+      <FlexCol radius="radius.md" backgroundColor="white" :margin="[20, 0, 0, 0]">
         <map 
           id="map"
           :latitude="center[1]"
@@ -63,8 +63,8 @@
 
 <script setup lang="ts">
 import { computed, ref } from 'vue';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { useCollectStore } from '@/store/collect';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import CollectModuleList from '../components/CollectModuleList.vue';

+ 6 - 6
src/pages/dig/admin/review.vue

@@ -34,7 +34,7 @@
               </FlexCol>
             </FlexRow>
             <template #action>
-              <Button type="primary" icon="task-filling" radius="radius.larger" @click="handleShowDetail(item)" />
+              <Button type="primary" icon="task-filling" radius="radius.lgr" @click="handleShowDetail(item)" />
             </template>
           </VolunteerItem>
         </FlexCol>
@@ -65,14 +65,14 @@
 
 <script setup lang="ts">
 import { onMounted, ref } from 'vue';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
-import { useSimplePageListLoader } from '@/common/composeabe/SimplePageListLoader';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
 import { closeToast, confirm, toast } from '@/components/dialog/CommonRoot';
 import { showError } from '@/common/composeabe/ErrorDisplay';
 import { getVolunteerForm } from './data/volunteer';
 import type { IDynamicFormOptions, IDynamicFormRef } from '@/components/dynamic';
 import VillageApi, { VolunteerInfo } from '@/api/inhert/VillageApi';
-import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
 import Button from '@/components/basic/Button.vue';
 import Image from '@/components/basic/Image.vue';
 import Text from '@/components/basic/Text.vue';
@@ -90,7 +90,7 @@ const UserHead = 'https://mncdn.wenlvti.net/app_static/xiangyuan/images/user/ava
 const { querys } = useLoadQuerys({ 
   villageId: 0,
 }, () => {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 });
 
 const searchText = ref('');
@@ -150,7 +150,7 @@ async function handleReview(status: number) {
   }
 }
 function search() {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 }
 
 onMounted(() => {

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

@@ -16,7 +16,7 @@
 
 <script setup lang="ts">
 import { computed, nextTick, ref } from 'vue';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { showError } from '@/common/composeabe/ErrorDisplay';
 import { alert, toast } from '@/components/dialog/CommonRoot';
 import { backAndCallOnPageBack } from '@/components/utils/PageAction';

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

@@ -3,7 +3,7 @@
     <Image
       v-if="currentTaskBanner"
       :src="currentTaskBanner"
-      radius="radius.medium"
+      radius="radius.md"
       :width="690"
       mode="widthFix"
     />

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

@@ -44,7 +44,7 @@ defineProps({
 <template>
   <Touchable 
     :padding="25" 
-    radius="radius.medium" 
+    radius="radius.md" 
     :touchable="button && enable"
     direction="row"
     backgroundColor="white" 

+ 4 - 4
src/pages/dig/details.vue

@@ -3,10 +3,10 @@
     <Image
       src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_1.jpg" 
       mode="widthFix"
-      radius="radius.medium"
+      radius="radius.md"
       :width="690" 
     />
-    <FlexRow align="center" :gap="10" backgroundColor="white" :padding="20" radius="radius.medium">
+    <FlexRow align="center" :gap="10" backgroundColor="white" :padding="20" radius="radius.md">
       <FlexRow flexBasis="50%">
         <Text :fontSize="30" text="已认领:" />
         <Width :width="20" />
@@ -23,7 +23,7 @@
     <Touchable 
       direction="column" 
       touchable 
-      radius="radius.medium"
+      radius="radius.md"
       :padding="30"
       align="center"
       backgroundColor="background.primary"
@@ -56,7 +56,7 @@
 </template>
 
 <script setup lang="ts">
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { useAuthStore } from '@/store/auth';
 import { useCollectStore } from '@/store/collect';
 import { useTaskEntryForm } from './forms/composeable/TaskEntryForm';

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

@@ -30,7 +30,7 @@
 <script setup lang="ts">
 import { nextTick, ref, watch, type Ref } from 'vue';
 import { useCollectStore } from '@/store/collect';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { getVillageInfoForm, getVillageInfoFormIds, type SingleForm } from './forms';
 import { showError } from '@/common/composeabe/ErrorDisplay';
 import { backAndCallOnPageBack } from '@/components/utils/PageAction';

+ 1 - 1
src/pages/dig/forms/composeable/TaskEntryForm.ts

@@ -1,4 +1,4 @@
-import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
+import { useLoadQuerys } from "@/components/composeabe/LoadQuerys";
 import { navTo } from "@/components/utils/PageAction";
 
 export function useTaskEntryForm() {

+ 9 - 9
src/pages/dig/forms/list.vue

@@ -16,7 +16,7 @@
         :key="item.id" 
         :gap="20"
         :padding="[15,20]"
-        radius="radius.medium"
+        radius="radius.md"
         align="center"
         backgroundColor="white"
         direction="row"
@@ -28,7 +28,7 @@
           :showFailed="false"
           :width="100"
           :height="100"
-          radius="radius.small"
+          radius="radius.sm"
           mode="aspectFill"
           round
         />
@@ -56,12 +56,12 @@ import { ref } from 'vue';
 import { onPullDownRefresh } from '@dcloudio/uni-app';
 import { alert } from '@/components/dialog/CommonRoot';
 import { DataDateUtils } from '@imengyu/js-request-transform';
-import { useSimplePageListLoader } from '@/common/composeabe/SimplePageListLoader';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { useCollectStore } from '@/store/collect';
 import { useAuthStore } from '@/store/auth';
 import { navTo } from '@/components/utils/PageAction';
-import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
 import VillageInfoApi from '@/api/inhert/VillageInfoApi';
 import Image from '@/components/basic/Image.vue';
 import Empty from '@/components/feedback/Empty.vue';
@@ -157,7 +157,7 @@ function goDetail(id: number) {
   });
 }
 function search() {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 }
 
 const { querys } = useLoadQuerys({ 
@@ -174,17 +174,17 @@ const { querys } = useLoadQuerys({
     subTitle.value = querys.subTitle;
     uni.setNavigationBarTitle({  title: subTitle.value + '列表', })
   }
-  listLoader.loadData(querys)
+  listLoader.load(false, querys)
 });
 
 onPullDownRefresh(() => {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 });
 
 defineExpose({
   onPageBack(name: string, param: any) {
     if (param && param.needRefresh)
-      listLoader.loadData(undefined, true);
+      listLoader.reload();
   }
 })
 </script>

+ 12 - 12
src/pages/dig/forms/submits.vue

@@ -39,7 +39,7 @@
           v-for="item in listLoader.list.value"
           :key="item.id" 
           :padding="[15,20]"
-          radius="radius.medium"
+          radius="radius.md"
           justify="space-between"
           align="center"
           backgroundColor="white"
@@ -53,7 +53,7 @@
               :showFailed="false"
               :width="150"
               :height="150"
-              radius="radius.small"
+              radius="radius.sm"
               mode="aspectFill"
               round
             />
@@ -92,13 +92,13 @@
 import { ref, watch } from 'vue';
 import { onPullDownRefresh } from '@dcloudio/uni-app';
 import { DataDateUtils } from '@imengyu/js-request-transform';
-import { useSimplePageListLoader } from '@/common/composeabe/SimplePageListLoader';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { useCollectStore } from '@/store/collect';
 import { selectObjectByType } from '@/components/theme/ThemeTools';
 import { navTo } from '@/components/utils/PageAction';
-import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
 import VillageInfoApi, { CommonInfoModel } from '@/api/inhert/VillageInfoApi';
 import Image from '@/components/basic/Image.vue';
 import Empty from '@/components/feedback/Empty.vue';
@@ -165,7 +165,7 @@ function goDetail(item: CommonInfoModel) {
   });
 }
 function search() {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 }
 
 const { querys } = useLoadQuerys({ 
@@ -173,20 +173,20 @@ const { querys } = useLoadQuerys({
   villageVolunteerId: 0,
 }, async (querys) => {
   filterVillage.value = querys.villageId || 0;
-  listLoader.loadData(querys)
+  listLoader.load(false, querys)
 });
 
-watch(filterStatus, () => listLoader.loadData(undefined, true))
-watch(filterVillage, () => listLoader.loadData(undefined, true))
+watch(filterStatus, () => listLoader.reload())
+watch(filterVillage, () => listLoader.reload())
 
 onPullDownRefresh(() => {
-  listLoader.loadData(undefined, true);
+  listLoader.reload();
 });
 
 defineExpose({
   onPageBack(name: string, param: any) {
     if (param && param.needRefresh)
-      listLoader.loadData(undefined, true);
+      listLoader.reload();
   }
 })
 </script>

+ 1 - 1
src/pages/dig/forms/task.vue

@@ -12,7 +12,7 @@
 </template>
 
 <script setup lang="ts">
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import CollectModuleList from '../components/CollectModuleList.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 

+ 8 - 8
src/pages/dig/index.vue

@@ -7,7 +7,7 @@
         src="https://xy.wenlvti.net/app_static/images/dig/IntrodBanner.png"
         width="100%"
         :height="300"
-        radius="radius.medium"
+        radius="radius.md"
         :innerStyle="{
           border: '1px solid #fff',
         }"
@@ -38,7 +38,7 @@
             <FlexRow 
               v-for="item in villageListLoader.content.value"
               :key="item.id"
-              radius="radius.medium"
+              radius="radius.md"
               :padding="20"
               align="center"
               justify="space-between"
@@ -99,7 +99,7 @@
 
       <Height :height="20" />
       <HomeTitle v-if="authStore.isLogged" title="我的贡献" />
-      <FlexRow v-if="authStore.isLogged" backgroundColor="white" radius="radius.medium" :padding="[40,20]">
+      <FlexRow v-if="authStore.isLogged" backgroundColor="white" radius="radius.md" :padding="[40,20]">
         <FlexCol :flex="1" :gap="10" center>
           <Text :fontSize="60" fontFamily="Rockwell" color="primary">{{ volunteerInfoLoader.content.value?.points || 0 }}</Text>
           <Text>文化积分</Text>
@@ -129,11 +129,11 @@ import { ref, watch } from 'vue';
 import { navTo } from '@/components/utils/PageAction';
 import { useAuthStore } from '@/store/auth';
 import { useCollectStore } from '@/store/collect';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { checkIsNotVolunteerError } from './forms/bind';
 import VillageApi, { VillageListItem } from '@/api/inhert/VillageApi';
 import RequireLogin from '@/common/components/RequireLogin.vue';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import Button from '@/components/basic/Button.vue';
 import Image from '@/components/basic/Image.vue';
 import SubTitle from '@/components/display/title/SubTitle.vue';
@@ -169,9 +169,9 @@ const rankListLoader = useSimpleDataLoader(async () => await VillageApi.getVolun
 
 watch(() => authStore.isLogged, (newVal) => {
   if (newVal) {
-    villageListLoader.loadData();
-    volunteerInfoLoader.loadData();
-    rankListLoader.loadData();
+    villageListLoader.reload();
+    volunteerInfoLoader.reload();
+    rankListLoader.reload();
   }
 })
 

+ 1 - 1
src/pages/dig/sharereg/bind-wx.vue

@@ -11,7 +11,7 @@
 <script setup lang="ts">
 import { ref } from 'vue';
 import { useAppInit } from '@/common/composeabe/AppInit';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { toast } from '@/components/dialog/CommonRoot';
 import { showError } from '@/common/composeabe/ErrorDisplay';
 import { back, redirectTo } from '@/components/utils/PageAction';

+ 1 - 1
src/pages/dig/sharereg/share-reg-page.vue

@@ -74,7 +74,7 @@ import { useAuthStore } from '@/store/auth';
 import { redirectTo } from '@/components/utils/PageAction';
 import { closeToast, toast } from '@/components/dialog/CommonRoot';
 import { showError } from '@/common/composeabe/ErrorDisplay';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { onMounted, ref } from 'vue';
 import { getVolunteerForm } from '../admin/data/volunteer';
 import VillageApi, { VolunteerInfo } from '@/api/inhert/VillageApi';

+ 2 - 2
src/pages/home/about/about.vue

@@ -9,9 +9,9 @@
 <script setup lang="ts">
 import FlexCol from '@/components/layout/FlexCol.vue';
 import Parse from '@/components/display/parse/Parse.vue';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import CommonContent from '@/api/CommonContent';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 
 const loader = useSimpleDataLoader(async () => await CommonContent.getContentDetail(6929, undefined, 18));
 </script>

+ 2 - 2
src/pages/home/about/contract.vue

@@ -9,9 +9,9 @@
 <script setup lang="ts">
 import FlexCol from '@/components/layout/FlexCol.vue';
 import Parse from '@/components/display/parse/Parse.vue';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import CommonContent from '@/api/CommonContent';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 
 const loader = useSimpleDataLoader(async () => await CommonContent.getContentDetail(6930, undefined, 18));
 </script>

+ 60 - 0
src/pages/home/components/CitySelect.vue

@@ -0,0 +1,60 @@
+
+
+<template>
+  <FlexCol 
+    :padding="[0,'space.md']" 
+    backgroundColor="white" 
+    radius="radius.md"
+  >
+    <NavBar title="选择城市" />
+    <SearchBar 
+      v-model="searchValue"
+      leftIcon=""
+      searchIcon="search"
+      searchState="show"
+      placeholder="搜索城市" 
+      @search="cities.reload()"
+      @clear="cities.reload()"
+    />
+    <IndexList 
+      v-if="cities.content.value"
+      :data="cities.content.value"
+      :groupDataBy="(item) => item.first_letter"
+      :sortGroup="arr => arr.sort()"
+      :innerStyle="{
+        height: '60vh',
+      }"
+      dataDisplayProp="name"
+      @itemClick="handleCityClick"
+    />
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import MapApi, { type CityItem } from '@/api/map/MapApi';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
+import SearchBar from '@/components/form/SearchBar.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import { ref } from 'vue';
+import NavBar from '@/components/nav/NavBar.vue';
+import IndexList from '@/components/list/IndexList.vue';
+
+const searchValue = ref('');
+const cities = useSimpleDataLoader(async () => {
+  return (await MapApi.loadCityData(true))
+    .flatMap(item => {
+      return (item.children || []).map(child => ({
+        ...child,
+        parentName: item.name,
+      }));
+    })
+    .filter(item => !searchValue.value || item.name.includes(searchValue.value));
+})
+
+const emit = defineEmits(['selectCity']);
+
+const handleCityClick = (item: CityItem) => {
+  console.log(item);
+  emit('selectCity', item);
+}
+</script>

+ 4 - 1
src/pages/home/components/VillageMiniMap.vue

@@ -10,6 +10,7 @@
       map-id="prevMap"
       class="mini-village-map-map"
       :enable-poi="false"
+      :markers="markers"
       :scale="12"
       :longitude="AppCofig.defaultLonLat[0]"
       :latitude="AppCofig.defaultLonLat[1]"
@@ -58,6 +59,7 @@ import MapApi from '@/api/map/MapApi';
 import AppCofig from '@/common/config/AppCofig';
 import Button from '@/components/basic/Button.vue';
 import NoticeBar from '@/components/display/NoticeBar.vue';
+import type { MapMarker } from '@/types/Map';
 
 const instance = getCurrentInstance();
 const mapCtx = uni.createMapContext('prevMap', instance);
@@ -66,9 +68,10 @@ const currentLonlat = ref<{ longitude: number, latitude: number }>({
   longitude: AppCofig.defaultLonLat[0], 
   latitude: AppCofig.defaultLonLat[1],
 });
-const emit = defineEmits(['getedCurrentLonlat']);
 
+const emit = defineEmits(['getedCurrentLonlat']);
 const props = defineProps<{
+  markers?: MapMarker[];
   startLonlat?: { longitude: number, latitude: number } | undefined;
 }>();
 

+ 42 - 0
src/pages/home/components/VillageMyFollow.vue

@@ -0,0 +1,42 @@
+<template>
+  <FlexCol padding="space.md">
+    <HomeTitle title="我的关注" />
+    <RequireLogin unLoginMessage="登录后查看我的关注村社" @loginSuccess="myFollowLoader.reload()">
+      <FlexCol>
+        <SimplePageListLoader :loader="myFollowLoader">
+          <ImageBlock3
+            v-for="(item, i) in myFollowLoader.list.value"
+            :key="i"
+            backgroundColor="transparent"
+            :src="item.image"
+            :title="item.villageName"
+            :desc="item.address"
+            :imageRadius="15"
+            :imageWidth="200"
+            :imageHeight="140"
+            @click="emit('goDetails', item)"
+          />
+        </SimplePageListLoader>
+      </FlexCol>
+    </RequireLogin>
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import HomeTitle from '@/common/components/parts/HomeTitle.vue';
+import ImageBlock3 from '@/components/display/block/ImageBlock3.vue';
+import FollowVillageApi from '@/api/light/FollowVillageApi';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
+import type { VillageListItem } from '@/api/inhert/VillageApi';
+import RequireLogin from '@/common/components/RequireLogin.vue';
+
+const emit = defineEmits<{
+  (e: 'goDetails', item: VillageListItem): void;
+}>();
+
+const myFollowLoader = useSimplePageListLoader(20, async (page, pageSize) => {
+  return (await FollowVillageApi.getFollowVillageList({ page: page, pageSize: pageSize }));
+});
+</script>

+ 2 - 2
src/pages/home/components/VillageRankList.vue

@@ -6,10 +6,10 @@
       :width="item.rank == 1 ? '40%' : '28.5%'" 
       position="relative" 
       justify="space-between"
-      radius="radius.medium"
+      radius="radius.md"
       overflow="hidden"
     >
-      <Image :src="item.image" width="100%" :height="280" mode="aspectFill" radius="radius.medium" />
+      <Image :src="item.image" width="100%" :height="280" mode="aspectFill" radius="radius.md" />
       <FlexRow
         position="absolute"
         :left="0"

+ 1 - 1
src/pages/home/components/VillageUserRankList.vue

@@ -21,7 +21,7 @@
         :bottom="item.rank == 1 ? 66 : 40"
         center
       >
-        <Avatar :url="item.image" :size="item.rank == 1 ? 100 : 90" mode="aspectFill" radius="radius.medium" />
+        <Avatar :url="item.image" :size="item.rank == 1 ? 100 : 90" mode="aspectFill" radius="radius.md" />
         <Height :height="item.rank == 1 ? 15 : 10" />
         <Text fontConfig="h4" :text="item.title" color="white" />
         <Text fontConfig="h2" :text="item.score" :color="scoreColors[item.rank - 1]" />

+ 7 - 8
src/pages/home/discover/details.vue

@@ -18,7 +18,7 @@
                   :src="item" 
                   width="100%"
                   :height="500"
-                  radius="radius.medium"
+                  radius="radius.md"
                   mode="aspectFill" 
                   @click="onPreviewImage(key)"
                 />
@@ -44,7 +44,7 @@
               :src="loader.content.value.image"
               :imageWidth="100"
               :imageHeight="100"
-              radius="radius.medium"
+              radius="radius.md"
               :title="loader.content.value.villageName"
               :desc="`${loader.content.value.villageProvince}${loader.content.value.villageCity}`"
             />
@@ -80,14 +80,13 @@
 
 <script setup lang="ts">
 import { computed } from "vue";
-import { useSimpleDataLoader } from "@/common/composeabe/SimpleDataLoader";
-import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
+import { useSimpleDataLoader } from "@/components/composeabe/loader/SimpleDataLoader";
 import { useSwiperImagePreview } from "@/common/composeabe/SwiperImagePreview";
-import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
+import { useLoadQuerys } from "@/components/composeabe/LoadQuerys";
 import { navTo } from "@/components/utils/PageAction";
 import { onShareTimeline, onShareAppMessage } from "@dcloudio/uni-app";
 import { DataDateUtils } from "@imengyu/js-request-transform";
-import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
+import SimplePageContentLoader from "@/components/loader/SimplePageContentLoader.vue";
 import Parse from "@/components/display/parse/Parse.vue";
 import Box2LineImageRightShadow from "@/common/components/parts/Box2LineImageRightShadow.vue";
 import AppCofig from "@/common/config/AppCofig";
@@ -104,7 +103,7 @@ import ImageBlock3 from "@/components/display/block/ImageBlock3.vue";
 const { onPreviewImage } = useSwiperImagePreview(() => loader.content.value?.images || []);
 const emptyContent = computed(() => (loader.content.value?.content || '').trim() === '');
 
-const loader = useSimplePageContentLoader(async () => {
+const loader = useSimpleDataLoader(async () => {
   const res = await VillageInfoApi.getInfoForDiscover(querys.value.id);
   uni.setNavigationBarTitle({ title: res.title });
   return res;
@@ -120,7 +119,7 @@ const recommendListLoader = useSimpleDataLoader(async () => {
 const { querys } = useLoadQuerys({ 
   id: 0,
   keywords: '',
-}, (t) => loader.loadData(t));
+}, (t) => loader.load(false, t));
 
 function goDetails(id: number) {
   navTo('details', { 

+ 4 - 4
src/pages/home/discover/index.vue

@@ -6,7 +6,7 @@
       src="https://xy.wenlvti.net/app_static/images/dig/IntrodBanner.png"
       width="100%"
       :height="300"
-      radius="radius.medium"
+      radius="radius.md"
       :innerStyle="{
         border: '1px solid #fff',
       }"
@@ -30,7 +30,7 @@
     
     <HomeTitle title="最新推荐" />
     <SimplePageContentLoader :loader="discoverLoader">
-      <FlexCol :gap="25" backgroundColor="background.tertiary" radius="radius.medium" :padding="30">
+      <FlexCol :gap="25" backgroundColor="background.tertiary" radius="radius.md" :padding="30">
         <ImageBlock3 
           v-for="(item, i) in discoverLoader.content.value"
           :key="i"
@@ -53,8 +53,8 @@
 
 <script setup lang="ts">
 import { navTo } from '@/components/utils/PageAction';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
+import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import Loadmore from '@/components/display/loading/Loadmore.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import Height from '@/components/layout/space/Height.vue';

+ 1 - 1
src/pages/home/discover/list.vue

@@ -11,7 +11,7 @@
 <script setup lang="ts">
 import CommonListPage from '../../article/common/CommonListPage.vue';
 import VillageInfoApi from '@/api/inhert/VillageInfoApi';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { navTo } from '@/components/utils/PageAction';
 
 const { querys } = useLoadQuerys({

+ 112 - 12
src/pages/home/index.vue

@@ -7,6 +7,16 @@
     backgroundPosition: 'top center',
     backgroundColor: themeContext.resolveThemeColor('background.primary'),
   }">
+    <FlexCol position="absolute" :left="0" :top="0">
+      <StatusBarSpace />
+      <Button 
+        @click="showCityPopup = true"
+        icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
+        :text="currentCity"
+        type="custom"
+        color="transparent"
+      />
+    </FlexCol>
     <Height height="200px" />
     <FlexCol :gap="20" align="center">
       <SearchBar v-model="searchKeywords" placeholder="搜索" :innerStyle="{
@@ -20,7 +30,8 @@
       <Image 
         src="https://xy.wenlvti.net/app_static/images/home/BannerIndex.png" 
         width="700rpx" 
-        mode="widthFix" 
+        height="200px"
+        mode="aspectFill" 
         :innerStyle="{
           borderRadius: '20rpx',
           clipPath: 'ellipse(100% 90% at 50% 0%)'
@@ -28,11 +39,55 @@
       />
     </FlexCol>
 
-    <VillageMiniMap />
-    <FlexRow justify="space-between" :padding="[10, 16]">
-      <Button icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png" radius="radius.larger" :padding="[10, 30]" :iconProps="{ innerStyle: { marginRight: '10rpx' }}">切换城市</Button>
-      <Button icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png" radius="radius.larger" :padding="[10, 30]" :iconProps="{ innerStyle: { marginRight: '10rpx' }}">我的关注</Button>
-      <Button icon="https://xy.wenlvti.net/app_static/images/home/IconLight.png" radius="radius.larger" :padding="[10, 30]" :iconProps="{ innerStyle: { marginRight: '10rpx' }}">点亮村社</Button>
+    <LightMap
+      small
+    >
+      <NoticeBar 
+        v-if="currentNoticeContent"
+        :content="currentNoticeContent"
+        :innerStyle="{
+          position: 'absolute',
+          top: '20rpx',
+          left: '20rpx',
+          right: '20rpx',
+          zIndex: 100,
+          borderRadius: '30rpx',
+        }"
+        :textStyle="{
+          fontSize: '26rpx',
+        }"
+        icon="https://xy.wenlvti.net/app_static/images/home/IconLightActive.png"
+        :iconProps="{
+          size: 34,
+        }"
+        textColor="#C9211F"
+        backgroundColor="#D9492E10"
+    />
+    </LightMap>
+
+    <FlexRow justify="space-between" :padding="[10, 16]" gap="gap.md">
+      <Button 
+        icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png" 
+        radius="radius.lg" 
+        :padding="[10, 30]"
+        @click="showCityPopup = true"
+      >
+        切换城市
+      </Button>
+      <Button 
+        icon="https://xy.wenlvti.net/app_static/images/home/IconFollow.png" 
+        radius="radius.lg" :padding="[10, 30]" 
+        @click="showMyFollowPopup = true"
+      >
+        我的关注
+      </Button>
+      <Button 
+        icon="https://xy.wenlvti.net/app_static/images/home/IconLight.png" 
+        radius="radius.lg" :padding="[10, 30]" 
+        @click="navTo('/pages/home/light/submit-map', { code: currentCityCode })"
+      >
+        点亮村社
+      </Button>
     </FlexRow>
 
     <HomeTitle title="乡村排名" showMore />
@@ -61,13 +116,33 @@
 
     <Loadmore status="nomore" />
     <Height :height="150" />
+
+    <Popup 
+      v-model:show="showCityPopup" 
+      closeable
+      position="top"
+      size="80vh"
+    >
+      <CitySelect @selectCity="handleSelectCity" />
+    </Popup>
+
+    <Popup 
+      v-model:show="showMyFollowPopup" 
+      closeable
+      position="bottom"
+      round
+      size="80vh"
+    >
+      <VillageMyFollow />
+    </Popup>
   </FlexCol>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
 import { useTheme } from '@/components/theme/ThemeDefine';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
+import { useStorageVar } from '@/components/composeabe/StorageVar';
 import Image from '@/components/basic/Image.vue';
 import Loadmore from '@/components/display/loading/Loadmore.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
@@ -79,17 +154,35 @@ import Button from '@/components/basic/Button.vue';
 import HomeTitle from '@/common/components/parts/HomeTitle.vue';
 import VillageRankList from './components/VillageRankList.vue';
 import VillageUserRankList from './components/VillageUserRankList.vue';
-import ImageBlock2 from '@/components/display/block/ImageBlock2.vue';
 import VillageInfoApi from '@/api/inhert/VillageInfoApi';
-import Avatar from '@/components/display/Avatar.vue';
-import Icon from '@/components/basic/Icon.vue';
-import Text from '@/components/basic/Text.vue';
 import MasonryGrid from '@/components/layout/masonry/MasonryGrid.vue';
 import MasonryGridItem from '@/components/layout/masonry/MasonryGridItem.vue';
 import IndexCommonImageItem from '@/common/components/parts/IndexCommonImageItem.vue';
+import Popup from '@/components/dialog/Popup.vue';
+import CitySelect from './components/CitySelect.vue';
+import VillageMyFollow from './components/VillageMyFollow.vue';
+import type { CityItem } from '@/api/map/MapApi';
+import { navTo } from '@/components/utils/PageAction';
+import MapApi from '@/api/map/MapApi';
+import LightMap from '../components/LightMap.vue';
+import NoticeBar from '@/components/display/NoticeBar.vue';
+import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
+import NavBar from '@/components/nav/NavBar.vue';
+
 
 const themeContext = useTheme();
 const searchKeywords = ref('');
+const showCityPopup = ref(false);
+const showMyFollowPopup = ref(false);
+
+const { value: currentCity } = useStorageVar('currentCityName', '厦门市');
+const currentCityCode = ref('');
+
+watch(currentCity, async (newVal) => {
+  currentCityCode.value = await MapApi.simpleGetRegionCode(newVal);
+}, { immediate: true });
+
+const currentNoticeContent = ref('目前厦门市已被点亮一个社区,刚刚小亮贡献了10个光源,让湖里区点亮了一个社区');
 
 const recommendLoader = useSimpleDataLoader(async () => {
    const res = (await VillageInfoApi.getListForDiscover(
@@ -105,4 +198,11 @@ const recommendLoader = useSimpleDataLoader(async () => {
   }))
 });
 
+
+
+function handleSelectCity(city: CityItem) {
+  currentCity.value = city.name;
+  showCityPopup.value = false;
+}
+
 </script>

+ 8 - 8
src/pages/home/light/details.vue

@@ -1,6 +1,6 @@
 <template>
   <SimplePageContentLoader :loader="contentLoader">
-    <FlexCol v-if="contentLoader.loadStatus.value == 'finished'">
+    <FlexCol v-if="contentLoader.status.value == 'finished'">
       <swiper 
         circular
         :indicator-dots="false"
@@ -15,7 +15,7 @@
         <swiper-item v-for="(item, k) in data.images" :key="k">
           <Image 
             :src="item" 
-            radius="radius.medium"
+            radius="radius.md"
             :height="500"
             :width="750"
             mode="aspectFill"
@@ -25,7 +25,7 @@
         </swiper-item>
       </swiper>
 
-      <FlexCol :padding="20" radius="radius.medium" :gap="20">
+      <FlexCol :padding="20" radius="radius.md" :gap="20">
 
         <Height :height="20" />
         <FlexCol>
@@ -55,7 +55,7 @@
 
         <FlexCol :margin="[20, 0, 30, 0]">
           <SubTitle title="地理位置" />
-          <FlexCol radius="radius.medium" backgroundColor="white" :margin="[20, 0, 0, 0]">
+          <FlexCol radius="radius.md" backgroundColor="white" :margin="[20, 0, 0, 0]">
             <map 
               id="map"
               :latitude="center[1]"
@@ -95,15 +95,15 @@
 
 <script setup lang="ts">
 import { ref, toRefs, type Ref } from 'vue';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { useSwiperImagePreview } from '@/common/composeabe/SwiperImagePreview';
 import { navTo } from '@/components/utils/PageAction';
 import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { useHomePageMiniCommonListGoMoreAndGoDetail, type IHomePageMiniCommonListGoMoreAndGoDetail } from '@/pages/article/common/CommonContent';
 import IntroBlock from '@/pages/article/common/IntroBlock.vue';
 import Parse from '@/components/display/parse/Parse.vue';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import Box2LineLargeImageUserShadow from '@/common/components/parts/Box2LineLargeImageUserShadow.vue';
 import VillageApi from '@/api/inhert/VillageApi';
 import ImagesUrls from '@/common/config/ImagesUrls';
@@ -124,7 +124,7 @@ const markers = ref<any>([]);
 
 const { querys } = useLoadQuerys({ 
   id: 0,
-}, () => contentLoader.loadData());
+}, () => contentLoader.reload());
 
 const data = ref<Record<string, any>>({
   images: [],

+ 9 - 4
src/pages/home/light/submit-map.vue

@@ -1,10 +1,12 @@
 <script setup lang="ts">
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import AppCofig from '@/common/config/AppCofig';
 import LightMap from '@/pages/components/LightMap.vue';
 
 const { querys } = useLoadQuerys({
-  latitude: 0,
-  longitude: 0,
+  latitude: AppCofig.defaultLonLat[1],
+  longitude: AppCofig.defaultLonLat[0],
+  code: ''
 }, async (querys) => {
 });
 
@@ -13,7 +15,10 @@ const { querys } = useLoadQuerys({
 <template>
   <LightMap 
     full
-    :startLonlat="{ latitude: querys.latitude, longitude: querys.longitude }"
+    :startLonlat="{ 
+      latitude: querys.latitude, 
+      longitude: querys.longitude 
+    }"
     :isLightMode="true" 
   />
 </template>

+ 1 - 1
src/pages/home/light/submit.vue

@@ -64,7 +64,7 @@ import { useAuthStore } from '@/store/auth';
 import { back, redirectTo } from '@/components/utils/PageAction';
 import { closeToast, toast } from '@/components/dialog/CommonRoot';
 import { showError } from '@/common/composeabe/ErrorDisplay';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { onMounted, ref } from 'vue';
 import { getVolunteerForm } from '@/pages/dig/admin/data/volunteer';
 import VillageApi, { VolunteerInfo } from '@/api/inhert/VillageApi';

+ 9 - 9
src/pages/home/village/details.vue

@@ -1,6 +1,6 @@
 <template>
   <SimplePageContentLoader :loader="contentLoader">
-    <FlexCol v-if="contentLoader.loadStatus.value == 'finished'">
+    <FlexCol v-if="contentLoader.status.value == 'finished'">
       <swiper 
         circular
         :indicator-dots="false"
@@ -15,7 +15,7 @@
         <swiper-item v-for="(item, k) in data.images" :key="k">
           <Image 
             :src="item" 
-            radius="radius.medium"
+            radius="radius.md"
             :height="500"
             :width="750"
             mode="aspectFill"
@@ -25,7 +25,7 @@
         </swiper-item>
       </swiper>
 
-      <FlexCol :padding="20" radius="radius.medium" :gap="20">
+      <FlexCol :padding="20" radius="radius.md" :gap="20">
 
         <Height :height="20" />
         <FlexCol>
@@ -70,7 +70,7 @@
 
         <FlexCol :margin="[20, 0, 30, 0]">
           <SubTitle title="地理位置" />
-          <FlexCol radius="radius.medium" backgroundColor="white" :margin="[20, 0, 0, 0]">
+          <FlexCol radius="radius.md" backgroundColor="white" :margin="[20, 0, 0, 0]">
             <map 
               id="map"
               :latitude="center[1]"
@@ -132,15 +132,15 @@
 
 <script setup lang="ts">
 import { ref, toRefs, type Ref } from 'vue';
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { useSwiperImagePreview } from '@/common/composeabe/SwiperImagePreview';
 import { navTo } from '@/components/utils/PageAction';
 import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { useHomePageMiniCommonListGoMoreAndGoDetail, type IHomePageMiniCommonListGoMoreAndGoDetail } from '@/pages/article/common/CommonContent';
 import IntroBlock from '@/pages/article/common/IntroBlock.vue';
 import Parse from '@/components/display/parse/Parse.vue';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import Box2LineLargeImageUserShadow from '@/common/components/parts/Box2LineLargeImageUserShadow.vue';
 import VillageApi from '@/api/inhert/VillageApi';
 import ImagesUrls from '@/common/config/ImagesUrls';
@@ -173,7 +173,7 @@ const tagsDataRecommend = ref<TagDataRecommendItem[]>([]) as unknown as Ref<TagD
 
 const { querys } = useLoadQuerys({ 
   id: 0,
-}, () => contentLoader.loadData());
+}, () => contentLoader.reload());
 
 const data = ref<Record<string, any>>({
   images: [],
@@ -253,7 +253,7 @@ const contentLoader = useSimpleDataLoader(async () => {
   }) as any;
 
   tagsDataRecommend.value.forEach(e => {
-    e.loader.loadData();
+    e.loader.reload();
   });
 }, false);
 

+ 1 - 1
src/pages/home/village/index.vue

@@ -26,5 +26,5 @@ import { ref } from 'vue';
 import Card from './introd/card.vue';
 import Tree from './introd/tree.vue';
 
-const tab = ref('tree');
+const tab = ref('card');
 </script>

+ 10 - 10
src/pages/home/village/introd/card.vue

@@ -7,7 +7,7 @@
       color2="white"
       color2Position="45%"
       color3="white"
-      radius="radius.large"
+      radius="radius.lg"
       direction="column"
       :padding="[35,30]"
       gap="gap.lg"
@@ -20,7 +20,7 @@
           <Text :text="villageInfoLoader.content.value?.address" fontConfig="contentText" />
         </FlexCol>
         <FlexCol gap="gap.md">
-          <Button icon="https://xy.wenlvti.net/app_static/images/village/IconUser.png" radius="radius.larger" :padding="[10, 30]" backgroundColor="white">申请管理者</Button>
+          <Button icon="https://xy.wenlvti.net/app_static/images/village/IconUser.png" radius="radius.lgr" :padding="[10, 30]" backgroundColor="white">申请管理者</Button>
           <Text :text="`${villageInfoLoader.content.value?.applyCount} 个乡源果可申请`" fontConfig="secondText" />
         </FlexCol>
       </FlexRow>
@@ -31,7 +31,7 @@
           v-for="index of 3" 
           :key="index" 
           :src="villageInfoLoader.content.value.images[index - 1]" 
-          radius="radius.medium"
+          radius="radius.md"
           :width="200"
           :height="140"
           mode="aspectFill"
@@ -45,7 +45,7 @@
               center
               backgroundColor="background.primary"
             >
-              <Icon name="add" size="fontSize.medium" />
+              <Icon name="add" size="fontSize.md" />
               <Text text="上传封面" fontConfig="secondText" />
             </Touchable>
           </template>
@@ -55,14 +55,14 @@
             center 
             backgroundColor="mask.default"
           >
-            <Text :text="`+${villageInfoLoader.content.value.images.length - 3}`" fontSize="fontSize.large" color="white" />
+            <Text :text="`+${villageInfoLoader.content.value.images.length - 3}`" fontSize="fontSize.lg" color="white" />
           </FlexCol>
         </Image>
       </FlexRow>
 
       <!-- 地址 -->
       <FlexRow align="center" gap="gap.sm">
-        <Icon name="https://xy.wenlvti.net/app_static/images/village/IconMap.png" size="fontSize.medium" />
+        <Icon name="https://xy.wenlvti.net/app_static/images/village/IconMap.png" size="fontSize.md" />
         <Text :text="villageInfoLoader.content.value?.address" fontConfig="contentText" />
       </FlexRow>
       <VillageMiniMap />
@@ -70,7 +70,7 @@
       <FlexRow center gap="gap.lg">
         <Button 
           icon="https://xy.wenlvti.net/app_static/images/village/IconJoin.png" 
-          radius="radius.larger"
+          radius="radius.lgr"
           size="large"
           :innerStyle="{ flexBasis: '20%' }"
         >
@@ -79,7 +79,7 @@
         <Button 
           icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png" 
           size="large"
-          radius="radius.larger"
+          radius="radius.lgr"
           :innerStyle="{ flexBasis: '20%' }"
         >
           加入
@@ -98,7 +98,7 @@
         </FlexRow>
       </FlexRow>
 
-      <FlexRow backgroundColor="background.tertiary" radius="radius.medium" :padding="[30, 20]">
+      <FlexRow backgroundColor="background.tertiary" radius="radius.md" :padding="[30, 20]">
         <FlexCol center gap="gap.sm" flexBasis="25%">
           <Text text="乡源光" fontConfig="secondText" />
           <Text :text="villageInfoLoader.content.value?.light" fontConfig="importantTitle" />
@@ -199,7 +199,7 @@
 <script setup lang="ts">
 import { ref } from 'vue';
 import HomeTitle from '@/common/components/parts/HomeTitle.vue';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import Button from '@/components/basic/Button.vue';
 import Icon from '@/components/basic/Icon.vue';
 import Image from '@/components/basic/Image.vue';

+ 3 - 3
src/pages/home/village/introd/tree.vue

@@ -59,7 +59,7 @@
         <FlexRow 
           v-for="item in infoLoader.content.value" :key="item.id"
           backgroundColor="background.tertiary"
-          radius="radius.medium"
+          radius="radius.md"
           :padding="[20, 30]"
           gap="gap.lg"
           align="center"
@@ -98,7 +98,7 @@
           >
             <Text :text="index + 1" color="white" />
           </BackgroundBox>
-          <Image :src="item.image" :width="210" :height="250" radius="radius.medium" mode="aspectFill" />
+          <Image :src="item.image" :width="210" :height="250" radius="radius.md" mode="aspectFill" />
           <BackgroundBox
             backgroundImage="https://xy.wenlvti.net/app_static/images/village/ImageBlessingBar.png"
             :padding="10"
@@ -124,7 +124,7 @@ import Touchable from '@/components/feedback/Touchable.vue';
 import Image from '@/components/basic/Image.vue';
 import Height from '@/components/layout/space/Height.vue';
 import Avatar from '@/components/display/Avatar.vue';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Progress from '@/components/display/Progress.vue';
 

+ 1 - 1
src/pages/index.vue

@@ -52,7 +52,7 @@ import DiscoverIndex from './home/discover/index.vue';
 import VillageIndex from './home/village/index.vue';
 import CommonRoot from '@/components/dialog/CommonRoot.vue';
 
-const tabIndex = ref(1);
+const tabIndex = ref(0);
 const themeContext = useTheme();
 
 onShareAppMessage(() => {

+ 1 - 1
src/pages/test/blank.vue

@@ -5,7 +5,7 @@
 </template>
 
 <script setup lang="ts">
-import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 
 const { querys } = useLoadQuerys({
   userId: 0,

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

@@ -66,7 +66,7 @@ import { computed } from 'vue';
 import { navTo } from '@/components/utils/PageAction';
 import { confirm } from '@/components/dialog/CommonRoot';
 import { useAuthStore } from '@/store/auth';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import CellGroup from '@/components/basic/CellGroup.vue';
 import Cell from '@/components/basic/Cell.vue';
 import Image from '@/components/basic/Image.vue';

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

@@ -101,7 +101,7 @@ import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
 import Image from '@/components/basic/Image.vue';
 import Text from '@/components/basic/Text.vue';
 import CheckBox from '@/components/form/CheckBox.vue';
-import MemoryTimeOut from '@/common/composeabe/MemoryTimeOut';
+import MemoryTimeOut from '@/components/composeabe/MemoryTimeOut';
 import NavBar from '@/components/nav/NavBar.vue';
 
 const baseLogo = 'https://mncdn.wenlvti.net/app_static/xiangyuan/logo.png';

+ 0 - 49
src/wxcomponents/cax/cax.js

@@ -1,49 +0,0 @@
-import cax from './index'
-
-Component({
-  /**
-   * 组件的属性列表
-   */
-  properties: {
-
-    option: {
-      type: Object
-    }
-  },
-
-  /**
-   * 组件的初始数据
-   */
-  data: {
-    width: 0,
-    height: 0,
-    id: 'caxCanvas' + cax.caxCanvasId++,
-    index: cax.caxCanvasId - 1
-  },
-
-  /**
-   * 组件的方法列表
-   */
-  methods: {
-
-    getCaxCanvasId: function () {
-      return this.data.id
-    },
-
-    touchStart: function (evt) {
-      this.stage.touchStartHandler(evt)
-      this.stage.touchStart && this.stage.touchStart(evt)
-    },
-
-    touchMove: function (evt) {
-      this.stage.touchMoveHandler(evt)
-      this.stage.touchMove && this.stage.touchMove(evt)
-    },
-
-    touchEnd: function (evt) {
-      this.stage.touchEndHandler(evt)
-      this.stage.touchEnd && this.stage.touchEnd(evt)
-    }
-
-  }
-})

+ 0 - 4
src/wxcomponents/cax/cax.json

@@ -1,4 +0,0 @@
-{
-  "component": true,
-  "usingComponents": {}
-}

+ 0 - 12
src/wxcomponents/cax/cax.wxml

@@ -1,12 +0,0 @@
-<canvas 
-    class="cax-canvas"
-    bindtouchstart="touchStart" 
-    bindtouchmove="touchMove" 
-    bindtouchend="touchEnd" 
-    canvas-id="{{ id }}" 
-    style="width: {{width}}px; height: {{height}}px;">
-    <slot></slot>
-</canvas>
-
-<canvas class="cax-canvas-hit" canvas-id="{{id}}Hit" style='width:1px;height:1px;display: none;'></canvas>
-<canvas class="cax-canvas-measure" canvas-id="measure{{index}}" style='width:1px;height:1px;display: none;'></canvas>

+ 0 - 0
src/wxcomponents/cax/cax.wxss


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 6531
src/wxcomponents/cax/index.js