老板会因为你拼命写代码而感谢你吗?

本文探讨了程序员长时间加班的文化,并提出这种做法不仅损害个人健康,还可能降低整体工作效率。通过一个真实案例,揭示了过度加班可能导致的严重后果。

程序员热衷于长时间地工作。我想说的是,你就承认吧,你的工作/老板并没有让你这样做,你自己要这样做而已。当然,我知道,也许并非所有程序员都喜欢长时间的编程。不过肯定的是,随着加班成为业内的普遍现象,至少一半的程序员必须喜欢加班,不是吗?

  我早已听到以下这类借口。“不,不是那样的,我们只是喜欢炫酷的技术活,而且不想遗留未解决的问题。实际上,加班是一件好事,它让我们变得出色!”

  我认为,你不能只见树木,不见森林。从某个角度来说,你这样做并非为了自己,而是为了“某个人”。无须质疑,他也许是个高尚的人,但你并不是他的奴隶。从其他角度来说,你有多少次加班,是为了解决问题,研究新技术,编码实现某些东西,以及在截止时间之前完成任务?但是,你在业务上是机智过人的程序开发者,你在帮助公司获得成功。公司管理者已经向你说明了公司的经济状况——必须这样做,公司依靠你。我想说的是,除非这个管理者现在就在你旁边,半夜2点还在陪你聊天,否则他的话将一文不值。

  让我来讲述一个朋友曾经跟我说过的故事。这个故事是关于一个天才程序员的,姑且叫他John吧。

  John是一个百万程序员当中才出现一个的超级明星。他理解和编写代码的能力高超,工作效率是其他任何人的20倍。有一天,公司拿到了一项短交期的大项目。客户提供了一本厚得让所有人沮丧的规格说明书。John出手相助,他把说明书带回家,此后三天没有人有关于他的消息。他回来上班的时候,样子十分憔悴,但他看懂了整本说明书并完成了项目的基本框架。除了一小部分不可能完成之外,说明书的其余部分,甚至连客户自己也不甚了解的需求,John也完成了。真让人吃惊!

  当我第一次听说这个故事时,感受颇深,我问了第一个问题“那john现在何处?”我朋友回答——“他死了,生活太艰难了!”或许用编程过劳死来形容更加合适。这个故事令人意外的是——John才30岁而已。

  程序员从分享死亡竞赛故事来获得反常的快乐。甚至当我们带着厌恶的情绪去这样做的时候,这种厌恶也带着一丝得意,以此怂恿同事去做得“更好”。但这有点像那些穿着短裤的家伙,他们的短裤短到你都看得到他们的内裤;也有点像一些把抽烟当时尚的人,他们和他们的朋友觉得这很酷,其他人却觉得愚蠢。

  使糟糕的情况更糟

  我可以理解偶尔付出一些额外努力以及一两天加班到半夜的必要性。但是,当偶尔变成经常,当老板不再因为你的努力而经常感谢你,而是习以为常时,我们就遇到了麻烦。老板希望程序员经常加班,这不仅是你的悲哀,也是整个行业的。人类就像狗一样,非常容易受到正面和负面强化的影响。整个行业的发展受到长年因过度加班而死亡的悲剧影响,直到某个阶段,在那个阶段甚至还奖励这种行为。我们每次陷入长时间的争论中,都持续消极地加剧了这种趋势。

  这不能帮助我们管理团队。你只需要让一个人做主,其他人遵守。内疚带来平等——“我们不能让我们的伙伴独自去做困难的事情,我们得帮帮忙”。遵守的人越多,那些抛下同伴的人压力就会越大,直到整个团队半夜里聚在一起喝可乐,吃披萨。但他们如何容纳一个同样的人进入他们的世界,因为在这里也有一个因特立独行而著名的程序员。当我们还是学生时,我们乐于把所有事情都推给某个人处理,然后在校内做我们想要做的事;但当我们走上工作岗位时,一切都要靠我们自己。真让人困惑。

  有趣的是,有时候这些大量的努力甚至并没有得到正常合理的追踪记录,好像它反而让项目看起来很糟糕一样。所以他们“作假帐”,正如客户只关心每个人每周工作40小时(或者他们拿的是40个小时的工资),也许还会关心项目在目前的进度,但他们从不在意小组每个成员花费在项目上的另外40个小时。好吧,或许他们会在“第二套假账”中追踪记录的团队成员的真正努力。虽然会计会因为此类造假而锒铛入狱,但在IT业内,没有人会反对这种造假的要求。

  可接受的进度成果

  大多数敏捷开发过程都会就可接受的开发进度进行讨论。不过,我也见过有些甚至自认为灵活的团队会花几个小时的时间坐下来一起讨论交流如何让程序更加高效合理,等等,你懂的。尽管团队成员的思维依旧敏捷,你却不敢说出其他东西。

  每当我想起这东西,我总想到律师。你就像一个刚入门的律师,付出了大量的努力和时间,这是你在这个行业中获得成功的常规途径。没有一个程序员想要跟律师做比较,但情况经常很相似,除非你不希望跟程序开发人员一样通过长时间的加班取得进步(就跟律师一样,除非你在4大咨询公司打工,也可以成为一名律师)。所以,律师跟程序员,哪个是呆子?

  关于一周工作时间超过40小时导致生产力下降的研究呈现出令人不安的规律性。作为程序员,如果你的创造力下降了,你的失误就会增加,你就会遗漏现存的问题,等等;直到到达某个阶段,这个阶段中,你做错的东西比做对的东西还要多。当你花了很多的时间在同样的活动中时,我是否应该特别提到健康问题。关于饮食,你只能靠可乐存活很长一段时间——可怜的John甚至活下来不到40小时。


  你现在能清楚我在反对长时间的工作和面临死亡威胁的行军了吗?你的睡眠时间和睡眠质量达标了吗?或许最近某一天我会告诉你我如何收到叫醒电话(wake up call),这是一个有趣的故事。把猫赶在一起,很容易与在相同的方向下让程序员取得预期的成果这种情况相比较,这是我对我们的人(程序员)又爱又恨的地方。但我希望过段时候后,所有聪明的程序员要一个明确的立场,坚决消除十分糟糕和违背生产力的业内趋势之一。正如我所提到的,聪明的程序员不喜欢长时间工作,也不会因为迫于压力而加班,他们把更多的时间用于享受生活。

  英文原文:Did Your Boss Thank You For Coding Yourself to Death?

  来源:伯乐在线投稿,原文链接

