From 8597504b574649b3f6343eb1fd84b859afc7af2a Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 09:15:26 +0800
Subject: [PATCH 01/46] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0.trae=E5=88=B0?=
=?UTF-8?q?gitignore=E5=BF=BD=E7=95=A5=E5=88=97=E8=A1=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将.trae目录加入gitignore,避免将本地临时目录提交到版本库
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index f98040d..d619add 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
.codebuddy
.vscode
+.trae
# 鸿蒙元服务构建产物
stub-app-projects/mp-harmony/ascf/ascf_src
From aa155189808e354e14a0638c784411128f8573ba Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 09:59:18 +0800
Subject: [PATCH 02/46] feat(splash-screen): add configurable splash screen
display options
add splashConfig in config-external.js to control progress/label/percent display,
add runtime config api in splash-state.js,
refactor splash-screen.vue to support inline layout and prop override
---
common/js/config-external.js | 15 ++
common/js/splash-state.js | 33 +++++
components/splash-screen/splash-screen.vue | 155 ++++++++++++++++-----
3 files changed, 165 insertions(+), 38 deletions(-)
diff --git a/common/js/config-external.js b/common/js/config-external.js
index b380f61..eae8989 100644
--- a/common/js/config-external.js
+++ b/common/js/config-external.js
@@ -158,6 +158,21 @@ export const privacyConfig = {
// },
};
+// ============================================================
+// 启动屏配置 (与 privacyConfig 同构, 由 splash-state.js 消费)
+// showProgress 是否显示进度条本身 + 数字 (label 单独可独立关闭)
+// showLabel 是否显示阶段文案 (Loading language... 等)
+// showPercent 是否显示百分比数字
+// showProgress=true + showLabel=false + showPercent=true → 只显示进度条+百分比
+// showProgress=false → 整个进度条区域隐藏
+// 业务侧可改 splashConfig.* 或在 splash 组件 覆盖
+// ============================================================
+export const splashConfig = {
+ showProgress: true,
+ showLabel: false, // 默认 false: 用户想要"只显示百分比"
+ showPercent: true,
+}
+
// 配置外置管理类
class ConfigExternal {
constructor() {
diff --git a/common/js/splash-state.js b/common/js/splash-state.js
index 5f66b6f..5e2e363 100644
--- a/common/js/splash-state.js
+++ b/common/js/splash-state.js
@@ -10,8 +10,12 @@
// 4. 兜底超时: 默认 8s 未关闭则强制 hide,防止卡死
// 5. 平台隔离: APP-PLUS 有原生 splash,这里直接返回 false
// 6. 跨平台兼容: 同步状态可被 Vue 组件直接 watch
+// 7. 可配置: splashConfig 支持 showProgress/showLabel/showPercent 单独控制
+// 业务侧可在 config-external.js 改 splashConfig, 或 覆盖
// ============================================================
+import { splashConfig as _defaultSplashConfig } from './config-external.js'
+
// APP-PLUS 直接返回 false (有原生启动屏)
function needSplash() {
// #ifdef APP-PLUS
@@ -67,6 +71,8 @@ class SplashState {
// 避免多通道并发时, splash 组件 re-render 风暴
this._pendingProgress = null; // 待 emit 的最新进度
this._flushTimer = null; // 节流定时器
+ // 启动屏显示配置: 初始读 config-external.js 的 splashConfig, 可运行时改
+ this._splashConfig = Object.assign({}, _defaultSplashConfig)
}
show(opts = {}) {
@@ -343,6 +349,33 @@ getStageDef(stageId) {
return STAGES[stageId]
}
+/**
+ * 读取启动屏显示配置
+ * splash 组件读这个决定 label/percent 是否显示
+ * 缺字段时回退到默认 (splashConfig=true)
+ * @returns {{showProgress: boolean, showLabel: boolean, showPercent: boolean}}
+ */
+getSplashConfig() {
+ const c = this._splashConfig || {}
+ return {
+ showProgress: c.showProgress !== false,
+ showLabel: !!c.showLabel,
+ showPercent: c.showPercent !== false,
+ }
+}
+
+
+/**
+ * 运行时合并更新 splash 配置
+ * 全局 splashConfig 在 boot 前注入; 业务运行时改某些字段可用本方法
+ * 不会触发组件 re-render (splash 组件用 props 优先)
+ * @param {Object} partial
+ */
+setSplashConfig(partial) {
+ if (!partial || typeof partial !== 'object') return
+ this._splashConfig = Object.assign({}, this._splashConfig, partial)
+}
+
/**
* 立即刷新挂起的进度更新
* - reportProgress 默认 16ms throttle (1 帧), 避免连续触发 splash 组件多次 re-render
diff --git a/components/splash-screen/splash-screen.vue b/components/splash-screen/splash-screen.vue
index 654795e..6b4bf72 100644
--- a/components/splash-screen/splash-screen.vue
+++ b/components/splash-screen/splash-screen.vue
@@ -23,14 +23,28 @@
-
-
-
-
+
+
+
+
+
+
+
+ {{ percent }}%
-
- {{ stageLabel }}
- {{ percent }}%
+
+
+
+
+
+
+ {{ stageLabel }}
+ {{ percent }}%
+
@@ -42,6 +56,23 @@
export default {
name: 'splash-screen',
+ // props 优先级: 页面级 > splashState.getSplashConfig()
+ // 任何 prop 为 undefined 时回退全局, 给业务侧最大灵活性
+ // 注意: prop 命名前缀 'display' 避免与未来 splash-state 内部字段同名引起 Vue prop-mutation 误判
+ props: {
+ hideProgress: {
+ type: Boolean,
+ default: undefined
+ },
+ hideLabel: {
+ type: Boolean,
+ default: undefined
+ },
+ hidePercent: {
+ type: Boolean,
+ default: undefined
+ },
+ },
data() {
return {
// 默认 visible=true: 让 splash 组件一渲染就可见, 减少"白屏闪烁"
@@ -51,16 +82,32 @@
leaving: false,
percent: 0,
stage: 'boot',
- showProgress: false,
+ // 内部 showProgress 标志 (保留兼容, 由 created() 维护)
+ // 实际模板用 displayConfig.showProgress 决定, 这个内部标志走原逻辑
+ _showProgressLegacy: false,
};
},
computed: {
// 直接从 splashState.STAGES 读取文案, 避免重复维护 STAGE_LABEL
- // (splash-state.js 暴露 getStageDef() 避免外部直接读 STAGES)
stageLabel() {
const def = splashState.getStageDef(this.stage)
return def ? def.label : this.stage
},
+ // 显示配置: props 优先 (hide 语义, false=show), 否则回退全局 splashConfig
+ // 注意: splashConfig 的 show* 是肯定语义 (true=show), 我们这里 hide* 取反
+ displayConfig() {
+ const cfg = splashState.getSplashConfig()
+ return {
+ showProgress: this.hideProgress !== undefined ? !this.hideProgress : cfg.showProgress,
+ showLabel: this.hideLabel !== undefined ? !this.hideLabel : cfg.showLabel,
+ showPercent: this.hidePercent !== undefined ? !this.hidePercent : cfg.showPercent,
+ }
+ },
+ // inline 布局: 只显示百分比 + 进度条 (label 隐藏)
+ // 此时进度条和百分比在同一水平线 (flex)
+ inlineMode() {
+ return this.displayConfig.showPercent && !this.displayConfig.showLabel
+ },
},
// created (而非 mounted): 提前同步 splashState 状态, 减少"白屏一帧"
created() {
@@ -68,7 +115,10 @@
const init = splashState.getProgress()
this.percent = init.percent
this.stage = init.stage
- this.showProgress = init.percent > 0
+ // _showProgressLegacy 控制 progress-fade-in CSS 动画触发
+ // (true → 触发 CSS 关键帧; false → 隐藏整个进度区)
+ // 模板用 displayConfig.showProgress 综合 props + splashConfig 决定
+ this._showProgressLegacy = init.percent > 0
} catch (e) {}
// 同步 splashState 状态: 防止组件挂载在 splash 已 hide 之后误显示
if (!splashState.isShow()) {
@@ -76,34 +126,34 @@
}
},
mounted() {
- splashState.notifyMounted();
- splashState.on('splash:show', () => {
- this.visible = true;
- this.leaving = false;
- // 注意: 不要在这里再读 getProgress(). percent 不允许后退
- // 已通过 created() 同步; emit 进来多是无变化
- const init = splashState.getProgress()
- if (init.percent > this.percent) this.percent = init.percent
- if (init.stage !== this.stage) this.stage = init.stage
- this.showProgress = true
- });
- splashState.on('splash:hide', (info) => {
- // 进度条补完到 100% (视觉上"完成"), 立即 leave
- if (this.percent < 100) {
- this.percent = 100;
- this.stage = 'done';
- }
- // mode='fast' (隐私弹窗即将出现) → 80ms 短动画让 modal 更快接管视觉
- const fast = info && info.mode === 'fast'
- this._leave(fast)
- });
- splashState.onProgress((info) => {
- // 去重: percent/stage 没变化就不 setState, 避免空 re-render
- if (info.percent !== this.percent) this.percent = info.percent
- if (info.stage !== this.stage) this.stage = info.stage
- this.showProgress = true;
- });
- },
+ splashState.notifyMounted();
+ splashState.on('splash:show', () => {
+ this.visible = true;
+ this.leaving = false;
+ // 注意: 不要在这里再读 getProgress(). percent 不允许后退
+ // 已通过 created() 同步; emit 进来多是无变化
+ const init = splashState.getProgress()
+ if (init.percent > this.percent) this.percent = init.percent
+ if (init.stage !== this.stage) this.stage = init.stage
+ this._showProgressLegacy = true
+ });
+ splashState.on('splash:hide', (info) => {
+ // 进度条补完到 100% (视觉上"完成"), 立即 leave
+ if (this.percent < 100) {
+ this.percent = 100;
+ this.stage = 'done';
+ }
+ // mode='fast' (隐私弹窗即将出现) → 80ms 短动画让 modal 更快接管视觉
+ const fast = info && info.mode === 'fast'
+ this._leave(fast)
+ });
+ splashState.onProgress((info) => {
+ // 去重: percent/stage 没变化就不 setState, 避免空 re-render
+ if (info.percent !== this.percent) this.percent = info.percent
+ if (info.stage !== this.stage) this.stage = info.stage
+ this._showProgressLegacy = true;
+ });
+ },
beforeDestroy() {
splashState.off('splash:show');
splashState.off('splash:hide');
@@ -187,6 +237,18 @@
background: rgba($accent, 0.08);
border-radius: 3rpx;
overflow: hidden;
+
+ // inline 模式 (showLabel=false, showPercent=true): 进度条占主宽, 比例调整
+ &--inline {
+ flex: 1;
+ min-width: 0;
+ }
+
+ // full 模式: 原始整条进度条
+ &--full {
+ // 默认宽度 100% 由父容器 .splash-screen__progress 提供
+ width: 100%;
+ }
}
.splash-screen__progress-fill {
@@ -200,6 +262,15 @@
box-shadow: 0 0 12rpx rgba($accent, 0.4);
}
+ // 新增: inline row 布局 (showLabel=false + showPercent=true)
+ // 进度条和百分比在同一水平线: 进度条 + 右侧百分比
+ .splash-screen__progress-row {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ width: 100%;
+ }
+
.splash-screen__progress-text {
display: flex;
justify-content: space-between;
@@ -220,6 +291,14 @@
font-variant-numeric: tabular-nums;
font-weight: 500;
color: $accent;
+
+ // inline 模式: 字号略大, 与进度条同行
+ &--inline {
+ flex-shrink: 0;
+ font-size: 24rpx;
+ min-width: 72rpx;
+ text-align: right;
+ }
}
@keyframes progress-fade-in {
From 3f09dca054fa12bf54cfb932f946ab7d75c55ee3 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 10:22:45 +0800
Subject: [PATCH 03/46] =?UTF-8?q?refactor(splash-screen):=20=E7=A7=BB?=
=?UTF-8?q?=E9=99=A4=E5=BA=9F=E5=BC=83=E7=9A=84=E5=90=AF=E5=8A=A8=E5=B1=8F?=
=?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=B3=BB=E7=BB=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
简化启动屏组件代码,删除不再使用的splashConfig全局配置、相关工具方法和冗余的布局逻辑,统一进度条展示样式。
---
common/js/config-external.js | 15 --
common/js/splash-state.js | 33 ----
components/splash-screen/splash-screen.vue | 194 ++-------------------
3 files changed, 18 insertions(+), 224 deletions(-)
diff --git a/common/js/config-external.js b/common/js/config-external.js
index eae8989..b380f61 100644
--- a/common/js/config-external.js
+++ b/common/js/config-external.js
@@ -158,21 +158,6 @@ export const privacyConfig = {
// },
};
-// ============================================================
-// 启动屏配置 (与 privacyConfig 同构, 由 splash-state.js 消费)
-// showProgress 是否显示进度条本身 + 数字 (label 单独可独立关闭)
-// showLabel 是否显示阶段文案 (Loading language... 等)
-// showPercent 是否显示百分比数字
-// showProgress=true + showLabel=false + showPercent=true → 只显示进度条+百分比
-// showProgress=false → 整个进度条区域隐藏
-// 业务侧可改 splashConfig.* 或在 splash 组件 覆盖
-// ============================================================
-export const splashConfig = {
- showProgress: true,
- showLabel: false, // 默认 false: 用户想要"只显示百分比"
- showPercent: true,
-}
-
// 配置外置管理类
class ConfigExternal {
constructor() {
diff --git a/common/js/splash-state.js b/common/js/splash-state.js
index 5e2e363..5f66b6f 100644
--- a/common/js/splash-state.js
+++ b/common/js/splash-state.js
@@ -10,12 +10,8 @@
// 4. 兜底超时: 默认 8s 未关闭则强制 hide,防止卡死
// 5. 平台隔离: APP-PLUS 有原生 splash,这里直接返回 false
// 6. 跨平台兼容: 同步状态可被 Vue 组件直接 watch
-// 7. 可配置: splashConfig 支持 showProgress/showLabel/showPercent 单独控制
-// 业务侧可在 config-external.js 改 splashConfig, 或 覆盖
// ============================================================
-import { splashConfig as _defaultSplashConfig } from './config-external.js'
-
// APP-PLUS 直接返回 false (有原生启动屏)
function needSplash() {
// #ifdef APP-PLUS
@@ -71,8 +67,6 @@ class SplashState {
// 避免多通道并发时, splash 组件 re-render 风暴
this._pendingProgress = null; // 待 emit 的最新进度
this._flushTimer = null; // 节流定时器
- // 启动屏显示配置: 初始读 config-external.js 的 splashConfig, 可运行时改
- this._splashConfig = Object.assign({}, _defaultSplashConfig)
}
show(opts = {}) {
@@ -349,33 +343,6 @@ getStageDef(stageId) {
return STAGES[stageId]
}
-/**
- * 读取启动屏显示配置
- * splash 组件读这个决定 label/percent 是否显示
- * 缺字段时回退到默认 (splashConfig=true)
- * @returns {{showProgress: boolean, showLabel: boolean, showPercent: boolean}}
- */
-getSplashConfig() {
- const c = this._splashConfig || {}
- return {
- showProgress: c.showProgress !== false,
- showLabel: !!c.showLabel,
- showPercent: c.showPercent !== false,
- }
-}
-
-
-/**
- * 运行时合并更新 splash 配置
- * 全局 splashConfig 在 boot 前注入; 业务运行时改某些字段可用本方法
- * 不会触发组件 re-render (splash 组件用 props 优先)
- * @param {Object} partial
- */
-setSplashConfig(partial) {
- if (!partial || typeof partial !== 'object') return
- this._splashConfig = Object.assign({}, this._splashConfig, partial)
-}
-
/**
* 立即刷新挂起的进度更新
* - reportProgress 默认 16ms throttle (1 帧), 避免连续触发 splash 组件多次 re-render
diff --git a/components/splash-screen/splash-screen.vue b/components/splash-screen/splash-screen.vue
index 6b4bf72..d763a8f 100644
--- a/components/splash-screen/splash-screen.vue
+++ b/components/splash-screen/splash-screen.vue
@@ -2,10 +2,7 @@
-
-
-
@@ -15,36 +12,17 @@
-
-
-
-
-
-
-
-
-
- {{ percent }}%
-
-
-
-
-
-
-
- {{ stageLabel }}
- {{ percent }}%
-
+
+
+ {{ percent }}%
+
+
@@ -56,71 +34,18 @@
export default {
name: 'splash-screen',
- // props 优先级: 页面级 > splashState.getSplashConfig()
- // 任何 prop 为 undefined 时回退全局, 给业务侧最大灵活性
- // 注意: prop 命名前缀 'display' 避免与未来 splash-state 内部字段同名引起 Vue prop-mutation 误判
- props: {
- hideProgress: {
- type: Boolean,
- default: undefined
- },
- hideLabel: {
- type: Boolean,
- default: undefined
- },
- hidePercent: {
- type: Boolean,
- default: undefined
- },
- },
data() {
return {
- // 默认 visible=true: 让 splash 组件一渲染就可见, 减少"白屏闪烁"
- // created() 钩子会同步 splashState 状态, 如果 splashState 已 hide, 立刻设 false
- // 这样既能保证首屏冷启动可见, 又避免挂载到非 splash 路径时误显示
visible: true,
leaving: false,
percent: 0,
- stage: 'boot',
- // 内部 showProgress 标志 (保留兼容, 由 created() 维护)
- // 实际模板用 displayConfig.showProgress 决定, 这个内部标志走原逻辑
- _showProgressLegacy: false,
};
},
- computed: {
- // 直接从 splashState.STAGES 读取文案, 避免重复维护 STAGE_LABEL
- stageLabel() {
- const def = splashState.getStageDef(this.stage)
- return def ? def.label : this.stage
- },
- // 显示配置: props 优先 (hide 语义, false=show), 否则回退全局 splashConfig
- // 注意: splashConfig 的 show* 是肯定语义 (true=show), 我们这里 hide* 取反
- displayConfig() {
- const cfg = splashState.getSplashConfig()
- return {
- showProgress: this.hideProgress !== undefined ? !this.hideProgress : cfg.showProgress,
- showLabel: this.hideLabel !== undefined ? !this.hideLabel : cfg.showLabel,
- showPercent: this.hidePercent !== undefined ? !this.hidePercent : cfg.showPercent,
- }
- },
- // inline 布局: 只显示百分比 + 进度条 (label 隐藏)
- // 此时进度条和百分比在同一水平线 (flex)
- inlineMode() {
- return this.displayConfig.showPercent && !this.displayConfig.showLabel
- },
- },
- // created (而非 mounted): 提前同步 splashState 状态, 减少"白屏一帧"
created() {
try {
const init = splashState.getProgress()
this.percent = init.percent
- this.stage = init.stage
- // _showProgressLegacy 控制 progress-fade-in CSS 动画触发
- // (true → 触发 CSS 关键帧; false → 隐藏整个进度区)
- // 模板用 displayConfig.showProgress 综合 props + splashConfig 决定
- this._showProgressLegacy = init.percent > 0
} catch (e) {}
- // 同步 splashState 状态: 防止组件挂载在 splash 已 hide 之后误显示
if (!splashState.isShow()) {
this.visible = false
}
@@ -130,28 +55,18 @@
splashState.on('splash:show', () => {
this.visible = true;
this.leaving = false;
- // 注意: 不要在这里再读 getProgress(). percent 不允许后退
- // 已通过 created() 同步; emit 进来多是无变化
const init = splashState.getProgress()
if (init.percent > this.percent) this.percent = init.percent
- if (init.stage !== this.stage) this.stage = init.stage
- this._showProgressLegacy = true
});
splashState.on('splash:hide', (info) => {
- // 进度条补完到 100% (视觉上"完成"), 立即 leave
if (this.percent < 100) {
this.percent = 100;
- this.stage = 'done';
}
- // mode='fast' (隐私弹窗即将出现) → 80ms 短动画让 modal 更快接管视觉
const fast = info && info.mode === 'fast'
this._leave(fast)
});
splashState.onProgress((info) => {
- // 去重: percent/stage 没变化就不 setState, 避免空 re-render
if (info.percent !== this.percent) this.percent = info.percent
- if (info.stage !== this.stage) this.stage = info.stage
- this._showProgressLegacy = true;
});
},
beforeDestroy() {
@@ -165,12 +80,6 @@
}
},
methods: {
- /**
- * 离开 splash
- * - 默认: 480ms fade-out 动画 (视觉柔和过渡)
- * - 当 privacy 弹窗即将出现时: 用 80ms 短动画, 让 modal 更快接管视觉
- * 否则弹窗与 splash 同时显示会有"双遮罩"感
- */
_leave(fast) {
this.leaving = true;
if (this._leaveTimer) clearTimeout(this._leaveTimer);
@@ -185,15 +94,8 @@
+
\ No newline at end of file
From edea8c32de596f51b61243f3a5395c4a0f15aa90 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 10:36:04 +0800
Subject: [PATCH 04/46] =?UTF-8?q?fix(splash-screen):=20=E5=90=AF=E5=8A=A8?=
=?UTF-8?q?=E5=B1=8F=E7=AD=89=E5=BE=85=E9=A6=96=E9=A1=B5=E5=86=85=E5=AE=B9?=
=?UTF-8?q?=E5=B0=B1=E7=BB=AA=E5=90=8E=E5=86=8D=E6=B6=88=E5=A4=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
问题描述: 首次启动时,启动屏已关闭但首页内容还是空白的,用户体验差。
解决方案:
1. 在 splash-state.js 中新增页面就绪等待机制 (_pageReady 标志、notifyPageReady 方法)
2. 修改软隐藏逻辑,必须同时满足组件已 mount 和页面已就绪才关闭启动屏
3. 在首页 onDiyGroupLoaded 回调中通知页面就绪
兜底保护:
- 组件 1s 未 mount → 强制关闭
- 页面 2s 未就绪 → 强制关闭
- 总超时 8s → 强制关闭
---
common/js/splash-state.js | 60 ++++++++++++++++++++++++++++++----
pages/index/public/js/index.js | 4 +++
2 files changed, 58 insertions(+), 6 deletions(-)
diff --git a/common/js/splash-state.js b/common/js/splash-state.js
index 5f66b6f..ebc6360 100644
--- a/common/js/splash-state.js
+++ b/common/js/splash-state.js
@@ -35,6 +35,7 @@ function useNativeLoading() {
const DEFAULT_TIMEOUT = 8000; // 总兜底超时
const MOUNT_GRACE_MS = 1000; // 软隐藏后等组件 mount 的宽限时间
+const PAGE_READY_GRACE_MS = 2000; // 软隐藏后等页面就绪的宽限时间
// 启动期阶段定义 (id → { percent, label })
// percent: 阶段完成的百分比 (0-100)
@@ -56,9 +57,12 @@ class SplashState {
this._listeners = [];
this._shownAt = 0;
// 软隐藏相关
- this._pendingHide = null; // 软隐藏请求的 opts, 等待组件 mount
- this._hasMounted = false; // 是否有 splash 组件 mount 过
- this._mountGraceId = null; // 软隐藏兜底定时器
+ this._pendingHide = null; // 软隐藏请求的 opts, 等待组件 mount
+ this._hasMounted = false; // 是否有 splash 组件 mount 过
+ this._mountGraceId = null; // 软隐藏兜底定时器 (等 mount)
+ // 页面就绪等待
+ this._pageReady = false; // 页面内容是否已就绪 (由首页通知)
+ this._pageReadyGraceId = null; // 页面就绪兜底定时器
// 进度上报
this._stage = 'boot';
this._percent = 0;
@@ -77,6 +81,7 @@ class SplashState {
this._shownAt = Date.now();
this._pendingHide = null;
this._hasMounted = false;
+ this._pageReady = false;
this._stage = 'boot';
// 初始化时直接显示 'boot' 阶段的 5%, 而不是 0%
// 避免首帧出现"进度条 0%"的视觉突兀
@@ -91,6 +96,10 @@ class SplashState {
clearTimeout(this._mountGraceId);
this._mountGraceId = null;
}
+ if (this._pageReadyGraceId) {
+ clearTimeout(this._pageReadyGraceId);
+ this._pageReadyGraceId = null;
+ }
this._emit('splash:show');
// H5 降级: 走系统 loading (Vue 还没挂载,组件层无法渲染)
@@ -138,15 +147,16 @@ class SplashState {
}
/**
- * 软隐藏: 标记应该 hide, 等组件 mount
+ * 软隐藏: 标记应该 hide, 等组件 mount + 页面就绪
* 如果组件 1s 内没 mount, 兜底自动 hide
+ * 如果页面 2s 内没就绪, 兜底自动 hide
*/
_softHide(opts) {
if (!this._visible) return; // 已经隐藏了
this._pendingHide = opts;
- // 如果组件已经 mount, 立即隐藏
- if (this._hasMounted) {
+ // 如果组件已经 mount 且页面已就绪, 立即隐藏
+ if (this._hasMounted && this._pageReady) {
this._flushPendingHide();
return;
}
@@ -162,6 +172,7 @@ class SplashState {
/**
* 真正触发软隐藏
+ * 如果页面还未就绪, 等待页面就绪后再 hide
*/
_flushPendingHide() {
const opts = this._pendingHide;
@@ -170,6 +181,20 @@ class SplashState {
clearTimeout(this._mountGraceId);
this._mountGraceId = null;
}
+
+ // 如果页面还未就绪, 等待页面就绪
+ if (!this._pageReady) {
+ // 兜底: 2s 内没就绪, 强制隐藏
+ if (this._pageReadyGraceId) clearTimeout(this._pageReadyGraceId);
+ this._pageReadyGraceId = setTimeout(() => {
+ if (this._visible) {
+ console.warn('[splash-state] auto-hide after page-ready timeout');
+ this._doHide(opts || { reason: 'page-ready-timeout' });
+ }
+ }, PAGE_READY_GRACE_MS);
+ return;
+ }
+
this._doHide(opts || { reason: 'mount-completed' });
}
@@ -186,6 +211,10 @@ class SplashState {
clearTimeout(this._mountGraceId);
this._mountGraceId = null;
}
+ if (this._pageReadyGraceId) {
+ clearTimeout(this._pageReadyGraceId);
+ this._pageReadyGraceId = null;
+ }
this._pendingHide = null;
this._visible = false;
// H5 降级对应的关闭
@@ -217,6 +246,25 @@ class SplashState {
}
}
+ /**
+ * 页面内容就绪后通知 state 模块
+ * - 标记 _pageReady
+ * - 如果有 _pendingHide, 触发真关
+ * 调用时机: 首页 diy-group 渲染完成后调用
+ */
+ notifyPageReady() {
+ if (this._pageReady) return;
+ this._pageReady = true;
+ if (this._pageReadyGraceId) {
+ clearTimeout(this._pageReadyGraceId);
+ this._pageReadyGraceId = null;
+ }
+ if (this._pendingHide) {
+ // 给页面一帧时间完成渲染, 再隐藏
+ setTimeout(() => this._flushPendingHide(), 50);
+ }
+ }
+
/**
* 组件 unmount 时通知 (用于 _hasMounted 状态管理)
* 目前保留 _hasMounted=true, 因为一般只有 tab 页有 splash 组件, 不会动态增减
diff --git a/pages/index/public/js/index.js b/pages/index/public/js/index.js
index 61041ba..3fef1eb 100644
--- a/pages/index/public/js/index.js
+++ b/pages/index/public/js/index.js
@@ -1,3 +1,5 @@
+import splashState from '@/common/js/splash-state.js';
+
export default {
data() {
return {
@@ -124,8 +126,10 @@ export default {
return false;
},
// diy-group 渲染完毕回调, 用于触发版本水印等延后显示元素
+ // 同时通知 splash-state 页面就绪, 启动屏可以关闭
onDiyGroupLoaded() {
this.diyGroupReady = true;
+ try { splashState.notifyPageReady(); } catch (e) {}
},
/**
* 版本连击 3 次以上显示构建版本信息(供测试人员使用)
From 6b8b4acc96b3c8c6384a181f2156cff1a093d3bb Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 10:43:47 +0800
Subject: [PATCH 05/46] =?UTF-8?q?fix(splash-state):=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=B0=B1=E7=BB=AA=E5=90=8E=E6=97=A0=E6=B3=95?=
=?UTF-8?q?=E5=85=B3=E9=97=AD=E5=90=AF=E5=8A=A8=E5=B1=8F=E7=9A=84=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
问题根源: _flushPendingHide() 中过早清除了 _pendingHide,导致页面就绪后 notifyPageReady() 检测不到 pending hide 请求,启动屏无法关闭。
修复方案: 当页面未就绪时,保留 _pendingHide 直到页面真正就绪后再清除。
---
common/js/splash-state.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/common/js/splash-state.js b/common/js/splash-state.js
index ebc6360..10fb2aa 100644
--- a/common/js/splash-state.js
+++ b/common/js/splash-state.js
@@ -175,26 +175,26 @@ class SplashState {
* 如果页面还未就绪, 等待页面就绪后再 hide
*/
_flushPendingHide() {
- const opts = this._pendingHide;
- this._pendingHide = null;
if (this._mountGraceId) {
clearTimeout(this._mountGraceId);
this._mountGraceId = null;
}
- // 如果页面还未就绪, 等待页面就绪
+ // 如果页面还未就绪, 等待页面就绪 (保留 _pendingHide, 等页面就绪后再处理)
if (!this._pageReady) {
// 兜底: 2s 内没就绪, 强制隐藏
if (this._pageReadyGraceId) clearTimeout(this._pageReadyGraceId);
this._pageReadyGraceId = setTimeout(() => {
- if (this._visible) {
+ if (this._visible && this._pendingHide) {
console.warn('[splash-state] auto-hide after page-ready timeout');
- this._doHide(opts || { reason: 'page-ready-timeout' });
+ this._doHide(this._pendingHide || { reason: 'page-ready-timeout' });
}
}, PAGE_READY_GRACE_MS);
return;
}
+ const opts = this._pendingHide;
+ this._pendingHide = null;
this._doHide(opts || { reason: 'mount-completed' });
}
From 88921e61d22e29a0211e7722c49fa039f4750b64 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 10:54:17 +0800
Subject: [PATCH 06/46] =?UTF-8?q?fix(splash-screen):=20=E4=BF=AE=E5=A4=8DH?=
=?UTF-8?q?5=E7=8E=AF=E5=A2=83=E4=B8=8B=E5=90=AF=E5=8A=A8=E5=B1=8F?=
=?UTF-8?q?=E5=85=B3=E9=97=AD=E5=90=8E=E9=A1=B5=E9=9D=A2=E7=A9=BA=E7=99=BD?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
问题根源分析:
1. diy-group 的 mounted() 在组件挂载时就触发了,但此时 diyData 还没有数据
2.
otifyMounted() 在组件 mount 时立即关闭了 H5 的 uni.showLoading(),但页面内容还没就绪
3. 导致启动屏关闭后,首页内容还是空白的
修复方案:
1. 在 diy-group.vue 中,将 loaded 事件的触发时机改为 diyData watch 中,确保数据加载完成后再通知
2. 在 splash-state.js 中,
otifyMounted() 不再立即关闭 H5 原生 loading,等待页面就绪后由 _doHide() 统一关闭
3. 在 index.js 中,onDiyGroupLoaded() 延迟 500ms 再通知页面就绪,确保内容渲染完成
---
common/js/splash-state.js | 7 +------
components-diy/diy-group.vue | 13 ++++++++++---
pages/index/public/js/index.js | 5 ++++-
3 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/common/js/splash-state.js b/common/js/splash-state.js
index 10fb2aa..b34a6d9 100644
--- a/common/js/splash-state.js
+++ b/common/js/splash-state.js
@@ -231,17 +231,12 @@ class SplashState {
* 组件 mount 后通知 state 模块
* - 标记 _hasMounted
* - 如果有 _pendingHide, 触发真关
- * - H5 降级场景: 立即关掉原生 loading, 让 splash 组件接管视觉
+ * - H5 降级场景: 不再立即关掉原生 loading, 等待页面就绪后再关
*/
notifyMounted() {
if (this._hasMounted) return;
this._hasMounted = true;
- // H5 降级: 立即关掉原生 loading, 避免和 splash 组件重叠
- if (useNativeLoading()) {
- try { uni.hideLoading(); } catch (e) {}
- }
if (this._pendingHide) {
- // 给组件一帧时间渲染, 再隐藏 (避免一帧闪烁)
setTimeout(() => this._flushPendingHide(), 50);
}
}
diff --git a/components-diy/diy-group.vue b/components-diy/diy-group.vue
index 5794df5..edc192d 100644
--- a/components-diy/diy-group.vue
+++ b/components-diy/diy-group.vue
@@ -300,6 +300,7 @@ export default {
handler() {
this.diyGlobalData = this.diyData ? this.$util.deepClone(this.diyData) : null;
this.processData();
+ this._notifyLoaded();
},
deep: true
}
@@ -308,8 +309,9 @@ export default {
this.processData();
},
mounted() {
- // 通知父组件 diy-group 内容已渲染完毕
- this.$emit('loaded');
+ if (this.diyData && this.diyData.value) {
+ this._notifyLoaded();
+ }
},
computed: {
topNavColor() {
@@ -323,7 +325,6 @@ export default {
return topNavColor;
},
- // 过滤组件的渲染
diyDataArray() {
if (!this.processedData) {
this.processData();
@@ -347,6 +348,12 @@ export default {
}
},
methods: {
+ _notifyLoaded() {
+ if (this._loadedTimer) clearTimeout(this._loadedTimer);
+ this._loadedTimer = setTimeout(() => {
+ this.$emit('loaded');
+ }, 300);
+ },
processData() {
if (!this.diyGlobalData || !this.diyGlobalData.value) {
this.processedData = [];
diff --git a/pages/index/public/js/index.js b/pages/index/public/js/index.js
index 3fef1eb..b15c395 100644
--- a/pages/index/public/js/index.js
+++ b/pages/index/public/js/index.js
@@ -127,9 +127,12 @@ export default {
},
// diy-group 渲染完毕回调, 用于触发版本水印等延后显示元素
// 同时通知 splash-state 页面就绪, 启动屏可以关闭
+ // 延迟 500ms 是为了确保页面内容有足够时间渲染
onDiyGroupLoaded() {
this.diyGroupReady = true;
- try { splashState.notifyPageReady(); } catch (e) {}
+ setTimeout(() => {
+ try { splashState.notifyPageReady(); } catch (e) {}
+ }, 500);
},
/**
* 版本连击 3 次以上显示构建版本信息(供测试人员使用)
From c739d691a2a2ef3d9e17426a45a8ca9d479cf211 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 14:11:14 +0800
Subject: [PATCH 07/46] =?UTF-8?q?fix(diy-video):=20=E9=80=82=E9=85=8D?=
=?UTF-8?q?=E9=B8=BF=E8=92=99=E5=85=83=E6=9C=8D=E5=8A=A1=E7=AB=AF=E8=A7=86?=
=?UTF-8?q?=E9=A2=91=E7=BB=84=E4=BB=B6=E5=B1=95=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 新增鸿蒙平台专属video组件渲染分支
2. 新增窗口宽度初始化逻辑用于计算鸿蒙视频高度
3. 针对不同平台调整视频容器样式计算逻辑
4. 补充视频加载元数据和错误回调
5. 修复微信小程序视频比例适配逻辑
---
components-diy/diy-video.vue | 84 +++++++++++++++++++++++++++++++++---
1 file changed, 79 insertions(+), 5 deletions(-)
diff --git a/components-diy/diy-video.vue b/components-diy/diy-video.vue
index 51a1cab..28a4012 100644
--- a/components-diy/diy-video.vue
+++ b/components-diy/diy-video.vue
@@ -26,6 +26,23 @@
:poster="posterUrl"
:style="videoWarpCss"
controls
+ object-fit="cover"
+ @loadedmetadata="onVideoLoaded"
+ @error="onVideoError"
+ >
+
+
+
+
+
@@ -45,13 +62,15 @@ export default {
},
data() {
return {
- videoRatio: null, // 视频比例:'landscape'(横屏)或 'portrait'(竖屏)
+ videoRatio: null,
videoUrl: '',
- posterUrl: ''
+ posterUrl: '',
+ windowWidth: 0
};
},
created() {
this.initVideoData();
+ this.initWindowWidth();
},
mixins: [DiyMinx],
watch: {
@@ -79,18 +98,43 @@ export default {
containerStyle() {
let style = '';
+ // #ifdef H5
if (this.videoRatio === 'landscape') {
- // 横屏视频:16/9比例
style += 'aspect-ratio: 16 / 9;';
} else if (this.videoRatio === 'portrait') {
- // 竖屏视频:9/16比例
style += 'aspect-ratio: 9 / 16;';
} else {
- // 未加载完成 → 默认使用竖屏比例
style += 'aspect-ratio: 9 / 16;';
}
+ // #endif
+
+ // #ifdef MP-WEIXIN
+ if (this.videoRatio === 'landscape') {
+ style += 'padding-top: 56.25%;';
+ } else if (this.videoRatio === 'portrait') {
+ style += 'padding-top: 177.78%;';
+ } else {
+ style += 'padding-top: 177.78%;';
+ }
+ // #endif
+
+ // #ifdef MP-HARMONY
+ style += 'width: 100%;';
+ style += 'height: ' + this.harmonyVideoHeight + 'px;';
+ // #endif
return style;
+ },
+
+ // 鸿蒙平台视频高度
+ harmonyVideoHeight() {
+ if (!this.videoRatio) {
+ return (this.windowWidth * 9) / 16;
+ }
+ if (this.videoRatio === 'landscape') {
+ return (this.windowWidth * 9) / 16;
+ }
+ return (this.windowWidth * 16) / 9;
}
},
methods: {
@@ -104,6 +148,16 @@ export default {
// #endif
},
+ // 初始化窗口宽度
+ initWindowWidth() {
+ try {
+ const systemInfo = uni.getSystemInfoSync();
+ this.windowWidth = systemInfo.windowWidth || 375;
+ } catch (e) {
+ this.windowWidth = 375;
+ }
+ },
+
// 获取视频URL
getVideoUrl() {
if (!this.value || !this.value.videoUrl) return '';
@@ -169,6 +223,13 @@ export default {
}
// #endif
+ // #ifdef MP-HARMONY
+ if (event && event.detail) {
+ width = event.detail.width || 0;
+ height = event.detail.height || 0;
+ }
+ // #endif
+
if (width && height) {
this.videoRatio = width >= height ? 'landscape' : 'portrait';
}
@@ -225,6 +286,19 @@ export default {
.mp-video {
width: 100%;
height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+/* #endif */
+
+/* 鸿蒙元服务端样式 */
+/* #ifdef MP-HARMONY */
+.harmony-video {
+ width: 100%;
+ height: 100%;
+ display: block;
+ box-sizing: border-box;
}
/* #endif */
From 10bc06f212dbc76c84dcf795a5f4bc41d63a6143 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 14:24:09 +0800
Subject: [PATCH 08/46] =?UTF-8?q?refactor(components/diy-video):=20?=
=?UTF-8?q?=E9=87=8D=E6=9E=84=E8=A7=86=E9=A2=91=E6=AF=94=E4=BE=8B=E8=AE=A1?=
=?UTF-8?q?=E7=AE=97=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
替换原有的固定比例方案,改为通过视频实际宽高动态计算适配比例,移除冗余的harmonyVideoHeight计算方法,统一多端样式处理逻辑,优化鸿蒙端视频高度计算方式
---
components-diy/diy-video.vue | 61 +++++++++++++++---------------------
1 file changed, 26 insertions(+), 35 deletions(-)
diff --git a/components-diy/diy-video.vue b/components-diy/diy-video.vue
index 28a4012..f770f7a 100644
--- a/components-diy/diy-video.vue
+++ b/components-diy/diy-video.vue
@@ -61,13 +61,14 @@ export default {
}
},
data() {
- return {
- videoRatio: null,
- videoUrl: '',
- posterUrl: '',
- windowWidth: 0
- };
- },
+ return {
+ videoWidth: 0,
+ videoHeight: 0,
+ videoUrl: '',
+ posterUrl: '',
+ windowWidth: 0
+ };
+ },
created() {
this.initVideoData();
this.initWindowWidth();
@@ -97,44 +98,27 @@ export default {
// 动态计算容器比例
containerStyle() {
let style = '';
+ const defaultRatio = 9 / 16;
+
+ let ratio = defaultRatio;
+ if (this.videoWidth && this.videoHeight) {
+ ratio = this.videoHeight / this.videoWidth;
+ }
// #ifdef H5
- if (this.videoRatio === 'landscape') {
- style += 'aspect-ratio: 16 / 9;';
- } else if (this.videoRatio === 'portrait') {
- style += 'aspect-ratio: 9 / 16;';
- } else {
- style += 'aspect-ratio: 9 / 16;';
- }
+ style += 'padding-top: ' + (ratio * 100) + '%;';
// #endif
// #ifdef MP-WEIXIN
- if (this.videoRatio === 'landscape') {
- style += 'padding-top: 56.25%;';
- } else if (this.videoRatio === 'portrait') {
- style += 'padding-top: 177.78%;';
- } else {
- style += 'padding-top: 177.78%;';
- }
+ style += 'padding-top: ' + (ratio * 100) + '%;';
// #endif
// #ifdef MP-HARMONY
style += 'width: 100%;';
- style += 'height: ' + this.harmonyVideoHeight + 'px;';
+ style += 'height: ' + (this.windowWidth * ratio) + 'px;';
// #endif
return style;
- },
-
- // 鸿蒙平台视频高度
- harmonyVideoHeight() {
- if (!this.videoRatio) {
- return (this.windowWidth * 9) / 16;
- }
- if (this.videoRatio === 'landscape') {
- return (this.windowWidth * 9) / 16;
- }
- return (this.windowWidth * 16) / 9;
}
},
methods: {
@@ -192,7 +176,8 @@ export default {
const height = video.videoHeight;
if (width && height) {
- this.videoRatio = width >= height ? 'landscape' : 'portrait';
+ this.videoWidth = width;
+ this.videoHeight = height;
}
});
@@ -231,7 +216,8 @@ export default {
// #endif
if (width && height) {
- this.videoRatio = width >= height ? 'landscape' : 'portrait';
+ this.videoWidth = width;
+ this.videoHeight = height;
}
},
@@ -277,6 +263,11 @@ export default {
/* H5端样式 */
/* #ifdef H5 */
.h5-video {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
object-fit: cover;
}
/* #endif */
From cb9234bdac5b13240d896f2b30c0c86ce2579090 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 14:41:29 +0800
Subject: [PATCH 09/46] =?UTF-8?q?refactor(diy-video-list):=20=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E5=BC=B9=E7=AA=97=E8=A7=86=E9=A2=91=E9=80=82=E9=85=8D?=
=?UTF-8?q?=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 重构视频比例计算逻辑,改为通过实际宽高计算适配
2. 新增多端适配:H5、微信小程序、鸿蒙元服务
3. 添加视频加载错误捕获与日志输出
4. 调整视频容器样式,统一背景与溢出处理
5. 移除旧的比例状态变量,改用宽高状态存储
---
components-diy/diy-video-list.vue | 104 ++++++++++++++++++++++--------
1 file changed, 78 insertions(+), 26 deletions(-)
diff --git a/components-diy/diy-video-list.vue b/components-diy/diy-video-list.vue
index 53a8f06..2b651f2 100644
--- a/components-diy/diy-video-list.vue
+++ b/components-diy/diy-video-list.vue
@@ -64,7 +64,9 @@
id="myVideo"
:src="video_url"
:controls="true"
+ object-fit="contain"
@loadedmetadata="onPopupVideoLoaded"
+ @error="onPopupVideoError"
>
@@ -88,11 +90,14 @@ export default {
pageWidth: '',
indicatorDots: false,
swiperCurrent: 0,
- video_url: ''
+ video_url: '',
+ popupVideoWidth: 0,
+ popupVideoHeight: 0,
+ windowWidth: 0
}
},
created() {
- // 组件创建时的逻辑
+ this.initWindowWidth();
},
watch: {
componentRefresh(newValue) {
@@ -140,24 +145,33 @@ export default {
// 弹窗视频容器样式
popupVideoStyle() {
let style = 'position:fixed;width:100%;left:0;';
-
- if (this.popupVideoRatio) {
- // 根据视频比例设置位置
- if (this.popupVideoRatio === 'portrait') {
- // 竖屏视频:居中显示,使用9/16比例
- style += 'top:50%;transform:translateY(-50%);';
- style += 'aspect-ratio: 9 / 16;';
- } else {
- // 横屏视频:靠近顶部显示,使用16/9比例
- style += 'top:20%;';
- style += 'aspect-ratio: 16 / 9;';
- }
- } else {
- // 默认:居中显示,使用竖屏比例
- style += 'top:50%;transform:translateY(-50%);';
- style += 'aspect-ratio: 9 / 16;';
+ const defaultRatio = 9 / 16;
+
+ let ratio = defaultRatio;
+ if (this.popupVideoWidth && this.popupVideoHeight) {
+ ratio = this.popupVideoHeight / this.popupVideoWidth;
}
-
+
+ const isPortrait = ratio > 1;
+
+ if (isPortrait) {
+ style += 'top:50%;transform:translateY(-50%);';
+ } else {
+ style += 'top:20%;';
+ }
+
+ // #ifdef H5
+ style += 'padding-top: ' + (ratio * 100) + '%;';
+ // #endif
+
+ // #ifdef MP-WEIXIN
+ style += 'padding-top: ' + (ratio * 100) + '%;';
+ // #endif
+
+ // #ifdef MP-HARMONY
+ style += 'height: ' + (this.windowWidth * ratio) + 'px;';
+ // #endif
+
return style;
}
},
@@ -165,8 +179,8 @@ export default {
// 显示视频播放弹窗
showVideo(item) {
this.video_url = item.videoUrl;
- // 重置视频比例为null,等待新的检测结果
- this.popupVideoRatio = null;
+ this.popupVideoWidth = 0;
+ this.popupVideoHeight = 0;
this.$refs.videoPopup.open();
},
@@ -174,6 +188,16 @@ export default {
imgError(index) {
// 图片加载失败的处理逻辑
},
+
+ // 初始化窗口宽度
+ initWindowWidth() {
+ try {
+ const systemInfo = uni.getSystemInfoSync();
+ this.windowWidth = systemInfo.windowWidth || 375;
+ } catch (e) {
+ this.windowWidth = 375;
+ }
+ },
// 弹窗视频加载完成事件
onPopupVideoLoaded(event) {
@@ -198,13 +222,24 @@ export default {
}
// #endif
+ // 鸿蒙元服务端:通过事件对象获取视频宽高
+ // #ifdef MP-HARMONY
+ if (event && event.detail) {
+ width = event.detail.width || 0;
+ height = event.detail.height || 0;
+ }
+ // #endif
+
if (width && height) {
- this.popupVideoRatio = width >= height ? 'landscape' : 'portrait';
- } else {
- // 获取失败,默认竖屏
- this.popupVideoRatio = 'portrait';
+ this.popupVideoWidth = width;
+ this.popupVideoHeight = height;
}
});
+ },
+
+ // 弹窗视频错误事件
+ onPopupVideoError(event) {
+ console.error('Popup video load failed:', event);
}
}
}
@@ -576,8 +611,9 @@ export default {
.video-container {
width: 100%;
- height: 300px;
position: relative;
+ overflow: hidden;
+ background-color: #000;
}
.adaptive-video {
@@ -589,6 +625,22 @@ export default {
left: 0;
}
+/* #ifdef MP-WEIXIN */
+.adaptive-video {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+/* #endif */
+
+/* #ifdef MP-HARMONY */
+.adaptive-video {
+ position: static;
+ display: block;
+ box-sizing: border-box;
+}
+/* #endif */
+
.video-nav {
padding: 16rpx;
box-sizing: border-box;
From 30cda717493af910430834223d990a9ab58301ea Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 15:45:15 +0800
Subject: [PATCH 10/46] =?UTF-8?q?refactor(http,lang):=20=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E8=AF=B7=E6=B1=82=E9=80=BB=E8=BE=91=E5=B9=B6=E6=96=B0=E5=A2=9E?=
=?UTF-8?q?=E5=A4=9A=E8=AF=AD=E8=A8=80=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 新增网络相关错误的多语言翻译配置
2. 重写http请求层,添加自动重试机制与错误分类提示
3. 优化鸿蒙平台下的组件渲染兼容性与数据更新逻辑
4. 调整部分页面的模板结构与请求配置
---
common/js/diy.js | 6 +-
common/js/http.js | 257 ++++++++++++++-------------
components/ns-payment/ns-payment.vue | 2 +
lang/en-us/common.js | 10 ++
lang/zh-cn/common.js | 10 ++
pages/index/index.vue | 40 ++---
pages_goods/category.vue | 56 ++++--
pages_order/public/js/orderMethod.js | 10 ++
8 files changed, 234 insertions(+), 157 deletions(-)
diff --git a/common/js/diy.js b/common/js/diy.js
index a11a5cd..87149b5 100644
--- a/common/js/diy.js
+++ b/common/js/diy.js
@@ -352,14 +352,16 @@ export default {
this.$util.showToast({
title: res.message
});
- this.diyData = {};
+ // 保持原有数据结构,仅清空 value,避免鸿蒙下重新布局
+ this.diyData = { ...(this.diyData || {}), value: [] };
return;
}
this.$util.showToast({
title: this.$lang('diy.noDiyData')
});
- this.diyData = {};
+ // 保持原有数据结构,仅清空 value,避免鸿蒙下重新布局
+ this.diyData = { ...(this.diyData || {}), value: [] };
return;
}
diff --git a/common/js/http.js b/common/js/http.js
index df855fe..0ad0506 100644
--- a/common/js/http.js
+++ b/common/js/http.js
@@ -5,7 +5,6 @@ import Platform from './platform.js'
const { appType, appTypeName } = Platform.getPlatformInfo();
-// 惰性加载 Lang,避免与 lang.js 的循环依赖
let _Lang = null;
function getLang() {
if (!_Lang) {
@@ -14,6 +13,116 @@ function getLang() {
return _Lang;
}
+const DEFAULT_TIMEOUT = 15000;
+const DEFAULT_MAX_RETRIES = 2;
+const RETRY_DELAY_BASE = 2000;
+
+function _shouldRetry(errMsg) {
+ if (!errMsg) return false;
+ if (errMsg === 'request:fail url not in domain list') return false;
+ if (errMsg === 'request:fail abort') return false;
+ const retryableErrors = [
+ 'request:fail timeout',
+ 'request:fail connect',
+ 'request:fail network'
+ ];
+ return retryableErrors.some(err => errMsg.startsWith(err));
+}
+
+function _classifyError(errMsg, statusCode) {
+ if (!errMsg && !statusCode) return 'unknown';
+ if (errMsg === 'request:fail abort') return 'aborted';
+ if (errMsg && errMsg.indexOf('timeout') !== -1) return 'timeout';
+ if (errMsg && (errMsg.indexOf('connect') !== -1 || errMsg.indexOf('network') !== -1)) return 'networkError';
+ if (statusCode && statusCode >= 500) return 'serverError';
+ if (errMsg && errMsg.indexOf('url not in domain list') !== -1) return 'domainNotInList';
+ return 'unknown';
+}
+
+function _sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+function _makeRequest(url, method, data, header, dataType, responseType, timeout) {
+ return new Promise((resolve, reject) => {
+ uni.request({
+ url: url,
+ method: method,
+ data: data,
+ header: header,
+ dataType: dataType,
+ responseType: responseType,
+ timeout: timeout,
+ success: (res) => {
+ if (res.statusCode && [200, 500].indexOf(res.statusCode) == -1) {
+ reject({ errMsg: `request:fail statusCode=${res.statusCode}`, statusCode: res.statusCode });
+ return;
+ }
+ if (res.data.code == -3 && store.state.siteState > 0) {
+ store.commit('setSiteState', -3);
+ Util.redirectTo('/pages_tool/storeclose/storeclose', {}, 'reLaunch');
+ return;
+ }
+ if (res.data.refreshtoken) {
+ store.commit('setToken', res.data.refreshtoken);
+ }
+ if (res.data.code == -10009 || res.data.code == -10010) {
+ store.commit('setToken', '');
+ store.commit('setMemberInfo', '');
+ }
+ resolve(res.data);
+ },
+ fail: (res) => {
+ reject(res);
+ }
+ });
+ });
+}
+
+async function _requestWithRetry(params, url, method, data, header, dataType, responseType) {
+ const timeout = params.timeout || DEFAULT_TIMEOUT;
+ const maxRetries = params.noRetry ? 0 : (params.maxRetries || DEFAULT_MAX_RETRIES);
+ let lastError = null;
+ let lastStatusCode = null;
+
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
+ try {
+ const result = await _makeRequest(url, method, data, header, dataType, responseType, timeout);
+ return result;
+ } catch (err) {
+ lastError = err;
+ lastStatusCode = err && err.statusCode;
+ if (attempt >= maxRetries || !_shouldRetry(err.errMsg)) {
+ break;
+ }
+ const delay = RETRY_DELAY_BASE * Math.pow(2, attempt);
+ await _sleep(delay);
+ }
+ }
+
+ if (params.silentError) {
+ throw lastError;
+ }
+
+ const errorType = _classifyError(lastError && lastError.errMsg, lastStatusCode);
+ const lang = getLang();
+ if (errorType === 'domainNotInList') {
+ uni.showToast({
+ title: lang.lang('http.domainNotInList', { url: Config.baseUrl }),
+ 'icon': 'none',
+ duration: 10000
+ });
+ } else {
+ const i18nKey = 'http.' + errorType;
+ uni.showToast({
+ title: lang.lang(i18nKey),
+ 'icon': 'none',
+ duration: 10000
+ });
+ }
+ throw lastError;
+}
+
export default {
sendRequest(params) {
if (!Config.baseUrl) {
@@ -25,13 +134,9 @@ export default {
return;
}
- // remote-only boot 门控: 所有业务请求必须等国际化数据就绪后才发送
- // 这样业务请求返回时 lang() 已有翻译数据,不会造成 UI 污染
- // bundle 请求走 lang-loader.js 的 uni.request 直接发送,不受此门控影响
const _isRemoteOnly = getLang().isRemoteOnly && getLang().isRemoteOnly();
const _bootDone = getLang().isBootCompleted && getLang().isBootCompleted();
if (_isRemoteOnly && !_bootDone) {
- // 获取 boot promise(优先从 App 实例拿,避免重复 boot)
let bootPromise = null;
try {
const app = getApp();
@@ -40,10 +145,8 @@ export default {
if (!bootPromise) bootPromise = getLang().boot();
if (params.async === false) {
- // 同步 Promise 模式: 链式等待 boot 后再发请求
return bootPromise.then(() => this._sendRequestNow(params));
} else {
- // 回调模式: boot 完成后再执行请求
bootPromise.then(() => this._sendRequestNow(params));
return;
}
@@ -61,11 +164,9 @@ export default {
app_type_name: appTypeName
};
- // token
data.token = store.state.token || '';
- data.uniacid = uni.getStorageSync('uniacid') || Config.uniacid; // 从缓存中获取uniacid,或使用默认uniacid,支持快应用
+ data.uniacid = uni.getStorageSync('uniacid') || Config.uniacid;
- // 门店id
var default_store_info = store.state.defaultStoreInfo;
if (default_store_info) {
data.store_id = default_store_info.store_id;
@@ -75,122 +176,34 @@ export default {
if (store_info) data.store_id = store_info.store_id;
- // 参数
if (params.data != undefined) Object.assign(data, params.data);
+ const header = params.header || {
+ 'content-type': 'application/x-www-form-urlencoded;application/json'
+ };
+ const dataType = params.dataType || 'json';
+ const responseType = params.responseType || 'text';
+
if (params.async === false) {
- //同步
- return new Promise((resolve, reject) => {
- uni.request({
- url: url,
- method: method,
- data: data,
- header: params.header || {
- // 'Accept': 'application/json',
- 'content-type': 'application/x-www-form-urlencoded;application/json'
- },
- dataType: params.dataType || 'json',
- responseType: params.responseType || 'text',
- success: (res) => {
- // try {
- // res.data = JSON.parse(res.data);
- // } catch (e) {
- // //TODO handle the exception
- // console.log('api error:', e);
- // }
- if (res.data.code == -3 && store.state.siteState > 0) {
- store.commit('setSiteState', -3);
- Util.redirectTo('/pages_tool/storeclose/storeclose', {}, 'reLaunch');
- return;
- }
- if (res.data.refreshtoken) {
- store.commit('setToken', res.data.refreshtoken);
- }
- if (res.data.code == -10009 || res.data.code == -10010) {
- store.commit('setToken', '');
- store.commit('setMemberInfo', '');
- }
- resolve(res.data);
- },
- fail: (res) => {
- if (res.errMsg && res.errMsg == 'request:fail url not in domain list') {
- uni.showToast({
- title: getLang().lang('http.domainNotInList', { url: Config.baseUrl }),
- 'icon': 'none',
- duration: 10000
- });
- return;
- }
- reject(res);
- },
- complete: (res) => {
- // success/fail 已处理 resolve/reject,complete 只负责兜底提示非预期状态
- if ((res.errMsg && res.errMsg != "request:ok") || (res.statusCode && [200, 500].indexOf(res.statusCode) == -1)) {
- uni.showToast({
- title: getLang().lang('http.requestFailed', { url: Config.baseUrl }),
- 'icon': 'none',
- duration: 10000
- });
- }
- }
+ return _requestWithRetry(params, url, method, data, header, dataType, responseType)
+ .then(res => {
+ typeof params.complete == 'function' && params.complete(res);
+ return res;
+ })
+ .catch(err => {
+ typeof params.complete == 'function' && params.complete();
+ throw err;
});
- });
} else {
- //异步
- uni.request({
- url: url,
- method: method,
- data: data,
- header: params.header || {
- // 'Accept': 'application/json',
- 'content-type': 'application/x-www-form-urlencoded;application/json'
- },
- dataType: params.dataType || 'json',
- responseType: params.responseType || 'text',
- success: (res) => {
- // try {
- // res.data = JSON.parse(res.data);
- // } catch (e) {
- // //TODO handle the exception
- // console.log('api error:', e);
- // }
- if (res.data.code == -3 && store.state.siteState > 0) {
- store.commit('setSiteState', -3);
- Util.redirectTo('/pages_tool/storeclose/storeclose', {}, 'reLaunch');
- return;
- }
- if (res.data.refreshtoken) {
- store.commit('setToken', res.data.refreshtoken);
- }
- if (res.data.code == -10009 || res.data.code == -10010) {
- store.commit('setToken', '');
- store.commit('setMemberInfo', '');
- }
- typeof params.success == 'function' && params.success(res.data);
- },
- fail: (res) => {
- if (res.errMsg && res.errMsg == 'request:fail url not in domain list') {
- uni.showToast({
- title: getLang().lang('http.domainNotInList', { url: Config.baseUrl, err: res.errMsg }),
- 'icon': 'none',
- duration: 10000
- });
- return;
- }
- typeof params.fail == 'function' && params.fail(res);
- },
- complete: (res) => {
- if ((res.errMsg && res.errMsg != "request:ok") || (res.statusCode && [200, 500].indexOf(res.statusCode) == -1)) {
- uni.showToast({
- title: getLang().lang('http.requestFailed', { url: Config.baseUrl, err: res.errMsg }),
- 'icon': 'none',
- duration: 10000
- });
- return;
- }
- typeof params.complete == 'function' && params.complete(res.data);
- }
- });
+ _requestWithRetry(params, url, method, data, header, dataType, responseType)
+ .then(res => {
+ typeof params.success == 'function' && params.success(res);
+ typeof params.complete == 'function' && params.complete(res);
+ })
+ .catch(err => {
+ typeof params.fail == 'function' && params.fail(err);
+ typeof params.complete == 'function' && params.complete();
+ });
}
},
uploadBase64(params) {
@@ -258,4 +271,4 @@ export default {
}
});
}
-}
+}
\ No newline at end of file
diff --git a/components/ns-payment/ns-payment.vue b/components/ns-payment/ns-payment.vue
index d34ce7c..2766da8 100644
--- a/components/ns-payment/ns-payment.vue
+++ b/components/ns-payment/ns-payment.vue
@@ -231,6 +231,7 @@ export default {
}
this.$api.sendRequest({
url: '/api/pay/pay',
+ noRetry: true,
data: {
out_trade_no: this.payInfo.out_trade_no,
pay_type: payType.type,
@@ -350,6 +351,7 @@ export default {
if (!payType) return;
this.$api.sendRequest({
url: '/api/pay/pay',
+ noRetry: true,
data: {
out_trade_no: this.payInfo.out_trade_no,
pay_type: payType.type,
diff --git a/lang/en-us/common.js b/lang/en-us/common.js
index e586268..7f07cb3 100644
--- a/lang/en-us/common.js
+++ b/lang/en-us/common.js
@@ -132,6 +132,16 @@ export const lang = {
domainNotInList: '{url} is not in the request domain whitelist',
// request failed: {url} is the domain placeholder
requestFailed: '{url} request failed. {err}',
+ // network timeout
+ timeout: 'Network request timeout, please try again later',
+ // network connection failed
+ networkError: 'Network connection failed, please check your network',
+ // server error
+ serverError: 'Service temporarily unavailable, please try again later',
+ // request aborted
+ aborted: 'Request cancelled',
+ // unknown error
+ unknownError: 'Request failed, please try again later',
},
// share utility
diff --git a/lang/zh-cn/common.js b/lang/zh-cn/common.js
index dc563fa..31e6665 100644
--- a/lang/zh-cn/common.js
+++ b/lang/zh-cn/common.js
@@ -132,6 +132,16 @@ export const lang = {
domainNotInList: '{url}不在request 合法域名列表中',
// 请求失败: {url} 为域名占位符
requestFailed: '{url}请求失败. {err}',
+ // 网络超时
+ timeout: '网络请求超时,请稍后重试',
+ // 网络连接失败
+ networkError: '网络连接失败,请检查网络设置',
+ // 服务器错误
+ serverError: '服务暂时不可用,请稍后重试',
+ // 请求被中断
+ aborted: '请求已取消',
+ // 未知错误
+ unknownError: '请求失败,请稍后重试',
},
// share 分享工具
diff --git a/pages/index/index.vue b/pages/index/index.vue
index 7223fb6..14adb33 100644
--- a/pages/index/index.vue
+++ b/pages/index/index.vue
@@ -21,28 +21,28 @@
-->
-
-
-
-
-
-
-
-
-
-
-
-
- {{ versionWatermark }}
-
-
+ :scrollTop="scrollTop" :diyGlobal="diyData.global" class="diy-index-page">
+
+
+
+
+
+
+
+
+
+
+
+ {{ versionWatermark }}
+
+
+
diff --git a/pages_goods/category.vue b/pages_goods/category.vue
index ff370db..dd813d7 100644
--- a/pages_goods/category.vue
+++ b/pages_goods/category.vue
@@ -1,12 +1,12 @@
-
+
-
+
@@ -26,7 +26,7 @@
export default {
data() {
return {
- diyData: null,
+ diyData: { value: [] },
mpShareData: null //小程序分享数据
};
},
@@ -38,8 +38,15 @@ export default {
},
onShow() {
this.$util.hideHomeButton();
- if (this.$refs.category) this.$refs.category[0].pageShow();
-
+ try {
+ // 鸿蒙元服务 JSVM 下 $refs.category 可能为 undefined, 需防御
+ if (this.$refs.category && this.$refs.category[0]) {
+ this.$refs.category[0].pageShow();
+ }
+ } catch (e) {
+ console.warn('onShow pageShow failed', e);
+ }
+
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare('/pages_goods/category').then(res => {
@@ -60,25 +67,48 @@ export default {
// #endif
},
onUnload() {
- !this.storeToken && this.$refs.login?.cancelCompleteInfo?.();
+ try {
+ if (!this.storeToken && this.$refs.login && typeof this.$refs.login.cancelCompleteInfo === 'function') {
+ this.$refs.login.cancelCompleteInfo();
+ }
+ } catch (e) {
+ console.warn('onUnload cancelCompleteInfo failed', e);
+ }
},
methods: {
getDiyInfo() {
+ // 避免重复请求导致布局抖动
+ if (this._diyInfoLoading) return;
+ this._diyInfoLoading = true;
+
this.$api.sendRequest({
url: '/api/diyview/info',
data: {
name: 'DIY_VIEW_GOODS_CATEGORY'
},
success: res => {
- if (res.code == 0 && res.data) {
- this.diyData = res.data;
- if (this.diyData.value) {
- this.diyData = JSON.parse(this.diyData.value);
- if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
+ if (res.code == 0 && res.data) {
+ const newData = res.data.value ? JSON.parse(res.data.value) : res.data;
+ // 仅在数据真正变化时赋值,避免鸿蒙元服务下频繁 re-render 造成频闪
+ if (JSON.stringify(newData) !== JSON.stringify(this.diyData)) {
+ this.diyData = newData;
}
-
- uni.stopPullDownRefresh();
+ } else {
+ this.$util.showToast({
+ title: this.$lang('diy.noDiyData')
+ });
}
+ },
+ fail: err => {
+ console.error('getDiyInfo request failed', err);
+ this.$util.showToast({
+ title: this.$lang('diy.networkError')
+ });
+ },
+ complete: () => {
+ this._diyInfoLoading = false;
+ if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
+ uni.stopPullDownRefresh();
}
});
},
diff --git a/pages_order/public/js/orderMethod.js b/pages_order/public/js/orderMethod.js
index 02f6e26..078996e 100644
--- a/pages_order/public/js/orderMethod.js
+++ b/pages_order/public/js/orderMethod.js
@@ -22,6 +22,7 @@ export default {
pay() {
this.$api.sendRequest({
url: '/api/order/pay',
+ noRetry: true,
data: {
order_ids: this.orderData.order_id
},
@@ -49,6 +50,7 @@ export default {
if (res.confirm) {
this.$api.sendRequest({
url: '/api/order/close',
+ noRetry: true,
data: {
order_id
},
@@ -82,6 +84,7 @@ export default {
if (res.confirm) {
this.$api.sendRequest({
url: '/api/order/takedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
@@ -110,6 +113,7 @@ export default {
if (res.extraData.status == 'success') {
this.$api.sendRequest({
url: '/api/order/takedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
@@ -123,6 +127,7 @@ export default {
} else {
this.$api.sendRequest({
url: '/api/order/takedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
@@ -146,6 +151,7 @@ export default {
if (res.confirm) {
this.$api.sendRequest({
url: '/api/order/takedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
@@ -177,6 +183,7 @@ export default {
if (res.confirm) {
this.$api.sendRequest({
url: '/api/order/membervirtualtakedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
@@ -205,6 +212,7 @@ export default {
if (res.extraData.status == 'success') {
this.$api.sendRequest({
url: '/api/order/membervirtualtakedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
@@ -218,6 +226,7 @@ export default {
} else {
this.$api.sendRequest({
url: '/api/order/membervirtualtakedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
@@ -240,6 +249,7 @@ export default {
if (res.confirm) {
this.$api.sendRequest({
url: '/api/order/membervirtualtakedelivery',
+ noRetry: true,
data: {
order_id: orderData.order_id
},
From edc077eef79300e471522f27d792f2ad8e5c54a7 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 16:08:53 +0800
Subject: [PATCH 11/46] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0TODO=E6=B8=85?=
=?UTF-8?q?=E5=8D=95=EF=BC=8C=E8=A1=A5=E5=85=85=E9=B8=BF=E8=92=99=E5=85=83?=
=?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AD=89=E7=8E=AF=E5=A2=83=E7=9A=84bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增多个鸿蒙元服务环境的组件显示异常bug,以及微信小程序的控制台报错bug,更新已修复的bug项格式
---
docs/TODO.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/TODO.md b/docs/TODO.md
index 0973e22..75a71d7 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -29,4 +29,12 @@
## BUG
- [x] BUG: diy-image-nav 组件,singleSlide 模式,在H5环境下,不支持鼠标滑动;
-- [x] BUG: diy-article 组件,在H5环境下,不支持通过鼠标滚动
\ No newline at end of file
+- [x] BUG: diy-article 组件,在H5环境下,不支持通过鼠标滚动
+- [ ] BUG: diy-video 组件,在鸿蒙元服务环境下,不显示视频
+- [ ] BUG: diy-video 组件,在鸿蒙元服务环境下,视频显示了,但是比例不对
+- [ ] BUG: diy-video-list 组件,在鸿蒙元服务环境下,比例不对
+- [ ] BUG: diy-goods-list 组件,在鸿蒙元服务环境下,图片显示不全 (H5,微信小程序环境正常)
+- [ ] BUG: 在鸿蒙元服务环境下,偶尔出现首页空白的情况
+- [ ] BUG: /pages_tool/article/detail 页面,应该空白占满屏
+- [ ] BUG: 微信小程序环境:
+- [ ] BUG: [console]: {"stack":"TypeError: r.map is not a function\n at Tr.selectAllComponents (:1:141070)\n at eval (webpack-internal:///./common/vendor.js:18:76114)\n at a.get [as $refs] (webpack-internal:///./common/vendor.js:18:76205)\n at a.onUnload (webpack-internal:///./pages_goods/category.js:26:51)\n at Re (webpack-internal:///./common/vendor.js:10:11210)\n at e.__call_hook (webpack-internal:///./common/vendor.js:11:22791)\n at p.default.__call_hook (webpack-internal:///./common/vendor.js:18:77128)\n at e. (webpack-internal:///./common/vendor.js:18:69329)\n at Tr.jr (:1:139499)\n at :1:140379","message":"r.map is not a function"}
\ No newline at end of file
From 83fc02a2799eef18e9ab6f81fd52fedd90f4d160 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 16:22:24 +0800
Subject: [PATCH 12/46] =?UTF-8?q?refactor(diy-goods-list):=20=E7=BB=9F?=
=?UTF-8?q?=E4=B8=80=E5=95=86=E5=93=81=E5=9B=BE=E7=89=87mode=E9=80=82?=
=?UTF-8?q?=E9=85=8D=E9=B8=BF=E8=92=99=E5=85=83=E6=9C=8D=E5=8A=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修复鸿蒙元服务环境下商品图片显示裁剪/不全的问题,根据模板和平台动态适配图片mode模式
---
components-diy/diy-goods-list.vue | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/components-diy/diy-goods-list.vue b/components-diy/diy-goods-list.vue
index e94964f..c7c60dc 100644
--- a/components-diy/diy-goods-list.vue
+++ b/components-diy/diy-goods-list.vue
@@ -7,7 +7,7 @@
@@ -117,7 +117,7 @@
:class="[goodsValue.ornament.type]" :style="goodsItemCss">
@@ -184,7 +184,7 @@
:class="[goodsValue.ornament.type]" :style="goodsItemCss">
@@ -302,6 +302,19 @@ export default {
}
},
computed: {
+ // 商品图 image 的 mode 模式:
+ // - 大图模式(large-mode):容器高度由图片决定,固定使用 widthFix 保持原比例
+ // - 鸿蒙元服务:其他模板下 image 的 widthFix 模式异常(图片被裁剪/显示不全),改用 aspectFill 填满固定容器
+ // - H5、微信小程序等其他环境:保持 widthFix 行为不变
+ imgMode() {
+ if (this.goodsValue.template == 'large-mode') return 'widthFix';
+ // #ifdef MP-HARMONY
+ return 'aspectFill';
+ // #endif
+ // #ifndef MP-HARMONY
+ return 'widthFix';
+ // #endif
+ },
goodsListWarpCss() {
var obj = '';
obj += 'background-color:' + this.goodsValue.componentBgColor + ';';
From e88dc2b205710c8926ca85eed1b9dda60560b786 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 16:30:10 +0800
Subject: [PATCH 13/46] =?UTF-8?q?docs(mp-share-default):=20=E4=BF=AE?=
=?UTF-8?q?=E6=AD=A3=E6=B3=A8=E9=87=8A=E9=87=8C=E7=9A=84=E9=A2=84=E5=A4=84?=
=?UTF-8?q?=E7=90=86=E5=AE=8F=E6=8F=8F=E8=BF=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将原来的具体代码注释调整为更通用的"MP-WEIXIN 预处理宏"表述,让注释更清晰易懂
---
common/js/mp-share-default.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common/js/mp-share-default.js b/common/js/mp-share-default.js
index 2b37d25..77379e6 100644
--- a/common/js/mp-share-default.js
+++ b/common/js/mp-share-default.js
@@ -2,7 +2,7 @@
* 全局兜底分享 mixin
*
* 仅在微信小程序编译产物中被加载:
- * main.js 中的 import + Vue.mixin 调用被 // #ifdef MP-WEIXIN 包住,
+ * main.js 中的 import + Vue.mixin 调用被 MP-WEIXIN 预处理宏 包住,
* 其它平台编译时整个模块不会进入产物,因此文件内部无需再写条件编译。
*
* 背景:
From 3d822ed641662e6ea299bdbe7e8764e938d56ab3 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 18:20:51 +0800
Subject: [PATCH 14/46] =?UTF-8?q?fix(util):=20=E8=A1=A5=E5=85=A8wap=5Furl?=
=?UTF-8?q?=E5=85=AC=E5=85=B1=E5=8F=82=E6=95=B0=E5=B9=B6=E9=80=8F=E4=BC=A0?=
=?UTF-8?q?page=5Fparams?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将page_params合并到公共参数中,一并追加到跳转链接里
---
common/js/util.js | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/common/js/util.js b/common/js/util.js
index eed9cd6..3e12220 100644
--- a/common/js/util.js
+++ b/common/js/util.js
@@ -1322,13 +1322,18 @@ export default {
// 外部链接
if (link.wap_url && link.wap_url.indexOf('http') != -1) {
- // 公共参数
- const params = {
- title: link?.title ?? '',
- enable_set_title: link?.enable_set_title ? 1 : 0
- }
+ // 公共参数 + 透传 page_params(resolveLinkUrl 已把 wap_url 中的 query 合并到 page_params)
+ const pageParams = (link.page_params && typeof link.page_params === 'object') ? link.page_params : {};
+ const params = Object.assign(
+ {},
+ pageParams,
+ {
+ title: link?.title ?? '',
+ enable_set_title: link?.enable_set_title ? 1 : 0
+ }
+ );
let url = this.normalizeUrl(decodeEntity(link.wap_url));
- url = this.appendQueryParams(url, params);
+ url = this.appendQueryParams(url, params);
// #ifdef H5
// console.log(`url ---->`, url, link)
From 2708ac55d1d42145b2a73dbe218a1d8161dfa21c Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Mon, 13 Jul 2026 18:22:22 +0800
Subject: [PATCH 15/46] =?UTF-8?q?fix(util):=20=E4=BF=AE=E5=A4=8D=E5=B0=8F?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E8=B7=B3=E8=BD=AC=E6=97=B6=E6=9C=AA=E6=8B=BC?=
=?UTF-8?q?=E6=8E=A5=E5=8F=82=E6=95=B0=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修复uni.navigateToMiniProgram调用时未将page_params参数拼接进跳转path的问题,正确处理了参数过滤和url拼接逻辑
---
common/js/util.js | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/common/js/util.js b/common/js/util.js
index 3e12220..55aa178 100644
--- a/common/js/util.js
+++ b/common/js/util.js
@@ -1349,9 +1349,22 @@ export default {
} else if (link.appid) {
// 跳转其他小程序
+ // page_params 转 query string,拼到 path 后面(resolveLinkUrl 已把 wap_url 中的 query 合并到 page_params)
+ let path = link.page || '';
+ const pageParams = (link.page_params && typeof link.page_params === 'object') ? link.page_params : {};
+ const paramKeys = Object.keys(pageParams);
+ if (paramKeys.length > 0) {
+ const qs = paramKeys
+ .filter(k => pageParams[k] !== undefined && pageParams[k] !== null && pageParams[k] !== '')
+ .map(k => `${k}=${pageParams[k]}`)
+ .join('&');
+ if (qs) {
+ path += (path.indexOf('?') !== -1 ? '&' : '?') + qs;
+ }
+ }
uni.navigateToMiniProgram({
appId: link.appid,
- path: link.page
+ path
})
} else if (link.name == 'MOBILE' && !link.wap_url) {
From aac44de359e84658a83507da05358b14ded1f4e4 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Tue, 14 Jul 2026 09:38:35 +0800
Subject: [PATCH 16/46] style(article detail page): adjust page height to
min-h-screen
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
将文章详情页的固定高度改为视口最小高度,适配不同屏幕内容高度场景
---
pages_tool/article/detail.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pages_tool/article/detail.vue b/pages_tool/article/detail.vue
index b457603..76d93bc 100644
--- a/pages_tool/article/detail.vue
+++ b/pages_tool/article/detail.vue
@@ -111,7 +111,7 @@
From 4c58db07104ccc89bb77e3e24dd6b8f92af1e916 Mon Sep 17 00:00:00 2001
From: ZF sun <34314687@qq.com>
Date: Wed, 15 Jul 2026 17:13:45 +0800
Subject: [PATCH 43/46] feat: add back-to-top component to multiple pages and
import scroll mixin
add to-top component to card.vue, card_buy.vue, diy.vue and member index.vue, import scroll mixin for diy and member index pages to support scroll to top functionality
---
pages_tool/index/diy.vue | 4 +++-
pages_tool/member/card.vue | 1 +
pages_tool/member/card_buy.vue | 1 +
pages_tool/member/index.vue | 4 +++-
4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/pages_tool/index/diy.vue b/pages_tool/index/diy.vue
index 1249b3c..8059d7f 100644
--- a/pages_tool/index/diy.vue
+++ b/pages_tool/index/diy.vue
@@ -51,6 +51,7 @@
+
@@ -58,10 +59,11 @@
diff --git a/pages_tool/member/card.vue b/pages_tool/member/card.vue
index c166f5a..77e5de8 100644
--- a/pages_tool/member/card.vue
+++ b/pages_tool/member/card.vue
@@ -118,6 +118,7 @@
+
diff --git a/pages_tool/member/card_buy.vue b/pages_tool/member/card_buy.vue
index f2c8edc..a08d1c0 100644
--- a/pages_tool/member/card_buy.vue
+++ b/pages_tool/member/card_buy.vue
@@ -215,6 +215,7 @@
+
diff --git a/pages_tool/member/index.vue b/pages_tool/member/index.vue
index 6700c81..70ea90f 100644
--- a/pages_tool/member/index.vue
+++ b/pages_tool/member/index.vue
@@ -71,12 +71,14 @@
+