Pārlūkot izejas kodu

📦 村社升级完善,对接支付

快乐的梦鱼 2 mēneši atpakaļ
vecāks
revīzija
ab35f9c3e7
30 mainītis faili ar 909 papildinājumiem un 229 dzēšanām
  1. 78 2
      src/api/light/TreeApi.ts
  2. 1 0
      src/common/components/CommonDialog.vue
  3. 6 12
      src/common/components/RequireLogin.vue
  4. 19 0
      src/common/components/box/BoxMid.vue
  5. 2 1
      src/common/config/Theme.ts
  6. 2 2
      src/components/display/block/BackgroundBox.vue
  7. 25 1
      src/pages.json
  8. 3 7
      src/pages/dig/details.vue
  9. 3 5
      src/pages/dig/forms/list.vue
  10. 3 3
      src/pages/home/components/LightMap.vue
  11. 3 6
      src/pages/home/components/NotVolTip.vue
  12. 0 1
      src/pages/home/components/VillageMiniMap.vue
  13. 3 5
      src/pages/home/dig.vue
  14. 3 7
      src/pages/home/light/submit-map.vue
  15. 9 28
      src/pages/home/light/submit.vue
  16. 3 6
      src/pages/home/village/bless/my-orders.vue
  17. 0 87
      src/pages/home/village/dialogs/ApplyGoodsDialog.vue
  18. 3 6
      src/pages/home/village/dialogs/BlessBuyDialog.vue
  19. 5 15
      src/pages/home/village/dialogs/JoinDialog.vue
  20. 97 0
      src/pages/home/village/dialogs/UpgradeDialog.vue
  21. 6 16
      src/pages/home/village/goods/detail.vue
  22. 6 10
      src/pages/home/village/goods/index.vue
  23. 2 2
      src/pages/home/village/introd/card.vue
  24. 84 0
      src/pages/home/village/upgrade/dialogs/DirectPayDialog.vue
  25. 117 0
      src/pages/home/village/upgrade/dialogs/UploadVoucherDialog.vue
  26. 129 0
      src/pages/home/village/upgrade/my-orders.vue
  27. 119 0
      src/pages/home/village/upgrade/pay-public.vue
  28. 141 0
      src/pages/home/village/upgrade/select.vue
  29. 3 5
      src/pages/home/village/volunteer/detail.vue
  30. 34 2
      src/pages/index.vue

+ 78 - 2
src/api/light/TreeApi.ts

@@ -208,7 +208,7 @@ export class UpgradePackageItem extends DataModel<UpgradePackageItem> {
       imageLimit: { clientSide: 'number', serverSide: 'number' },
       storageLimit: { clientSide: 'number', serverSide: 'number' },
       managerLimit: { clientSide: 'number', serverSide: 'number' },
-      isRecommend: { clientSide: 'number', serverSide: 'number' },
+      isRecommend: { clientSide: 'boolean', serverSide: 'number' },
       sort: { clientSide: 'number', serverSide: 'number' },
       status: { clientSide: 'number', serverSide: 'number' },
     };
@@ -221,7 +221,7 @@ export class UpgradePackageItem extends DataModel<UpgradePackageItem> {
   imageLimit = 0;
   storageLimit = 0;
   managerLimit = 0;
-  isRecommend = 0;
+  isRecommend = false;
   isRecommendText = '';
   desc = '';
   content = '';
@@ -260,6 +260,7 @@ export class UpgradeOrderItem extends DataModel<UpgradeOrderItem> {
       storageLimit: { clientSide: 'number', serverSide: 'number' },
       managerLimit: { clientSide: 'number', serverSide: 'number' },
       money: { clientSide: 'number', serverSide: 'number' },
+      payType: { clientSide: 'number', serverSide: 'number' },
       status: { clientSide: 'number', serverSide: 'number' },
       volunteerId: { clientSide: 'number', serverSide: 'number' },
     };
@@ -284,6 +285,10 @@ export class UpgradeOrderItem extends DataModel<UpgradeOrderItem> {
   storageLimit = 0;
   managerLimit = 0;
   money = 0;
+  /** 支付方式: 1=线上支付, 2=对公付款 */
+  payType = 0;
+  payVoucher = '';
+  /** 状态: -1=已取消, 0=未支付, 1=已支付, 2=待审核(对公付款审核凭证) */
   status = 0;
   payTime = null as Date | null;
   villageName = '';
@@ -296,6 +301,45 @@ export class UpgradeOrderItem extends DataModel<UpgradeOrderItem> {
   deletetime = '' as string | null;
 }
 
+export class UpgradeOrderConfirm extends DataModel<UpgradeOrderConfirm> {
+  constructor() {
+    super(UpgradeOrderConfirm, '升级订单确认');
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      order: {
+        clientSide: 'object',
+        clientSideChildDataModel: UpgradeOrderItem,
+        serverSide: 'undefined',
+        clientSideRequired: true,
+      },
+      pay: {
+        clientSide: 'object',
+        clientSideChildDataModel: {
+          convertTable: {
+            appId: { clientSide: 'string', clientSideRequired: true },
+            timeStamp: { clientSide: 'string', clientSideRequired: true },
+            nonceStr: { clientSide: 'string', clientSideRequired: true },
+            package: { clientSide: 'string', clientSideRequired: true },
+            signType: { clientSide: 'string', clientSideRequired: true },
+            paySign: { clientSide: 'string', clientSideRequired: true },
+          },
+        },
+        serverSide: 'undefined',
+      },
+    };
+  }
+
+  order!: UpgradeOrderItem;
+  pay?: {
+    appId: string;
+    timeStamp: string;
+    nonceStr: string;
+    package: string;
+    signType: string;
+    paySign: string;
+  };
+}
+
 type GrowthLogSearch = {
   page?: number;
   pageSize?: number;
@@ -630,6 +674,8 @@ export class TreeApi extends AppServerRequestModule<DataModel> {
     keywords?: string;
     villageId?: number;
     userId?: number;
+    /** -1=已取消, 0=未支付, 1=已支付, 2=待审核(对公付款审核凭证) */
+    status?: -1 | 0 | 1 | 2 | number;
   }) {
     const res = await this.post<PagedGrowthResponse>(
       '/village/growth/upgradeOrderList',
@@ -640,6 +686,7 @@ export class TreeApi extends AppServerRequestModule<DataModel> {
         keywords: options?.keywords,
         village_id: options?.villageId,
         user_id: options?.userId,
+        status: options?.status,
       },
     );
     return this.parsePagedList<UpgradeOrderItem>(
@@ -648,6 +695,35 @@ export class TreeApi extends AppServerRequestModule<DataModel> {
       '升级订单',
     );
   }
+
+  /** 升级订单详情 */
+  async getUpgradeOrderInfo(id: number) {
+    const res = await this.post<KeyValue>('/village/growth/upgradeOrderInfo', '升级订单详情', { id });
+    return transformDataModel<UpgradeOrderItem>(UpgradeOrderItem, res.requireData());
+  }
+
+  /** 升级订单下单 */
+  async createUpgradeOrder(
+    villageId: number,
+    upgradeLevelId: number,
+    payType: 1 | 2,
+  ) {
+    const res = await this.post<KeyValue>('/village/growth/upgrade', '升级订单下单', {
+      village_id: villageId,
+      upgrade_level_id: upgradeLevelId,
+      pay_type: payType,
+    });
+    return transformDataModel<UpgradeOrderConfirm>(UpgradeOrderConfirm, res.requireData());
+  }
+
+  /** 对公付款上传凭证 */
+  async saveUpgrade(id: number, payVoucher: string) {
+    const res = await this.post<null>('/village/growth/saveUpgrade', '提交升级订单凭证', {
+      id,
+      pay_voucher: payVoucher,
+    });
+    return this.getStringDataOrMessage(res);
+  }
 }
 
 export default new TreeApi();

+ 1 - 0
src/common/components/CommonDialog.vue

@@ -20,6 +20,7 @@
         <Height :size="24" />
         <slot name="titlePre"></slot>
         <FlexCol center gap="gap.lg">
+          <slot name="titleHeader"></slot>
           <H3 fontConfig="primaryTitle">{{ props.title }}</H3>
           <CommonDivider v-if="props.showDivider" />
         </FlexCol>

+ 6 - 12
src/common/components/RequireLogin.vue

@@ -1,29 +1,23 @@
 <template>
   <slot v-if="isLogged" />
-  <BackgroundBox 
-    v-else center :height="300" :gap="30"
-    backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-    :backgroundCutBorder="32"
-    :backgroundCutBorderSize="36"
-    :padding="[45,35]"
-  >
+  <BoxMid v-else center :height="300" :gap="30">
     <FlexCol center :gap="20">
       <Icon name="smile-filling" :size="80" />
       <Text :text="unLoginMessage" textAlign="center" fontConfig="contentSpeicalText" />
       <PrimaryButton :innerStyle="{width: '50%'}" @click="goLogin" text="去登录" />
     </FlexCol>
-  </BackgroundBox>
+  </BoxMid>
 </template>
 
 <script setup lang="ts">
-import Icon from '@/components/basic/Icon.vue';
-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, watch } from 'vue';
+import Icon from '@/components/basic/Icon.vue';
+import Text from '@/components/basic/Text.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
 import PrimaryButton from './PrimaryButton.vue';
