mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-13 21:27:19 +08:00
重构错误处理逻辑,移除旧的错误处理工具,新增 useErrorHandler 组合式 API,优化模态框组件,添加 ID 属性以支持更灵活的 DOM 操作
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<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">
|
||||
<div class="relative p-4 w-full" :class="[maxWidthClass]">
|
||||
<!-- Modal content -->
|
||||
@@ -26,7 +26,8 @@
|
||||
</template>
|
||||
|
||||
<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";
|
||||
|
||||
@@ -37,6 +38,8 @@ const props = defineProps<{
|
||||
size?: ModalSize;
|
||||
/** 关闭模态框的回调函数 */
|
||||
closeModal: () => void;
|
||||
/** 模态框ID,用于DOM选择 */
|
||||
id?: string;
|
||||
}>();
|
||||
|
||||
/** 根据size属性计算最大宽度类名 */
|
||||
@@ -51,4 +54,9 @@ const maxWidthClass = computed(() => {
|
||||
|
||||
return sizes[props.size || "md"];
|
||||
});
|
||||
|
||||
// 确保Flowbite初始化,这对于PopupModal的正常工作至关重要
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<template>
|
||||
<BaseModal title="部门管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">部门名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
<label for="category" class="block mb-2 text-sm font-medium text-gray-900">上级部门</label>
|
||||
<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">
|
||||
<option :value="null">无</option>
|
||||
<option v-for="dept in availableDepartments" :key="dept.id" :value="dept.id"
|
||||
:selected="dept.id === formData.parentId">{{ dept.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
<BaseModal :id="id" title="部门管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">部门名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
<label for="category" class="block mb-2 text-sm font-medium text-gray-900">上级部门</label>
|
||||
<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">
|
||||
<option :value="null">无</option>
|
||||
<option v-for="dept in availableDepartments" :key="dept.id" :value="dept.id"
|
||||
:selected="dept.id === formData.parentId">{{ dept.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -36,12 +36,14 @@ import { onMounted, ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import BaseModal from "./BaseModal.vue";
|
||||
|
||||
const { department, availableDepartments, onSubmit, closeModal } = defineProps<{
|
||||
department?: components["schemas"]["Department"];
|
||||
availableDepartments?: components["schemas"]["Department"][];
|
||||
closeModal: () => void;
|
||||
onSubmit: (department: DepartmentUpsertModel) => Promise<void>;
|
||||
}>();
|
||||
const { department, availableDepartments, onSubmit, closeModal, id } =
|
||||
defineProps<{
|
||||
department?: components["schemas"]["Department"];
|
||||
availableDepartments?: components["schemas"]["Department"][];
|
||||
closeModal: () => void;
|
||||
onSubmit: (department: DepartmentUpsertModel) => Promise<void>;
|
||||
id?: string;
|
||||
}>();
|
||||
|
||||
const formData = ref<DepartmentUpsertModel>({
|
||||
name: "",
|
||||
@@ -92,8 +94,4 @@ const handleSubmit = async () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseModal title="大模型配置" size="md" :closeModal="closeModal">
|
||||
<BaseModal :id="id" title="大模型配置" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<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 BaseModal from "./BaseModal.vue";
|
||||
|
||||
const { llm, onSubmit } = defineProps<{
|
||||
const { llm, onSubmit, id } = defineProps<{
|
||||
llm?: components["schemas"]["LlmVm"];
|
||||
closeModal: () => void;
|
||||
onSubmit: (data: components["schemas"]["LlmVm"]) => Promise<void>;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
// 初始化默认值,避免undefined错误
|
||||
@@ -139,8 +140,4 @@ const handleSubmit = async () => {
|
||||
await onSubmit(validatedData);
|
||||
updateFormData(undefined);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
<template>
|
||||
<BaseModal title="权限管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">权限名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
<label for="code" class="block mb-2 text-sm font-medium text-gray-900">权限代码</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
<BaseModal :id="id" title="权限管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">权限名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
<label for="code" class="block mb-2 text-sm font-medium text-gray-900">权限代码</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from "@/api/types/schema";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import type { PermissionUpsertModel } from "@/types/permission";
|
||||
import { initFlowbite } from "flowbite";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import BaseModal from "./BaseModal.vue";
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
const { permission, onSubmit, closeModal } = defineProps<{
|
||||
const { permission, onSubmit, closeModal, id } = defineProps<{
|
||||
permission?: components["schemas"]["PermissionRespDto"];
|
||||
onSubmit: (data: PermissionUpsertModel) => Promise<void>;
|
||||
closeModal: () => void;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const formData = ref<PermissionUpsertModel>({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseModal :closeModal="closeModal" size="sm">
|
||||
<BaseModal :id="id" :closeModal="closeModal" size="sm">
|
||||
<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"
|
||||
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
<template>
|
||||
<BaseModal title="岗位管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">岗位名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
<BaseModal :id="id" title="岗位管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">岗位名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from "@/api/types/schema";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import type { PositionUpsertModel } from "@/types/position";
|
||||
import { initFlowbite } from "flowbite";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import BaseModal from "./BaseModal.vue";
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
const { position, closeModal, onSubmit } = defineProps<{
|
||||
const { position, closeModal, onSubmit, id } = defineProps<{
|
||||
position?: components["schemas"]["Position"];
|
||||
closeModal: () => void;
|
||||
onSubmit: (data: PositionUpsertModel) => Promise<void>;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const formData = ref<PositionUpsertModel>({
|
||||
@@ -78,8 +78,4 @@ const handleSubmit = async () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
<template>
|
||||
<BaseModal title="角色管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">角色名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
<label for="code" class="block mb-2 text-sm font-medium text-gray-900">角色代码</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
<BaseModal :id="id" title="角色管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">角色名称</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
<label for="code" class="block mb-2 text-sm font-medium text-gray-900">角色代码</label>
|
||||
<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"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from "@/api/types/schema";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import type { RoleUpsertModel } from "@/types/role";
|
||||
import { initFlowbite } from "flowbite";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import BaseModal from "./BaseModal.vue";
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
const { role, closeModal, onSubmit } = defineProps<{
|
||||
const { role, closeModal, onSubmit, id } = defineProps<{
|
||||
role?: components["schemas"]["RoleRespDto"];
|
||||
closeModal: () => void;
|
||||
onSubmit: (data: RoleUpsertModel) => Promise<void>;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const formData = ref<RoleUpsertModel>({
|
||||
@@ -92,8 +92,4 @@ const handleSubmit = async () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
<template>
|
||||
<BaseModal title="定时任务配置" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<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"
|
||||
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 />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
<BaseModal :id="id" title="定时任务配置" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<div class="p-4 md:p-5">
|
||||
<div class="grid gap-4 mb-4 grid-cols-1">
|
||||
<div class="col-span-full">
|
||||
<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"
|
||||
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 />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from "@/api/types/schema";
|
||||
import { initFlowbite } from "flowbite";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import BaseModal from "./BaseModal.vue";
|
||||
|
||||
const { job, closeModal, onSubmit } = defineProps<{
|
||||
const { job, closeModal, onSubmit, id } = defineProps<{
|
||||
job?: components["schemas"]["JobTriggerDto"];
|
||||
closeModal: () => void;
|
||||
onSubmit: (cronExpression: string) => Promise<void>;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const formData = ref({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseModal title="用户管理" size="md" :closeModal="closeModal">
|
||||
<BaseModal :id="id" title="用户管理" size="md" :closeModal="closeModal">
|
||||
<!-- Modal body -->
|
||||
<form class="p-4 md:p-5">
|
||||
<div class="space-y-4">
|
||||
@@ -53,10 +53,11 @@ import { onMounted, ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import BaseModal from "./BaseModal.vue";
|
||||
|
||||
const { user, onSubmit } = defineProps<{
|
||||
const { user, onSubmit, id } = defineProps<{
|
||||
user?: components["schemas"]["UserRolePermissionDto"];
|
||||
closeModal: () => void;
|
||||
onSubmit: (data: UserUpsertSubmitModel) => Promise<void>;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const formData = ref();
|
||||
|
||||
72
frontend/src/composables/useErrorHandler.ts
Normal file
72
frontend/src/composables/useErrorHandler.ts
Normal 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;
|
||||
@@ -3,12 +3,10 @@ import "./assets/main.css";
|
||||
import { createPinia } from "pinia";
|
||||
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 App from "./App.vue";
|
||||
import useErrorHandler from "./composables/useErrorHandler";
|
||||
import router from "./router";
|
||||
import "@vuepic/vue-datepicker/dist/main.css";
|
||||
import "./assets/datepicker.css";
|
||||
|
||||
@@ -27,11 +25,13 @@ async function enableMocking() {
|
||||
enableMocking().then(() => {
|
||||
const app = createApp(App);
|
||||
app.use(createPinia());
|
||||
const { signOut } = useUserAuth();
|
||||
const { showAlert } = useAlertStore();
|
||||
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.mount("#app");
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -115,7 +115,7 @@
|
||||
<RoleDeleteModal :id="'role-delete-modal'" :closeModal="() => {
|
||||
roleDeleteModal!.hide();
|
||||
}" :onSubmit="handleDeletedModalSubmit" title="确定删除该角色吗" content="删除角色"></RoleDeleteModal>
|
||||
<RoleUpsertModal :onSubmit="handleUpsertModalSubmit" :closeModal="() => {
|
||||
<RoleUpsertModal :id="'role-upsert-modal'" :onSubmit="handleUpsertModalSubmit" :closeModal="() => {
|
||||
roleUpsertModal!.hide();
|
||||
}" :role="selectedRole">
|
||||
</RoleUpsertModal>
|
||||
|
||||
Reference in New Issue
Block a user