feat: 优化路由加载

master
绝弹 2023-12-17 23:16:59 +08:00
parent 43487136fb
commit 943ec54aed
28 changed files with 309 additions and 209 deletions

2
.env
View File

@ -30,3 +30,5 @@ VITE_PROXY = https://appnify.app.juetan.cn/
VITE_OPENAPI = http://127.0.0.1:3030/openapi.json
# 文件后缀 说明设为dev时会优先加载index.dev.vue文件否则回退至index.vue文件
VITE_EXTENSION = dev
# 是否以离线模式启动,跳过登录
VITE_OFFLINE = true

View File

@ -1,9 +1,9 @@
import { Button, ButtonInstance, FormInstance, Message, Modal } from '@arco-design/web-vue';
import { useVModel } from '@vueuse/core';
import { InjectionKey, PropType, Ref } from 'vue';
import { getModel, setModel } from '../utils/useFormModel';
import { AnForm, AnFormInstance, AnFormSubmit } from './Form';
import { AnFormItemProps } from './FormItem';
import { useVModel } from '@vueuse/core';
import { getModel, setModel } from '../utils/useFormModel';
export interface AnFormModalContext {
visible: Ref<boolean>;
@ -210,6 +210,8 @@ export const AnFormModal = defineComponent({
<Modal
titleAlign="start"
closable={false}
maskAnimationName=""
modalAnimationName=""
{...this.modalProps}
v-model:visible={this.visible}
class="an-form-modal"

View File

@ -154,7 +154,7 @@ function useTableButtonColumn(column: TableButtonColumn & TableColumnData) {
}
return (
<>
{index !== 0 && <Divider direction="vertical" margin={2} />}
{index !== 0 && <Divider direction="vertical" margin={4} />}
<Link {...item.buttonProps} disabled={item.disable?.(props)} onClick={() => item.onClick?.(props)}>
{{
default: () => item.text,

View File

@ -10,7 +10,7 @@
</div>
<slot name="content">
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto" type="track">
<div class="m-4 p-4 bg-white rounded overflow-hidden">
<div class="m-4 bg-white rounded overflow-hidden" :class="{ 'p-4': contentPadding }">
<slot></slot>
</div>
</a-scrollbar>
@ -19,7 +19,14 @@
</template>
<script setup lang="ts">
import BreadCrumb from "./bread-crumb.vue";
import BreadCrumb from './bread-crumb.vue';
defineProps({
contentPadding: {
type: Boolean,
default: true,
},
});
</script>
<style scoped></style>

View File

@ -1,5 +1,12 @@
<template>
<a-modal v-model:visible="show" :fullscreen="true" :footer="false" class="ani-modal">
<a-modal
v-model:visible="show"
mask-animation-name=""
modal-animation-name=""
:fullscreen="true"
:footer="false"
class="ani-modal"
>
<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
@ -41,17 +48,17 @@
</template>
<script setup lang="ts">
import { delConfirm, sleep } from '@/utils';
import { Message } from '@arco-design/web-vue';
import { useVModel } from '@vueuse/core';
import { Block, ContextMenuItem, EditorKey, useEditor } from '../core';
import ContextMenu from './ContextMenu.vue';
import EditorSetting from './EditorConfig.vue';
import EditorHeader from './EditorHeader.vue';
import EditorLeft from './EditorLeft.vue';
import EditorMain from './EditorMain.vue';
import EditorRight from './EditorRight.vue';
import EditorPreview from './EditorPreview.vue';
import EditorSetting from './EditorConfig.vue';
import ContextMenu from './ContextMenu.vue';
import { delConfirm, sleep } from '@/utils';
import { useVModel } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import EditorRight from './EditorRight.vue';
const props = defineProps({
visible: {
@ -85,7 +92,7 @@ const blockMenuItems: ContextMenuItem[] = [
await delConfirm({
content: '确定删除该组件吗?',
okText: '确定删除',
})
});
if (blockMenu.block) {
rmBlock(blockMenu.block);
}

View File

@ -1,17 +1,17 @@
<template>
<div class="h-full flex items-center justify-between pl-2 pr-4">
<div class="text-base group">
<div class="text-base flex items-center group">
<a-link @click="emit('exit')">
<template #icon>
<i class="icon-park-outline-left"></i>
</template>
返回
</a-link>
<a-divider :direction="'vertical'" :margin="4"></a-divider>
<a-tag :color="container.id ? 'blue' : 'green'" class="mr-2 ml-1">
{{ container.id ? '修改' : '新增' }}
</a-tag>
<a-divider :direction="'vertical'" :margin="8"></a-divider>
<ani-texter v-model="container.title"></ani-texter>
<!-- <a-tag :color="container.id ? 'blue' : 'green'" class="mr-2 ml-1">
{{ container.id ? '修改' : '新增' }}
</a-tag> -->
</div>
<div class="flex gap-2">
<a-button @click="emit('preview')">

View File

@ -1,10 +1,11 @@
<template>
<span v-if="!descEditing">
<span v-if="!descEditing" class="inline-block leading-[32px] h-8">
{{ modelValue }}
<i
class="!hidden !group-hover:inline-block icon-park-outline-edit text-gray-400 hover:text-gray-700 ml-1 cursor-pointer"
@click="onDescEdit"
></i>
<a-button type="text" size="small" shape="round" class="!hidden !group-hover:inline-block !text-gray-500" @click="onDescEdit">
<template #icon>
<i class="icon-park-outline-edit"></i>
</template>
</a-button>
</span>
<span v-else class="inline-flex items-center">
<a-input size="small" v-model="descContent" class="!w-96" v-bind="inputProps"></a-input>
@ -22,8 +23,8 @@
</template>
<script setup lang="ts">
import { PropType } from "vue";
import { Input } from "@arco-design/web-vue";
import { PropType } from "vue";
const props = defineProps({
modelValue: {

View File

@ -25,29 +25,15 @@
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
import Image404 from "./image-404.svg?raw";
import { useRouter } from 'vue-router';
import Image404 from './image-404.svg?raw';
defineOptions({ name: "AllUncatchedPage" });
defineOptions({ name: 'AllUncatchedPage' });
const router = useRouter();
</script>
<style scoped>
.slide-in-bottom {
animation: slide-in-bottom 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@keyframes slide-in-bottom {
0% {
transform: translateY(100px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
</style>
<style scoped></style>
<route lang="json">
{

View File

@ -1,9 +1,9 @@
<script lang="tsx">
import { MenuItem } from "@/router";
import { useMenuStore } from "@/store/menu";
import { MenuItem } from '@/router';
import { useMenuStore } from '@/store/menu';
export default defineComponent({
name: "LayoutMenu",
name: 'LayoutMenu',
setup() {
const selectedKeys = ref<string[]>([]);
const route = useRoute();
@ -13,21 +13,21 @@ export default defineComponent({
watch(
() => route.path,
() => {
selectedKeys.value = route.matched.map((i) => i.path);
selectedKeys.value = route.matched.map(i => i.path);
},
{ immediate: true }
);
function goto(route: MenuItem) {
if (route.external) {
window.open(route.path, "_blank");
window.open(route.path, '_blank');
return;
}
router.push(route.path);
}
function renderItem(routes: MenuItem[]) {
return routes.map((route) => {
return routes.map(route => {
const icon = route.icon ? () => <i class={route.icon} /> : null;
const node: any = route.children?.length ? (
<>
@ -38,8 +38,13 @@ export default defineComponent({
</>
) : (
<a-menu-item key={route.path} v-slots={{ icon }} onClick={() => goto(route)}>
{route.title}
{false && <span class="text-xs text-slate-400 ml-2">({route.sort})</span>}
<div class="flex items-center justify-between gap-2 pr-4">
<div>{route.title}</div>
<div class="text-xs text-gray-400">
{/* <a-badge count={8}>8</a-badge> */}
{ route.only === 'dev' ? '仅开发' : null }
</div>
</div>
</a-menu-item>
);
return node;
@ -47,7 +52,7 @@ export default defineComponent({
}
return () => (
<a-menu style={{ width: "100%" }} selectedKeys={selectedKeys.value} autoOpenSelected={true} levelIndent={0}>
<a-menu style={{ width: '100%' }} selectedKeys={selectedKeys.value} autoOpenSelected={true} levelIndent={0}>
{renderItem(menuStore.menus)}
</a-menu>
);

View File

@ -44,7 +44,7 @@
<a-divider :margin="4"></a-divider>
<a-doption @click="logout">
<template #icon>
<i class="icon-park-outline-logout"></i>
<i class="icon-park-outline-power"></i>
</template>
退出
</a-doption>
@ -75,7 +75,7 @@ const logout = async () => {
const { component: PasswordModal, open } = useFormModal({
title: '修改密码',
trigger: false,
width: 452,
width: 500,
items: [
{
field: 'password',
@ -84,6 +84,7 @@ const { component: PasswordModal, open } = useFormModal({
setterProps: {
placeholder: '请输入原密码',
},
required: true,
},
{
field: 'password1',
@ -92,14 +93,16 @@ const { component: PasswordModal, open } = useFormModal({
setterProps: {
placeholder: '请输入新密码',
},
required: true,
},
{
field: 'password2',
label: '确认密码',
label: '确认密码',
setter: 'input',
setterProps: {
placeholder: '请再次输入新密码',
},
required: true,
},
],
});

View File

@ -4,12 +4,18 @@
class="h-13 overflow-hidden flex justify-between items-center gap-4 px-2 pr-4 border-b border-slate-200 bg-white dark:bg-slate-800 dark:border-slate-700"
>
<div class="h-13 flex items-center">
<!-- <a-button size="small" @click="isCollapsed = !isCollapsed">
<template #icon>
<i class="icon-park-outline-hamburger-button text-base"></i>
</template>
</a-button> -->
<router-link to="/" class="px-2 flex items-center gap-2 text-slate-700">
<img src="/favicon.ico" alt="" width="24" height="24" class="" />
<h1 class="relative text-[22px] leading-[22px] dark:text-white m-0 p-0 font-normal">
<h1 class="relative text-[18px] leading-[22px] dark:text-white m-0 p-0 font-normal">
{{ appStore.title }}
<span class="absolute -right-10 -top-1 font-normal text-xs text-gray-400"> v0.0.1 </span>
</h1>
<!-- <span class="text-gray-400">{{ appStore.subtitle }}</span> -->
</router-link>
</div>
<div class="flex items-center gap-2">
@ -41,7 +47,7 @@
<a-layout-sider
class="h-full overflow-hidden dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700"
:width="224"
:collapsed-width="52"
:collapsed-width="49"
:collapsible="true"
:collapsed="isCollapsed"
:hide-trigger="false"
@ -112,6 +118,13 @@ 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

@ -85,7 +85,7 @@ const updateFileCategories = async () => {
loading.value = true;
const res = await api.fileCategory.getFileCategorys({ size: 0 });
list.value = res.data.data ?? [];
list.value.length && emit('change', list.value[0]);
list.value.length && emit('change', {});
} catch {
// nothing to do
} finally {

View File

@ -14,15 +14,15 @@
<a-modal v-model:visible="show" :footer="false"></a-modal>
</template>
<template v-else>
<a-modal v-model:visible="show" title="预览" title-align="start" :closable="false" :width="420">
<a-modal v-model:visible="show" title="预览" title-align="start" mask-animation-name="" modal-animation-name="" :closable="false" :width="420">
抱歉此文件类型暂不支持预览!
</a-modal>
</template>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { useVModel } from '@vueuse/core';
import { PropType } from 'vue';
type FileType = 'text' | 'audio' | 'image' | 'video' | 'unknown';

View File

@ -9,7 +9,9 @@
title="上传文件"
title-align="start"
v-model:visible="visible"
:width="940"
mask-animation-name=""
modal-animation-name=""
:width="960"
:mask-closable="false"
:on-before-cancel="onBeforeCancel"
@close="onClose"
@ -27,7 +29,12 @@
@error="onUploadError"
>
<template #upload-button>
<a-button type="primary"> 选择文件 </a-button>
<a-button type="primary">
<template #icon>
<i class="icon-park-outline-upload-one"></i>
</template>
选择
</a-button>
</template>
</a-upload>
<div class="flex-1 flex items-center text-gray-400">
@ -38,7 +45,7 @@
</div>
</div>
<div class="h-[424px] border-t border-b border-zinc-100 mt-3">
<div class="h-[424px] border-t border-b border-zinc-100 mt-4">
<ul v-if="fileList.length" class="overflow-hidden p-0 m-0">
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto pr-[20px] divide-y">
<li v-for="item in fileList" :key="item.uid" class="flex items-center gap-4 py-3">
@ -99,7 +106,6 @@
</div>
</div>
<template #footer>
<div class="flex justify-between gap-2 items-center">
<div class="text-gray-400">已上传 {{ stat.doneCount }}/{{ fileList.length }} </div>

View File

@ -22,8 +22,8 @@ import { useCreateColumn, useTable, useTableDelete, useUpdateColumn } from '@/co
import { Message } from '@arco-design/web-vue';
import numeral from 'numeral';
import AnGroup from './components/AnGroup.vue';
import AnUpload from './components/AnUpload.vue';
import AnPreview from './components/AnPreview.vue';
import AnUpload from './components/AnUpload.vue';
import { getIcon } from './components/util';
const current = ref<FileCategory>();
@ -111,10 +111,12 @@ const {
onClick: props => {
window.open(props.record.path, '_blank');
},
icon: 'icon-park-outline-download',
},
{
type: 'modify',
text: '修改',
icon: 'icon-park-outline-edit',
},
{
type: 'delete',
@ -138,6 +140,41 @@ const {
categoryId: undefined,
},
items: [
{
field: 'type',
label: '类型',
setter: 'select',
options: [
{
label: '视频',
value: 1,
},
{
label: '音频',
value: 2,
},
{
label: '图片',
value: 3,
},
{
label: '文本',
value: 4,
},
{
label: '其他',
value: 5,
},
],
setterProps: {
style: {
width: '100px',
},
triggerProps: {
autoFitPopupMinWidth: true
}
}
},
{
field: 'name',
label: '素材名称',

View File

@ -6,7 +6,7 @@
{
"only": "dev",
"meta": {
"sort": 20010,
"sort": 120010,
"title": "开发相关",
"icon": "icon-park-outline-home"
}

View File

@ -18,7 +18,7 @@
{
"only": "dev",
"meta": {
"sort": 20010,
"sort": 120010,
"title": "接口文档",
"icon": "icon-park-outline-api"
}

View File

@ -7,8 +7,8 @@
<script setup lang="tsx">
import { api } from '@/api';
import { TableColumnRender, useCreateColumn, useTable, useUpdateColumn } from '@/components/AnTable';
import { useFormModal } from '@/components/AnForm';
import { TableColumnRender, useCreateColumn, useTable, useUpdateColumn } from '@/components/AnTable';
defineOptions({ name: 'SystemUserPage' });
@ -37,7 +37,7 @@ const usernameRender: TableColumnRender = ({ record }) => (
</a-avatar>
<div class="w-full flex-1 overflow-hidden">
<div>
<span>{record.nickname}</span>
<span class="cursor-pointer hover:text-brand-500">{record.nickname}</span>
<span class="text-gray-400 text-xs truncate ml-2">@{record.username}</span>
</div>
<div class="w-full text-gray-400 space-x-4 text-xs">
@ -71,6 +71,7 @@ const { component: UserTable } = useTable({
title: '操作',
type: 'button',
width: 200,
align: 'right',
buttons: [
{
text: '重置密码',

View File

@ -1,15 +1,15 @@
<template>
<BreadPage>
<template #content>
<section class="my-page m-5 py-4 pr-4 h-full bg-white">
<a-tabs direction="vertical">
<BreadPage :content-padding="false">
<section class="my-page bg-white">
<a-tabs size="large">
<a-tab-pane key="1" title="基本设置">
<template #title>
<i class="icon-park-outline-config"></i>
基本设置
</template>
<a-form :model="user" layout="vertical">
<a-form-item label="个人头像">
<a-form
:model="user"
:label-col-props="{ span: 3 }"
label-align="left"
class="px-6 divide-y divide-gray-100"
>
<a-form-item label="用户头像">
<a-avatar :size="64">
<img src="https://github.com/juetan.png" alt="" />
<template #trigger-icon>
@ -23,12 +23,13 @@
v-model="user.nickname"
placeholder="请输入"
class="!w-[432px]"
allow-clear
:max-length="24"
:show-word-limit="true"
></a-input>
<template #help> 用作系统内显示的名称可在后台修改 </template>
</a-form-item>
<a-form-item label="个人描述">
<a-form-item label="用户描述">
<a-textarea
v-model="user.description"
placeholder="请输入"
@ -47,15 +48,42 @@
<a-form-item label="出生日期">
<a-date-picker v-model="user.birth"></a-date-picker>
</a-form-item>
<a-button type="primary">保存修改</a-button>
<a-form-item>
<a-button type="primary">保存修改</a-button>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="5" title="消息推送">
<template #title>
<i class="icon-park-outline-message"></i>
消息通知
</template>
<a-form :model="user" layout="vertical">
<a-tab-pane key="5" title="消息推送">
<a-form :model="user" :label-col-props="{ span: 3 }" label-align="left" class="px-6 divide-y divide-gray-100">
<a-form-item label="站点LOGO">
<a-avatar :size="64">
<img :src="appStore.logo" alt="" />
<template #trigger-icon>
<i class="icon-park-outline-edit"></i>
</template>
</a-avatar>
<template #help> 支持 5MB 以内大小, png jpg 格式的图片 </template>
</a-form-item>
<a-form-item label="站点名称">
<a-input
v-model="appStore.title"
placeholder="请输入"
class="!w-[432px]"
allow-clear
:max-length="24"
:show-word-limit="true"
></a-input>
<template #help> 用作系统内显示的名称可在后台修改 </template>
</a-form-item>
<a-form-item label="站点描述">
<a-textarea
v-model="appStore.subtitle"
placeholder="请输入"
class="!w-[432px] h-24"
:max-length="140"
:show-word-limit="true"
></a-textarea>
</a-form-item>
<a-form-item label="消息推送">
<div>
<a-checkbox-group direction="vertical" v-model="user.msg">
@ -67,60 +95,41 @@
</div>
<template elp> 用作系统内显示的名称可在后台修改 </template>
</a-form-item>
<a-form-item label="邮箱账号">
<a-input v-model="user.email" placeholder="请输入" class="!w-[432px]"></a-input>
<a-form-item label="默认主题">
<div>
<a-radio-group v-model="user.theme" direction="vertical">
<a-radio value="dark">
黑曜深空
<div class="text-gray-400 mt-1">浅绿色主题包含深林绿叶河流等内容</div>
</a-radio>
<a-radio value="light">
天空之城
<div class="text-gray-400 mt-1">浅绿色主题包含深林绿叶河流等内容</div>
</a-radio>
</a-radio-group>
</div>
</a-form-item>
<a-form-item label="SMTP地址">
<a-input v-model="user.smtpIp" allow-clear placeholder="请输入" class="!w-[314px]"></a-input>
<span class="inline-block px-2">:</span>
<a-input-number v-model="user.smtpPort" :min="0" :max="65535" class="w-24!"></a-input-number>
<template #help> 推荐使用主流邮箱, 例如: gmail.com, qq.com, 163.com等后缀 </template>
</a-form-item>
<a-form-item label="SMTP账号">
<a-input v-model="user.smtpUser" placeholder="请输入" class="!w-[432px]"></a-input>
<template #help> 例如: gmail.com, qq.com, 163.com等后缀 </template>
</a-form-item>
<a-form-item label="SMTP密钥">
<a-input v-model="user.smtpPass" placeholder="请输入" class="!w-[432px]"></a-input>
<template #help> 测试 </template>
</a-form-item>
<a-form-item>
<a-button type="primary"> 保存修改 </a-button>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="8" title="账号密码">
<template #title>
<i class="icon-park-outline-lock"></i>
账号密码
</template>
<a-form :model="user" layout="vertical">
<a-form-item label="原密码">
<a-input placeholder="请输入原密码"></a-input>
</a-form-item>
<a-form-item label="新密码">
<a-input placeholder="请输入新密码"></a-input>
</a-form-item>
<a-form-item label="确认新密码">
<a-input placeholder="请再次输入新密码"></a-input>
</a-form-item>
<a-button type="primary">修改密码</a-button>
</a-form>
</a-tab-pane>
<a-tab-pane key="2" title="主题偏好">
<template #title>
<i class="icon-park-outline-theme"></i>
主题设置
</template>
<div class="flex-1">
<a-form :model="user" layout="vertical">
<a-form-item label="主题">
<div>
<a-radio-group v-model="user.theme" direction="vertical">
<a-radio value="dark">
黑曜深空
<div class="text-gray-400 mt-1">浅绿色主题包含深林绿叶河流等内容</div>
</a-radio>
<a-radio value="light">
天空之城
<div class="text-gray-400 mt-1">浅绿色主题包含深林绿叶河流等内容</div>
</a-radio>
</a-radio-group>
</div>
</a-form-item>
</a-form>
</div>
</a-tab-pane>
<a-tab-pane key="3" title="额外功能">
<template #title>
<i class="icon-park-outline-shield"></i>
基本设置
</template>
<div class="flex-1 grid">
<div class="flex-1 grid px-6">
<div class="mb-3">功能列表</div>
<div v-for="i in 3" class="border-t py-4 flex justify-between items-center gap-4">
<div class="flex gap-3 items-center">
@ -180,13 +189,15 @@
</a-tab-pane>
</a-tabs>
</section>
</template>
</BreadPage>
</template>
<script setup lang="tsx">
import { useAppStore } from '@/store';
import { reactive } from 'vue';
const appStore = useAppStore()
const user = reactive({
nickname: '绝弹',
description: '选择在公开个人资料中显示私有项目的贡献,但不显示任何项目,仓库或组织信息',
@ -195,11 +206,21 @@ const user = reactive({
msg: [2],
gender: 1,
birth: '1988-12-18',
smtpPort: 25,
smtpIp: '10.10.10.30',
smtpUser: '952222@163.com',
smtpPass: 'xxxxx',
});
</script>
<style lang="less">
.my-page {
.arco-form-item-label {
color: var(--color-text-3);
}
.arco-tabs-nav-type-line .arco-tabs-tab {
height: 52px;
}
.arco-form-item.arco-form-item-error,
.arco-form-item.arco-form-item-has-help {
margin-bottom: 20px;
@ -220,6 +241,9 @@ const user = reactive({
.arco-radio-icon-hover {
margin-top: 4px;
}
.arco-form-item:not(:first-child) {
padding: 20px 0 0;
}
}
</style>
@ -228,8 +252,7 @@ const user = reactive({
"meta": {
"sort": 30401,
"title": "个人设置",
"icon": "icon-park-outline-config",
"auth": ["1"]
"icon": "icon-park-outline-config"
}
}
</route>

View File

@ -1,4 +1,5 @@
import { api } from '@/api';
import { env } from '@/config/env';
import { store, useUserStore } from '@/store';
import { useMenuStore } from '@/store/menu';
import { treeEach, treeFilter, treeFind } from '@/utils/listToTree';
@ -7,7 +8,6 @@ import { Router } from 'vue-router';
import { menus } from '../menus';
import { APP_HOME_NAME } from '../routes/base';
import { APP_ROUTE_NAME, routes } from '../routes/page';
import { env } from '@/config/env';
const WHITE_LIST = ['/:all(.*)*'];
const UNSIGNIN_LIST = ['/login'];
@ -27,7 +27,6 @@ export function useAuthGuard(router: Router) {
};
router.beforeEach(async function (to, from) {
console.log(to);
const userStore = useUserStore(store);
const menuStore = useMenuStore(store);

View File

@ -13,6 +13,7 @@ export interface MenuItem {
icon?: string;
external?: boolean;
name?: string;
only?: undefined | 'none' | 'dev';
keepAlive: boolean;
children?: MenuItem[];
}
@ -25,13 +26,13 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
const items: MenuItem[] = [];
for (const route of routes) {
const { meta = {}, parentMeta, path } = route as any;
const { title, sort, icon, keepAlive = false, name } = meta;
const { meta = {}, parentMeta, only, path } = route as any;
const { title, sort, icon, keepAlive = false, name } = meta;
let id = path;
let paths = route.path.split('/');
let parentId = paths.slice(0, -1).join('/');
if (parentMeta) {
const { title, icon, sort } = parentMeta;
const { title, icon, sort, only } = parentMeta;
id = `${path}/index`;
parentId = path;
items.push({
@ -39,6 +40,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
icon,
sort,
path,
only,
id: path,
keepAlive: false,
parentId: paths.slice(0, -1).join('/'),
@ -49,7 +51,7 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
parentId = p;
}
}
items.push({ id, title, parentId, path, icon, sort, keepAlive, name });
items.push({ id, title, parentId, path, icon, sort, only, keepAlive, name });
}
return items;

View File

@ -1,5 +1,8 @@
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

@ -6,6 +6,7 @@ export const useAppStore = defineStore({
state: (): AppStore => ({
isDarkMode: false,
title: env.title,
logo: "/favicon.ico",
subtitle: env.subtitle,
pageLoding: false,
pageTags: [],
@ -74,6 +75,7 @@ export const useAppStore = defineStore({
});
interface AppStore {
logo: string;
/**
*
*/

View File

@ -132,7 +132,7 @@ body {
margin-right: 0;
}
.ani-form-modal .arco-modal-body {
.an-form-modal .arco-modal-body {
padding-bottom: 8px;
}
}

View File

@ -22,7 +22,6 @@ declare module '@vue/runtime-core' {
ADoption: typeof import('@arco-design/web-vue')['Doption']
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
ADropdownButton: typeof import('@arco-design/web-vue')['DropdownButton']
AEmpty: typeof import('@arco-design/web-vue')['Empty']
AForm: typeof import('@arco-design/web-vue')['Form']
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
@ -43,7 +42,6 @@ declare module '@vue/runtime-core' {
AnEmpty: typeof import('./../components/AnEmpty/AnEmpty.vue')['default']
AnForbidden: typeof import('./../components/AnForbidden/AnForbidden.vue')['default']
AnToast: typeof import('./../components/AnToast/AnToast.vue')['default']
AOption: typeof import('@arco-design/web-vue')['Option']
APagination: typeof import('@arco-design/web-vue')['Pagination']
APopover: typeof import('@arco-design/web-vue')['Popover']
AProgress: typeof import('@arco-design/web-vue')['Progress']
@ -73,7 +71,6 @@ declare module '@vue/runtime-core' {
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']
EditorMainConfig: typeof import('./../components/editor/components/EditorMainConfig.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']
@ -84,13 +81,6 @@ declare module '@vue/runtime-core' {
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']
PanelHeader: typeof import('./../components/editor/components/PanelHeader.vue')['default']
PanelLeft: typeof import('./../components/editor/components/PanelLeft.vue')['default']
PanelMain: typeof import('./../components/editor/components/PanelMain.vue')['default']
PanelMainBlock: typeof import('./../components/editor/components/PanelMainBlock.vue')['default']
PanelMainConfig: typeof import('./../components/editor/components/PanelMainConfig.vue')['default']
PanelMainHeader: typeof import('./../components/editor/components/PanelMainHeader.vue')['default']
PanelRight: typeof import('./../components/editor/components/PanelRight.vue')['default']
Render: typeof import('./../components/editor/blocks/date/render.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']

View File

@ -42,25 +42,24 @@ declare module 'vue-router/auto/routes' {
'/_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> }>,
'/dev/openapi': RouteRecordInfo<'/dev/openapi', '/dev/openapi', Record<never, never>, Record<never, never>>,
'/home/demo': RouteRecordInfo<'/home/demo', '/home/demo', Record<never, never>, Record<never, never>>,
'/home/home': RouteRecordInfo<'/home/home', '/home/home', Record<never, never>, Record<never, never>>,
'/home/test': RouteRecordInfo<'/home/test', '/home/test', Record<never, never>, Record<never, never>>,
'/my/': RouteRecordInfo<'/my/', '/my', Record<never, never>, Record<never, never>>,
'/my/dev': RouteRecordInfo<'/my/dev', '/my/dev', Record<never, never>, Record<never, never>>,
'/my/editor': RouteRecordInfo<'/my/editor', '/my/editor', Record<never, never>, Record<never, never>>,
'/post/': RouteRecordInfo<'/post/', '/post', Record<never, never>, Record<never, never>>,
'/post/category/': RouteRecordInfo<'/post/category/', '/post/category', Record<never, never>, Record<never, never>>,
'/post/comment/': RouteRecordInfo<'/post/comment/', '/post/comment', Record<never, never>, Record<never, never>>,
'/post/media/': RouteRecordInfo<'/post/media/', '/post/media', Record<never, never>, Record<never, never>>,
'/post/post/': RouteRecordInfo<'/post/post/', '/post/post', Record<never, never>, Record<never, never>>,
'/content/': RouteRecordInfo<'/content/', '/content', Record<never, never>, Record<never, never>>,
'/content/category/': RouteRecordInfo<'/content/category/', '/content/category', Record<never, never>, Record<never, never>>,
'/content/comment/': RouteRecordInfo<'/content/comment/', '/content/comment', Record<never, never>, Record<never, never>>,
'/content/material/': RouteRecordInfo<'/content/material/', '/content/material', Record<never, never>, Record<never, never>>,
'/content/post/': RouteRecordInfo<'/content/post/', '/content/post', Record<never, never>, Record<never, never>>,
'/dev/': RouteRecordInfo<'/dev/', '/dev', Record<never, never>, Record<never, never>>,
'/dev/editor/': RouteRecordInfo<'/dev/editor/', '/dev/editor', Record<never, never>, Record<never, never>>,
'/dev/openapi/': RouteRecordInfo<'/dev/openapi/', '/dev/openapi', Record<never, never>, Record<never, never>>,
'/home/': RouteRecordInfo<'/home/', '/home', Record<never, never>, Record<never, never>>,
'/log/': RouteRecordInfo<'/log/', '/log', Record<never, never>, Record<never, never>>,
'/log/login/': RouteRecordInfo<'/log/login/', '/log/login', Record<never, never>, Record<never, never>>,
'/log/operation/': RouteRecordInfo<'/log/operation/', '/log/operation', Record<never, never>, Record<never, never>>,
'/system/': RouteRecordInfo<'/system/', '/system', Record<never, never>, Record<never, never>>,
'/system/login-log/': RouteRecordInfo<'/system/login-log/', '/system/login-log', Record<never, never>, Record<never, never>>,
'/system/dict/': RouteRecordInfo<'/system/dict/', '/system/dict', Record<never, never>, Record<never, never>>,
'/system/menu/': RouteRecordInfo<'/system/menu/', '/system/menu', Record<never, never>, Record<never, never>>,
'/system/operation-log/': RouteRecordInfo<'/system/operation-log/', '/system/operation-log', Record<never, never>, Record<never, never>>,
'/system/permission/': RouteRecordInfo<'/system/permission/', '/system/permission', Record<never, never>, Record<never, never>>,
'/system/role/': RouteRecordInfo<'/system/role/', '/system/role', Record<never, never>, Record<never, never>>,
'/system/user/': RouteRecordInfo<'/system/user/', '/system/user', Record<never, never>, Record<never, never>>,
'/user/': RouteRecordInfo<'/user/', '/user', Record<never, never>, Record<never, never>>,
}
}

View File

@ -4,6 +4,8 @@ import { merge } from 'lodash-es';
export type DelOptions = string | Partial<Omit<ModalConfig, 'onOk' | 'onCancel'>>;
export const delOptions: ModalConfig = {
maskAnimationName: '',
modalAnimationName: '',
title: '提示',
titleAlign: 'start',
width: 432,

View File

@ -1,18 +1,19 @@
import Vue from "@vitejs/plugin-vue";
import VueJsx from "@vitejs/plugin-vue-jsx";
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 { 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 plugin from "./scripts/vite/plugin";
import Vue from '@vitejs/plugin-vue';
import VueJsx from '@vitejs/plugin-vue-jsx';
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 plugin from './scripts/vite/plugin';
/**
* vite
@ -20,12 +21,21 @@ import plugin from "./scripts/vite/plugin";
*/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
const base = env.VITE_BASE ?? "/";
const host = env.VITE_HOST ?? "0.0.0.0";
const base = env.VITE_BASE ?? '/';
const host = env.VITE_HOST ?? '0.0.0.0';
const port = Number(env.VITE_PORT ?? 3020);
return {
base,
plugins: [
/**
*
* @see https://github.com/posva/unplugin-vue-router
*/
router({
dts: 'src/types/auto-router.d.ts',
exclude: ['**/components/*'],
}),
/**
* Vue 3
* @see https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue
@ -47,9 +57,9 @@ export default defineConfig(({ mode }) => {
* @see https://github.com/antfu/unplugin-auto-import#readme
*/
AutoImport({
imports: ["vue", "vue-router"],
dts: 'src/types/auto-import.d.ts',
imports: ['vue', 'vue-router'],
resolvers: [ArcoResolver()],
dts: "./src/types/auto-import.d.ts",
}),
/**
@ -57,8 +67,8 @@ export default defineConfig(({ mode }) => {
* @see https://github.com/antfu/unplugin-vue-components
*/
AutoComponent({
dts: 'src/types/auto-component.d.ts',
resolvers: [ArcoResolver({ sideEffect: false })],
dts: "./src/types/auto-component.d.ts",
}),
/**
@ -66,13 +76,13 @@ export default defineConfig(({ mode }) => {
* @see https://github.com/hannoeru/vite-plugin-pages
*/
Page({
exclude: ["**/components/*", "**/*.*.*"],
importMode: "async",
exclude: ['**/components/*', '**/*.*.*'],
importMode: 'async',
onRoutesGenerated(routes) {
// if (env.DEV) {
// return routes;
// }
return routes.filter((route) => route.only !== "dev");
if (mode === 'development') {
return routes.filter(route => route.only !== 'none');
}
return routes.filter(route => !['none', 'dev'].includes(route.only));
},
}),
@ -83,17 +93,17 @@ export default defineConfig(({ mode }) => {
Unocss({
theme: {
colors: {
brand: arcoToUnoColor("primary"),
brand: arcoToUnoColor('primary'),
},
},
include: ["src/**/*.{vue,ts,tsx,css,scss,sass,less,styl}"],
include: ['src/**/*.{vue,ts,tsx,css,scss,sass,less,styl}'],
presets: [
presetUno(),
presetIcons({
prefix: "",
prefix: '',
collections: {
"icon-file": iconFile,
"icon-fmt": iconFmt,
'icon-file': iconFile,
'icon-fmt': iconFmt,
},
}),
],
@ -105,7 +115,7 @@ export default defineConfig(({ mode }) => {
*/
visualizer({
title: `构建统计 | ${env.VITE_SUBTITLE}`,
filename: ".gitea/stat.html",
filename: '.gitea/stat.html',
}),
/**
@ -117,8 +127,8 @@ export default defineConfig(({ mode }) => {
resolve: {
alias: [
{
find: "@",
replacement: "/src",
find: '@',
replacement: '/src',
},
],
},
@ -126,10 +136,10 @@ export default defineConfig(({ mode }) => {
host,
port,
proxy: {
"/api": {
'/api': {
target: env.VITE_PROXY,
},
"/upload": {
'/upload': {
target: env.VITE_PROXY,
},
},
@ -139,8 +149,8 @@ export default defineConfig(({ mode }) => {
less: {
javascriptEnabled: true,
modifyVars: {
hack: `true; @import (reference) "${resolve("src/styles/css-arco.less")}";`,
arcoblue: "#66f",
hack: `true; @import (reference) "${resolve('src/styles/css-arco.less')}";`,
arcoblue: '#66f',
},
},
},