修复若干bug

This commit is contained in:
hero920103 committed 2026-11-09 11:42:23 +08:00
1 parent ee49ef15b5
commit 244b4bd9fd
12 files changed
+329 -83

No files matched your search

+95 -19
View File
@@ -14,6 +14,9 @@
</div> </div>
<!-- 顶级导航:当前模块下的顶级菜单(不是门户里的模块卡片) --> <!-- 顶级导航:当前模块下的顶级菜单(不是门户里的模块卡片) -->
<nav class="top-nav"> <nav class="top-nav">
<span v-if="currentModule" class="current-module-label">
<span class="module-label-text">{{ currentModule.title }}</span>
</span>
<el-tooltip content="切换模块" placement="bottom"> <el-tooltip content="切换模块" placement="bottom">
<button class="module-switch-btn" title="切换模块" @click="switchModule"> <button class="module-switch-btn" title="切换模块" @click="switchModule">
<el-icon><Grid /></el-icon> <el-icon><Grid /></el-icon>
@@ -34,20 +37,31 @@
</nav> </nav>
<div class="header-tools"> <div class="header-tools">
<!-- 菜单快捷搜索 --> <!-- 菜单快捷搜索:按钮触发,输入框向左弹出 -->
<div class="header-search"> <div class="header-search-wrap">
<el-input <div class="header-search" :class="{ 'is-open': searchVisible }">
v-model="keyword" <el-input
placeholder="请输入关键词搜索" ref="searchInputRef"
clearable v-model="keyword"
@focus="searchOpen = true" placeholder="请输入关键词搜索"
@blur="handleSearchBlur" clearable
@keyup.enter="handleSearchEnter" @focus="searchOpen = true"
@blur="handleSearchBlur"
@keyup.enter="handleSearchEnter"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<button
class="tool-btn search-toggle-btn"
:class="{ 'is-active': searchVisible }"
title="搜索菜单"
@click="toggleSearch"
> >
<template #prefix> <el-icon><Search /></el-icon>
<el-icon><Search /></el-icon> </button>
</template>
</el-input>
<div v-if="searchOpen && keyword.trim()" class="search-results"> <div v-if="searchOpen && keyword.trim()" class="search-results">
<div v-if="!searchResults.length" class="search-empty">未找到相关菜单</div> <div v-if="!searchResults.length" class="search-empty">未找到相关菜单</div>
<div <div
@@ -170,7 +184,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, reactive, onMounted, onUnmounted } from "vue"; import { ref, computed, reactive, onMounted, onUnmounted, nextTick } from "vue";
import { useRouter, useRoute } from "vue-router"; import { useRouter, useRoute } from "vue-router";
import { useMenuStore } from "@/stores/menu"; import { useMenuStore } from "@/stores/menu";
import { useAllDataStore } from "@/stores"; import { useAllDataStore } from "@/stores";
@@ -220,6 +234,20 @@ let stopSystemThemeWatch = null;
// 顶部菜单快捷搜索 // 顶部菜单快捷搜索
const keyword = ref(""); const keyword = ref("");
const searchOpen = ref(false); const searchOpen = ref(false);
// 搜索面板开合:默认收起为搜索按钮,点击后输入框向左弹出
const searchVisible = ref(false);
const searchInputRef = ref();
const toggleSearch = async () => {
searchVisible.value = !searchVisible.value;
if (searchVisible.value) {
await nextTick();
searchInputRef.value?.focus();
} else {
keyword.value = "";
searchOpen.value = false;
}
};
const searchResults = computed(() => { const searchResults = computed(() => {
const kw = keyword.value.trim().toLowerCase(); const kw = keyword.value.trim().toLowerCase();
if (!kw) return []; if (!kw) return [];
@@ -265,6 +293,7 @@ const handleSearchSelect = (item) => {
emit("menu-click", { path: item.path, title: item.title, icon: item.icon || "Grid" }); emit("menu-click", { path: item.path, title: item.title, icon: item.icon || "Grid" });
keyword.value = ""; keyword.value = "";
searchOpen.value = false; searchOpen.value = false;
searchVisible.value = false;
}; };
const handleSearchEnter = () => { const handleSearchEnter = () => {
@@ -272,9 +301,12 @@ const handleSearchEnter = () => {
}; };
const handleSearchBlur = () => { const handleSearchBlur = () => {
// 延迟关闭,保证结果项的 mousedown 先触发 // 延迟关闭,保证结果项的 mousedown 先触发;关键词为空时同时收起搜索面板
window.setTimeout(() => { window.setTimeout(() => {
searchOpen.value = false; searchOpen.value = false;
if (!keyword.value.trim()) {
searchVisible.value = false;
}
}, 150); }, 150);
}; };
@@ -968,6 +1000,27 @@ onUnmounted(() => {
gap: 0; gap: 0;
} }
.current-module-label {
display: inline-flex;
align-items: center;
flex: 0 0 auto;
height: 32px;
margin-right: 8px;
padding: 0 12px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
color: var(--yz-primary);
background: var(--yz-primary-soft);
white-space: nowrap;
.module-label-text {
overflow: hidden;
text-overflow: ellipsis;
max-width: 140px;
}
}
.module-switch-btn { .module-switch-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -1159,10 +1212,25 @@ onUnmounted(() => {
flex: 0 0 auto; flex: 0 0 auto;
} }
.header-search { .header-search-wrap {
position: relative; position: relative;
width: 200px; display: flex;
align-items: center;
flex: 0 0 auto; flex: 0 0 auto;
}
.header-search {
width: 0;
flex: 0 0 auto;
overflow: hidden;
visibility: hidden;
transition: width 0.25s ease, visibility 0s linear 0.25s;
&.is-open {
width: 200px;
visibility: visible;
transition: width 0.25s ease;
}
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
padding: 0 12px; padding: 0 12px;
@@ -1185,6 +1253,14 @@ onUnmounted(() => {
} }
} }
.search-toggle-btn {
&.is-active {
color: var(--yz-primary);
background: var(--yz-primary-soft);
border-color: var(--el-color-primary-light-7);
}
}
.search-results { .search-results {
position: absolute; position: absolute;
top: calc(100% + 8px); top: calc(100% + 8px);
@@ -1358,7 +1434,7 @@ onUnmounted(() => {
} }
@media (max-width: 1280px) { @media (max-width: 1280px) {
.header-search { .header-search.is-open {
width: 160px; width: 160px;
} }
@@ -1368,7 +1444,7 @@ onUnmounted(() => {
} }
@media (max-width: 1080px) { @media (max-width: 1080px) {
.header-search { .header-search-wrap {
display: none; display: none;
} }
} }
+36 -36
View File
@@ -636,7 +636,7 @@ defineExpose({ serialize, fitView })
.flow-canvas { .flow-canvas {
display: flex; display: flex;
height: 560px; height: 560px;
border: 1px solid #e4e7ed; border: 1px solid var(--el-border-color);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
} }
@@ -645,8 +645,8 @@ defineExpose({ serialize, fitView })
width: 220px; width: 220px;
flex: 0 0 220px; flex: 0 0 220px;
padding: 14px; padding: 14px;
background: #fafbfc; background: var(--el-fill-color-lighter);
border-right: 1px solid #e4e7ed; border-right: 1px solid var(--el-border-color-lighter);
overflow-y: auto; overflow-y: auto;
.palette-title { .palette-title {
@@ -655,7 +655,7 @@ defineExpose({ serialize, fitView })
font-weight: 600; font-weight: 600;
margin-bottom: 12px; margin-bottom: 12px;
font-size: 14px; font-size: 14px;
color: #303133; color: var(--el-text-color-primary);
letter-spacing: 0.5px; letter-spacing: 0.5px;
&::before { &::before {
@@ -663,7 +663,7 @@ defineExpose({ serialize, fitView })
display: inline-block; display: inline-block;
width: 3px; width: 3px;
height: 14px; height: 14px;
background: #409eff; background: var(--el-color-primary);
border-radius: 2px; border-radius: 2px;
margin-right: 8px; margin-right: 8px;
} }
@@ -682,16 +682,16 @@ defineExpose({ serialize, fitView })
padding: 12px 14px; padding: 12px 14px;
min-height: 48px; min-height: 48px;
border-radius: 8px; border-radius: 8px;
border: 1px solid #dcdfe6; border: 1px solid var(--el-border-color);
background: #fff; background: var(--el-bg-color);
cursor: grab; cursor: grab;
font-size: 13px; font-size: 13px;
user-select: none; user-select: none;
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, background 0.18s ease; transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, background 0.18s ease;
&:hover { &:hover {
border-color: #409eff; border-color: var(--el-color-primary);
background: #f5faff; background: var(--el-color-primary-light-9);
box-shadow: 0 3px 10px rgba(64, 158, 255, 0.15); box-shadow: 0 3px 10px rgba(64, 158, 255, 0.15);
transform: translateY(-1px); transform: translateY(-1px);
} }
@@ -762,13 +762,13 @@ defineExpose({ serialize, fitView })
.pal-label { .pal-label {
font-weight: 600; font-weight: 600;
color: #303133; color: var(--el-text-color-primary);
font-size: 13.5px; font-size: 13.5px;
line-height: 1.3; line-height: 1.3;
} }
.pal-desc { .pal-desc {
color: #909399; color: var(--el-text-color-secondary);
font-size: 11.5px; font-size: 11.5px;
line-height: 1.3; line-height: 1.3;
overflow: hidden; overflow: hidden;
@@ -787,7 +787,7 @@ defineExpose({ serialize, fitView })
margin-top: 14px; margin-top: 14px;
padding: 10px 12px; padding: 10px 12px;
font-size: 11.5px; font-size: 11.5px;
color: #909399; color: var(--el-text-color-secondary);
line-height: 1.7; line-height: 1.7;
background: rgba(64, 158, 255, 0.05); background: rgba(64, 158, 255, 0.05);
border: 1px dashed rgba(64, 158, 255, 0.2); border: 1px dashed rgba(64, 158, 255, 0.2);
@@ -798,15 +798,15 @@ defineExpose({ serialize, fitView })
.canvas-wrap { .canvas-wrap {
flex: 1; flex: 1;
position: relative; position: relative;
background: #f7f8fb; background: var(--el-fill-color-lighter);
} }
.fn { .fn {
min-width: 150px; min-width: 150px;
padding: 8px 12px; padding: 8px 12px;
border-radius: 8px; border-radius: 8px;
background: #fff; background: var(--el-bg-color-overlay);
border: 1px solid #dcdfe6; border: 1px solid var(--el-border-color);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
font-size: 13px; font-size: 13px;
@@ -821,12 +821,12 @@ defineExpose({ serialize, fitView })
.fn-title { .fn-title {
font-weight: 600; font-weight: 600;
color: #303133; color: var(--el-text-color-primary);
} }
.fn-sub { .fn-sub {
margin-top: 2px; margin-top: 2px;
color: #909399; color: var(--el-text-color-secondary);
font-size: 12px; font-size: 12px;
max-width: 180px; max-width: 180px;
overflow: hidden; overflow: hidden;
@@ -847,26 +847,26 @@ defineExpose({ serialize, fitView })
min-width: 90px; min-width: 90px;
padding: 8px 16px; padding: 8px 16px;
border-radius: 999px; border-radius: 999px;
background: #fff; background: var(--el-bg-color-overlay);
border: 1px solid #dcdfe6; border: 1px solid var(--el-border-color);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: #303133; color: var(--el-text-color-primary);
.fn-pill-icon { .fn-pill-icon {
font-size: 15px; font-size: 15px;
} }
} }
.fn-pill-start { .fn-pill-start {
border-color: #67c23a; border-color: var(--el-color-success);
color: #529b2e; color: var(--el-color-success);
background: #f0f9eb; background: var(--el-color-success-light-9);
} }
.fn-pill-end { .fn-pill-end {
border-color: #909399; border-color: var(--el-border-color);
color: #73767a; color: var(--el-text-color-secondary);
background: #f4f4f5; background: var(--el-fill-color-light);
} }
// 判断节点:菱形 // 判断节点:菱形
@@ -882,8 +882,8 @@ defineExpose({ serialize, fitView })
position: absolute; position: absolute;
inset: 18px; inset: 18px;
transform: rotate(45deg); transform: rotate(45deg);
background: #fdf6ec; background: var(--el-color-warning-light-9);
border: 2px solid #e6a23c; border: 2px solid var(--el-color-warning);
border-radius: 8px; border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
} }
@@ -893,7 +893,7 @@ defineExpose({ serialize, fitView })
z-index: 1; z-index: 1;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
color: #b88230; color: var(--el-color-warning);
text-align: center; text-align: center;
max-width: 80px; max-width: 80px;
overflow: hidden; overflow: hidden;
@@ -916,14 +916,14 @@ defineExpose({ serialize, fitView })
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
background: #fff; background: var(--el-bg-color-overlay);
border: 2px solid transparent; border: 2px solid transparent;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
} }
.fn-circle-notify { .fn-circle-notify {
border-color: #d3adf7; border-color: var(--el-color-primary-light-5);
background: #f9f0ff; background: var(--el-color-primary-light-9);
color: #531dab; color: var(--el-color-primary);
} }
.fn-circle-icon { .fn-circle-icon {
font-size: 16px; font-size: 16px;
@@ -938,7 +938,7 @@ defineExpose({ serialize, fitView })
.hint { .hint {
font-size: 12px; font-size: 12px;
color: #909399; color: var(--el-text-color-secondary);
} }
</style> </style>
@@ -946,13 +946,13 @@ defineExpose({ serialize, fitView })
/* 多人方式问号图标 + 气泡内容:因 el-dialog / el-tooltip 均 append-to-body,需非 scoped 样式 */ /* 多人方式问号图标 + 气泡内容:因 el-dialog / el-tooltip 均 append-to-body,需非 scoped 样式 */
.el-radio .mode-tip-icon { .el-radio .mode-tip-icon {
margin-left: 4px; margin-left: 4px;
color: #909399; color: var(--el-text-color-secondary);
cursor: help; cursor: help;
vertical-align: -2px; vertical-align: -2px;
transition: color 0.2s; transition: color 0.2s;
} }
.el-radio .mode-tip-icon:hover { .el-radio .mode-tip-icon:hover {
color: #409eff; color: var(--el-color-primary);
} }
.el-popper.is-dark .mode-tip { .el-popper.is-dark .mode-tip {
font-size: 12px; font-size: 12px;
+60 -3
View File
@@ -4,15 +4,21 @@
<div class="not-found-big">404</div> <div class="not-found-big">404</div>
<div class="not-found-message">页面未找到</div> <div class="not-found-message">页面未找到</div>
<div class="not-found-desc">很抱歉,您访问的页面不存在或已被删除。</div> <div class="not-found-desc">很抱歉,您访问的页面不存在或已被删除。</div>
<a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome"> <div class="action-btns">
返回首页 <a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome">
</a> 返回首页
</a>
<a href="javascript:void(0)" class="clear-cache-btn" @click.prevent="clearCache">
清除缓存
</a>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
const router = useRouter(); const router = useRouter();
@@ -20,6 +26,34 @@ const backToHome = () => {
// 使用 /home 作为首页(Hash模式下的系统导航页) // 使用 /home 作为首页(Hash模式下的系统导航页)
router.push("/home"); router.push("/home");
}; };
const clearAllCookies = () => {
const cookies = document.cookie ? document.cookie.split(";") : [];
for (const cookie of cookies) {
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.slice(0, eqPos).trim() : cookie.trim();
if (!name) continue;
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=${location.hostname}`;
}
};
const clearCache = async () => {
try {
await ElMessageBox.confirm("确定要清除本地缓存和 Cookie 吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
localStorage.clear();
sessionStorage.clear();
clearAllCookies();
ElMessage.success("缓存已清除");
window.location.reload();
} catch {
// 用户取消,不做任何操作
}
};
</script> </script>
<style scoped> <style scoped>
@@ -63,6 +97,12 @@ const backToHome = () => {
margin-bottom: 28px; margin-bottom: 28px;
} }
.action-btns {
display: flex;
gap: 12px;
justify-content: center;
}
.back-home-btn { .back-home-btn {
display: inline-block; display: inline-block;
background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%); background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%);
@@ -80,6 +120,23 @@ const backToHome = () => {
background: linear-gradient(-90deg, #6a82fb 0%, #fc5c7d 100%); background: linear-gradient(-90deg, #6a82fb 0%, #fc5c7d 100%);
} }
.clear-cache-btn {
display: inline-block;
background: #fff;
color: #fc5c7d;
padding: 10px 36px;
border-radius: 24px;
font-size: 16px;
text-decoration: none;
font-weight: 500;
border: 1px solid #fc5c7d;
transition: background 0.2s;
}
.clear-cache-btn:hover {
background: #fff0f3;
}
@keyframes float-in { @keyframes float-in {
0% { 0% {
transform: translateY(30px) scale(0.97); transform: translateY(30px) scale(0.97);
+17
View File
@@ -541,6 +541,23 @@ onUnmounted(() => {
gap: 8px; gap: 8px;
flex-shrink: 0; flex-shrink: 0;
} }
// 暗色主题:亮蓝渐变在深色底上过于刺眼,改为深色底 + 主色淡染
html.dark & {
background:
linear-gradient(135deg, rgba(64, 128, 255, 0.28) 0%, rgba(64, 128, 255, 0.1) 100%),
var(--el-bg-color);
border: 1px solid rgba(64, 128, 255, 0.3);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
.welcome-title {
color: var(--el-text-color-primary);
}
.welcome-subtitle {
color: var(--el-text-color-secondary);
}
}
} }
// 统计周期切换条 // 统计周期切换条
+7 -7
View File
@@ -352,12 +352,12 @@ onMounted(() => {
border: 1px solid transparent; border: 1px solid transparent;
&:hover { &:hover {
background: #f5f7fa; background: var(--el-fill-color-light);
} }
&.active { &.active {
background: #ecf5ff; background: var(--el-color-primary-light-9);
border-color: #b3d8ff; border-color: var(--el-color-primary-light-7);
} }
.flow-name { .flow-name {
@@ -365,13 +365,13 @@ onMounted(() => {
align-items: center; align-items: center;
gap: 6px; gap: 6px;
font-size: 14px; font-size: 14px;
color: #303133; color: var(--el-text-color-primary);
} }
.flow-code { .flow-code {
margin-top: 2px; margin-top: 2px;
font-size: 12px; font-size: 12px;
color: #409eff; color: var(--el-color-primary);
font-family: Menlo, Consolas, monospace; font-family: Menlo, Consolas, monospace;
} }
@@ -381,7 +381,7 @@ onMounted(() => {
align-items: center; align-items: center;
margin-top: 4px; margin-top: 4px;
font-size: 12px; font-size: 12px;
color: #909399; color: var(--el-text-color-secondary);
} }
} }
} }
@@ -390,7 +390,7 @@ onMounted(() => {
.hint { .hint {
margin-left: 10px; margin-left: 10px;
font-size: 12px; font-size: 12px;
color: #909399; color: var(--el-text-color-secondary);
} }
} }
@@ -938,7 +938,7 @@ onMounted(load)
/* 展开行样式 */ /* 展开行样式 */
.expanded-account-box { .expanded-account-box {
padding: 12px 18px; padding: 12px 18px;
background-color: #f8fafc; background-color: var(--el-fill-color-light);
border-radius: 6px; border-radius: 6px;
margin: 6px 0; margin: 6px 0;
} }
@@ -972,7 +972,7 @@ onMounted(load)
} }
.account-card { .account-card {
background: #ffffff; background: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter); border: 1px solid var(--el-border-color-lighter);
border-radius: 6px; border-radius: 6px;
padding: 12px; padding: 12px;
@@ -1079,7 +1079,7 @@ onMounted(load)
} }
.account-edit-card { .account-edit-card {
background: #f8fafc; background: var(--el-fill-color-light);
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
border-radius: 6px; border-radius: 6px;
padding: 12px 14px 4px 14px; padding: 12px 14px 4px 14px;
@@ -1121,7 +1121,7 @@ onMounted(load)
} }
.detail-info-card { .detail-info-card {
background: #f8fafc; background: var(--el-fill-color-light);
border-radius: 6px; border-radius: 6px;
padding: 12px 16px; padding: 12px 16px;
display: flex; display: flex;
@@ -1167,7 +1167,7 @@ onMounted(load)
border: 1px solid var(--el-border-color-lighter); border: 1px solid var(--el-border-color-lighter);
border-radius: 6px; border-radius: 6px;
padding: 12px; padding: 12px;
background: #ffffff; background: var(--el-bg-color);
} }
.detail-acc-head { .detail-acc-head {
+2 -1
View File
@@ -124,9 +124,10 @@ func (c *BackendOperationLogController) List() {
return return
} }
userResolver := services.NewOperationLogUserResolver()
list := make([]map[string]interface{}, 0, len(rows)) list := make([]map[string]interface{}, 0, len(rows))
for i := range rows { for i := range rows {
userAccount, userName := services.OperationLogUser(rows[i].Tid, rows[i].UserID) userAccount, userName := userResolver.Resolve(rows[i].Tid, rows[i].UserID)
item := map[string]interface{}{ item := map[string]interface{}{
"id": rows[i].ID, "id": rows[i].ID,
"tid": rows[i].Tid, "tid": rows[i].Tid,
+2 -1
View File
@@ -119,9 +119,10 @@ func (c *PlatformOperationLogController) List() {
return return
} }
userResolver := services.NewOperationLogUserResolver()
list := make([]map[string]interface{}, 0, len(rows)) list := make([]map[string]interface{}, 0, len(rows))
for i := range rows { for i := range rows {
userAccount, userName := services.OperationLogUser(rows[i].Tid, rows[i].UserID) userAccount, userName := userResolver.Resolve(rows[i].Tid, rows[i].UserID)
item := map[string]interface{}{ item := map[string]interface{}{
"id": rows[i].ID, "id": rows[i].ID,
"tid": rows[i].Tid, "tid": rows[i].Tid,
+30
View File
@@ -37,3 +37,33 @@ func OperationLogUser(tid *uint64, userID uint64) (account, name string) {
} }
return account, name return account, name
} }
type operationLogUserKey struct {
tid uint64
uid uint64
}
// OperationLogUserResolver 请求级缓存的操作人解析器。
// 日志列表逐行调用 OperationLogUser 时,同一 (tid,userID) 会重复执行
// FindLegacyUID + 用户表查询(N+1),用它按键去重后每个用户只查一次。
type OperationLogUserResolver struct {
cache map[operationLogUserKey][2]string
}
func NewOperationLogUserResolver() *OperationLogUserResolver {
return &OperationLogUserResolver{cache: make(map[operationLogUserKey][2]string)}
}
func (r *OperationLogUserResolver) Resolve(tid *uint64, userID uint64) (account, name string) {
var tidVal uint64
if tid != nil {
tidVal = *tid
}
key := operationLogUserKey{tid: tidVal, uid: userID}
if v, ok := r.cache[key]; ok {
return v[0], v[1]
}
account, name = OperationLogUser(tid, userID)
r.cache[key] = [2]string{account, name}
return account, name
}
+10 -3
View File
@@ -436,16 +436,23 @@ func scanAndSendReminders() {
for i := range list { for i := range list {
reminder := &list[i] reminder := &list[i]
// 3.1 获取日程信息(主要拿 Content,Title 统一为 "日程提醒") // 3.1 获取日程信息;消息内容统一拼接为【标题】内容,内容为空时用兜底文案,保证永远有提示内容
var schedule models.BackendSchedule var schedule models.BackendSchedule
err := models.Orm.QueryTable(new(models.BackendSchedule)). err := models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("id", reminder.ScheduleID). Filter("id", reminder.ScheduleID).
One(&schedule) One(&schedule)
title := "日程提醒" title := "日程提醒"
content := "您有一个待处理的日程时间已到,请注意查收。" body := ""
if err == nil { if err == nil {
content = schedule.Content if t := strings.TrimSpace(schedule.Title); t != "" {
title = t
}
body = strings.TrimSpace(schedule.Content)
} }
if body == "" {
body = "您有一个待处理的日程时间已到,请注意查收。"
}
content := fmt.Sprintf("【%s】%s", title, body)
sender, ok := senders[reminder.RemindChannel] sender, ok := senders[reminder.RemindChannel]
if !ok { if !ok {
+60 -3
View File
@@ -4,15 +4,21 @@
<div class="not-found-big">404</div> <div class="not-found-big">404</div>
<div class="not-found-message">页面未找到</div> <div class="not-found-message">页面未找到</div>
<div class="not-found-desc">很抱歉,您访问的页面不存在或已被删除。</div> <div class="not-found-desc">很抱歉,您访问的页面不存在或已被删除。</div>
<a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome"> <div class="action-btns">
返回首页 <a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome">
</a> 返回首页
</a>
<a href="javascript:void(0)" class="clear-cache-btn" @click.prevent="clearCache">
清除缓存
</a>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
const router = useRouter(); const router = useRouter();
@@ -20,6 +26,34 @@ const backToHome = () => {
// 使用 /home 作为首页(Hash模式下的系统导航页) // 使用 /home 作为首页(Hash模式下的系统导航页)
router.push("/home"); router.push("/home");
}; };
const clearAllCookies = () => {
const cookies = document.cookie ? document.cookie.split(";") : [];
for (const cookie of cookies) {
const eqPos = cookie.indexOf("=");
const name = eqPos > -1 ? cookie.slice(0, eqPos).trim() : cookie.trim();
if (!name) continue;
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=${location.hostname}`;
}
};
const clearCache = async () => {
try {
await ElMessageBox.confirm("确定要清除本地缓存和 Cookie 吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
localStorage.clear();
sessionStorage.clear();
clearAllCookies();
ElMessage.success("缓存已清除");
window.location.reload();
} catch {
// 用户取消,不做任何操作
}
};
</script> </script>
<style scoped> <style scoped>
@@ -63,6 +97,12 @@ const backToHome = () => {
margin-bottom: 28px; margin-bottom: 28px;
} }
.action-btns {
display: flex;
gap: 12px;
justify-content: center;
}
.back-home-btn { .back-home-btn {
display: inline-block; display: inline-block;
background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%); background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%);
@@ -80,6 +120,23 @@ const backToHome = () => {
background: linear-gradient(-90deg, #6a82fb 0%, #fc5c7d 100%); background: linear-gradient(-90deg, #6a82fb 0%, #fc5c7d 100%);
} }
.clear-cache-btn {
display: inline-block;
background: #fff;
color: #fc5c7d;
padding: 10px 36px;
border-radius: 24px;
font-size: 16px;
text-decoration: none;
font-weight: 500;
border: 1px solid #fc5c7d;
transition: background 0.2s;
}
.clear-cache-btn:hover {
background: #fff0f3;
}
@keyframes float-in { @keyframes float-in {
0% { 0% {
transform: translateY(30px) scale(0.97); transform: translateY(30px) scale(0.97);
@@ -948,7 +948,7 @@ onMounted(load)
/* 展开行样式 */ /* 展开行样式 */
.expanded-account-box { .expanded-account-box {
padding: 12px 18px; padding: 12px 18px;
background-color: #f8fafc; background-color: var(--el-fill-color-light);
border-radius: 6px; border-radius: 6px;
margin: 6px 0; margin: 6px 0;
} }
@@ -982,7 +982,7 @@ onMounted(load)
} }
.account-card { .account-card {
background: #ffffff; background: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter); border: 1px solid var(--el-border-color-lighter);
border-radius: 6px; border-radius: 6px;
padding: 12px; padding: 12px;
@@ -1089,7 +1089,7 @@ onMounted(load)
} }
.account-edit-card { .account-edit-card {
background: #f8fafc; background: var(--el-fill-color-light);
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
border-radius: 6px; border-radius: 6px;
padding: 12px 14px 4px 14px; padding: 12px 14px 4px 14px;
@@ -1131,7 +1131,7 @@ onMounted(load)
} }
.detail-info-card { .detail-info-card {
background: #f8fafc; background: var(--el-fill-color-light);
border-radius: 6px; border-radius: 6px;
padding: 12px 16px; padding: 12px 16px;
display: flex; display: flex;
@@ -1177,7 +1177,7 @@ onMounted(load)
border: 1px solid var(--el-border-color-lighter); border: 1px solid var(--el-border-color-lighter);
border-radius: 6px; border-radius: 6px;
padding: 12px; padding: 12px;
background: #ffffff; background: var(--el-bg-color);
} }
.detail-acc-head { .detail-acc-head {