This commit is contained in:
xiaozhiyong
2026-06-12 13:40:28 +08:00
parent 286ba122b3
commit 47b6351492
7 changed files with 383 additions and 111 deletions

View File

@@ -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>

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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) => {

View File

@@ -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>

View File

@@ -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>