重构错误处理逻辑,移除旧的错误处理工具,新增 useErrorHandler 组合式 API,优化模态框组件,添加 ID 属性以支持更灵活的 DOM 操作

This commit is contained in:
Chuck1sn
2025-06-16 15:41:09 +08:00
parent ca42fbbda9
commit 772ad547bf
13 changed files with 225 additions and 224 deletions

View File

@@ -1,5 +1,5 @@
<template> <template>
<div tabindex="-1" aria-hidden="true" <div :id="id" tabindex="-1" aria-hidden="true"
class="bg-gray-900/50 hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full"> class="bg-gray-900/50 hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative p-4 w-full" :class="[maxWidthClass]"> <div class="relative p-4 w-full" :class="[maxWidthClass]">
<!-- Modal content --> <!-- Modal content -->
@@ -26,7 +26,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { computed, onMounted } from "vue";
import { initFlowbite, Modal } from "flowbite";
export type ModalSize = "xs" | "sm" | "md" | "lg" | "xl"; export type ModalSize = "xs" | "sm" | "md" | "lg" | "xl";
@@ -37,6 +38,8 @@ const props = defineProps<{
size?: ModalSize; size?: ModalSize;
/** 关闭模态框的回调函数 */ /** 关闭模态框的回调函数 */
closeModal: () => void; closeModal: () => void;
/** 模态框ID用于DOM选择 */
id?: string;
}>(); }>();
/** 根据size属性计算最大宽度类名 */ /** 根据size属性计算最大宽度类名 */
@@ -51,4 +54,9 @@ const maxWidthClass = computed(() => {
return sizes[props.size || "md"]; return sizes[props.size || "md"];
}); });
// 确保Flowbite初始化这对于PopupModal的正常工作至关重要
onMounted(() => {
initFlowbite();
});
</script> </script>

View File

@@ -1,30 +1,30 @@
<template> <template>
<BaseModal title="部门管理" size="md" :closeModal="closeModal"> <BaseModal :id="id" title="部门管理" size="md" :closeModal="closeModal">
<!-- Modal body --> <!-- Modal body -->
<div class="p-4 md:p-5"> <div class="p-4 md:p-5">
<div class="grid gap-4 mb-4 grid-cols-1"> <div class="grid gap-4 mb-4 grid-cols-1">
<div class="col-span-full"> <div class="col-span-full">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">部门名称</label> <label for="name" class="block mb-2 text-sm font-medium text-gray-900">部门名称</label>
<input type="text" id="name" v-model="formData.name" <input type="text" id="name" v-model="formData.name"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required /> required />
</div> </div>
<div class="col-span-full"> <div class="col-span-full">
<label for="category" class="block mb-2 text-sm font-medium text-gray-900">上级部门</label> <label for="category" class="block mb-2 text-sm font-medium text-gray-900">上级部门</label>
<select id="category" v-model="formData.parentId" <select id="category" v-model="formData.parentId"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"> class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
<option :value="null"></option> <option :value="null"></option>
<option v-for="dept in availableDepartments" :key="dept.id" :value="dept.id" <option v-for="dept in availableDepartments" :key="dept.id" :value="dept.id"
:selected="dept.id === formData.parentId">{{ dept.name }}</option> :selected="dept.id === formData.parentId">{{ dept.name }}</option>
</select> </select>
</div> </div>
</div> </div>
<button type="submit" @click="handleSubmit" <button type="submit" @click="handleSubmit"
class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5"> class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5">
保存 保存
</button> </button>
</div> </div>
</BaseModal> </BaseModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -36,12 +36,14 @@ import { onMounted, ref, watch } from "vue";
import { z } from "zod"; import { z } from "zod";
import BaseModal from "./BaseModal.vue"; import BaseModal from "./BaseModal.vue";
const { department, availableDepartments, onSubmit, closeModal } = defineProps<{ const { department, availableDepartments, onSubmit, closeModal, id } =
department?: components["schemas"]["Department"]; defineProps<{
availableDepartments?: components["schemas"]["Department"][]; department?: components["schemas"]["Department"];
closeModal: () => void; availableDepartments?: components["schemas"]["Department"][];
onSubmit: (department: DepartmentUpsertModel) => Promise<void>; closeModal: () => void;
}>(); onSubmit: (department: DepartmentUpsertModel) => Promise<void>;
id?: string;
}>();
const formData = ref<DepartmentUpsertModel>({ const formData = ref<DepartmentUpsertModel>({
name: "", name: "",
@@ -92,8 +94,4 @@ const handleSubmit = async () => {
} }
} }
}; };
onMounted(() => {
initFlowbite();
});
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<BaseModal title="大模型配置" size="md" :closeModal="closeModal"> <BaseModal :id="id" title="大模型配置" size="md" :closeModal="closeModal">
<!-- Modal body --> <!-- Modal body -->
<div class="p-4 md:p-5"> <div class="p-4 md:p-5">
<div class="grid gap-4 mb-4 grid-cols-1"> <div class="grid gap-4 mb-4 grid-cols-1">
@@ -63,10 +63,11 @@ import { onMounted, ref, watch } from "vue";
import { z } from "zod"; import { z } from "zod";
import BaseModal from "./BaseModal.vue"; import BaseModal from "./BaseModal.vue";
const { llm, onSubmit } = defineProps<{ const { llm, onSubmit, id } = defineProps<{
llm?: components["schemas"]["LlmVm"]; llm?: components["schemas"]["LlmVm"];
closeModal: () => void; closeModal: () => void;
onSubmit: (data: components["schemas"]["LlmVm"]) => Promise<void>; onSubmit: (data: components["schemas"]["LlmVm"]) => Promise<void>;
id: string;
}>(); }>();
// 初始化默认值避免undefined错误 // 初始化默认值避免undefined错误
@@ -139,8 +140,4 @@ const handleSubmit = async () => {
await onSubmit(validatedData); await onSubmit(validatedData);
updateFormData(undefined); updateFormData(undefined);
}; };
onMounted(() => {
initFlowbite();
});
</script> </script>

