This commit is contained in:
吴红兵
2026-03-07 01:34:48 +08:00
parent adc511cfdc
commit 94c3473958
1211 changed files with 599405 additions and 322105 deletions

View File

@@ -10,15 +10,15 @@
</template>
<script setup lang="ts" name="layoutAside">
import {computed, defineAsyncComponent, onBeforeMount, reactive, ref, watch} from 'vue';
import {storeToRefs} from 'pinia';
import { computed, defineAsyncComponent, onBeforeMount, reactive, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import {useRoutesList} from '/@/stores/routesList';
import {useThemeConfig} from '/@/stores/themeConfig';
import {useTagsViewRoutes} from '/@/stores/tagsViewRoutes';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import mittBus from '/@/utils/mitt';
import {useI18n} from 'vue-i18n';
import {useRoute} from "vue-router";
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
// 引入组件
const Logo = defineAsyncComponent(() => import('/@/layout/logo/index.vue'));
@@ -78,12 +78,12 @@ const setCollapseStyle = computed(() => {
const route = useRoute();
// 设置是否显示左侧菜单栏
const setShowAside = computed(() => {
let { layout } = themeConfig.value;
if(layout !== 'classic'){
return true
}
// 首页不显示侧边栏
return route.path !== '/home'
let { layout } = themeConfig.value;
if (layout !== 'classic') {
return true;
}
// 首页不显示侧边栏
return route.path !== '/home';
});
// 设置显示/隐藏 logo

View File

@@ -10,7 +10,7 @@
</el-scrollbar>
<el-backtop :target="setBacktopClass" />
<check-token />
<chat/>
<chat />
</el-main>
</template>

View File

@@ -200,10 +200,10 @@ const onLockScreenSubmit = async () => {
} catch (err: any) {
// 捕获异常并将错误提示信息赋值给mes变量
mes.value = err.msg;
themeConfig.value.isLockScreen = false;
themeConfig.value.lockScreenTime = 30;
// 将最新的主题配置保存到本地存储中
setLocalThemeConfig();
themeConfig.value.isLockScreen = false;
themeConfig.value.lockScreenTime = 30;
// 将最新的主题配置保存到本地存储中
setLocalThemeConfig();
}
};

View File

@@ -1,90 +1,90 @@
<template>
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange" :style="{backgroundColor: setBackgroundColor}">
<span :style="{color:setFontColor}">{{ themeConfig.globalTitle }}</span>
</div>
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
<img :src="logoMini" class="layout-logo-size-img"/>
</div>
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange" :style="{ backgroundColor: setBackgroundColor }">
<span :style="{ color: setFontColor }">{{ themeConfig.globalTitle }}</span>
</div>
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
<img :src="logoMini" class="layout-logo-size-img" />
</div>
</template>
<script setup lang="ts" name="layoutLogo">
import {useThemeConfig} from '/@/stores/themeConfig';
import { useThemeConfig } from '/@/stores/themeConfig';
import logoMini from '/@/assets/logo-mini.svg';
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const {themeConfig} = storeToRefs(storesThemeConfig);
const { themeConfig } = storeToRefs(storesThemeConfig);
// 设置 logo 的显示。classic 经典布局默认显示 logo
const setShowLogo = computed(() => {
let {isCollapse, layout} = themeConfig.value;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
let { isCollapse, layout } = themeConfig.value;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
});
// 设置 title 的显示颜色。使用顶栏配置,与顶部导航栏保持一致
const setFontColor = computed(() => {
// let {layout} = themeConfig.value;
// return layout === 'classic' || layout === 'transverse' ? `var(--next-bg-topBarColor)` : 'var(--el-color-primary)';
return themeConfig.value.topBarColor;
// let {layout} = themeConfig.value;
// return layout === 'classic' || layout === 'transverse' ? `var(--next-bg-topBarColor)` : 'var(--el-color-primary)';
return themeConfig.value.topBarColor;
});
// 设置背景色。使用顶栏配置,与顶部导航栏保持一致
const setBackgroundColor = computed(() => {
return themeConfig.value.topBar;
return themeConfig.value.topBar;
});
// logo 点击实现菜单展开/收起
const onThemeConfigChange = () => {
if (themeConfig.value.layout === 'transverse') return false;
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
if (themeConfig.value.layout === 'transverse') return false;
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
};
</script>
<style scoped lang="scss">
.layout-logo {
width: 200px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
font-size: 14px;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
// 背景色通过内联样式动态设置,使用顶栏配置
width: 200px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
font-size: 14px;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
// 背景色通过内联样式动态设置,使用顶栏配置
span {
white-space: nowrap;
display: inline-block;
font-size: 18px;
font-weight: 700;
white-space: nowrap;
// 文字颜色通过内联样式动态设置,使用顶栏配置
}
span {
white-space: nowrap;
display: inline-block;
font-size: 18px;
font-weight: 700;
white-space: nowrap;
// 文字颜色通过内联样式动态设置,使用顶栏配置
}
&:hover {
span {
opacity: 0.8;
}
}
&:hover {
span {
opacity: 0.8;
}
}
}
.layout-logo-size {
width: 100%;
height: 50px;
display: flex;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
width: 100%;
height: 50px;
display: flex;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
&-img {
width: 20px;
margin: auto;
}
&-img {
width: 20px;
margin: auto;
}
&:hover {
img {
animation: logoAnimation 0.3s ease-in-out;
}
}
&:hover {
img {
animation: logoAnimation 0.3s ease-in-out;
}
}
}
</style>

View File

@@ -12,7 +12,7 @@
</template>
<script setup lang="ts" name="layoutClassic">
import {useThemeConfig} from '/@/stores/themeConfig';
import { useThemeConfig } from '/@/stores/themeConfig';
// 引入组件
const LayoutAside = defineAsyncComponent(() => import('/@/layout/component/aside.vue'));
@@ -28,11 +28,11 @@ const { themeConfig } = storeToRefs(storesThemeConfig);
// 判断是否显示 tasgview
const isTagsview = computed(() => {
let { layout } = themeConfig.value;
// 经典模式首页没有tagview
if(layout === 'classic' && route.path === '/home'){
return false
}
let { layout } = themeConfig.value;
// 经典模式首页没有tagview
if (layout === 'classic' && route.path === '/home') {
return false;
}
return themeConfig.value.isTagsview;
});
// 重置滚动条高度,更新子级 scrollbar

View File

@@ -1,12 +1,5 @@
<template>
<el-drawer
v-model="visible"
title="上传和下载任务"
direction="rtl"
size="50%"
destroy-on-close
@open="onOpen"
>
<el-drawer v-model="visible" title="上传和下载任务" direction="rtl" size="50%" destroy-on-close @open="onOpen">
<el-tabs v-model="activeTab">
<el-tab-pane label="上传" name="upload" />
<el-tab-pane label="下载" name="download" />
@@ -28,12 +21,20 @@
>
<el-table-column label="所属模块" prop="moduleName" width="120" show-overflow-tooltip />
<el-table-column label="任务类型" prop="typeLabel" width="100" show-overflow-tooltip />
<el-table-column label="任务名称" prop="detailType" min-width="150" show-overflow-tooltip >
<template #default="{ row }">
<el-button v-if="row.type==2" type="text" icon="Download" class="task-name-text" :loading="downloadingId === row.id" @click="handleDownloadFile(row)">{{row.detailType}}</el-button>
<span v-else class="task-name-text">{{row.detailType}}</span>
</template>
</el-table-column>
<el-table-column label="任务名称" prop="detailType" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
<el-button
v-if="row.type == 2"
type="text"
icon="Download"
class="task-name-text"
:loading="downloadingId === row.id"
@click="handleDownloadFile(row)"
>{{ row.detailType }}</el-button
>
<span v-else class="task-name-text">{{ row.detailType }}</span>
</template>
</el-table-column>
<el-table-column label="任务状态" align="center" show-overflow-tooltip>
<template #default="{ row }">
<el-tag>{{ row.status }}</el-tag>
@@ -62,25 +63,25 @@
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed } from 'vue'
import { fetchList, downloadTaskFile } from '/@/api/basic/basicasynctask'
import { useMessage } from '/@/hooks/message'
import { ref, reactive, watch, computed } from 'vue';
import { fetchList, downloadTaskFile } from '/@/api/basic/basicasynctask';
import { useMessage } from '/@/hooks/message';
type TaskTab = 'upload' | 'download' | 'other'
type TaskTab = 'upload' | 'download' | 'other';
const visible = ref(false)
const activeTab = ref<TaskTab>('upload')
const loading = reactive<Record<TaskTab, boolean>>({ upload: false, download: false, other: false })
const list = reactive<Record<TaskTab, any[]>>({ upload: [], download: [], other: [] })
const visible = ref(false);
const activeTab = ref<TaskTab>('upload');
const loading = reactive<Record<TaskTab, boolean>>({ upload: false, download: false, other: false });
const list = reactive<Record<TaskTab, any[]>>({ upload: [], download: [], other: [] });
const pagination = reactive<Record<TaskTab, { current: number; size: number; total: number }>>({
upload: { current: 1, size: 10, total: 0 },
download: { current: 1, size: 10, total: 0 },
other: { current: 1, size: 10, total: 0 },
})
});
/** Tab 对应接口 type1 上传 2 下载 3 其他 */
const TAB_TYPE_MAP: Record<TaskTab, number> = { upload: 1, download: 2, other: 3 }
const EMPTY_TEXT_MAP: Record<TaskTab, string> = { upload: '暂无上传任务', download: '暂无下载任务', other: '暂无其他任务' }
const TAB_TYPE_MAP: Record<TaskTab, number> = { upload: 1, download: 2, other: 3 };
const EMPTY_TEXT_MAP: Record<TaskTab, string> = { upload: '暂无上传任务', download: '暂无下载任务', other: '暂无其他任务' };
// 表格样式,参考主列表页通用样式
const tableStyle = {
@@ -90,87 +91,85 @@ const tableStyle = {
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)',
},
}
};
const emptyText = computed(() => EMPTY_TEXT_MAP[activeTab.value])
const message = useMessage()
const emptyText = computed(() => EMPTY_TEXT_MAP[activeTab.value]);
const message = useMessage();
const loadList = async () => {
const type = activeTab.value
const p = pagination[type]
loading[type] = true
const type = activeTab.value;
const p = pagination[type];
loading[type] = true;
try {
const res = await fetchList({ type: TAB_TYPE_MAP[type], current: p.current, size: p.size })
const data = res?.data ?? res
const records = data?.records ?? []
const total = data?.total ?? 0
list[type] = Array.isArray(records) ? records : []
p.total = Number(total) || 0
const res = await fetchList({ type: TAB_TYPE_MAP[type], current: p.current, size: p.size });
const data = res?.data ?? res;
const records = data?.records ?? [];
const total = data?.total ?? 0;
list[type] = Array.isArray(records) ? records : [];
p.total = Number(total) || 0;
} catch {
list[type] = []
list[type] = [];
} finally {
loading[type] = false
loading[type] = false;
}
}
};
const onPageChange = (page: number) => {
pagination[activeTab.value].current = page
loadList()
}
pagination[activeTab.value].current = page;
loadList();
};
const onSizeChange = (size: number) => {
pagination[activeTab.value].size = size
pagination[activeTab.value].current = 1
loadList()
}
pagination[activeTab.value].size = size;
pagination[activeTab.value].current = 1;
loadList();
};
const onOpen = () => {
loadList()
}
loadList();
};
// 切换 Tab 时加载对应列表
watch(activeTab, () => {
if (visible.value) loadList()
})
if (visible.value) loadList();
});
const formatTime = (item: any) => {
const t = item?.createTime ?? item?.create_time ?? item?.updateTime ?? item?.update_time ?? ''
return t ? (t.slice(0, 16).replace('T', ' ')) : '-'
}
const t = item?.createTime ?? item?.create_time ?? item?.updateTime ?? item?.update_time ?? '';
return t ? t.slice(0, 16).replace('T', ' ') : '-';
};
const open = () => {
visible.value = true
}
visible.value = true;
};
const downloadingId = ref<string | number | null>(null)
const downloadingId = ref<string | number | null>(null);
const handleDownloadFile = async (row: any) => {
if (!row?.id) return
downloadingId.value = row.id
if (!row?.id) return;
downloadingId.value = row.id;
try {
const response: any = await downloadTaskFile({ id: row.id })
const blob = (response && response.data instanceof Blob)
? response.data
: (response instanceof Blob ? response : new Blob([response]))
const dateStr = new Date().toISOString().slice(0, 10)
const baseName = row.detailType ? String(row.detailType).replace(/\s+/g, '_') : '下载文件'
const fileName = `${baseName}_${dateStr}.xls`
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
message.success('下载成功')
const response: any = await downloadTaskFile({ id: row.id });
const blob = response && response.data instanceof Blob ? response.data : response instanceof Blob ? response : new Blob([response]);
const dateStr = new Date().toISOString().slice(0, 10);
const baseName = row.detailType ? String(row.detailType).replace(/\s+/g, '_') : '下载文件';
const fileName = `${baseName}_${dateStr}.xls`;
const elink = document.createElement('a');
elink.download = fileName;
elink.style.display = 'none';
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href);
document.body.removeChild(elink);
message.success('下载成功');
} catch {
message.error('下载失败')
message.error('下载失败');
} finally {
downloadingId.value = null
downloadingId.value = null;
}
}
};
defineExpose({ open })
defineExpose({ open });
</script>
<style scoped lang="scss">

