修复若干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>
<!-- 顶级导航:当前模块下的顶级菜单(不是门户里的模块卡片) -->
<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">
<button class="module-switch-btn" title="切换模块" @click="switchModule">
<el-icon><Grid /></el-icon>
@@ -34,20 +37,31 @@
</nav>
<div class="header-tools">
<!-- 菜单快捷搜索 -->
<div class="header-search">
<el-input
v-model="keyword"
placeholder="请输入关键词搜索"
clearable
@focus="searchOpen = true"
@blur="handleSearchBlur"
@keyup.enter="handleSearchEnter"
<!-- 菜单快捷搜索:按钮触发,输入框向左弹出 -->
<div class="header-search-wrap">
<div class="header-search" :class="{ 'is-open': searchVisible }">
<el-input
ref="searchInputRef"
v-model="keyword"
placeholder="请输入关键词搜索"
clearable
@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>
</template>
</el-input>
<el-icon><Search /></el-icon>
</button>
<div v-if="searchOpen && keyword.trim()" class="search-results">
<div v-if="!searchResults.length" class="search-empty">未找到相关菜单</div>
<div
@@ -170,7 +184,7 @@
</template>
<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 { useMenuStore } from "@/stores/menu";
import { useAllDataStore } from "@/stores";
@@ -220,6 +234,20 @@ let stopSystemThemeWatch = null;
// 顶部菜单快捷搜索
const keyword = ref("");
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 kw = keyword.value.trim().toLowerCase();
if (!kw) return [];
@@ -265,6 +293,7 @@ const handleSearchSelect = (item) => {
emit("menu-click", { path: item.path, title: item.title, icon: item.icon || "Grid" });
keyword.value = "";
searchOpen.value = false;
searchVisible.value = false;
};
const handleSearchEnter = () => {
@@ -272,9 +301,12 @@ const handleSearchEnter = () => {
};
const handleSearchBlur = () => {
// 延迟关闭,保证结果项的 mousedown 先触发
// 延迟关闭,保证结果项的 mousedown 先触发;关键词为空时同时收起搜索面板
window.setTimeout(() => {
searchOpen.value = false;
if (!keyword.value.trim()) {
searchVisible.value = false;
}
}, 150);
};
@@ -968,6 +1000,27 @@ onUnmounted(() => {
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 {
display: inline-flex;
align-items: center;
@@ -1159,10 +1212,25 @@ onUnmounted(() => {
flex: 0 0 auto;
}
.header-search {
.header-search-wrap {
position: relative;
width: 200px;
display: flex;
align-items: center;
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) {
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 {
position: absolute;
top: calc(100% + 8px);
@@ -1358,7 +1434,7 @@ onUnmounted(() => {
}
@media (max-width: 1280px) {
.header-search {
.header-search.is-open {
width: 160px;
}
@@ -1368,7 +1444,7 @@ onUnmounted(() => {
}
@media (max-width: 1080px) {
.header-search {
.header-search-wrap {
display: none;
}
}
+36 -36
View File
@@ -636,7 +636,7 @@ defineExpose({ serialize, fitView })
.flow-canvas {
display: flex;
height: 560px;
border: 1px solid #e4e7ed;
border: 1px solid var(--el-border-color);
border-radius: 8px;
overflow: hidden;
}
@@ -645,8 +645,8 @@ defineExpose({ serialize, fitView })
width: 220px;
flex: 0 0 220px;
padding: 14px;
background: #fafbfc;
border-right: 1px solid #e4e7ed;
background: var(--el-fill-color-lighter);
border-right: 1px solid var(--el-border-color-lighter);
overflow-y: auto;
.palette-title {
@@ -655,7 +655,7 @@ defineExpose({ serialize, fitView })
font-weight: 600;
margin-bottom: 12px;
font-size: 14px;
color: #303133;
color: var(--el-text-color-primary);
letter-spacing: 0.5px;
&::before {
@@ -663,7 +663,7 @@ defineExpose({ serialize, fitView })
display: inline-block;
width: 3px;
height: 14px;
background: #409eff;
background: var(--el-color-primary);
border-radius: 2px;
margin-right: 8px;
}
@@ -682,16 +682,16 @@ defineExpose({ serialize, fitView })
padding: 12px 14px;
min-height: 48px;
border-radius: 8px;
border: 1px solid #dcdfe6;
background: #fff;
border: 1px solid var(--el-border-color);
background: var(--el-bg-color);
cursor: grab;
font-size: 13px;
user-select: none;
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, background 0.18s ease;
&:hover {
border-color: #409eff;
background: #f5faff;
border-color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
box-shadow: 0 3px 10px rgba(64, 158, 255, 0.15);
transform: translateY(-1px);
}
@@ -762,13 +762,13 @@ defineExpose({ serialize, fitView })
.pal-label {
font-weight: 600;
color: #303133;
color: var(--el-text-color-primary);
font-size: 13.5px;
line-height: 1.3;
}
.pal-desc {
color: #909399;
color: var(--el-text-color-secondary);
font-size: 11.5px;
line-height: 1.3;
overflow: hidden;
@@ -787,7 +787,7 @@ defineExpose({ serialize, fitView })
margin-top: 14px;
padding: 10px 12px;
font-size: 11.5px;
color: #909399;
color: var(--el-text-color-secondary);
line-height: 1.7;
background: rgba(64, 158, 255, 0.05);
border: 1px dashed rgba(64, 158, 255, 0.2);
@@ -798,15 +798,15 @@ defineExpose({ serialize, fitView })
.canvas-wrap {
flex: 1;
position: relative;
background: #f7f8fb;
background: var(--el-fill-color-lighter);
}
.fn {
min-width: 150px;
padding: 8px 12px;
border-radius: 8px;
background: #fff;
border: 1px solid #dcdfe6;
background: var(--el-bg-color-overlay);
border: 1px solid var(--el-border-color);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
font-size: 13px;
@@ -821,12 +821,12 @@ defineExpose({ serialize, fitView })
.fn-title {
font-weight: 600;
color: #303133;
color: var(--el-text-color-primary);
}
.fn-sub {
margin-top: 2px;
color: #909399;
color: var(--el-text-color-secondary);
font-size: 12px;
max-width: 180px;
overflow: hidden;
@@ -847,26 +847,26 @@ defineExpose({ serialize, fitView })
min-width: 90px;
padding: 8px 16px;
border-radius: 999px;
background: #fff;
border: 1px solid #dcdfe6;
background: var(--el-bg-color-overlay);
border: 1px solid var(--el-border-color);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
font-size: 13px;
font-weight: 600;
color: #303133;
color: var(--el-text-color-primary);
.fn-pill-icon {
font-size: 15px;
}
}
.fn-pill-start {
border-color: #67c23a;
color: #529b2e;
background: #f0f9eb;
border-color: var(--el-color-success);
color: var(--el-color-success);
background: var(--el-color-success-light-9);
}
.fn-pill-end {
border-color: #909399;
color: #73767a;
background: #f4f4f5;
border-color: var(--el-border-color);
color: var(--el-text-color-secondary);
background: var(--el-fill-color-light);
}
// 判断节点:菱形
@@ -882,8 +882,8 @@ defineExpose({ serialize, fitView })
position: absolute;
inset: 18px;
transform: rotate(45deg);
background: #fdf6ec;
border: 2px solid #e6a23c;
background: var(--el-color-warning-light-9);
border: 2px solid var(--el-color-warning);
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
@@ -893,7 +893,7 @@ defineExpose({ serialize, fitView })
z-index: 1;
font-size: 12px;
font-weight: 600;
color: #b88230;
color: var(--el-color-warning);
text-align: center;
max-width: 80px;
overflow: hidden;
@@ -916,14 +916,14 @@ defineExpose({ serialize, fitView })
font-size: 12px;
font-weight: 600;
text-align: center;
background: #fff;
background: var(--el-bg-color-overlay);
border: 2px solid transparent;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.fn-circle-notify {
border-color: #d3adf7;
background: #f9f0ff;
color: #531dab;
border-color: var(--el-color-primary-light-5);
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.fn-circle-icon {
font-size: 16px;
@@ -938,7 +938,7 @@ defineExpose({ serialize, fitView })
.hint {
font-size: 12px;
color: #909399;
color: var(--el-text-color-secondary);
}
</style>
@@ -946,13 +946,13 @@ defineExpose({ serialize, fitView })
/* 多人方式问号图标 + 气泡内容:因 el-dialog / el-tooltip 均 append-to-body,需非 scoped 样式 */
.el-radio .mode-tip-icon {
margin-left: 4px;
color: #909399;
color: var(--el-text-color-secondary);
cursor: help;
vertical-align: -2px;
transition: color 0.2s;
}
.el-radio .mode-tip-icon:hover {
color: #409eff;
color: var(--el-color-primary);
}
.el-popper.is-dark .mode-tip {
font-size: 12px;
+60 -3
View File
@@ -4,15 +4,21 @@
<div class="not-found-big">404</div>
<div class="not-found-message">页面未找到</div>
<div class="not-found-desc">很抱歉,您访问的页面不存在或已被删除。</div>
<a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome">
返回首页
</a>
<div class="action-btns">
<a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome">
返回首页
</a>
<a href="javascript:void(0)" class="clear-cache-btn" @click.prevent="clearCache">
清除缓存
</a>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
const router = useRouter();
@@ -20,6 +26,34 @@ const backToHome = () => {
// 使用 /home 作为首页(Hash模式下的系统导航页)
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>
<style scoped>
@@ -63,6 +97,12 @@ const backToHome = () => {
margin-bottom: 28px;
}
.action-btns {
display: flex;
gap: 12px;
justify-content: center;
}
.back-home-btn {
display: inline-block;
background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%);
@@ -80,6 +120,23 @@ const backToHome = () => {
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 {
0% {
transform: translateY(30px) scale(0.97);
+17
View File
@@ -541,6 +541,23 @@ onUnmounted(() => {
gap: 8px;
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;
&:hover {
background: #f5f7fa;
background: var(--el-fill-color-light);
}
&.active {
background: #ecf5ff;
border-color: #b3d8ff;
background: var(--el-color-primary-light-9);
border-color: var(--el-color-primary-light-7);
}
.flow-name {
@@ -365,13 +365,13 @@ onMounted(() => {
align-items: center;
gap: 6px;
font-size: 14px;
color: #303133;
color: var(--el-text-color-primary);
}
.flow-code {
margin-top: 2px;
font-size: 12px;
color: #409eff;
color: var(--el-color-primary);
font-family: Menlo, Consolas, monospace;
}
@@ -381,7 +381,7 @@ onMounted(() => {
align-items: center;
margin-top: 4px;
font-size: 12px;
color: #909399;
color: var(--el-text-color-secondary);
}
}
}
@@ -390,7 +390,7 @@ onMounted(() => {
.hint {
margin-left: 10px;
font-size: 12px;
color: #909399;
color: var(--el-text-color-secondary);
}
}
@@ -938,7 +938,7 @@ onMounted(load)
/* 展开行样式 */
.expanded-account-box {
padding: 12px 18px;
background-color: #f8fafc;
background-color: var(--el-fill-color-light);
border-radius: 6px;
margin: 6px 0;
}
@@ -972,7 +972,7 @@ onMounted(load)
}
.account-card {
background: #ffffff;
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
padding: 12px;
@@ -1079,7 +1079,7 @@ onMounted(load)
}
.account-edit-card {
background: #f8fafc;
background: var(--el-fill-color-light);
border: 1px solid var(--el-border-color);
border-radius: 6px;
padding: 12px 14px 4px 14px;
@@ -1121,7 +1121,7 @@ onMounted(load)
}
.detail-info-card {
background: #f8fafc;
background: var(--el-fill-color-light);
border-radius: 6px;
padding: 12px 16px;
display: flex;
@@ -1167,7 +1167,7 @@ onMounted(load)
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
padding: 12px;
background: #ffffff;
background: var(--el-bg-color);
}
.detail-acc-head {
+2 -1
View File
@@ -124,9 +124,10 @@ func (c *BackendOperationLogController) List() {
return
}
userResolver := services.NewOperationLogUserResolver()
list := make([]map[string]interface{}, 0, len(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{}{
"id": rows[i].ID,
"tid": rows[i].Tid,
+2 -1
View File
@@ -119,9 +119,10 @@ func (c *PlatformOperationLogController) List() {
return
}
userResolver := services.NewOperationLogUserResolver()
list := make([]map[string]interface{}, 0, len(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{}{
"id": rows[i].ID,
"tid": rows[i].Tid,
+30
View File
@@ -37,3 +37,33 @@ func OperationLogUser(tid *uint64, userID uint64) (account, name string) {
}
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 {
reminder := &list[i]
// 3.1 获取日程信息(主要拿 Content,Title 统一为 "日程提醒")
// 3.1 获取日程信息;消息内容统一拼接为【标题】内容,内容为空时用兜底文案,保证永远有提示内容
var schedule models.BackendSchedule
err := models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("id", reminder.ScheduleID).
One(&schedule)
title := "日程提醒"
content := "您有一个待处理的日程时间已到,请注意查收。"
body := ""
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]
if !ok {
+60 -3
View File
@@ -4,15 +4,21 @@
<div class="not-found-big">404</div>
<div class="not-found-message">页面未找到</div>
<div class="not-found-desc">很抱歉,您访问的页面不存在或已被删除。</div>
<a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome">
返回首页
</a>
<div class="action-btns">
<a href="javascript:void(0)" class="back-home-btn" @click.prevent="backToHome">
返回首页
</a>
<a href="javascript:void(0)" class="clear-cache-btn" @click.prevent="clearCache">
清除缓存
</a>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
const router = useRouter();
@@ -20,6 +26,34 @@ const backToHome = () => {
// 使用 /home 作为首页(Hash模式下的系统导航页)
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>
<style scoped>
@@ -63,6 +97,12 @@ const backToHome = () => {
margin-bottom: 28px;
}
.action-btns {
display: flex;
gap: 12px;
justify-content: center;
}
.back-home-btn {
display: inline-block;
background: linear-gradient(90deg, #6a82fb 0%, #fc5c7d 100%);
@@ -80,6 +120,23 @@ const backToHome = () => {
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 {
0% {
transform: translateY(30px) scale(0.97);
@@ -948,7 +948,7 @@ onMounted(load)
/* 展开行样式 */
.expanded-account-box {
padding: 12px 18px;
background-color: #f8fafc;
background-color: var(--el-fill-color-light);
border-radius: 6px;
margin: 6px 0;
}
@@ -982,7 +982,7 @@ onMounted(load)
}
.account-card {
background: #ffffff;
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
padding: 12px;
@@ -1089,7 +1089,7 @@ onMounted(load)
}
.account-edit-card {
background: #f8fafc;
background: var(--el-fill-color-light);
border: 1px solid var(--el-border-color);
border-radius: 6px;
padding: 12px 14px 4px 14px;
@@ -1131,7 +1131,7 @@ onMounted(load)
}
.detail-info-card {
background: #f8fafc;
background: var(--el-fill-color-light);
border-radius: 6px;
padding: 12px 16px;
display: flex;
@@ -1177,7 +1177,7 @@ onMounted(load)
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
padding: 12px;
background: #ffffff;
background: var(--el-bg-color);
}
.detail-acc-head {