feat: 优化路由权限逻辑
自动部署 / build (push) Failing after 12s Details

master
绝弹 2024-01-11 21:18:49 +08:00
parent 21de506907
commit 9436f5feee
89 changed files with 239 additions and 202 deletions

File diff suppressed because one or more lines are too long

View File

@ -2,9 +2,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%VITE_TITLE% | %VITE_SUBTITLE%</title>
<meta name="description" content="%VITE_SUBTITLE%" />
<link rel="icon" href="./favicon.ico" />
<title>%VITE_TITLE%</title>
</head>
<body>
<div id="app" class="dark:bg-slate-900 dark:text-slate-200">
@ -13,6 +14,8 @@
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IHJnYigyNTUsIDI1NSwgMjU1KTsgZGlzcGxheTogYmxvY2s7IHNoYXBlLXJlbmRlcmluZzogYXV0bzsiIHdpZHRoPSIyMDBweCIgaGVpZ2h0PSIyMDBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1MCA1MCkiPjxnPjxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiB2YWx1ZXM9IjA7NDUiIGtleVRpbWVzPSIwOzEiIGR1cj0iMC4ycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiPjwvYW5pbWF0ZVRyYW5zZm9ybT48cGF0aCBkPSJNMjkuNDkxNTI0MjA2MTE3MjU1IC01LjUgTDM3LjQ5MTUyNDIwNjExNzI1NSAtNS41IEwzNy40OTE1MjQyMDYxMTcyNTUgNS41IEwyOS40OTE1MjQyMDYxMTcyNTUgNS41IEEzMCAzMCAwIDAgMSAyNC43NDI3NDQwNTAxOTg3MzggMTYuOTY0NTY5NDU3MTQ2NzEyIEwyNC43NDI3NDQwNTAxOTg3MzggMTYuOTY0NTY5NDU3MTQ2NzEyIEwzMC4zOTk1OTgyOTk2OTExMTcgMjIuNjIxNDIzNzA2NjM5MDkyIEwyMi42MjE0MjM3MDY2MzkwOTYgMzAuMzk5NTk4Mjk5NjkxMTE0IEwxNi45NjQ1Njk0NTcxNDY3MTYgMjQuNzQyNzQ0MDUwMTk4NzM0IEEzMCAzMCAwIDAgMSA1LjUgMjkuNDkxNTI0MjA2MTE3MjU1IEw1LjUgMjkuNDkxNTI0MjA2MTE3MjU1IEw1LjUgMzcuNDkxNTI0MjA2MTE3MjU1IEwtNS40OTk5OTk5OTk5OTk5OTcgMzcuNDkxNTI0MjA2MTE3MjU1IEwtNS40OTk5OTk5OTk5OTk5OTcgMjkuNDkxNTI0MjA2MTE3MjU1IEEzMCAzMCAwIDAgMSAtMTYuOTY0NTY5NDU3MTQ2NzA1IDI0Ljc0Mjc0NDA1MDE5ODczOCBMLTE2Ljk2NDU2OTQ1NzE0NjcwNSAyNC43NDI3NDQwNTAxOTg3MzggTC0yMi42MjE0MjM3MDY2MzkwODUgMzAuMzk5NTk4Mjk5NjkxMTE3IEwtMzAuMzk5NTk4Mjk5NjkxMTE3IDIyLjYyMTQyMzcwNjYzOTA5MiBMLTI0Ljc0Mjc0NDA1MDE5ODczOCAxNi45NjQ1Njk0NTcxNDY3MTIgQTMwIDMwIDAgMCAxIC0yOS40OTE1MjQyMDYxMTcyNTUgNS41MDAwMDAwMDAwMDAwMDkgTC0yOS40OTE1MjQyMDYxMTcyNTUgNS41MDAwMDAwMDAwMDAwMDkgTC0zNy40OTE1MjQyMDYxMTcyNTUgNS41MDAwMDAwMDAwMDAwMSBMLTM3LjQ5MTUyNDIwNjExNzI1NSAtNS41MDAwMDAwMDAwMDAwMDEgTC0yOS40OTE1MjQyMDYxMTcyNTUgLTUuNTAwMDAwMDAwMDAwMDAyIEEzMCAzMCAwIDAgMSAtMjQuNzQyNzQ0MDUwMTk4NzM4IC0xNi45NjQ1Njk0NTcxNDY3MDUgTC0yNC43NDI3NDQwNTAxOTg3MzggLTE2Ljk2NDU2OTQ1NzE0NjcwNSBMLTMwLjM5OTU5ODI5OTY5MTExNyAtMjIuNjIxNDIzNzA2NjM5MDg1IEwtMjIuNjIxNDIzNzA2NjM5MDkyIC0zMC4zOTk1OTgyOTk2OTExMTcgTC0xNi45NjQ1Njk0NTcxNDY3MTIgLTI0Ljc0Mjc0NDA1MDE5ODczOCBBMzAgMzAgMCAwIDEgLTUuNTAwMDAwMDAwMDAwMDExIC0yOS40OTE1MjQyMDYxMTcyNTUgTC01LjUwMDAwMDAwMDAwMDAxMSAtMjkuNDkxNTI0MjA2MTE3MjU1IEwtNS41MDAwMDAwMDAwMDAwMTIgLTM3LjQ5MTUyNDIwNjExNzI1NSBMNS40OTk5OTk5OTk5OTk5OTggLTM3LjQ5MTUyNDIwNjExNzI1NSBMNS41IC0yOS40OTE1MjQyMDYxMTcyNTUgQTMwIDMwIDAgMCAxIDE2Ljk2NDU2OTQ1NzE0NjcwMiAtMjQuNzQyNzQ0MDUwMTk4NzQgTDE2Ljk2NDU2OTQ1NzE0NjcwMiAtMjQuNzQyNzQ0MDUwMTk4NzQgTDIyLjYyMTQyMzcwNjYzOTA4IC0zMC4zOTk1OTgyOTk2OTExMiBMMzAuMzk5NTk4Mjk5NjkxMTE3IC0yMi42MjE0MjM3MDY2MzkxIEwyNC43NDI3NDQwNTAxOTg3MzggLTE2Ljk2NDU2OTQ1NzE0NjcxNiBBMzAgMzAgMCAwIDEgMjkuNDkxNTI0MjA2MTE3MjU1IC01LjUwMDAwMDAwMDAwMDAxMyBNMCAtMjBBMjAgMjAgMCAxIDAgMCAyMCBBMjAgMjAgMCAxIDAgMCAtMjAiIGZpbGw9IiMwOWYiPjwvcGF0aD48L2c+PC9nPjwvc3ZnPgo="
alt="loading"
class="loading-image"
width="64"
height="64"
/>
<h1 class="loading-title">欢迎访问%VITE_TITLE%</h1>
<div class="loading-tip">资源加载中, 请稍等...</div>