-import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
+import BoxMid from './box/BoxMid.vue';
 
 const authStore = useAuthStore();
 const isLogged = computed(() => authStore.isLogged);

+ 19 - 0
src/common/components/box/BoxMid.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
+import type { FlexProps } from '@/components/layout/FlexView.vue';
+
+const props = defineProps<FlexProps>();
+
+</script>
+
+<template>
+  <BackgroundBox
+    v-bind="props"
+    backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
+    :backgroundCutBorder="32"
+    :backgroundCutBorderSize="36"
+    :padding="32"
+  >
+    <slot />
+  </BackgroundBox>
+</template>

+ 2 - 1
src/common/config/Theme.ts

@@ -15,6 +15,7 @@ export function configAppTheme() {
     theme.colorConfigs.text.content = '#7b5e49';
     theme.colorConfigs.text.second = '#95755a';
     theme.colorConfigs.text.titleLight = '#55989a';
+    theme.colorConfigs.text.gold = '#cb8833';
 
     theme.colorConfigs.border.primary = '#55989a';
     theme.colorConfigs.border.secondary = '#5F3F2C';
@@ -38,7 +39,7 @@ export function configAppTheme() {
       fontWeight: 'bold',
     };
     theme.textConfigs.lightGoldTitle = {
-      color: '#cb8833',
+      color: 'text.gold',
       fontFamily: 'SongtiSCBlack',
       fontSize: '40rpx',
       fontWeight: 'bold',

+ 2 - 2
src/components/display/block/BackgroundBox.vue

@@ -40,7 +40,7 @@ export default {}
 import FlexView, { type FlexProps } from '@/components/layout/FlexView.vue';
 import { type ViewStyle } from '@/components/theme/ThemeDefine';
 import { computed } from 'vue';
-import { useBackgroundBoxStyleMaker } from './BackgroundBox';
+import { useBackgroundBoxStyleMaker, type BackgroundBoxProps } from './BackgroundBox';
 
 defineOptions({
   options: {
@@ -52,7 +52,7 @@ defineOptions({
 /**
  * 内容积木组件:背景盒子,
  */
-const props = withDefaults(defineProps<import('./BackgroundBox').BackgroundBoxProps & FlexProps>(), {
+const props = withDefaults(defineProps<BackgroundBoxProps & FlexProps>(), {
   color1: undefined,
   color2: undefined,
   radius: undefined,

+ 25 - 1
src/pages.json

@@ -138,6 +138,30 @@
       }
     },
     {
+      "path": "pages/home/village/upgrade/select",
+      "style": {
+        "navigationBarTitleText": "升级村社",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/home/village/upgrade/pay-public",
+      "style": {
+        "navigationBarTitleText": "对公打款",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/home/village/upgrade/my-orders",
+      "style": {
+        "navigationBarTitleText": "我的升级订单",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
       "path": "pages/home/village/volunteer/list",
       "style": {
         "navigationBarTitleText": "志愿者列表",
@@ -399,4 +423,4 @@
     "backgroundColor": "#fef2e8"
   },
   "uniIdRouter": {}
-}
+}

+ 3 - 7
src/pages/dig/details.vue

@@ -25,12 +25,7 @@
           </Touchable>
         </FlexRow>
       </FlexRow>
-      <BackgroundBox
-        backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-        :backgroundCutBorder="32"
-        :backgroundCutBorderSize="36"
-        :padding="24"
-      >
+      <BoxMid :padding="24">
         <Touchable 
           direction="column" 
           touchable 
@@ -45,7 +40,7 @@
           <Text :fontSize="22" color="gray">写随手记,记录下村社文化发现和思考,自动分类</Text>
           <Text :fontSize="22" color="gray">也可点击下方进入指定的分类采集信息</Text>
         </Touchable>
-      </BackgroundBox>
+      </BoxMid>
       <FlexCol :gap="20">
         <Alert
           v-if="!authStore.isAdmin && isEmpty"
@@ -93,6 +88,7 @@ import { CollectableModulesIdMap } from './forms/forms';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
 import NotVolTip from '../home/components/NotVolTip.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const { querys } = useLoadQuerys({ 
   name: '',

+ 3 - 5
src/pages/dig/forms/list.vue

@@ -21,13 +21,10 @@
         <Height :height="5" />
         <SimplePageListLoader :loader="listLoader" :noEmpty="true">
           <FlexCol :gap="20">
-            <BackgroundBox 
+            <BoxMid 
               v-for="item in listLoader.list.value"
               :key="item.id" 
               direction="row"
-              backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-              :backgroundCutBorder="30"
-              :backgroundCutBorderSize="30"
               :padding="[16, 30]"
               align="center"
             >
@@ -51,7 +48,7 @@
                   <Text :size="23">{{ item.desc }}</Text>
                 </FlexCol>
               </Touchable>
-            </BackgroundBox>
+            </BoxMid>
           </FlexCol>
           <template #empty>
             <Empty v-if="querys.isView" image="search" description="暂无数据,等待志愿者编写中..." />
@@ -98,6 +95,7 @@ import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import BackgroundImageButton from '@/components/basic/BackgroundImageButton.vue';
 import PrimaryButton from '@/common/components/PrimaryButton.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const subTitle = ref('');
 const searchText = ref('');

+ 3 - 3
src/pages/home/components/LightMap.vue

@@ -69,12 +69,12 @@
         bottom: '20rpx',
         left: '20rpx',
         zIndex: 100,
+        borderRadius: '10rpx',
         backgroundColor: '#ffffff',
       }"
     >
-      <Text :text="`lonlat:`" />
-      <Text :text="`${lonlat?.longitude}`" />
-      <Text :text="`${lonlat?.latitude}`" />
+      <Text :text="`${lonlat?.longitude}`" :fontSize="20" />
+      <Text :text="`${lonlat?.latitude}`" :fontSize="20" />
     </FlexCol>
     <NButton 
       :innerStyle="{

+ 3 - 6
src/pages/home/components/NotVolTip.vue

@@ -1,8 +1,5 @@
 <template>
-  <BackgroundBox 
-    backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-    :backgroundCutBorder="32"
-    :backgroundCutBorderSize="36"
+  <BoxMid 
     :padding="[40,20]"
     center 
     gap="gap.lg" 
@@ -19,14 +16,14 @@
     <Text fontConfig="lightGoldTitle">您还不是当前村社的志愿者</Text>
     <Text fontConfig="contentSpeicalText">欢迎注册,加入志愿者队伍,点亮村落</Text>
     <PrimaryButton type="primary" text="去点亮村落" @click="emit('goJoin')" />
-  </BackgroundBox>
+  </BoxMid>
 </template>
 
 <script setup lang="ts">
 import Image from '@/components/basic/Image.vue';
 import Text from '@/components/basic/Text.vue';
-import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import PrimaryButton from '@/common/components/PrimaryButton.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const emit = defineEmits<{
   (e: 'goJoin'): void;

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

@@ -9,7 +9,6 @@
       id="prevMap"
       map-id="prevMap"
       class="mini-village-map-map"
-      :enable-poi="false"
       :markers="markers"
       :scale="15"
       :longitude="lonlat.longitude"

+ 3 - 5
src/pages/home/dig.vue

@@ -89,11 +89,8 @@
 
       <Height :height="20" />
       <HomeTitle v-if="authStore.isLogged" title="我的贡献" />
-      <BackgroundBox 
+      <BoxMid 
         v-if="authStore.isLogged" 
-        backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-        :backgroundCutBorder="32"
-        :backgroundCutBorderSize="36"
         :padding="[40,20]"
         direction="row"
       >
@@ -108,7 +105,7 @@
           <Text fontConfig="lightGoldTitle">Lv.{{ volunteerInfoLoader.content.value?.level || 1 }}</Text>
           <Text>等级</Text>
         </FlexCol>
-      </BackgroundBox>
+      </BoxMid>
     </FlexCol>
   </FlexCol>
 </template>
@@ -142,6 +139,7 @@ import Touchable from '@/components/feedback/Touchable.vue';
 import Icon from '@/components/basic/Icon.vue';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import ImageSwiper from '@/common/components/parts/ImageSwiper.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const authStore = useAuthStore();
 const collectStore = useCollectStore();

+ 3 - 7
src/pages/home/light/submit-map.vue

@@ -7,9 +7,9 @@ import FlexCol from '@/components/layout/FlexCol.vue';
 import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
 import NavBar from '@/components/nav/NavBar.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
-import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Text from '@/components/basic/Text.vue';
 import Image from '@/components/basic/Image.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const { querys } = useLoadQuerys({
   latitude: AppCofig.defaultLonLat[1],
@@ -30,11 +30,7 @@ const currentLocation = useGetCurrentLocation({
     <StatusBarSpace />
     <NavBar leftButton="back" />
     <FlexRow center>
-      <BackgroundBox 
-        backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-        :backgroundCutBorder="32"
-        :backgroundCutBorderSize="36"
-        :padding="24"
+      <BoxMid 
         :innerStyle="{
           width: '500rpx',
         }"
@@ -42,7 +38,7 @@ const currentLocation = useGetCurrentLocation({
       > 
         <Image src="https://xy.wenlvti.net/app_static/images/home/BadgeNew.png" :width="220" mode="widthFix" />
         <Text text="请选择您要点亮的村社" fontConfig="primaryTitle" />
-      </BackgroundBox>
+      </BoxMid>
     </FlexRow>
   </FlexCol>
   <LightMap 

+ 9 - 28
src/pages/home/light/submit.vue

@@ -6,12 +6,7 @@
       <FlexCol :gap="20" :padding="30">
         <!--注册-->
         <FlexCol v-if="step === 'register'" :gap="20">
-          <BackgroundBox 
-            backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-            :backgroundCutBorder="32"
-            :backgroundCutBorderSize="36"
-            :padding="[45,35]"
-          >
+          <BoxMid :padding="[45,35]">
             <FlexCol center :gap="20">
               <Icon name="smile-filling" :size="80" />
               <Text 
@@ -20,47 +15,32 @@
                 fontConfig="contentSpeicalText" 
               />
             </FlexCol>
-          </BackgroundBox>
-          <BackgroundBox
-            backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-            :backgroundCutBorder="32"
-            :backgroundCutBorderSize="36"
-            :padding="10"
-          >
+          </BoxMid>
+          <BoxMid :padding="10">
             <DynamicForm
               ref="registerFormRef"
               :model="registerFormModel"
               :options="registerFormDefine"
             />
-          </BackgroundBox>
+          </BoxMid>
           <Height :height="20" />
           <PrimaryButton text="提交" @click="registerSubmit" :loading="registerFormLoading" />
         </FlexCol>
         <!--认领-->
         <FlexCol v-else-if="step === 'add'" :gap="20">
-          <BackgroundBox 
-            backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-            :backgroundCutBorder="32"
-            :backgroundCutBorderSize="36"
-            :padding="[45,35]"
-          >
+          <BoxMid :padding="[45,35]">
             <FlexCol center :gap="20">
               <Icon name="smile-filling" :size="80" />
               <Text text="您已经是志愿者,请完善以下信息认领当前村社" textAlign="center" fontConfig="contentSpeicalText" />
             </FlexCol>
-          </BackgroundBox>
-          <BackgroundBox
-            backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-            :backgroundCutBorder="32"
-            :backgroundCutBorderSize="36"
-            :padding="10"
-          >
+          </BoxMid>
+          <BoxMid :padding="10">
             <DynamicForm
               ref="addFormRef"
               :model="addFormModel"
               :options="addFormDefine"
             />
-          </BackgroundBox>
+          </BoxMid>
           <Height :height="20" />
           <PrimaryButton block text="提交" @click="addSubmit" :loading="addFormLoading" />
         </FlexCol>
@@ -133,6 +113,7 @@ import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import PrimaryButton from '@/common/components/PrimaryButton.vue';
 import ProvideVar from '@/components/theme/ProvideVar.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 /**
  * 点亮村社页面

+ 3 - 6
src/pages/home/village/bless/my-orders.vue

@@ -2,13 +2,10 @@
   <CommonTopBanner title="我的赐福订单">
     <SimplePageListLoader :loader="listLoader" :emptyView="{ text: '冷冷清清,等你来添光加彩' }">
       <FlexCol gap="gap.md" padding="padding.md">
-        <BackgroundBox
+        <BoxMid
           v-for="item in listLoader.list.value"
           :key="item.id" 
           direction="row"
-          backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-          :backgroundCutBorder="30"
-          :backgroundCutBorderSize="30"
           :padding="[16, 30]"
         >
           <Touchable
@@ -34,7 +31,7 @@
               <Text fontConfig="secondText">{{ item.createtime }}</Text>
             </FlexCol>
           </Touchable>
-        </BackgroundBox>
+        </BoxMid>
       </FlexCol>
     </SimplePageListLoader>
   </CommonTopBanner>
@@ -47,13 +44,13 @@ import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePa
 import FlexCol from '@/components/layout/FlexCol.vue';
 import TreeApi from '@/api/light/TreeApi';
 import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
-import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
 import Text from '@/components/basic/Text.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
 import Tag from '@/components/display/Tag.vue';
 import Width from '@/components/layout/space/Width.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const { querys } = useLoadQuerys({
   villageId: 0,

+ 0 - 87
src/pages/home/village/dialogs/ApplyGoodsDialog.vue

@@ -1,87 +0,0 @@
-<template>
-  <CommonDialog v-model:show="show" title="升级村社">
-    <FlexCol gap="gap.lg">
-      <FlexCol gap="gap.md">
-        <BackgroundBox
-          v-for="(item, k) in upgradeList"
-          :key="k"
-          position="relative"
-          backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-          :backgroundCutBorder="32"
-          :backgroundCutBorderSize="36"
-          :padding="32"
-          direction="row"
-          justify="space-between"
-          align="center"
-          gap="gap.md"
-        >
-          <FlexCol>
-            <FlexRow align="center" gap="gap.md">
-              <Text :text="item.name" fontConfig="lightImportantTitle" :fontSize="42" />
-              <FlexRow 
-                v-if="item.isRecommend"
-                radius="radius.md"
-                backgroundColor="danger"
-                :padding="[4, 12]"
-              >
-                <Text text="推荐升级" fontConfig="contentText" color="white" />
-              </FlexRow>
-            </FlexRow>
-            <Text :text="item.desc" fontConfig="lightGoldTitle" :fontSize="30" />
-          </FlexCol>
-          <FlexCol>
-            <Text :text="item.vipLevel + '级'" fontConfig="contentText" />
-            <FlexRow align="center">
-              <Text fontConfig="lightGoldTitle">¥</Text>
-              <Text :text="item.price" fontFamily="SongtiSCBlack" fontConfig="lightGoldTitle" fontSize="50" />
-            </FlexRow>
-          </FlexCol>
-        </BackgroundBox>
-      </FlexCol>
-      <BackgroundBox 
-        backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-        :backgroundCutBorder="32"
-        :backgroundCutBorderSize="36"
-        :padding="24"
-        direction="column"
-        center
-      >
-        <Text text="额外权益" fontConfig="lightImportantTitle" />
-        <Text text="平台首页展厅/好图上传、村图券赠送、修改图片等功能;平台基础建设、乡源光、乡源果奖励等" fontConfig="contentText" />
-      </BackgroundBox>
-    </FlexCol>
-  </CommonDialog>
-</template>
-
-<script setup lang="ts">
-import CommonDialog from '@/common/components/CommonDialog.vue';
-import FrameButton from '@/common/components/FrameButton.vue';
-import Text from '@/components/basic/Text.vue';
-import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
-import FlexCol from '@/components/layout/FlexCol.vue';
-import FlexRow from '@/components/layout/FlexRow.vue';
-import TreeApi, { type UpgradePackageItem } from '@/api/light/TreeApi';
-import { computed, ref } from 'vue';
-
-const show = ref(false);
-const props = defineProps({
-  villageId: {
-    type: Number,
-    required: true,
-  },
-});
-
-const upgradeList = ref<UpgradePackageItem[]>([]);
-
-async function loadUpgradeList() {
-  const res = await TreeApi.getUpgradeList();
-  upgradeList.value = res.list;
-}
-
-defineExpose({
-  show: () => {
-    show.value = true;
-    loadUpgradeList();
-  },
-});
-</script>

+ 3 - 6
src/pages/home/village/dialogs/BlessBuyDialog.vue

@@ -7,13 +7,9 @@
         :height="100" 
       />
       <Height :height="20" />
-      <BackgroundBox 
+      <BoxMid 
         v-for="(item, k) in infoGrids"
         :key="k"
-        backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-        :backgroundCutBorder="32"
-        :backgroundCutBorderSize="36"
-        :padding="24"
         direction="row"
         justify="space-between"
         align="center"
@@ -33,7 +29,7 @@
           <Text :text="item.value" fontConfig="lightGoldTitle" :fontSize="50" />
           <Text :text="item.unit" fontConfig="lightGoldTitle" :fontSize="26" />
         </FlexRow>
-      </BackgroundBox>
+      </BoxMid>
 
       <BackgroundBox 
         backgroundImage="https://xy.wenlvti.net/app_static/images/home/bless/PriceBg.png"
@@ -68,6 +64,7 @@
 
 <script setup lang="ts">
 import type { BlessPackageItem } from '@/api/light/TreeApi';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 import CommonDialog from '@/common/components/CommonDialog.vue';
 import FrameButton from '@/common/components/FrameButton.vue';
 import PrimaryButton from '@/common/components/PrimaryButton.vue';

+ 5 - 15
src/pages/home/village/dialogs/JoinDialog.vue

@@ -8,13 +8,9 @@
           <Text text="加入村社后,您即可为村社做贡献。请完善身份信息,解锁更多村社专属功能" fontConfig="contentSpeicalText" />
           <Height :height="10" />
 
-          <BackgroundBox 
+          <BoxMid 
             v-for="(item, k) in identityChoices"
             :key="k"
-            backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-            :backgroundCutBorder="32"
-            :backgroundCutBorderSize="36"
-            :padding="24"
             direction="row"
           >
             <Touchable direction="row" gap="gap.md"center @click="handleChooseIdentity(item.value)">
@@ -22,7 +18,7 @@
               <Width :width="20" />
               <Text :text="item.label" fontConfig="lightGoldTitle" />
             </Touchable>
-          </BackgroundBox>
+          </BoxMid>
         </FlexCol>
       </template>
       <template v-else-if="step === 'form'">
@@ -32,18 +28,13 @@
         <ProvideVar :vars="{
           FieldBackgroundColor: 'transparent',
         }">
-          <BackgroundBox
-            backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-            :backgroundCutBorder="32"
-            :backgroundCutBorderSize="36"
-            :padding="24"
-          >
+          <BoxMid>
             <DynamicForm
               ref="addFormRef"
               :model="addFormModel"
               :options="addFormDefine"
             />
-          </BackgroundBox>
+          </BoxMid>
         </ProvideVar>
         <Height :height="20" />
         <FlexRow justify="space-around" gap="gap.md">
@@ -81,12 +72,11 @@ import Height from '@/components/layout/space/Height.vue';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
 import type { RadioValueProps } from '@/components/dynamic/wrappers/RadioValue';
-import type { RuleItem } from 'async-validator';
-import type { FieldProps } from '@/components/form/Field.vue';
 import type { IDynamicFormOptions, IDynamicFormRef } from '@/components/dynamic';
 import Result from '@/components/feedback/Result.vue';
 import Width from '@/components/layout/space/Width.vue';
 import ProvideVar from '@/components/theme/ProvideVar.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const show = ref(false);
 const props = defineProps<{

+ 97 - 0
src/pages/home/village/dialogs/UpgradeDialog.vue

@@ -0,0 +1,97 @@
+<template>
+  <CommonDialog v-model:show="show" title="升级村社">
+    <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>
+        <WxButton openType="contact">
+          <Text text="不知道怎么选择?问问客服" fontConfig="lightImportantTitle" />
+        </WxButton>
+      </BoxMid>
+    </FlexCol>
+  </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 WxButton from '@/components/basic/WxButton.vue';
+import Badge from '@/components/display/Badge.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
+
+const show = ref(false);
+const props = defineProps({
+  villageId: {
+    type: Number,
+    required: true,
+  },
+});
+
+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>

+ 6 - 16
src/pages/home/village/goods/detail.vue

@@ -2,20 +2,10 @@
   <FlexCol gap="gap.md" padding="padding.md">
     <SimplePageContentLoader :loader="goodLoader">
       <template v-if="goodLoader.content.value">
-        <BackgroundBox 
-          backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-          :backgroundCutBorder="32"
-          :backgroundCutBorderSize="36"
-          :padding="24"
-        >
+        <BoxMid>
           <Image :src="goodLoader.content.value.image" :radius="20" width="100%" height="300rpx" mode="aspectFill" />
-        </BackgroundBox>
-        <BackgroundBox 
-          backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-          :backgroundCutBorder="32"
-          :backgroundCutBorderSize="36"
-          padding="padding.lg"
-        >
+        </BoxMid>
+        <BoxMid padding="padding.lg">
           <FlexRow justify="space-between" align="center" gap="gap.md">
             <FlexCol>
               <Text :text="goodLoader.content.value.name" fontConfig="titleText" />
@@ -31,7 +21,7 @@
           </FlexRow>
           <Divider />
           <Text :text="goodLoader.content.value.description" fontConfig="contentText" />
-        </BackgroundBox>
+        </BoxMid>
         <Height :height="160" />
         <PrimaryButton
           text="去咨询"
@@ -47,16 +37,16 @@
 import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { toast } from '@/components/utils/DialogAction';
 import Image from '@/components/basic/Image.vue';
-import ImageButton from '@/components/basic/ImageButton.vue';
 import Text from '@/components/basic/Text.vue';
 import Avatar from '@/components/display/Avatar.vue';
-import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Divider from '@/components/display/Divider.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import Height from '@/components/layout/space/Height.vue';
 import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import PrimaryButton from '@/common/components/PrimaryButton.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
+
 const goodLoader = useSimpleDataLoader(async () => {
   return {
     id: 1,

+ 6 - 10
src/pages/home/village/goods/index.vue

@@ -6,12 +6,8 @@
     </FlexRow>
     <Construction text="现在是测试数据,暂无接口" />
     <FlexRow wrap justify="space-around" gap="gap.sm">
-      <BackgroundBox 
+      <BoxMid
         v-for="good in goodsLoader.content.value" :key="good.id"
-        backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-        :backgroundCutBorder="32"
-        :backgroundCutBorderSize="36"
-        :padding="24"
         :innerStyle="{
           width: 'calc(50% - 50rpx)',
         }"
@@ -32,25 +28,25 @@
             <Text :text="`发布人: ${good.userName}`" fontConfig="contentText" />
           </FlexRow>
         </Touchable>
-      </BackgroundBox>
+      </BoxMid>
     </FlexRow>
   </FlexCol>
 </template>
 
 <script setup lang="ts">
+import { navTo } from '@/components/utils/PageAction';
+import { ref } from 'vue';
+import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 import Construction from '@/common/components/Construction.vue';
 import FrameButton from '@/common/components/FrameButton.vue';
 import Image from '@/components/basic/Image.vue';
 import Text from '@/components/basic/Text.vue';
-import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import Avatar from '@/components/display/Avatar.vue';
-import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
 import SearchBar from '@/components/form/SearchBar.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
-import { navTo } from '@/components/utils/PageAction';
-import { ref } from 'vue';
 
 const goodsLoader = useSimpleDataLoader(async () => {
   let games = [

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

@@ -229,7 +229,7 @@
     v-if="villageStore.currentVillage"
     :villageId="villageStore.currentVillage.id" 
   />
-  <ApplyGoodsDialog 
+  <UpgradeDialog 
     ref="applyGoodsDialog" 
     v-if="villageStore.currentVillage"
     :villageId="villageStore.currentVillage.id"
@@ -268,7 +268,7 @@ import GridItem from '@/components/layout/grid/GridItem.vue';
 import LightVillageApi from '@/api/light/LightVillageApi';
 import Construction from '@/common/components/Construction.vue';
 import OfficialAccountPublishWrap from '@/common/components/OfficialAccountPublishWrap.vue';
-import ApplyGoodsDialog from '../dialogs/ApplyGoodsDialog.vue';
+import UpgradeDialog from '../dialogs/UpgradeDialog.vue';
 import JoinDialog from '../dialogs/JoinDialog.vue';
 import VillageGallery from '../components/VillageGallery.vue';
 import WxButton from '@/components/basic/WxButton.vue';

+ 84 - 0
src/pages/home/village/upgrade/dialogs/DirectPayDialog.vue

@@ -0,0 +1,84 @@
+<template>
+  <CommonDialog v-model:show="show" title="升级付款" :showDivider="false">
+    <FlexCol gap="gap.lg" padding="padding.md" width="600rpx">
+      <template v-if="uploadStep === 'form'">
+        <Text textAlign="center" text="选择支付方式" fontConfig="contentSpeicalText" />
+        <Height :height="10" />
+        <FlexCol gap="gap.md">
+          <BoxMid justify="space-between">
+            <Text text="微信支付" fontConfig="lightImportantTitle" :fontSize="42" />
+            <CheckBox :modelValue="payMethod == 'wechat'" @update="payMethod = 'wechat'" />
+          </BoxMid>
+        </FlexCol>
+        <FlexRow justify="space-around" gap="gap.md">
+          <FrameButton text="取消" @click="show = false" width="220rpx" />
+          <FrameButton primary text="支付" @click="handleSubmitPay" width="220rpx" />
+        </FlexRow>
+      </template>
+      <template v-else-if="uploadStep === 'finished'">
+        <Result
+          status="success"
+          title="感谢您的贡献!"
+          description="村社升级已经生效啦"
+        />
+        <FlexRow justify="space-around" gap="gap.md">
+          <FrameButton primary text="完成" @click="handleFinish" width="220rpx" />
+        </FlexRow>
+      </template>
+    </FlexCol>
+  </CommonDialog>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import { toast } from "@/components/dialog/CommonRoot";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import { showError } from "@/common/composeabe/ErrorDisplay";
+import type { IDynamicFormOptions, IDynamicFormRef } from "@/components/dynamic";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import CommonDialog from "@/common/components/CommonDialog.vue";
+import FrameButton from "@/common/components/FrameButton.vue";
+import FlexCol from "@/components/layout/FlexCol.vue";
+import FlexRow from "@/components/layout/FlexRow.vue";
+import Height from "@/components/layout/space/Height.vue";
+import Text from "@/components/basic/Text.vue";
+import DynamicForm from "@/components/dynamic/DynamicForm.vue";
+import Result from "@/components/feedback/Result.vue";
+import ProvideVar from "@/components/theme/ProvideVar.vue";
+import BoxMid from "@/common/components/box/BoxMid.vue";
+import TreeApi from "@/api/light/TreeApi";
+import CheckBox from "@/components/form/CheckBox.vue";
+
+const emit = defineEmits(['success']);
+
+const show = ref(false);
+const uploadStep = ref<'form' | 'finished'>('form');
+
+const payMethod = ref('wechat');
+const orderId = ref<number>(0);
+
+function handleSubmitPay() {
+  if (!orderId.value) {
+    showError('缺少必要参数');
+    return;
+  } 
+  switch (payMethod.value) {
+    case 'wechat':
+      // 微信支付
+      break;
+    default:
+      break;
+  }
+}
+function handleFinish() {
+  emit('success');
+}
+
+defineExpose({
+  open(id: number) {
+    orderId.value = id;
+    uploadStep.value = 'form';
+    show.value = true;
+  },
+});
+</script>

+ 117 - 0
src/pages/home/village/upgrade/dialogs/UploadVoucherDialog.vue

@@ -0,0 +1,117 @@
+<template>
+  <CommonDialog v-model:show="show" title="上传付款凭证" :showDivider="false">
+    <FlexCol gap="gap.lg" padding="padding.md" width="600rpx">
+      <template v-if="uploadStep === 'form'">
+        <Text textAlign="center" text="请上传对公打款凭证图片" fontConfig="contentSpeicalText" />
+        <Height :height="10" />
+        <ProvideVar :vars="{ FieldBackgroundColor: 'transparent' }">
+          <BoxMid>
+            <DynamicForm
+              ref="uploadFormRef"
+              :model="uploadFormModel"
+              :options="uploadFormDefine"
+            />
+          </BoxMid>
+        </ProvideVar>
+        <Height :height="20" />
+        <FlexRow justify="space-around" gap="gap.md">
+          <FrameButton text="取消" @click="show = false" width="220rpx" />
+          <FrameButton primary text="提交凭证" @click="handleSubmitProof" :loading="submitLoading" width="220rpx" />
+        </FlexRow>
+      </template>
+      <template v-else-if="uploadStep === 'finished'">
+        <Result
+          status="success"
+          title="感谢您的贡献!"
+          description="凭证已提交,等待系统审核完成后生效。"
+        />
+        <FlexRow justify="space-around" gap="gap.md">
+          <FrameButton primary text="完成" @click="handleFinish" width="220rpx" />
+        </FlexRow>
+      </template>
+    </FlexCol>
+  </CommonDialog>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import { toast } from "@/components/dialog/CommonRoot";
+import { useAliOssUploadCo } from "@/common/components/upload/AliOssUploadCo";
+import { showError } from "@/common/composeabe/ErrorDisplay";
+import type { IDynamicFormOptions, IDynamicFormRef } from "@/components/dynamic";
+import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
+import CommonDialog from "@/common/components/CommonDialog.vue";
+import FrameButton from "@/common/components/FrameButton.vue";
+import FlexCol from "@/components/layout/FlexCol.vue";
+import FlexRow from "@/components/layout/FlexRow.vue";
+import Height from "@/components/layout/space/Height.vue";
+import Text from "@/components/basic/Text.vue";
+import DynamicForm from "@/components/dynamic/DynamicForm.vue";
+import Result from "@/components/feedback/Result.vue";
+import ProvideVar from "@/components/theme/ProvideVar.vue";
+import BoxMid from "@/common/components/box/BoxMid.vue";
+import TreeApi from "@/api/light/TreeApi";
+
+const emit = defineEmits(['success']);
+
+const show = ref(false);
+const uploadStep = ref<'form' | 'finished'>('form');
+const submitLoading = ref(false);
+const uploadFormRef = ref<IDynamicFormRef>();
+const uploadFormModel = ref({ payVoucher: '' });
+const orderId = ref<number>(0);
+
+const uploadFormDefine: IDynamicFormOptions = {
+  formAdditionaProps: {
+    labelPosition: 'top',
+  },
+  formItems: [
+    {
+      label: '付款凭证',
+      name: 'payVoucher',
+      type: 'uploader',
+      additionalProps: {
+        single: true,
+        maxFileSize: 1024 * 1024 * 10,
+        upload: useAliOssUploadCo('xiangyuan/upgrade/voucher'),
+      } as UploaderFieldProps,
+      rules: [{ required: true, message: '请上传付款凭证' }],
+    },
+  ],
+};
+
+async function handleSubmitProof() {
+  if (!uploadFormRef.value) return;
+  try {
+    await uploadFormRef.value.validate();
+  } catch {
+    toast({ content: '请上传付款凭证' });
+    return;
+  }
+
+  try {
+    submitLoading.value = true;
+    await TreeApi.saveUpgrade(orderId.value, uploadFormModel.value.payVoucher);
+    toast({ content: '提交成功' });
+    uploadStep.value = 'finished';
+    emit('success');
+  } catch (e) {
+    showError(e);
+  } finally {
+    submitLoading.value = false;
+  }
+}
+
+function handleFinish() {
+  show.value = false;
+}
+
+defineExpose({
+  open(id: number) {
+    orderId.value = id;
+    uploadStep.value = 'form';
+    uploadFormModel.value = { payVoucher: '' };
+    show.value = true;
+  },
+});
+</script>

+ 129 - 0
src/pages/home/village/upgrade/my-orders.vue

@@ -0,0 +1,129 @@
+<template>
+  <CommonTopBanner title="我的升级订单">
+    <SimplePageListLoader :loader="listLoader" :emptyView="{ text: '暂无升级订单' }">
+      <FlexCol gap="gap.md" padding="padding.md">
+        <BoxMid
+          v-for="item in listLoader.list.value"
+          :key="item.id"
+          direction="row"
+          :padding="[16, 30]"
+        >
+          <Touchable
+            :gap="20"
+            :padding="[15, 20]"
+            touchable
+            flex="1"
+            direction="row"
+            justify="space-between"
+            align="center"
+          >
+            <FlexCol>
+              <Text fontConfig="lightImportantTitle" fontFamily="SongtiSCBlack">{{ item.levelName }}</Text>
+              <Text :fontSize="23" :text="`${item.villageName} ${item.vipLevel}级`" />
+              <Text :fontSize="21" fontConfig="secondText">{{ item.payType === 1 ? '线上支付' : '对公付款' }}</Text>
+            </FlexCol>
+            <FlexRow gap="gap.xl">
+              <FlexCol align="flex-end">
+                <FlexRow align="center" gap="gap.sm">
+                  <Text :fontSize="23" fontConfig="lightGoldTitle">¥</Text>
+                  <Text fontConfig="lightGoldTitle">{{ item.price }}</Text>
+                  <Width :width="10" />
+                  <Tag size="small" :text="item.statusText" :type="getStatusTypeByStatusText(item.statusText)" />
+                </FlexRow>
+                <Text fontConfig="secondText">{{ item.createtime }}</Text>
+              </FlexCol>
+              <FrameButton
+                v-if="item.status === 0"
+                primary
+                text="去支付"
+                width="100rpx"
+                size="small"
+                @click="handlePay(item)"
+              />
+            </FlexRow>
+          </Touchable>
+        </BoxMid>
+      </FlexCol>
+    </SimplePageListLoader>
+
+    <DirectPayDialog ref="directPayDialog" @success="handlePaySuccess" />
+    <UploadVoucherDialog ref="uploadVoucherRef" @success="listLoader.load()" />
+  </CommonTopBanner>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import { useAuthStore } from '@/store/auth';
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
+import { showError } from '@/common/composeabe/ErrorDisplay';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import TreeApi, { UpgradeOrderItem } from '@/api/light/TreeApi';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
+import Touchable from '@/components/feedback/Touchable.vue';
+import Text from '@/components/basic/Text.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
+import Tag from '@/components/display/Tag.vue';
+import Width from '@/components/layout/space/Width.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
+import FrameButton from '@/common/components/FrameButton.vue';
+import UploadVoucherDialog from './dialogs/UploadVoucherDialog.vue';
+import DirectPayDialog from './dialogs/DirectPayDialog.vue';
+import { backAndCallOnPageBack } from '@/components/utils/PageAction.js';
+
+const { querys } = useLoadQuerys({
+  villageId: 0,
+}, () => {
+  listLoader.load();
+});
+const authStore = useAuthStore();
+
+const listLoader = useSimplePageListLoader(20, async (page, pageSize) => {
+  if (!querys.value.villageId || !authStore.userId) {
+    return {
+      list: [],
+      total: 0,
+    };
+  }
+  const res = await TreeApi.getUpgradeOrderList({
+    page,
+    pageSize,
+    villageId: querys.value.villageId || undefined,
+    userId: authStore.userId,
+  });
+  return {
+    list: res.list,
+    total: res.total,
+  };
+});
+
+const directPayDialog = ref<InstanceType<typeof DirectPayDialog>>();
+const uploadVoucherRef = ref<InstanceType<typeof UploadVoucherDialog>>();
+
+function handlePay(item: UpgradeOrderItem) {
+  if (item.payType === 2) {
+    uploadVoucherRef.value?.open(item.id);
+  } else if (item.payType === 1) {
+    directPayDialog.value?.open(item.id);
+  } else {
+    showError('请使用微信支付完成付款');
+  }
+}
+
+function handlePaySuccess() {
+  backAndCallOnPageBack('handlePaySuccess', {});
+}
+
+function getStatusTypeByStatusText(statusText: string): 'warning' | 'success' | 'default' {
+  switch (statusText) {
+    case '待支付':
+    case '待审核':
+      return 'warning';
+    case '已支付':
+      return 'success';
+    default:
+      return 'default';
+  }
+}
+</script>

+ 119 - 0
src/pages/home/village/upgrade/pay-public.vue

@@ -0,0 +1,119 @@
+<template>
+  <CommonTopBanner
+    title="对公打款"
+    showNav
+  >
+    <FlexCol padding="padding.md">
+      <SimplePageContentLoader :loader="loader">
+        <template v-if="loader.content.value">
+          <FlexCol :gap="20">
+            <FlexCol :padding="20" :radius="20">
+              <Parse
+                v-if="loader.content.value.content"
+                :content="loader.content.value.content"
+              />
+              <Text v-else="emptyContent" fontConfig="subText">暂无对公打款说明,请在后台补充!</Text>
+            </FlexCol>
+          </FlexCol>
+        </template>
+      </SimplePageContentLoader>
+
+      <FlexCol gap="gap.md">
+        <FrameButton text="下载对公打款材料" @click="handleDownloadMaterial()" />
+        <FrameButton primary text="我已打款上传凭证" @click="handleUploadProof()" />
+      </FlexCol>
+
+      <UploadVoucherDialog ref="uploadVoucherRef" />
+    </FlexCol>
+  </CommonTopBanner>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from "vue";
+import { useSimpleDataLoader } from "@/components/composeabe/loader/SimpleDataLoader";
+import { useLoadQuerys } from "@/components/composeabe/LoadQuerys";
+import { showError } from "@/common/composeabe/ErrorDisplay";
+import type { GetContentDetailItem } from "@/api/CommonContent";
+import SimplePageContentLoader from "@/components/loader/SimplePageContentLoader.vue";
+import Parse from "@/components/display/parse/Parse.vue";
+import CommonContent from "@/api/CommonContent";
+import CommonTopBanner from "@/common/components/CommonTopBanner.vue";
+import FrameButton from "@/common/components/FrameButton.vue";
+import FlexCol from "@/components/layout/FlexCol.vue";
+import Text from "@/components/basic/Text.vue";
+import TreeApi from "@/api/light/TreeApi";
+import UploadVoucherDialog from "./dialogs/UploadVoucherDialog.vue";
+
+const loader = useSimpleDataLoader<
+  GetContentDetailItem,
+  { id: number }
+>(async (params) => {
+  if (!params)
+    throw new Error("!params");
+  const res = await CommonContent.getContentDetail(params.id);
+  return res;
+});
+
+const { querys } = useLoadQuerys({
+  villageId: 0,
+  upgradePackageId: 0,
+});
+
+onMounted(() => {
+  loader.load(false, { id: 7019 });
+});
+
+async function handleDownloadMaterial() {
+  if (!loader.content.value?.archives) {
+    showError('暂无对公打款附件(archives),请在后台补充!')
+    return
+  }
+  try {
+    uni.showLoading({
+      title: '下载中...'
+    })
+    const filePath = await new Promise<string>((resolve, reject) => {
+      uni.downloadFile({
+        url: loader.content.value!.archives as string,
+        success: (res) => {
+          if (res.statusCode === 200) {
+            resolve(res.tempFilePath)
+          } else {
+            reject('材料下载失败')
+          }
+        }
+      })
+    });
+    uni.openDocument({
+      filePath,
+      showMenu: true
+    })
+  } catch (error) {
+    showError(error as string)
+  } finally {
+    uni.hideLoading()
+  }
+}
+
+const uploadVoucherRef = ref<InstanceType<typeof UploadVoucherDialog>>();
+
+async function handleUploadProof() {
+  if (!querys.value.villageId || !querys.value.upgradePackageId) {
+    showError('缺少必要参数');
+    return;
+  }
+  try {
+    uni.showLoading({ title: '创建订单中...' });
+    const result = await TreeApi.createUpgradeOrder(
+      querys.value.villageId,
+      querys.value.upgradePackageId,
+      2,
+    );
+    uploadVoucherRef.value?.open(result.order.id);
+  } catch (e) {
+    showError(e);
+  } finally {
+    uni.hideLoading();
+  }
+}
+</script>

+ 141 - 0
src/pages/home/village/upgrade/select.vue

@@ -0,0 +1,141 @@
+
+
+<template>
+  <CommonTopBanner 
+    title="升级村社"
+    showNav
+  >
+    <FlexCol gap="gap.lg" padding="padding.md">
+      
+      <FlexRow justify="flex-end">
+        <Button icon="https://xy.wenlvti.net/app_static/images/home/IconOrder.png" text="我的订单" @click="handleMyOrders()" />
+      </FlexRow>
+      <FlexRow center>
+        <Image src="https://xy.wenlvti.net/app_static/images/village/IconBlessing.png" :width="100" :height="100" />
+      </FlexRow>
+      <FlexCol padding="padding.md" center>
+        <Text 
+          text="感谢您选择升级村社,请选择您要付款方式" 
+          fontConfig="contentText" :fontSize="30" textAlign="center" 
+        />
+      </FlexCol>
+
+      <FlexCol gap="gap.md">
+        <BoxMid
+          direction="row"
+          justify="space-between"
+          align="center"
+          gap="gap.md"
+        >
+          <FlexCol width="74%">
+            <Text text="在线支付" fontConfig="lightImportantTitle" :fontSize="42" />
+            <Text text="使用微信线支付方式,方便快捷,立即生效" fontConfig="lightGoldTitle" :fontSize="30" />
+          </FlexCol>
+          <FrameButton primary text="选择" @click="handleDirectPay" />
+        </BoxMid>
+        <BoxMid
+          direction="row"
+          justify="space-between"
+          align="center"
+          gap="gap.md"
+        >
+          <FlexCol width="74%">
+            <Text text="对公打款" fontConfig="lightImportantTitle" :fontSize="42" />
+            <Text text="对公打款,需要提供对公账户信息。适用于村社政府做出贡献" fontConfig="lightGoldTitle" :fontSize="30" />
+          </FlexCol>
+          <FrameButton primary text="选择" @click="handlePublicPay" />
+        </BoxMid>
+      </FlexCol>
+
+      <BoxMid :padding="24" direction="column" center>
+        <WxButton openType="contact">
+          <Text text="不知道怎么选择?问问客服" fontConfig="lightImportantTitle" />
+        </WxButton>
+      </BoxMid>
+    </FlexCol>
+
+    <DirectPayDialog
+      ref="directPayDialog"
+      @success="handlePaySuccess"
+    />
+  </CommonTopBanner>
+</template>
+
+<script setup lang="ts">
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import { useRequireLogin } from '@/common/composeabe/RequireLogin';
+import { ref } from 'vue';
+import { navTo, backAndCallOnPageBack } from '@/components/utils/PageAction';
+import { showError } from '@/common/composeabe/ErrorDisplay';
+import BoxMid from '@/common/components/box/BoxMid.vue';
+import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
+import FrameButton from '@/common/components/FrameButton.vue';
+import Text from '@/components/basic/Text.vue';
+import WxButton from '@/components/basic/WxButton.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Button from '@/components/basic/Button.vue';
+import Image from '@/components/basic/Image.vue';
+import DirectPayDialog from './dialogs/DirectPayDialog.vue';
+import TreeApi from '@/api/light/TreeApi';
+
+const { requireLoginAsync } = useRequireLogin();
+
+const directPayDialog = ref<InstanceType<typeof DirectPayDialog>>();
+
+function handlePaySuccess() {
+  setTimeout(() => {
+    backAndCallOnPageBack('paySuccessAndRefresh', {});
+  }, 100);
+}
+
+const { querys } = useLoadQuerys({
+  villageId: 0,
+  upgradePackageId: 0,
+}, () => {
+
+});
+
+async function handleMyOrders() {
+  if (!await requireLoginAsync('登录后查看我的升级订单哦'))
+    return;
+  navTo('/pages/home/village/upgrade/my-orders', {
+    villageId: querys.value.villageId,
+  });
+}
+
+async function handleDirectPay() {
+  if (!requireLoginAsync('登录后为村社升级,做出你的贡献哦'))
+    return;
+  try {
+    uni.showLoading({ title: '创建订单中...' });
+    const result = await TreeApi.createUpgradeOrder(
+      querys.value.villageId,
+      querys.value.upgradePackageId,
+      1,
+    );
+    directPayDialog.value?.open(result.order.id);
+  } catch (e) {
+    showError(e);
+  } finally {
+    uni.hideLoading();
+  }
+}
+
+function handlePublicPay() {
+  if (!requireLoginAsync('登录后为村社升级,做出你的贡献哦'))
+    return;
+  navTo('/pages/home/village/upgrade/pay-public', {
+    villageId: querys.value.villageId,
+    upgradePackageId: querys.value.upgradePackageId,
+  });
+}
+
+defineExpose({
+  onPageBack(name: string, data: any) {
+    if (name === 'handlePaySuccess') {
+      handlePaySuccess();
+    }
+  },
+});
+</script>

+ 3 - 5
src/pages/home/village/volunteer/detail.vue

@@ -36,12 +36,9 @@
         </BackgroundBox>
 
         <FlexRow wrap justify="space-around" gap="gap.md">
-          <BackgroundBox 
+          <BoxMid
             v-for="(item, k) in infoGrids"
             :key="k"
-            backgroundImage="https://xy.wenlvti.net/app_static/images/village/BoxMid.png"
-            :backgroundCutBorder="32"
-            :backgroundCutBorderSize="36"
             :padding="24"
             :innerStyle="{ width: 'calc(50% - 58rpx)' }"
             direction="row"
@@ -56,7 +53,7 @@
                 <Text :text="item.unit" fontConfig="contentText" />
               </FlexRow>
             </FlexCol>
-          </BackgroundBox>
+          </BoxMid>
         </FlexRow>
 
         <HomeTitle title="最新动态" />
@@ -102,6 +99,7 @@ import { computed } from 'vue';
 import HomeTitle from '@/common/components/parts/HomeTitle.vue';
 import Width from '@/components/layout/space/Width.vue';
 import Construction from '@/common/components/Construction.vue';
+import BoxMid from '@/common/components/box/BoxMid.vue';
 
 const { querys } = useLoadQuerys({
   id: 0,

+ 34 - 2
src/pages/index.vue

@@ -15,7 +15,7 @@
         align="left"
       />
       <HomeIndex v-show="tabIndex === 0" @goVillage="tabIndex = 1" />
-      <VillageIndex v-if="tabIndex === 1" showSwitch />
+      <VillageIndex v-if="tabIndex === 1" showSwitch ref="villageIndex" />
       <DigIndex v-else-if="tabIndex === 2" />
       <DiscoverIndex v-else-if="tabIndex === 3" />
       <UserIndex v-else-if="tabIndex === 4" @goSubmit="tabIndex = 2" />
@@ -57,6 +57,7 @@ import DiscoverIndex from './home/discover/index.vue';
 import VillageIndex from './home/village/index.vue';
 import CommonRoot from '@/components/dialog/CommonRoot.vue';
 import LightVillageApi from '@/api/light/LightVillageApi';
+import { waitTimeOut } from '@imengyu/imengyu-utils';
 
 const tabIndex = ref(0);
 const themeContext = useTheme();
@@ -89,6 +90,18 @@ async function loadShareVillageInfo() {
   }
   tabIndex.value = 1;
 }
+async function reloadVillageInfo() {
+  try {
+    if (!villageStore.currentVillage)
+      return;
+    const res = await LightVillageApi.getVillageDetails(villageStore.currentVillage.id as number);
+    villageStore.setCurrentVillage(res);
+  } catch (error) {
+    console.error(error);
+    return;
+  }
+  tabIndex.value = 1;
+}
 function getShareVillageInfo() {
   if (villageStore.currentVillage) {
     //分享村社信息
@@ -104,11 +117,30 @@ function getShareVillageInfo() {
     imageUrl: 'https://mn.wenlvti.net/app_static/xiangyuan/images/index-post.jpg',
   };
 }
+async function paySuccessAndRefresh() {
+  try {
+    uni.showLoading({
+      title: '请稍后...',
+    });
+
+    tabIndex.value = 0;
+    await reloadVillageInfo();
+    await waitTimeOut(100);
+    tabIndex.value = 1;
+  } catch (error) {
+    console.error(error);
+    return;
+  } finally {
+    uni.hideLoading();
+  }
+}
 
 defineExpose({
   onPageBack: (name: string, data: Record<string, unknown>) => {
     if (data.type === 'goVillage') {
       tabIndex.value = 1;
+    } else if (name === 'paySuccessAndRefresh') {
+      paySuccessAndRefresh();
     }
   }
 })
@@ -120,7 +152,7 @@ onShareTimeline(() => {
 })
 onMounted(() => {
   if (isDevEnv) {
-    //tabIndex.value = 1;
+    tabIndex.value = 1;
   }
 })
 </script>