feat(diy): add in-page error prompt for diy data loading failure

1. add multi-language error copy for api request and network exceptions
2. add fixed top error banner component for index page
3. rewrite diy data request error handling: replace toast with reliable in-page prompt with retry button
4. add detailed error reason and request url display in error prompt
This commit is contained in:
2026-07-18 15:58:40 +08:00
parent 5831189e5e
commit c8d92d8100
4 changed files with 147 additions and 7 deletions

View File

@@ -51,6 +51,11 @@ export default {
lastDiyInfoTime: 0, // 上次成功加载的时间戳
lastDiyInfoLang: '', // 上次加载时的语言,语言切换时强制刷新
diyInfoTtl: 3 * 60 * 1000, // 3分钟内不重复请求
// diy 数据网络加载失败时的页面内错误提示(比 showModal 在各端更可靠)
diyLoadError: false,
diyLoadErrorMsg: '',
diyLoadErrorUrl: '', // 出错的网络请求地址
//启动广告
adv:{},
@@ -310,6 +315,52 @@ export default {
}
},
// 无法从网络获取 diy 数据时:在页面内渲染错误横幅(各端可靠、一定可见)
// @param {Object|string} [err] 网络异常对象 {errMsg, statusCode} / 服务端消息字符串
// @param {string} [url] 出错的请求地址(完整 URL),用于横幅展示
_showDiyLoadError(err, url) {
this.diyLoadErrorMsg = this._buildDiyErrorMsg(err);
this.diyLoadErrorUrl = url || '';
this.diyLoadError = true;
},
// 根据错误信息组织更详细的提示文案
// 说明:remote-only 模式下 $lang 可能返回空串,故统一用 `|| 中文兜底` 保证非空
_buildDiyErrorMsg(err) {
// 无错误信息 -> 通用网络异常
if (!err) return this.$lang('diy.networkError');
// 服务端明确返回的业务消息(字符串)
if (typeof err === 'string') return err;
const statusCode = err.statusCode;
const errMsg = err.errMsg || err.message || '';
let reason;
if (statusCode) {
// HTTP 状态码类错误
if (statusCode === 404) reason = this.$lang('diy.errNotFound');
else if (statusCode >= 500) reason = this.$lang('diy.errServer');
else if (statusCode === 401 || statusCode === 403) reason = this.$lang('diy.errAuth');
else reason = this.$lang('diy.errRequest');
reason += '(' + statusCode + ')';
} else if (/timeout/i.test(errMsg)) {
reason = this.$lang('diy.errTimeout');
} else if (/(connect|network|fail)/i.test(errMsg)) {
reason = this.$lang('diy.errNetwork');
} else {
// 未知错误 -> 通用网络异常
return this.$lang('diy.networkError');
}
const tpl = this.$lang('diy.loadFailTpl');
return tpl.replace('{reason}', reason);
},
// 错误横幅「重试」入口
retryDiyInfo() {
this.diyLoadError = false;
this.diyLoadErrorMsg = '';
this.diyLoadErrorUrl = '';
this.getDiyInfo(true);
},
async getDiyInfo(force = false) {
// 节流:正在请求 / N 秒内已加载过 / 语言未变 -> 跳过
const now = Date.now();
@@ -324,9 +375,12 @@ export default {
this.diyInfoLoading = true;
let res;
// 出错时用于展示的完整请求地址(Request URL),与 http.js 拼接/归一化方式保持一致
const diyInfoUrl = '/api/diyview/info';
const diyInfoFullUrl = ((Config.baseUrl || '') + diyInfoUrl).replace(/(?<!:)\/+/g, '/');
try {
res = await this.$api.sendRequest({
url: '/api/diyview/info',
url: diyInfoUrl,
data: {
id: this.id,
name: this.name,
@@ -337,10 +391,9 @@ export default {
} catch (e) {
console.error('getDiyInfo request failed', e);
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
this.$util.showToast({
title: this.$lang('diy.networkError')
});
this.diyInfoLoading = false;
// 无法从网络获取数据时,页面内显示错误横幅并提供重试(带详细原因 + 请求地址)
this._showDiyLoadError(e, diyInfoFullUrl);
return;
}
this.diyInfoLoading = false;
@@ -348,6 +401,14 @@ export default {
if (!res || res.code != 0 || !res.data) {
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
// 完全没有拿到网络响应,视为无法从网络获取数据,页面内显示错误横幅
if (!res) {
this._showDiyLoadError(null, diyInfoFullUrl);
// 保持原有数据结构,仅清空 value,避免鸿蒙下重新布局
this.diyData = { ...(this.diyData || {}), value: [] };
return;
}
if (res && res.code == -3) {
this.$util.showToast({
title: res.message
@@ -357,9 +418,9 @@ export default {
return;
}
this.$util.showToast({
title: this.$lang('diy.noDiyData')
});
// 拿到响应但业务失败 / 无数据(如接口 404 返回 200+错误码、code!=0)
// 同样视为无法获取到 diy 数据,页面内显示错误横幅
this._showDiyLoadError(res && res.message, diyInfoFullUrl);
// 保持原有数据结构,仅清空 value,避免鸿蒙下重新布局
this.diyData = { ...(this.diyData || {}), value: [] };
return;
@@ -367,6 +428,10 @@ export default {
this.lastDiyInfoTime = now;
this.lastDiyInfoLang = currentLang;
// 成功加载后清除错误提示
this.diyLoadError = false;
this.diyLoadErrorMsg = '';
this.diyLoadErrorUrl = '';
let diyDataValue = res.data;
if (diyDataValue.value) {

View File

@@ -61,6 +61,14 @@ export const lang = {
retry: 'Retry',
noDiyData: 'Custom page data not configured',
locationNotAvailable: 'Location not available',
// detailed data-load failure messages
loadFailTpl: 'Failed to load page data: {reason}, please try again later',
errNotFound: 'API not found',
errServer: 'Server error',
errAuth: 'No access permission',
errRequest: 'Request failed',
errTimeout: 'Request timeout',
errNetwork: 'Network connection failed',
},
//customer service
@@ -142,6 +150,8 @@ export const lang = {
serverError: 'Service temporarily unavailable, please try again later',
// request aborted
aborted: 'Request cancelled',
// unknown error (errorType fallback, e.g. non-5xx HTTP status like 404)
unknown: 'Request failed, please try again later',
// unknown error
unknownError: 'Request failed, please try again later',
},

View File

@@ -68,6 +68,14 @@ export const lang = {
retry: '重试',
noDiyData: '未配置自定义页面数据',
locationNotAvailable: '未获取到定位',
// 数据加载失败详细文案
loadFailTpl: '页面数据加载失败:{reason},请稍后重试',
errNotFound: '接口不存在',
errServer: '服务器异常',
errAuth: '无访问权限',
errRequest: '请求失败',
errTimeout: '请求超时',
errNetwork: '网络连接失败',
},
// 客服服务
@@ -142,6 +150,8 @@ export const lang = {
serverError: '服务暂时不可用,请稍后重试',
// 请求被中断
aborted: '请求已取消',
// 未知错误(errorType 分类兜底,如非 5xx 的 HTTP 状态码 404 等)
unknown: '请求失败,请稍后重试',
// 未知错误
unknownError: '请求失败,请稍后重试',
},

View File

@@ -2,6 +2,15 @@
<view :style="themeColor">
<view :style="{ backgroundColor: bgColor, minHeight: openBottomNav ? 'calc(100vh - 55px)' : '' }"
class="page-img">
<!-- diy 数据网络加载失败提示(页面内横幅,各端可靠,悬浮在最上层) -->
<view v-if="diyLoadError" class="diy-load-error">
<view class="diy-load-error__body">
<text class="diy-load-error__msg">{{ diyLoadErrorMsg || '网络异常,请稍后重试' }}</text>
<text v-if="diyLoadErrorUrl" class="diy-load-error__url" selectable user-select>{{ diyLoadErrorUrl }}</text>
</view>
<view class="diy-load-error__btn" @tap="retryDiyInfo">{{ $lang('diy.retry') || '重试' }}</view>
</view>
<view class="site-info-box"
v-if="$util.isWeiXin() && followOfficialAccount && followOfficialAccount.isShow && wechatQrcode">
<view class="site-info">
@@ -179,6 +188,52 @@ export default {
z-index: 122;
}
.diy-load-error {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100000;
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 28rpx;
padding-top: calc(24rpx + var(--status-bar-height, 0px));
background-color: #fff5f5;
border-bottom: 1rpx solid #ffccc7;
}
.diy-load-error__body {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.diy-load-error__msg {
font-size: 26rpx;
color: #cf1322;
line-height: 1.5;
}
.diy-load-error__url {
margin-top: 6rpx;
font-size: 22rpx;
color: #999;
line-height: 1.4;
word-break: break-all;
}
.diy-load-error__btn {
margin-left: 20rpx;
padding: 10rpx 28rpx;
font-size: 26rpx;
color: #fff;
background-color: #cf1322;
border-radius: 32rpx;
white-space: nowrap;
}
.small-bot swiper {
width: 560rpx;
height: 784rpx;