View File

@@ -11,11 +11,13 @@
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="!v.meta.tagsViewName ? v.name : v.meta.tagsViewName">
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
<div v-if="!v.meta.tagsViewName">{{ $t(v.name.split("_")[0]) }}</div>
<div v-else>{{ v.meta.tagsViewName.split("_")[0] }}</div>
<div v-if="!v.meta.tagsViewName">{{ $t(v.name.split('_')[0]) }}</div>
<div v-else>{{ v.meta.tagsViewName.split('_')[0] }}</div>
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{ $t(v.name.split('_')[0]) }}
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />{{
$t(v.name.split('_')[0])
}}
</a>
</el-breadcrumb-item>
</transition-group>
@@ -85,21 +87,21 @@ const getBreadcrumbList = (arr: RouteItems) => {
// 当前路由字符串切割成数组,并删除第一项空内容
const initRouteSplit = (toRoute: RouteLocation) => {
let path = toRoute.path;
if (!themeConfig.value.isBreadcrumb) return false;
state.breadcrumbList = [routesList.value[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(routesList.value);
state.breadcrumbList.push(route);
// 首页或异常页只显示第一个
if (toRoute.name === 'router.home' || (toRoute.name === 'staticRoutes.notFound' && state.breadcrumbList.length > 1)) {
state.breadcrumbList.splice(0, state.breadcrumbList.length - 1);
} else if (state.breadcrumbList.length > 0) {
state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setTagsViewNameI18n(<RouteToFrom>route);
}
let path = toRoute.path;
if (!themeConfig.value.isBreadcrumb) return false;
state.breadcrumbList = [routesList.value[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(routesList.value);
state.breadcrumbList.push(route);
// 首页或异常页只显示第一个
if (toRoute.name === 'router.home' || (toRoute.name === 'staticRoutes.notFound' && state.breadcrumbList.length > 1)) {
state.breadcrumbList.splice(0, state.breadcrumbList.length - 1);
} else if (state.breadcrumbList.length > 0) {
state.breadcrumbList[state.breadcrumbList.length - 1].meta.tagsViewName = other.setTagsViewNameI18n(<RouteToFrom>route);
}
};
// 页面加载时

View File

@@ -59,41 +59,41 @@ const delClassicChildren = <T extends ChilType>(arr: T[]): T[] => {
// 路由过滤递归函数
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr.reduce<T[]>((acc, item) => {
if (!item.meta?.isHide) {
const newItem = { ...item };
if (newItem.children) newItem.children = filterRoutesFun(newItem.children);
acc.push(newItem);
}
return acc;
}, []);
return arr.reduce<T[]>((acc, item) => {
if (!item.meta?.isHide) {
const newItem = { ...item };
if (newItem.children) newItem.children = filterRoutesFun(newItem.children);
acc.push(newItem);
}
return acc;
}, []);
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
let currentData: MittMenu = { children: [] };
const route = searchParent(routesList.value, path as string);
if (route) {
const filteredRoutes = filterRoutesFun(routesList.value);
const matchedRoute = filteredRoutes.find(v => v.path === route.path);
if (matchedRoute) {
currentData['item'] = { ...matchedRoute };
currentData['children'] = matchedRoute.children || [];
}
}
return currentData;
let currentData: MittMenu = { children: [] };
const route = searchParent(routesList.value, path as string);
if (route) {
const filteredRoutes = filterRoutesFun(routesList.value);
const matchedRoute = filteredRoutes.find((v) => v.path === route.path);
if (matchedRoute) {
currentData['item'] = { ...matchedRoute };
currentData['children'] = matchedRoute.children || [];
}
}
return currentData;
};
// 使用递归查询对应的父级路由
const searchParent = (routesList: any, path: string) => {
for (const item of routesList) {
if (item.path === path) return item;
if (item.children) {
const parent = searchParent(item.children, path);
if (parent) return item;
}
}
return undefined;
for (const item of routesList) {
if (item.path === path) return item;
if (item.children) {
const parent = searchParent(item.children, path);
if (parent) return item;
}
}
return undefined;
};
// 页面加载时

View File

@@ -95,7 +95,7 @@ defineExpose({
.layout-search-dialog {
position: relative;
:deep(.el-dialog) {
width: 560px;
width: 560px;
.el-dialog__header,
.el-dialog__body {
display: none;
@@ -108,8 +108,8 @@ defineExpose({
}
}
:deep(.el-autocomplete) {
position: absolute;
width: 560px;
position: absolute;
width: 560px;
top: 53vh;
left: 50%;
transform: translateX(-50%);

View File

@@ -21,14 +21,13 @@
<ele-Lock />
</el-icon>
</div>
<div class="layout-navbars-breadcrumb-user-icon" v-if="useFlowJob().jsonFlowEnable()" @click="useFlowJob().onTodoJobClick()">
<el-badge :value="useFlowJob().jobLen"
:max="999">
<el-icon :title="`${useFlowJob().jobLen}` + $t('user.title7')">
<ele-ChatLineRound />
</el-icon>
</el-badge>
</div>
<div class="layout-navbars-breadcrumb-user-icon" v-if="useFlowJob().jsonFlowEnable()" @click="useFlowJob().onTodoJobClick()">
<el-badge :value="useFlowJob().jobLen" :max="999">
<el-icon :title="`${useFlowJob().jobLen}` + $t('user.title7')">
<ele-ChatLineRound />
</el-icon>
</el-badge>
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<el-icon :title="$t('user.title2')">
<ele-Search />
@@ -76,11 +75,11 @@
</template>
</el-dropdown>
<Search ref="searchRef" />
<!-- <global-websocket uri="/admin/ws/info" v-if="websocketEnable" @rollback="rollback" />-->
<!-- <global-websocket uri="/jsonflow/ws/info" v-if="useFlowJob().jsonFlowEnable()" @rollback="rollback" />-->
<!-- <global-websocket uri="/admin/ws/info" v-if="websocketEnable" @rollback="rollback" />-->
<!-- <global-websocket uri="/jsonflow/ws/info" v-if="useFlowJob().jsonFlowEnable()" @rollback="rollback" />-->
<personal-drawer ref="personalDrawerRef"></personal-drawer>
<change-role ref="ChangeRoleRef" />
<change-role ref="ChangeRoleRef" />
<AsyncTaskDrawer ref="asyncTaskDrawerRef" />
</div>
</template>
@@ -98,13 +97,12 @@ import { Local, Session } from '/@/utils/storage';
import { formatAxis } from '/@/utils/formatTime';
import { useMsg } from '/@/stores/msg';
import { fetchUserMessageList } from '/@/api/admin/message';
import {useFlowJob} from "/@/flow/stores/flowJob";
import { useFlowJob } from '/@/flow/stores/flowJob';
const ChangeRoleRef = ref()
const ChangeRole = defineAsyncComponent(() => import('/@/views/admin/system/role/change-role.vue'))
const asyncTaskDrawerRef = ref()
const AsyncTaskDrawer = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/asyncTaskDrawer.vue'))
const ChangeRoleRef = ref();
const ChangeRole = defineAsyncComponent(() => import('/@/views/admin/system/role/change-role.vue'));
const asyncTaskDrawerRef = ref();
const AsyncTaskDrawer = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/asyncTaskDrawer.vue'));
// 引入组件
const GlobalWebsocket = defineAsyncComponent(() => import('/@/components/Websocket/index.vue'));
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
@@ -204,10 +202,9 @@ const onHandleCommandClick = (path: string) => {
} else if (path === 'personal') {
// 打开个人页面
personalDrawerRef.value.open();
}else if(path === 'changeRole'){
ChangeRoleRef.value.open()
}
else {
} else if (path === 'changeRole') {
ChangeRoleRef.value.open();
} else {
router.push(path);
}
};
@@ -261,8 +258,8 @@ onMounted(() => {
initI18nOrSize('globalComponentSize', 'disabledSize');
initI18nOrSize('globalI18n', 'disabledI18n');
}
useFlowJob().topJobList()
getIsDot()
useFlowJob().topJobList();
getIsDot();
});
</script>

View File

@@ -1,120 +1,119 @@
<template>
<div class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<div class="head-box-title">{{ $t('user.newTitle') }}</div>
<div class="head-box-btn" @click="listRef.openDialog('1')">{{ $t('user.newBtn') }}</div>
</div>
<div class="content-box">
<template v-if="newsList.length > 0">
<div class="relative">
<div class="content-box-item" v-for="(v, k) in newsList" :key="k" @click="contentRef.openDialog(v)">
<button
class="py-1 px-3 -left-8 -top-2 border bg-primary text-white font-bold">
{{parseDate(v.createTime)}}
</button>
<div class="purple_border p-2 border border-gray-400">
{{ v.title }}
</div>
</div>
</div>
</template>
<el-empty :description="$t('user.newDesc')" v-else></el-empty>
</div>
<div class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<div class="head-box-title">{{ $t('user.newTitle') }}</div>
<div class="head-box-btn" @click="listRef.openDialog('1')">{{ $t('user.newBtn') }}</div>
</div>
<div class="content-box">
<template v-if="newsList.length > 0">
<div class="relative">
<div class="content-box-item" v-for="(v, k) in newsList" :key="k" @click="contentRef.openDialog(v)">
<button class="py-1 px-3 -left-8 -top-2 border bg-primary text-white font-bold">
{{ parseDate(v.createTime) }}
</button>
<div class="purple_border p-2 border border-gray-400">
{{ v.title }}
</div>
</div>
</div>
</template>
<el-empty :description="$t('user.newDesc')" v-else></el-empty>
</div>
<!-- 消息列表 -->
<news-lists ref="listRef"/>
<!-- 消息列表 -->
<news-lists ref="listRef" />
<!-- 消息内容 -->
<news-content ref="contentRef" @refresh="getUserMessage"/>
</div>
<!-- 消息内容 -->
<news-content ref="contentRef" @refresh="getUserMessage" />
</div>
</template>
<script setup lang="ts" name="layoutBreadcrumbUserNews">
import {fetchUserMessageList} from "/@/api/admin/message";
import { fetchUserMessageList } from '/@/api/admin/message';
const NewsContent = defineAsyncComponent(() => import('/@/views/home/news/content.vue'));
const NewsLists = defineAsyncComponent(() => import('/@/views/home/news/list.vue'));
const newsList = ref([])
const listRef = ref()
const contentRef = ref()
const newsList = ref([]);
const listRef = ref();
const contentRef = ref();
// 获取用户的信息
const getUserMessage = () => {
// 取前五条数据
return fetchUserMessageList({current: 1, size: 5, category: '1', readFlag: '0'}).then(res => {
newsList.value = res.data.records;
})
}
// 取前五条数据
return fetchUserMessageList({ current: 1, size: 5, category: '1', readFlag: '0' }).then((res) => {
newsList.value = res.data.records;
});
};
onMounted(() => {
getUserMessage()
})
getUserMessage();
});
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user-news {
.head-box {
display: flex;
border-bottom: 1px solid var(--el-border-color-lighter);
box-sizing: border-box;
color: var(--el-text-color-primary);
justify-content: space-between;
height: 35px;
align-items: center;
.head-box {
display: flex;
border-bottom: 1px solid var(--el-border-color-lighter);
box-sizing: border-box;
color: var(--el-text-color-primary);
justify-content: space-between;
height: 35px;
align-items: center;
.head-box-btn {
color: var(--el-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
.head-box-btn {
color: var(--el-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
&:hover {
opacity: 1;
}
}
}
.content-box {
font-size: 13px;
.content-box {
font-size: 13px;
.content-box-item {
padding-top: 12px;
.content-box-item {
padding-top: 12px;
&:last-of-type {
padding-bottom: 12px;
}
&:last-of-type {
padding-bottom: 12px;
}
.content-box-msg {
color: var(--el-text-color-secondary);
margin-top: 5px;
margin-bottom: 5px;
}
.content-box-msg {
color: var(--el-text-color-secondary);
margin-top: 5px;
margin-bottom: 5px;
}
.content-box-time {
color: var(--el-text-color-secondary);
}
}
}
.content-box-time {
color: var(--el-text-color-secondary);
}
}
}
.foot-box {
height: 35px;
color: var(--el-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid var(--el-border-color-lighter);
.foot-box {
height: 35px;
color: var(--el-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid var(--el-border-color-lighter);
&:hover {
opacity: 1;
}
}
&:hover {
opacity: 1;
}
}
:deep(.el-empty__description p) {
font-size: 13px;
}
:deep(.el-empty__description p) {
font-size: 13px;
}
}
</style>

View File

@@ -121,8 +121,7 @@ const isAffixTagAllowed = (v: RouteItem) => v.path === '/home' || v.path === '/h
/** 后端菜单 sortOrder/sort_order/weight 为 1 的项常被误当“默认”加载为 tag排除首页除外 */
const isSortOrderOneExcluded = (v: RouteItem) => {
const meta = v.meta as any;
const order =
meta?.sortOrder ?? meta?.sort_order ?? meta?.weight ?? (v as any).sort_order ?? (v as any).weight;
const order = meta?.sortOrder ?? meta?.sort_order ?? meta?.weight ?? (v as any).sort_order ?? (v as any).weight;
return Number(order) === 1 && !isAffixTagAllowed(v);
};
/** 递归扁平化路由(含 children用于校验 path 是否在当前角色菜单中 */
@@ -137,10 +136,7 @@ const flattenRoutes = (routes: RouteItem[]): RouteItem[] => {
/** 当前 tag 的 path 是否存在于当前角色路由列表中(避免恢复出“不存在的 tag” */
const pathInCurrentRoutes = (tag: RouteItem): boolean => {
const flat = flattenRoutes(state.tagsViewRoutesList);
return flat.some(
(r: RouteItem) =>
r.path === tag.path || (r.meta?.isDynamic && (r.meta as any).isDynamicPath === tag.path)
);
return flat.some((r: RouteItem) => r.path === tag.path || (r.meta?.isDynamic && (r.meta as any).isDynamicPath === tag.path));
};
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
const addBrowserSetSession = (tagsViewList: Array<object>) => {
@@ -161,11 +157,7 @@ const initTagsView = async () => {
// 仅当当前角色已有路由列表时,才从缓存恢复;恢复时只保留当前路由中存在的 path避免切换角色后出现“不存在的 tag”
if (hasValidCache && state.tagsViewRoutesList.length > 0) {
state.tagsViewList = cached.filter(
(v: RouteItem) =>
!isCarouselTag(v) &&
!isSortOrderOneExcluded(v) &&
pathInCurrentRoutes(v) &&
(v.meta?.isAffix ? isAffixTagAllowed(v) : true)
(v: RouteItem) => !isCarouselTag(v) && !isSortOrderOneExcluded(v) && pathInCurrentRoutes(v) && (v.meta?.isAffix ? isAffixTagAllowed(v) : true)
);
} else {
await state.tagsViewRoutesList.map((v: RouteItem) => {
@@ -429,10 +421,10 @@ const onMousedownMenu = (v: RouteItem, e: MouseEvent) => {
};
// 当前的 tagsView 项点击时
const onTagsClick = (v: RouteItem, k: number) => {
state.tagsRefsIndex = k;
if(v.name.indexOf("router.home")!=0){
v.name=v.name.replaceAll("_","").replaceAll(v.id,"")+"_"+v.id
}
state.tagsRefsIndex = k;
if (v.name.indexOf('router.home') != 0) {
v.name = v.name.replaceAll('_', '').replaceAll(v.id, '') + '_' + v.id;
}
router.push(v);
};
// 处理 url地址栏链接有参数时tagsview 右键菜单刷新功能失效问题,感谢 @ZzZz-RIPPER、@dejavuuuuu

View File

@@ -1,41 +1,40 @@
<template>
<div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
<el-menu router :default-active="state.defaultActive" :ellipsis="false" background-color="transparent"
mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="getMenuIcon(val.meta.icon)"/>
<span class="font-semibold">{{ $t(val.name.split('_')[0]) }}</span>
</template>
<SubItem :chil="val.children"/>
</el-sub-menu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<!-- 此处可 指定 color='red' 等指定顶栏SVG颜色 -->
<SvgIcon :name="getMenuIcon(val.meta.icon)"/>
<p class="font-semibold">{{ $t(val.name.split('_')[0]) }}</p>
</template>
<template #title v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="getMenuIcon(val.meta.icon)"/>
{{ $t(val.name.split('_')[0]) }}
</a>
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</el-scrollbar>
</div>
<div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
<el-menu router :default-active="state.defaultActive" :ellipsis="false" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
<span class="font-semibold">{{ $t(val.name.split('_')[0]) }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<!-- 此处可 指定 color='red' 等指定顶栏SVG颜色 -->
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
<p class="font-semibold">{{ $t(val.name.split('_')[0]) }}</p>
</template>
<template #title v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
{{ $t(val.name.split('_')[0]) }}
</a>
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup lang="ts" name="navMenuHorizontal">
import {RouteRecordRaw} from 'vue-router';
import {useRoutesList} from '/@/stores/routesList';
import {useThemeConfig} from '/@/stores/themeConfig';
import { RouteRecordRaw } from 'vue-router';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
@@ -44,147 +43,147 @@ const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue
// 定义父组件传过来的值
const props = defineProps({
// 菜单列表
menuList: {
type: Array<RouteRecordRaw>,
default: () => [],
},
// 菜单列表
menuList: {
type: Array<RouteRecordRaw>,
default: () => [],
},
});
// 定义变量内容
const elMenuHorizontalScrollRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const {routesList} = storeToRefs(stores);
const {themeConfig} = storeToRefs(storesThemeConfig);
const { routesList } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive({
defaultActive: '' as string | undefined,
defaultActive: '' as string | undefined,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return <RouteItems>props.menuList;
return <RouteItems>props.menuList;
});
// 获取菜单图标,如果没有则返回默认图标
const getMenuIcon = (icon?: string) => {
return icon && icon.trim() ? icon : 'ele-Menu';
return icon && icon.trim() ? icon : 'ele-Menu';
};
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e: WheelEventType) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft + eventDelta / 4;
const eventDelta = e.wheelDelta || -e.deltaY * 40;
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els = <HTMLElement>document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = els.offsetLeft;
});
nextTick(() => {
let els = <HTMLElement>document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = els.offsetLeft;
});
};
// 路由过滤递归函数
const filterRoutesFun = <T extends RouteItem>(arr: T[]): T[] => {
return arr
.filter((item: T) => !item.meta?.isHide)
.map((item: T) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
return arr
.filter((item: T) => !item.meta?.isHide)
.map((item: T) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
let currentData: MittMenu = {children: []};
if (!state.defaultActive) {
const route = searchParent(routesList.value, path as string) as any;
state.defaultActive = route!.path;
}
filterRoutesFun(routesList.value).map((v, k) => {
if (v.path === state.defaultActive) {
v['k'] = k;
currentData['item'] = {...v};
currentData['children'] = [{...v}];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
let currentData: MittMenu = { children: [] };
if (!state.defaultActive) {
const route = searchParent(routesList.value, path as string) as any;
state.defaultActive = route!.path;
}
filterRoutesFun(routesList.value).map((v, k) => {
if (v.path === state.defaultActive) {
v['k'] = k;
currentData['item'] = { ...v };
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 设置页面当前路由高亮
const setCurrentRouterHighlight = (currentRoute: RouteToFrom) => {
const {path, meta} = currentRoute;
const route = searchParent(routesList.value, path as string) as any;
if (themeConfig.value.layout === 'classic') {
if (route) {
state.defaultActive = route!.path;
} else {
state.defaultActive = `/${path?.split('/')[1]}`;
}
} else {
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
if (pathSplit.length >= 4 && meta?.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
else state.defaultActive = path;
}
const { path, meta } = currentRoute;
const route = searchParent(routesList.value, path as string) as any;
if (themeConfig.value.layout === 'classic') {
if (route) {
state.defaultActive = route!.path;
} else {
state.defaultActive = `/${path?.split('/')[1]}`;
}
} else {
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
if (pathSplit.length >= 4 && meta?.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
else state.defaultActive = path;
}
};
// 使用递归查询对应的父级路由
const searchParent = (routesList: any, path: string) => {
let route = undefined;
routesList.forEach((item) => {
if (item.path === path) {
route = item;
return;
}
if (item.children && searchParent(item.children, path)) {
route = item;
return;
}
});
return route;
let route = undefined;
routesList.forEach((item) => {
if (item.path === path) {
route = item;
return;
}
if (item.children && searchParent(item.children, path)) {
route = item;
return;
}
});
return route;
};
// 打开外部链接
const onALinkClick = (val: RouteItem) => {
other.handleOpenLink(val);
other.handleOpenLink(val);
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
setCurrentRouterHighlight(route);
});
// 页面加载时
onMounted(() => {
initElMenuOffsetLeft();
initElMenuOffsetLeft();
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setCurrentRouterHighlight(to);
// 修复经典布局开启切割菜单时点击tagsView后左侧导航菜单数据不变的问题
let {layout, isClassicSplitMenu} = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
setCurrentRouterHighlight(to);
// 修复经典布局开启切割菜单时点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
</script>
<style scoped lang="scss">
.el-menu-horizontal-warp {
flex: 1;
overflow: hidden;
margin-right: 30px;
flex: 1;
overflow: hidden;
margin-right: 30px;
:deep(.el-scrollbar__bar.is-vertical) {
display: none;
}
:deep(.el-scrollbar__bar.is-vertical) {
display: none;
}
:deep(a) {
width: 100%;
}
:deep(a) {
width: 100%;
}
.el-menu.el-menu--horizontal {
display: flex;
height: 100%;
width: 100%;
box-sizing: border-box;
}
.el-menu.el-menu--horizontal {
display: flex;
height: 100%;
width: 100%;
box-sizing: border-box;
}
}
</style>

View File

@@ -3,26 +3,26 @@
<el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
<template #title>
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
<!-- <span>{{ $t(val.name) }}</span>-->
<span>{{ $t(val.name.split('_')[0]) }}</span>
<!-- <span>{{ $t(val.name) }}</span>-->
<span>{{ $t(val.name.split('_')[0]) }}</span>
</template>
<sub-item :chil="val.children" />
<sub-item :chil="val.children" />
</el-sub-menu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
<!-- <span>{{ $t(val.name) }}</span>-->
<span>{{ $t(val.name.split('_')[0]) }}</span>
</template>
<!-- <span>{{ $t(val.name) }}</span>-->
<span>{{ $t(val.name.split('_')[0]) }}</span>
</template>
<template v-else>
<a class="w100" @click.prevent="onALinkClick(val)">
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
<!-- {{ $t(val.name) }}-->
{{ $t(val.name.split('_')[0]) }}
<!-- {{ $t(val.name) }}-->
{{ $t(val.name.split('_')[0]) }}
</a>
</template>
</el-menu-item>
</el-menu-item>
</template>
</template>
</template>

View File

@@ -8,7 +8,7 @@
:collapse-transition="false"
>
<template v-for="val in menuLists">
<!-- 三级菜单 -->
<!-- 三级菜单 -->
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
@@ -17,7 +17,7 @@
<SubItem :chil="val.children" />
</el-sub-menu>
<template v-else>
<!-- 二级菜单 -->
<!-- 二级菜单 -->
<el-menu-item :index="val.path" :key="val.path">
<SvgIcon :name="getMenuIcon(val.meta.icon)" />
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
@@ -33,8 +33,8 @@
</template>
<script setup lang="ts" name="navMenuVertical">
import {RouteRecordRaw} from 'vue-router';
import {useThemeConfig} from '/@/stores/themeConfig';
import { RouteRecordRaw } from 'vue-router';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
// 引入组件