mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-11 03:18:56 +08:00
fix(buyer): 修复登录过期异常及全局请求错误处理
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { setupRequestErrorHandling } from "@/utils/requestError";
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import "core-js/stable";
|
||||
@@ -24,6 +25,7 @@ if (aMapSecurityJsCode) {
|
||||
}
|
||||
|
||||
const app = createApp(App);
|
||||
setupRequestErrorHandling(app);
|
||||
|
||||
setupElementPlus(app);
|
||||
setupLegacyMessage(app);
|
||||
|
||||
@@ -200,7 +200,7 @@ function handleSubmit () { // 提交密码
|
||||
Message.success('修改密码成功');
|
||||
router.push('login');
|
||||
}
|
||||
}).catch(() => { loading.value = false; });
|
||||
}).catch(() => { loading1.value = false; });
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { catchHandledRequestError } from "@/utils/requestError";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { Message } from "@/utils/message";
|
||||
@@ -230,6 +231,8 @@ function handleSubmit() {
|
||||
params.content = "";
|
||||
getDetail();
|
||||
}
|
||||
}).catch(catchHandledRequestError).finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<!-- 列表 -->
|
||||
<empty v-if="orderList.length === 0"/>
|
||||
<el-skeleton v-if="spinShow" :rows="5" animated />
|
||||
<el-empty v-else-if="queryFailed" description="加载失败,请重试"><el-button type="primary" @click="getList">重新加载</el-button></el-empty>
|
||||
<empty v-else-if="orderList.length === 0" />
|
||||
<div class="order-content" v-else>
|
||||
<div class="order-cols">
|
||||
<span>订单详情</span>
|
||||
@@ -77,7 +79,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
|
||||
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<div class="page-size">
|
||||
@@ -134,6 +136,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { catchHandledRequestError } from "@/utils/requestError";
|
||||
import { nextTick, onMounted, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
@@ -157,6 +160,8 @@ const params = reactive({ // 请求参数
|
||||
order: 'desc'
|
||||
});
|
||||
const total = ref(0); // 订单总数
|
||||
const queryFailed = ref(false); // 区分加载失败与暂无记录,支持重新查询
|
||||
let listRequestVersion = 0; // 仅允许最后一次查询更新列表与加载状态
|
||||
const spinShow = ref(false); // 加载状态
|
||||
const companyList = ref([]); // 物流公司列表
|
||||
const logisticsShow = ref(false); // 物流信息modal
|
||||
@@ -230,14 +235,27 @@ function change(index) {
|
||||
getList()
|
||||
}
|
||||
function getList() { // 获取售后列表
|
||||
const version = ++listRequestVersion;
|
||||
queryFailed.value = false;
|
||||
spinShow.value = true;
|
||||
let paramsData = JSON.parse(JSON.stringify(params))
|
||||
afterSaleList(paramsData).then(res => {
|
||||
spinShow.value = false
|
||||
if (version !== listRequestVersion) return;
|
||||
if (res.success) {
|
||||
orderList.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (version !== listRequestVersion) {
|
||||
catchHandledRequestError(error);
|
||||
return;
|
||||
}
|
||||
queryFailed.value = true;
|
||||
orderList.value = [];
|
||||
total.value = 0;
|
||||
catchHandledRequestError(error);
|
||||
}).finally(() => {
|
||||
if (version === listRequestVersion) spinShow.value = false;
|
||||
});
|
||||
}
|
||||
function changePageNum(val) { // 修改页码
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
</template>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<empty v-if="orderList.length === 0" />
|
||||
<el-skeleton v-if="spinShow" :rows="5" animated />
|
||||
<el-empty v-else-if="queryFailed" description="加载失败,请重试"><el-button type="primary" @click="getList">重新加载</el-button></el-empty>
|
||||
<empty v-else-if="orderList.length === 0" />
|
||||
<div class="order-content" v-else>
|
||||
<div class="order-cols">
|
||||
<span>订单详情</span>
|
||||
@@ -91,7 +93,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
@@ -103,7 +105,9 @@
|
||||
</UserCenterLayout>
|
||||
|
||||
<!-- 个人中心首页订单列表 -->
|
||||
<empty v-if="homePage && orderList.length === 0" />
|
||||
<el-skeleton v-if="homePage && spinShow" :rows="5" animated />
|
||||
<el-empty v-else-if="homePage && queryFailed" description="加载失败,请重试"><el-button type="primary" @click="getList">重新加载</el-button></el-empty>
|
||||
<empty v-else-if="homePage && orderList.length === 0" />
|
||||
<div class="order-content" v-else-if="homePage">
|
||||
<div class="order-cols">
|
||||
<span>订单详情</span>
|
||||
@@ -173,7 +177,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-skeleton size="large" fix v-if="spinShow"></el-skeleton>
|
||||
|
||||
</div>
|
||||
<el-dialog v-model="cancelAvail" title="请选择取消订单原因">
|
||||
<el-alert v-if="cancelIsPickup" title="此订单使用提货卡兑换,取消不退还提货权益,无法再次兑换。" type="warning" :closable="false" style="margin-bottom:16px" />
|
||||
@@ -193,6 +197,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { catchHandledRequestError } from "@/utils/requestError";
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
@@ -227,6 +232,8 @@ const cancelParams = reactive({ // 取消售后参数
|
||||
const changeWay = ref(['全部', '待付款', '待发货', '待收货/使用']);
|
||||
const activeOrderTab = ref(0);
|
||||
const total = ref(0); // 数据总数
|
||||
const queryFailed = ref(false); // 区分加载失败与暂无记录,支持重新查询
|
||||
let listRequestVersion = 0; // 仅允许最后一次查询更新列表与加载状态
|
||||
const spinShow = ref(false); // 加载状态
|
||||
const cancelIsPickup = ref(false);
|
||||
const cancelAvail = ref(false); // 取消订单modal控制
|
||||
@@ -322,17 +329,30 @@ function delOrder (sn) { // 删除订单
|
||||
});
|
||||
}
|
||||
function getList () {
|
||||
const version = ++listRequestVersion;
|
||||
queryFailed.value = false;
|
||||
spinShow.value = true;
|
||||
let _params = JSON.parse(JSON.stringify(params))
|
||||
if (_params.orderStatus === 'ALL') {
|
||||
delete _params.orderStatus
|
||||
}
|
||||
getOrderList(_params).then(res => {
|
||||
spinShow.value = false
|
||||
if (version !== listRequestVersion) return;
|
||||
if (res.success) {
|
||||
orderList.value = res.result.records;
|
||||
total.value = res.result.total;
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (version !== listRequestVersion) {
|
||||
catchHandledRequestError(error);
|
||||
return;
|
||||
}
|
||||
queryFailed.value = true;
|
||||
orderList.value = [];
|
||||
total.value = 0;
|
||||
catchHandledRequestError(error);
|
||||
}).finally(() => {
|
||||
if (version === listRequestVersion) spinShow.value = false;
|
||||
});
|
||||
}
|
||||
function mapAfterSaleOrder (item) {
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<el-button class="del-btn" @click="clearAll" type="primary">删除全部</el-button>
|
||||
</template>
|
||||
<!-- 订单列表 -->
|
||||
<empty v-if="list.length === 0" />
|
||||
<el-skeleton v-if="spinShow" :rows="5" animated />
|
||||
<el-empty v-else-if="queryFailed" description="加载失败,请重试"><el-button type="primary" @click="getList">重新加载</el-button></el-empty>
|
||||
<empty v-else-if="list.length === 0" />
|
||||
<ul class="track-list" v-else>
|
||||
<li
|
||||
v-for="(item, index) in list"
|
||||
@@ -39,6 +41,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { catchHandledRequestError } from "@/utils/requestError";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
@@ -50,6 +53,8 @@ defineOptions({ name: "MyTrack" });
|
||||
const router = useRouter();
|
||||
|
||||
const list = ref([]); // 我的足迹,商品列表
|
||||
const queryFailed = ref(false); // 区分加载失败与暂无记录,支持重新查询
|
||||
let listRequestVersion = 0; // 仅允许最后一次查询更新列表与加载状态
|
||||
const spinShow = ref(false); // 控制loading是否加载
|
||||
const params = reactive({
|
||||
pageNumber: 1,
|
||||
@@ -118,9 +123,11 @@ function changePageSize(val) {
|
||||
|
||||
function getList() {
|
||||
// 获取足迹列表
|
||||
const version = ++listRequestVersion;
|
||||
queryFailed.value = false;
|
||||
spinShow.value = true;
|
||||
tracksList(params).then((res) => {
|
||||
spinShow.value = false;
|
||||
if (version !== listRequestVersion) return;
|
||||
if (res.success && res.result.records.length) {
|
||||
list.value = res.result.records.filter(item =>{
|
||||
return item != null
|
||||
@@ -129,6 +136,17 @@ function getList() {
|
||||
} else {
|
||||
list.value = [];
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (version !== listRequestVersion) {
|
||||
catchHandledRequestError(error);
|
||||
return;
|
||||
}
|
||||
queryFailed.value = true;
|
||||
list.value = [];
|
||||
total.value = 0;
|
||||
catchHandledRequestError(error);
|
||||
}).finally(() => {
|
||||
if (version === listRequestVersion) spinShow.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ function getGoodsDetail() {
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading.value = false;
|
||||
router.push("/");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from "axios";
|
||||
import { markHandledRequestError, isHandledRequestError } from "@/utils/requestError";
|
||||
import { ElLoading } from "element-plus";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import Storage from "./storage";
|
||||
@@ -14,7 +15,7 @@ export const buyerUrl =
|
||||
? BASE.API_DEV.buyer
|
||||
: BASE.API_PROD.buyer;
|
||||
|
||||
let isRefreshToken = 0;
|
||||
let loginPrompt = null;
|
||||
const refreshToken = getTokenDebounce();
|
||||
|
||||
/** 会员中心(/home/*)页面使用局部 loading,不启用全屏遮罩 */
|
||||
@@ -37,20 +38,22 @@ const service = axios.create({
|
||||
});
|
||||
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
async (config) => {
|
||||
const { loading } = config;
|
||||
const isPutPost = config.method === "put" || config.method === "post";
|
||||
const isJson = config.headers["Content-Type"] === "application/json";
|
||||
const isFile = config.headers["Content-Type"] === "multipart/form-data";
|
||||
if (isPutPost && isJson) {
|
||||
if (!config._dataSerialized && isPutPost && isJson) {
|
||||
config.data = JSON.stringify(config.data);
|
||||
}
|
||||
if (isPutPost && !isFile && !isJson) {
|
||||
if (!config._dataSerialized && isPutPost && !isFile && !isJson) {
|
||||
config.data = qs.stringify(config.data, {
|
||||
arrayFormat: "repeat",
|
||||
});
|
||||
}
|
||||
|
||||
config._dataSerialized = true;
|
||||
|
||||
const method = (config.method || "get").toLowerCase();
|
||||
const isReadRequest = method === "get" || method === "head";
|
||||
const shouldShowLoading =
|
||||
@@ -58,7 +61,7 @@ service.interceptors.request.use(
|
||||
(!isReadRequest && loading !== false && !isMemberCenterRoute());
|
||||
|
||||
if (typeof window !== "undefined" && shouldShowLoading) {
|
||||
config.loading = ElLoading.service({ fullscreen: true });
|
||||
config._loadingInstance = ElLoading.service({ fullscreen: true });
|
||||
}
|
||||
|
||||
let uuid = Storage.getItem("uuid");
|
||||
@@ -82,7 +85,7 @@ service.interceptors.request.use(
|
||||
const expired = jwtData.exp < Math.round(new Date() / 1000);
|
||||
if (expired) {
|
||||
if (config.needToken) {
|
||||
return refresh({ response: { config } });
|
||||
return refresh(config);
|
||||
}
|
||||
// 公开接口:丢弃过期 token,匿名继续请求,避免浏览商品时被引导登录
|
||||
Storage.removeItem("accessToken");
|
||||
@@ -94,7 +97,7 @@ service.interceptors.request.use(
|
||||
Storage.removeItem("accessToken");
|
||||
Storage.removeItem("refreshToken");
|
||||
if (config.needToken) {
|
||||
return refresh({ response: { config } });
|
||||
return refresh(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,59 +107,64 @@ service.interceptors.request.use(
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
/** 保留请求配置、状态与错误码,供页面判断业务失败并定位原因。 */
|
||||
function buildRejectError(error, errorData = {}) {
|
||||
const message =
|
||||
errorData.message ||
|
||||
(error?.code === "ECONNABORTED" ? "连接超时,请稍候再试!" : "网络错误,请稍后再试!");
|
||||
return Object.assign(new Error(message), { data: errorData });
|
||||
return Object.assign(new Error(message), { data: errorData, config: error.config, response: error.response, code: error.code });
|
||||
}
|
||||
|
||||
async function refresh(error) {
|
||||
const getTokenRes = await refreshToken();
|
||||
if (getTokenRes === "success") {
|
||||
if (error?.response?.config) {
|
||||
error.response.config.headers.accessToken = Storage.getItem("accessToken");
|
||||
return service(error.response.config);
|
||||
/**
|
||||
* 刷新凭证并返回请求配置;请求拦截器不能返回业务响应。每个业务请求最多刷新一次。
|
||||
* 刷新失败时保持 Promise 拒绝,阻止后续支付、订单等成功回调继续执行。
|
||||
*/
|
||||
async function refresh(config) {
|
||||
if (!config._retryAfterRefresh) {
|
||||
config._retryAfterRefresh = true;
|
||||
const result = await refreshToken();
|
||||
if (result === "success") {
|
||||
config.headers.accessToken = Storage.getItem("accessToken");
|
||||
return config;
|
||||
}
|
||||
router.go(0);
|
||||
return new Promise(() => {});
|
||||
}
|
||||
Storage.removeItem("accessToken");
|
||||
Storage.removeItem("refreshToken");
|
||||
Storage.removeItem("userInfo");
|
||||
Storage.setItem("cartNum", 0);
|
||||
useAppStore().setCartNum(0);
|
||||
const currentRoute = router.currentRoute.value;
|
||||
Modal.confirm({
|
||||
title: "请登录",
|
||||
content: "请登录后执行此操作",
|
||||
okText: "立即登录",
|
||||
cancelText: "继续浏览",
|
||||
onOk: () => {
|
||||
router.push({
|
||||
// 多个受保护接口同时失败时共用一个弹窗,关闭后允许再次提示。
|
||||
if (!loginPrompt) {
|
||||
const currentRoute = router.currentRoute.value;
|
||||
loginPrompt = Modal.confirm({
|
||||
title: "请登录",
|
||||
content: "请登录后执行此操作",
|
||||
okText: "立即登录",
|
||||
cancelText: "继续浏览",
|
||||
onOk: () => router.push({
|
||||
path: "/login",
|
||||
query: {
|
||||
rePath: currentRoute.path,
|
||||
query: JSON.stringify(currentRoute.query),
|
||||
},
|
||||
});
|
||||
},
|
||||
onCancel: () => {
|
||||
router.push("/");
|
||||
Modal.remove();
|
||||
},
|
||||
});
|
||||
return Promise.reject(new Error("登录已过期"));
|
||||
query: { rePath: currentRoute.path, query: JSON.stringify(currentRoute.query) },
|
||||
}),
|
||||
onCancel: () => router.push("/"),
|
||||
}).finally(() => { loginPrompt = null; });
|
||||
}
|
||||
const error = Object.assign(new Error("登录已过期"), { code: "AUTH_EXPIRED", config });
|
||||
throw markHandledRequestError(error);
|
||||
}
|
||||
|
||||
service.interceptors.response.use(
|
||||
async (response) => {
|
||||
await closeLoading(response);
|
||||
const config = response.config;
|
||||
if (!config.skipAuthHandler && [401, 403, 20004].includes(response.data?.code)) {
|
||||
return service(await refresh(config));
|
||||
}
|
||||
return response.data;
|
||||
},
|
||||
async (error) => {
|
||||
if (typeof window === "undefined") return Promise.reject(error);
|
||||
await closeLoading(error);
|
||||
if (isHandledRequestError(error)) return Promise.reject(error);
|
||||
const errorResponse = error.response || {};
|
||||
const errorData = errorResponse.data || {};
|
||||
|
||||
@@ -166,32 +174,26 @@ service.interceptors.response.use(
|
||||
errorResponse.status === 403 ||
|
||||
error.response?.data?.code === 20004)
|
||||
) {
|
||||
isRefreshToken++;
|
||||
if (isRefreshToken === 1) {
|
||||
isRefreshToken = 0;
|
||||
return refresh(error);
|
||||
}
|
||||
return Promise.reject(buildRejectError(error, errorData));
|
||||
return service(await refresh(error.config || errorResponse.config));
|
||||
}
|
||||
if (errorResponse.status === 404) {
|
||||
return Promise.reject(buildRejectError(error, { message: "请求的资源不存在" }));
|
||||
if (!axios.isAxiosError(error) || axios.isCancel(error)) return Promise.reject(error);
|
||||
const rejected = buildRejectError(error, errorResponse.status === 404
|
||||
? { ...errorData, message: "请求的资源不存在" }
|
||||
: errorData);
|
||||
if (!error.config?.skipMessage && axios.isAxiosError(error) && !axios.isCancel(error)) {
|
||||
Message.error(rejected.message);
|
||||
markHandledRequestError(rejected);
|
||||
}
|
||||
if (error.message && !error.config?.skipMessage) {
|
||||
const _message =
|
||||
error.code === "ECONNABORTED"
|
||||
? "连接超时,请稍候再试!"
|
||||
: "网络错误,请稍后再试!";
|
||||
Message.error(errorData.message || _message);
|
||||
}
|
||||
return Promise.reject(buildRejectError(error, errorData));
|
||||
return Promise.reject(rejected);
|
||||
}
|
||||
);
|
||||
|
||||
const closeLoading = (target) => {
|
||||
if (!target.config || !target.config.loading) return true;
|
||||
if (typeof target.config?._loadingInstance?.close !== "function") return true;
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
target.config.loading.close();
|
||||
target.config._loadingInstance.close();
|
||||
delete target.config._loadingInstance;
|
||||
resolve();
|
||||
}, 200);
|
||||
});
|
||||
@@ -208,42 +210,22 @@ export default function request(options) {
|
||||
return service(options);
|
||||
}
|
||||
|
||||
/** 并发刷新共用同一个 Promise;不轮询,不保留上次刷新结果,失败后允许下次重试。 */
|
||||
function getTokenDebounce() {
|
||||
let lock = false;
|
||||
let success = false;
|
||||
let pending = null;
|
||||
return function () {
|
||||
if (!lock) {
|
||||
const oldRefreshToken = Storage.getItem("refreshToken");
|
||||
// 无 refreshToken(未登录)时不发起刷新请求,直接判失败,避免请求 /refresh/undefined 造成拦截器死锁
|
||||
if (!oldRefreshToken) {
|
||||
return Promise.resolve("fail");
|
||||
}
|
||||
lock = true;
|
||||
handleRefreshToken(oldRefreshToken)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
const { accessToken, refreshToken: newRefreshToken } = res.result;
|
||||
Storage.setItem("accessToken", accessToken);
|
||||
Storage.setItem("refreshToken", newRefreshToken);
|
||||
success = true;
|
||||
lock = false;
|
||||
} else {
|
||||
success = false;
|
||||
lock = false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
success = false;
|
||||
lock = false;
|
||||
});
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const timer = setInterval(() => {
|
||||
if (!lock) {
|
||||
clearInterval(timer);
|
||||
resolve(success ? "success" : "fail");
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
if (pending) return pending;
|
||||
const token = Storage.getItem("refreshToken");
|
||||
if (!token) return Promise.resolve("fail");
|
||||
pending = handleRefreshToken(token)
|
||||
.then((res) => {
|
||||
if (!res.success || !res.result?.accessToken || !res.result?.refreshToken) return "fail";
|
||||
Storage.setItem("accessToken", res.result.accessToken);
|
||||
Storage.setItem("refreshToken", res.result.refreshToken);
|
||||
return "success";
|
||||
})
|
||||
.catch(() => "fail")
|
||||
.finally(() => { pending = null; });
|
||||
return pending;
|
||||
};
|
||||
}
|
||||
|
||||
40
buyer/src/utils/requestError.js
Normal file
40
buyer/src/utils/requestError.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 已向用户提示的请求异常兜底;只识别本模块登记的错误,不屏蔽代码异常。
|
||||
* @author Bulbasaur
|
||||
* @since 2026-11-09
|
||||
*/
|
||||
const handledErrors = new WeakSet();
|
||||
|
||||
/** 登记已由请求层提示的错误,仍保持原请求的拒绝语义。 */
|
||||
export function markHandledRequestError(error) {
|
||||
handledErrors.add(error);
|
||||
// 开发服务器的错误遮罩有独立监听器,通过同一 Symbol 识别已提示的异常。
|
||||
Object.defineProperty(error, Symbol.for("lilishop.buyer.handledRequestError"), { value: true });
|
||||
return error;
|
||||
}
|
||||
|
||||
/** 判断错误是否已由请求层向用户展示。 */
|
||||
export function isHandledRequestError(error) {
|
||||
return !!error && typeof error === "object" && handledErrors.has(error);
|
||||
}
|
||||
|
||||
/** 页面处理预期请求失败;程序错误继续向上抛出,避免被静默吞掉。 */
|
||||
export function catchHandledRequestError(error) {
|
||||
if (!isHandledRequestError(error)) throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖未返回 Promise 的事件回调及 Vue 异步钩子;只有已提示的请求异常免于重复报错。
|
||||
* 未登记异常沿用已有 Vue 错误处理,无处理器时重新抛出供开发环境定位。
|
||||
*/
|
||||
export function setupRequestErrorHandling(app, target = window) {
|
||||
target.addEventListener("unhandledrejection", (event) => {
|
||||
if (isHandledRequestError(event.reason)) event.preventDefault();
|
||||
});
|
||||
const previous = app.config.errorHandler;
|
||||
app.config.errorHandler = (error, instance, info) => {
|
||||
if (isHandledRequestError(error)) return;
|
||||
if (previous) return previous(error, instance, info);
|
||||
throw error;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user