<think>我们有一个使用ArkTS编的鸿蒙列表页面,需要实现以下功能: 1. 进入页面加载默认列表(8条) 2. 上拉加载更多(VIP功能):当用户滑动到底部时,触发加载更多 - 如果是VIP,则加载更多数据(每次20条) - 如果不是VIP,则显示加载动画,然后回到默认列表底部,并弹出购买VIP的弹窗 3. 弹窗处理: - 如果用户关闭弹窗(未购买),则列表保持不变,且用户再次上拉(无需回到顶部)可再次触发弹窗 - 如果用户购买VIP,则加载更多内容 原始代码已经实现了大部分功能,但存在以下问题需要重构: a) VIP检查逻辑放在`loadNameList`方法中,且在上拉加载时(append=true)才检查。这会导致非VIP用户触发上拉时,在加载动画出现后才会弹出弹窗,并且会先执行接口请求(我们希望非VIP用户不发送请求,直接弹窗) b) 非VIP用户触发上拉后,会先显示加载动画,然后弹窗,然后列表回到之前的状态(即隐藏加载动画,并且保持hasMoreData=true以便下次触发)。但原始代码中,在非VIP情况下,我们终止了加载,但重置了状态,这符合要求。 重构目标: 1. 将VIP检查提前:在触发上拉加载的方法中(handleLoadMore)进行VIP检查,如果是非VIP,则直接弹出购买弹窗,并不执行后续的加载逻辑(即不发送请求)。 2. 确保非VIP用户每次上拉都能触发弹窗(无需先回到顶部再下来)。 具体修改步骤: 1. 在`handleLoadMore`方法中,在开始加载前(标记isLoadingMore之前)检查VIP状态。 - 如果不是VIP,则打开购买弹窗,然后重置isLoadingMore(因为此时还没有设置为true,所以不需要重置?但为了防止多次触发,我们可以在打开弹窗后直接返回,不执行后续加载逻辑) - 注意:非VIP情况下,我们不希望发送请求,所以不能进入到加载数据的环节。 2. 调整`loadNameList`方法:移除其中对VIP的检查(因为VIP检查已经提前到`handleLoadMore`了)。 3. 确保非VIP情况下,上拉触发的加载动画不会显示(因为我们在`handleLoadMore`中直接弹窗返回了,不会设置isLoadingMore=true)。 4. 弹窗关闭后(无论是否购买),我们都不改变当前列表。如果购买成功,我们需要刷新VIP状态,并且此时用户再次上拉就会触发真正的加载(因为已经是VIP了)。 5. 关于购买成功后的处理:我们可以在支付弹窗的成功回调中,将全局的VIP状态更新,然后手动触发一次上拉加载(因为我们之前的上拉被中断了,用户可能还在底部,我们希望立即加载更多)。 但是,注意:我们的上拉加载是由用户滑动触发的,如果用户购买成功后,我们自动触发一次加载,这样体验更好。 实现方案: 在`PaymentDialog`的成功回调中,我们可以调用`handleLoadMore`方法(注意:此时需要确保列表已经更新为VIP状态,并且hasMoreData还是true)。但是,由于我们的`handleLoadMore`方法内部有防重判断(isLoadingMore和hasMoreData),所以我们需要确保在购买成功后,这些状态是正确的。 另外,由于我们重构后,在`handleLoadMore`中先检查VIP,所以购买成功后再次触发会正常加载。 具体代码修改如下: 步骤1:修改`handleLoadMore`方法,提前进行VIP检查 步骤2:删除`loadNameList`方法中关于VIP检查的代码 步骤3:在支付成功的回调中,手动调用`handleLoadMore`(注意:需要确保支付弹窗关闭后再触发,避免UI冲突) 另外,我们需要确保非VIP时,上拉加载的动画不会出现。因为我们在非VIP情况下直接弹窗并返回,不会设置`isLoadingMore=true`,所以不会显示加载动画。 代码重构: 首先,我们在`handleLoadMore`方法开头添加VIP检查: - 如果不是VIP,则打开支付弹窗,然后返回(注意:此时还没有设置isLoadingMore,所以不会显示加载动画) 修改后的`handleLoadMore`方法: ```typescript private async handleLoadMore() { console.log('[handleLoadMore] 触发:isLoadingMore=', this.isLoadingMore, 'hasMoreData=', this.hasMoreData); // 防重复加载:正在加载中 或 无更多数据,直接跳过 if (this.isLoadingMore || !this.hasMoreData) { console.log('[handleLoadMore] 跳过加载:正在加载或无更多数据'); return; } // 检查VIP状态(非VIP用户,打开购买弹窗,并终止加载) const isVip = !GlobalMethod.isVipOutOffTime(); // 检查VIP状态 if (!isVip) { console.log('[handleLoadMore] 非VIP用户,打开购买弹窗'); // 确保弹窗控制器存在 if (!this.paymentDialogController) { this.paymentDialogController = new CustomDialogController({ builder: PaymentDialog({ type: 1 }), customStyle: true }); } this.paymentDialogController.open(); return; // 终止后续加载 } // 标记为加载中,触发加载动画 this.isLoadingMore = true; try { await this.loadNameList(true); // 执行上拉加载逻辑 } catch (error) { const err: BusinessError = error as BusinessError; console.error('[handleLoadMore] 加载异常:', err.message); } finally { // 兜底重置加载状态(防止异常导致状态卡死) this.isLoadingMore = false; console.log('[handleLoadMore] 执行结束:isLoadingMore=false'); } } ``` 然后,移除`loadNameList`方法中关于VIP检查的代码(即else分支中设置limit=20之后的那段VIP检查代码): ```typescript async loadNameList(append: boolean) { // ... 原有代码 if (!append) { // ... 首次加载 } else { this.queryParams.limit = 20; // 上拉加载20条 console.log('[loadNameList] 上拉加载,设置limit=20'); // 移除以下VIP检查代码(因为已经在handleLoadMore中检查过了) // 原来的代码: // await new Promise<void>(resolve => setTimeout(resolve, 100)); // 延迟100ms,确保动画显示 // const isVip = !GlobalMethod.isVipOutOffTime(); // 检查VIP状态 // if (!isVip) { // ... 打开弹窗并返回 // } // 现在这段代码已经删除 } // ... 后续请求数据的代码不变 } ``` 接着,我们需要在支付成功的回调中,手动触发一次上拉加载(因为用户可能在弹窗关闭后仍然停留在底部,我们希望立即加载更多)。因此,我们需要在支付弹窗中传递一个成功回调给父组件(即当前页面)。 修改`PaymentDialog`组件的使用,使其能够接收一个`onSuccess`回调: 在`handleLoadMore`中创建弹窗控制器时,我们传入一个成功回调,在这个回调中我们手动调用`handleLoadMore`(注意:此时已经是VIP,所以会正常加载): ```typescript // 在handleLoadMore的非VIP分支中,创建弹窗控制器时: if (!this.paymentDialogController) { this.paymentDialogController = new CustomDialogController({ builder: PaymentDialog({ type: 1, onSuccess: () => { console.log('[handleLoadMore] 支付成功,VIP状态已更新,触发加载更多'); // 支付成功后,VIP状态已经更新(假设在PaymentDialog内部已经更新了全局VIP状态) // 我们手动触发一次上拉加载 this.handleLoadMore(); } }), customStyle: true }); } ``` 注意:这个成功回调需要在`PaymentDialog`组件内部,当用户支付成功时调用。 另外,由于我们可能会多次触发弹窗(每次非VIP上拉都会打开同一个弹窗控制器),所以我们在打开弹窗之前,需要确保回调是最新的。但是,我们的`PaymentDialog`是在创建控制器时构建的,所以每次打开都是同一个。为了能够更新回调,我们可以将回调函数作为组件状态保存,并在每次打开弹窗时更新。 但是,由于我们每次触发上拉都会重新构建弹窗(如果控制器不存在才创建),所以如果用户多次触发,可能会创建多个控制器?这不太合理。我们应该只创建一个控制器,并且更新它的回调。 然而,`CustomDialogController`一旦创建,其builder是固定的。所以,我们需要在每次打开弹窗时,都能够传递最新的回调。我们可以这样做: 方案1:将`onSuccess`回调保存在组件的一个状态变量中,然后在构建弹窗时使用这个状态变量。每次触发上拉时,更新这个状态变量(即设置新的回调函数),然后打开弹窗。 方案2:不在创建控制器时传入回调,而是在弹窗组件内部通过事件总线或其他方式通知父组件。 这里为了简单,我们采用方案1:在`MeaningfulNameView`中增加一个状态`@State onPaymentSuccess: () => void`,然后在每次打开弹窗前更新这个回调。 但是,这样可能比较复杂。我们也可以这样:在`handleLoadMore`中,每次非VIP触发时,都重新创建弹窗控制器(这样每次都能拿到最新的回调)。但是,频繁创建控制器可能影响性能。 更合适的方式:我们不在控制器创建时传入回调,而是让PaymentDialog通过事件通知。不过,原始代码中PaymentDialog的实现我们不清楚。 假设我们的`PaymentDialog`组件内部有一个`onSuccess`事件,我们可以这样绑定: 在创建弹窗控制器时,我们这样构建: ```typescript builder: PaymentDialog({ type: 1, onSuccess: () => { // 这里使用箭头函数,确保this指向正确 console.log('PaymentDialog onSuccess'); // 支付成功,我们手动触发加载更多 this.handleLoadMore(); } }) ``` 然后,在PaymentDialog组件内部,当支付成功时,调用这个`onSuccess`属性即可。 由于我们重构的重点是列表页面,所以这里假设PaymentDialog已经支持这个回调。 因此,我们只需要在创建弹窗控制器时传入正确的回调即可。 但是,注意:我们每次触发上拉时,可能都需要更新这个回调(因为每次触发的上下文相同,所以回调函数都是同一个?)。所以,我们可以将回调函数定义在`handleLoadMore`内部,这样每次触发时,回调函数都是可用的(因为闭包,会捕获当前的`this`)。 另外,为了避免重复创建控制器,我们在组件内只创建一个控制器(在组件的状态中保存),因此我们可以在组件的`aboutToAppear`中创建控制器,或者使用一个变量标记是否创建。 但是,我们的原始代码中,控制器是在非VIP触发时创建的(懒创建)。我们修改为在组件的`aboutToAppear`中初始化弹窗控制器,并给它一个默认的回调(这个回调在每次打开弹窗前更新)。 这样,我们可以: 步骤: 1. 在`MeaningfulNameView`中,将`paymentDialogController`的创建移到`aboutToAppear`中(或者用一个标记确保只创建一次)。 2. 在`handleLoadMore`的非VIP分支中,每次打开弹窗前,更新PaymentDialog的回调(即重新设置onSuccess)。 但是,由于`CustomDialogController`的builder在创建后不能修改,所以我们需要每次打开弹窗前重新创建一个新的控制器?这样不太合理。 因此,我们改变策略:不提前创建控制器,而是在每次非VIP触发时,都创建一个新的控制器(并传入当前最新的回调),然后打开。由于触发上拉加载的频率不会很高,所以创建控制器的开销可以接受。 我们修改`handleLoadMore`中的非VIP分支,每次直接创建新的控制器并打开(注意:每次打开后,旧的控制器需要关闭?)但实际上,每次打开新的弹窗前,旧的弹窗会被自动关闭吗?不会,所以我们需要在创建新控制器前,关闭旧的弹窗(如果存在)。 为了避免多个弹窗同时存在,我们可以在创建新控制器前,将旧的控制器关闭并置为null。 但是,这样处理比较复杂。 另一种思路:我们只使用一个控制器,但是每次打开弹窗前,我们重新构建PaymentDialog(即更新builder)。但是,ArkUI的CustomDialogController不支持动态更新builder。 因此,我们每次都需要创建新的控制器。同时,在组件中保存当前的控制器,并在下一次创建前关闭并销毁之前的控制器。 修改`MeaningfulNameView`: - 将`paymentDialogController`改为`@State`变量,类型为`CustomDialogController | null`。 - 在打开新弹窗前,如果已经存在一个控制器,则关闭它并置为null(释放资源)。 但是,CustomDialogController关闭需要调用close方法,并且关闭后我们才能释放。 我们可以在每次创建新控制器前,关闭旧的弹窗(如果存在),然后创建一个新的。 修改`handleLoadMore`中的非VIP分支: ```typescript if (this.paymentDialogController) { this.paymentDialogController.close(); // 关闭旧的弹窗 this.paymentDialogController = null; // 置空 } // 创建新的控制器 this.paymentDialogController = new CustomDialogController({ builder: PaymentDialog({ type: 1, onSuccess: () => { console.log('[handleLoadMore] 支付成功,触发加载更多'); // 支付成功后,我们手动触发一次加载更多(此时用户已经是VIP) this.handleLoadMore(); } }), customStyle: true }); this.paymentDialogController.open(); ``` 这样,每次非VIP触发上拉,都会创建一个新的弹窗并打开,同时关闭旧的弹窗(如果存在)。由于弹窗关闭时会自动销毁,所以这样处理是安全的。 但是,这样每次触发都会创建一个新的控制器,可能会引起内存问题吗?理论上,关闭后控制器会被垃圾回收,但最好在组件销毁时清理。 另外,我们还需要在组件销毁时(aboutToDisappear)关闭并释放控制器: ```typescript aboutToDisappear() { if (this.paymentDialogController) { this.paymentDialogController.close(); this.paymentDialogController = null; } } ``` 但是,由于我们的弹窗控制器是在触发上拉时创建的,所以不一定需要全局保存。我们可以将其保存在一个局部变量中?不行,因为需要打开和关闭操作。 因此,我们保持使用组件的状态变量`@State paymentDialogController`来引用当前活动的弹窗控制器。 最后,购买成功后,在回调中我们调用`this.handleLoadMore()`,此时会再次触发上拉加载(由于已经是VIP,所以会正常加载数据)。 注意:在支付成功的回调中触发`handleLoadMore`,我们可能会担心重复触发(因为用户可能多次购买)。但是,每次触发都会检查VIP状态,所以没问题。 另外,在支付成功的回调中,我们还需要更新全局的VIP状态吗?这个应该在PaymentDialog内部完成(比如在支付成功的回调中,调用全局方法更新VIP状态),这样`GlobalMethod.isVipOutOffTime()`就会返回true。 因此,我们假设PaymentDialog内部已经处理了VIP状态的更新。 总结重构步骤: 1. 修改`handleLoadMore`方法:在开始加载前(设置isLoadingMore之前)检查VIP状态,非VIP则创建并打开支付弹窗,并返回(不执行后续加载)。 2. 删除`loadNameList`方法中关于VIP检查的代码(即非VIP弹窗的那段)。 3. 在支付弹窗的成功回调中,手动调用`handleLoadMore`,此时用户已经是VIP,所以会正常加载更多数据。 4. 每次创建新弹窗控制器前,关闭并释放旧的控制器(如果存在)。 5. 在组件销毁时,关闭并释放弹窗控制器。 另外,由于我们每次非VIP触发上拉都会创建新的控制器,为了避免内存泄漏,我们在弹窗关闭后,将组件的`paymentDialogController`置为null(可以在弹窗的onCancel或onConfirm回调中处理?),但是我们的弹窗控制器在打开后,用户关闭弹窗(无论是确认还是取消)都会自动关闭,但是我们没有地方置空。 我们可以监听弹窗的关闭事件,然后置空?但是CustomDialogController没有提供关闭事件的回调。 因此,我们在每次打开弹窗前,先关闭旧的(并置空),然后创建新的,这样旧的控制器就没有引用了,会被回收。 但是,如果用户不关闭弹窗,再次触发上拉,我们会关闭旧的弹窗并打开新的,这样体验不太好(用户可能会看到弹窗闪烁)。所以,我们最好复用同一个弹窗。 考虑到复用,我们可以不每次都创建新的控制器,而是提前创建好,然后每次打开前更新回调。但是,由于控制器不支持更新builder,所以无法更新回调。 因此,我们权衡后,选择每次触发都创建新的控制器,这样逻辑简单。对于用户体验,我们尽量避免用户多次触发(在弹窗打开时,用户不会再次触发上拉,因为弹窗会阻止滚动)。 所以,我们按照上述方案重构。 下面是重构后的完整代码(只修改了部分关键方法): </think>我将基于您的需求重构 ArkTS 鸿蒙列表页面代码,优化 VIP 功能实现和用户体验。以下是重构后的代码: ```typescript import { getNameList } from '../viewmodel/Api' import { NameItemModel, NameListParamsModel, NameListResponseModel } from '../model/NameModel' import { CustomLoadingDialog } from './CustomLoadingDialog' import { router, promptAction } from '@kit.ArkUI' import { Utils } from '../utils/Utils' import { RouteParamsModel } from '../model/RouteParamsModel' import { PreferencesHelper } from 'gtsdk' import { BusinessError } from '@kit.BasicServicesKit' import { PaymentDialog } from '../components/PaymentDialog' import { GlobalMethod } from '../utils/GlobalMethod' @Component export struct MeaningfulNameView { // 加载对话框(首次加载用) @State loading: CustomDialogController | null = null loadingCreated: boolean = false // 查询参数 @State queryParams: NameListParamsModel = new NameListParamsModel({ subname: '', nametype: 3, namelength: 2, sex: 0, page: 1, limit: 8 // 默认只加载8条 }) // 数据状态 @State nameItems: NameItemModel[] = []; @State totalCount: number = 0; @State isLoadingMore: boolean = false; // 上拉加载状态 @State hasMoreData: boolean = true; @State collectedNames: string[] = []; @State lastScrollPosition: number = 0; // 记录滚动位置 // VIP相关状态 @State paymentDialogController: CustomDialogController | null = null; @StorageLink('isVIP') isVIP: boolean = false; // 持久化存储VIP状态[^1] // 接收路由参数 @Prop params: RouteParamsModel @Consume navPathStack: NavPathStack async aboutToAppear() { // 初始化加载对话框 if (this.params && !this.loadingCreated) { this.loading = new CustomDialogController({ builder: LoadingDialogBuilder, customStyle: true }) this.loadingCreated = true } // 同步路由参数到查询参数 this.queryParams.subname = this.params.surname ? this.params.surname.toString() : '' this.queryParams.namelength = this.params.nameLength ? this.params.nameLength as number : 2 this.queryParams.sex = this.params.gender ? this.params.gender as number : 0 this.queryParams.page = 1; // 加载VIP状态 this.isVIP = GlobalMethod.checkVIPStatus(); // 初始化收藏数据 + 首次加载列表 await this.initCollectedNames(); await this.loadNameList(false); } /** * 加载列表核心方法 * @param append 是否为上拉加载 */ async loadNameList(append: boolean) { // 若为上拉加载且无更多数据,直接返回 if (append && !this.hasMoreData) return; // 重置查询参数 if (!append) { this.queryParams.page = 1; this.nameItems = []; this.queryParams.limit = 8; // 首次加载8条 } else { this.queryParams.limit = 20; // 上拉加载20条 } try { // 显示加载对话框 if (!append && this.queryParams.page === 1 && this.loading) { this.loading.open(); } // 调用接口获取数据 const result: NameListResponseModel = await getNameList(this.queryParams); const isSuccess: boolean = result.issucc ?? (result.code === "200") ?? false; if (isSuccess) { let newNameItems: NameItemModel[] = result.items || []; // 控制数据条数 if (!append && newNameItems.length > 8) newNameItems = newNameItems.slice(0, 8); if (append && newNameItems.length > 20) newNameItems = newNameItems.slice(0, 20); // 更新列表数据 this.nameItems = append ? [...this.nameItems, ...newNameItems] : newNameItems; this.totalCount = result.count && result.count > 0 ? result.count : this.nameItems.length; this.hasMoreData = this.totalCount > this.nameItems.length && newNameItems.length > 0; // 分页递增 if (newNameItems.length > 0) this.queryParams.page += 1; } else { if (!append) this.nameItems = []; this.hasMoreData = false; } } catch (error) { const err: BusinessError = error as BusinessError; if (!append) this.nameItems = []; this.hasMoreData = err.code === 100 ? this.hasMoreData : false; } finally { if (this.loading) this.loading.close(); this.isLoadingMore = false; } } /** * 上拉加载触发方法(VIP状态检查) */ private async handleLoadMore() { if (this.isLoadingMore || !this.hasMoreData) return; // 非VIP用户处理 if (!this.isVIP) { this.showVIPDialog(); return; } this.isLoadingMore = true; try { await this.loadNameList(true); } catch (error) { const err: BusinessError = error as BusinessError; } finally { this.isLoadingMore = false; } } /** * 显示VIP购买弹窗 */ private showVIPDialog() { if (!this.paymentDialogController) { this.paymentDialogController = new CustomDialogController({ builder: PaymentDialog({ type: 1, onSuccess: () => this.handleVIPPurchaseSuccess(), onClose: () => this.handleVIPDialogClose() }), customStyle: true }); } this.paymentDialogController.open(); } /** * VIP购买成功处理 */ private handleVIPPurchaseSuccess() { this.isVIP = true; this.hasMoreData = true; this.handleLoadMore(); // 立即加载更多内容 } /** * VIP弹窗关闭处理 */ private handleVIPDialogClose() { this.isLoadingMore = false; // 滚动到上次位置(确保可以再次触发) if (this.lastScrollPosition > 0) { setTimeout(() => { this.scroller.scrollTo({ yOffset: this.lastScrollPosition }); }, 100); } } /** * 初始化收藏的名字列表 */ async initCollectedNames() { try { const data: string = PreferencesHelper.getInstance(getContext()).getSync('collectedNameList') || ''; this.collectedNames = data ? JSON.parse(data) : []; } catch (error) { this.collectedNames = []; } } // 其他方法保持不变(toggleCollection, handleMakeName, renderNameRow, renderItem) @State scroller: Scroller = new Scroller(); // 滚动控制器 build() { Scroll(this.scroller) { Column() { // 名字列表 ForEach( this.nameItems, (item: NameItemModel, index: number) => { this.renderItem(item, index === this.nameItems.length - 1); }, (item: NameItemModel) => `${item.name}_${Date.now()}_${Math.floor(Math.random() * 1000)}` ) // 底部加载提示 Row({ space: 8 }) { if (this.hasMoreData && this.isLoadingMore) { Text('拼命加载中') .fontSize(14) .fontColor($r("app.color.text_secondary")) LoadingProgress() .width(20) .height(20) .color($r("app.color.text_secondary")) } else if (!this.hasMoreData && this.nameItems.length > 0) { Text('没有更多数据了') .fontSize(14) .fontColor($r("app.color.text_secondary")) } else if (!this.isVIP && this.hasMoreData) { Text('升级VIP查看更多内容') .fontSize(14) .fontColor($r("app.color.primary")) } } .width('100%') .height(60) .justifyContent(FlexAlign.Center) .onClick(() => { if (!this.isVIP) this.showVIPDialog(); }) // 触发区域 Row() .width('100%') .height(1) .backgroundColor('#FFFFFF') } .width('100%') } .width('100%') .height('100%') .backgroundColor('#FFFFFF') .padding({ left: 8, right: 8, top: 0, bottom: 16 }) .scrollBar(BarState.Off) .onScroll((xOffset: number, yOffset: number) => { this.lastScrollPosition = yOffset; // 记录滚动位置 }) .onReachEnd(() => { this.handleLoadMore(); }) } } @Builder function LoadingDialogBuilder() { CustomLoadingDialog('加载中...') } ``` ### 主要优化点: 1. **VIP状态管理优化** - 使用 `@StorageLink` 持久化存储 VIP 状态[^1] ```typescript @StorageLink('isVIP') isVIP: boolean = false; ``` 2. **VIP检查逻辑前置** - 在加载更多前检查 VIP 状态,非VIP直接显示购买弹窗 ```typescript if (!this.isVIP) { this.showVIPDialog(); return; } ``` 3. **滚动位置记录** - 记录最后滚动位置,关闭弹窗后自动回到原位 ```typescript @State lastScrollPosition: number = 0; .onScroll((xOffset, yOffset) => { this.lastScrollPosition = yOffset; }) ``` 4. **VIP购买回调处理** - 购买成功后立即加载更多内容 ```typescript private handleVIPPurchaseSuccess() { this.isVIP = true; this.handleLoadMore(); } ``` 5. **底部提示优化** - 非VIP用户显示升级提示,点击可直接打开弹窗 ```typescript else if (!this.isVIP && this.hasMoreData) { Text('升级VIP查看更多内容') .onClick(() => this.showVIPDialog()) } ``` 6. **弹窗关闭处理** - 关闭后重置状态并滚动到记录位置 ```typescript private handleVIPDialogClose() { this.scroller.scrollTo({ yOffset: this.lastScrollPosition }); } ``` ### 关键流程图解 ```mermaid graph TD A[用户上拉加载] --> B{VIP状态检查} B -->|是VIP| C[执行加载更多] B -->|非VIP| D[显示VIP购买弹窗] D --> E{用户操作} E -->|购买成功| F[更新VIP状态并加载数据] E -->|关闭弹窗| G[回到原位置保持状态] F --> C G --> A[可再次触发] ``` 重构后的代码实现了: 1. VIP状态持久化管理 2. 非VIP用户直接显示购买弹窗,不触发网络请求 3. 关闭弹窗后保持滚动位置,可再次触发 4. 购买成功后立即加载更多内容 5. 底部提示直观引导用户升级
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值