Commit ceb26770 authored by 朱招明's avatar 朱招明

init

parents
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
VITE_GHPAGES=true
MODE=production
src/auto-imports.d.ts
src/components.d.ts
{
"globals": {
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"EffectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isReadonly": true,
"isRef": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useRoute": true,
"useRouter": true,
"useSlots": true,
"watch": true,
"watchEffect": true
}
}
\ No newline at end of file
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-recommended',
'eslint:recommended',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier',
// unplugin-auto-import :: generated automatically
'./.eslintrc-auto-import.json',
],
env: {
'vue/setup-compiler-macros': true,
},
rules: {
// all rules docs https://eslint.org/docs/rules/
'prettier/prettier': ['error', { semi: true, singleQuote: true, printWidth: 120 }],
},
};
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"printWidth": 100,
"proseWrap": "never",
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": ".prettierrc",
"options": {
"parser": "json"
}
}
]
}
\ No newline at end of file
MIT License
Copyright (c) 2021 Sendya
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Ant Design Vue Pro Layout
This template should help get you started developing with Vue 3 and Ant Design Vue 2 in Vite.
### Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
### Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.
/// <reference types="vite/client" />
interface Window {
readonly PKG: Record<string, string>;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Preview Pro</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
\ No newline at end of file
{
"name": "preview-pro",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"dev:force": "vite --force",
"build": "vue-tsc --noEmit && vite build",
"build:ghpages": "vue-tsc --noEmit && vite build --base=/preview-pro/ --mode=ghpages",
"report": "vite build",
"preview": "vite preview --port 5050",
"test:unit": "vitest --environment jsdom",
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@ant-design-vue/pro-layout": "^3.2.1",
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^3.2.0",
"vue": "^3.2.33",
"vue-router": "^4.0.14"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.3",
"@types/node": "^17.0.25",
"@vitejs/plugin-vue": "^2.3.1",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vue/tsconfig": "^0.1.3",
"eslint": "^8.13.0",
"eslint-plugin-vue": "^8.6.0",
"less": "^4.1.2",
"prettier": "^2.6.2",
"rollup-plugin-visualizer": "^5.6.0",
"typescript": "~4.5.5",
"unplugin-auto-import": "^0.5.11",
"unplugin-vue-components": "^0.17.21",
"vite": "^2.9.5",
"vite-plugin-optimize-persist": "^0.1.2",
"vite-plugin-package-config": "^0.1.1",
"vitest": "^0.9.3",
"vue-tsc": "^0.34.6"
},
"author": {
"email": "18x@loacg.com",
"name": "Sendya",
"url": "https://github.com/sendya"
},
"vite": {
"optimizeDeps": {
"include": [
"@ant-design-vue/pro-layout",
"ant-design-vue/es",
"vue",
"vue-router"
]
}
}
}
This diff is collapsed.
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
<title>Vue Pro Components</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient id="linearGradient-2" y2="157.637507%" x2="138.57919%" y1="-36.7931464%" x1="-19.8191553%">
<stop offset="0%" stop-color="#008CFF"/>
<stop offset="100%" stop-color="#0063FF"/>
</linearGradient>
<linearGradient id="linearGradient-3" y2="114.942679%" x2="30.4400914%" y1="-35.6905737%" x1="68.1279872%">
<stop offset="0%" stop-color="#FF8384"/>
<stop offset="100%" stop-color="#FF4553"/>
</linearGradient>
<linearGradient id="svg_1" y2="100%" x2="NaN" y1="0%" x1="NaN">
<stop offset="0%" stop-color="#0E89FF"/>
<stop offset="58.999999%" stop-color="#04b4ff"/>
<stop offset="100%" stop-color="#046fd6"/>
</linearGradient>
</defs>
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="402" width="582" y="-1" x="-1"/>
</g>
<g>
<title>Layer 1</title>
<g fill-rule="evenodd" fill="none" id="Vue">
<g id="Group">
<path fill-rule="nonzero" fill="url(#svg_1)" id="Path-Copy" d="m41.870002,99.48c11.38,3 21.63,-7.12 22.34,-8l20.95955,-20.25077c1.15807,-1.11891 1.81777,-2.65635 1.83075,-4.26659l0.32614,-40.44165c0.00867,-1.07512 0.44979,-2.10148 1.22387,-2.84764l13.63369,-13.14181c1.59052,-1.53315 4.12276,-1.48663 5.6559,0.1039c0.71859,0.74548 1.1201,1.74057 1.1201,2.776l0,59.97504c0,2.69431 -1.08722,5.27463 -3.01541,7.15649l-37.71505,36.80909c-2.34617,2.28981 -6.0959,2.27255 -8.42088,-0.03878"/>
<path fill-rule="nonzero" fill="url(#linearGradient-2)" id="Path" d="m87,99.11631c-11.38,3 -22.54,-6.75631 -23.25,-7.63631l-20.95955,-20.25077c-1.15807,-1.11891 -1.81777,-2.65635 -1.83075,-4.26659l-0.32614,-40.44165c-0.00867,-1.07512 -0.44979,-2.10148 -1.22387,-2.84764l-13.63369,-13.14181c-1.59052,-1.53315 -4.12276,-1.48663 -5.6559,0.1039c-0.71859,0.74548 -1.1201,1.74057 -1.1201,2.776l0,59.97504c0,2.69431 1.08722,5.27463 3.01541,7.15649l37.7653,36.85813c2.32622,2.27034 6.03731,2.27542 8.36973,0.01146"/>
<path fill="url(#linearGradient-3)" id="Path" d="m62.29835,43.587129l-15.74174,-15.21673c-0.79418,-0.76769 -0.81565,-2.03384 -0.04796,-2.82802c0.37683,-0.38983 0.89579,-0.60997 1.43799,-0.60997l31.44586,0c1.10457,0 2,0.89543 2,2c0,0.54137 -0.21946,1.05961 -0.60825,1.43633l-15.70412,15.21673c-0.77497,0.75092 -2.00591,0.75165 -2.78178,0.00166z"/>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<template>
<ConfigProvider>
<router-view />
</ConfigProvider>
</template>
<script setup lang="ts">
import { ConfigProvider } from 'ant-design-vue';
import { useUserTheme } from './hooks/useTheme';
useUserTheme();
</script>
<style>
#app {
height: 100%;
}
.ant-pro-sider {
z-index: 20;
}
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition-duration: 0.5s;
transition-property: height, opacity, transform;
transition-timing-function: cubic-bezier(0.55, 0, 0.1, 1);
overflow: hidden;
}
.slide-left-enter,
.slide-right-leave-active {
opacity: 0;
transform: translate(2em, 0);
}
.slide-left-leave-active,
.slide-right-enter {
opacity: 0;
transform: translate(-2em, 0);
}
.zoom-enter-active,
.zoom-leave-active {
animation-duration: 0.3s;
animation-fill-mode: both;
animation-name: zoomIn;
}
.zoom-leave-active {
animation-direction: reverse;
}
@keyframes zoomIn {
from {
opacity: 0;
transform: scale3d(0.95, 0.95, 0.95);
}
100% {
opacity: 1;
}
}
@keyframes zoomOut {
0% {
opacity: 1;
}
to {
opacity: 0;
transform: scale3d(0.95, 0.95, 0.95);
}
}
</style>
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const EffectScope: typeof import('vue')['EffectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
}
export {}
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
declare module 'vue' {
export interface GlobalComponents {
AAvatar: typeof import('ant-design-vue/es')['Avatar']
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
ACol: typeof import('ant-design-vue/es')['Col']
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
ADivider: typeof import('ant-design-vue/es')['Divider']
ADrawer: typeof import('ant-design-vue/es')['Drawer']
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
APagination: typeof import('ant-design-vue/es')['Pagination']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
AResult: typeof import('ant-design-vue/es')['Result']
ARow: typeof import('ant-design-vue/es')['Row']
ASpace: typeof import('ant-design-vue/es')['Space']
AStatistic: typeof import('ant-design-vue/es')['Statistic']
ASwitch: typeof import('ant-design-vue/es')['Switch']
ATag: typeof import('ant-design-vue/es')['Tag']
ATypography: typeof import('ant-design-vue/es')['Typography']
ATypographyLink: typeof import('ant-design-vue/es')['TypographyLink']
ATypographyParagraph: typeof import('ant-design-vue/es')['TypographyParagraph']
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
ATypographyTitle: typeof import('ant-design-vue/es')['TypographyTitle']
RightContent: typeof import('./components/RightContent/RightContent.vue')['default']
SettingDrawer: typeof import('./components/SettingDrawer/SettingDrawer.vue')['default']
}
}
export { }
<template>
<div style="margin-right: 12px">
<a-space>
<a style="padding: 0 12px; display: inline-block; user-select: none" @click="handleClick"><BgColorsOutlined /></a>
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item>
<template #icon>
<SettingOutlined />
</template>
<span>个人设置</span>
</a-menu-item>
<a-menu-item>
<template #icon>
<LogoutOutlined />
</template>
<span>退出登录</span>
</a-menu-item>
</a-menu>
</template>
<a-avatar shape="square" size="small">
<template #icon>
<UserOutlined />
</template>
{{ currentUser.nickname }}
</a-avatar>
</a-dropdown>
</a-space>
</div>
</template>
<script setup lang="ts">
import { UserOutlined, SettingOutlined, LogoutOutlined, BgColorsOutlined } from '@ant-design/icons-vue';
import { apply, randomTheme } from '../../hooks/useTheme';
export type CurrentUser = {
nickname: string;
avatar?: string;
};
defineProps<{
currentUser: CurrentUser;
}>();
const handleClick = () => {
apply(randomTheme());
};
</script>
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import RightContent from '../RightContent.vue';
describe('RightContent', () => {
it('renders properly', () => {
const currentUser = { nickname: 'Admin' };
const wrapper = mount(RightContent, {
props: { currentUser: currentUser },
});
expect(wrapper.text()).toContain(currentUser.nickname);
});
});
<template>
<a-drawer v-model:visible="visible" :width="300" placement="right" :closable="false">
<template #handle>
<div class="ant-pro-setting-drawer-handle" @click="handleShowDrawer">
<SettingOutlined v-if="!visible" />
<CloseOutlined v-else />
</div>
</template>
<div class="margin-bottom: 24px">
<h3>导航模式</h3>
<a-radio-group :value="modelValue.layout" @change="e => updateConf(e.target.value, 'layout')">
<a-radio value="side">左侧菜单布局</a-radio>
<a-radio value="top">顶部菜单布局</a-radio>
<a-radio value="mix">混合菜单布局</a-radio>
</a-radio-group>
<a-divider />
<a-row style="margin-bottom: 12px">
<a-col :span="12">固定 Header</a-col>
<a-col :span="12" style="text-align: right">
<a-switch
checked-children="开"
un-checked-children="关"
:checked="modelValue.fixedHeader"
@change="checked => updateConf(checked, 'fixedHeader')"
/>
</a-col>
</a-row>
<a-row style="margin-bottom: 12px">
<a-col :span="12">固定 左侧菜单</a-col>
<a-col :span="12" style="text-align: right">
<a-switch
checked-children="开"
un-checked-children="关"
:checked="modelValue.fixSiderbar"
@change="checked => updateConf(checked, 'fixSiderbar')"
/>
</a-col>
</a-row>
<a-row style="margin-bottom: 12px">
<a-col :span="12">自动分割菜单</a-col>
<a-col :span="12" style="text-align: right">
<a-switch
checked-children="开"
un-checked-children="关"
:checked="modelValue.splitMenus"
@change="checked => updateConf(checked, 'splitMenus')"
/>
</a-col>
</a-row>
<a-divider />
<h3>内容区域</h3>
<a-row style="margin-bottom: 12px">
<a-col :span="12">顶栏</a-col>
<a-col :span="12" style="text-align: right">
<a-switch
checked-children="开"
un-checked-children="关"
:checked="modelValue.headerRender === undefined"
@change="checked => updateConf(checked === true && undefined, 'headerRender')"
/>
</a-col>
</a-row>
<a-row style="margin-bottom: 12px">
<a-col :span="12">页脚</a-col>
<a-col :span="12" style="text-align: right">
<a-switch
checked-children="开"
un-checked-children="关"
:checked="modelValue.footerRender === undefined"
@change="checked => updateConf(checked === true && undefined, 'footerRender')"
/>
</a-col>
</a-row>
<a-row style="margin-bottom: 12px">
<a-col :span="12">菜单</a-col>
<a-col :span="12" style="text-align: right">
<a-switch
disabled
checked-children="开"
un-checked-children="关"
:checked="modelValue.menu === undefined"
@change="checked => updateConf(checked === true && undefined, 'menu')"
/>
</a-col>
</a-row>
<a-row style="margin-bottom: 12px">
<a-col :span="12">菜单头</a-col>
<a-col :span="12" style="text-align: right">
<a-switch
checked-children="开"
un-checked-children="关"
:checked="modelValue.menuHeaderRender === undefined"
@change="checked => updateConf(checked === true && undefined, 'menuHeaderRender')"
/>
</a-col>
</a-row>
</div>
</a-drawer>
</template>
<script setup lang="ts">
import { SettingOutlined, CloseOutlined } from '@ant-design/icons-vue';
import type { CheckedType } from 'ant-design-vue/es/switch';
type ConfType = 'layout' | 'fixedHeader' | 'fixSiderbar' | string;
const props = defineProps<{
modelValue: Record<string, string | boolean | undefined>;
}>();
const emit = defineEmits(['update:modelValue']);
const visible = ref<boolean>(false);
const handleShowDrawer = () => {
visible.value = !visible.value;
};
const updateConf = (val: string | CheckedType | undefined, type: ConfType) => {
const newVal = {
...toRaw(props.modelValue),
[`${type}`]: val,
};
console.log('newConf', newVal);
emit('update:modelValue', newVal);
};
</script>
<style lang="less">
.ant-pro-setting-drawer-handle {
position: absolute;
top: 240px;
right: 300px;
z-index: 0;
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
font-size: 16px;
text-align: center;
background: var(--primary-color);
border-radius: 4px 0 0 4px;
cursor: pointer;
pointer-events: auto;
> span {
color: rgb(255, 255, 255);
font-size: 20px;
}
}
</style>
export { default as SettingDrawer } from './SettingDrawer.vue';
export { default as RightContent } from './RightContent/RightContent.vue';
export { default as SettingDrawer } from './SettingDrawer/SettingDrawer.vue';
import { ref, inject, provide } from 'vue';
const INJECT_LOADING_KEY = Symbol('loading_store');
export const createLoading = (v = false) => {
const loading = ref<boolean>(v);
const change = (bool: boolean) => {
loading.value = bool;
};
provide(INJECT_LOADING_KEY, loading);
return [loading, change];
};
export const useLoading = () => {
return inject(INJECT_LOADING_KEY);
};
import { onBeforeMount } from 'vue';
import { ConfigProvider } from 'ant-design-vue';
const LOCAL_THEME = 'local_theme';
export const colors: string[] = [
'#f5222d',
'#fa541c',
'#fa8c16',
'#a0d911',
'#13c2c2',
'#1890ff',
'#722ed1',
'#eb2f96',
'#faad14',
'#52c41a',
];
export const useUserTheme = () => {
onBeforeMount(() => {
apply(load());
});
};
export const randomTheme = (): string => {
const i = Math.floor(Math.random() * 10);
return colors[i];
};
export const load = () => {
const color = localStorage.getItem(LOCAL_THEME) || '#1890ff';
return color;
};
export const save = (color: string) => {
localStorage.setItem(LOCAL_THEME, color);
};
export const apply = (color: string) => {
ConfigProvider.config({
theme: {
primaryColor: color,
},
});
save(color);
};
<template>
<pro-layout
v-model:collapsed="state.collapsed"
v-model:selectedKeys="state.selectedKeys"
v-model:openKeys="state.openKeys"
:loading="loading"
:menu-data="menuData"
:breadcrumb="{ routes: breadcrumb }"
disable-content-margin
style="min-height: 100vh"
iconfont-url="//at.alicdn.com/t/font_2804900_2sp8hxw3ln8.js"
v-bind="proConfig"
>
<template #menuHeaderRender>
<router-link :to="{ path: '/' }">
<img src="https://alicdn.antdv.com/v2/assets/logo.1ef800a8.svg" />
<h1>Preview Pro</h1>
</router-link>
</template>
<template #rightContentRender>
<RightContent :current-user="currentUser" />
</template>
<!-- custom breadcrumb itemRender -->
<template #breadcrumbRender="{ route, params, routes }">
<span v-if="routes.indexOf(route) === routes.length - 1">
<HeartOutlined />
{{ route.breadcrumbName }}
</span>
<router-link v-else :to="{ path: route.path, params }">
<SmileOutlined />
{{ route.breadcrumbName }}
</router-link>
</template>
<SettingDrawer v-model="proConfig" />
<RouterView v-slot="{ Component, route }">
<transition name="slide-left" mode="out-in">
<component :is="Component" :key="route.path" />
</transition>
</RouterView>
</pro-layout>
</template>
<script setup lang="ts">
import { useRouter, RouterView, RouterLink } from 'vue-router';
import { getMenuData, clearMenuItem, type RouteContextProps } from '@ant-design-vue/pro-layout';
import { SmileOutlined, HeartOutlined } from '@ant-design/icons-vue';
const router = useRouter();
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
const state = reactive<Omit<RouteContextProps, 'menuData'>>({
collapsed: false, // default collapsed
openKeys: [], // defualt openKeys
selectedKeys: [], // default selectedKeys
});
const loading = ref(false);
const proConfig = ref({
layout: 'mix',
navTheme: 'light',
fixedHeader: true,
fixSiderbar: true,
splitMenus: true,
});
const breadcrumb = computed(() =>
router.currentRoute.value.matched.concat().map(item => {
return {
path: item.path,
breadcrumbName: item.meta.title || '',
};
}),
);
const currentUser = reactive({
nickname: 'Admin',
avatar: 'A',
});
watch(
router.currentRoute,
() => {
const matched = router.currentRoute.value.matched.concat();
state.selectedKeys = matched.filter(r => r.name !== 'index').map(r => r.path);
state.openKeys = matched.filter(r => r.path !== router.currentRoute.value.path).map(r => r.path);
},
{
immediate: true,
},
);
</script>
<template>
<router-view />
</template>
<template>
<pro-layout
v-model:selectedKeys="baseState.selectedKeys"
v-model:openKeys="baseState.openKeys"
collapsed
:loading="loading"
:breadcrumb="{ routes: breadcrumb }"
:header-render="false"
:fix-siderbar="true"
:collapsed-button-render="false"
:menu-data="routes"
disable-content-margin
style="min-height: 100vh"
iconfont-url="//at.alicdn.com/t/font_2804900_2sp8hxw3ln8.js"
>
<template #menuHeaderRender>
<a>
<img src="/favicon.svg" />
</a>
</template>
<WaterMark :content="watermarkContent">
<pro-layout
v-model:collapsed="baseState.collapsed"
v-model:selectedKeys="baseState.childrenSelectedKeys"
v-model:openKeys="baseState.childrenOpenKeys"
nav-theme="light"
:menu-header-render="false"
:menu-data="cachedMap[currentRouteKey]"
:fix-siderbar="true"
:is-children-layout="true"
style="min-height: 100vh"
disable-content-margin
>
<!-- custom right-content -->
<template #rightContentRender>
<div style="margin-right: 12px">
<a-avatar shape="square" size="small">
<template #icon>
<UserOutlined />
</template>
</a-avatar>
</div>
</template>
<template #headerContentRender>
<div style="height: 100%; display: flex; align-items: center">
<a-breadcrumb>
<a-breadcrumb-item v-for="item of breadcrumb" :key="item.path">
<router-link :to="{ path: item.path, item: item.params }">
{{ item.breadcrumbName }}
</router-link>
</a-breadcrumb-item>
</a-breadcrumb>
</div>
</template>
<!-- content begin -->
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</pro-layout>
</WaterMark>
</pro-layout>
</template>
<script setup lang="ts">
import { computed, reactive, ref, watchEffect, onMounted } from 'vue';
import { useRouter, type RouteRecordRaw, type RouteRecordName } from 'vue-router';
import { Avatar as AAvatar, Breadcrumb as ABreadcrumb, BreadcrumbItem as ABreadcrumbItem } from 'ant-design-vue';
import { getMenuData, clearMenuItem, WaterMark } from '@ant-design-vue/pro-layout';
import { UserOutlined } from '@ant-design/icons-vue';
import type { RouteContextProps } from '@ant-design-vue/pro-layout';
const loading = ref(false);
const watermarkContent = ref('Pro Layout');
const router = useRouter();
const currentRouteKey = computed(() => router.currentRoute.value.matched.concat()[1].name);
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
// flat menus
const routes = menuData.map(item => {
return {
...item,
children: null,
};
});
const cachedMap = menuData.reduce((pre, cur) => {
const key = cur.name || cur.path;
const child = cur.children || [];
pre[key] = child;
return pre;
}, {} as Record<RouteRecordName, RouteRecordRaw>);
console.log('cachedMap', cachedMap);
const baseState = reactive<Omit<RouteContextProps, 'menuData'>>({
selectedKeys: ['/welcome'],
openKeys: [],
childrenSelectedKeys: [],
childrenOpenKeys: [],
collapsed: false,
});
const breadcrumb = computed(() =>
router.currentRoute.value.matched.concat().map(item => {
return {
path: item.path,
icon: item.meta.icon,
params: item.meta?.params,
breadcrumbName: item.meta.title || '',
};
}),
);
watchEffect(() => {
if (router.currentRoute) {
const matched = router.currentRoute.value.matched.concat();
baseState.selectedKeys = matched.filter(r => r.name !== 'index').map(r => r.path);
baseState.childrenSelectedKeys = matched.filter(r => r.name !== 'index').map(r => r.path);
baseState.childrenOpenKeys = matched.filter(r => r.path !== router.currentRoute.value.path).map(r => r.path);
}
});
onMounted(() => {
loading.value = true;
new Promise<string>(resolve => {
setTimeout(() => {
resolve('Sendya <18x@loacg.com>');
}, 2000);
}).then(res => {
watermarkContent.value = res;
loading.value = false;
});
});
</script>
import '@ant-design-vue/pro-layout/dist/style.css';
import 'ant-design-vue/dist/antd.variable.min.css';
import { createApp } from 'vue';
import { ConfigProvider } from 'ant-design-vue';
import ProLayout, { PageContainer } from '@ant-design-vue/pro-layout';
import router from './router';
import App from './App.vue';
createApp(App).use(router).use(ConfigProvider).use(ProLayout).use(PageContainer).mount('#app');
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
import BasicLayout from '../layouts/BasicLayout.vue';
import BlankLayout from '../layouts/BlankLayout.vue';
import WelcomePage from '../views/Page1.vue';
// only githubpages preview site used, if use template please remove this check
// and use `createWebHistory` is recommend
const hasGithubPages = import.meta.env.VITE_GHPAGES;
export default createRouter({
history: hasGithubPages ? createWebHashHistory() : createWebHistory(),
routes: [
{
path: '/',
name: 'index',
meta: { title: 'Home' },
component: BasicLayout,
redirect: '/welcome',
children: [
{
path: '/welcome',
name: 'welcome',
meta: { title: '欢迎', icon: 'icon-icon-test' },
component: WelcomePage,
},
{
path: '/admins',
name: 'admins',
meta: { title: '管理页', icon: 'icon-tuijian', flat: true },
component: BlankLayout,
redirect: () => ({ name: 'page1' }),
children: [
{
path: 'page-1',
name: 'page1',
meta: { title: '一级页面' },
component: () => import('../views/admins/PageInfo.vue'),
},
{
path: 'page-2',
name: 'page2',
meta: { title: '二级页面' },
component: () => import('../views/admins/PageTypography.vue'),
},
{
path: 'dynamic-match/:id(\\d+)',
name: 'dynamic-match',
// 路由 path 默认参数再 meta.params 里
meta: { title: '动态参数页面', params: { id: 1 } },
component: () => import('../views/admins/DynamicMatch.vue'),
},
],
},
{
path: '/version',
name: 'version',
meta: { title: 'Version', icon: 'icon-antdesign' },
component: () => import('../views/Detail.vue'),
},
],
},
],
});
<template>
<page-container title="Version" sub-title="show current project dependencies">
<template #content>
<strong>Content Area</strong>
</template>
<template #extra>
<strong>Extra Area</strong>
</template>
<template #extraContent>
<strong>ExtraContent Area</strong>
</template>
<template #tags>
<a-tag>Tag1</a-tag>
<a-tag color="pink">Tag2</a-tag>
</template>
<a-card title="Project Version">
<p v-for="(dep, key) in dependencies" :key="key">
<strong style="margin-right: 12px">{{ key }}:</strong>
<a-tag>{{ dep }}</a-tag>
</p>
<p v-for="d in new Array(50)" :key="d">text block...</p>
</a-card>
</page-container>
</template>
<script lang="ts" setup>
import { dependencies } from '../../package.json';
</script>
<template>
<PageContainer>
<a-result
status="404"
:style="{
height: '100%',
background: '#fff',
}"
title="Hello World"
sub-title="Sorry, you are not authorized to access this page."
>
<template #extra>
<a-button type="primary" @click="handleClick">Back Home</a-button>
</template>
</a-result>
</PageContainer>
</template>
<script lang="ts" setup>
import { message } from 'ant-design-vue';
import { PageContainer as PageContainer } from '@ant-design-vue/pro-layout';
const handleClick = () => {
console.log('info');
message.info('BackHome button clicked!');
};
</script>
<template>
<a-layout class="layout">
<a-layout-header>
<div class="logo" />
<a-menu v-model:selectedKeys="selectedKeys" theme="dark" mode="horizontal" :style="{ lineHeight: '64px' }">
<a-menu-item key="1">nav 1</a-menu-item>
<a-menu-item key="2">nav 2</a-menu-item>
<a-menu-item key="3">nav 3</a-menu-item>
</a-menu>
</a-layout-header>
<a-layout-content style="padding: 0 50px">
<a-breadcrumb style="margin: 16px 0">
<a-breadcrumb-item>Home</a-breadcrumb-item>
<a-breadcrumb-item>List</a-breadcrumb-item>
<a-breadcrumb-item>App</a-breadcrumb-item>
</a-breadcrumb>
<div :style="{ background: '#fff', padding: '24px', minHeight: '280px' }">Content</div>
</a-layout-content>
<a-layout-footer style="text-align: center">Ant Design ©2018 Created by Ant UED</a-layout-footer>
</a-layout>
</template>
<script lang="ts" setup>
const selectedKeys = ref<string[]>(['2']);
</script>
<style scoped>
.site-layout-content {
min-height: 280px;
padding: 24px;
background: #fff;
}
#components-layout-demo-top .logo {
float: left;
width: 120px;
height: 31px;
margin: 16px 24px 16px 0;
background: rgba(255, 255, 255, 0.3);
}
.ant-row-rtl #components-layout-demo-top .logo {
float: right;
margin: 16px 0 16px 24px;
}
[data-theme='dark'] .site-layout-content {
background: #141414;
}
</style>
<template>
<page-container :title="`${route.meta.title} ${route.params.id}`">
<template #content>
<a-descriptions size="small" :column="2">
<a-descriptions-item label="创建人">张三</a-descriptions-item>
<a-descriptions-item label="联系方式">
<a>421421</a>
</a-descriptions-item>
<a-descriptions-item label="创建时间">2017-01-10</a-descriptions-item>
<a-descriptions-item label="更新时间">2017-10-10</a-descriptions-item>
<a-descriptions-item label="备注">中国浙江省杭州市西湖区古翠路</a-descriptions-item>
</a-descriptions>
</template>
<template #extra>
<a-button key="3">操作</a-button>
<a-button key="2">操作</a-button>
<a-button key="1" type="primary">主操作</a-button>
</template>
<template #extraContent>
<a-space>
<a-statistic title="Feedback" :value="1128">
<template #prefix>
<LikeOutlined />
</template>
</a-statistic>
<a-statistic title="Unmerged" :value="93" suffix="/ 100" />
</a-space>
</template>
<!-- 主内容区 -->
<div style="height: 300px">
<p>路由参数联动 分页器 组件</p>
<a-space>
<a-button type="dashed" @click="prev">跳转上一页</a-button>
<a-button type="dashed" @click="next">跳转下一页</a-button>
</a-space>
<a-divider />
<a-pagination :current="currentId" :total="total" show-less-items @change="handlePageChange" />
</div>
</page-container>
</template>
<script setup lang="ts">
import { LikeOutlined } from '@ant-design/icons-vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const currentId = computed(() => Number.parseInt(route.params.id as string, 10));
const total = computed(() => {
const v = currentId.value * 20;
if (v >= Number.MAX_SAFE_INTEGER) {
return Number.MAX_SAFE_INTEGER;
}
return v;
});
const next = () => {
router.push({
name: 'dynamic-match',
params: { id: currentId.value + 1 },
});
};
const prev = () => {
router.push({
name: 'dynamic-match',
params: { id: currentId.value > 1 ? currentId.value - 1 : 1 },
});
};
const handlePageChange = (currentPage: number) => {
router.push({
name: 'dynamic-match',
params: { id: currentPage },
});
};
</script>
<template>
<page-container :title="route.meta.title">
<template #content>
<a-descriptions size="small" :column="2">
<a-descriptions-item label="创建人">张三</a-descriptions-item>
<a-descriptions-item label="联系方式">
<a>421421</a>
</a-descriptions-item>
<a-descriptions-item label="创建时间">2017-01-10</a-descriptions-item>
<a-descriptions-item label="更新时间">2017-10-10</a-descriptions-item>
<a-descriptions-item label="备注">中国浙江省杭州市西湖区古翠路</a-descriptions-item>
</a-descriptions>
</template>
<template #extra>
<a-button key="3">操作</a-button>
<a-button key="2">操作</a-button>
<a-button key="1" type="primary">主操作</a-button>
</template>
<template #extraContent>
<a-space>
<a-statistic title="Feedback" :value="1128">
<template #prefix>
<LikeOutlined />
</template>
</a-statistic>
<a-statistic title="Unmerged" :value="93" suffix="/ 100" />
</a-space>
</template>
<!-- 主内容区 -->
<div style="height: 120vh">
<a-result
status="404"
:style="{
height: '100%',
background: '#fff',
}"
title="Hello World"
sub-title="Sorry, you are not authorized to access this page."
>
<template #extra>
<a-button type="primary">Back Home</a-button>
</template>
</a-result>
</div>
</page-container>
</template>
<script setup lang="ts">
import { LikeOutlined } from '@ant-design/icons-vue';
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
<template>
<a-card>
<a-typography>
<a-typography-title>Introduction</a-typography-title>
<a-typography-paragraph>
In the process of internal desktop applications development, many different design specs and implementations
would be involved, which might cause designers and developers difficulties and duplication and reduce the
efficiency of development.
</a-typography-paragraph>
<a-typography-paragraph>
After massive project practice and summaries, Ant Design, a design language for background applications, is
refined by Ant UED Team, which aims to
<a-typography-text strong>
uniform the user interface specs for internal background projects, lower the unnecessary cost of design
differences and implementation and liberate the resources of design and front-end development.
</a-typography-text>
</a-typography-paragraph>
<a-typography-title :level="2">Guidelines and Resources</a-typography-title>
<a-typography-paragraph>
We supply a series of design principles, practical patterns and high quality design resources (
<a-typography-text code>Sketch</a-typography-text>
and
<a-typography-text code>Axure</a-typography-text>
), to help people create their product prototypes beautifully and efficiently.
</a-typography-paragraph>
<a-typography-paragraph>
<ul>
<li>
<a-typography-link href="/docs/resources">Resource Download</a-typography-link>
</li>
</ul>
</a-typography-paragraph>
<a-typography-paragraph>
Press
<a-typography-text keyboard>Esc</a-typography-text>
to exit...
</a-typography-paragraph>
<a-divider />
<a-typography-title>介绍</a-typography-title>
<a-typography-paragraph>
蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
</a-typography-paragraph>
<a-typography-paragraph>
随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系
Ant Design。基于
<a-typography-text mark>『确定』和『自然』</a-typography-text>
的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于
<a-typography-text strong>更好的用户体验</a-typography-text>
</a-typography-paragraph>
<a-typography-title :level="2">设计资源</a-typography-title>
<a-typography-paragraph>
我们提供完善的设计原则、最佳实践和设计资源文件(
<a-typography-text code>Sketch</a-typography-text>
<a-typography-text code>Axure</a-typography-text>
),来帮助业务快速设计出高质量的产品原型。
</a-typography-paragraph>
<a-typography-paragraph>
<ul>
<li>
<a-typography-link href="/docs/resources-cn">设计资源</a-typography-link>
</li>
</ul>
</a-typography-paragraph>
<a-typography-paragraph>
<blockquote>{{ blockContent }}</blockquote>
<pre>{{ blockContent }}</pre>
</a-typography-paragraph>
<a-typography-paragraph>
<a-typography-text keyboard>Esc</a-typography-text>
键退出阅读……
</a-typography-paragraph>
</a-typography>
</a-card>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
return {
blockContent: `AntV 是蚂蚁金服全新一代数据可视化解决方案,致力于提供一套简单方便、专业可靠、不限可能的数据可视化最佳实践。得益于丰富的业务场景和用户需求挑战,AntV 经历多年积累与不断打磨,已支撑整个阿里集团内外 20000+ 业务系统,通过了日均千万级 UV 产品的严苛考验。
我们正在基础图表,图分析,图编辑,地理空间可视化,智能可视化等各个可视化的领域耕耘,欢迎同路人一起前行。`,
};
},
});
</script>
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*", "node_modules"],
"compilerOptions": {
"composite": true,
"paths": {
"@/*": ["./src/*"],
"vue-types": ["../../node_modules/vue-types"],
}
}
}
\ No newline at end of file
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"files": [],
"references": [
{
"path": "./tsconfig.vite-config.json"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.vitest.json"
}
]
}
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "package.json"],
"compilerOptions": {
"composite": true,
"types": ["node", "vitest"]
}
}
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["node", "jsdom"]
}
}
import { fileURLToPath, URL } from 'url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import PkgConfig from 'vite-plugin-package-config';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import OptimizationPersist from 'vite-plugin-optimize-persist';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
import { visualizer } from 'rollup-plugin-visualizer';
// https://vitejs.dev/config/
export default defineConfig(() => {
const lifecycle = process.env.npm_lifecycle_event;
return {
plugins: [
vue(),
vueJsx(),
AutoImport({
dts: 'src/auto-imports.d.ts',
imports: ['vue', 'vue-router'],
eslintrc: {
enabled: true,
filepath: './.eslintrc-auto-import.json',
globalsPropValue: true,
},
}),
Components({
dts: 'src/components.d.ts',
deep: true,
dirs: ['src/components'],
extensions: ['vue', 'tsx'],
resolvers: [
AntDesignVueResolver({
importStyle: false,
}),
],
}),
PkgConfig(),
OptimizationPersist(),
lifecycle === 'report' ? visualizer({ open: true, brotliSize: true, filename: 'report.html' }) : null,
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
css: {
preprocessorOptions: {
less: {
// DO NOT REMOVE THIS LINE
javascriptEnabled: true,
modifyVars: {
// hack: `true; @import 'ant-design-vue/dist/antd.variable.less'`,
// '@primary-color': '#eb2f96', // 全局主色
},
},
},
},
optimizeDeps: {
include: ['@ant-design/icons-vue', 'ant-design-vue'],
},
};
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment