feat: implement gas and delivery admin systems
This commit is contained in:
16
frontend/delivery_admin/src/router/app-menus/index.ts
Normal file
16
frontend/delivery_admin/src/router/app-menus/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { appExternalRoutes, appRoutes } from '../routes';
|
||||
|
||||
const mixinRoutes = [...appRoutes, ...appExternalRoutes];
|
||||
|
||||
const appClientMenus = mixinRoutes.map((el) => {
|
||||
const { name, path, meta, redirect, children } = el;
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
meta,
|
||||
redirect,
|
||||
children,
|
||||
};
|
||||
});
|
||||
|
||||
export default appClientMenus;
|
||||
18
frontend/delivery_admin/src/router/constants.ts
Normal file
18
frontend/delivery_admin/src/router/constants.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const WHITE_LIST = [
|
||||
{ name: 'notFound', children: [] },
|
||||
{ name: 'login', children: [] },
|
||||
];
|
||||
|
||||
export const NOT_FOUND = {
|
||||
name: 'notFound',
|
||||
};
|
||||
|
||||
export const REDIRECT_ROUTE_NAME = 'Redirect';
|
||||
|
||||
export const DEFAULT_ROUTE_NAME = 'Workplace';
|
||||
|
||||
export const DEFAULT_ROUTE = {
|
||||
title: 'menu.dashboard.workplace',
|
||||
name: DEFAULT_ROUTE_NAME,
|
||||
fullPath: '/dashboard/workplace',
|
||||
};
|
||||
17
frontend/delivery_admin/src/router/guard/index.ts
Normal file
17
frontend/delivery_admin/src/router/guard/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Router } from 'vue-router';
|
||||
import { setRouteEmitter } from '@/utils/route-listener';
|
||||
import setupPermissionGuard from './permission';
|
||||
import setupUserLoginInfoGuard from './userLoginInfo';
|
||||
|
||||
function setupPageGuard(router: Router) {
|
||||
router.beforeEach(async (to) => {
|
||||
// emit route change
|
||||
setRouteEmitter(to);
|
||||
});
|
||||
}
|
||||
|
||||
export default function createRouteGuard(router: Router) {
|
||||
setupPageGuard(router);
|
||||
setupUserLoginInfoGuard(router);
|
||||
setupPermissionGuard(router);
|
||||
}
|
||||
53
frontend/delivery_admin/src/router/guard/permission.ts
Normal file
53
frontend/delivery_admin/src/router/guard/permission.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import type { RouteRecordNormalized, Router } from 'vue-router';
|
||||
|
||||
import usePermission from '@/hooks/permission';
|
||||
import { useAppStore, useUserStore } from '@/store';
|
||||
import { NOT_FOUND, WHITE_LIST } from '../constants';
|
||||
import { appRoutes } from '../routes';
|
||||
|
||||
export default function setupPermissionGuard(router: Router) {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const Permission = usePermission();
|
||||
const permissionsAllow = Permission.accessRouter(to);
|
||||
if (appStore.menuFromServer) {
|
||||
// 针对来自服务端的菜单配置进行处理
|
||||
// Handle routing configuration from the server
|
||||
|
||||
// 根据需要自行完善来源于服务端的菜单配置的permission逻辑
|
||||
// Refine the permission logic from the server's menu configuration as needed
|
||||
if (
|
||||
!appStore.appAsyncMenus.length &&
|
||||
!WHITE_LIST.find((el) => el.name === to.name)
|
||||
) {
|
||||
await appStore.fetchServerMenuConfig();
|
||||
}
|
||||
const serverMenuConfig = [...appStore.appAsyncMenus, ...WHITE_LIST];
|
||||
|
||||
let exist = false;
|
||||
while (serverMenuConfig.length && !exist) {
|
||||
const element = serverMenuConfig.shift();
|
||||
if (element?.name === to.name) exist = true;
|
||||
|
||||
if (element?.children) {
|
||||
serverMenuConfig.push(
|
||||
...(element.children as unknown as RouteRecordNormalized[]),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (exist && permissionsAllow) {
|
||||
next();
|
||||
} else next(NOT_FOUND);
|
||||
} else if (permissionsAllow) {
|
||||
next();
|
||||
} else {
|
||||
const destination =
|
||||
Permission.findFirstPermissionRoute(appRoutes, userStore.role) ||
|
||||
NOT_FOUND;
|
||||
next(destination);
|
||||
}
|
||||
NProgress.done();
|
||||
});
|
||||
}
|
||||
44
frontend/delivery_admin/src/router/guard/userLoginInfo.ts
Normal file
44
frontend/delivery_admin/src/router/guard/userLoginInfo.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import type { LocationQueryRaw, Router } from 'vue-router';
|
||||
|
||||
import { useUserStore } from '@/store';
|
||||
import { isLogin } from '@/utils/auth';
|
||||
|
||||
export default function setupUserLoginInfoGuard(router: Router) {
|
||||
router.beforeEach(async (to, _from, next) => {
|
||||
NProgress.start();
|
||||
const userStore = useUserStore();
|
||||
// 登录页不需要校验旧令牌,避免后端未启动或令牌过期时重复请求用户资料。
|
||||
if (to.name === 'login') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if (isLogin()) {
|
||||
if (userStore.role) {
|
||||
next();
|
||||
} else {
|
||||
try {
|
||||
await userStore.info();
|
||||
next();
|
||||
} catch {
|
||||
await userStore.logout();
|
||||
next({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: to.name,
|
||||
...to.query,
|
||||
} as LocationQueryRaw,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: to.name,
|
||||
...to.query,
|
||||
} as LocationQueryRaw,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
37
frontend/delivery_admin/src/router/index.ts
Normal file
37
frontend/delivery_admin/src/router/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
import createRouteGuard from './guard';
|
||||
import { appRoutes } from './routes';
|
||||
import { NOT_FOUND_ROUTE, REDIRECT_MAIN } from './routes/base';
|
||||
|
||||
NProgress.configure({ showSpinner: false }); // NProgress Configuration
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: 'login',
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
...appRoutes,
|
||||
REDIRECT_MAIN,
|
||||
NOT_FOUND_ROUTE,
|
||||
],
|
||||
scrollBehavior() {
|
||||
return { top: 0 };
|
||||
},
|
||||
});
|
||||
|
||||
createRouteGuard(router);
|
||||
|
||||
export default router;
|
||||
31
frontend/delivery_admin/src/router/routes/base.ts
Normal file
31
frontend/delivery_admin/src/router/routes/base.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
|
||||
export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue');
|
||||
|
||||
export const REDIRECT_MAIN: RouteRecordRaw = {
|
||||
path: '/redirect',
|
||||
name: 'redirectWrapper',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
hideInMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path',
|
||||
name: REDIRECT_ROUTE_NAME,
|
||||
component: () => import('@/views/redirect/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const NOT_FOUND_ROUTE: RouteRecordRaw = {
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'notFound',
|
||||
component: () => import('@/views/not-found/index.vue'),
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
export default {
|
||||
path: 'https://arco.design',
|
||||
name: 'arcoWebsite',
|
||||
meta: {
|
||||
locale: 'menu.arcoWebsite',
|
||||
icon: 'icon-link',
|
||||
requiresAuth: true,
|
||||
order: 8,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
export default {
|
||||
path: 'https://arco.design/vue/docs/pro/faq',
|
||||
name: 'faq',
|
||||
meta: {
|
||||
locale: 'menu.faq',
|
||||
icon: 'icon-question-circle',
|
||||
requiresAuth: true,
|
||||
order: 9,
|
||||
},
|
||||
};
|
||||
8
frontend/delivery_admin/src/router/routes/index.ts
Normal file
8
frontend/delivery_admin/src/router/routes/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
import platformRoutes from './modules/platform';
|
||||
|
||||
/** 配送点后台仅注册已实现的业务路由,菜单能力由后端返回。 */
|
||||
export const appRoutes: RouteRecordNormalized[] = platformRoutes as unknown as RouteRecordNormalized[];
|
||||
|
||||
/** 本期没有外部菜单。 */
|
||||
export const appExternalRoutes: RouteRecordNormalized[] = [];
|
||||
@@ -0,0 +1,55 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
|
||||
const resourcePage = () => import('@/views/shared/ResourcePage.vue');
|
||||
const child = (domain: string, path: string, title: string, resource: string, menuCode: string, meta: Record<string, unknown> = {}): AppRouteRecordRaw => ({
|
||||
path, name: `${domain}-${path}`, component: resourcePage,
|
||||
meta: { title, resource, requiresAuth: true, menuCode, ...meta },
|
||||
});
|
||||
const group = (path: string, name: string, title: string, icon: string, order: number, children: AppRouteRecordRaw[]): AppRouteRecordRaw => ({
|
||||
path: `/${path}`, name, component: DEFAULT_LAYOUT, redirect: `/${path}/${children[0].path}`,
|
||||
meta: { title, requiresAuth: true, icon, order, menuCode: name }, children,
|
||||
});
|
||||
|
||||
const routes: AppRouteRecordRaw[] = [
|
||||
{ path: '/dashboard', name: 'dashboard', component: DEFAULT_LAYOUT, redirect: '/dashboard/overview', meta: { title: '数据概述', requiresAuth: true, icon: 'icon-dashboard', order: 10, menuCode: 'dashboard' }, children: [
|
||||
{ path: 'overview', name: 'dashboard-overview', component: () => import('@/views/dashboard/DashboardPage.vue'), meta: { title: '运营概览', requiresAuth: true, menuCode: 'dashboard_overview' } },
|
||||
{ path: 'reports', name: 'dashboard-reports', component: () => import('@/views/dashboard/ReportPage.vue'), meta: { title: '订单报表', requiresAuth: true, menuCode: 'dashboard_reports' } },
|
||||
] },
|
||||
group('profile', 'profile', '配送点资料', 'icon-storage', 20, [
|
||||
child('profile', 'basic', '本点资料', '/delivery_profile', 'delivery_profile'),
|
||||
]),
|
||||
group('staff', 'staff', '配送人员管理', 'icon-user-group', 30, [
|
||||
child('staff', 'add', '新增配送人员', '/staff_account', 'staff_add', { createMode: true }),
|
||||
child('staff', 'delivery', '配送人员列表', '/staff_account', 'staff_delivery'),
|
||||
child('staff', 'credentials', '人员资质', '/staff_credential', 'staff', { hideInMenu: true, activeMenu: 'staff-delivery' }),
|
||||
]),
|
||||
group('user', 'user', '用户管理', 'icon-user', 40, [
|
||||
child('user', 'accounts', '用户账户', '/user_account', 'user_account'),
|
||||
child('user', 'addresses', '用户地址', '/user_address', 'user_account', { hideInMenu: true, activeMenu: 'user-accounts' }),
|
||||
]),
|
||||
group('contract', 'contract', '合同管理', 'icon-file', 50, [
|
||||
child('contract', 'contracts', '配送合同', '/gasorder_contract', 'gasorder_contract'),
|
||||
child('contract', 'products', '合同气瓶', '/gasorder_contract_product', 'gasorder_contract', { hideInMenu: true, activeMenu: 'contract-contracts' }),
|
||||
child('contract', 'revisions', '合同修订记录', '/gasorder_contract_revision', 'gasorder_contract', { hideInMenu: true, activeMenu: 'contract-contracts' }),
|
||||
child('contract', 'candidates', '合同可选气瓶', '/product_info', 'gasorder_contract', { hideInMenu: true, activeMenu: 'contract-contracts' }),
|
||||
]),
|
||||
group('gasorder', 'gasorder', '配送订单', 'icon-list', 60, [
|
||||
child('gasorder', 'create', '创建订单', '/gasorder_basic', 'gasorder_create', { createMode: true }),
|
||||
child('gasorder', 'orders', '订单列表', '/gasorder_basic', 'gasorder_basic'),
|
||||
]),
|
||||
group('finance', 'finance', '财务管理', 'icon-bar-chart', 70, [
|
||||
child('finance', 'wallet', '钱包', '/wallet_basic', 'wallet_basic'),
|
||||
child('finance', 'banks', '银行卡', '/wallet_bank', 'wallet_bank'),
|
||||
child('finance', 'payments', '支付记录', '/wallet_payment', 'wallet_payment'),
|
||||
child('finance', 'records', '钱包流水', '/wallet_record', 'wallet_record'),
|
||||
child('finance', 'refunds', '退款记录', '/wallet_refund', 'wallet_refund'),
|
||||
child('finance', 'recharge', '钱包充值', '/wallet_recharge', 'wallet_recharge'),
|
||||
child('finance', 'withdrawals', '提现申请', '/wallet_apply_cash', 'wallet_apply_cash'),
|
||||
child('finance', 'settlements', '结算结果', '/fin_settlement', 'fin_settlement'),
|
||||
]),
|
||||
{ path: '/invitation', name: 'invitation', component: DEFAULT_LAYOUT, redirect: '/invitation/qrcode', meta: { title: '邀请注册', requiresAuth: true, icon: 'icon-qrcode', order: 80, menuCode: 'invitation' }, children: [
|
||||
{ path: 'qrcode', name: 'invitation-qrcode', component: () => import('@/views/invitation/QrcodePage.vue'), meta: { title: '邀请二维码', requiresAuth: true, menuCode: 'invitation_qrcode' } },
|
||||
] },
|
||||
];
|
||||
export default routes;
|
||||
20
frontend/delivery_admin/src/router/routes/types.ts
Normal file
20
frontend/delivery_admin/src/router/routes/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { defineComponent } from 'vue';
|
||||
import type { NavigationGuard, RouteMeta } from 'vue-router';
|
||||
|
||||
export type Component<T = any> =
|
||||
| ReturnType<typeof defineComponent>
|
||||
| (() => Promise<typeof import('*.vue')>)
|
||||
| (() => Promise<T>);
|
||||
|
||||
export interface AppRouteRecordRaw {
|
||||
path: string;
|
||||
name?: string | symbol;
|
||||
meta?: RouteMeta;
|
||||
redirect?: string;
|
||||
component: Component | string;
|
||||
children?: AppRouteRecordRaw[];
|
||||
alias?: string | string[];
|
||||
props?: Record<string, any>;
|
||||
beforeEnter?: NavigationGuard | NavigationGuard[];
|
||||
fullPath?: string;
|
||||
}
|
||||
19
frontend/delivery_admin/src/router/typings.d.ts
vendored
Normal file
19
frontend/delivery_admin/src/router/typings.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'vue-router';
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
roles?: string[]; // Controls roles that have access to the page
|
||||
menuCode?: string; // Server-assigned menu domain required by this route
|
||||
staffType?: 'installer' | 'delivery' | 'operations';
|
||||
createMode?: boolean;
|
||||
requiresAuth: boolean; // Whether login is required to access the current page (every route must declare)
|
||||
icon?: string; // The icon show in the side menu
|
||||
locale?: string; // The locale name show in side menu and breadcrumb
|
||||
hideInMenu?: boolean; // If true, it is not displayed in the side menu
|
||||
hideChildrenInMenu?: boolean; // if set true, the children are not displayed in the side menu
|
||||
activeMenu?: string; // if set name, the menu will be highlighted according to the name you set
|
||||
order?: number; // Sort routing menu items. If set key, the higher the value, the more forward it is
|
||||
noAffix?: boolean; // if set true, the tag will not affix in the tab-bar
|
||||
ignoreCache?: boolean; // if set true, the page will not be cached
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user