Quellcode durchsuchen

📦 专家村落存储空间显示和上传记录

快乐的梦鱼 vor 3 Tagen
Ursprung
Commit
7b147e9cf3

+ 40 - 13
src/common/components/upload/AliOssUploadCo.ts

@@ -1,6 +1,7 @@
 import { Base64Utils, RandomUtils, StringUtils } from "@imengyu/imengyu-utils";
 import { hmacSha1Base64 } from "./hmac";
 import type { UploaderAction } from "@/components/form/Uploader";
+import LightVillageApi from "@/api/light/LightVillageApi";
 
 const client = {
   region: 'oss-cn-shenzhen',
@@ -76,23 +77,49 @@ function uploadOSS(uploadPath: string, filePath: string, cancelHandler: { cancel
   })
 }
 
-export function useAliOssUploadCo(subPath: string) {
+export function useAliOssUploadCo(subPath: string, storageManage?: {
+  getVillageId: () => number,
+  overflow: () => void,
+}) {
   return (action: UploaderAction) => {
-    const name = StringUtils.path.getFileName(action.item.filePath);
-    const uploadPath = `${subPath}/${name.split('.')[0]}-${RandomUtils.genNonDuplicateID(26)}.${StringUtils.path.getFileExt(name)}`;  
     const cancelHandler: { cancel: () => void } = {
       cancel: () => {},
-    };    
-    uploadOSS(uploadPath, action.item.filePath, cancelHandler,(progress) => {
-      action.onProgress?.(progress)
-    }).then((res) => {  
-      action.onFinish?.({
-        uploadedUrl: res,
-        previewUrl: res,
+    }; 
+
+    if (storageManage && action.item.size) {
+      const villageId = storageManage.getVillageId();
+      //更新存储
+      LightVillageApi.updateStorage({
+        villageId,
+        memorySize: action.item.size,
+      }).then(() => {
+        doUpload();
+      }).catch((err) => {
+        if ((err + '').includes('超')) {
+          //存储已超限
+          storageManage.overflow();
+        }
+        action.onError?.(err);
       });
-    }).catch((err) => {
-      action.onError?.(err);
-    });
+    } else {
+      doUpload();
+    }
+
+    function doUpload() {
+      const name = StringUtils.path.getFileName(action.item.filePath);
+      const uploadPath = `${subPath}/${name.split('.')[0]}-${RandomUtils.genNonDuplicateID(26)}.${StringUtils.path.getFileExt(name)}`;  
+      uploadOSS(uploadPath, action.item.filePath, cancelHandler,(progress) => {
+        action.onProgress?.(progress)
+      }).then((res) => {
+        action.onFinish?.({
+          uploadedUrl: res,
+          previewUrl: res,
+        });
+      }).catch((err) => {
+        action.onError?.(err);
+      });
+    }
+
     return () => {
       //取消上传
       cancelHandler.cancel();

+ 29 - 1
src/common/components/upload/ImageUploadCo.ts

@@ -1,10 +1,36 @@
 import CommonContent from "@/api/CommonContent";
+import LightVillageApi from "@/api/light/LightVillageApi";
 import type { UploaderAction } from "@/components/form/Uploader";
 
-export function useImageSimpleUploadCo(additionData?: Record<string, any>) {
+export function useImageSimpleUploadCo(storageManage?: {
+  getVillageId: () => number,
+  overflow: () => void,
+}, additionData?: Record<string, any>) {
   return (action: UploaderAction) => {
     action.onStart('正在上传');
     let task: UniApp.UploadTask | null = null;
+
+    if (storageManage && action.item.size) {
+      const villageId = storageManage.getVillageId();
+      //更新存储
+      LightVillageApi.updateStorage({
+        villageId,
+        memorySize: action.item.size,
+      }).then(() => {
+        doUpload();
+      }).catch((err) => {
+        if ((err + '').includes('超')) {
+          //存储已超限
+          storageManage.overflow();
+        }
+        action.onError?.(err);
+      });
+    } else {
+      doUpload();
+    }
+    
+    function doUpload() {
+
     CommonContent.uploadFile(action.item.filePath, 'image', 'file', additionData, (t) => {
       task = t;
       task.onProgressUpdate((res) => {
@@ -20,6 +46,8 @@ export function useImageSimpleUploadCo(additionData?: Record<string, any>) {
       }).catch((err) => {
         action.onError?.(err);
       })
+    }
+
     return () => {
       task?.abort();
     };

+ 19 - 5
src/pages/dig/forms/common.vue

@@ -23,6 +23,7 @@
               :options="formDefine"
               :model="formModel"
               :globalParams="querys"
+              @storageOverflow="handleStorageOverflow"
             />
           </ProvideVar>
         </BackgroundBox>
@@ -48,7 +49,7 @@ import { nextTick, ref, watch, type Ref } from 'vue';
 import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { CollectableModulesIdMap, getVillageInfoForm, getVillageInfoFormIds, type SingleForm } from './forms';
 import { showError } from '@/common/composeabe/ErrorDisplay';
-import { backAndCallOnPageBack } from '@/components/utils/PageAction';
+import { backAndCallOnPageBack, navTo } from '@/components/utils/PageAction';
 import { toast } from '@/components/utils/DialogAction';
 import { alert, confirm } from '@/components/dialog/CommonRoot';
 import { Debounce, RequestApiError, waitTimeOut } from '@imengyu/imengyu-utils';
@@ -56,7 +57,6 @@ import VillageInfoApi, { CommonInfoModel } from '@/api/inhert/VillageInfoApi';
 import DynamicForm from '@/components/dynamic/DynamicForm.vue';
 import LoadingPage from '@/components/display/loading/LoadingPage.vue';
 import Button from '@/components/basic/Button.vue';
-import CommonRoot from '@/components/dialog/CommonRoot.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import Height from '@/components/layout/space/Height.vue';
 import XBarSpace from '@/components/layout/space/XBarSpace.vue';
@@ -66,7 +66,6 @@ import type { UploaderFieldInstance } from '@/components/form/UploaderField.vue'
 import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
 import ProvideVar from '@/components/theme/ProvideVar.vue';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
-import BackgroundImageButton from '@/components/basic/BackgroundImageButton.vue';
 import PrimaryButton from '@/common/components/PrimaryButton.vue';
 
 const loading = ref(false);
@@ -138,6 +137,7 @@ const { querys } = useLoadQuerys({
         findId,
         model,
       );
+      console.log('加载数据', formData);
     }
   } catch (e) {
     if (!(e instanceof RequestApiError && e.errorMessage.startsWith('请完成')))
@@ -259,10 +259,10 @@ function getSaveName() {
 async function loadLocalSave(formData: CommonInfoModel|undefined) {
   if (!currentFormInfo)
     return formData;
-  console.log('加载暂存数据');
   const saveName = getSaveName();
   const saveData = uni.getStorageSync(saveName);
   if (saveData) {
+    console.log('加载暂存数据');
     const res = await confirm({
       content: '您有上次编辑未完成的内容,是否要从上次的编辑数据继续?',
       cancelText: '取消',
@@ -273,7 +273,7 @@ async function loadLocalSave(formData: CommonInfoModel|undefined) {
       const d = JSON.parse(saveData);
       d.id = 0;
       formData = new currentFormInfo[0]().fromServerSide(d) as CommonInfoModel;  
-      console.log('暂存数据', formData);
+      console.log('读取暂存数据', formData);
     } else
       deleteLocalSave();
   }
@@ -291,6 +291,20 @@ function saveLocalSave() {
   console.log('保存暂存数据');
 }
 
+async function handleStorageOverflow() {
+  const res = await confirm({
+    title: '提示',
+    content: '当前村社采编数据库已超过存储限制啦,无法再上传资源,您可以选择升级村社获得更多存储空间。',
+    confirmText: '去升级',
+    cancelText: '取消',
+  });
+  if (res) {
+    navTo('/pages/home/village/upgrade/upgrade-village', {
+      villageId: querys.value.villageId,
+    });
+  }
+}
+
 watch(formModel, () => {
   if (!canSaveNow)
     return;

+ 4 - 1
src/pages/dig/forms/data/building.ts

@@ -532,7 +532,10 @@ export const villageInfoDistributionForm : SingleForm = [CommonInfoModel, (r) =>
       type: 'uploader', 
       defaultValue: '',
       additionalProps: {
-        upload: useAliOssUploadCo('xiangyuan/distribution'),
+        upload: useAliOssUploadCo('xiangyuan/distribution', {
+          getVillageId: () => r.value.getGlobalParams().villageId,
+          overflow: () => r.value.emitMessage('storageOverflow'),
+        }),
         maxFileSize: 1024 * 1024 * 20,
         single: true,
       } as UploaderFieldProps,

+ 16 - 4
src/pages/dig/forms/data/common.ts

@@ -95,7 +95,10 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             type: 'uploader',
             defaultValue: '',
             additionalProps: {
-              upload: useAliOssUploadCo('xiangyuan/common'),
+              upload: useAliOssUploadCo('xiangyuan/common', {
+                getVillageId: () => ref.value.getGlobalParams().villageId,
+                overflow: () => ref.value.emitMessage('storageOverflow'),
+              }),
               maxFileSize: 1024 * 1024 * 20, // 20MB
               maxUploadCount: 20,
             } as UploaderFieldProps,
@@ -110,7 +113,10 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             type: 'uploader',
             defaultValue: '',
             additionalProps: {
-              upload: useAliOssUploadCo('xiangyuan/video'),
+              upload: useAliOssUploadCo('xiangyuan/video', {
+                getVillageId: () => ref.value.getGlobalParams().villageId,
+                overflow: () => ref.value.emitMessage('storageOverflow'),
+              }),
               maxFileSize: 1024 * 1024 * 1000, // 1000MB
               single: true,
               chooseType: 'video',
@@ -142,7 +148,10 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             type: 'uploader',
             defaultValue: '',
             additionalProps: {
-              upload: useAliOssUploadCo('xiangyuan/archives'),
+              upload: useAliOssUploadCo('xiangyuan/archives', {
+                getVillageId: () => ref.value.getGlobalParams().villageId,
+                overflow: () => ref.value.emitMessage('storageOverflow'),
+              }),
               maxFileSize: 1024 * 1024 * 200, // 200MB
               chooseType: '',
               single: true,
@@ -158,7 +167,10 @@ export function villageCommonContent (ref: Ref<IDynamicFormRef>, options: {
             type: 'uploader',
             defaultValue: '',
             additionalProps: {
-              upload: useAliOssUploadCo('xiangyuan/annex'),
+              upload: useAliOssUploadCo('xiangyuan/annex', {
+                getVillageId: () => ref.value.getGlobalParams().villageId,
+                overflow: () => ref.value.emitMessage('storageOverflow'),
+              }),
               maxFileSize: 1024 * 1024 * 1000, // 1000MB
               maxUploadCount: 20,
               chooseType: '',

+ 8 - 3
src/pages/dig/forms/data/cultural.ts

@@ -52,7 +52,6 @@ export function villageInfoFolkCultureForm(title: string) : SingleForm {
       ...villageCommonContent(m, {
         title: title,
         showTitle: false,
-        contentKey: 'details'
       }).formItems
     ]
   }), { title: title, typeName: 'folkCultureType', }];
@@ -79,7 +78,10 @@ export const villageInfoCulture : GroupForm = {
         type: 'uploader',
         defaultValue: '',
         additionalProps: {
-          upload: useAliOssUploadCo('xiangyuan/cultural/scan'),
+          upload: useAliOssUploadCo('xiangyuan/cultural/scan', {
+            getVillageId: () => m.value.getGlobalParams().villageId,
+            overflow: () => m.value.emitMessage('storageOverflow'),
+          }),
           maxFileSize: 1024 * 1024 * 20,
           maxUploadCount: 20,
         } as UploaderFieldProps,
@@ -165,7 +167,10 @@ export const villageInfoCulture : GroupForm = {
           type: 'uploader',
           defaultValue: '',
           additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/cultural/video'),
+            upload: useAliOssUploadCo('xiangyuan/cultural/video', {
+              getVillageId: () => m.value.getGlobalParams().villageId,
+              overflow: () => m.value.emitMessage('storageOverflow'),
+            }),
             chooseType: '',
             maxFileSize: 1024 * 1024 * 1000, // 1000MB
             single: true,

+ 4 - 1
src/pages/dig/forms/data/element.ts

@@ -137,7 +137,10 @@ export const vilElementForm : SingleForm = [CommonInfoModel, (r) => ({
           type: 'uploader', 
           defaultValue: '',
           additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/element'),
+            upload: useAliOssUploadCo('xiangyuan/element', {
+              getVillageId: () => r.value.getGlobalParams().villageId,
+              overflow: () => r.value.emitMessage('storageOverflow'),
+            }),
             maxFileSize: 1024 * 1024 * 20,
             maxUploadCount: 20,
           } as UploaderFieldProps,

+ 1 - 1
src/pages/dig/forms/data/food.ts

@@ -20,7 +20,7 @@ export function villageInfoFoodProductsForm(title: string) : SingleForm {
       }, 
       {
         label: `${title}详情`,
-        name: 'details',
+        name: 'content',
         type: 'richtext',
         defaultValue: '',
         additionalProps: {

+ 4 - 1
src/pages/dig/forms/data/relic.ts

@@ -127,7 +127,10 @@ export const villageInfoRelicForm : SingleForm = [CommonInfoModel, (r) => ({
           type: 'uploader', 
           defaultValue: '',
           additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/relic'),
+            upload: useAliOssUploadCo('xiangyuan/relic', {
+              getVillageId: () => r.value.getGlobalParams().villageId,
+              overflow: () => r.value.emitMessage('storageOverflow'),
+            }),
             maxFileSize: 1024 * 1024 * 20,
             maxUploadCount: 20,
           } as UploaderFieldProps,

+ 8 - 2
src/pages/dig/forms/data/specker.ts

@@ -89,7 +89,10 @@ export const villageInfoSpeakerForm : SingleForm = [CommonInfoModel, (r) => ({
           type: 'uploader',
           defaultValue: '',
           additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/speaker'),
+            upload: useAliOssUploadCo('xiangyuan/speaker', {
+              getVillageId: () => r.value.getGlobalParams().villageId,
+              overflow: () => r.value.emitMessage('storageOverflow'),
+            }),
             maxFileSize: 1024 * 1024 * 20,
             maxUploadCount: 1,
           } as UploaderFieldProps,
@@ -111,7 +114,10 @@ export const villageInfoSpeakerForm : SingleForm = [CommonInfoModel, (r) => ({
           type: 'uploader',
           defaultValue: '',
           additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/speaker/audio'),
+            upload: useAliOssUploadCo('xiangyuan/speaker/audio', {
+              getVillageId: () => r.value.getGlobalParams().villageId,
+              overflow: () => r.value.emitMessage('storageOverflow'),
+            }),
             maxFileSize: 1024 * 1024 * 500, // 500MB
             maxUploadCount: 1,
             accept: 'audio/*',

+ 9 - 3
src/pages/dig/forms/data/travel.ts

@@ -7,7 +7,7 @@ import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
 import type { GroupForm, SingleForm } from "../forms";
 import { villageCommonContent } from "./common";
 
-export const villageInfoTravelGuideForm : SingleForm = [CommonInfoModel, () => ({
+export const villageInfoTravelGuideForm : SingleForm = [CommonInfoModel, (r) => ({
   formItems: [
     {
       label: '交通基本信息', 
@@ -161,7 +161,10 @@ export const villageInfoTravelGuideForm : SingleForm = [CommonInfoModel, () => (
           type: 'uploader', 
           defaultValue: '',
           additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/travel/panorama'),
+            upload: useAliOssUploadCo('xiangyuan/travel/panorama', {
+              getVillageId: () => r.value.getGlobalParams().villageId,
+              overflow: () => r.value.emitMessage('storageOverflow'),
+            }),
             maxFileSize: 1024 * 1024 * 20,
             single: true,
           } as UploaderFieldProps,
@@ -173,7 +176,10 @@ export const villageInfoTravelGuideForm : SingleForm = [CommonInfoModel, () => (
           type: 'uploader', 
           defaultValue: '',
           additionalProps: {
-            upload: useAliOssUploadCo('xiangyuan/travel/guide'),
+            upload: useAliOssUploadCo('xiangyuan/travel/guide', {
+              getVillageId: () => r.value.getGlobalParams().villageId,
+              overflow: () => r.value.emitMessage('storageOverflow'),
+            }),
             maxFileSize: 1024 * 1024 * 20,
             single: true,
           } as UploaderFieldProps,

+ 5 - 70
src/pages/home/village/dialogs/UpgradeDialog.vue

@@ -3,68 +3,18 @@
     <template #titleHeader>    
       <Image src="https://xy.wenlvti.net/app_static/images/village/IconBlessing.png" :width="100" :height="100" />
     </template>
-    <FlexCol gap="gap.lg">
-      
-      <FlexCol padding="padding.md">
-        <Text 
-          text="感谢您选择升级村社,升级后您将获得更多权益,包括管理功能,更多的相册、乡源光、乡源果奖励等。请选择您要升级的套餐" 
-          fontConfig="contentText" :fontSize="30" textAlign="center" 
-        />
-      </FlexCol>
-
-      <FlexCol gap="gap.md">
-        <BoxMid
-          v-for="(item, k) in upgradeList"
-          :key="k"
-          position="relative"
-          :padding="32"
-          direction="row"
-          justify="space-between"
-          align="center"
-          gap="gap.md"
-        >
-          <FlexCol>
-            <Text :text="item.name" fontConfig="lightImportantTitle" :fontSize="42" />
-            <Text :text="item.desc" fontConfig="lightGoldTitle" :fontSize="30" />
-          </FlexCol>
-
-          <FlexRow gap="gap.lg">
-            <FlexCol align="flex-end">
-              <Text :text="item.vipLevel + '级'" fontConfig="contentText" />
-              <FlexRow align="center" gap="gap.sm">
-                <Text text="¥" color="text.gold" />
-                <Text :text="item.price" fontFamily="SongtiSCBlack" fontConfig="lightGoldTitle" fontSize="50" />
-              </FlexRow>
-            </FlexCol>
-            <Badge :show="item.isRecommend" content="推荐">
-              <FrameButton primary text="选择" @click="handleSelect(item as UpgradePackageItem)" />
-            </Badge>
-          </FlexRow>
-        </BoxMid>
-      </FlexCol>
-
-      <BoxMid :padding="24" direction="column" center>
-        <Touchable @click="navTo('/pages/article/details', { id: 7021, modelId: 18, showRecommend: false })">
-          <Text text="了解各村社等级权益" fontConfig="lightImportantTitle" />
-        </Touchable>
-      </BoxMid>
-    </FlexCol>
+    <UpgradeSelect
+      :villageId="villageId"
+      @selected="show = false"
+    />
   </CommonDialog>
 </template>
 
 <script setup lang="ts">
 import { ref } from 'vue';
-import { navTo } from '@/components/utils/PageAction';
 import CommonDialog from '@/common/components/CommonDialog.vue';
-import FrameButton from '@/common/components/FrameButton.vue';
-import Text from '@/components/basic/Text.vue';
-import FlexCol from '@/components/layout/FlexCol.vue';
-import FlexRow from '@/components/layout/FlexRow.vue';
-import TreeApi, { type UpgradePackageItem } from '@/api/light/TreeApi';
 import Image from '@/components/basic/Image.vue';
-import Touchable from '@/components/feedback/Touchable.vue';
-import Badge from '@/components/display/Badge.vue';
-import BoxMid from '@/common/components/box/BoxMid.vue';
+import UpgradeSelect from '../upgrade/components/UpgradeSelect.vue';
 
 const show = ref(false);
 const props = defineProps({
@@ -74,24 +24,9 @@ const props = defineProps({
   },
 });
 
-const upgradeList = ref<UpgradePackageItem[]>([]);
-
-async function loadUpgradeList() {
-  const res = await TreeApi.getUpgradeList();
-  upgradeList.value = res.list;
-}
-function handleSelect(item: UpgradePackageItem) {
-  show.value = false;
-  navTo('/pages/home/village/upgrade/select', {
-    villageId: props.villageId,
-    upgradePackageId: item.id,
-  });
-}
-
 defineExpose({
   show: () => {
     show.value = true;
-    loadUpgradeList();
   },
 });
 </script>

+ 3 - 4
src/pages/home/village/introd/card.vue

@@ -61,7 +61,7 @@
         <FlexRow align="center">
           <FlexCol gap="gap.md"> 
             <Text :text="`${villageInfoLoader.content.value?.levelText || '默认级别'}`" fontConfig="secondText" />  
-            <Text :text="`存储空间内存:${villageInfoLoader.content.value?.sizeText || 0}`" fontConfig="secondText" />
+            <Text :text="`存储空间内存:${villageInfoLoader.content.value?.sizeText || ''}`" fontConfig="secondText" />
           </FlexCol>
         </FlexRow>
         <FlexRow align="center" gap="gap.md">
@@ -304,7 +304,7 @@ import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLo
 import { useVillageStore } from '@/store/village';
 import { useRequireLogin } from '@/common/composeabe/RequireLogin';
 import { useFollow } from '../composeabe/Follow';
-import { ArrayUtils, assertNotNull, waitTimeOut } from '@imengyu/imengyu-utils';
+import { ArrayUtils, assertNotNull, FormatUtils, waitTimeOut } from '@imengyu/imengyu-utils';
 import { navTo } from '@/components/utils/PageAction';
 import HomeTitle from '@/common/components/parts/HomeTitle.vue';
 import Icon from '@/components/basic/Icon.vue';
@@ -375,7 +375,7 @@ const villageInfoLoader = useSimpleDataLoader(async () => {
     title: village?.name || '',
     desc: village?.desc || '',
     address: village?.address,
-    sizeText: village?.storageLimit || 0,
+    sizeText: `${FormatUtils.formatSize(village?.storageUsed || 0)}已用/${FormatUtils.formatSize(village?.storageLimit || 0)}空间`,
     level: village?.level.toString() || '',
     levelText: village?.levelText || '',
     rankText: village?.rank.toString() || '',
@@ -393,7 +393,6 @@ const emit = defineEmits<{
   (e: 'goJoin'): void;
 }>();
 
-
 const rankActiveTag = ref('文化积分');
 const listActiveTag = ref('广场');
 

+ 85 - 0
src/pages/home/village/upgrade/components/UpgradeSelect.vue

@@ -0,0 +1,85 @@
+<template>
+  <FlexCol gap="gap.lg">
+    <FlexCol padding="padding.md">
+      <Text 
+        text="感谢您选择升级村社,升级后您将获得更多权益,包括管理功能,更多的相册、乡源光、乡源果奖励等。请选择您要升级的套餐" 
+        fontConfig="contentText" :fontSize="30" textAlign="center" 
+      />
+    </FlexCol>
+    <FlexCol gap="gap.md">
+      <BoxMid
+        v-for="(item, k) in upgradeList"
+        :key="k"
+        position="relative"
+        :padding="32"
+        direction="row"
+        justify="space-between"
+        align="center"
+        gap="gap.md"
+      >
+        <FlexCol>
+          <Text :text="item.name" fontConfig="lightImportantTitle" :fontSize="42" />
+          <Text :text="item.desc" fontConfig="lightGoldTitle" :fontSize="30" />
+        </FlexCol>
+
+        <FlexRow gap="gap.lg">
+          <FlexCol align="flex-end">
+            <Text :text="item.vipLevel + '级'" fontConfig="contentText" />
+            <FlexRow align="center" gap="gap.sm">
+              <Text text="¥" color="text.gold" />
+              <Text :text="item.price" fontFamily="SongtiSCBlack" fontConfig="lightGoldTitle" fontSize="50" />
+            </FlexRow>
+          </FlexCol>
+          <Badge :show="item.isRecommend" content="推荐">
+            <FrameButton primary text="选择" @click="handleSelect(item as UpgradePackageItem)" />
+          </Badge>
+        </FlexRow>
+      </BoxMid>
+    </FlexCol>
+    <BoxMid :padding="24" direction="column" center>
+      <Touchable @click="navTo('/pages/article/details', { id: 7021, modelId: 18, showRecommend: false })">
+        <Text text="了解各村社等级权益" fontConfig="lightImportantTitle" />
+      </Touchable>
+    </BoxMid>
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from 'vue';
+import { navTo } from '@/components/utils/PageAction';
+import FrameButton from '@/common/components/FrameButton.vue';
+import Text from '@/components/basic/Text.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import TreeApi, { type UpgradePackageItem } from '@/api/light/TreeApi';
+import Touchable from '@/components/feedback/Touchable.vue';
+import Badge from '@/components/display/Badge.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
+
+const props = defineProps({
+  villageId: {
+    type: Number,
+    required: true,
+  },
+});
+
+const emit = defineEmits(['selected']);
+
+const upgradeList = ref<UpgradePackageItem[]>([]);
+
+async function loadUpgradeList() {
+  const res = await TreeApi.getUpgradeList();
+  upgradeList.value = res.list;
+}
+function handleSelect(item: UpgradePackageItem) {
+  emit('selected', item);
+  navTo('/pages/home/village/upgrade/select', {
+    villageId: props.villageId,
+    upgradePackageId: item.id,
+  });
+}
+
+onMounted(() => {
+  loadUpgradeList();
+});
+</script>

+ 42 - 0
src/pages/home/village/upgrade/upgrade-village.vue

@@ -0,0 +1,42 @@
+
+
+<template>
+  <CommonTopBanner 
+    title="升级村社"
+    showNav
+  >
+    <FlexCol center padding="padding.md">
+      <Image src="https://xy.wenlvti.net/app_static/images/village/IconBlessing.png" :width="100" :height="100" />
+      <UpgradeSelect
+        :villageId="querys.villageId"
+      />
+    </FlexCol>
+  </CommonTopBanner>
+</template>
+
+<script setup lang="ts">
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import { backAndCallOnPageBack } from '@/components/utils/PageAction';
+import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import Image from '@/components/basic/Image.vue';
+import UpgradeSelect from './components/UpgradeSelect.vue';
+
+const { querys } = useLoadQuerys({
+  villageId: 0,
+});
+
+function handlePaySuccess() {
+  setTimeout(() => {
+    backAndCallOnPageBack('paySuccessAndRefresh', {});
+  }, 100);
+}
+
+defineExpose({
+  onPageBack(name: string, data: any) {
+    if (name === 'handlePaySuccess') {
+      handlePaySuccess();
+    }
+  },
+});
+</script>