Parcourir la source

🎨 乡源树优化

快乐的梦鱼 il y a 9 heures
Parent
commit
f60f12419a

+ 4 - 1
src/api/light/LightVillageApi.ts

@@ -1,6 +1,7 @@
 import { DataModel, transformArrayDataModel, transformDataModel, type KeyValue } from '@imengyu/js-request-transform';
 import { AppServerRequestModule } from '../RequestModules';
 import { transformSomeToArray } from '../Utils';
+import type { MiniRender } from '@/components/canvas/MiniRender';
 
 export class VillageListItem extends DataModel<VillageListItem> {
   constructor() {
@@ -271,7 +272,9 @@ export class VillageListItem extends DataModel<VillageListItem> {
 export interface VillageTreeAnimProps {
   width: number,
   height: number,
-  frames: number[][],
+  offsetX?: number,
+  offsetY?: number,
+  frames: MiniRender.AnimateSpriteFrame[],
   framerate: number,
   animations: Record<string, {
     frames: number[],

+ 10 - 1
src/api/system/ConfigurationApi.ts

@@ -1,6 +1,7 @@
 import { UpdateServerRequestModule } from '@/api/RequestModules';
 import { LogUtils } from '@imengyu/imengyu-utils';
 import { DataModel } from '@imengyu/js-request-transform';
+import type { VillageTreeAnimProps } from '../light/LightVillageApi';
 import DefaultConfiguration from './DefaultConfiguration.json';
 
 export const CommonConfigurationConfig = {
@@ -27,6 +28,14 @@ export interface IConfigurationItem {
     home: IBannerItem;
     dig: IBannerItem;
     discover: IBannerItem;
+  },
+  tree: {
+    fruitImage: string;
+    backgroundImage: string;
+    animWater: string[];
+    animWaterAnimProps: VillageTreeAnimProps;
+    animFertilize: string[];
+    animFertilizeAnimProps: VillageTreeAnimProps;
   }
 }
 
@@ -50,7 +59,7 @@ export class ConfigurationApi extends UpdateServerRequestModule<DataModel> {
         })).data!.data;
     } catch (error) {
       LogUtils.printLog("ConfigurationApi", 'error', '获取配置失败,使用默认配置', error);
-      return DefaultConfiguration as IConfigurationItem;
+      return DefaultConfiguration as unknown as IConfigurationItem;
     }
   }
 }

Fichier diff supprimé car celui-ci est trop grand
+ 28 - 0
src/api/system/DefaultConfiguration.json


+ 1 - 1
src/api/system/useAppConfiguration.ts

@@ -25,5 +25,5 @@ export function useAppConfiguration() {
 }
 
 export function injectAppConfiguration() {
-  return inject(APP_CONFIGURATION_KEY) as Ref<IConfigurationItem | null>;
+  return inject(APP_CONFIGURATION_KEY) as Ref<IConfigurationItem>;
 }

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

@@ -38,7 +38,7 @@
         src="https://xy.wenlvti.net/app_static/images/ButtonClose.png"
         :width="80"
         :height="80"
-        @click="emit('update:show', false)"
+        @click="emit('update:show', false);emit('close')"
       />
     </template>
   </Dialog>
@@ -63,6 +63,7 @@ const props = withDefaults(defineProps<DialogProps & {
 });
 const emit = defineEmits<{
   (e: 'update:show', value: boolean): void;
+  (e: 'close'): void;
 }>();
 
 

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