View File

@ -0,0 +1,36 @@
import fs from 'fs';
import { Plugin, ResolvedConfig } from 'vite';
/**
*
* @description
* index.xx.vue
* 退 index.vue
*/
export default function plugin(): Plugin {
let config: ResolvedConfig;
let extension: string;
return {
name: 'vite:extension',
enforce: 'pre',
configResolved(resolvedConfig) {
config = resolvedConfig;
extension = config.env.VITE_EXTENTION ?? config.isProduction ? 'prod' : 'dev';
},
load(id) {
if (!extension || !id.includes('src')) {
return;
}
if (id.includes('?vue')) {
return;
}
const targetPath = id.replace(/\.([^.]*?)$/, `.${extension}.$1`);
if (targetPath && fs.existsSync(targetPath)) {
return fs.readFileSync(targetPath, 'utf-8');
}
},
};
}

View File

@ -1,7 +1,6 @@
import { spawn } from "child_process";
import fs from "fs";
import { Plugin, ResolvedConfig } from "vite";
import pkg from "../../package.json";
import { spawn } from 'child_process';
import { Plugin, ResolvedConfig } from 'vite';
import pkg from '../../package.json';
/**
* logo
@ -21,15 +20,15 @@ const LOGO = ` _ _______ _______ ____ _____ _____ ________ ____
* @returns Promise<string | void>
*/
const exec = (cmd: string) => {
return new Promise<string | void>((resolve) => {
return new Promise<string | void>(resolve => {
if (!cmd) {
return resolve();
}
const child = spawn(cmd, [], { shell: true });
child.stdout.once("data", (data) => {
resolve(data.toString().replace(/"|\n/g, ""));
child.stdout.once('data', data => {
resolve(data.toString().replace(/"|\n/g, ''));
});
child.stderr.once("data", () => {
child.stderr.once('data', () => {
resolve();
});
});
@ -40,9 +39,9 @@ const exec = (cmd: string) => {
* @returns Promise<string>
*/
const getBuildInfo = async () => {
const hash = await exec("git log --format=%h -n 1");
const time = new Date().toLocaleString("zh-Hans-CN");
const latestTag = await exec("git describe --tags --abbrev=0");
const hash = await exec('git log --format=%h -n 1');
const time = new Date().toLocaleString('zh-Hans-CN');
const latestTag = await exec('git describe --tags --abbrev=0');
const commits = await exec(`git rev-list --count ${latestTag}..HEAD`);
const version = commits ? `${latestTag}.${commits}` : `v${pkg.version}`;
const content = `欢迎访问!版本: ${version} 标识: ${hash} 构建: ${time}`;
@ -57,39 +56,24 @@ const getBuildInfo = async () => {
*/
export default function plugin(): Plugin {
let config: ResolvedConfig;
let extension: string;
return {
name: "vite:customizer",
enforce: "pre",
name: 'vite:info',
enforce: 'pre',
configResolved(resolvedConfig) {
config = resolvedConfig;
extension = config.env.VITE_EXTENTION ?? config.isProduction ? "prod" : "dev";
},
async transformIndexHtml() {
const script = await getBuildInfo();
return [
{
tag: "script",
injectTo: "body",
tag: 'script',
injectTo: 'body',
children: script,
},
];
},
load(id) {
if (!extension || !id.includes("src")) {
return;
}
if (id.includes("?vue")) {
return;
}
const targetPath = id.replace(/\.([^.]*?)$/, `.${extension}.$1`);
if (targetPath && fs.existsSync(targetPath)) {
return fs.readFileSync(targetPath, "utf-8");
}
},
};
}

View File

@ -2,7 +2,7 @@
<a-config-provider>
<router-view v-slot="{ Component, route }">
<keep-alive :include="menuStore.caches">
<component v-if="hasAuth(route)" :is="Component"></component>
<component v-if="hasAuth" :is="Component"></component>
<AnForbidden v-else></AnForbidden>
</keep-alive>
</router-view>
@ -10,27 +10,36 @@
</template>
<script setup lang="ts">
import { RouteLocationNormalizedLoaded } from 'vue-router';
import { useUserStore } from '@/store/user';
import { useMenuStore } from '@/store/menu';
import { useUserStore } from '@/store/user';
const route = useRoute();
const userStore = useUserStore();
const menuStore = useMenuStore();
const hasAuth = (route: RouteLocationNormalizedLoaded) => {
const neddAuth = route.meta.auth;
const userAuth = userStore.auth;
if (!neddAuth?.length) {
return true;
}
if (neddAuth.some(i => i === '*')) {
return true;
}
if (userAuth.some(i => neddAuth.some(j => j === i))) {
return true;
}
return false;
};
const hasAuth = computed(() => {
return route.matched.every(item => {
console.log('i', item);
const needAuth = item.meta.auth;
const userAuth = userStore.auth;
if (needAuth?.includes('*')) {
return true;
}
if (!userStore.accessToken && needAuth?.includes('unlogin')) {
return true;
}
if (!userStore.accessToken) {
return false;
}
if (!needAuth) {
return true;
}
if (userAuth.some(i => needAuth.some(j => j === i))) {
return true;
}
return false;
});
});
</script>
<style scoped></style>

View File

@ -1,15 +1,15 @@
<template>
<div class="h-full overflow-hidden grid grid-rows-[auto_1fr]">
<div class="bg-white px-4 py-2">
<div class="bg-white px-4 py-2 border-b border-gray-200">
<div class="flex justify-between gap-4">
<BreadCrumb></BreadCrumb>
<div>
<a-link>需要帮助</a-link>
<a-link @click="router.push({ path: route.path, query: { s: Math.random() }, force: true })">
<a-button size="mini" @click="router.push({ path: route.path, query: { s: Math.random() }, force: true })">
<template #icon>
<i class="icon-park-outline-refresh"></i>
</template>
</a-link>
</a-button>
</div>
</div>
</div>

View File

@ -1,11 +1,12 @@
<template>
<div :style="style"></div>
<div :style="style" class="w-full h-full bg-orange-500 flex items-center justify-center text-white text-lg">
图片组件
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { CSSProperties, PropType } from 'vue';
import { Image } from './interface';
import { CSSProperties } from 'vue';
const props = defineProps({
data: {

View File

@ -1,13 +1,12 @@
<template>
<div :style="style" class="w-full h-full bg-brand-500 flex items-center justify-center text-white text-lg">
<div :style="style" class="w-full h-full bg-blue-500 flex items-center justify-center text-white text-lg">
视频组件
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { CSSProperties, PropType } from 'vue';
import { Video } from './interface';
import { CSSProperties } from 'vue';
const props = defineProps({
data: {

View File

@ -1,6 +1,7 @@
<!-- 修改自: https://github.com/zuley/vue-color-picker -->
<script setup lang="ts">
// @ts-nocheck
import { onClickOutside } from "@vueuse/core";
import { computed, ref } from "vue";

View File

@ -1,12 +1,5 @@
<template>
<a-modal
v-model:visible="show"
mask-animation-name=""
modal-animation-name=""
:fullscreen="true"
:footer="false"
class="ani-modal"
>
<a-modal v-model:visible="show" :fullscreen="true" :footer="false" class="an-editor">
<div class="w-full h-full bg-slate-100 grid grid-rows-[auto_1fr] select-none">
<div class="h-13 bg-white border-b border-slate-200 z-10">
<EditorHeader
@ -144,7 +137,7 @@ onMounted(loadData);
</script>
<style lang="less">
.ani-modal {
.an-editor {
.muti-form-item .arco-form-item .arco-form-item-label {
line-height: 1;
}

View File

@ -1,6 +1,6 @@
import { Ref } from "vue";
import { Block } from "./block";
import { getClosestValInSortedArr } from "../utils/closest";
import { Block } from "./block";
/**
* 线
@ -100,8 +100,8 @@ export const useReferenceLine = (blocks: Ref<Block[]>, current: Ref<Block | null
* 6. 线
*/
function updateRefLine(rect: DragRect) {
const allXLines = [];
const allYLines = [];
const allXLines: any[] = [];
const allYLines: any[] = [];
const box = getRectBox(rect);
let offsetX: number | undefined;
let offsetY: number | undefined;
@ -138,7 +138,7 @@ export const useReferenceLine = (blocks: Ref<Block[]>, current: Ref<Block | null
} else if (isEqualNum(closetDistX, distMaxX)) {
offsetX = closetMaxX - box.maxX;
} else {
throw new Error("un");
throw new Error('un');
}
}
@ -150,7 +150,7 @@ export const useReferenceLine = (blocks: Ref<Block[]>, current: Ref<Block | null
} else if (isEqualNum(closetDistY, distMaxY)) {
offsetY = closetMaxY - box.maxY;
} else {
throw new Error("un");
throw new Error('un');
}
}

View File

@ -1,4 +1,3 @@
export function getModel(model: Recordable) {
const data: Recordable = {};
@ -42,7 +41,7 @@ function rmString(str: string) {
}
function setModelArray(data: Recordable, key: string) {
const result = [];
const result: any[] = [];
const field = rmString(key);
for (const key of field.split(',')) {
result.push(data[key]);

View File

@ -11,7 +11,7 @@
{{ currentFormated }}
</div>
<div class="w-96">
<audio ref="audioRef" src="" class="hidden" @timeupdate="onTimeUpdate"></audio>
<audio ref="audioRef" src="" class="hidden"></audio>
<a-slider class="block!"></a-slider>
</div>
<div>
@ -19,10 +19,7 @@
</div>
<div class="dd">
<a-popover>
<div
@click="onMuteToggle"
class="text-xl hover:bg-[rgba(255,255,255,.1)] h-8 px-1.5 flex items-center justify-center rounded"
>
<div class="text-xl hover:bg-[rgba(255,255,255,.1)] h-8 px-1.5 flex items-center justify-center rounded">
<i :class="volumeIcon"></i>
</div>
<template #content>
@ -39,6 +36,8 @@
</template>
<script setup lang="ts">
import numeral from 'numeral';
const playing = ref(true);
const volume = ref(50);
const volumeLast = ref(50);

View File

@ -104,7 +104,7 @@
</template>
<script setup lang="tsx">
import { getIcon } from '@/pages/content/material/components/util';
import { getIcon } from '@/pages/content/material/util';
import { useVModel } from '@vueuse/core';
import DPlayer from 'dplayer';
import numeral from 'numeral';

View File

@ -39,6 +39,7 @@ const router = useRouter();
{
"meta": {
"title": "404",
"auth": ["*"],
"icon": "icon-park-outline-home"
}
}

21
src/pages/_demo/index.vue Normal file
View File

@ -0,0 +1,21 @@
<template>
<div>
本页面可直接访问!
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>
<route lang="json">
{
"meta": {
"name": "DemoPage",
"sort": 101,
"title": "测试页面",
"auth": ["*"],
"icon": "icon-park-outline-home"
}
}
</route>

View File

@ -30,7 +30,7 @@ export default defineComponent({
function renderItem(routes: MenuItem[], level = 1) {
return routes.map((route): any => {
const icon = route.icon ? () => <i class={route.icon} /> : null;
if (level < 3 && route.children?.some(i => !i.hide)) {
if (level < 3 && route.children?.some(i => i.hide !== true)) {
return (
<>
<a-divider margin={6} class="!border-slate-100 px-2"></a-divider>
@ -45,7 +45,7 @@ export default defineComponent({
<div>{route.title}</div>
<div class="text-xs text-gray-400">
{/* <a-badge count={8}>8</a-badge> */}
{route.hide === 'prod' ? <a-tag color="blue">{'开发'}</a-tag> : null}
{route.hide === 'prod' ? <a-tag color="red">{'开发'}</a-tag> : null}
</div>
</div>
</a-menu-item>

View File

@ -91,7 +91,7 @@ import userDropdown from './UserDropdown.vue';
defineOptions({ name: 'LayoutPage' });
const route = useRoute()
const route = useRoute();
const appStore = useAppStore();
const menuStore = useMenuStore();
const isCollapsed = ref(false);
@ -131,13 +131,6 @@ const buttons = [
window.open('https://github.com/appnify/starter-vue', '_blank');
},
},
{
icon: 'icon-park-outline-info',
tooltip: '关于',
onClick: () => {
window.open('https://github.com/appnify/starter-vue', '_blank');
},
},
];
</script>

View File

@ -136,6 +136,7 @@ const onSubmitForm = async () => {
"name": "LoginPage",
"sort": 101,
"title": "登录",
"auth": ["unlogin"],
"icon": "icon-park-outline-home"
}
}

View File

@ -1,4 +1,4 @@
<script></script>
<template></template>
<route lang="json">
{
"component": null,

View File

@ -1,14 +1,14 @@
<template>
<bread-page>
<div class="h-full grid grid-cols-[auto_auto_1fr]">
<div class="w-[300px]">
<a-tabs type="capsule" @change="onChange">
<div class="h-full grid grid-cols-[1fr_auto_1fr]">
<div>
<a-tabs @change="onChange">
<a-tab-pane v-for="tag in tags" :key="tag.name" :title="tag.description">
<a-form :model="{}" layout="vertical">
<a-form-item label="新增接口">
<a-radio-group type="button" v-model="type.create">
<a-radio
v-for="route in routes.filter((i) => i.tag === tag.name)"
v-for="route in routes.filter(i => i.tag === tag.name)"
:value="route.operationId"
:key="route.path"
>
@ -19,7 +19,7 @@
<a-form-item label="修改接口">
<a-radio-group type="button" v-model="type.modify">
<a-radio
v-for="route in routes.filter((i) => i.tag === tag.name)"
v-for="route in routes.filter(i => i.tag === tag.name)"
:value="route.operationId"
:key="route.path"
>
@ -30,7 +30,7 @@
<a-form-item label="查询接口">
<a-radio-group type="button" v-model="type.select">
<a-radio
v-for="route in routes.filter((i) => i.tag === tag.name)"
v-for="route in routes.filter(i => i.tag === tag.name)"
:value="route.operationId"
:key="route.path"
>
@ -41,7 +41,7 @@
<a-form-item label="删除接口">
<a-radio-group type="button" v-model="type.delete">
<a-radio
v-for="route in routes.filter((i) => i.tag === tag.name)"
v-for="route in routes.filter(i => i.tag === tag.name)"
:value="route.operationId"
:key="route.path"
>
@ -65,12 +65,12 @@
</template>
<script setup lang="ts">
import ejs from "ejs";
import doc from "./components/data.json";
import editorModal from "./components/editor.vue";
import template from "./components/page.ejs?raw";
import ejs from 'ejs';
import doc from './data.json';
import editorModal from './editor.vue';
import template from './page.ejs?raw';
const content = ref("");
const content = ref('');
const { tags, routes } = doc;
const type = ref({
create: undefined,
@ -85,8 +85,8 @@ const onChange = (value: string | number) => {
const onOpen = () => {
const data = {
tag: "",
operationId: "",
tag: '',
operationId: '',
create: {},
select: {},
modify: {},
@ -114,9 +114,9 @@ const onOpen = () => {
<route lang="json">
{
"only": "dev",
"meta": {
"sort": 20010,
"hide": "prod",
"title": "接口生成",
"icon": "icon-park-outline-code"
}

View File

@ -1,5 +1,3 @@
<template></template>
<template>
<div></div>
</template>
@ -9,6 +7,7 @@
"component": null,
"meta": {
"sort": 120012,
"hide": "prod",
"title": "前端导航",
"link": "https://nav.juetan.cn",
"icon": "icon-park-outline-mail"

View File

@ -1,14 +1,12 @@
<template>
<div></div>
</template>
<template></template>
<route lang="json">
{
"component": null,
"meta": {
"sort": 30000,
"title": "日志管理",
"icon": "icon-park-outline-log",
"sort": 30000
"icon": "icon-park-outline-log"
}
}
</route>

View File

@ -8,7 +8,7 @@
</template>
添加
</a-button>
<ani-editor v-model:visible="visible"></ani-editor>
<Editor v-model:visible="visible"></Editor>
</template>
</LoginLogTable>
</BreadPage>
@ -16,8 +16,8 @@
<script setup lang="tsx">
import { api } from '@/api';
import { Editor } from '@/components/AnEditor';
import { useTable } from '@/components/AnTable';
import { Editor as aniEditor } from '@/components/editor';
import { TableColumnData } from '@arco-design/web-vue';
import dayjs from 'dayjs';
@ -124,7 +124,7 @@ const { component: LoginLogTable } = useTable({
{
field: 'nickname',
label: '登陆账号',
setter: 'input',
setter: 'search',
},
],
},

View File

@ -1,6 +1,6 @@
<template>
<bread-page>
<a-form :model="{}" :label-col-props="{ span: 3 }" label-align="left" layout="vertical">
<a-form :model="{}" :label-col-props="{ span: 3 }" label-align="left" layout="vertical" class="space-y-6">
<a-form-item label="站点LOGO">
<a-avatar :size="64">
<img :src="appStore.logo" alt="" />

View File

@ -7,7 +7,7 @@
:disabled="!mail.enable"
layout="vertical"
label-align="left"
class="w-[580px]! divide-y divide-gray-100"
class="w-[580px]! space-y-6"
>
<a-form-item label="是否启用" :disabled="false">
<a-radio-group v-model="mail.enable">

View File

@ -98,7 +98,7 @@ const { component: UserTable } = useTable({
{
field: 'nickname',
label: '用户昵称',
setter: 'input',
setter: 'search',
},
],
create: {

View File

@ -1,6 +1,4 @@
<template>
<div></div>
</template>
<template></template>
<route lang="json">
{

View File

@ -59,7 +59,7 @@ const { component: RoleTable } = useTable({
{
field: 'name',
label: '角色名称',
setter: 'input',
setter: 'search',
},
],
create: {

View File

@ -32,7 +32,7 @@ const { component: PasswordModal, open } = useFormModal({
const usernameRender: TableColumnRender = ({ record }) => (
<div class="flex items-center gap-4 w-full overflow-hidden">
<a-avatar size={32} class="!bg-brand-500">
<a-avatar size={32}>
{record.avatar?.startsWith('/') ? <img src={record.avatar} alt="" /> : record.nickname?.[0]}
</a-avatar>
<div class="w-full flex-1 overflow-hidden">
@ -98,7 +98,7 @@ const { component: UserTable } = useTable({
{
field: 'nickname',
label: '用户昵称',
setter: 'input',
setter: 'search',
},
],
create: {
@ -176,6 +176,7 @@ const { component: UserTable } = useTable({
"cache": true,
"sort": 10301,
"title": "用户管理",
"auth": ["*"],
"icon": "icon-park-outline-user"
}
}

View File

@ -9,9 +9,6 @@ import { menus } from '../menus';
import { APP_HOME_NAME } from '../routes/base';
import { APP_ROUTE_NAME, routes } from '../routes/page';
const WHITE_LIST = ['/:all(.*)*'];
const UNSIGNIN_LIST = ['/login'];
/**
*
* @param to
@ -31,17 +28,12 @@ export function useAuthGuard(router: Router) {
const menuStore = useMenuStore(store);
// 手动指定直接通过
if (to.meta.auth?.some(i => i === '*')) {
return true;
}
// 在白名单内直接通过
if (WHITE_LIST.includes(to.path)) {
if (to.meta.auth?.includes('*')) {
return true;
}
// 未登陆才能访问的页面
if (UNSIGNIN_LIST.includes(to.path)) {
if (to.meta.auth?.includes('unlogin')) {
// 未登陆则允许通过
if (!userStore.accessToken) {
return true;

View File

@ -1,8 +1,5 @@
import generatedRoutes from 'virtual:generated-pages';
import { RouteRecordRaw } from 'vue-router';
import { routes as vroutes } from 'vue-router/auto/routes';
console.log({ vroutes, generatedRoutes });
export const TOP_ROUTE_PREF = '_';
export const APP_ROUTE_NAME = '_layout';

View File

@ -59,30 +59,30 @@ declare module 'vue' {
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
AUpload: typeof import('@arco-design/web-vue')['Upload']
BaseOption: typeof import('./../components/editor/components/BaseOption.vue')['default']
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default']
ColorPicker: typeof import('./../components/editor/components/ColorPicker.vue')['default']
ContextMenu: typeof import('./../components/editor/components/ContextMenu.vue')['default']
ContextMenuList: typeof import('./../components/editor/components/ContextMenuList.vue')['default']
DragResizer: typeof import('./../components/editor/components/DragResizer.vue')['default']
Editor: typeof import('./../components/editor/components/Editor.vue')['default']
EditorConfig: typeof import('./../components/editor/components/EditorConfig.vue')['default']
EditorHeader: typeof import('./../components/editor/components/EditorHeader.vue')['default']
EditorLeft: typeof import('./../components/editor/components/EditorLeft.vue')['default']
EditorMain: typeof import('./../components/editor/components/EditorMain.vue')['default']
EditorMainBlock: typeof import('./../components/editor/components/EditorMainBlock.vue')['default']
EditorMainHeader: typeof import('./../components/editor/components/EditorMainHeader.vue')['default']
EditorPreview: typeof import('./../components/editor/components/EditorPreview.vue')['default']
EditorRight: typeof import('./../components/editor/components/EditorRight.vue')['default']
EditorSetting: typeof import('./../components/editor/components/EditorSetting.vue')['default']
ImagePicker: typeof import('./../components/editor/components/ImagePicker.vue')['default']
InputColor: typeof import('./../components/editor/components/InputColor.vue')['default']
InputImage: typeof import('./../components/editor/components/InputImage.vue')['default']
InputTexter: typeof import('./../components/editor/components/InputTexter.vue')['default']
Marquee: typeof import('./../components/editor/blocks/text/marquee.vue')['default']
Option: typeof import('./../components/editor/blocks/date/option.vue')['default']
Render: typeof import('./../components/editor/blocks/date/render.vue')['default']
BaseOption: typeof import('./../components/AnEditor/components/BaseOption.vue')['default']
BreadCrumb: typeof import('./../components/AnBreadcrumb/bread-crumb.vue')['default']
BreadPage: typeof import('./../components/AnBreadcrumb/bread-page.vue')['default']
ColorPicker: typeof import('./../components/AnEditor/components/ColorPicker.vue')['default']
ContextMenu: typeof import('./../components/AnEditor/components/ContextMenu.vue')['default']
ContextMenuList: typeof import('./../components/AnEditor/components/ContextMenuList.vue')['default']
DragResizer: typeof import('./../components/AnEditor/components/DragResizer.vue')['default']
Editor: typeof import('./../components/AnEditor/components/Editor.vue')['default']
EditorConfig: typeof import('./../components/AnEditor/components/EditorConfig.vue')['default']
EditorHeader: typeof import('./../components/AnEditor/components/EditorHeader.vue')['default']
EditorLeft: typeof import('./../components/AnEditor/components/EditorLeft.vue')['default']
EditorMain: typeof import('./../components/AnEditor/components/EditorMain.vue')['default']
EditorMainBlock: typeof import('./../components/AnEditor/components/EditorMainBlock.vue')['default']
EditorMainHeader: typeof import('./../components/AnEditor/components/EditorMainHeader.vue')['default']
EditorPreview: typeof import('./../components/AnEditor/components/EditorPreview.vue')['default']
EditorRight: typeof import('./../components/AnEditor/components/EditorRight.vue')['default']
EditorSetting: typeof import('./../components/AnEditor/components/EditorSetting.vue')['default']
ImagePicker: typeof import('./../components/AnEditor/components/ImagePicker.vue')['default']
InputColor: typeof import('./../components/AnEditor/components/InputColor.vue')['default']
InputImage: typeof import('./../components/AnEditor/components/InputImage.vue')['default']
InputTexter: typeof import('./../components/AnEditor/components/InputTexter.vue')['default']
Marquee: typeof import('./../components/AnEditor/blocks/text/marquee.vue')['default']
Option: typeof import('./../components/AnEditor/blocks/date/option.vue')['default']
Render: typeof import('./../components/AnEditor/blocks/date/render.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@ -39,6 +39,7 @@ import type {
declare module 'vue-router/auto/routes' {
export interface RouteNamedMap {
'/_demo/': RouteRecordInfo<'/_demo/', '/_demo', Record<never, never>, Record<never, never>>,
'/_layout/': RouteRecordInfo<'/_layout/', '/_layout', Record<never, never>, Record<never, never>>,
'/_login/': RouteRecordInfo<'/_login/', '/_login', Record<never, never>, Record<never, never>>,
'/[..._all]/': RouteRecordInfo<'/[..._all]/', '/:_all(.*)', { _all: ParamValue<true> }, { _all: ParamValue<false> }>,

View File

@ -1,19 +1,20 @@
import Vue from '@vitejs/plugin-vue';
import VueJsx from '@vitejs/plugin-vue-jsx';
import Unocss from 'unocss/vite';
import AutoImport from 'unplugin-auto-import/vite';
import AutoComponent from 'unplugin-vue-components/vite';
import router from 'unplugin-vue-router/vite';
import Page from 'vite-plugin-pages';
import iconFile from './scripts/vite/file.json';
import iconFmt from './scripts/vite/fmt.json';
import plugin from './scripts/vite/plugin';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { presetIcons, presetUno } from 'unocss';
import Unocss from 'unocss/vite';
import AutoImport from 'unplugin-auto-import/vite';
import { ArcoResolver } from 'unplugin-vue-components/resolvers';
import AutoComponent from 'unplugin-vue-components/vite';
import router from 'unplugin-vue-router/vite';
import { defineConfig, loadEnv } from 'vite';
import Page from 'vite-plugin-pages';
import { arcoToUnoColor } from './scripts/vite/color';
import iconFile from './scripts/vite/file.json';
import iconFmt from './scripts/vite/fmt.json';
import extension from './scripts/vite/plugin-extension';
import info from './scripts/vite/plugin-info';
/**
* vite
@ -78,7 +79,7 @@ export default defineConfig(({ mode }) => {
*/
Page({
exclude: ['**/components/*', '**/*.*.*', '**/!(index).*'],
importMode: 'async',
importMode: 'sync',
extensions: ['vue'],
onRoutesGenerated(routes) {
const isProd = mode !== 'development';
@ -133,10 +134,16 @@ export default defineConfig(({ mode }) => {
}),
/**
*
* @see ./scripts/vite/plugin.ts
*
* @see ./scripts/vite/plugin-info.ts
*/
plugin(),
info(),
/**
*
* @see ./scripts/vite/plugin-extension.ts
*/
extension(),
],
resolve: {
alias: [