菜单、菜单筛选、个人中心、基本信息、切换用户
Frontend CI/CD / build (web-office) (push) Failing after 11s Details

This commit is contained in:
hujiale 2024-10-10 20:01:58 +08:00
parent 3e41e8ca50
commit 154c104148
22 changed files with 1157 additions and 144 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -20,6 +20,9 @@ import { $t } from '#/locales';
import { useAuthStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';
import { UserInfo,UserSwitch,UserLike } from '#/layouts/user';
const notifications = ref<NotificationItem[]>([
{
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
@ -61,27 +64,21 @@ const showDot = computed(() =>
const menus = computed(() => [
{
handler: () => {
openWindow(VBEN_DOC_URL, {
target: '_blank',
});
infoVisible.value = true
},
icon: BookOpenText,
text: '个人信息',
},
{
handler: () => {
openWindow(VBEN_GITHUB_URL, {
target: '_blank',
});
switchVisible.value = true
},
icon: MdiGithub,
text: '切换租户',
},
{
handler: () => {
openWindow(`${VBEN_GITHUB_URL}/issues`, {
target: '_blank',
});
likeVisible.value = true
},
icon: CircleHelp,
text: '个人偏好',
@ -103,6 +100,10 @@ function handleNoticeClear() {
function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true));
}
let infoVisible = ref(false)
let switchVisible = ref(false)
let likeVisible = ref(false)
</script>
<template>
@ -110,7 +111,7 @@ function handleMakeAll() {
<template #user-dropdown>
<UserDropdown :avatar :menus :text="userStore.userInfo?.realName"
@logout="handleLogout" :hideLockLcreen="true" />
@logout="handleLogout" />
</template>
<template #notification>
<Notification :dot="showDot" :notifications="notifications" @clear="handleNoticeClear" :icColor="'#fff'"
@ -125,4 +126,8 @@ function handleMakeAll() {
<LockScreen :avatar @to-login="handleLogout" />
</template>
</BasicLayout>
<UserInfo v-model:visible="infoVisible"></UserInfo>
<UserSwitch v-model:visible="switchVisible"></UserSwitch>
<UserLike v-model:visible="likeVisible"></UserLike>
</template>

View File

@ -3,10 +3,9 @@ import { computed, useSlots } from 'vue';
import { preferences, usePreferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores';
import { VbenFullScreen } from '@vben-core/shadcn-ui';
import VbenFullScreen from './../../full-screen/full-screen.vue';
import {
GlobalSearch,
LanguageToggle,
PreferencesButton,
ThemeToggle,
@ -121,11 +120,6 @@ function clearPreferencesAndLogout() {
<template v-for="slot in rightSlots" :key="slot.name">
<slot :name="slot.name">
<template v-if="slot.name === 'global-search'">
<!-- <GlobalSearch
:enable-shortcut-key="globalSearchShortcutKey"
:menus="accessStore.accessMenus"
class="mr-1 sm:mr-4"
/> -->
</template>
<template v-else-if="slot.name === 'preferences'">

View File

@ -13,7 +13,8 @@ import {
import { useLockStore, useUserStore } from '@vben/stores';
import { deepToRaw, mapTree } from '@vben/utils';
import { VbenAdminLayout } from '@vben-core/layout-ui';
import { Toaster, VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
import { Toaster, VbenBackTop } from '@vben-core/shadcn-ui';
import { VbenLogo } from '#/layouts/logo';
import { Breadcrumb, CheckUpdates, Preferences } from '@vben/layouts';
import { LayoutContent, LayoutContentSpinner } from './content';
@ -201,6 +202,7 @@ const headerSlots = computed(() => {
:text="'西北油田办公平台'"
:theme="'!white'"
:textClass="'text-white text-2xl'"
:logoSize="100"
/>
</template>
<!-- 头部区域 -->

View File

@ -4,6 +4,10 @@ import type { MenuProps } from '@vben-core/menu-ui';
import { Menu } from '@vben-core/menu-ui';
import {
GlobalSearch,
} from './../../global-search';
interface Props extends MenuProps {
menus: MenuRecordRaw[];
}
@ -20,9 +24,20 @@ const emit = defineEmits<{
function handleMenuSelect(key: string) {
emit('select', key, props.mode);
}
import { usePreferences } from '@vben/preferences';
const {globalSearchShortcutKey } = usePreferences();
import { useAccessStore } from '@vben/stores';
const accessStore = useAccessStore();
</script>
<template>
<GlobalSearch
:enable-shortcut-key="globalSearchShortcutKey"
:menus="accessStore.accessMenus"
class="mr-1 sm:mr-4 pl-3 my-3"
/>
<Menu
:accordion="accordion"
:collapse="collapse"

View File

@ -0,0 +1,31 @@
<script lang="ts" setup>
import { Maximize, Minimize } from '@vben/icons';
import { useFullscreen } from '@vueuse/core';
import {
VbenIconButton,
} from '@vben-core/shadcn-ui';
defineOptions({ name: 'FullScreen' });
const { isFullscreen, toggle } = useFullscreen();
//
isFullscreen.value = !!(
document.fullscreenElement ||
// @ts-ignore
document.webkitFullscreenElement ||
// @ts-ignore
document.mozFullScreenElement ||
// @ts-ignore
document.msFullscreenElement
);
</script>
<template>
<VbenIconButton @click="toggle" class="hover:text-[#007da3] text-white">
<Minimize v-if="isFullscreen" class="size-4 hover:text-[#007da3]" />
<Maximize v-else class="size-4 hover:text-[#007da3]" />
</VbenIconButton>
</template>

View File

@ -0,0 +1 @@
export { default as VbenFullScreen } from './full-screen.vue';

View File

@ -0,0 +1,144 @@
<script setup lang="ts">
import type { MenuRecordRaw } from '@vben/types';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import {
ArrowDown,
ArrowUp,
CornerDownLeft,
MdiKeyboardEsc,
Search,
} from '@vben/icons';
import { $t } from '@vben/locales';
import { isWindowsOs } from '@vben/utils';
import { useVbenModal } from '@vben-core/popup-ui';
import { useMagicKeys, whenever } from '@vueuse/core';
import SearchPanel from './search-panel.vue';
defineOptions({
name: 'GlobalSearch',
});
const props = withDefaults(
defineProps<{ enableShortcutKey?: boolean; menus: MenuRecordRaw[] }>(),
{
enableShortcutKey: true,
menus: () => [],
},
);
const [Modal, modalApi] = useVbenModal({
onCancel() {
modalApi.close();
},
});
const open = modalApi.useStore((state) => state.isOpen);
const keyword = ref('');
const searchInputRef = ref<HTMLInputElement>();
function handleClose() {
modalApi.close();
keyword.value = '';
}
const keys = useMagicKeys();
const cmd = isWindowsOs() ? keys['ctrl+k'] : keys['cmd+k'];
whenever(cmd!, () => {
if (props.enableShortcutKey) {
modalApi.open();
}
});
whenever(open, () => {
nextTick(() => {
searchInputRef.value?.focus();
});
});
const preventDefaultBrowserSearchHotKey = (event: KeyboardEvent) => {
if (event.key?.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
}
};
const toggleKeydownListener = () => {
if (props.enableShortcutKey) {
window.addEventListener('keydown', preventDefaultBrowserSearchHotKey);
} else {
window.removeEventListener('keydown', preventDefaultBrowserSearchHotKey);
}
};
const toggleOpen = () => {
open.value ? modalApi.close() : modalApi.open();
};
watch(() => props.enableShortcutKey, toggleKeydownListener);
onMounted(() => {
toggleKeydownListener();
onUnmounted(() => {
window.removeEventListener('keydown', preventDefaultBrowserSearchHotKey);
});
});
import { usePreferences } from '@vben/preferences';
const {sidebarCollapsed } = usePreferences();
</script>
<template>
<div>
<Modal :fullscreen-button="false" class="w-[600px]" header-class="py-2">
<template #title>
<div class="flex items-center">
<Search class="text-muted-foreground mr-2 size-4" />
<input ref="searchInputRef" v-model="keyword" :placeholder="$t('widgets.search.searchNavigate')"
class="ring-none placeholder:text-muted-foreground w-[80%] rounded-md border border-none bg-transparent p-2 pl-0 text-sm font-normal outline-none ring-0 ring-offset-transparent focus-visible:ring-transparent" />
</div>
</template>
<SearchPanel :keyword="keyword" :menus="menus" @close="handleClose" />
<template #footer>
<div class="flex w-full justify-start text-xs">
<div class="mr-2 flex items-center">
<CornerDownLeft class="mr-1 size-3" />
{{ $t('widgets.search.select') }}
</div>
<div class="mr-2 flex items-center">
<ArrowUp class="mr-1 size-3" />
<ArrowDown class="mr-1 size-3" />
{{ $t('widgets.search.navigate') }}
</div>
<div class="flex items-center">
<MdiKeyboardEsc class="mr-1 size-3" />
{{ $t('widgets.search.close') }}
</div>
</div>
</template>
</Modal>
<div v-if="!sidebarCollapsed"
class="md:bg-accent group flex h-8 cursor-pointer items-center gap-3 rounded-2xl border-none bg-none px-2 py-0.5 outline-none"
@click="toggleOpen()">
<Search class="text-muted-foreground group-hover:text-foreground size-4 group-hover:opacity-100" />
<span class="text-muted-foreground group-hover:text-foreground hidden text-xs duration-300 md:block">
{{ $t('widgets.search.title') }}
</span>
<span v-if="enableShortcutKey"
class="bg-background border-foreground/60 text-muted-foreground group-hover:text-foreground relative hidden rounded-sm rounded-r-xl px-1.5 py-1 text-xs leading-none group-hover:opacity-100 md:block">
{{ isWindowsOs() ? 'Ctrl' : '⌘' }}
<kbd>K</kbd>
</span>
<span v-else></span>
</div>
<div v-else class="rounded-full w100% flex justify-center">
<Search class="text-muted-foreground group-hover:text-foreground size-4 group-hover:opacity-100" />
</div>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as GlobalSearch } from './global-search.vue';

View File

@ -0,0 +1,287 @@
<script setup lang="ts">
import type { MenuRecordRaw } from '@vben/types';
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
import { useRouter } from 'vue-router';
import { SearchX, X } from '@vben/icons';
import { $t } from '@vben/locales';
import { mapTree, traverseTreeValues, uniqueByField } from '@vben/utils';
import { VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
import { isHttpUrl } from '@vben-core/shared/utils';
import { onKeyStroke, useLocalStorage, useThrottleFn } from '@vueuse/core';
defineOptions({
name: 'SearchPanel',
});
const props = withDefaults(
defineProps<{ keyword: string; menus: MenuRecordRaw[] }>(),
{
keyword: '',
menus: () => [],
},
);
const emit = defineEmits<{ close: [] }>();
const router = useRouter();
const searchHistory = useLocalStorage<MenuRecordRaw[]>(
`__search-history-${location.hostname}__`,
[],
);
const activeIndex = ref(-1);
const searchItems = shallowRef<MenuRecordRaw[]>([]);
const searchResults = ref<MenuRecordRaw[]>([]);
const handleSearch = useThrottleFn(search, 200);
//
function search(searchKey: string) {
//
searchKey = searchKey.trim();
//
if (!searchKey) {
searchResults.value = [];
return;
}
// 使
const reg = createSearchReg(searchKey);
//
const results: MenuRecordRaw[] = [];
//
traverseTreeValues(searchItems.value, (item) => {
//
if (reg.test(item.name?.toLowerCase())) {
results.push(item);
}
});
//
searchResults.value = results;
// 0
if (results.length > 0) {
activeIndex.value = 0;
}
// 0
activeIndex.value = 0;
}
// When the keyboard up and down keys move to an invisible place
// the scroll bar needs to scroll automatically
function scrollIntoView() {
const element = document.querySelector(
`[data-search-item="${activeIndex.value}"]`,
);
if (element) {
element.scrollIntoView({ block: 'nearest' });
}
}
// enter keyboard event
async function handleEnter() {
if (searchResults.value.length === 0) {
return;
}
const result = searchResults.value;
const index = activeIndex.value;
if (result.length === 0 || index < 0) {
return;
}
const to = result[index];
if (to) {
searchHistory.value.push(to);
handleClose();
await nextTick();
if (isHttpUrl(to.path)) {
window.open(to.path, '_blank');
} else {
router.push({ path: to.path, replace: true });
}
}
}
// Arrow key up
function handleUp() {
if (searchResults.value.length === 0) {
return;
}
activeIndex.value--;
if (activeIndex.value < 0) {
activeIndex.value = searchResults.value.length - 1;
}
scrollIntoView();
}
// Arrow key down
function handleDown() {
if (searchResults.value.length === 0) {
return;
}
activeIndex.value++;
if (activeIndex.value > searchResults.value.length - 1) {
activeIndex.value = 0;
}
scrollIntoView();
}
// close search modal
function handleClose() {
searchResults.value = [];
emit('close');
}
// Activate when the mouse moves to a certain line
function handleMouseenter(e: MouseEvent) {
const index = (e.target as HTMLElement)?.dataset.index;
activeIndex.value = Number(index);
}
function removeItem(index: number) {
if (props.keyword) {
searchResults.value.splice(index, 1);
} else {
searchHistory.value.splice(index, 1);
}
activeIndex.value = activeIndex.value - 1 >= 0 ? activeIndex.value - 1 : 0;
scrollIntoView();
}
//
const code = new Set([
'$',
'(',
')',
'*',
'+',
'.',
'?',
'[',
'\\',
']',
'^',
'{',
'|',
'}',
]);
//
function transform(c: string) {
//
//
return code.has(c) ? `\\${c}` : c;
}
//
function createSearchReg(key: string) {
//
//
// '.*'
const keys = [...key].map((item) => transform(item)).join('.*');
//
return new RegExp(`.*${keys}.*`);
}
watch(
() => props.keyword,
(val) => {
if (val) {
handleSearch(val);
} else {
searchResults.value = [...searchHistory.value];
}
},
);
onMounted(() => {
searchItems.value = mapTree(props.menus, (item) => {
return {
...item,
name: $t(item?.name),
};
});
if (searchHistory.value.length > 0) {
searchResults.value = searchHistory.value;
}
// enter search
onKeyStroke('Enter', handleEnter);
// Monitor keyboard arrow keys
onKeyStroke('ArrowUp', handleUp);
onKeyStroke('ArrowDown', handleDown);
// esc close
onKeyStroke('Escape', handleClose);
});
</script>
<template>
<VbenScrollbar>
<div class="!flex h-full justify-center px-2 sm:max-h-[450px]">
<!-- 无搜索结果 -->
<div
v-if="keyword && searchResults.length === 0"
class="text-muted-foreground text-center"
>
<SearchX class="mx-auto mt-4 size-12" />
<p class="mb-10 mt-6 text-xs">
{{ $t('widgets.search.noResults') }}
<span class="text-foreground text-sm font-medium">
"{{ keyword }}"
</span>
</p>
</div>
<!-- 历史搜索记录 & 没有搜索结果 -->
<div
v-if="!keyword && searchResults.length === 0"
class="text-muted-foreground text-center"
>
<p class="my-10 text-xs">
{{ $t('widgets.search.noRecent') }}
</p>
</div>
<ul v-show="searchResults.length > 0" class="w-full">
<li
v-if="searchHistory.length > 0 && !keyword"
class="text-muted-foreground mb-2 text-xs"
>
{{ $t('widgets.search.recent') }}
</li>
<li
v-for="(item, index) in uniqueByField(searchResults, 'path')"
:key="item.path"
:class="
activeIndex === index
? 'active bg-primary text-primary-foreground'
: ''
"
:data-index="index"
:data-search-item="index"
class="bg-accent flex-center group mb-3 w-full cursor-pointer rounded-lg px-4 py-4"
@click="handleEnter"
@mouseenter="handleMouseenter"
>
<VbenIcon
:icon="item.icon"
class="mr-2 size-5 flex-shrink-0"
fallback
/>
<span class="flex-1">{{ item.name }}</span>
<div
class="flex-center dark:hover:bg-accent hover:text-primary-foreground rounded-full p-1 hover:scale-110"
@click.stop="removeItem(index)"
>
<X class="size-4" />
</div>
</li>
</ul>
</div>
</VbenScrollbar>
</template>

View File

@ -0,0 +1 @@
export { default as VbenLogo } from './logo.vue';

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
// import { VbenAvatar } from '../avatar';
interface Props {
/**
* @zh_CN 是否收起文本
*/
collapsed?: boolean;
/**
* @zh_CN Logo 跳转地址
*/
href?: string;
/**
* @zh_CN Logo 图片大小
*/
logoSize?: number;
/**
* @zh_CN Logo 图标
*/
src?: string;
/**
* @zh_CN Logo 文本
*/
text: string;
/**
* @zh_CN Logo 主题
*/
theme?: string;
/**
* logo字体颜色
*/
textClass?: string;
}
defineOptions({
name: 'VbenLogo',
});
withDefaults(defineProps<Props>(), {
collapsed: false,
href: 'javascript:void 0',
logoSize: 32,
src: '',
theme: 'light',
});
</script>
<template>
<div :class="theme" class="flex h-full items-center text-lg">
<a :class="$attrs.class" :href="href"
class="flex h-full items-center gap-2 overflow-hidden px-3 text-lg leading-normal transition-all duration-500">
<!-- <VbenAvatar v-if="src" :alt="text" :src="src" :size="logoSize" class="relative w-8 rounded-none bg-transparent" /> -->
<img v-if="src" :alt="text" :src="src" class="relative rounded-none bg-transparent w-100" >
<span v-if="!collapsed" class="text-foreground truncate text-nowrap font-semibold" :class="textClass">
{{ text }}
</span>
</a>
</div>
</template>

View File

@ -0,0 +1,3 @@
export { default as UserInfo } from './info.vue';
export { default as UserLike } from './like.vue';
export { default as UserSwitch } from './switch.vue';

View File

@ -0,0 +1,110 @@
<template>
<Modal title="基本信息" :visible="visible" :cancelText="'关闭'" @cancel="close">
<Form :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }">
<FormItem label="头像" class="mb-3">
<upload v-model:file-list="fileList" name="avatar" list-type="picture-card" class="avatar-uploader"
:show-upload-list="false" action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
:before-upload="beforeUpload" @change="handleChange">
<img v-if="imageUrl" :src="imageUrl" alt="avatar" />
<div v-else>
<loading-outlined v-if="loading"></loading-outlined>
<plus-outlined v-else></plus-outlined>
<div class="ant-upload-text">上传头像</div>
</div>
</upload>
</FormItem>
<FormItem :label="item.label" v-for="item in userData" :key="item.label" class="mb-3">
{{ item.value }}
</FormItem>
</Form>
<a href="#" class="text-[#007da3] ml-24">基本信息请至统一身份管理系统修改</a>
</Modal>
</template>
<script setup lang="ts">
import { Modal, Form, FormItem ,Upload } from 'ant-design-vue';
import { ref } from 'vue';
import { PlusOutlined, LoadingOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
function getBase64(img: Blob, callback: (base64Url: string) => void) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result as string));
reader.readAsDataURL(img);
}
const fileList = ref([]);
const loading = ref<boolean>(false);
const imageUrl = ref<string>('');
const handleChange = (info: UploadChangeParam) => {
if (info.file.status === 'uploading') {
loading.value = true;
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, (base64Url: string) => {
imageUrl.value = base64Url;
loading.value = false;
});
}
if (info.file.status === 'error') {
loading.value = false;
message.error('upload error');
}
};
const beforeUpload = (file: UploadProps['fileList'][number]) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
};
const visible = defineModel<boolean>('visible');
const close = () => {
visible.value = false
}
let userData = [{
label: '姓名',
value: '薛雄'
}, {
label: '登录名',
value: 'xuex23'
}, {
label: '性别',
value: '男'
}, {
label: '员工编号',
value: '00454711'
}, {
label: '机构单位',
value: '综合管理部(党委办公室)'
}, {
label: '邮件地址',
value: '123@163.com'
}]
</script>
<style scoped>
.avatar-uploader > .ant-upload {
width: 128px;
height: 128px;
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<Modal title="个人偏好" :visible="visible" @cancel="close">
<Tabs v-model:activeKey="activeKey" centered>
<TabPane key="1" tab="自定义菜单">Content of Tab Pane 1</TabPane>
<TabPane key="2" tab="菜单区设置" force-render>Content of Tab Pane 2</TabPane>
<TabPane key="3" tab="主题设置">Content of Tab Pane 3</TabPane>
<TabPane key="4" tab="消息语音">Content of Tab Pane 4</TabPane>
</Tabs>
<template #footer>
<Button type="link" key="back" @click="close">恢复</Button>
<Button key="back" @click="close">取消</Button>
<Button key="submit" type="primary" @click="close">确定</Button>
</template>
</Modal>
</template>
<script setup lang="ts">
import { Modal, Tabs, Form, FormItem, Select, Button, TabPane } from 'ant-design-vue';
import { ref } from 'vue';
let activeKey = ref('1');
let options1 = [
{
value: '1',
label: '西北油田xbyt',
},
{
value: '2',
label: '智能油气田znyqt',
}
]
const visible = defineModel<boolean>('visible');
const close = () => {
visible.value = false
}
</script>

View File

@ -0,0 +1,32 @@
<template>
<Modal title="切换租户" :visible="visible" @cancel="close">
<!-- <div class="flex justify-center mb-10">请选择您要切换到的租户</div> -->
<Form :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }">
<FormItem label="请选择租户">
<Select ref="select" v-model:value="value1" :options="options1"></Select>
</FormItem>
</Form>
</Modal>
</template>
<script setup lang="ts">
import { Modal, Form, FormItem, Select } from 'ant-design-vue';
import { ref } from 'vue';
let value1 = ref('1');
let options1 = [
{
value: '1',
label: '西北油田xbyt',
},
{
value: '2',
label: '智能油气田znyqt',
}
]
const visible = defineModel<boolean>('visible');
const close = () => {
visible.value = false
}
</script>

View File

@ -30,7 +30,13 @@ export const overridesPreferences = defineOverridesPreferences({
preferencesButtonPosition: 'auto',
watermark: false,
},
logo: {
source: '/logo1.png',
},
shortcutKeys: {
globalSearch: false,
},
header: {
height: 60,
},
}
});

View File

@ -2,6 +2,54 @@ import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { firstRouter, otherRouter } from "./linkdata"
const getRouter = (list: any, type: string) => {
let obj = [];
if (type == 'first') {
for (let i = 0; i < list.children.length; i++) {
obj.push({
name: list.children[i]?.rName,
path: '',
component: () => null,
meta: {
title: list.children[i]?.name,
link: list.children[i]?.url
},
})
}
} else {
for (let i = 0; i < list.length; i++) {
let chunk:any = {}
chunk.component = BasicLayout;
chunk.meta = {
icon: 'lucide:layout-dashboard',
title: list[i].name,
order: i,
};
chunk.name = list[i].rName;
chunk.path = '/'+list[i].rName;
chunk.children = [];
for (let j = 0; j < list[i].children.length; j++) {
chunk.children.push({
name: list[i].children[j]?.rName,
path: '',
component: () => null,
meta: {
title: list[i].children[j]?.name,
link: list[i].children[j]?.url
},
})
}
obj.push(chunk)
}
}
return obj
}
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
@ -10,7 +58,7 @@ const routes: RouteRecordRaw[] = [
order: -1,
title: '总部系统',
},
name: 'Dashboard',
name: 'zbxt',
path: '/home',
children: [
{
@ -23,34 +71,39 @@ const routes: RouteRecordRaw[] = [
title: '总部系统',
},
},
{
name: 'zshxgw',
path: '',
component: () => null,
meta: {
title: '中石化新公文系统',
link: 'http://www.baidu.com'
},
},
{
name: 'zshht',
path: '',
component: () => null,
meta: {
title: '中石化合同系统',
link: 'http://www.baidu.com'
},
},
...getRouter(firstRouter, 'first')
// {
// name: 'zshxgw',
// path: '',
// component: () => null,
// meta: {
// title: '中石化新公文系统',
// link: 'http://www.baidu.com'
// },
// },
// {
// name: 'zshht',
// path: '',
// component: () => null,
// meta: {
// title: '中石化合同系统',
// link: 'http://www.baidu.com'
// },
// },
// {
// name: 'Workspace',
// path: '/workspace',
// component: () => import('#/views/dashboard/workspace/index.vue'),
// component: () => import('#/views/dashboard/wor kspace/index.vue'),
// meta: {
// title: $t('page.dashboard.workspace'),
// },
// },
],
},
...getRouter(otherRouter,'other')
];
console.log('999999',...getRouter(otherRouter,'other'));
export default routes;

View File

@ -1,30 +1,30 @@
import type { RouteRecordRaw } from 'vue-router';
// import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
// import { BasicLayout } from '#/layouts';
// import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: $t('page.demos.title'),
},
name: 'Demos',
path: '/demos',
children: [
{
meta: {
title: $t('page.demos.antd'),
},
name: 'AntDesignDemos',
path: '/demos/ant-design',
component: () => import('#/views/demos/antd/index.vue'),
},
],
},
];
// const routes: RouteRecordRaw[] = [
// {
// component: BasicLayout,
// meta: {
// icon: 'ic:baseline-view-in-ar',
// keepAlive: true,
// order: 1000,
// title: $t('page.demos.title'),
// },
// name: 'Demos',
// path: '/demos',
// children: [
// {
// meta: {
// title: $t('page.demos.antd'),
// },
// name: 'AntDesignDemos',
// path: '/demos/ant-design',
// component: () => import('#/views/demos/antd/index.vue'),
// },
// ],
// },
// ];
export default routes;
// export default routes;

View File

@ -0,0 +1,232 @@
let firstRouter = {
"name": "总部系统",
"children": [
{
"name": "中石化新公文系统",
"url": "https://newoa12.sinopec.com?pageCode=zbxt_new_oa",
"rName": "zbxt_new_oa"
},
{
"name": "中石化移动应用平台",
"url": "https://console.m.sinopec.com/ACWeb/Portal/IndexPage?pageCode=zbxt_ydyypt",
"rName": "zbxt_ydyypt"
},
{
"name": "中石化合同系统",
"url": "https://cmis.sinopec.com/zen/portal/#/home?pageCode=zbxt_htxt",
"rName": "zbxt_htxt"
},
{
"name": "中石化党群管理系统",
"url": "https://dj.sinopec.com?pageCode=zbxt_dqgl",
"rName": "zbxt_dqgl"
},
{
"name": "中石化法律共享平台",
"url": "http://slsp.sinopec.com?pageCode=zbxt_flgl",
"rName": "zbxt_flgl"
},
{
"name": "中石化安全管理系统",
"url": "http://safety.sinopec.com:8000/IPWeb/Home?pageCode=zbxt_aqgl",
"rName": "zbxt_aqgl"
},
{
"name": "中石化法治合规管理",
"url": "https://lcrs.sinopec.com/?pageCode=zbxt_nkglxt",
"rName": "zbxt_nkglxt"
},
{
"name": "中石化制度管理系统",
"url": "http://10.249.201.77?pageCode=zbxt_zdgl",
"rName": "zbxt_zdgl"
},
{
"name": "中石化信息化标准系统",
"url": "http://infostd.sinopec.com/?pageCode=zbxt_xxhbz",
"rName": "zbxt_xxhbz"
},
{
"name": "中石化网络学院",
"url": "https://sia.sinopec.com/learn/index.html?pageCode=zbxt_wlxy",
"rName": "zbxt_wlxy"
},
{
"name": "中石化科技管理平台",
"url": "http://istm.sinopec.com/?pageCode=zbxt_kjglpt",
"rName": "zbxt_kjglpt"
},
{
"name": "中石化信创邮件系统",
"url": "https://webmail.sinopec.com/?pageCode=zbxt_xcyj",
"rName": "zbxt_xcyj"
}
]
}
let otherRouter =
[
{
"name": "经营管理",
"children": [
{
"name": "市场管理信息系统",
"url": "https://xbscgl.xbyt.sinopec.com?pageCode=jygl_scglxx",
"rName": "jygl_scglxx"
},
{
"name": "工程造价管理",
"url": "http://10.16.0.157:8082/xbscjyweb?pageCode=jygl_gczjgl",
"rName": "jygl_gczjgl"
}
],
"rName": "jygl"
},
{
"name": "勘探开发",
"icon": "lucide:area-chart",
"children": [
{
"name": "油气开发平台",
"url": "http://yqkfyw.xbyt.sinopec.com/webptframe/home_navtree_hn.html?pageCode=yqkfyw_kfpt",
"rName": "yqkfyw_kfpt"
},
{
"name": "地面工程GIS",
"url": "https://ssco.xbsj.sinopec.com/DMGCFZ//HomeXBYTGISFront/Index?pageCode=yqkfyw_gis",
"rName": "yqkfyw_gis"
},
{
"name": "设备管理系统",
"url": "https://sbgl.xbyt.sinopec.com:9006/XBEM?pageCode=yqkfyw_sbxt",
"rName": "yqkfyw_sbxt"
},
{
"name": "石油工程决策系统",
"url": "https://gcjc.xbyt.sinopec.com?pageCode=yqkfyw_sygc",
"rName": "yqkfyw_sygc"
},
{
"name": "完井快报",
"url": "http://xbyt.sinopec.com/wczx/Lists/sc/SinopecAllItems.aspx?pageCode=yqkfyw_",
"rName": "yqkfyw_"
},
{
"name": "钻井数据库系统",
"url": "http://10.16.0.74/tyrz_zjsjk/?pageCode=yqkfyw_zjsjk",
"rName": "yqkfyw_zjsjk"
},
{
"name": "完井测试日报",
"url": "http://xbyt.sinopec.com/wczx/Lists/kt/SinopecAllItems.aspx?pageCode=yqkfyw_wjcs",
"rName": "yqkfyw_wjcs"
},
{
"name": "IPPE",
"url": "http://ippe.sinopec.com/?pageCode=yqkfyw_ippe",
"rName": "yqkfyw_ippe"
}
],
"rName": "yqkfyw"
},
{
"name": "安全生产",
"children": [
{
"name": "生产重点",
"url": "http://10.16.128.237:9528/yxrb/zdbb/scxxyxrb?pageCode=scyx_zdjxx",
"rName": "scyx_zdjxx"
},
{
"name": "集输防腐数据平台",
"url": "https://dmgcysbgl.xbyt.sinopec.com:8080/xbyqjs//xbjqmh_1.0.0/mh/loginSuccess.action?pageCode=scyx_jsffsj",
"rName": "scyx_jsffsj"
},
{
"name": "土地信息管理系统",
"url": "http://10.16.0.73:8888/xbtd?pageCode=scyx_tdxxgl",
"rName": "scyx_tdxxgl"
},
{
"name": "HSSE管理系统",
"url": "http://10.16.128.247:8088/?pageCode=scyx_hseglxx",
"rName": "scyx_hseglxx"
},
{
"name": "生产运行(应急)指挥中心协同平台",
"url": "http://10.16.66.120:8088/home?pageCode=scyx_yjzhxt",
"rName": "scyx_yjzhxt"
}
],
"rName": "scyx"
},
{
"name": "综合行政",
"children": [
{
"name": "领导请销假",
"url": "https://10.16.0.128/xjOA/main/login/leave.jsp?pageCode=gcgl_ldqxj",
"rName": "gcgl_ldqxj"
},
{
"name": "督办管理",
"url": "https://10.16.0.128/xjOA/main/login/urge.jsp?pageCode=gcgl_db",
"rName": "gcgl_db"
},
{
"name": "员工诉求",
"url": "https://10.16.0.128/xjOA/main/login/appeal.jsp?pageCode=gcgl_ygsq",
"rName": "gcgl_ygsq"
},
{
"name": "会议管理系统",
"url": "http://10.16.0.128:8089/HYXT/?pageCode=gcgl_hyxt",
"rName": "gcgl_hyxt"
}
],
"rName": "gcgl"
},
{
"name": "数据管理",
"children": [
{
"name": "GIS数据服务",
"url": "http://10.16.0.210:8017?pageCode=sjgl_gissjfw",
"rName": "sjgl_gissjfw"
},
{
"name": "EPBP",
"url": "http://10.16.128.130/blankSSO.action?pageCode=sjgl_EPBP_ktkfsjcj",
"rName": "sjgl_EPBP_ktkfsjcj"
},
{
"name": "单井数据服务",
"url": "https://ssco.xbsj.sinopec.com/djsjfw/goto.html?pageCode=sjgl_djsjfw",
"rName": "sjgl_djsjfw"
},
{
"name": "数据管理平台",
"url": "http://10.16.3.30:10008/?pageCode=sjgl_sjgl",
"rName": "sjgl_sjgl"
},
{
"name": "文档资料共享环境",
"url": "https://10.16.67.140/xbsjzlgx/sso?pageCode=sjgl_wdzlgx",
"rName": "sjgl_wdzlgx"
},
{
"name": "视频共享平台",
"url": "http://10.16.128.81:8020/?pageCode=sjgl_spgxpt",
"rName": "sjgl_spgxpt"
},
{
"name": "smartbi报表工具",
"url": "http://10.16.67.135:18080/smartbi/vision/index.jsp?pageCode=sjgl_smartbi",
"rName": "sjgl_smartbi"
}
],
"rName": "sjgl"
}
]
export { firstRouter, otherRouter };

View File

@ -1,81 +1,81 @@
import type { RouteRecordRaw } from 'vue-router';
// import type { RouteRecordRaw } from 'vue-router';
import {
VBEN_DOC_URL,
VBEN_ELE_PREVIEW_URL,
VBEN_GITHUB_URL,
VBEN_LOGO_URL,
VBEN_NAIVE_PREVIEW_URL,
} from '@vben/constants';
// import {
// VBEN_DOC_URL,
// VBEN_ELE_PREVIEW_URL,
// VBEN_GITHUB_URL,
// VBEN_LOGO_URL,
// VBEN_NAIVE_PREVIEW_URL,
// } from '@vben/constants';
import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales';
// import { BasicLayout, IFrameView } from '#/layouts';
// import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9999,
title: $t('page.vben.title'),
},
name: 'VbenProject',
path: '/vben-admin',
children: [
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('page.vben.about'),
},
},
{
name: 'VbenDocument',
path: '/vben-admin/document',
component: IFrameView,
meta: {
icon: 'lucide:book-open-text',
link: VBEN_DOC_URL,
title: $t('page.vben.document'),
},
},
{
name: 'VbenGithub',
path: '/vben-admin/github',
component: IFrameView,
meta: {
icon: 'mdi:github',
link: VBEN_GITHUB_URL,
title: 'Github',
},
},
{
name: 'VbenNaive',
path: '/vben-admin/naive',
component: IFrameView,
meta: {
badgeType: 'dot',
icon: 'logos:naiveui',
link: VBEN_NAIVE_PREVIEW_URL,
title: $t('page.vben.naive-ui'),
},
},
{
name: 'VbenElementPlus',
path: '/vben-admin/ele',
component: IFrameView,
meta: {
badgeType: 'dot',
icon: 'logos:element',
link: VBEN_ELE_PREVIEW_URL,
title: $t('page.vben.element-plus'),
},
},
],
},
];
// const routes: RouteRecordRaw[] = [
// {
// component: BasicLayout,
// meta: {
// badgeType: 'dot',
// icon: VBEN_LOGO_URL,
// order: 9999,
// title: $t('page.vben.title'),
// },
// name: 'VbenProject',
// path: '/vben-admin',
// children: [
// {
// name: 'VbenAbout',
// path: '/vben-admin/about',
// component: () => import('#/views/_core/about/index.vue'),
// meta: {
// icon: 'lucide:copyright',
// title: $t('page.vben.about'),
// },
// },
// {
// name: 'VbenDocument',
// path: '/vben-admin/document',
// component: IFrameView,
// meta: {
// icon: 'lucide:book-open-text',
// link: VBEN_DOC_URL,
// title: $t('page.vben.document'),
// },
// },
// {
// name: 'VbenGithub',
// path: '/vben-admin/github',
// component: IFrameView,
// meta: {
// icon: 'mdi:github',
// link: VBEN_GITHUB_URL,
// title: 'Github',
// },
// },
// {
// name: 'VbenNaive',
// path: '/vben-admin/naive',
// component: IFrameView,
// meta: {
// badgeType: 'dot',
// icon: 'logos:naiveui',
// link: VBEN_NAIVE_PREVIEW_URL,
// title: $t('page.vben.naive-ui'),
// },
// },
// {
// name: 'VbenElementPlus',
// path: '/vben-admin/ele',
// component: IFrameView,
// meta: {
// badgeType: 'dot',
// icon: 'logos:element',
// link: VBEN_ELE_PREVIEW_URL,
// title: $t('page.vben.element-plus'),
// },
// },
// ],
// },
// ];
export default routes;
// export default routes;

View File

@ -107,7 +107,7 @@ onBeforeUnmount(() => {
</template>
<slot name="title"></slot>
</VbenTooltip>
<div v-show="!showTooltip" :class="[e('content')]">
<div v-show="!showTooltip" :class="[e('content')]" class="text-wrap">
<VbenMenuBadge
v-if="rootMenu.props.mode !== 'horizontal'"
class="right-2"