更新
This commit is contained in:
@@ -1,126 +1,305 @@
|
||||
<template>
|
||||
<div
|
||||
class="fixed inset-0 bg-black/40 dark:bg-black/60 flex items-center justify-center z-[999]"
|
||||
@click.self="closeModal"
|
||||
>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-dialog dark:shadow-lg w-full max-w-md mx-4 transform transition-all duration-300 ease-in-out border border-transparent dark:border-gray-700">
|
||||
<div class="gva-error-preview" @click.self="closeModal">
|
||||
<div class="gva-error-preview__panel">
|
||||
<!-- 弹窗头部 -->
|
||||
<div class="p-5 border-b border-gray-100 dark:border-gray-700 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-100">{{ displayData.title }}</h3>
|
||||
<div class="text-gray-400 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-200 transition-colors cursor-pointer" @click="closeModal">
|
||||
<div class="gva-error-preview__header">
|
||||
<h3 class="gva-error-preview__title">{{ displayData.title }}</h3>
|
||||
<div class="gva-error-preview__close" @click="closeModal">
|
||||
<close class="h-6 w-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 弹窗内容 -->
|
||||
<div class="p-6 pt-0">
|
||||
<div class="gva-error-preview__body">
|
||||
<!-- 错误类型 -->
|
||||
<div class="mb-4">
|
||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-2">错误类型</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="gva-error-preview__section">
|
||||
<div class="gva-error-preview__label">错误类型</div>
|
||||
<div class="gva-error-preview__type-row">
|
||||
<lock v-if="displayData.icon === 'lock'" :class="['w-5 h-5', displayData.color]" />
|
||||
<warn v-if="displayData.icon === 'warn'" :class="['w-5 h-5', displayData.color]" />
|
||||
<server v-if="displayData.icon === 'server'" :class="['w-5 h-5', displayData.color]" />
|
||||
<span class="font-medium text-gray-800 dark:text-gray-100">{{ displayData.type }}</span>
|
||||
<span class="gva-error-preview__type-text">{{ displayData.type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 具体错误 -->
|
||||
<div class="mb-6">
|
||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-2">具体错误</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-900/40 rounded-lg p-3 text-sm text-gray-700 dark:text-gray-200 leading-relaxed">
|
||||
<div class="gva-error-preview__section gva-error-preview__section--message">
|
||||
<div class="gva-error-preview__label">具体错误</div>
|
||||
<div class="gva-error-preview__message">
|
||||
{{ displayData.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<div v-if="displayData.tips">
|
||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-2">提示</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<idea class="text-blue-500 dark:text-blue-400 w-5 h-5" />
|
||||
<p class="text-sm text-gray-600 dark:text-gray-300">{{ displayData.tips }}</p>
|
||||
<div v-if="displayData.tips" class="gva-error-preview__section">
|
||||
<div class="gva-error-preview__label">提示</div>
|
||||
<div class="gva-error-preview__tips-row">
|
||||
<idea class="gva-error-preview__tips-icon w-5 h-5" />
|
||||
<p class="gva-error-preview__tips-text">{{ displayData.tips }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 弹窗底部 -->
|
||||
<div class="py-2 px-4 border-t border-gray-100 dark:border-gray-700 flex justify-end">
|
||||
<div class="px-4 py-2 bg-blue-600 dark:bg-blue-500 text-white dark:text-gray-100 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors font-medium text-sm shadow-sm cursor-pointer" @click="handleConfirm">
|
||||
确定
|
||||
</div>
|
||||
<div class="gva-error-preview__footer">
|
||||
<div class="gva-error-preview__confirm" @click="handleConfirm">确定</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, computed } from 'vue';
|
||||
import { defineProps, defineEmits, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
errorData: {
|
||||
type: Object,
|
||||
required: true
|
||||
const props = defineProps({
|
||||
errorData: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['close', 'confirm'])
|
||||
|
||||
const presetErrors = {
|
||||
500: {
|
||||
title: '检测到接口错误',
|
||||
type: '服务器发生内部错误',
|
||||
icon: 'server',
|
||||
color: 'gva-error-preview__icon--danger',
|
||||
tips: '此类错误内容常见于后台panic,请先查看后台日志,如果影响您正常使用可强制登出清理缓存'
|
||||
},
|
||||
404: {
|
||||
title: '资源未找到',
|
||||
type: 'Not Found',
|
||||
icon: 'warn',
|
||||
color: 'gva-error-preview__icon--warning',
|
||||
tips: '此类错误多为接口未注册(或未重启)或者请求路径(方法)与api路径(方法)不符--如果为自动化代码请检查是否存在空格'
|
||||
},
|
||||
401: {
|
||||
title: '身份认证失败',
|
||||
type: '身份令牌无效',
|
||||
icon: 'lock',
|
||||
color: 'gva-error-preview__icon--purple',
|
||||
tips: '您的身份认证已过期或无效,请重新登录。'
|
||||
},
|
||||
network: {
|
||||
title: '网络错误',
|
||||
type: 'Network Error',
|
||||
icon: 'fa-wifi-slash',
|
||||
color: 'gva-error-preview__icon--muted',
|
||||
tips: '无法连接到服务器,请检查您的网络连接。'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(['close', 'confirm']);
|
||||
const displayData = computed(() => {
|
||||
const preset = presetErrors[props.errorData.code]
|
||||
if (preset) {
|
||||
return {
|
||||
...preset,
|
||||
message: props.errorData.message || '没有提供额外信息。'
|
||||
}
|
||||
}
|
||||
|
||||
const presetErrors = {
|
||||
500: {
|
||||
title: '检测到接口错误',
|
||||
type: '服务器发生内部错误',
|
||||
icon: 'server',
|
||||
color: 'text-red-500 dark:text-red-400',
|
||||
tips: '此类错误内容常见于后台panic,请先查看后台日志,如果影响您正常使用可强制登出清理缓存'
|
||||
},
|
||||
404: {
|
||||
title: '资源未找到',
|
||||
type: 'Not Found',
|
||||
icon: 'warn',
|
||||
color: 'text-orange-500 dark:text-orange-400',
|
||||
tips: '此类错误多为接口未注册(或未重启)或者请求路径(方法)与api路径(方法)不符--如果为自动化代码请检查是否存在空格'
|
||||
},
|
||||
401: {
|
||||
title: '身份认证失败',
|
||||
type: '身份令牌无效',
|
||||
icon: 'lock',
|
||||
color: 'text-purple-500 dark:text-purple-400',
|
||||
tips: '您的身份认证已过期或无效,请重新登录。'
|
||||
},
|
||||
'network': {
|
||||
title: '网络错误',
|
||||
type: 'Network Error',
|
||||
icon: 'fa-wifi-slash',
|
||||
color: 'text-gray-500 dark:text-gray-400',
|
||||
tips: '无法连接到服务器,请检查您的网络连接。'
|
||||
}
|
||||
};
|
||||
|
||||
const displayData = computed(() => {
|
||||
const preset = presetErrors[props.errorData.code];
|
||||
if (preset) {
|
||||
return {
|
||||
...preset,
|
||||
message: props.errorData.message || '没有提供额外信息。'
|
||||
};
|
||||
title: '未知错误',
|
||||
type: '检测到请求错误',
|
||||
icon: 'fa-question-circle',
|
||||
color: 'gva-error-preview__icon--muted',
|
||||
message: props.errorData.message || '发生了一个未知错误。',
|
||||
tips: '请检查控制台获取更多信息。'
|
||||
}
|
||||
})
|
||||
|
||||
const closeModal = () => {
|
||||
emits('close')
|
||||
}
|
||||
|
||||
return {
|
||||
title: '未知错误',
|
||||
type: '检测到请求错误',
|
||||
icon: 'fa-question-circle',
|
||||
color: 'text-gray-400 dark:text-gray-300',
|
||||
message: props.errorData.message || '发生了一个未知错误。',
|
||||
tips: '请检查控制台获取更多信息。'
|
||||
};
|
||||
});
|
||||
|
||||
const closeModal = () => {
|
||||
emits('close')
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
emits('confirm', props.errorData.code);
|
||||
closeModal();
|
||||
};
|
||||
const handleConfirm = () => {
|
||||
emits('confirm', props.errorData.code)
|
||||
closeModal()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.gva-error-preview {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
background: rgba(2, 6, 23, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.gva-error-preview__panel {
|
||||
width: 100%;
|
||||
max-width: 28rem;
|
||||
overflow: hidden;
|
||||
color: var(--tech-text);
|
||||
border: 1px solid var(--tech-border-strong);
|
||||
border-radius: var(--tech-radius);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(9, 24, 48, 0.96), rgba(5, 16, 32, 0.92)),
|
||||
radial-gradient(circle at 100% 0%, rgba(0, 212, 255, 0.1), transparent 38%);
|
||||
box-shadow:
|
||||
var(--tech-shadow),
|
||||
0 0 0 1px rgba(0, 212, 255, 0.08),
|
||||
inset 0 0 32px rgba(0, 212, 255, 0.06);
|
||||
animation: gva-error-preview-in 0.28s ease-out;
|
||||
}
|
||||
|
||||
@keyframes gva-error-preview-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px) scale(0.98);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.gva-error-preview__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18px 20px;
|
||||
border-bottom: 1px solid rgba(0, 212, 255, 0.18);
|
||||
background: rgba(2, 8, 23, 0.48);
|
||||
}
|
||||
|
||||
.gva-error-preview__title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--tech-text-strong);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.gva-error-preview__close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--tech-text-muted);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--tech-text-strong);
|
||||
}
|
||||
}
|
||||
|
||||
.gva-error-preview__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.gva-error-preview__section {
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&--message {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.gva-error-preview__label {
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--tech-cyan);
|
||||
}
|
||||
|
||||
.gva-error-preview__type-row,
|
||||
.gva-error-preview__tips-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gva-error-preview__type-text {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--tech-text-strong);
|
||||
}
|
||||
|
||||
.gva-error-preview__message {
|
||||
padding: 12px 14px;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
color: var(--tech-text-strong);
|
||||
word-break: break-word;
|
||||
border: 1px solid rgba(0, 212, 255, 0.16);
|
||||
border-radius: 10px;
|
||||
background: rgba(2, 8, 23, 0.72);
|
||||
box-shadow: inset 0 0 18px rgba(0, 212, 255, 0.05);
|
||||
}
|
||||
|
||||
.gva-error-preview__tips-icon {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
color: var(--tech-cyan);
|
||||
}
|
||||
|
||||
.gva-error-preview__tips-text {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
color: var(--tech-text-muted);
|
||||
}
|
||||
|
||||
.gva-error-preview__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 12px 20px 16px;
|
||||
border-top: 1px solid rgba(0, 212, 255, 0.18);
|
||||
}
|
||||
|
||||
.gva-error-preview__confirm {
|
||||
min-width: 88px;
|
||||
padding: 8px 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
color: #031424;
|
||||
cursor: pointer;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, #67e8f9 0%, var(--tech-primary) 46%, #2563eb 100%);
|
||||
box-shadow:
|
||||
0 0 20px var(--tech-primary-glow),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.34);
|
||||
transition:
|
||||
transform 0.22s ease,
|
||||
box-shadow 0.22s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow:
|
||||
0 0 24px var(--tech-primary-glow),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.34);
|
||||
}
|
||||
}
|
||||
|
||||
.gva-error-preview__icon--danger {
|
||||
color: var(--tech-danger);
|
||||
}
|
||||
|
||||
.gva-error-preview__icon--warning {
|
||||
color: var(--tech-amber);
|
||||
}
|
||||
|
||||
.gva-error-preview__icon--purple {
|
||||
color: #a78bfa;
|
||||
}
|
||||
|
||||
.gva-error-preview__icon--muted {
|
||||
color: var(--tech-text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -294,8 +294,9 @@ body:not(.login-light-mode) {
|
||||
}
|
||||
}
|
||||
.el-menu--vertical {
|
||||
.el-menu-item {
|
||||
.el-menu-item:not(.gva-tech-menu-item) {
|
||||
border-radius: 2px;
|
||||
|
||||
&.is-active {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
color: #fff !important;
|
||||
@@ -317,8 +318,45 @@ body:not(.login-light-mode) {
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active{
|
||||
color: var(--el-color-primary)!important;
|
||||
.el-menu-item.is-active:not(.gva-tech-menu-item) {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.gva-tech-aside,
|
||||
.gva-tech-aside-head,
|
||||
.gva-tech-aside-rail,
|
||||
.gva-tech-aside-secondary {
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* 一级菜单 */
|
||||
.gva-tech-aside-rail .el-menu-item.is-active,
|
||||
.el-sub-menu.is-active > .el-sub-menu__title,
|
||||
.gva-tech-aside > .el-scrollbar .el-menu > .gva-tech-menu-item.is-active {
|
||||
color: #ffffff !important;
|
||||
background: linear-gradient(90deg, rgba(0, 212, 255, 0.24), rgba(59, 130, 246, 0.13)) !important;
|
||||
box-shadow: inset 3px 0 0 var(--tech-primary), 0 0 18px rgba(0, 212, 255, 0.16);
|
||||
}
|
||||
|
||||
/* 二级菜单 */
|
||||
.gva-tech-aside-secondary .gva-tech-menu-item.is-active,
|
||||
.el-sub-menu .el-menu .gva-tech-menu-item.is-active {
|
||||
color: #67e8f9 !important;
|
||||
font-weight: 500;
|
||||
background: rgba(0, 212, 255, 0.1) !important;
|
||||
box-shadow: inset 2px 0 0 rgba(0, 212, 255, 0.72);
|
||||
}
|
||||
|
||||
.el-menu--horizontal {
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu.is-active > .el-sub-menu__title {
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, rgba(0, 212, 255, 0.2), rgba(59, 130, 246, 0.16)) !important;
|
||||
box-shadow: 0 0 18px rgba(0, 212, 255, 0.18);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu__title.el-tooltip__trigger,
|
||||
|
||||
@@ -244,12 +244,22 @@ html.light {
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active,
|
||||
/* 一级菜单:侧栏图标轨 + 子菜单标题 */
|
||||
.gva-tech-aside-rail .el-menu-item.is-active,
|
||||
.el-sub-menu.is-active > .el-sub-menu__title {
|
||||
color: #ffffff !important;
|
||||
background: linear-gradient(90deg, rgba(0, 212, 255, 0.24), rgba(59, 130, 246, 0.13)) !important;
|
||||
box-shadow: inset 3px 0 0 var(--tech-primary), 0 0 18px rgba(0, 212, 255, 0.16);
|
||||
}
|
||||
|
||||
/* 二级菜单:次级侧栏 + 子菜单内叶子项 */
|
||||
.gva-tech-aside-secondary .gva-tech-menu-item.is-active,
|
||||
.el-sub-menu .el-menu .gva-tech-menu-item.is-active {
|
||||
color: #67e8f9 !important;
|
||||
font-weight: 500;
|
||||
background: rgba(0, 212, 255, 0.1) !important;
|
||||
box-shadow: inset 2px 0 0 rgba(0, 212, 255, 0.72);
|
||||
}
|
||||
}
|
||||
|
||||
.gva-tech-collapse-btn {
|
||||
|
||||
@@ -9,14 +9,7 @@ export const formatBoolean = (bool) => {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
export const formatDate = (time) => {
|
||||
if (time !== null && time !== '') {
|
||||
var date = new Date(time)
|
||||
return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const filterDict = (value, options) => {
|
||||
// 递归查找函数
|
||||
const findInOptions = (opts, targetValue) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<el-menu-item
|
||||
class="gva-tech-menu-item"
|
||||
:index="routerInfo.name"
|
||||
:style="{
|
||||
height: sideHeight
|
||||
@@ -46,4 +47,57 @@ const isCollapse = inject('isCollapse', {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
<style lang="scss" scoped>
|
||||
.gva-tech-menu-item {
|
||||
position: relative;
|
||||
margin: 4px 0;
|
||||
color: var(--tech-text-muted) !important;
|
||||
border-radius: 12px !important;
|
||||
transition:
|
||||
color 0.22s ease,
|
||||
background 0.22s ease,
|
||||
box-shadow 0.22s ease,
|
||||
transform 0.22s ease;
|
||||
|
||||
:deep(.el-icon) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--tech-text-strong) !important;
|
||||
background: rgba(0, 212, 255, 0.1) !important;
|
||||
box-shadow: inset 0 0 18px rgba(0, 212, 255, 0.08);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: #67e8f9 !important;
|
||||
font-weight: 500;
|
||||
background: rgba(0, 212, 255, 0.1) !important;
|
||||
box-shadow: inset 2px 0 0 rgba(0, 212, 255, 0.72);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
/* 一级菜单:直接挂在根 el-menu 下的项(normal 模式无子路由的顶层项) */
|
||||
.gva-tech-aside .el-menu > .gva-tech-menu-item.is-active {
|
||||
color: #ffffff !important;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(90deg, rgba(0, 212, 255, 0.24), rgba(59, 130, 246, 0.13)) !important;
|
||||
box-shadow: inset 3px 0 0 var(--tech-primary), 0 0 18px rgba(0, 212, 255, 0.16);
|
||||
}
|
||||
|
||||
.gva-tech-aside-head .gva-tech-menu-item {
|
||||
border-radius: 999px !important;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: linear-gradient(135deg, rgba(0, 212, 255, 0.2), rgba(59, 130, 246, 0.16)) !important;
|
||||
box-shadow: 0 0 18px rgba(0, 212, 255, 0.18);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -127,13 +127,11 @@
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
.gva-tech-aside-head .el-menu-item.is-active:not(.gva-tech-menu-item) {
|
||||
background-color: var(--el-color-primary-light-8) !important;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.el-menu-item.is-active {
|
||||
background-color: var(--el-color-primary-bg) !important;
|
||||
}
|
||||
.dark .gva-tech-aside-head .el-menu-item.is-active:not(.gva-tech-menu-item) {
|
||||
background-color: var(--el-color-primary-bg) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user