View File

@@ -1,43 +1,43 @@
<template> <template>
<BaseModal title="权限管理" size="md" :closeModal="closeModal"> <BaseModal :id="id" title="权限管理" size="md" :closeModal="closeModal">
<!-- Modal body --> <!-- Modal body -->
<div class="p-4 md:p-5"> <div class="p-4 md:p-5">
<div class="grid gap-4 mb-4 grid-cols-1"> <div class="grid gap-4 mb-4 grid-cols-1">
<div class="col-span-full"> <div class="col-span-full">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">权限名称</label> <label for="name" class="block mb-2 text-sm font-medium text-gray-900">权限名称</label>
<input type="text" id="name" v-model="formData.name" <input type="text" id="name" v-model="formData.name"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required /> required />
</div> </div>
<div class="col-span-full"> <div class="col-span-full">
<label for="code" class="block mb-2 text-sm font-medium text-gray-900">权限代码</label> <label for="code" class="block mb-2 text-sm font-medium text-gray-900">权限代码</label>
<input type="text" id="code" v-model="formData.code" <input type="text" id="code" v-model="formData.code"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required /> required />
</div> </div>
</div> </div>
<button type="submit" @click="handleSubmit" <button type="submit" @click="handleSubmit"
class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5"> class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5">
保存 保存
</button> </button>
</div> </div>
</BaseModal> </BaseModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { components } from "@/api/types/schema"; import type { components } from "@/api/types/schema";
import useAlertStore from "@/composables/store/useAlertStore"; import useAlertStore from "@/composables/store/useAlertStore";
import type { PermissionUpsertModel } from "@/types/permission"; import type { PermissionUpsertModel } from "@/types/permission";
import { initFlowbite } from "flowbite"; import { ref, watch } from "vue";
import { onMounted, ref, watch } from "vue";
import { z } from "zod"; import { z } from "zod";
import BaseModal from "./BaseModal.vue"; import BaseModal from "./BaseModal.vue";
const alertStore = useAlertStore(); const alertStore = useAlertStore();
const { permission, onSubmit, closeModal } = defineProps<{ const { permission, onSubmit, closeModal, id } = defineProps<{
permission?: components["schemas"]["PermissionRespDto"]; permission?: components["schemas"]["PermissionRespDto"];
onSubmit: (data: PermissionUpsertModel) => Promise<void>; onSubmit: (data: PermissionUpsertModel) => Promise<void>;
closeModal: () => void; closeModal: () => void;
id: string;
}>(); }>();
const formData = ref<PermissionUpsertModel>({ const formData = ref<PermissionUpsertModel>({

View File

@@ -1,5 +1,5 @@
<template> <template>
<BaseModal :closeModal="closeModal" size="sm"> <BaseModal :id="id" :closeModal="closeModal" size="sm">
<div class="p-5 md:p-6 text-center"> <div class="p-5 md:p-6 text-center">
<svg class="w-14 h-14 sm:w-16 sm:h-16 mx-auto text-red-600 mb-4" fill="none" stroke="currentColor" <svg class="w-14 h-14 sm:w-16 sm:h-16 mx-auto text-red-600 mb-4" fill="none" stroke="currentColor"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">

View File

@@ -1,37 +1,37 @@
<template> <template>
<BaseModal title="岗位管理" size="md" :closeModal="closeModal"> <BaseModal :id="id" title="岗位管理" size="md" :closeModal="closeModal">
<!-- Modal body --> <!-- Modal body -->
<div class="p-4 md:p-5"> <div class="p-4 md:p-5">
<div class="grid gap-4 mb-4 grid-cols-1"> <div class="grid gap-4 mb-4 grid-cols-1">
<div class="col-span-full"> <div class="col-span-full">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">岗位名称</label> <label for="name" class="block mb-2 text-sm font-medium text-gray-900">岗位名称</label>
<input type="text" id="name" v-model="formData.name" <input type="text" id="name" v-model="formData.name"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required /> required />
</div> </div>
</div> </div>
<button type="submit" @click="handleSubmit" <button type="submit" @click="handleSubmit"
class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5"> class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5">
保存 保存
</button> </button>
</div> </div>
</BaseModal> </BaseModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { components } from "@/api/types/schema"; import type { components } from "@/api/types/schema";
import useAlertStore from "@/composables/store/useAlertStore"; import useAlertStore from "@/composables/store/useAlertStore";
import type { PositionUpsertModel } from "@/types/position"; import type { PositionUpsertModel } from "@/types/position";
import { initFlowbite } from "flowbite"; import { ref, watch } from "vue";
import { onMounted, ref, watch } from "vue";
import { z } from "zod"; import { z } from "zod";
import BaseModal from "./BaseModal.vue"; import BaseModal from "./BaseModal.vue";
const alertStore = useAlertStore(); const alertStore = useAlertStore();
const { position, closeModal, onSubmit } = defineProps<{ const { position, closeModal, onSubmit, id } = defineProps<{
position?: components["schemas"]["Position"]; position?: components["schemas"]["Position"];
closeModal: () => void; closeModal: () => void;
onSubmit: (data: PositionUpsertModel) => Promise<void>; onSubmit: (data: PositionUpsertModel) => Promise<void>;
id: string;
}>(); }>();
const formData = ref<PositionUpsertModel>({ const formData = ref<PositionUpsertModel>({
@@ -78,8 +78,4 @@ const handleSubmit = async () => {
} }
} }
}; };
onMounted(() => {
initFlowbite();
});
</script> </script>

View File

@@ -1,43 +1,43 @@
<template> <template>
<BaseModal title="角色管理" size="md" :closeModal="closeModal"> <BaseModal :id="id" title="角色管理" size="md" :closeModal="closeModal">
<!-- Modal body --> <!-- Modal body -->
<div class="p-4 md:p-5"> <div class="p-4 md:p-5">
<div class="grid gap-4 mb-4 grid-cols-1"> <div class="grid gap-4 mb-4 grid-cols-1">
<div class="col-span-full"> <div class="col-span-full">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">角色名称</label> <label for="name" class="block mb-2 text-sm font-medium text-gray-900">角色名称</label>
<input type="text" id="name" v-model="formData.name" <input type="text" id="name" v-model="formData.name"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required /> required />
</div> </div>
<div class="col-span-full"> <div class="col-span-full">
<label for="code" class="block mb-2 text-sm font-medium text-gray-900">角色代码</label> <label for="code" class="block mb-2 text-sm font-medium text-gray-900">角色代码</label>
<input type="text" id="code" v-model="formData.code" <input type="text" id="code" v-model="formData.code"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required /> required />
</div> </div>
</div> </div>
<button type="submit" @click="handleSubmit" <button type="submit" @click="handleSubmit"
class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5"> class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5">
保存 保存
</button> </button>
</div> </div>
</BaseModal> </BaseModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { components } from "@/api/types/schema"; import type { components } from "@/api/types/schema";
import useAlertStore from "@/composables/store/useAlertStore"; import useAlertStore from "@/composables/store/useAlertStore";
import type { RoleUpsertModel } from "@/types/role"; import type { RoleUpsertModel } from "@/types/role";
import { initFlowbite } from "flowbite"; import { ref, watch } from "vue";
import { onMounted, ref, watch } from "vue";
import { z } from "zod"; import { z } from "zod";
import BaseModal from "./BaseModal.vue"; import BaseModal from "./BaseModal.vue";
const alertStore = useAlertStore(); const alertStore = useAlertStore();
const { role, closeModal, onSubmit } = defineProps<{ const { role, closeModal, onSubmit, id } = defineProps<{
role?: components["schemas"]["RoleRespDto"]; role?: components["schemas"]["RoleRespDto"];
closeModal: () => void; closeModal: () => void;
onSubmit: (data: RoleUpsertModel) => Promise<void>; onSubmit: (data: RoleUpsertModel) => Promise<void>;
id: string;
}>(); }>();
const formData = ref<RoleUpsertModel>({ const formData = ref<RoleUpsertModel>({
@@ -92,8 +92,4 @@ const handleSubmit = async () => {
} }
} }
}; };
onMounted(() => {
initFlowbite();
});
</script> </script>

View File

@@ -1,34 +1,34 @@
<template> <template>
<BaseModal title="定时任务配置" size="md" :closeModal="closeModal"> <BaseModal :id="id" title="定时任务配置" size="md" :closeModal="closeModal">
<!-- Modal body --> <!-- Modal body -->
<div class="p-4 md:p-5"> <div class="p-4 md:p-5">
<div class="grid gap-4 mb-4 grid-cols-1"> <div class="grid gap-4 mb-4 grid-cols-1">
<div class="col-span-full"> <div class="col-span-full">
<label for="cronExpression" class="block mb-2 text-sm font-medium text-gray-900">CRON表达式</label> <label for="cronExpression" class="block mb-2 text-sm font-medium text-gray-900">CRON表达式</label>
<input type="text" id="cronExpression" v-model="formData.cronExpression" <input type="text" id="cronExpression" v-model="formData.cronExpression"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required /> required />
</div> </div>
</div> </div>
<button type="submit" @click="handleSubmit" <button type="submit" @click="handleSubmit"
class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5"> class="w-auto text-sm px-4 py-2 text-white flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-center self-start mt-5">
保存 保存
</button> </button>
</div> </div>
</BaseModal> </BaseModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { components } from "@/api/types/schema"; import type { components } from "@/api/types/schema";
import { initFlowbite } from "flowbite"; import { ref, watch } from "vue";
import { onMounted, ref, watch } from "vue";
import { z } from "zod"; import { z } from "zod";
import BaseModal from "./BaseModal.vue"; import BaseModal from "./BaseModal.vue";
const { job, closeModal, onSubmit } = defineProps<{ const { job, closeModal, onSubmit, id } = defineProps<{
job?: components["schemas"]["JobTriggerDto"]; job?: components["schemas"]["JobTriggerDto"];
closeModal: () => void; closeModal: () => void;
onSubmit: (cronExpression: string) => Promise<void>; onSubmit: (cronExpression: string) => Promise<void>;
id: string;
}>(); }>();
const formData = ref({ const formData = ref({

View File

@@ -1,5 +1,5 @@
<template> <template>
<BaseModal title="用户管理" size="md" :closeModal="closeModal"> <BaseModal :id="id" title="用户管理" size="md" :closeModal="closeModal">
<!-- Modal body --> <!-- Modal body -->
<form class="p-4 md:p-5"> <form class="p-4 md:p-5">
<div class="space-y-4"> <div class="space-y-4">
@@ -53,10 +53,11 @@ import { onMounted, ref, watch } from "vue";
import { z } from "zod"; import { z } from "zod";
import BaseModal from "./BaseModal.vue"; import BaseModal from "./BaseModal.vue";
const { user, onSubmit } = defineProps<{ const { user, onSubmit, id } = defineProps<{
user?: components["schemas"]["UserRolePermissionDto"]; user?: components["schemas"]["UserRolePermissionDto"];
closeModal: () => void; closeModal: () => void;
onSubmit: (data: UserUpsertSubmitModel) => Promise<void>; onSubmit: (data: UserUpsertSubmitModel) => Promise<void>;
id: string;
}>(); }>();
const formData = ref(); const formData = ref();

View File

@@ -0,0 +1,72 @@
import useUserAuth from "@/composables/auth/useUserAuth";
import useAlertStore from "@/composables/store/useAlertStore";
import { Routes } from "@/router/constants";
import {
ForbiddenError,
InternalServerError,
RequestError,
UnAuthError,
ValidationError,
} from "@/types/error";
import { useRouter } from "vue-router";
import { z } from "zod";
/**
* 错误处理 Composable
*/
export function useErrorHandler() {
const router = useRouter();
const { signOut } = useUserAuth();
const alertStore = useAlertStore();
/**
* 处理各类错误,显示对应的提示信息
*/
const handleError = (err: unknown) => {
console.error(err);
try {
if (err instanceof ValidationError) {
alertStore.showAlert({
level: "error",
content: err.message,
});
} else if (err instanceof UnAuthError) {
signOut();
router.push(Routes.LOGIN.path);
alertStore.showAlert({
level: "error",
content: err.message,
});
} else if (err instanceof ForbiddenError || err instanceof RequestError) {
alertStore.showAlert({
level: "error",
content: err.message,
});
} else if (err instanceof InternalServerError) {
alertStore.showAlert({
level: "error",
content: err.detail ?? err.message,
});
} else if (err instanceof z.ZodError) {
alertStore.showAlert({
level: "error",
content: err.errors[0].message,
});
} else {
alertStore.showAlert({
level: "error",
content: "发生异常,请稍候再试",
});
}
} catch (e) {
console.error("Error handler failed:", e);
}
};
return {
handleError,
};
}
export default useErrorHandler;

View File

@@ -3,12 +3,10 @@ import "./assets/main.css";
import { createPinia } from "pinia"; import { createPinia } from "pinia";
import { createApp } from "vue"; import { createApp } from "vue";
import App from "./App.vue";
import useUserAuth from "./composables/auth/useUserAuth";
import useAlertStore from "./composables/store/useAlertStore";
import router from "./router";
import makeErrorHandler from "./utils/errorHandler";
import VueDatePicker from "@vuepic/vue-datepicker"; import VueDatePicker from "@vuepic/vue-datepicker";
import App from "./App.vue";
import useErrorHandler from "./composables/useErrorHandler";
import router from "./router";
import "@vuepic/vue-datepicker/dist/main.css"; import "@vuepic/vue-datepicker/dist/main.css";
import "./assets/datepicker.css"; import "./assets/datepicker.css";
@@ -27,11 +25,13 @@ async function enableMocking() {
enableMocking().then(() => { enableMocking().then(() => {
const app = createApp(App); const app = createApp(App);
app.use(createPinia()); app.use(createPinia());
const { signOut } = useUserAuth();
const { showAlert } = useAlertStore();
app.use(router); app.use(router);
const errorHandler = makeErrorHandler(router, signOut, showAlert);
app.config.errorHandler = errorHandler; const { handleError } = useErrorHandler();
app.config.errorHandler = (err, instance, info) => {
handleError(err);
};
app.component("VueDatePicker", VueDatePicker); app.component("VueDatePicker", VueDatePicker);
app.mount("#app"); app.mount("#app");
}); });

View File

@@ -1,67 +0,0 @@
import type { ComponentPublicInstance } from "vue";
import type { Router } from "vue-router";
import {
ForbiddenError,
InternalServerError,
RequestError,
ValidationError,
UnAuthError,
} from "../types/error";
import { z } from "zod";
import { Routes } from "../router/constants";
const makeErrorHandler =
(
router: Router,
signOut: () => void,
showAlert: ({
content,
level,
}: {
content: string;
level: "info" | "success" | "warning" | "error";
}) => void,
) =>
(err: unknown, instance: ComponentPublicInstance | null, info: string) => {
console.error(err);
if (err instanceof ValidationError) {
showAlert({
level: "error",
content: err.message,
});
} else if (err instanceof UnAuthError) {
signOut();
router.push(Routes.LOGIN.path);
showAlert({
level: "error",
content: err.message,
});
} else if (err instanceof ForbiddenError) {
showAlert({
level: "error",
content: err.message,
});
} else if (err instanceof RequestError) {
showAlert({
level: "error",
content: err.message,
});
} else if (err instanceof InternalServerError) {
showAlert({
level: "error",
content: err.detail ?? err.message,
});
} else if (err instanceof z.ZodError) {
showAlert({
level: "error",
content: err.errors[0].message,
});
} else {
showAlert({
level: "error",
content: "发生异常,请稍候再试",
});
}
};
export default makeErrorHandler;

View File

@@ -115,7 +115,7 @@
<RoleDeleteModal :id="'role-delete-modal'" :closeModal="() => { <RoleDeleteModal :id="'role-delete-modal'" :closeModal="() => {
roleDeleteModal!.hide(); roleDeleteModal!.hide();
}" :onSubmit="handleDeletedModalSubmit" title="确定删除该角色吗" content="删除角色"></RoleDeleteModal> }" :onSubmit="handleDeletedModalSubmit" title="确定删除该角色吗" content="删除角色"></RoleDeleteModal>
<RoleUpsertModal :onSubmit="handleUpsertModalSubmit" :closeModal="() => { <RoleUpsertModal :id="'role-upsert-modal'" :onSubmit="handleUpsertModalSubmit" :closeModal="() => {
roleUpsertModal!.hide(); roleUpsertModal!.hide();
}" :role="selectedRole"> }" :role="selectedRole">
</RoleUpsertModal> </RoleUpsertModal>