feat: 优化路由权限逻辑
自动部署 / build (push) Failing after 12s
Details
自动部署 / build (push) Failing after 12s
Details
parent
21de506907
commit
9436f5feee
File diff suppressed because one or more lines are too long
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
43
src/App.vue
43
src/App.vue
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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: {
|
||||
|
|
@ -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: {
|
||||
|
|
@ -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";
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ const router = useRouter();
|
|||
{
|
||||
"meta": {
|
||||
"title": "404",
|
||||
"auth": ["*"],
|
||||
"icon": "icon-park-outline-home"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ const onSubmitForm = async () => {
|
|||
"name": "LoginPage",
|
||||
"sort": 101,
|
||||
"title": "登录",
|
||||
"auth": ["unlogin"],
|
||||
"icon": "icon-park-outline-home"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<script></script>
|
||||
<template></template>
|
||||
<route lang="json">
|
||||
{
|
||||
"component": null,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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="" />
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ const { component: UserTable } = useTable({
|
|||
{
|
||||
field: 'nickname',
|
||||
label: '用户昵称',
|
||||
setter: 'input',
|
||||
setter: 'search',
|
||||
},
|
||||
],
|
||||
create: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<template></template>
|
||||
|
||||
<route lang="json">
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const { component: RoleTable } = useTable({
|
|||
{
|
||||
field: 'name',
|
||||
label: '角色名称',
|
||||
setter: 'input',
|
||||
setter: 'search',
|
||||
},
|
||||
],
|
||||
create: {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> }>,
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
Loading…
Reference in New Issue