feat: 注释&剔除无用代码&配置调整&TODO
This commit is contained in:
parent
50fe6619c8
commit
9798aa0bb1
|
@ -73,6 +73,7 @@ module.exports = {
|
|||
'global-require': 0,
|
||||
'no-plusplus': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'vue/attributes-order': 1,
|
||||
},
|
||||
// 对特定文件进行配置
|
||||
overrides: [
|
||||
|
|
|
@ -6,11 +6,13 @@ import svgLoader from 'vite-svg-loader';
|
|||
import configArcoStyleImportPlugin from './plugin/arcoStyleImport';
|
||||
import configArcoResolverPlugin from './plugin/arcoResolver';
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
|
||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
vueSetupExtend(),
|
||||
svgLoader({ svgoConfig: {} }),
|
||||
configArcoResolverPlugin(),
|
||||
configArcoStyleImportPlugin(),
|
||||
|
|
|
@ -36,8 +36,8 @@
|
|||
"dependencies": {
|
||||
"@7polo/kity": "2.0.8",
|
||||
"@7polo/kityminder-core": "1.4.53",
|
||||
"@arco-design/web-vue": "^2.46.0",
|
||||
"@arco-themes/vue-ms-theme-default": "^0.0.7",
|
||||
"@arco-design/web-vue": "^2.46.2",
|
||||
"@arco-themes/vue-ms-theme-default": "^0.0.12",
|
||||
"@form-create/arco-design": "^3.1.21",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"ace-builds": "^1.21.1",
|
||||
|
@ -54,7 +54,7 @@
|
|||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"query-string": "^8.1.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"vue": "^3.3.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-echarts": "^6.5.5",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.2.0",
|
||||
|
@ -101,7 +101,7 @@
|
|||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"sass": "^1.62.1",
|
||||
"stylelint": "^14.6.0",
|
||||
"stylelint": "^14.16.1",
|
||||
"stylelint-config-html": "^1.0.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
|
@ -121,6 +121,7 @@
|
|||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-imagemin": "^0.6.1",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||
"vite-svg-loader": "^3.6.0",
|
||||
"vitest": "^0.31.0",
|
||||
"vue-tsc": "^1.6.5"
|
||||
|
|
|
@ -5,7 +5,29 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, nextTick } from 'vue';
|
||||
import VCharts from 'vue-echarts';
|
||||
// import { useAppStore } from '@/store';
|
||||
import { use } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
GraphicComponent,
|
||||
} from 'echarts/components';
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
RadarChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
GraphicComponent,
|
||||
]);
|
||||
|
||||
defineProps({
|
||||
options: {
|
||||
|
@ -27,13 +49,8 @@
|
|||
default: '100%',
|
||||
},
|
||||
});
|
||||
// const appStore = useAppStore();
|
||||
// const theme = computed(() => {
|
||||
// if (appStore.theme === 'dark') return 'dark';
|
||||
// return '';
|
||||
// });
|
||||
|
||||
const renderChart = ref(false);
|
||||
// wait container expand
|
||||
nextTick(() => {
|
||||
renderChart.value = true;
|
||||
});
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import { App } from 'vue';
|
||||
import { use } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
GraphicComponent,
|
||||
} from 'echarts/components';
|
||||
import Chart from './chart/index.vue';
|
||||
import Breadcrumb from './breadcrumb/index.vue';
|
||||
|
||||
// Manually introduce ECharts modules to reduce packing size
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
RadarChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
GraphicComponent,
|
||||
]);
|
||||
|
||||
export default {
|
||||
install(Vue: App) {
|
||||
Vue.component('Chart', Chart);
|
||||
Vue.component('Breadcrumb', Breadcrumb);
|
||||
},
|
||||
};
|
|
@ -30,45 +30,57 @@
|
|||
const openKeys = ref<string[]>([]);
|
||||
const selectedKey = ref<string[]>([]);
|
||||
|
||||
const goto = (item: RouteRecordRaw) => {
|
||||
// Open external link
|
||||
if (regexUrl.test(item.path)) {
|
||||
openWindow(item.path);
|
||||
selectedKey.value = [item.name as string];
|
||||
return;
|
||||
const goto = (item: RouteRecordRaw | null) => {
|
||||
if (item) {
|
||||
// 如果菜单是外链
|
||||
if (regexUrl.test(item.path)) {
|
||||
openWindow(item.path);
|
||||
selectedKey.value = [item.name as string];
|
||||
return;
|
||||
}
|
||||
// 已激活的菜单重复点击不处理
|
||||
const { hideInMenu, activeMenu } = item.meta as RouteMeta;
|
||||
if (route.name === item.name && !hideInMenu && !activeMenu) {
|
||||
selectedKey.value = [item.name as string];
|
||||
return;
|
||||
}
|
||||
router.push({
|
||||
name: item.name,
|
||||
});
|
||||
} else {
|
||||
router.push({
|
||||
name: 'notFound',
|
||||
});
|
||||
}
|
||||
// Eliminate external link side effects
|
||||
const { hideInMenu, activeMenu } = item.meta as RouteMeta;
|
||||
if (route.name === item.name && !hideInMenu && !activeMenu) {
|
||||
selectedKey.value = [item.name as string];
|
||||
return;
|
||||
}
|
||||
// Trigger router change
|
||||
router.push({
|
||||
name: item.name,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 查找激活的菜单项
|
||||
* @param target 目标菜单名
|
||||
*/
|
||||
const findMenuOpenKeys = (target: string) => {
|
||||
const result: string[] = [];
|
||||
let isFind = false;
|
||||
const backtrack = (item: RouteRecordRaw, keys: string[]) => {
|
||||
if (item.name === target) {
|
||||
const backtrack = (item: RouteRecordRaw | null, keys: string[]) => {
|
||||
if (item?.name === target) {
|
||||
isFind = true;
|
||||
result.push(...keys);
|
||||
return;
|
||||
}
|
||||
if (item.children?.length) {
|
||||
if (item?.children?.length) {
|
||||
item.children.forEach((el) => {
|
||||
backtrack(el, [...keys, el.name as string]);
|
||||
});
|
||||
}
|
||||
};
|
||||
menuTree.value.forEach((el: RouteRecordRaw) => {
|
||||
menuTree.value?.forEach((el: RouteRecordRaw | null) => {
|
||||
if (isFind) return; // Performance optimization
|
||||
backtrack(el, [el.name as string]);
|
||||
backtrack(el, [el?.name as string]);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
/**
|
||||
* 监听路由变化,存储打开及选中的菜单
|
||||
*/
|
||||
listenerRouteChange((newRoute) => {
|
||||
const { requiresAuth, activeMenu, hideInMenu } = newRoute.meta;
|
||||
if (requiresAuth !== false && (!hideInMenu || activeMenu)) {
|
||||
|
@ -85,7 +97,7 @@
|
|||
};
|
||||
|
||||
const renderSubMenu = () => {
|
||||
function travel(_route: RouteRecordRaw[], nodes = []) {
|
||||
function travel(_route: (RouteRecordRaw | null)[] | null, nodes = []) {
|
||||
if (_route) {
|
||||
_route.forEach((element) => {
|
||||
// This is demo, modify nodes as needed
|
||||
|
|
|
@ -4,6 +4,10 @@ import usePermission from '@/hooks/usePermission';
|
|||
import appClientMenus from '@/router/app-menus';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* 获取菜单树
|
||||
* @returns
|
||||
*/
|
||||
export default function useMenuTree() {
|
||||
const permission = usePermission();
|
||||
const menuTree = computed(() => {
|
||||
|
@ -14,7 +18,7 @@ export default function useMenuTree() {
|
|||
function travel(_routes: RouteRecordRaw[], layer: number) {
|
||||
if (!_routes) return null;
|
||||
|
||||
const collector: any = _routes.map((element) => {
|
||||
const collector = _routes.map((element) => {
|
||||
// 权限校验不通过
|
||||
if (!permission.accessRouter(element)) {
|
||||
return null;
|
||||
|
@ -32,13 +36,13 @@ export default function useMenuTree() {
|
|||
// 解析子菜单
|
||||
const subItem = travel(element.children, layer + 1);
|
||||
|
||||
if (subItem.length) {
|
||||
element.children = subItem;
|
||||
if (subItem && subItem.length) {
|
||||
element.children = subItem as RouteRecordRaw[];
|
||||
return element;
|
||||
}
|
||||
// the else logic
|
||||
if (layer > 1) {
|
||||
element.children = subItem;
|
||||
element.children = subItem as RouteRecordRaw[];
|
||||
return element;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
import { PropType, computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useTabBarStore } from '@/store';
|
||||
import type { TagProps } from '@/store/modules/tab-bar/types';
|
||||
import type { TabProps } from '@/store/modules/tab-bar/types';
|
||||
import { DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
|
@ -63,7 +63,7 @@
|
|||
|
||||
const props = defineProps({
|
||||
itemData: {
|
||||
type: Object as PropType<TagProps>,
|
||||
type: Object as PropType<TabProps>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
|
@ -78,7 +78,7 @@
|
|||
const route = useRoute();
|
||||
const tabBarStore = useTabBarStore();
|
||||
|
||||
const goto = (tag: TagProps) => {
|
||||
const goto = (tag: TabProps) => {
|
||||
router.push({ ...tag });
|
||||
};
|
||||
const tabList = computed(() => {
|
||||
|
@ -101,7 +101,7 @@
|
|||
return props.index === tabList.value.length - 1;
|
||||
});
|
||||
|
||||
const tagClose = (tag: TagProps, idx: number) => {
|
||||
const tagClose = (tag: TabProps, idx: number) => {
|
||||
tabBarStore.deleteTag(idx, tag);
|
||||
if (props.itemData.fullPath === route.fullPath) {
|
||||
const latest = tabList.value[idx - 1]; // 获取队列的前一个tab
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { DirectiveBinding } from 'vue';
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
/**
|
||||
* 权限指令,TODO:权限判定按权限点来
|
||||
* @param el dom 节点
|
||||
* @param binding vue 绑定的数据
|
||||
*/
|
||||
function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const { value } = binding;
|
||||
const userStore = useUserStore();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @description: Request result set
|
||||
* 请求结果枚举
|
||||
*/
|
||||
export enum ResultEnum {
|
||||
SUCCESS = 0,
|
||||
|
@ -9,7 +9,7 @@ export enum ResultEnum {
|
|||
}
|
||||
|
||||
/**
|
||||
* @description: request method
|
||||
* 请求方法枚举
|
||||
*/
|
||||
export enum RequestEnum {
|
||||
GET = 'GET',
|
||||
|
@ -19,7 +19,7 @@ export enum RequestEnum {
|
|||
}
|
||||
|
||||
/**
|
||||
* @description: contentTyp
|
||||
* 请求响应体格式
|
||||
*/
|
||||
export enum ContentTypeEnum {
|
||||
// json
|
||||
|
|
|
@ -10,6 +10,9 @@ interface optionsFn {
|
|||
(isDark: boolean): EChartsOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* echarts 配置
|
||||
*/
|
||||
export default function useChartOption(sourceOption: optionsFn) {
|
||||
const appStore = useAppStore();
|
||||
const isDark = computed(() => {
|
||||
|
|
|
@ -11,22 +11,12 @@ type I18nGlobalTranslation = {
|
|||
|
||||
type I18nTranslationRestParameters = [string, any];
|
||||
|
||||
function getKey(namespace: string | undefined, key: string) {
|
||||
if (!namespace) {
|
||||
return key;
|
||||
}
|
||||
if (key.startsWith(namespace)) {
|
||||
return key;
|
||||
}
|
||||
return `${namespace}.${key}`;
|
||||
}
|
||||
|
||||
export function useI18n(namespace?: string): {
|
||||
t: I18nGlobalTranslation;
|
||||
} {
|
||||
const normalFn = {
|
||||
t: (key: string) => {
|
||||
return getKey(namespace, key);
|
||||
return key;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -40,7 +30,7 @@ export function useI18n(namespace?: string): {
|
|||
if (!key) return '';
|
||||
if (!key.includes('.') && !namespace) return key;
|
||||
// @ts-ignore
|
||||
return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters));
|
||||
return t(key, ...(arg as I18nTranslationRestParameters));
|
||||
};
|
||||
return {
|
||||
...methods,
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* loading显示隐藏
|
||||
* @param initValue 初始化值
|
||||
* @returns 调用方法
|
||||
*/
|
||||
export default function useLoading(initValue = false) {
|
||||
const loading = ref(initValue);
|
||||
const setLoading = (value: boolean) => {
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
/**
|
||||
* 用户权限
|
||||
* @returns 调用方法
|
||||
*/
|
||||
export default function usePermission() {
|
||||
const userStore = useUserStore();
|
||||
return {
|
||||
/**
|
||||
* 是否为允许访问的路由
|
||||
* @param route 路由信息
|
||||
* @returns 是否
|
||||
*/
|
||||
accessRouter(route: RouteLocationNormalized | RouteRecordRaw) {
|
||||
return (
|
||||
route.meta?.requiresAuth === false ||
|
||||
|
@ -12,6 +21,12 @@ export default function usePermission() {
|
|||
route.meta?.roles?.includes(userStore.role)
|
||||
);
|
||||
},
|
||||
/**
|
||||
* 查找第一个允许访问的路由
|
||||
* @param _routers 路由数组
|
||||
* @param role 用户角色
|
||||
* @returns 路由信息 or null
|
||||
*/
|
||||
findFirstPermissionRoute(_routers: any, role = 'admin') {
|
||||
const cloneRouters = [..._routers];
|
||||
while (cloneRouters.length) {
|
||||
|
|
|
@ -5,13 +5,24 @@ import { addEventListen, removeEventListen } from '@/utils/event';
|
|||
|
||||
const WIDTH = 992; // https://arco.design/vue/component/grid#responsivevalue
|
||||
|
||||
/**
|
||||
* 判断设备是否为移动端
|
||||
* @returns 是否为移动端
|
||||
*/
|
||||
function queryDevice() {
|
||||
const rect = document.body.getBoundingClientRect();
|
||||
return rect.width - 1 < WIDTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应布局变化
|
||||
* @param immediate 是否立即执行
|
||||
*/
|
||||
export default function useResponsive(immediate?: boolean) {
|
||||
const appStore = useAppStore();
|
||||
/**
|
||||
* 切换布局
|
||||
*/
|
||||
function resizeHandler() {
|
||||
if (!document.hidden) {
|
||||
const isMobile = queryDevice();
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { computed } from 'vue';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
/**
|
||||
* 暗黑模式相关
|
||||
* @returns 调用方法
|
||||
*/
|
||||
export default function useThemes() {
|
||||
const appStore = useAppStore();
|
||||
const isDark = computed(() => {
|
||||
|
|
|
@ -3,6 +3,10 @@ import { Message } from '@arco-design/web-vue';
|
|||
import { useUserStore } from '@/store';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
/**
|
||||
* 用户相关
|
||||
* @returns 调用方法
|
||||
*/
|
||||
export default function useUser() {
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* 切换显示隐藏
|
||||
* @param initValue 初始化值
|
||||
* @returns 调用方法
|
||||
*/
|
||||
export default function useVisible(initValue = false) {
|
||||
const visible = ref(initValue);
|
||||
const setVisible = (value: boolean) => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="fade" mode="out-in" appear>
|
||||
<!-- transition内必须有且只有一个根元素,不然会导致二级路由的组件无法渲染 -->
|
||||
<div>
|
||||
<component :is="Component" v-if="route.meta.ignoreCache" :key="route.fullPath" />
|
||||
<keep-alive v-else :include="cacheList">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { computed, unref } from 'vue';
|
||||
import { unref, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { i18n } from '@/locale';
|
||||
import { setHtmlPageLang, loadLocalePool } from '@/locale/helper';
|
||||
|
@ -11,6 +11,10 @@ interface LangModule {
|
|||
dayjsLocaleName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
* @param locale 语言类型
|
||||
*/
|
||||
function setI18nLanguage(locale: LocaleType) {
|
||||
if (i18n.mode === 'legacy') {
|
||||
i18n.global.locale = locale;
|
||||
|
@ -21,6 +25,11 @@ function setI18nLanguage(locale: LocaleType) {
|
|||
setHtmlPageLang(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换语言
|
||||
* @param locale 语言类型
|
||||
* @returns 语言类型
|
||||
*/
|
||||
async function changeLocale(locale: LocaleType) {
|
||||
const globalI18n = i18n.global;
|
||||
const currentLocale = unref(globalI18n.locale);
|
||||
|
@ -47,9 +56,7 @@ async function changeLocale(locale: LocaleType) {
|
|||
|
||||
export default function useLocale() {
|
||||
const { locale } = i18n.global;
|
||||
const currentLocale = computed(() => {
|
||||
return locale;
|
||||
});
|
||||
const currentLocale = ref(locale);
|
||||
|
||||
return {
|
||||
currentLocale,
|
||||
|
|
|
@ -2,6 +2,7 @@ import { createApp } from 'vue';
|
|||
import FormCreate from '@form-create/arco-design';
|
||||
import ArcoVueIcon from '@arco-design/web-vue/es/icon';
|
||||
import SvgIcon from '@/components/svg-icon/index.vue';
|
||||
import Breadcrumb from '@/components/breadcrumb/index.vue';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import { setupI18n } from './locale';
|
||||
|
@ -24,6 +25,7 @@ async function bootstrap() {
|
|||
|
||||
app.use(ArcoVueIcon);
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
app.component('Breadcrumb', Breadcrumb);
|
||||
|
||||
app.use(router);
|
||||
app.use(directive);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// 请求返回结构
|
||||
export default interface CommonReponse<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
export type HttpResponse = {
|
||||
data: any;
|
||||
code: number;
|
||||
};
|
||||
export default {};
|
|
@ -1,8 +1,10 @@
|
|||
// 登录信息
|
||||
export interface LoginData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// 登录返回
|
||||
export interface LoginRes {
|
||||
token: string;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import appRoutes from '../routes';
|
|||
|
||||
const mixinRoutes = [...appRoutes];
|
||||
|
||||
// 菜单信息根据路由配置推导
|
||||
const appClientMenus = mixinRoutes.map((el) => {
|
||||
const { name, path, meta, redirect, children } = el;
|
||||
return {
|
||||
|
|
|
@ -11,7 +11,10 @@ function setupPageGuard(router: Router) {
|
|||
}
|
||||
|
||||
export default function createRouteGuard(router: Router) {
|
||||
// 设置路由监听守卫
|
||||
setupPageGuard(router);
|
||||
// 设置用户登录校验守卫
|
||||
setupUserLoginInfoGuard(router);
|
||||
// 设置菜单权限守卫
|
||||
setupPermissionGuard(router);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export default function setupUserLoginInfoGuard(router: Router) {
|
|||
await userStore.info();
|
||||
next();
|
||||
} catch (error) {
|
||||
// 获取用户信息错误则先退出,重定向到登录页
|
||||
await userStore.logout();
|
||||
next({
|
||||
name: 'login',
|
||||
|
@ -27,6 +28,7 @@ export default function setupUserLoginInfoGuard(router: Router) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// 未登录的都直接跳转至登录页,访问的页面地址缓存到 query 上
|
||||
if (to.name === 'login') {
|
||||
next();
|
||||
return;
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import { AppRouteRecordRaw } from '../types';
|
||||
|
||||
const MINDER: AppRouteRecordRaw = {
|
||||
path: '/minder',
|
||||
name: 'minder',
|
||||
component: DEFAULT_LAYOUT,
|
||||
redirect: '/minder/index',
|
||||
meta: {
|
||||
locale: 'menu.minder',
|
||||
icon: 'icon-dashboard',
|
||||
order: 0,
|
||||
hideChildrenInMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'minder',
|
||||
component: () => import('@/views/minder/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.minder',
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default MINDER;
|
|
@ -25,13 +25,19 @@ const useAppStore = defineStore('app', {
|
|||
},
|
||||
|
||||
actions: {
|
||||
// Update app settings
|
||||
/**
|
||||
* 更新设置
|
||||
* @param partial 设置
|
||||
*/
|
||||
updateSettings(partial: Partial<AppState>) {
|
||||
// @ts-ignore-next-line
|
||||
this.$patch(partial);
|
||||
},
|
||||
|
||||
// Change theme color
|
||||
/**
|
||||
* 切换暗黑模式
|
||||
* @param dark 是否暗黑模式
|
||||
*/
|
||||
toggleTheme(dark: boolean) {
|
||||
if (dark) {
|
||||
this.theme = 'dark';
|
||||
|
@ -41,12 +47,23 @@ const useAppStore = defineStore('app', {
|
|||
document.body.removeAttribute('MS-theme');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 切换显示模式
|
||||
* @param device 显示模式:mobile | desktop
|
||||
*/
|
||||
toggleDevice(device: string) {
|
||||
this.device = device;
|
||||
},
|
||||
/**
|
||||
* 切换菜单显示
|
||||
* @param value 是否隐藏菜单
|
||||
*/
|
||||
toggleMenu(value: boolean) {
|
||||
this.hideMenu = value;
|
||||
},
|
||||
/**
|
||||
* 获取服务端菜单配置
|
||||
*/
|
||||
async fetchServerMenuConfig() {
|
||||
let notifyInstance: NotificationReturn | null = null;
|
||||
try {
|
||||
|
@ -71,6 +88,9 @@ const useAppStore = defineStore('app', {
|
|||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 清空服务端菜单配置
|
||||
*/
|
||||
clearServerMenu() {
|
||||
this.serverMenu = [];
|
||||
},
|
||||
|
|
|
@ -2,9 +2,9 @@ import type { RouteLocationNormalized } from 'vue-router';
|
|||
import { defineStore } from 'pinia';
|
||||
import { DEFAULT_ROUTE, DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
import { isString } from '@/utils/is';
|
||||
import { TabBarState, TagProps } from './types';
|
||||
import { TabBarState, TabProps } from './types';
|
||||
|
||||
const formatTag = (route: RouteLocationNormalized): TagProps => {
|
||||
const formatTag = (route: RouteLocationNormalized): TabProps => {
|
||||
const { name, meta, fullPath, query } = route;
|
||||
return {
|
||||
title: meta.locale || '',
|
||||
|
@ -24,7 +24,7 @@ const useAppStore = defineStore('tabBar', {
|
|||
}),
|
||||
|
||||
getters: {
|
||||
getTabList(): TagProps[] {
|
||||
getTabList(): TabProps[] {
|
||||
return this.tabList;
|
||||
},
|
||||
getCacheList(): string[] {
|
||||
|
@ -33,6 +33,11 @@ const useAppStore = defineStore('tabBar', {
|
|||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 更新 tabs 页面队列
|
||||
* @param route 当前路由信息
|
||||
* @returns void
|
||||
*/
|
||||
updateTabList(route: RouteLocationNormalized) {
|
||||
if (BAN_LIST.includes(route.name as string)) return;
|
||||
this.tabList.push(formatTag(route));
|
||||
|
@ -40,18 +45,35 @@ const useAppStore = defineStore('tabBar', {
|
|||
this.cacheTabList.add(route.name as string);
|
||||
}
|
||||
},
|
||||
deleteTag(idx: number, tag: TagProps) {
|
||||
/**
|
||||
* 移除 tab
|
||||
* @param idx 需要移除的下标
|
||||
* @param tag 标签名
|
||||
*/
|
||||
deleteTag(idx: number, tag: TabProps) {
|
||||
this.tabList.splice(idx, 1);
|
||||
this.cacheTabList.delete(tag.name);
|
||||
},
|
||||
/**
|
||||
* 添加缓存
|
||||
* @param name 添加缓存的标签名
|
||||
*/
|
||||
addCache(name: string) {
|
||||
if (isString(name) && name !== '') this.cacheTabList.add(name);
|
||||
},
|
||||
deleteCache(tag: TagProps) {
|
||||
/**
|
||||
* 删除缓存
|
||||
* @param tag 删除缓存的标签名
|
||||
*/
|
||||
deleteCache(tag: TabProps) {
|
||||
this.cacheTabList.delete(tag.name);
|
||||
},
|
||||
freshTabList(tags: TagProps[]) {
|
||||
this.tabList = tags;
|
||||
/**
|
||||
* 刷新 tabs 页面队列
|
||||
* @param tabs 重置后的 tabs 页面队列
|
||||
*/
|
||||
freshTabList(tabs: TabProps[]) {
|
||||
this.tabList = tabs;
|
||||
this.cacheTabList.clear();
|
||||
// 要先判断ignoreCache
|
||||
this.tabList
|
||||
|
@ -59,6 +81,9 @@ const useAppStore = defineStore('tabBar', {
|
|||
.map((el) => el.name)
|
||||
.forEach((x) => this.cacheTabList.add(x));
|
||||
},
|
||||
/**
|
||||
* 重置 tabs 页面队列
|
||||
*/
|
||||
resetTabList() {
|
||||
this.tabList = [DEFAULT_ROUTE];
|
||||
this.cacheTabList.clear();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export interface TagProps {
|
||||
export interface TabProps {
|
||||
title: string;
|
||||
name: string;
|
||||
fullPath: string;
|
||||
|
@ -7,6 +7,6 @@ export interface TagProps {
|
|||
}
|
||||
|
||||
export interface TabBarState {
|
||||
tabList: TagProps[];
|
||||
tabList: TabProps[];
|
||||
cacheTabList: Set<string>;
|
||||
}
|
||||
|
|
|
@ -40,24 +40,24 @@ const useUserStore = defineStore('user', {
|
|||
resolve(this.role);
|
||||
});
|
||||
},
|
||||
// Set user's information
|
||||
// 设置用户信息
|
||||
setInfo(partial: Partial<UserState>) {
|
||||
this.$patch(partial);
|
||||
},
|
||||
|
||||
// Reset user's information
|
||||
// 重置用户信息
|
||||
resetInfo() {
|
||||
this.$reset();
|
||||
},
|
||||
|
||||
// Get user's information
|
||||
// 获取用户信息
|
||||
async info() {
|
||||
const res = await getUserInfo();
|
||||
|
||||
this.setInfo(res);
|
||||
},
|
||||
|
||||
// Login
|
||||
// 登录
|
||||
async login(loginForm: LoginData) {
|
||||
try {
|
||||
const res = await userLogin(loginForm);
|
||||
|
@ -67,6 +67,7 @@ const useUserStore = defineStore('user', {
|
|||
throw err;
|
||||
}
|
||||
},
|
||||
// 登出回调
|
||||
logoutCallBack() {
|
||||
const appStore = useAppStore();
|
||||
this.resetInfo();
|
||||
|
@ -74,7 +75,7 @@ const useUserStore = defineStore('user', {
|
|||
removeRouteListener();
|
||||
appStore.clearServerMenu();
|
||||
},
|
||||
// Logout
|
||||
// 登出
|
||||
async logout() {
|
||||
try {
|
||||
await userLogout();
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
/**
|
||||
* 给目标元素添加监听事件
|
||||
* @param target 目标元素 DOM
|
||||
* @param event 事件名称
|
||||
* @param handler 处理函数
|
||||
* @param capture 是否在捕获阶段触发
|
||||
*/
|
||||
export function addEventListen(
|
||||
target: Window | HTMLElement,
|
||||
event: string,
|
||||
|
@ -9,6 +16,13 @@ export function addEventListen(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param target 目标元素 DOM
|
||||
* @param event 事件名称
|
||||
* @param handler 处理函数
|
||||
* @param capture 是否在捕获阶段触发
|
||||
*/
|
||||
export function removeEventListen(
|
||||
target: Window | HTMLElement,
|
||||
event: string,
|
||||
|
|
|
@ -2,6 +2,11 @@ import { isObject } from './is';
|
|||
|
||||
type TargetContext = '_self' | '_parent' | '_blank' | '_top';
|
||||
|
||||
/**
|
||||
* 打开新窗口
|
||||
* @param url 页面地址
|
||||
* @param opts 配置
|
||||
*/
|
||||
export const openWindow = (url: string, opts?: { target?: TargetContext; [key: string]: any }) => {
|
||||
const { target = '_blank', ...others } = opts || {};
|
||||
window.open(
|
||||
|
@ -16,11 +21,20 @@ export const openWindow = (url: string, opts?: { target?: TargetContext; [key: s
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* url 正则校验
|
||||
*/
|
||||
export const regexUrl = new RegExp(
|
||||
'^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$',
|
||||
'i'
|
||||
);
|
||||
|
||||
/**
|
||||
* 遍历对象属性并一一添加到 url 地址参数上
|
||||
* @param baseUrl 需要添加参数的 url
|
||||
* @param obj 参数对象
|
||||
* @returns 拼接后的 url
|
||||
*/
|
||||
export function setObjToUrlParams(baseUrl: string, obj: any): string {
|
||||
let parameters = '';
|
||||
Object.keys(obj).forEach((key) => {
|
||||
|
@ -30,6 +44,12 @@ export function setObjToUrlParams(baseUrl: string, obj: any): string {
|
|||
return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归深度合并
|
||||
* @param src 源对象
|
||||
* @param target 待合并的目标对象
|
||||
* @returns 合并后的对象
|
||||
*/
|
||||
export const deepMerge = <T = any>(src: any = {}, target: any = {}): T => {
|
||||
Object.keys(target).forEach((key) => {
|
||||
src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/**
|
||||
* 合并对象
|
||||
* @param target
|
||||
* @param source
|
||||
* @returns {Object} 返回合并后的对象
|
||||
*/
|
||||
function merge(target: any, source: any): any {
|
||||
target = target || {};
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
const obj = source[key];
|
||||
if (Object.prototype.toString.call(obj) === '[object Object]') {
|
||||
target[key] = merge(target[key], obj);
|
||||
} else {
|
||||
target[key] = obj;
|
||||
}
|
||||
}
|
||||
});
|
||||
return target;
|
||||
}
|
||||
export default merge;
|
|
@ -1,6 +1,12 @@
|
|||
import { App, ComponentPublicInstance } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* TODO:错误捕获
|
||||
* @param Vue
|
||||
* @param baseUrl
|
||||
* @returns
|
||||
*/
|
||||
export default function handleError(Vue: App, baseUrl: string) {
|
||||
if (!baseUrl) {
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/**
|
||||
* Listening to routes alone would waste rendering performance. Use the publish-subscribe model for distribution management
|
||||
* 单独监听路由会浪费渲染性能。使用发布订阅模式去进行分发管理。
|
||||
*/
|
||||
import mitt, { Handler } from 'mitt';
|
||||
|
@ -11,11 +10,20 @@ const key = Symbol('ROUTE_CHANGE');
|
|||
|
||||
let latestRoute: RouteLocationNormalized;
|
||||
|
||||
/**
|
||||
* 设置路由监听
|
||||
* @param to 要跳转的路由信息
|
||||
*/
|
||||
export function setRouteEmitter(to: RouteLocationNormalized) {
|
||||
emitter.emit(key, to);
|
||||
latestRoute = to;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听路由变化
|
||||
* @param handler 处理回调
|
||||
* @param immediate 是否立即执行
|
||||
*/
|
||||
export function listenerRouteChange(handler: (route: RouteLocationNormalized) => void, immediate = true) {
|
||||
emitter.on(key, handler as Handler);
|
||||
if (immediate && latestRoute) {
|
||||
|
@ -23,6 +31,9 @@ export function listenerRouteChange(handler: (route: RouteLocationNormalized) =>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除路由监听
|
||||
*/
|
||||
export function removeRouteListener() {
|
||||
emitter.off(key);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,11 @@ export default ({ mock, setup }: { mock?: boolean; setup: () => void }) => {
|
|||
if (mock !== false && debug) setup();
|
||||
};
|
||||
|
||||
/**
|
||||
* mock- 成功返回结果结构体
|
||||
* @param data mock 返回结果
|
||||
* @returns
|
||||
*/
|
||||
export const successResponseWrap = (data: unknown) => {
|
||||
return {
|
||||
data,
|
||||
|
@ -13,6 +18,13 @@ export const successResponseWrap = (data: unknown) => {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* mock- 失败返回结果结构体
|
||||
* @param data mock 返回结果
|
||||
* @param message 错误信息
|
||||
* @param code
|
||||
* @returns
|
||||
*/
|
||||
export const failResponseWrap = (data: unknown, message: string, code = 50000) => {
|
||||
return {
|
||||
data,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"build/**/*.ts",
|
||||
"build/**/*.d.ts",
|
||||
"mock/**/*.ts",
|
||||
"__test__/**/*.ts",
|
||||
], // TS解析路径配置
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
|
Loading…
Reference in New Issue