Selaa lähdekoodia

⚙ 动态探索2

快乐的梦鱼 3 viikkoa sitten
vanhempi
commit
0cc35cf0b1

+ 20 - 3
src/common/components/dynamic/NodeRender.vue

@@ -4,22 +4,39 @@
   <template v-if="node.type === 'test'">
     <text>这是一个测试节点</text>
   </template>
+
+
   <!-- 业务代码结束 -->
-  <slot v-else :node="node" />
+  <template v-else>
+    <slot :node="node" :context="xContext" />
+    <!-- #ifdef MP -->
+    <!-- <text style="color: red;">自定义渲染插槽在MP环境下不支持,当前节点类型:{{props.node.type}}</text> -->
+    <!-- #endif -->
+
+    <!-- #ifndef MP -->
+    <!-- <slot :node="node" :context="xContext" /> -->
+    <!-- #endif -->
+   </template>
 </template>
 
 <script setup lang="ts">
-import { ref, type PropType } from 'vue';
-import type { DynamicXNode } from '@/components/dynamic/DynamicX';
+import { inject, ref, type PropType } from 'vue';
+import type { DynamicXNode, DynamicXNodeContext } from '@/components/dynamic/DynamicX';
 
 const props = defineProps({	
   node: {
     type: Object as PropType<DynamicXNode>,
     default: () => ({})
   },
+  mergedProps: {
+    type: Object as PropType<Record<string, any>>,
+    default: () => ({})
+  },
 });
 const emit = defineEmits([]);
 
