refactor(projects): merge branch unocss to main

This commit is contained in:
Soybean
2022-04-28 00:50:22 +08:00
72 changed files with 3921 additions and 2117 deletions

View File

@ -13,7 +13,6 @@
</template>
<script setup lang="ts">
import { RouterView } from 'vue-router';
import { zhCN, dateZhCN } from 'naive-ui';
import { useThemeStore, subscribeStore } from '@/store';

View File

@ -1,6 +1,6 @@
<template>
<div
class="dark:(bg-[#18181c] text-white text-opacity-82) transition-all duration-300 ease-in-out"
class="dark:bg-[#18181c] dark:text-white dark:text-opacity-82 transition-all duration-300 ease-in-out"
:class="inverted ? 'bg-[#001428] text-white' : 'bg-white text-[#333639]'"
>
<slot></slot>

View File

@ -1,4 +1,6 @@
import UAParser from 'ua-parser-js';
import { useAuthStore } from '@/store';
import { isArray, isString } from '@/utils';
interface AppInfo {
/** 项目名称 */
@ -26,3 +28,27 @@ export function useDeviceInfo() {
const result = parser.getResult();
return result;
}
/** 权限判断 */
export function usePermission() {
const auth = useAuthStore();
function hasPermission(permission: Auth.RoleType | Auth.RoleType[]) {
const { userRole } = auth.userInfo;
let has = userRole === 'super';
if (!has) {
if (isArray(permission)) {
has = (permission as Auth.RoleType[]).includes(userRole);
}
if (isString(permission)) {
has = (permission as Auth.RoleType) === userRole;
}
}
return has;
}
return {
hasPermission
};
}

View File

@ -1,6 +1,5 @@
/** 百度地图sdk地址 */
export const BAIDU_MAP_SDK_URL =
'https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1';
export const BAIDU_MAP_SDK_URL = `https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1`;
/** 高德地图sdk地址 */
export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';

View File

@ -1,9 +1,11 @@
import type { App } from 'vue';
import setupNetworkDirective from './network';
import setupLoginDirective from './login';
import setupPermissionDirective from './permission';
/** setup custom vue directives. - [安装自定义的vue指令] */
export function setupDirectives(app: App) {
setupNetworkDirective(app);
setupLoginDirective(app);
setupPermissionDirective(app);
}

View File

@ -1,16 +1,26 @@
import type { App, Directive } from 'vue';
import { useAuthStore } from '@/store';
import { usePermission } from '@/composables';
export default function setupLoginDirective(app: App) {
const auth = useAuthStore();
export default function setupPermissionDirective(app: App) {
const { hasPermission } = usePermission();
const loginDirective: Directive<HTMLElement, Auth.RoleType | undefined> = {
mounted(el: HTMLElement, binding) {
if (binding.value !== auth.userInfo.userRole) {
el.remove();
}
function updateElVisible(el: HTMLElement, permission: Auth.RoleType | Auth.RoleType[]) {
if (!permission) {
throw new Error(`need roles: like v-permission="'admin'", v-permission="['admin', 'test]"`);
}
if (!hasPermission(permission)) {
el.parentElement?.removeChild(el);
}
}
const permissionDirective: Directive<HTMLElement, Auth.RoleType | Auth.RoleType[]> = {
mounted(el, binding) {
updateElVisible(el, binding.value);
},
beforeUpdate(el, binding) {
updateElVisible(el, binding.value);
}
};
app.directive('login', loginDirective);
app.directive('permission', permissionDirective);
}

View File

@ -1,3 +1,11 @@
/** 用户角色 */
export enum EnumUserRole {
super = '超级管理员',
admin = '管理员',
user = '普通用户'
// custom = '自定义角色'
}
/** 登录模块 */
export enum EnumLoginModule {
'pwd-login' = '账密登录',

View File

@ -1,5 +1,5 @@
<template>
<soybean-admin-layout
<admin-layout
:mode="mode"
:min-width="theme.layout.minWidth"
:fixed-header-and-tab="theme.fixedHeaderAndTab"
@ -25,12 +25,12 @@
<template #footer>
<global-footer />
</template>
</soybean-admin-layout>
</admin-layout>
<setting-drawer />
</template>
<script setup lang="ts">
import SoybeanAdminLayout from 'soybean-admin-layout';
import AdminLayout from '@soybeanjs/vue-admin-layout';
import { useAppStore, useThemeStore } from '@/store';
import { useBasicLayout } from '@/composables';
import { SettingDrawer, GlobalHeader, GlobalTab, GlobalSider, GlobalContent, GlobalFooter } from '../common';

View File

@ -1,10 +1,17 @@
<template>
<hover-container tooltip-content="github" class="w-40px h-full" content-class="hover:text-primary">
<a href="https://github.com/honghuangdc/soybean-admin" target="_blank" class="flex-center">
<icon-mdi-github class="text-20px" />
</a>
<hover-container
tooltip-content="github"
class="w-40px h-full"
content-class="hover:text-primary"
@click="handleClickLink"
>
<icon-mdi-github class="text-20px" />
</hover-container>
</template>
<script lang="ts" setup></script>
<script lang="ts" setup>
function handleClickLink() {
window.open('https://github.com/honghuangdc/soybean-admin', '_blank');
}
</script>
<style scoped></style>

View File

@ -1,5 +1,5 @@
<template>
<hover-container class="w-40px h-full" @click="app.toggleSiderCollapse">
<hover-container class="w-40px h-full" content-class="hover:text-primary" @click="app.toggleSiderCollapse">
<icon-line-md-menu-unfold-left v-if="app.siderCollapse" class="text-16px" />
<icon-line-md-menu-fold-left v-else class="text-16px" />
</hover-container>

View File

@ -2,12 +2,12 @@
<div class="mb-6px px-4px cursor-pointer" @mouseenter="setTrue" @mouseleave="setFalse">
<div
class="flex-center flex-col py-12px rounded-2px bg-transparent transition-colors duration-300 ease-in-out"
:class="{ 'text-primary !bg-primary-active': isActive, 'text-primary': isHover }"
:class="{ 'text-primary !bg-primary_active': isActive, 'text-primary': isHover }"
>
<component :is="icon" :class="[isMini ? 'text-16px' : 'text-20px']" />
<p
class="pt-8px text-12px overflow-hidden transition-height duration-300 ease-in-out"
:class="[isMini ? 'h-0 pt-0' : 'h-20px pt-8px']"
class="text-12px overflow-hidden transition-height duration-300 ease-in-out"
:class="[isMini ? 'h-0 pt-0' : 'h-24px pt-4px']"
>
{{ label }}
</p>

View File

@ -28,7 +28,7 @@
<script setup lang="ts">
import { ref, reactive, computed, nextTick, watch } from 'vue';
import { useEventListener } from '@vueuse/core';
import { ChromeTab, ButtonTab } from 'soybean-admin-tab';
import { ChromeTab, ButtonTab } from '@soybeanjs/vue-admin-tab';
import { Icon } from '@iconify/vue';
import { useThemeStore, useTabStore } from '@/store';
import { setTabRoutes } from '@/utils';

View File

@ -1,21 +1,26 @@
import { createApp } from 'vue';
import { setupImportAssets } from './plugins';
import { setupAssets } from './plugins';
import { setupStore } from './store';
import { setupDirectives } from './directives';
import { setupRouter } from './router';
import App from './App.vue';
async function setupApp() {
setupImportAssets();
// import assets: js、css
setupAssets();
const app = createApp(App);
// store plugin: pinia
setupStore(app);
// vue custom directives
setupDirectives(app);
// vue router
await setupRouter(app);
// mount app
app.mount('#app');
}

View File

@ -1,10 +1,10 @@
import 'virtual:windi.css';
import 'uno.css';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import '../styles/css/global.css';
/** import static assets: css, js , font and so on. - [引入静态资源css、js和字体文件等] */
export default function setupImportAssets() {
export default function setupAssets() {
//
}

View File

@ -1,3 +1,3 @@
import setupImportAssets from './assets';
import setupAssets from './assets';
export { setupImportAssets };
export { setupAssets };

View File

@ -16,7 +16,7 @@ export async function createDynamicRouteGuard(
const isLogin = Boolean(getToken());
// 初始化权限路由
if (!route.isInitedAuthRoute) {
if (!route.isInitAuthRoute) {
// 未登录情况下直接回到登录页,登录成功后再加载权限路由
if (!isLogin) {
if (to.name === routeName('login')) {

View File

@ -1,6 +1,6 @@
import type { App } from 'vue';
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
import { transformAuthRoutesToVueRoutes } from '@/utils';
import { transformAuthRoutesToVueRoutes, transformRouteNameToRoutePath } from '@/utils';
import { constantRoutes } from './routes';
import { scrollBehavior } from './helpers';
import { createRouterGuard } from './guard';
@ -20,5 +20,10 @@ export async function setupRouter(app: App) {
await router.isReady();
}
/** 路由名称 */
export const routeName = (key: AuthRoute.RouteKey) => key;
/** 路由路径 */
export const routePath = (key: Exclude<AuthRoute.RouteKey, 'not-found-page'>) => transformRouteNameToRoutePath(key);
export * from './routes';
export * from './modules';

View File

@ -6,9 +6,9 @@ const about: AuthRoute.Route = {
title: '关于',
requiresAuth: true,
singleLayout: 'basic',
permissions: ['super', 'admin', 'test'],
permissions: ['super', 'admin', 'user'],
icon: 'fluent:book-information-24-regular',
order: 7
order: 8
}
};

View File

@ -0,0 +1,35 @@
const authDemo: AuthRoute.Route = {
name: 'auth-demo',
path: '/auth-demo',
component: 'basic',
children: [
{
name: 'auth-demo_permission',
path: '/auth-demo/permission',
component: 'self',
meta: {
title: '权限切换',
requiresAuth: true,
icon: 'ic:round-construction'
}
},
{
name: 'auth-demo_super',
path: '/auth-demo/super',
component: 'self',
meta: {
title: '超级管理员可见',
requiresAuth: true,
permissions: ['super'],
icon: 'ic:round-supervisor-account'
}
}
],
meta: {
title: '权限示例',
icon: 'ic:baseline-security',
order: 5
}
};
export default authDemo;

View File

@ -37,7 +37,7 @@ const exception: AuthRoute.Route = {
meta: {
title: '异常页',
icon: 'ant-design:exception-outlined',
order: 5
order: 6
}
};

View File

@ -5,7 +5,7 @@ export const constantRoutes: AuthRoute.Route[] = [
{
name: 'root',
path: '/',
redirect: '/dashboard/analysis',
redirect: import.meta.env.VITE_ROUTE_HOME_PATH,
meta: {
title: 'Root'
}
@ -64,16 +64,3 @@ export const constantRoutes: AuthRoute.Route[] = [
}
}
];
/** 路由名称 */
export const routeName = (key: AuthRoute.RouteKey) => key;
/** 路由路径 */
export function routePath(key: Exclude<AuthRoute.RouteKey, 'not-found-page'>): AuthRoute.RoutePath {
const rootPath: AuthRoute.RoutePath = '/';
if (key === 'root') return rootPath;
const splitMark: AuthRoute.RouteSplitMark = '_';
const pathSplitMark = '/';
const path = key.split(splitMark).join(pathSplitMark);
return (pathSplitMark + path) as AuthRoute.RoutePath;
}

View File

@ -1,9 +1,9 @@
import { createRequest } from './request';
import { getEnvConfig } from '~/.env-config';
const { http } = getEnvConfig(import.meta.env);
const envConfig = getEnvConfig(import.meta.env);
const isHttpProxy = import.meta.env.VITE_HTTP_PROXY === 'true';
export const request = createRequest({ baseURL: isHttpProxy ? http.proxy : http.url });
export const request = createRequest({ baseURL: isHttpProxy ? envConfig.proxy : envConfig.url });
export const mockRequest = createRequest({ baseURL: '/mock' });

View File

@ -87,6 +87,9 @@ export const useAuthStore = defineStore('auth-store', {
await this.loginByToken(data);
}
this.loginLoading = false;
},
updateUserRole(userRole: Auth.RoleType) {
this.userInfo.userRole = userRole;
}
}
});

View File

@ -7,8 +7,11 @@ import {
transformAuthRouteToMenu,
transformAuthRoutesToVueRoutes,
transformAuthRoutesToSearchMenus,
getCacheRoutes
getCacheRoutes,
filterAuthRoutesByUserPermission,
transformRoutePathToRouteName
} from '@/utils';
import { useAuthStore } from '../auth';
import { useTabStore } from '../tab';
interface RouteState {
@ -19,7 +22,7 @@ interface RouteState {
*/
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE'];
/** 是否初始化了权限路由 */
isInitedAuthRoute: boolean;
isInitAuthRoute: boolean;
/** 路由首页name(前端静态路由时生效,后端动态路由该值会被后端返回的值覆盖) */
routeHomeName: AuthRoute.RouteKey;
/** 菜单 */
@ -33,8 +36,8 @@ interface RouteState {
export const useRouteStore = defineStore('route-store', {
state: (): RouteState => ({
authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,
isInitedAuthRoute: false,
routeHomeName: 'dashboard_analysis',
isInitAuthRoute: false,
routeHomeName: transformRoutePathToRouteName(import.meta.env.VITE_ROUTE_HOME_PATH),
menus: [],
searchMenus: [],
cacheRoutes: []
@ -73,9 +76,9 @@ export const useRouteStore = defineStore('route-store', {
* @param router - 路由实例
*/
async initStaticRoute(router: Router) {
// 先根据用户权限过滤一下staticRoutes
this.handleAuthRoutes(staticRoutes, router);
const auth = useAuthStore();
const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userRole);
this.handleAuthRoutes(routes, router);
},
/**
* 初始化权限路由
@ -84,6 +87,7 @@ export const useRouteStore = defineStore('route-store', {
async initAuthRoute(router: Router) {
const { initHomeTab } = useTabStore();
const { userId } = getUserInfo();
if (!userId) return;
const isDynamicRoute = this.authRouteMode === 'dynamic';
@ -94,7 +98,8 @@ export const useRouteStore = defineStore('route-store', {
}
initHomeTab(this.routeHomeName, router);
this.isInitedAuthRoute = true;
this.isInitAuthRoute = true;
}
}
});

View File

@ -21,7 +21,7 @@ export const useTabStore = defineStore('tab-store', {
name: 'root',
path: '/',
meta: {
title: 'root'
title: 'Root'
},
scrollPosition: {
left: 0,
@ -53,7 +53,8 @@ export const useTabStore = defineStore('tab-store', {
initHomeTab(routeHomeName: string, router: Router) {
const routes = router.getRoutes();
const findHome = routes.find(item => item.name === routeHomeName);
if (findHome) {
if (findHome && !findHome.children.length) {
// 有子路由的不能作为Tab
this.homeTab = getTabRouteByVueRoute(findHome);
}
},
@ -165,16 +166,20 @@ export const useTabStore = defineStore('tab-store', {
iniTabStore(currentRoute: RouteLocationNormalizedLoaded) {
const theme = useThemeStore();
const isHome = currentRoute.path === this.homeTab.path;
const tabs: GlobalTabRoute[] = theme.tab.isCache ? getTabRoutes() : [];
const hasHome = isInTabRoutes(tabs, this.homeTab.path);
const hasCurrent = isInTabRoutes(tabs, currentRoute.path);
if (!hasHome) {
if (!hasHome && this.homeTab.name !== 'root') {
tabs.unshift(this.homeTab);
}
const isHome = currentRoute.path === this.homeTab.path;
const hasCurrent = isInTabRoutes(tabs, currentRoute.path);
if (!isHome && !hasCurrent) {
tabs.push(getTabRouteByVueRoute(currentRoute));
const currentTab = getTabRouteByVueRoute(currentRoute);
tabs.push(currentTab);
}
this.tabs = tabs;
this.setActiveTab(currentRoute.path);
}

View File

@ -1,4 +1,5 @@
@import './transition.css';
@import './reset.css';
html, body, #app {
height: 100%;

366
src/styles/css/reset.css Normal file
View File

@ -0,0 +1,366 @@
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: currentColor; /* 2 */
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
*/
html {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */
font-size: 1em; /* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 1 */
/* background-color: transparent; 2 */
background-image: none; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/*
Ensure the default browser behavior of the `hidden` attribute.
*/
[hidden] {
display: none;
}
.dark {
color-scheme: dark;
}

View File

@ -4,10 +4,10 @@ declare namespace Auth {
* 用户角色类型(前端静态路由用角色类型进行路由权限的控制)
* - super: 超级管理员(该权限具有所有路由数据)
* - admin: 管理员
* - test: 测试
* - normal: 普通用户
* - user: 用户
* - custom: 自定义角色
*/
type RoleType = 'super' | 'admin' | 'test' | 'normal';
type RoleType = keyof typeof import('@/enum').EnumUserRole;
/** 用户信息 */
interface UserInfo {

103
src/typings/components.d.ts vendored Normal file
View File

@ -0,0 +1,103 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
BetterScroll: typeof import('./../components/custom/BetterScroll.vue')['default']
CountTo: typeof import('./../components/custom/CountTo.vue')['default']
DarkModeContainer: typeof import('./../components/common/DarkModeContainer.vue')['default']
DarkModeSwitch: typeof import('./../components/common/DarkModeSwitch.vue')['default']
GithubLink: typeof import('./../components/custom/GithubLink.vue')['default']
HoverContainer: typeof import('./../components/common/HoverContainer.vue')['default']
IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconCustomActivity: typeof import('~icons/custom/activity')['default']
IconCustomAvatar: typeof import('~icons/custom/avatar')['default']
IconCustomBanner: typeof import('~icons/custom/banner')['default']
IconCustomCast: typeof import('~icons/custom/cast')['default']
IconCustomEmptyData: typeof import('~icons/custom/empty-data')['default']
IconCustomLogo: typeof import('~icons/custom/logo')['default']
IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default']
IconCustomNetworkError: typeof import('~icons/custom/network-error')['default']
IconCustomNoPermission: typeof import('~icons/custom/no-permission')['default']
IconCustomNotFound: typeof import('~icons/custom/not-found')['default']
IconCustomServiceError: typeof import('~icons/custom/service-error')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconIcOutlineCheck: typeof import('~icons/ic/outline-check')['default']
IconLineMdMenuFoldLeft: typeof import('~icons/line-md/menu-fold-left')['default']
IconLineMdMenuUnfoldLeft: typeof import('~icons/line-md/menu-unfold-left')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiGithub: typeof import('~icons/mdi/github')['default']
IconMdiMoonWaningCrescent: typeof import('~icons/mdi/moon-waning-crescent')['default']
IconMdiPin: typeof import('~icons/mdi/pin')['default']
IconMdiPinOff: typeof import('~icons/mdi/pin-off')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiWechat: typeof import('~icons/mdi/wechat')['default']
IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default']
IconPhCaretDoubleLeftBold: typeof import('~icons/ph/caret-double-left-bold')['default']
IconPhCaretDoubleRightBold: typeof import('~icons/ph/caret-double-right-bold')['default']
IconSelect: typeof import('./../components/custom/IconSelect.vue')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
ImageVerify: typeof import('./../components/custom/ImageVerify.vue')['default']
LoadingEmptyWrapper: typeof import('./../components/business/LoadingEmptyWrapper.vue')['default']
LoginAgreement: typeof import('./../components/business/LoginAgreement.vue')['default']
NaiveProvider: typeof import('./../components/common/NaiveProvider.vue')['default']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGradientText: typeof import('naive-ui')['NGradientText']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NList: typeof import('naive-ui')['NList']
NListItem: typeof import('naive-ui')['NListItem']
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopover: typeof import('naive-ui')['NPopover']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
NTimeline: typeof import('naive-ui')['NTimeline']
NTimelineItem: typeof import('naive-ui')['NTimelineItem']
NTooltip: typeof import('naive-ui')['NTooltip']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SystemLogo: typeof import('./../components/common/SystemLogo.vue')['default']
WebSiteLink: typeof import('./../components/custom/WebSiteLink.vue')['default']
}
}
export {}

28
src/typings/env.d.ts vendored
View File

@ -1,12 +1,3 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<object, object, any>;
export default component;
}
/**
* env环境类型
* - dev: 后台开发环境
@ -15,6 +6,16 @@ declare module '*.vue' {
*/
type EnvType = 'dev' | 'test' | 'prod';
/**
* env环境配置
*/
interface EnvConfig {
/** 请求地址 */
url: string;
/** 代理地址 */
proxy: string;
}
interface ImportMetaEnv {
/** 项目基本地址 */
readonly VITE_BASE_URL: string;
@ -27,14 +28,21 @@ interface ImportMetaEnv {
/**
* 权限路由模式:
* - static - 前端声明的静态
* - dynamic - 后端返回的动态 */
* - dynamic - 后端返回的动态
*/
readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic';
/** 路由首页的路径 */
readonly VITE_ROUTE_HOME_PATH: Exclude<AuthRoute.RoutePath, '/not-found-page' | '/:pathMatch(.*)*'>;
/** vite环境类型 */
readonly VITE_ENV_TYPE?: EnvType;
/** 开启请求代理 */
readonly VITE_HTTP_PROXY?: 'true' | 'false';
/** 是否开启打包文件大小结果分析 */
readonly VITE_VISUALIZER?: 'true' | 'false';
/** 是否开启打包压缩 */
readonly VITE_COMPRESS?: 'true' | 'false';
/** 压缩算法类型 */
readonly VITE_COMPRESS_TYPE?: 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw';
/** hash路由模式 */
readonly VITE_HASH_ROUTE?: 'true' | 'false';
}

View File

@ -1,6 +1,6 @@
/** vue 的defineExpose导出的类型 */
declare namespace Expose {
interface BetterScroll {
instance: import('@better-scroll/core');
instance: import('@better-scroll/core').BScrollInstance;
}
}

View File

@ -36,6 +36,9 @@ declare namespace AuthRoute {
| 'plugin_icon'
| 'plugin_print'
| 'plugin_swiper'
| 'auth-demo'
| 'auth-demo_permission'
| 'auth-demo_super'
| 'exception'
| 'exception_403'
| 'exception_404'

View File

@ -84,6 +84,14 @@ declare namespace Service {
/** 接口消息 */
message: string;
}
/** mock的响应option */
interface MockOption {
url: Record<string, any>;
body: Record<string, any>;
query: Record<string, any>;
headers: Record<string, any>;
}
}
/** 主题相关类型 */

View File

@ -37,7 +37,7 @@ export function getUserInfo() {
userId: '',
userName: '',
userPhone: '',
userRole: 'test'
userRole: 'user'
};
const userInfo: Auth.UserInfo = getLocal<Auth.UserInfo>(EnumStorageKey['user-info']) || emptyInfo;
return userInfo;

View File

@ -1,16 +1,10 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/**
* 根据用户权限过滤路由
* @param routes - 权限路由
* @param permission - 权限
*/
export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {
const filters: AuthRoute.Route[] = [];
routes.forEach(route => {
filterAuthRouteByUserPermission(route, permission);
});
return filters;
return routes.map(route => filterAuthRouteByUserPermission(route, permission)).flat(1);
}
/**
@ -19,5 +13,12 @@ export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], perm
* @param permission - 权限
*/
function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
return [];
const hasPermission =
!route.meta.permissions || permission === 'super' || route.meta.permissions.includes(permission);
if (route.children) {
const filterChildren = route.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);
Object.assign(route, { children: filterChildren });
}
return hasPermission ? [route] : [];
}

View File

@ -31,6 +31,34 @@ export function transformAuthRoutesToSearchMenus(routes: AuthRoute.Route[], tree
}, treeMap);
}
/** 将路由名字转换成路由路径 */
export function transformRouteNameToRoutePath(
name: Exclude<AuthRoute.RouteKey, 'not-found-page'>
): AuthRoute.RoutePath {
const rootPath: AuthRoute.RoutePath = '/';
if (name === 'root') return rootPath;
const splitMark: AuthRoute.RouteSplitMark = '_';
const pathSplitMark = '/';
const path = name.split(splitMark).join(pathSplitMark);
return (pathSplitMark + path) as AuthRoute.RoutePath;
}
/** 将路由路径转换成路由名字 */
export function transformRoutePathToRouteName(
path: Exclude<AuthRoute.RoutePath, '/not-found-page' | '/:pathMatch(.*)*'>
): AuthRoute.RouteKey {
if (path === '/') return 'root';
const pathSplitMark = '/';
const routeSplitMark: AuthRoute.RouteSplitMark = '_';
const name = path.split(pathSplitMark).slice(1).join(routeSplitMark) as AuthRoute.RouteKey;
return name;
}
/**
* 将单个权限路由转换成vue路由
* @param item - 单个权限路由

View File

@ -1,6 +1,7 @@
export * from './module';
export * from './helpers';
export * from './cache';
export * from './auth';
export * from './menu';
export * from './breadcrumb';
export * from './tab';

View File

@ -0,0 +1,65 @@
<template>
<div class="h-full">
<n-card title="权限切换" class="h-full shadow-sm rounded-16px">
<div class="pb-12px">
<n-gradient-text type="primary" :size="20">当前用户的权限{{ auth.userInfo.userRole }}</n-gradient-text>
</div>
<n-select
:value="auth.userInfo.userRole"
class="w-120px"
size="small"
:options="options"
@update:value="auth.updateUserRole"
/>
<div class="py-12px">
<n-gradient-text type="primary" :size="20">权限指令 v-permission</n-gradient-text>
</div>
<div>
<n-button v-permission="'super'" class="mr-12px">super可见</n-button>
<n-button v-permission="'admin'" class="mr-12px">admin可见</n-button>
<n-button v-permission="['admin', 'user']">admin和test可见</n-button>
</div>
<div class="py-12px">
<n-gradient-text type="primary" :size="20">权限函数 hasPermission</n-gradient-text>
</div>
<n-space>
<n-button v-if="hasPermission('super')">super可见</n-button>
<n-button v-if="hasPermission('admin')">admin可见</n-button>
<n-button v-if="hasPermission(['admin', 'user'])">admin和user可见</n-button>
</n-space>
</n-card>
</div>
</template>
<script setup lang="ts">
import { watch } from 'vue';
import type { SelectOption } from 'naive-ui';
import { EnumUserRole } from '@/enum';
import { useAppStore, useAuthStore } from '@/store';
import { usePermission } from '@/composables';
const app = useAppStore();
const auth = useAuthStore();
const { hasPermission } = usePermission();
interface RoleList {
label: string;
value: keyof typeof EnumUserRole;
}
const roleList: RoleList[] = [
{ label: EnumUserRole.super, value: 'super' },
{ label: EnumUserRole.admin, value: 'admin' },
{ label: EnumUserRole.user, value: 'user' }
];
const options: SelectOption[] = roleList as unknown as SelectOption[];
watch(
() => auth.userInfo.userRole,
async () => {
app.reloadPage();
}
);
</script>
<style scoped></style>

View File

@ -0,0 +1,8 @@
<template>
<div class="h-full">
<n-card title="当前页面只有super才能看到" class="h-full shadow-sm rounded-16px"> </n-card>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -14,12 +14,13 @@ const domRef = ref<HTMLDivElement>();
async function renderBaiduMap() {
if (!domRef.value) return;
await load(true);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const map = new AMap.Map(domRef.value, {
zoom: 11,
center: [114.05834626586915, 22.546789983033168],
viewMode: '3D'
});
// eslint-disable-next-line no-console
console.log('map: ', map);
}
onMounted(() => {

View File

@ -14,12 +14,13 @@ const domRef = ref<HTMLDivElement | null>(null);
async function renderBaiduMap() {
if (!domRef.value) return;
await load(true);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const map = new TMap.Map(domRef.value, {
center: new TMap.LatLng(39.98412, 116.307484),
zoom: 11,
viewMode: '3D'
});
// eslint-disable-next-line no-console
console.log('map: ', map);
}
onMounted(() => {

View File

@ -26,8 +26,8 @@ function printTable() {
function printImage() {
printJS({
printable: [
'https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/qq_qrcode.JPG',
'https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/qq_qrcode.JPG'
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg',
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg'
],
type: 'image',
header: 'Multiple Images',

View File

@ -6,7 +6,7 @@
@update:dark="theme.setDarkMode"
/>
<n-card :bordered="false" size="large" class="z-4 !w-auto rounded-20px shadow-sm">
<div class="w-360px <sm:w-300px">
<div class="w-300px sm:w-360px">
<header class="flex-y-center justify-between">
<div class="w-70px h-70px rounded-35px overflow-hidden">
<system-logo class="text-70px text-primary" :fill="true" />