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:
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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: '请求失败,请稍后重试',
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user