+const xContext = inject<DynamicXNodeContext>('xContext');
+  
 defineOptions({
   options: {
     virtualHost: true,

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

@@ -96,7 +96,7 @@ export interface BadgeProps {
   badgeSize?: number;
   /**
    * 字号
-   * @default 12.5
+   * @default fontSize.sm
    */
   fontSize?: number|string,
   /**
@@ -135,7 +135,7 @@ const props = withDefaults(defineProps<BadgeProps>(), {
   offset: () => propGetThemeVar('BadgeOffset', { x: 0, y: 0 }),
   badgeSize: () => propGetThemeVar('BadgeSize', 18),
   radius: () => propGetThemeVar('BadgeRadius', 20),
-  fontSize: () => propGetThemeVar('BadgeFontSize', 'fontSize.md') as any,
+  fontSize: () => propGetThemeVar('BadgeFontSize', 'fontSize.sm') as any,
   paddingVertical: () => propGetThemeVar('BadgePaddingVertical', 4),
   paddingHorizontal: () => propGetThemeVar('BadgePaddingHorizontal', 8),
   hiddenIfZero: true,

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

@@ -1,16 +1,6 @@
 <template>
   <view :style="props.page.style">
-    <!-- #ifdef MP -->
     <slot :page="page" :context="xContext" />
-    <!-- #endif -->
-
-    <!-- #ifndef MP -->
-    <DynamicXNodeChild :node="page">
-      <template #nodeRender="{ node }">
-        <slot name="nodeRender" :node="node" />
-      </template>
-    </DynamicXNodeChild>
-    <!-- #endif -->
   </view>
 </template>
 
@@ -19,7 +9,6 @@ import { onBeforeUnmount, onMounted, provide } from 'vue';
 import { MiniScript } from './minisc/MiniScript';
 import { initDynamicXScript } from './x/DynamicXScript';
 import type { DynamicXNode, DynamicXNodeContext, DynamicXProps } from './DynamicX';
-import DynamicXNodeChild from './x/DynamicXNodeChild.vue';
 
 const props = withDefaults(defineProps<DynamicXProps>(), {
 })

+ 43 - 8
src/components/dynamic/x/DynamicXNode.vue

@@ -1,15 +1,48 @@
 <template>
   <template v-if="node.visible !== false">
     <view v-if="node.type === 'view'" :style="node.style">
-      <slot name="renderChild" :node="node" />
+      <!--#ifndef MP-->
+      <DynamicXNodeChildComponent :node="node">
+        <template #nodeRender="{ node }">
+          <slot name="nodeRender" :node="node" />
+        </template>
+      </DynamicXNodeChildComponent>
+      <!--#endif-->
+      <!--#ifdef MP-->
+      <block v-for="(item, index) in node.nodes" :key="item.name || index">
+        <slot name="renderChild" :node="node" />
+      </block>
+      <!--#endif-->
       <template v-if="node.text">{{ node.text }}</template>
     </view>
     <FlexView v-else-if="node.type === 'flex'" :innerStyle="node.style" v-bind="mergedProps">
-      <slot name="renderChild" :node="node" />
+      <!--#ifndef MP-->
+      <DynamicXNodeChildComponent :node="node">
+        <template #nodeRender="{ node }">
+          <slot name="nodeRender" :node="node" />
+        </template>
+      </DynamicXNodeChildComponent>
+      <!--#endif-->
+      <!--#ifdef MP-->
+      <block v-for="(item, index) in node.nodes" :key="item.name || index">
+        <slot name="renderChild" :node="node" />
+      </block>
+      <!--#endif-->
       <template v-if="node.text">{{ node.text }}</template>
     </FlexView>
     <BackgroundBox v-else-if="node.type === 'BackgroundBox'" :innerStyle="node.style" v-bind="mergedProps">
-      <slot name="renderChild" :node="node" />
+      <!--#ifndef MP-->
+      <DynamicXNodeChildComponent :node="node">
+        <template #nodeRender="{ node }">
+          <slot name="nodeRender" :node="node" />
+        </template>
+      </DynamicXNodeChildComponent>
+      <!--#endif-->
+      <!--#ifdef MP-->
+      <block v-for="(item, index) in node.nodes" :key="item.name || index">
+        <slot name="renderChild" :node="node" />
+      </block>
+      <!--#endif-->
       <template v-if="node.text">{{ node.text }}</template>
     </BackgroundBox>
     <Text v-else-if="node.type === 'text'" :innerStyle="node.style" v-bind="mergedProps" :text="node.text" />
@@ -17,17 +50,18 @@
     <Icon v-else-if="node.type === 'icon'" :innerStyle="node.style" v-bind="mergedProps" />
     <Button v-else-if="node.type === 'button'" :innerStyle="node.style" v-bind="mergedProps" />
     <IconButton v-else-if="node.type === 'iconButton'" :innerStyle="node.style" v-bind="mergedProps" />
-    <NodeRender v-else :node="node">
+    <NodeRender v-else :node="node" :mergedProps="mergedProps">
       <slot name="nodeRender" :node="node" />
     </NodeRender>
   </template>
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onBeforeUnmount, onMounted } from 'vue';
+import { computed, inject, onBeforeUnmount, onMounted, reactive } from 'vue';
 import { assertNotNull } from '@imengyu/imengyu-utils';
 import type { DynamicXNode, DynamicXNodeContext } from '../DynamicX';
 import FlexView from '@/components/layout/FlexView.vue';
+import DynamicXNodeChildComponent from './DynamicXNodeChild.vue';
 import NodeRender from '@/common/components/dynamic/NodeRender.vue';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Image from '@/components/basic/Image.vue';
@@ -45,11 +79,11 @@ const emit = defineEmits<{
   (e: 'click'): void
 }>()
 
-const nodeScriptContext = {
+const nodeScriptContext = reactive({
   data: {},
   params: {},
   globalContext: {},
-};
+});
 
 const mergedProps = computed(() => {
   const res : Record<string, any> = {};
@@ -110,7 +144,8 @@ onBeforeUnmount(() => {
 
 defineOptions({
   options: {
+    multipleSlots: true,
     virtualHost: true,
-   }
+  }
 })
 </script>

+ 52 - 40
src/components/dynamic/x/DynamicXNodeChild.vue

@@ -1,48 +1,59 @@
 <template>
-  <template v-if="node.nodes && node.nodes.length > 0">
-    <!-- Level 0 -->
-    <DynamicXNodeComponent 
-      v-for="(item, index) in node.nodes" :key="item.name || index"
-      :node="item"
-    >
-      <template #nodeRender="{ node }">
-        <slot name="nodeRender" :node="node" />
-      </template>
-      <template #renderChild="{ node }">
+  <!-- #ifndef MP -->
+  <!-- 简洁的原生vue递归渲染插槽 -->
+  <DynamicXNodeComponent 
+    v-for="(item, index) in node.nodes" :key="item.name || index"
+    :node="item"
+  >
+    <template #nodeRender="{ node }">
+      <slot name="nodeRender" :node="node" />
+    </template>
+  </DynamicXNodeComponent>
+  <!-- #endif -->
 
-        <!-- #ifndef MP -->
-        <DynamicXNodeChildComponent :node="node">
-          <template #nodeRender="{ node }">
-            <slot name="nodeRender" :node="node" />
-          </template>
-        </DynamicXNodeChildComponent>
-        <!-- #endif -->
-        
-        <!-- 为了适应劣质微信小程序无法递归渲染插槽的问题 -->
-        <!-- #ifdef MP -->
-        <template v-if="node.nodes && node.nodes.length > 0">
-          <!-- Level 1 -->
-          <DynamicXNodeComponent v-for="(item, index) in node.nodes" :key="item.name || index" :node="item">
-            <template #nodeRender="{ node }">
-              <slot name="nodeRender" :node="node" />
-            </template>
-            <template #renderChild="{ node }">
-              <slot name="renderChild" :node="node">
-                <!-- Level 2 -->
-                <DynamicXNodeComponent v-for="(item, index) in node.nodes" :key="item.name || index" :node="item">
-                  <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
-                  <template #renderChild="{ node }">
-                    
+  <!-- 
+    为了适应劣质微信小程序无法递归渲染插槽的问题 
+    此处只能写死递归渲染3级插槽
+  -->
+  <!-- #ifdef MP -->
+  <!-- Level 0 -->
+  <DynamicXNodeComponent 
+    v-for="(childNode, index) in node.nodes" :key="childNode.name || index"
+    :node="childNode"
+  >
+    <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+    <block v-for="(childNode2, index) in childNode.nodes" :key="childNode2.name || index">
+      <template #[`renderChild-${index}`]><!-- 傻xUniapp会自动加上插槽后缀,此处适应其写法 -->
+      
+        <!-- Level 1 -->
+        <DynamicXNodeComponent :node="childNode2">
+          <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+          <block v-for="(childNode3, index) in childNode2.nodes" :key="childNode3.name || index">
+            <template #[`renderChild-${index}`]>
+              <!-- Level 2 -->
+              <DynamicXNodeComponent :node="childNode3">
+                <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+                <block v-for="(childNode4, index) in childNode3.nodes" :key="childNode3.name || index">
+                  <template #[`renderChild-${index}`]>
+                    <!-- Level 3 -->
+                    <DynamicXNodeComponent :node="childNode4">
+                      <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+
+
+
+                    </DynamicXNodeComponent>
                   </template>
-                </DynamicXNodeComponent>
-              </slot>
+                </block>
+
+
+              </DynamicXNodeComponent>
             </template>
-          </DynamicXNodeComponent>
-        </template>
-        <!-- #endif -->
+          </block>
+        </DynamicXNodeComponent>
       </template>
-    </DynamicXNodeComponent>
-  </template>
+    </block>
+  </DynamicXNodeComponent>
+  <!-- #endif -->
 </template>
 
 <script lang="ts" setup>
@@ -66,6 +77,7 @@ defineSlots<{
 
 defineOptions({
   options: {
+    multipleSlots: true,
     virtualHost: true,
    }
 })

+ 2 - 0
src/pages/home/components/VillageMiniMap.vue

@@ -48,6 +48,8 @@
       textColor="#C9211F"
       backgroundColor="#D9492E10"
     />
+
+    <slot />
   </div>
 </template>
 

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

@@ -19,10 +19,9 @@
     >
       <FlexCol 
         position="absolute"
+        :top="item.rank == 1 ? '25.7%' : '24.7%'"
         :left="15"
         :right="15"
-        :bottom="item.rank == 1 ? 55 : 30"
-        justify="flex-end"
         align="center"
       >
         <Avatar 

+ 150 - 94
src/pages/home/village/introd/card.vue

@@ -8,91 +8,86 @@
       <DynamicXNodeChild :node="page">
         <template #nodeRender="{ node }">
 
-          <template v-if="node.type === 'Card:Basic'">
-
-        <!-- 标题 -->
-        <FlexRow justify="space-between" gap="gap.lg" wrap align="center" width="100%">
-          <FlexRow gap="gap.md">
-            <Text 
-              :text="title" 
-              :lines="1" 
-              :fontSize="40"
-              wordBreak="break-all"
-              fontConfig="primaryTitle"
-            />
-          </FlexRow>
-          <FlexRow flex="1" justify="space-between" align="center" gap="gap.lg">
-            <IconButton icon="cascades" :size="40" @click="shareQRDialog?.show()" />
-            <FlexRow align="center" gap="gap.lg">
-              <BubbleTip
-                v-model:show="showFollowTip"
-                content="关注我,方便下次进入"
-                @contentClick="isFollowed ? undefined : onFollow()"
-                @close="handleCloseFollowTip"
-              >
-                <Button 
-                  :icon="isFollowed ? 'success' : 'https://xy.wenlvti.net/app_static/images/village/IconJoin.png'" 
-                  radius="radius.lgr"
-                  :text="isFollowed ? (title.length < 5 ? '已关注' : '') : '关注'"
-                  :padding="[15, 10]"
-                  :iconProps="{ size: 38 }"
-                  @click="isFollowed ? onUnFollow() : onFollow()"
+          <FlexCol v-if="node.type === 'Card:Basic'" gap="gap.md">
+            <!-- 标题 -->
+            <FlexRow justify="space-between" gap="gap.lg" wrap align="center" width="100%">
+              <FlexRow gap="gap.md">
+                <Text 
+                  :text="title" 
+                  :lines="1" 
+                  :fontSize="40"
+                  wordBreak="break-all"
+                  fontConfig="primaryTitle"
                 />
-              </BubbleTip>
-              <FlexRow 
-                v-if="isJoined"
-                gap="gap.md"
-                radius="radius.lgr"
-                backgroundColor="background.tertiary"
-              >
-                <Avatar 
-                  :src="authStore.userInfo?.avatar" 
-                  :size="65"
-                  defaultAvatar="https://xy.wenlvti.net/app_static/images/mine/DefaultAvatar.png"
-                />
-                <FlexCol position="relative" gap="gap.sm" :padding="[0,30,0,0]">
-                  <FlexRow align="center" :gap="10">
-                    <Text :text="displayMyName" fontSize="23" :lines="1" :maxWidth="150" fontConfig="lightImportantTitle" />
+              </FlexRow>
+              <FlexRow flex="1" justify="space-between" align="center" gap="gap.lg">
+                <IconButton icon="cascades" :size="40" @click="shareQRDialog?.show()" />
+                <FlexRow align="center" gap="gap.lg">
+                  <BubbleTip
+                    v-model:show="showFollowTip"
+                    content="关注我,方便下次进入"
+                    @contentClick="isFollowed ? undefined : onFollow()"
+                    @close="handleCloseFollowTip"
+                  >
                     <Button 
-                      v-if="isStaff" 
-                      :innerStyle="{ position: 'absolute', right: 0, top: 0, transform: 'translate(5%,-50%)' }"
-                      text="管理" size="mini" radius="radius.md" type="danger" 
-                      @click="staffInfoRef?.show()" 
+                      :icon="isFollowed ? 'success' : 'https://xy.wenlvti.net/app_static/images/village/IconJoin.png'" 
+                      radius="radius.lgr"
+                      :text="isFollowed ? (title.length < 5 ? '已关注' : '') : '关注'"
+                      :padding="[15, 10]"
+                      :iconProps="{ size: 38 }"
+                      @click="isFollowed ? onUnFollow() : onFollow()"
                     />
+                  </BubbleTip>
+                  <FlexRow 
+                    v-if="isJoined"
+                    gap="gap.md"
+                    radius="radius.lgr"
+                    backgroundColor="background.tertiary"
+                  >
+                    <Avatar 
+                      :src="authStore.userInfo?.avatar" 
+                      :size="65"
+                      defaultAvatar="https://xy.wenlvti.net/app_static/images/mine/DefaultAvatar.png"
+                    />
+                    <FlexCol position="relative" gap="gap.sm" :padding="[0,30,0,0]">
+                      <FlexRow align="center" :gap="10">
+                        <Text :text="displayMyName" fontSize="23" :lines="1" :maxWidth="150" fontConfig="lightImportantTitle" />
+                        <Button 
+                          v-if="isStaff" 
+                          :innerStyle="{ position: 'absolute', right: 0, top: 0, transform: 'translate(5%,-50%)' }"
+                          text="管理" size="mini" radius="radius.md" type="danger" 
+                          @click="staffInfoRef?.show()" 
+                        />
+                      </FlexRow>
+                      <IconButton icon="edit-filling" size="26" @click="changeNickRef?.show()">
+                        <Text text="改昵称" :wrap="false" fontSize="22" fontConfig="subText" />
+                      </IconButton>
+                    </FlexCol>
                   </FlexRow>
-                  <IconButton icon="edit-filling" size="26" @click="changeNickRef?.show()">
-                    <Text text="改昵称" :wrap="false" fontSize="22" fontConfig="subText" />
-                  </IconButton>
-                </FlexCol>
+                  <Button 
+                    v-else
+                    icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png"
+                    radius="radius.lgr"
+                    text="加入"
+                    @click="handleGoJoin()"
+                  />
+                </FlexRow>
               </FlexRow>
-              <Button 
-                v-else
-                icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png"
-                radius="radius.lgr"
-                text="加入"
-                @click="handleGoJoin()"
-              />
             </FlexRow>
-          </FlexRow>
-        </FlexRow>
 
-        <!-- 简介 -->
-        <FlexView :direction="descOpen ? 'column' : 'row'" gap="gap.md" justify="space-between" align="flex-start">
-          <!-- 简介 -->
-          <TextEllipsis 
-            fontConfig="secondText"
-            :outerProps="{ direction: 'row', width: descOpen ? '' : '490rpx' }"
-            :lines="1" 
-            :expandable="(villageInfoLoader.content.value?.desc as string || '').length > 20"
-            :text="(villageInfoLoader.content.value?.desc as string) || '暂无简介,欢迎您来编写完善!'"
-            wordBreak="break-all"
-            @expand="descOpen = true;"
-            @collapse="descOpen = false;"
-          />
-          <Button size="mini" icon="edit-filling" text="编辑简介" @click="handleGoEdit" />
-        </FlexView>
+            <!-- 简介 -->
+            <TextEllipsis 
+              fontConfig="secondText"
+              :outerProps="{ direction: 'row', width: '490rpx' }"
+              :lines="1" 
+              :expandable="(villageInfoLoader.content.value?.desc as string || '').length > 20"
+              :text="(villageInfoLoader.content.value?.desc as string) || '暂无简介,欢迎您来编写完善!'"
+              wordBreak="break-all"
+              @expand="descOpen = true;"
+              @collapse="descOpen = false;"
+            />
 
-          </template>
+          </FlexCol>
           <template v-else-if="node.type === 'Card:Level'">
 
             <!-- 状态与申请 -->
@@ -140,14 +135,7 @@
           </template>
           <template v-else-if="node.type === 'Card:AddressAndMap'">
             
-            <!-- 地址 -->
-            <FlexRow justify="space-between" align="center">
-              <FlexRow align="center" gap="gap.sm">
-                <Icon name="https://xy.wenlvti.net/app_static/images/village/IconMap.png" size="fontSize.md" />
-                <Text :text="villageInfoLoader.content.value?.address" fontConfig="contentText" fontSize="fontSize.sm" />
-              </FlexRow>
-              <Button size="mini" icon="picture" text="村社相册" @click="handleGoGallery" />
-            </FlexRow>
+            <!-- 地图 -->
             <VillageMiniMap 
               v-if="villageInfoLoader.content.value"
               :lonlat="{ 
@@ -155,7 +143,14 @@
                 latitude: villageInfoLoader.content.value.latitude 
               }" 
               :currentNoticeContent="currentNoticeContent"
-            />
+            >
+              <FlexRow position="absolute" :inset="{ l: 13, r: 13, b: 13 }" center :zIndex="100">
+                <FlexRow align="center" gap="gap.sm" backgroundColor="background.tertiary" radius="radius.lg" padding="padding.sm">
+                  <Icon name="https://xy.wenlvti.net/app_static/images/village/IconMap.png" size="fontSize.md" />
+                  <Text :text="villageInfoLoader.content.value?.address" :maxWidth="550" fontConfig="contentText" fontSize="fontSize.sm" />
+                </FlexRow>
+              </FlexRow>
+            </VillageMiniMap>
 
           </template>
           <template v-else-if="node.type === 'Card:Static'">
@@ -172,6 +167,9 @@
                 <Text :text="`${villageInfoLoader.content.value?.level}级`" fontConfig="primaryTitle" />
               </FlexRow>
             </FlexRow>
+            
+            <Height height="gap.md" />
+
             <FlexRow backgroundColor="background.tertiary" radius="radius.md" :padding="[30, 20]">
               <Touchable direction="column" center gap="gap.sm" flexBasis="25%" @click="navTo('/pages/home/village/task/index')">
                 <Text text="乡源光" fontConfig="secondText" fontSize="fontSize.sm" />
@@ -223,8 +221,10 @@
                 villageId: villageStore.currentVillage?.id ?? undefined,
               })" 
             />
+            <Height height="gap.md" />
             <VillageUserRankList 
               :list="villageUserRankListLoader.content.value ?? []" 
+              scoreSuffix="文化积分"
               @goDetails="navTo('/pages/home/village/volunteer/detail', { id: $event.id })"
             />
 
@@ -255,6 +255,7 @@
                 </FlexRow>
               </template>
             </HomeTitle>
+            <Height height="gap.md" />
             <ProvideVar :vars="{
               GridItemIconSize: 90,
               GridItemBackgroundColor: 'transparent',
@@ -278,6 +279,7 @@
 
             <!-- 活力乡源 -->
             <HomeTitle :title="node.props?.title" />
+            <Height height="gap.md" />
             <ProvideVar :vars="{
               GridItemIconSize: 90,
               GridItemBackgroundColor: 'transparent',
@@ -328,10 +330,12 @@
                 </FlexRow>
               </template>
             </HomeTitle>
+            <Height height="gap.md" />
             <RoundTags 
               v-model:active="listActiveTag" 
               :tags="node.props?.tags" 
             />
+            <Height height="gap.md" />
             <OfficialAccountPublishWrap
               :topic="recommendTagName" 
               :path="recommendPath"
@@ -342,10 +346,6 @@
           </template>
         </template>
       </DynamicXNodeChild>
-
-      
-
-
     </template>
   </DynamicX>
 
@@ -406,7 +406,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref, watch } from 'vue';
+import { computed, reactive, ref, watch } from 'vue';
 import { useUserTools } from '@/common/composeabe/UserTools';
 import { useOfficialAccount } from '../../composeabe/OfficialAccount';
 import { useAuthStore } from '@/store/auth';
@@ -452,10 +452,11 @@ import Avatar from '@/components/display/Avatar.vue';
 import ChangeNickDialog from '../dialogs/ChangeNickDialog.vue';
 import StaffInfoDialog from '../dialogs/StaffInfoDialog.vue';
 import ShareQRDialog from '../dialogs/ShareQRDialog.vue';
-import FlexView from '@/components/layout/FlexView.vue';
 import DynamicX from '@/components/dynamic/DynamicX.vue';
 import type { DynamicXPage } from '@/components/dynamic/DynamicX';
 import DynamicXNodeChild from '@/components/dynamic/x/DynamicXNodeChild.vue';
+import Badge from '@/components/display/Badge.vue';
+import { isDevEnv } from '@/common/config/AppCofig';
 
 const emit = defineEmits<{
   (e: 'goTree'): void;
@@ -618,6 +619,8 @@ const staffInfoRef = ref<InstanceType<typeof StaffInfoDialog>>();
 
 async function checkIsStaff(needTopic?: boolean) {
   assertNotNull(villageStore.currentVillage?.id)
+  if (isDevEnv)
+    return true;
   const isAdmin = needTopic ? 
     await OfficialApi.checkTopicRule(villageStore.currentVillage.id)
     : (authStore.isAdmin || isStaff.value);
@@ -680,17 +683,21 @@ const upgradeRef = ref<InstanceType<typeof UpgradeDialog>>();
 const villageGalleryRef = ref<InstanceType<typeof VillageGallery>>();
 const changeNickRef = ref<InstanceType<typeof ChangeNickDialog>>();
 
-const pageScriptGlobal = {
+const pageScriptGlobal = reactive({
+  handleGoGallery,
+  handleGoEdit,
+  handleGoDigManage,
   handleGoCollect,
   getVillageId: () => villageStore.currentVillage?.id ?? 0,
   navTo,
   emit,
-};
+});
 const page : DynamicXPage = {
   name: 'VILLAGE',
   type: 'page',
   nodes: [
     {
+      name: 'VILLAGE:CONTENT',
       type: 'flex',
       props: {
         direction: 'column',
@@ -699,6 +706,7 @@ const page : DynamicXPage = {
       },
       nodes: [
         {
+          name: 'VILLAGE:CONTENT:Basic',
           type: 'BackgroundBox',
           props: {
             color1: "#eecaa0",
@@ -724,23 +732,70 @@ const page : DynamicXPage = {
               type: 'Card:AddressAndMap',
             },
             {
+              type: 'flex',
+              props: {
+                direction: 'row',
+                justify: "center" ,
+                align: "center",
+                gap:"gap.md",
+              },
+              nodes: [
+                {
+                  type: 'button',
+                  props: {
+                    size: "mini",
+                    icon: "picture",
+                    text: "村社相册",
+                  },
+                  events: {
+                    click: 'globalContext.handleGoGallery()',
+                  },
+                },
+                {
+                  type: 'button',
+                  props: {
+                    size: "mini",
+                    icon: "edit-filling",
+                    text: "编辑简介",
+                  },
+                  events: {
+                    click: 'globalContext.handleGoEdit()',
+                  },
+                },
+                {
+                  type: 'button',
+                  props: {
+                    size: "mini",
+                    icon: "fabulous",
+                    text: "主页换肤",
+                  },
+                  events: {
+                    click: 'globalContext.handleGoDigManage()',
+                  },
+                },
+              ],
+            },
+            {
               type: 'Card:Static',
             },
           ],
         },
         {
+          name: 'VILLAGE:CONTENT:Rank',
           type: 'Block:Rank',
           props: {
             title: '排行榜',
           },
         },
         {
+          name: 'VILLAGE:CONTENT:Collect',
           type: 'Block:Collect',
           props: {
             title: '魅力乡源',
           },
         },
         {
+          name: 'VILLAGE:CONTENT:Games',
           type: 'Block:Games',
           props: {
             title: '活力乡源',
@@ -769,6 +824,7 @@ const page : DynamicXPage = {
           },
         },
         {
+          name: 'VILLAGE:CONTENT:Official',
           type: 'Block:Official',
           props: {
             title: '文脉乡源',

+ 35 - 0
src/pages/home/village/introd/card/dynamic/BlockGames.vue

@@ -0,0 +1,35 @@
+<template>
+  <!-- 活力乡源 -->
+  <HomeTitle :title="node.props?.title" />
+  <ProvideVar :vars="{
+    GridItemIconSize: 90,
+    GridItemBackgroundColor: 'transparent',
+    GridItemPaddingHorizontal: 0,
+    GridItemPaddingVertical: 8,
+  }">
+    <Grid :borderGrid="false" :mainAxisCount="4">
+      <GridItem
+        v-for="item in node.props?.items" 
+        :key="item.title"
+        :title="item.title"
+        :icon="item.icon"
+        touchable 
+        @click="() => context.getScriptEngine().execute(item.handler, node.getNodeScriptContext?.())" 
+      />
+    </Grid>
+  </ProvideVar>
+</template>
+
+<script lang="ts" setup>
+import HomeTitle from '@/common/components/parts/HomeTitle.vue';
+import type { DynamicXNode, DynamicXNodeContext } from '@/components/dynamic/DynamicX';
+import Grid from '@/components/layout/grid/Grid.vue';
+import GridItem from '@/components/layout/grid/GridItem.vue';
+import ProvideVar from '@/components/theme/ProvideVar.vue';
+
+const props = withDefaults(defineProps<{
+  node: DynamicXNode;
+  context: DynamicXNodeContext;
+}>(), {
+})
+</script>