修改 ui 包结构.

This commit is contained in:
lijiahang
2023-07-29 13:11:19 +08:00
parent 900cd1c1d7
commit 5bee3b98a7
28 changed files with 149 additions and 365 deletions

View File

@@ -1,11 +0,0 @@
import axios from 'axios';
import type { RouteRecordNormalized } from 'vue-router';
import { UserState } from '@/store/modules/user/types';
export function getUserInfo() {
return axios.post<UserState>('/api/user/info');
}
export function getMenuList() {
return axios.post<RouteRecordNormalized[]>('/api/user/menu');
}

View File

@@ -1,4 +1,5 @@
import axios from 'axios';
import { UserState } from '@/store/modules/user/types';
export interface LoginRequest {
username: string;
@@ -9,10 +10,30 @@ export interface LoginResponse {
token: string;
}
/**
* 登陆
*/
export function login(data: LoginRequest) {
return axios.post<LoginResponse>('/infra/auth/login', data);
}
/**
* 登出
*/
export function logout() {
return axios.get('/infra/auth/logout');
}
/**
* 获取用户信息
*/
export function getUserPermission() {
return axios.get('/infra/permission/user');
}
/**
* 获取菜单列表
*/
export function getMenuList() {
return axios.get('/infra/permission/menu');
}

View File

@@ -12,6 +12,8 @@ body {
background-color: var(--color-bg-1);
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
--color-scrollbar-track: var(--color-neutral-1);
--color-scrollbar-thumb: #959FAB;
}
.echarts-tooltip-diy {
@@ -25,6 +27,7 @@ body {
/* Note: backdrop-filter has minimal browser support */
border-radius: 6px !important;
.content-panel {
display: flex;
justify-content: space-between;
@@ -37,12 +40,15 @@ body {
border-radius: 4px;
margin-bottom: 4px;
}
.tooltip-title {
margin: 0 0 10px 0;
}
p {
margin: 0;
}
.tooltip-title,
.tooltip-value {
font-size: 13px;
@@ -53,6 +59,7 @@ body {
color: #1d2129;
font-weight: bold;
}
.tooltip-item-icon {
display: inline-block;
margin-right: 8px;
@@ -65,11 +72,13 @@ body {
.general-card {
border-radius: 4px;
border: none;
& > .arco-card-header {
height: auto;
padding: 20px;
border: none;
}
& > .arco-card-body {
padding: 0 20px 20px 20px;
}
@@ -87,8 +96,32 @@ body {
height: 6px;
border-radius: 50%;
background-color: rgb(var(--blue-6));
&.pass {
background-color: rgb(var(--green-6));
}
}
}
#app {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
::-webkit-scrollbar-track {
background-color: var(--color-scrollbar-track);
border-radius: 8px;
}
::-webkit-scrollbar {
-webkit-appearance: none;
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
cursor: pointer;
border-radius: 8px;
background-color: var(--color-scrollbar-thumb);
transition: color .2s ease;
}

View File

@@ -5,7 +5,6 @@ import { useAppStore } from '@/store';
import { cloneDeep } from 'lodash';
export default function useMenuTree() {
const permission = usePermission();
const appStore = useAppStore();
const appRoute = computed(() => {
return appStore.appAsyncMenus;
@@ -20,39 +19,40 @@ export default function useMenuTree() {
if (!_routes) return null;
const collector: any = _routes.map((element) => {
// no access
if (!permission.accessRouter(element)) {
return null;
}
// leaf node
// 隐藏子目录
if (element.meta?.hideChildrenInMenu || !element.children) {
element.children = [];
return element;
if (element.meta?.hideInMenu) {
// 如果隐藏菜单 则不显示
return null;
} else {
return element;
}
}
// route filter hideInMenu true
// 过滤不显示的菜单
element.children = element.children.filter(
(x) => x.meta?.hideInMenu !== true
);
// Associated child node
// 关联子节点
const subItem = travel(element.children, layer + 1);
if (subItem.length) {
element.children = subItem;
return element;
}
// the else logic
// 第二层 (子目录)
if (layer > 1) {
element.children = subItem;
return element;
}
// 是否隐藏目录
if (element.meta?.hideInMenu === false) {
return element;
}
return null;
});
return collector.filter(Boolean);

View File

@@ -185,9 +185,8 @@
</template>
<script lang="ts" setup>
import { computed, ref, inject } from 'vue';
import { Message } from '@arco-design/web-vue';
import { useDark, useToggle, useFullscreen } from '@vueuse/core';
import { computed, inject, ref } from 'vue';
import { useDark, useFullscreen, useToggle } from '@vueuse/core';
import { useAppStore, useUserStore } from '@/store';
import { LOCALE_OPTIONS } from '@/locale';
import useLocale from '@/hooks/locale';

View File

@@ -1,14 +1,12 @@
import { DirectiveBinding } from 'vue';
import { useUserStore } from '@/store';
import usePermission from '@/hooks/permission';
function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
const userStore = useUserStore();
// TODO TEST
const permission = usePermission();
if (Array.isArray(value)) {
if (value.length > 0) {
const hasPermission = userStore.permission?.includes(value as any as string);
if (!hasPermission && el.parentNode) {
if (!permission.hasAnyPermission(value) && el.parentNode) {
el.parentNode.removeChild(el);
}
}

View File

@@ -17,7 +17,6 @@ export default function useChartOption(sourceOption: optionsFn) {
});
// echarts support https://echarts.apache.org/zh/theme-builder.html
// It's not used here
// TODO echarts themes
const chartOption = computed<EChartsOption>(() => {
return sourceOption(isDark.value);
});

View File

@@ -27,6 +27,7 @@
v-if="hideMenu"
:visible="drawerVisible"
placement="left"
:header="false"
:footer="false"
mask-closable
:closable="false"
@@ -47,23 +48,20 @@
</template>
<script lang="ts" setup>
import { ref, computed, watch, provide, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAppStore, useUserStore } from '@/store';
import { computed, onMounted, provide, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAppStore } from '@/store';
import NavBar from '@/components/navbar/index.vue';
import Menu from '@/components/menu/index.vue';
import Footer from '@/components/footer/index.vue';
import TabBar from '@/components/tab-bar/index.vue';
import usePermission from '@/hooks/permission';
import useResponsive from '@/hooks/responsive';
import PageLayout from './page-layout.vue';
const isInit = ref(false);
const appStore = useAppStore();
const userStore = useUserStore();
const router = useRouter();
const route = useRoute();
const permission = usePermission();
useResponsive(true);
const navbarHeight = `60px`;
const navbar = computed(() => appStore.navbar);
@@ -94,15 +92,6 @@
appStore.updateSettings({ menuCollapse: val });
};
// 监听权限变化
watch(
() => userStore.permission,
(permissionValue) => {
if (permissionValue && !permission.accessRouter(route))
router.push({ name: 'forbidden' });
}
);
const drawerVisible = ref(false);
const drawerCancel = () => {
drawerVisible.value = false;

View File

@@ -6,20 +6,6 @@ import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN';
import localeSettings from './zh-CN/settings';
export default {
'menu.dashboard': '仪表盘',
'menu.server.dashboard': '仪表盘-服务端',
'menu.server.workplace': '工作台-服务端',
'menu.server.monitor': '实时监控-服务端',
'menu.list': '列表页',
'menu.result': '结果页',
'menu.exception': '异常页',
'menu.form': '表单页',
'menu.profile': '详情页',
'menu.visualization': '数据可视化',
'menu.user': '个人中心',
'menu.arcoWebsite': 'Arco Design',
'menu.faq': '常见问题',
'navbar.docs': '文档中心',
'navbar.action.locale': '切换为中文',
...localeSettings,
...localeMessageBox,

View File

@@ -1,7 +1,5 @@
import Mock from 'mockjs';
import './user';
import './message-box';
// import './user';
import '@/views/dashboard/workplace/mock';

View File

@@ -1,85 +0,0 @@
import Mock from 'mockjs';
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
const haveReadIds: number[] = [];
const getMessageList = () => {
return [
{
id: 1,
type: 'message',
title: '郑曦月',
subTitle: '的私信',
avatar:
'123123',
content: '审批请求已发送,请查收',
time: '今天 12:30:01',
},
{
id: 2,
type: 'message',
title: '宁波',
subTitle: '的回复',
avatar:
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
content: '此处 bug 已经修复',
time: '今天 12:30:01',
},
{
id: 3,
type: 'message',
title: '宁波',
subTitle: '的回复',
avatar:
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
content: '此处 bug 已经修复',
time: '今天 12:20:01',
},
{
id: 4,
type: 'notice',
title: '续费通知',
subTitle: '',
avatar: '',
content: '您的产品使用期限即将截止,如需继续使用产品请前往购…',
time: '今天 12:20:01',
messageType: 3,
},
{
id: 5,
type: 'notice',
title: '规则开通成功',
subTitle: '',
avatar: '',
content: '内容屏蔽规则于 2021-12-01 开通成功并生效',
time: '今天 12:20:01',
messageType: 1,
},
{
id: 6,
type: 'todo',
title: '质检队列变更',
subTitle: '',
avatar: '',
content: '内容质检队列于 2021-12-01 19:50:23 进行变更,请重新…',
time: '今天 12:20:01',
messageType: 0,
},
].map((item) => ({
...item,
status: haveReadIds.indexOf(item.id) === -1 ? 0 : 1,
}));
};
setupMock({
setup: () => {
Mock.mock(new RegExp('/api/message/list'), () => {
return successResponseWrap(getMessageList());
});
Mock.mock(new RegExp('/api/message/read'), (params: { body: string }) => {
const { ids } = JSON.parse(params.body);
haveReadIds.push(...(ids || []));
return successResponseWrap(true);
});
},
});

View File

@@ -1,104 +1,16 @@
import Mock from 'mockjs';
import setupMock, {
successResponseWrap,
failResponseWrap,
} from '@/utils/setup-mock';
import { MockParams } from '@/types/mock';
import { isLogin } from '@/utils/auth';
setupMock({
setup() {
// Mock.XHR.prototype.withCredentials = true;
// 用户信息
Mock.mock(new RegExp('/api/user/info'), () => {
if (isLogin()) {
const role = window.localStorage.getItem('userRole') || 'admin';
return successResponseWrap({
id: '1',
name: '王立群',
username:
'//lf1-xgcdn-tos.pstatp.com/obj/vcloud/vadmin/start.8e0e4855ee346a46ccff8ff3e24db27b.png',
email: 'wangliqun@email.com',
job: 'frontend',
jobName: '前端艺术家',
organization: 'Frontend',
organizationName: '前端',
location: 'beijing',
locationName: '北京',
introduction: '人潇洒,性温存',
personalWebsite: 'https://www.arco.design',
phone: '150****0000',
registrationDate: '2013-05-10 12:10:00',
accountId: '15012312300',
certification: 1,
role,
});
}
return failResponseWrap(null, '未登录', 50008);
});
// 用户的服务端菜单
Mock.mock(new RegExp('/api/user/menu'), () => {
const menuList = [
{
path: '/dashboard',
name: 'dashboard',
meta: {
locale: 'menu.server.dashboard',
requiresAuth: true,
icon: 'icon-dashboard',
order: 1,
},
children: [
{
path: 'workplace',
name: 'workplace',
meta: {
locale: 'menu.server.workplace',
requiresAuth: true,
},
},
{
path: 'https://arco.design',
name: 'arcoWebsite',
meta: {
locale: 'menu.arcoWebsite',
requiresAuth: true,
},
},
],
},
{
name: 'User',
meta: {
locale: '1',
requiresAuth: true,
icon: 'icon-dashboard',
order: 1,
},
children: [
{
path: '/user/userChild',
name: 'userChild',
meta: {
locale: 'userChild',
requiresAuth: true,
},
},
{
path: '/user/userChild1',
name: 'userChild1',
meta: {
locale: 'userChild1',
requiresAuth: true,
},
},
],
},
];
return successResponseWrap(menuList);
});
},
});
// import Mock from 'mockjs';
// import setupMock, { successResponseWrap, } from '@/utils/setup-mock';
//
// setupMock({
// setup() {
// // Mock.XHR.prototype.withCredentials = true;
//
// // 用户信息
// Mock.mock(new RegExp('/api/user/info'), () => {
// return successResponseWrap({
// id: '1',
// });
// });
//
// },
// });

View File

@@ -7,7 +7,10 @@ import setupRouteEmitterGuard from './router-listener-emitter';
* 创建路由守卫
*/
export default function createRouteGuard(router: Router) {
// 路由监听守卫
setupRouteEmitterGuard(router);
// 登录检查守卫
setupUserLoginInfoGuard(router);
// 权限检查守卫
setupPermissionGuard(router);
}

View File

@@ -24,9 +24,6 @@ export const LOGIN_ROUTER: RouteRecordRaw = {
path: '/login',
name: LOGIN_ROUTE_NAME,
component: () => import('@/views/login/index.vue'),
meta: {
requiresAuth: false,
},
};
/**
@@ -37,7 +34,6 @@ export const REDIRECT_ROUTER: RouteRecordRaw = {
name: 'redirectWrapper',
component: DEFAULT_LAYOUT,
meta: {
requiresAuth: true,
hideInMenu: true,
},
children: [
@@ -46,7 +42,6 @@ export const REDIRECT_ROUTER: RouteRecordRaw = {
name: REDIRECT_ROUTE_NAME,
component: () => import('@/views/redirect/index.vue'),
meta: {
requiresAuth: true,
hideInMenu: true,
},
},

View File

@@ -2,24 +2,13 @@ import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const DASHBOARD: AppRouteRecordRaw = {
path: '/dashboard',
name: 'dashboard',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.dashboard',
requiresAuth: true,
icon: 'icon-dashboard',
order: 0,
},
children: [
{
path: 'workplace',
path: '/dashboard/workplace',
name: 'workplace',
component: () => import('@/views/dashboard/workplace/index.vue'),
meta: {
locale: 'menu.dashboard.workplace',
requiresAuth: true,
},
},
],
};

View File

@@ -2,24 +2,18 @@ import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const USER: AppRouteRecordRaw = {
path: '/user',
name: 'user',
component: DEFAULT_LAYOUT,
meta: {
locale: 'USER',
requiresAuth: true,
icon: 'icon-dashboard',
order: 0,
},
children: [
{
path: 'userChild',
name: 'userChild',
component: () => import('@/views/user/child/index.vue'),
meta: {
locale: '用户子页面',
requiresAuth: true,
},
path: '/user/userChild1',
name: 'userChild1',
component: () => import('@/views/user/child1/index.vue'),
},
{
path: '/user/userChild2',
name: 'userChild2',
component: () => import('@/views/user/child2/index.vue'),
},
],
};

View File

@@ -7,7 +7,7 @@ export type Component<T = any> =
| (() => Promise<T>);
export interface AppRouteRecordRaw {
path: string;
path?: string;
name?: string | symbol;
meta?: RouteMeta;
redirect?: string;

View File

@@ -2,15 +2,21 @@ import 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
permission?: string;
requiresAuth: boolean;
// 后端赋值
icon?: string;
// 后端赋值
locale?: string;
hideInMenu?: boolean;
hideChildrenInMenu?: boolean;
activeMenu?: string;
// 后端赋值
order?: number;
// 后端赋值 是否隐藏菜单
hideInMenu?: boolean;
// 后端赋值 是否隐藏子菜单
hideChildrenInMenu?: boolean;
// 后端赋值 是否添加到 tab
noAffix?: boolean;
// 前端赋值 是否忽略缓存
ignoreCache?: boolean;
// 不赋值
activeMenu?: string;
}
}

View File

@@ -15,5 +15,7 @@ export interface AppState {
device: string;
tabBar: boolean;
serverMenu: RouteRecordNormalized[];
menuFetched: boolean;
[key: string]: unknown;
}

View File

@@ -1,26 +1,9 @@
import type { RouteLocationNormalized } from 'vue-router';
import { defineStore } from 'pinia';
import { DEFAULT_TAB, DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME, } from '@/router/constants';
import { DEFAULT_TAB, DEFAULT_ROUTE_NAME, BAN_TAB_LIST, routerToTag } from '@/router/constants';
import { isString } from '@/utils/is';
import { TabBarState, TagProps } from './types';
/**
* router 转 tag
*/
const formatTag = (route: RouteLocationNormalized): TagProps => {
const { name, meta, fullPath, query } = route;
return {
title: meta.locale || '',
name: String(name),
fullPath,
query,
ignoreCache: meta.ignoreCache,
};
};
// 不添加的 tab 集合
const BAN_LIST = [REDIRECT_ROUTE_NAME];
const useTabBarStore = defineStore('tabBar', {
state: (): TabBarState => ({
cacheTabList: new Set([DEFAULT_ROUTE_NAME]),
@@ -41,8 +24,8 @@ const useTabBarStore = defineStore('tabBar', {
* 添加 tab
*/
updateTabList(route: RouteLocationNormalized) {
if (BAN_LIST.includes(route.name as string)) return;
this.tagList.push(formatTag(route));
if (BAN_TAB_LIST.includes(route.name as string)) return;
this.tagList.push(routerToTag(route));
if (!route.meta.ignoreCache) {
this.cacheTabList.add(route.name as string);
}

View File

@@ -3,6 +3,6 @@ export interface UserState {
username?: string;
nickname?: string;
avatar?: string;
permission?: string[];
roles?: string[];
permission?: string[];
}

View File

@@ -1,5 +0,0 @@
export interface MockParams {
url: string;
type: string;
body: string;
}

View File

@@ -57,37 +57,6 @@ export function isEmptyStr(val: any) {
return typeof (val) === 'undefined' || val == null || val === '';
}
/**
* 复制到剪切板
*/
// export function copyToClipboard(content: any) {
// const clipboardData = window.clipboardData;
// if (clipboardData) {
// clipboardData.clearData();
// clipboardData.setData('Text', content);
// return true;
// } else if (document.execCommand) {
// const el = document.createElement('textarea');
// el.value = content;
// el.setAttribute('readonly', '');
// el.style.position = 'absolute';
// el.style.left = '-9999px';
// document.body.appendChild(el);
// el.select();
// document.execCommand('copy');
// document.body.removeChild(el);
// return true;
// }
// return false;
// }
/**
* 获取剪切板内容 返回 promise
*/
export function getClipboardText() {
return navigator.clipboard.readText();
}
/**
* 格式化时间
*/

View File

@@ -7,7 +7,6 @@ export default ({ mock, setup }: { mock?: boolean; setup: () => void }) => {
export const successResponseWrap = (data: unknown) => {
return {
data,
status: 'ok',
msg: '请求成功',
code: 200,
};
@@ -16,7 +15,6 @@ export const successResponseWrap = (data: unknown) => {
export const failResponseWrap = (data: unknown, msg: string, code = 5000) => {
return {
data,
status: 'fail',
msg,
code,
};

View File

@@ -56,7 +56,7 @@
<script lang="ts">
export default {
name: 'Dashboard', // If you want the include property of keep-alive to take effect, you must name the component
name: 'workplace',
};
</script>
@@ -83,13 +83,16 @@
border-radius: 4px;
overflow: auto;
}
:deep(.panel-border) {
margin-bottom: 0;
border-bottom: 1px solid rgb(var(--gray-2));
}
.moduler-wrap {
border-radius: 4px;
background-color: var(--color-bg-2);
:deep(.text) {
font-size: 12px;
text-align: center;
@@ -106,11 +109,13 @@
margin-bottom: 0;
}
}
&:hover {
.icon {
color: rgb(var(--arcoblue-6));
background-color: #e8f3ff;
}
.text {
color: rgb(var(--arcoblue-6));
}
@@ -138,6 +143,7 @@
.container {
display: block;
}
.right-side {
// display: none;
width: 100%;

View File

@@ -1,13 +0,0 @@
<template>
<p>UserChild</p>
</template>
<script>
export default {
name: 'UserChild',
};
</script>
<style scoped>
</style>

View File

@@ -1,10 +1,12 @@
<template>
<p>这是user组件</p>
<div>
<p>UserChild 1</p>
</div>
</template>
<script>
export default {
name: 'User',
name: 'UserChild1',
};
</script>

View File

@@ -0,0 +1,16 @@
<template>
<div>
<p>UserChild 2</p>
<h1 v-permission="['admin']">123</h1>
</div>
</template>
<script>
export default {
name: 'UserChild2',
};
</script>
<style scoped>
</style>