@@ -17,6 +17,8 @@ export function configAppTheme() {
     theme.colorConfigs.text.titleLight = '#55989a';
     theme.colorConfigs.text.gold = '#cb8833';
 
+    theme.colorConfigs.mask.tertiary = 'rgba(240,240,220,0.5)';
+
     theme.colorConfigs.border.primary = '#55989a';
     theme.colorConfigs.border.secondary = '#5F3F2C';
 

+ 11 - 2
src/components/canvas/MiniRender.ts

@@ -492,6 +492,7 @@ export namespace MiniRender {
     private _frameCursor = 0;
     private _accMs = 0;
     private _resolvedImages: Array<any | null> = [];
+    private _waitingForImage = false;
 
     constructor(config?: Partial<AnimateSpriteConfig>) {
       super();
@@ -503,6 +504,10 @@ export namespace MiniRender {
       }
     }
 
+    public preload(scene: Scene): Promise<any[]> {
+      return Promise.all(this.images.map((src) => scene.assets.getImage(src)));
+    }
+
     public play(name?: string, opts?: { reset?: boolean; once?: boolean }): void {
       if (name) this.currentAnimation = name;
       if (opts?.reset ?? true) {
@@ -546,7 +551,7 @@ export namespace MiniRender {
     }
 
     public override update(dtMs: number): void {
-      if (!this.playing) return;
+      if (!this.playing || this._waitingForImage) return;
 
       const seq = this.getAnimationSequence();
       if (seq.length <= 1) return;
@@ -594,12 +599,16 @@ export namespace MiniRender {
       let img = this._resolvedImages[imageIndex] ?? null;
       img = img ?? scene.assets.getImageSync(src);
       if (!img) {
+        this._waitingForImage = true;
         scene.assets
           .getImage(src)
           .then((loaded) => {
             this._resolvedImages[imageIndex] = loaded;
+            this._waitingForImage = false;
           })
-          .catch(() => {});
+          .catch(() => {
+            this._waitingForImage = false;
+          });
         return;
       }
 

Fichier diff supprimé car celui-ci est trop grand
+ 36 - 29
src/pages/home/village/components/VillageTree.vue


+ 4 - 29
src/pages/home/village/dialogs/BlessSuccessDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <CommonDialog v-model:show="show">
+  <CommonDialog v-model:show="show" @close="emit('close')">
     <FlexCol gap="gap.lg" width="600rpx" align="center" :padding="35">
       <Image
         src="https://xy.wenlvti.net/app_static/images/home/bless/IconHeader.png"
@@ -17,43 +17,18 @@
 </template>
 
 <script setup lang="ts">
-import type { BlessPackageItem } from '@/api/light/TreeApi';
+import { ref } from 'vue';
 import CommonDialog from '@/common/components/CommonDialog.vue';
-import FrameButton from '@/common/components/FrameButton.vue';
-import PrimaryButton from '@/common/components/PrimaryButton.vue';
 import Image from '@/components/basic/Image.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 Height from '@/components/layout/space/Height.vue';
-import { DateUtils } from '@imengyu/imengyu-utils';
-import { computed, ref } from 'vue';
 
 const show = ref(false);
 
-const props = defineProps<{
-  currentBless?: BlessPackageItem;
+const emit = defineEmits<{
+  (e: 'close'): void;
 }>();
-const emit = defineEmits(['buyBless']);
-
-const infoGrids = computed(() => [
-  {
-    label: '村社加乡源光',
-    image: 'https://xy.wenlvti.net/app_static/images/home/bless/IconLight.png',
-    value: props.currentBless?.addLight || 0,
-    unit: '光',
-  },
-  {
-    label: '用户得乡源果',
-    image: 'https://xy.wenlvti.net/app_static/images/home/bless/IconFruit.png',
-    value: props.currentBless?.addFruit || 0,
-    unit: '个',
-  },
-]);
-const effectiveDate = computed(() => {
-  return DateUtils.formatDate(DateUtils.dateAddDays(new Date(), props.currentBless?.days || 0), 'YYYY-MM-DD');
-});
 
 defineExpose({
   show: () => {

+ 38 - 0
src/pages/home/village/dialogs/TreeLevelUpDialog.vue

@@ -0,0 +1,38 @@
+<template>
+  <CommonDialog v-model:show="show">
+    <FlexCol gap="gap.lg" width="600rpx" align="center" :padding="35">
+      <Image
+        src="https://xy.wenlvti.net/app_static/images/home/bless/IconHeader.png"
+        :width="300" 
+        :height="100" 
+      />
+      <Height :height="20" />
+      <Text 
+        textAlign="center" 
+        :text="`恭喜,乡源树已经升级为${props.treeLevel}`"
+        fontConfig="lightGoldTitle"
+      />
+    </FlexCol>
+  </CommonDialog>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import CommonDialog from '@/common/components/CommonDialog.vue';
+import Image from '@/components/basic/Image.vue';
+import Text from '@/components/basic/Text.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import Height from '@/components/layout/space/Height.vue';
+
+const show = ref(false);
+
+const props = defineProps<{
+  treeLevel: string
+}>();
+
+defineExpose({
+  show: () => {
+    show.value = true;
+  },
+});
+</script>

+ 8 - 3
src/pages/home/village/index.vue

@@ -162,6 +162,7 @@ watch(() => villageStore.currentVillage?.id, (newVal) => {
   if (newVal && topTab.value === 'around')
     topTab.value = 'village';
   tab.value = 'card';
+  handleTestGoTree();
   handleShowFollowTip();
 });
 watch(() => isFollowed.value, (newVal) => {
@@ -174,12 +175,16 @@ onMounted(async () => {
       title: villageStore.currentVillage?.name || '未选择村庄',
     });
   }
-  if (isDevEnv) {
-    //tab.value = 'tree';
-  }
   await waitTimeOut(1000);
+  handleTestGoTree();
 });
 
+function handleTestGoTree() {
+  if (isDevEnv) {
+    tab.value = 'tree';
+  }
+}
+
 defineExpose({
   onPageBack: (name: string, data: Record<string, unknown>) => {
     cardRef.value?.onPageBack(name, data);

+ 6 - 3
src/pages/home/village/introd/components/PopTextAnim.vue

@@ -12,9 +12,12 @@
 <script setup lang="ts">
 import { ref, watch, nextTick } from 'vue';
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
   show: boolean;
-}>();
+  delay?: number;
+}>(), {
+  delay: 2200,
+});
 
 const emit = defineEmits<{
   (e: 'end'): void;
@@ -31,7 +34,7 @@ watch(() => props.show, (val) => {
     fading.value = false;
     timer = setTimeout(() => {
       fading.value = true;
-    }, 1000);
+    }, props.delay);
   } else {
     cleanup();
   }

+ 84 - 15
src/pages/home/village/introd/tree.vue

@@ -8,6 +8,13 @@
       :treeImage="currentVillage.treeImage"
       :treeName="currentVillage.treeName"
       :treeAnimProps="currentVillage.treeImageAnimProps"
+      :fruitImage="appConfiguration.tree.fruitImage"
+      :backgroundImage="appConfiguration.tree.backgroundImage"
+      :animFertilize="appConfiguration.tree.animFertilize"
+      :animFertilizeAnimProps="appConfiguration.tree.animFertilizeAnimProps"
+      :animWater="appConfiguration.tree.animWater"
+      :animWaterAnimProps="appConfiguration.tree.animWaterAnimProps"
+      :enable="true"
       @fruitPick="handlePick"
       @waterEnd="handleWaterAnimEnd"
       @fertilizeEnd="handleFertilizeAnimEnd"
@@ -34,7 +41,17 @@
         </FlexRow>
         <Height height="space.lg" />
         <FlexRow position="relative" center gap="gap.md" overflow="visible">
-          <PopTextAnim :show="Boolean(popAnimText)" :text="popAnimText" @end="popAnimText = ''" />
+          <PopTextAnim :show="Boolean(popAnimText)" @end="popAnimText = ''">
+            <FlexRow 
+              center 
+              borderColor="border.secondary" borderWidth="1px" borderStyle="solid"
+              gap="gap.md" padding="padding.md" radius="radius.md" 
+              backgroundColor="background.tertiary"
+            >
+              <Icon icon="https://xy.wenlvti.net/app_static/images/village/IconLight.png" :size="50" />
+              <Text :text="popAnimText" fontConfig="contentText" fontSize="30rpx" color="#E79412" />
+            </FlexRow>
+          </PopTextAnim>
           <Icon icon="https://xy.wenlvti.net/app_static/images/village/IconLight.png" :size="50" />
           <Text :text="`乡源光 ${currentVillage?.lightTotal || 0} ${currentVillage.treeName}`" fontConfig="contentText" fontSize="30rpx" color="#E79412" />
         </FlexRow>
@@ -48,14 +65,14 @@
           <Image src="https://xy.wenlvti.net/app_static/images/village/IconCollect.png" :width="130" mode="widthFix" />
           <Text text="拾果" fontConfig="contentText" />
         </Touchable>
-        <Touchable center direction="column" flexBasis="22%" @click="handleFertilize">
-          <Image src="https://xy.wenlvti.net/app_static/images/village/IconFertilization.png" :width="130" mode="widthFix" />
-          <Text text="施肥" fontConfig="contentText" />
-        </Touchable>
         <Touchable center direction="column" flexBasis="22%" @click="handleWater">
           <Image src="https://xy.wenlvti.net/app_static/images/village/IconWatering.png" :width="130" mode="widthFix" />
           <Text text="浇水" fontConfig="contentText" textAlign="center" />
         </Touchable>
+        <Touchable center direction="column" flexBasis="22%" @click="handleFertilize">
+          <Image src="https://xy.wenlvti.net/app_static/images/village/IconFertilization.png" :width="130" mode="widthFix" />
+          <Text text="施肥" fontConfig="contentText" />
+        </Touchable>
         <Touchable center direction="column" flexBasis="22%" @click="handleGoBless">
           <Image src="https://xy.wenlvti.net/app_static/images/village/IconBlessing.png" :width="130" mode="widthFix" />
           <Text text="赐福" fontConfig="contentText" textAlign="center" />
@@ -139,6 +156,11 @@
   />
   <BlessSuccessDialog 
     ref="blessSuccessDialogRef" 
+    @close="handleBlessDone"
+  />
+  <TreeLevelUpDialog 
+    ref="treeLevelUpDialogRef" 
+    :treeLevel="lastLevelUp"
   />
 </template>
 
@@ -150,8 +172,9 @@ import { useRequireLogin } from '@/common/composeabe/RequireLogin';
 import { showError } from '@/common/composeabe/ErrorDisplay';
 import { toast } from '@/components/utils/DialogAction';
 import { navTo } from '@/components/utils/PageAction';
-import { RequestApiError, SimpleTimer, waitTimeOut } from '@imengyu/imengyu-utils';
+import { RequestApiError, requireNotNull, SimpleTimer, waitTimeOut } from '@imengyu/imengyu-utils';
 import { useAuthStore } from '@/store/auth';
+import { injectAppConfiguration } from '@/api/system/useAppConfiguration';
 import HomeTitle from '@/common/components/parts/HomeTitle.vue';
 import Text from '@/components/basic/Text.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
@@ -168,6 +191,7 @@ import TreeApi, { type BlessPackageItem, type GrowthLogFeedItem } from '@/api/li
 import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import BlessBuyDialog from '../dialogs/BlessBuyDialog.vue';
 import BlessSuccessDialog from '../dialogs/BlessSuccessDialog.vue';
+import TreeLevelUpDialog from '../dialogs/TreeLevelUpDialog.vue';
 import PopTextAnim from './components/PopTextAnim.vue';
 
 const GROWTH_FEED_COUNT = 6;
@@ -176,8 +200,11 @@ const DEFAULT_AVATAR = 'https://xy.wenlvti.net/app_static/images/village/Placeho
 const villageStore = useVillageStore();
 const authStore = useAuthStore();
 const { requireLoginAsync } = useRequireLogin();
+const appConfiguration = injectAppConfiguration();
 
 const blessBuyDialogRef = ref<InstanceType<typeof BlessBuyDialog>>();
+const blessSuccessDialogRef = ref<InstanceType<typeof BlessSuccessDialog>>();
+const treeLevelUpDialogRef = ref<InstanceType<typeof TreeLevelUpDialog>>();
 const villageTreeRef = ref<InstanceType<typeof VillageTree>>();
 const currentBless = ref<BlessPackageItem>();
 
@@ -330,16 +357,14 @@ async function handlePickOrWaterOrFertilize(action: 'pick' | 'water' | 'fertiliz
         break;
       case 'water': {
         await TreeApi.water(villageStore.currentVillage.id);
-        const addLight = 3;
         villageTreeRef.value?.playStateAnimation('water');
-        toast(`浇水成功!又获得乡源光啦`, 4000);
+        lastRefreshAction.value = 'water';
         break;
       }
       case 'fertilize': {
         await TreeApi.fertilize(villageStore.currentVillage.id);
-        const addLight = 3;
         villageTreeRef.value?.playStateAnimation('fertilize');
-        toast(`浇水成功!又获得乡源光啦`, 4000);
+        lastRefreshAction.value = 'fertilize';
         break;
       }
     }
@@ -348,18 +373,52 @@ async function handlePickOrWaterOrFertilize(action: 'pick' | 'water' | 'fertiliz
   } catch (e) {
     uni.hideLoading();
     if (e instanceof RequestApiError && typeof e.data === 'string') {
-      toast(e.data);
+      toast(e.data, 4000);
       return;
     }
     showError(e);
   }
 }
 
+const lastRefreshAction = ref<'water' | 'fertilize' | 'bless' | null>(null);
+const lastAddLight = ref(0);
+const lastLevelUp = ref('');
+
 function handleWaterAnimEnd() {
-  popAnimText.value = popAnimTextNext.value;  
+  handleShowActionTip(); 
 }
 function handleFertilizeAnimEnd() {
-  popAnimText.value = popAnimTextNext.value;
+  handleShowActionTip();
+}
+async function handleBlessDone() {
+  uni.pageScrollTo({ scrollTop: 0 });
+  lastRefreshAction.value = 'bless';
+  await handleBlessPaySuccessRefresh();
+  handleShowActionTip();
+}
+async function handleShowActionTip() {
+  let text = '', addLightText = '';
+  if (lastAddLight.value > 0) 
+    addLightText =  `乡源光 +${lastAddLight.value}`;
+  switch (lastRefreshAction.value) {
+    case 'water':
+      text = '谢谢您的浇水!';
+      popAnimText.value =  `${text} ${addLightText}`;
+      break;
+    case 'fertilize':
+      text = '谢谢您的施肥!';
+      popAnimText.value =  `${text} ${addLightText}`;
+      break;
+    case 'bless':
+      text = '感谢您的赐福!';
+      popAnimText.value =  `${text} ${addLightText}`;
+      break;
+  }
+  await waitTimeOut(2000);
+  if (lastLevelUp.value) {
+    treeLevelUpDialogRef.value?.show();
+  }
+  lastRefreshAction.value = null;
 }
 
 async function getFruits() {
@@ -371,7 +430,17 @@ async function getFruits() {
   }
 }
 async function refreshVillageTreeInfo() {
-  await villageStore.reloadVillageInfo();
+  const oldVillage = await villageStore.reloadVillageInfo();
+  if (oldVillage) {
+    const newVillage = requireNotNull(villageStore.currentVillage);
+    const oldLight = oldVillage.lightTotal;
+    const oldTreeName = oldVillage.treeName;
+    lastAddLight.value = Math.max(0, oldLight - newVillage.lightTotal);
+    lastLevelUp.value = oldTreeName !== newVillage.treeName ? newVillage.treeName : '';
+  } else {
+    lastAddLight.value = 0;
+    lastLevelUp.value = '';
+  }
 }
 
 const refreshFruitTimer = new SimpleTimer(undefined, () => getFruits(), 15000);
@@ -385,7 +454,7 @@ onBeforeMount(() => {
 defineExpose({
   onPageBack: (name: string, data: Record<string, unknown>) => {
     if (name === 'blessPaySuccessRefresh') {
-      handleBlessPaySuccessRefresh();
+      blessSuccessDialogRef.value?.show();
     }
 
   },

+ 4 - 2
src/store/village.ts

@@ -26,12 +26,14 @@ export const useVillageStore = defineStore('village', () => {
   async function reloadVillageInfo() {
     try {
       if (!currentVillage.value)
-        return;
+        return null;
+      const oldVillage = currentVillage.value;
       const res = await LightVillageApi.getVillageDetails(currentVillage.value.id as number);
       setCurrentVillage(res);
+      return oldVillage;
     } catch (error) {
       console.error(error);
-      return;
+      return null;
     }
   }
   function setCurrentLonlat(lonlat: { longitude: number, latitude: number }) {