提交 cbcd9098 编写于 作者: V vben

wip(menu): perf menu

上级 f81c4019
......@@ -66,8 +66,9 @@
.@{prefix-cls} {
display: flex;
align-items: center;
padding-left: 12px;
padding-left: 7px;
cursor: pointer;
transition: all 0.2s ease;
&.collapsed-show-title {
padding-left: 20px;
......
......@@ -2,6 +2,8 @@ import { withInstall } from '../util';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu'), { loading: false });
export const BasicMenu = createAsyncComponent(() => import('./src/BasicMenu.vue'), {
loading: false,
});
withInstall(BasicMenu);
import './index.less';
import type { MenuState } from './types';
import type { Menu as MenuType } from '/@/router/types';
import {
computed,
defineComponent,
unref,
reactive,
watch,
toRefs,
ComputedRef,
ref,
CSSProperties,
} from 'vue';
import { Menu } from 'ant-design-vue';
import MenuContent from './MenuContent';
// import { ScrollContainer } from '/@/components/Container';
// import { BasicArrow } from '/@/components/Basic';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { ThemeEnum } from '/@/enums/appEnum';
import { appStore } from '/@/store/modules/app';
import { useOpenKeys } from './useOpenKeys';
import { useRouter } from 'vue-router';
import { isFunction } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { menuHasChildren } from './helper';
import { getCurrentParentPath } from '/@/router/menus';
import { basicProps } from './props';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { REDIRECT_NAME } from '/@/router/constant';
import { tabStore } from '/@/store/modules/tab';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'BasicMenu',
props: basicProps,
emits: ['menuClick'],
setup(props, { slots, emit }) {
const currentParentPath = ref('');
const isClickGo = ref(false);
const menuState = reactive<MenuState>({
defaultSelectedKeys: [],
mode: props.mode,
theme: computed(() => props.theme) as ComputedRef<ThemeEnum>,
openKeys: [],
selectedKeys: [],
collapsedOpenKeys: [],
});
const { prefixCls } = useDesign('basic-menu');
const { items, mode, accordion } = toRefs(props);
const { getCollapsed, getIsHorizontal, getTopMenuAlign, getSplit } = useMenuSetting();
const { currentRoute } = useRouter();
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys(
menuState,
items,
mode,
accordion
);
const getMenuClass = computed(() => {
const { type } = props;
const { mode } = menuState;
return [
prefixCls,
`justify-${unref(getTopMenuAlign)}`,
{
[`${prefixCls}--hide-title`]: !unref(showTitle),
[`${prefixCls}--collapsed-show-title`]: props.collapsedShowTitle,
[`${prefixCls}__second`]:
!props.isHorizontal && appStore.getProjectConfig.menuSetting.split,
[`${prefixCls}__sidebar-hor`]:
type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL,
},
];
});
const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed));
const getInlineCollapseOptions = computed(() => {
const isInline = props.mode === MenuModeEnum.INLINE;
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {};
if (isInline) {
inlineCollapseOptions.inlineCollapsed = unref(getCollapsed);
}
return inlineCollapseOptions;
});
const getWrapperStyle = computed(
(): CSSProperties => {
const isHorizontal = unref(getIsHorizontal) || getSplit.value;
return {
height: isHorizontal ? '100%' : `calc(100% - ${props.showLogo ? '48px' : '0px'})`,
overflowY: isHorizontal ? 'hidden' : 'auto',
};
}
);
watch(
() => tabStore.getCurrentTab,
() => {
if (unref(currentRoute).name === REDIRECT_NAME) return;
handleMenuChange();
unref(getSplit) && getParentPath();
}
);
watch(
() => props.items,
() => {
handleMenuChange();
},
{
immediate: true,
}
);
getParentPath();
async function getParentPath() {
const { appendClass } = props;
if (!appendClass) return '';
const parentPath = await getCurrentParentPath(unref(currentRoute).path);
currentParentPath.value = parentPath;
}
async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) {
const { beforeClickFn } = props;
if (beforeClickFn && isFunction(beforeClickFn)) {
const flag = await beforeClickFn(key);
if (!flag) return;
}
emit('menuClick', key);
isClickGo.value = true;
menuState.openKeys = keyPath;
menuState.selectedKeys = [key];
}
function handleMenuChange() {
if (unref(isClickGo)) {
isClickGo.value = false;
return;
}
const path = unref(currentRoute).path;
if (menuState.mode !== MenuModeEnum.HORIZONTAL) {
setOpenKeys(path);
}
menuState.selectedKeys = [path];
}
// function renderExpandIcon({ key }: { key: string }) {
// const isOpen = getOpenKeys.value.includes(key);
// const collapsed = unref(getCollapsed);
// return (
// <BasicArrow
// expand={isOpen}
// bottom
// inset
// class={[
// `${prefixCls}__expand-icon`,
// {
// [`${prefixCls}__expand-icon--collapsed`]: collapsed,
// },
// ]}
// />
// );
// }
function renderItem(menu: MenuType, level = 1) {
return !menuHasChildren(menu) ? renderMenuItem(menu, level) : renderSubMenu(menu, level);
}
function renderMenuItem(menu: MenuType, level: number) {
const { appendClass } = props;
const isAppendActiveCls =
appendClass && level === 1 && menu.path === unref(currentParentPath);
const levelCls = [
`${prefixCls}-item__level${level}`,
` ${menuState.theme} `,
{
'top-active-menu': isAppendActiveCls,
},
];
return (
<Menu.Item key={menu.path} class={levelCls}>
{() => [
<MenuContent
item={menu}
showTitle={unref(showTitle)}
isHorizontal={props.isHorizontal}
/>,
]}
</Menu.Item>
);
}
function renderSubMenu(menu: MenuType, level: number) {
const levelCls = `${prefixCls}-item__level${level} ${menuState.theme} `;
return (
<Menu.SubMenu key={menu.path} class={levelCls}>
{{
title: () => [
<MenuContent
showTitle={unref(showTitle)}
item={menu}
isHorizontal={props.isHorizontal}
/>,
],
// expandIcon: renderExpandIcon,
default: () => (menu.children || []).map((item) => renderItem(item, level + 1)),
}}
</Menu.SubMenu>
);
}
function renderMenu() {
const { selectedKeys, defaultSelectedKeys, mode, theme } = menuState;
return (
<Menu
selectedKeys={selectedKeys}
defaultSelectedKeys={defaultSelectedKeys}
mode={mode}
openKeys={unref(getOpenKeys)}
inlineIndent={props.inlineIndent}
theme={unref(theme)}
onOpenChange={handleOpenChange}
class={unref(getMenuClass)}
onClick={handleMenuClick}
subMenuOpenDelay={0.2}
{...unref(getInlineCollapseOptions)}
>
{{
default: () => unref(items).map((item) => renderItem(item)),
}}
</Menu>
);
}
return () => {
return (
<>
{!unref(getIsHorizontal) && getSlot(slots, 'header')}
<div class={`${prefixCls}-wrapper`} style={unref(getWrapperStyle)}>
{renderMenu()}
</div>
</>
);
};
},
});
<template>
<slot name="header" v-if="!getIsHorizontal" />
<ScrollContainer :class="`${prefixCls}-wrapper`" :style="getWrapperStyle">
<Menu
:selectedKeys="selectedKeys"
:defaultSelectedKeys="defaultSelectedKeys"
:mode="mode"
:openKeys="getOpenKeys"
:inlineIndent="inlineIndent"
:theme="theme"
@openChange="handleOpenChange"
:class="getMenuClass"
@click="handleMenuClick"
:subMenuOpenDelay="0.2"
v-bind="getInlineCollapseOptions"
>
<template v-for="item in items" :key="item.path">
<BasicSubMenuItem
:item="item"
:theme="theme"
:level="1"
:appendClass="appendClass"
:parentPath="currentParentPath"
:showTitle="showTitle"
:isHorizontal="isHorizontal"
/>
</template>
</Menu>
</ScrollContainer>
</template>
<script lang="ts">
import type { MenuState } from './types';
import {
computed,
defineComponent,
unref,
reactive,
watch,
toRefs,
ref,
CSSProperties,
} from 'vue';
import { Menu } from 'ant-design-vue';
import BasicSubMenuItem from './components/BasicSubMenuItem.vue';
import { ScrollContainer } from '/@/components/Container';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { appStore } from '/@/store/modules/app';
import { useOpenKeys } from './useOpenKeys';
import { useRouter } from 'vue-router';
import { isFunction } from '/@/utils/is';
import { getCurrentParentPath } from '/@/router/menus';
import { basicProps } from './props';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { REDIRECT_NAME } from '/@/router/constant';
import { tabStore } from '/@/store/modules/tab';
import { useDesign } from '/@/hooks/web/useDesign';
// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export default defineComponent({
name: 'BasicMenu',
components: {
Menu,
ScrollContainer,
BasicSubMenuItem,
// BasicSubMenuItem: createAsyncComponent(() => import('./components/BasicSubMenuItem.vue')),
},
props: basicProps,
emits: ['menuClick'],
setup(props, { emit }) {
const currentParentPath = ref('');
const isClickGo = ref(false);
const menuState = reactive<MenuState>({
defaultSelectedKeys: [],
openKeys: [],
selectedKeys: [],
collapsedOpenKeys: [],
});
const { prefixCls } = useDesign('basic-menu');
const { items, mode, accordion } = toRefs(props);
const { getCollapsed, getIsHorizontal, getTopMenuAlign, getSplit } = useMenuSetting();
const { currentRoute } = useRouter();
const { handleOpenChange, setOpenKeys, getOpenKeys } = useOpenKeys(
menuState,
items,
mode,
accordion
);
const getMenuClass = computed(() => {
const { type, mode } = props;
return [
prefixCls,
`justify-${unref(getTopMenuAlign)}`,
{
[`${prefixCls}--hide-title`]: !unref(showTitle),
[`${prefixCls}--collapsed-show-title`]: props.collapsedShowTitle,
[`${prefixCls}__second`]:
!props.isHorizontal && appStore.getProjectConfig.menuSetting.split,
[`${prefixCls}__sidebar-hor`]:
type === MenuTypeEnum.TOP_MENU && mode === MenuModeEnum.HORIZONTAL,
},
];
});
const showTitle = computed(() => props.collapsedShowTitle && unref(getCollapsed));
const getInlineCollapseOptions = computed(() => {
const isInline = props.mode === MenuModeEnum.INLINE;
const inlineCollapseOptions: { inlineCollapsed?: boolean } = {};
if (isInline) {
inlineCollapseOptions.inlineCollapsed = unref(getCollapsed);
}
return inlineCollapseOptions;
});
const getWrapperStyle = computed(
(): CSSProperties => {
return {
height: `calc(100% - ${props.showLogo ? '48px' : '0px'})`,
overflowY: 'hidden',
};
}
);
watch(
() => tabStore.getCurrentTab,
() => {
if (unref(currentRoute).name === REDIRECT_NAME) return;
handleMenuChange();
unref(getSplit) && getParentPath();
}
);
watch(
() => props.items,
() => {
handleMenuChange();
},
{
immediate: true,
}
);
getParentPath();
async function getParentPath() {
const { appendClass } = props;
if (!appendClass) return '';
const parentPath = await getCurrentParentPath(unref(currentRoute).path);
currentParentPath.value = parentPath;
}
async function handleMenuClick({ key, keyPath }: { key: string; keyPath: string[] }) {
const { beforeClickFn } = props;
if (beforeClickFn && isFunction(beforeClickFn)) {
const flag = await beforeClickFn(key);
if (!flag) return;
}
emit('menuClick', key);
isClickGo.value = true;
menuState.openKeys = keyPath;
menuState.selectedKeys = [key];
}
function handleMenuChange() {
if (unref(isClickGo)) {
isClickGo.value = false;
return;
}
const path = unref(currentRoute).path;
if (props.mode !== MenuModeEnum.HORIZONTAL) {
setOpenKeys(path);
}
menuState.selectedKeys = [path];
}
return {
prefixCls,
getIsHorizontal,
getWrapperStyle,
handleMenuClick,
getInlineCollapseOptions,
getMenuClass,
handleOpenChange,
getOpenKeys,
currentParentPath,
showTitle,
...toRefs(menuState),
};
},
});
</script>
<style lang="less">
@import './index.less';
</style>
<template>
<MenuItem :class="getLevelClass">
<MenuContent v-bind="$props" :item="item" />
</MenuItem>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { Menu } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { itemProps } from '../props';
import MenuContent from '../MenuContent';
export default defineComponent({
name: 'BasicMenuItem',
components: { MenuItem: Menu.Item, MenuContent },
props: itemProps,
setup(props) {
const { prefixCls } = useDesign('basic-menu-item');
const getLevelClass = computed(() => {
const { appendClass, level, item, parentPath, theme } = props;
const isAppendActiveCls = appendClass && level === 1 && item.path === parentPath;
const levelCls = [
`${prefixCls}__level${level}`,
theme,
{
'top-active-menu': isAppendActiveCls,
},
];
return levelCls;
});
return {
prefixCls,
getLevelClass,
};
},
});
</script>
<template>
<BasicMenuItem v-if="!menuHasChildren(item)" v-bind="$props" />
<SubMenu v-else :class="[`${prefixCls}__level${level}`, theme]">
<template #title>
<MenuContent v-bind="$props" :item="item" />
</template>
<!-- <template #expandIcon="{ key }">
<ExpandIcon :key="key" />
</template> -->
<template v-for="childrenItem in item.children || []" :key="childrenItem.path">
<BasicSubMenuItem v-bind="$props" :item="childrenItem" :level="level + 1" />
</template>
</SubMenu>
</template>
<script lang="ts">
import type { Menu as MenuType } from '/@/router/types';
import { defineComponent } from 'vue';
import { Menu } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { itemProps } from '../props';
import BasicMenuItem from './BasicMenuItem.vue';
import MenuContent from '../MenuContent';
// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export default defineComponent({
name: 'BasicSubMenuItem',
components: {
BasicMenuItem,
SubMenu: Menu.SubMenu,
MenuItem: Menu.Item,
MenuContent,
// ExpandIcon: createAsyncComponent(() => import('./ExpandIcon.vue')),
},
props: itemProps,
setup() {
const { prefixCls } = useDesign('basic-menu-item');
function menuHasChildren(menuTreeItem: MenuType): boolean {
return (
Reflect.has(menuTreeItem, 'children') &&
!!menuTreeItem.children &&
menuTreeItem.children.length > 0
);
}
return {
prefixCls,
menuHasChildren,
};
},
});
</script>
<template>
<BasicArrow :expand="getIsOpen" bottom inset :class="getWrapperClass" />
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { BasicArrow } from '/@/components/Basic';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'BasicMenuItem',
components: { BasicArrow },
props: {
key: propTypes.string,
openKeys: {
type: Array as PropType<string[]>,
default: [],
},
collapsed: propTypes.bool,
},
setup(props) {
const { prefixCls } = useDesign('basic-menu');
const getIsOpen = computed(() => {
return props.openKeys.includes(props.key);
});
const getWrapperClass = computed(() => {
return [
`${prefixCls}__expand-icon`,
{
[`${prefixCls}__expand-icon--collapsed`]: props.collapsed,
},
];
});
return {
prefixCls,
getIsOpen,
getWrapperClass,
};
},
});
</script>
import type { Menu as MenuType } from '/@/router/types';
/**
* @description: Whether the menu has child nodes
*/
export function menuHasChildren(menuTreeItem: MenuType): boolean {
return (
Reflect.has(menuTreeItem, 'children') &&
!!menuTreeItem.children &&
menuTreeItem.children.length > 0
);
}
......@@ -4,6 +4,7 @@
.active-style() {
color: @white;
// background: @primary-color !important;
background: linear-gradient(
118deg,
rgba(@primary-color, 0.8),
......@@ -27,6 +28,7 @@
// right: 16px;
// width: 10px;
// transform-origin: none;
// opacity: 0.45;
// span[role='img'] {
// margin-right: 0;
......@@ -52,9 +54,9 @@
> .ant-menu-item-group-list
> .ant-menu-submenu
> .ant-menu-submenu-title,
&.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title {
padding-right: 20px !important;
padding-left: 20px !important;
&.ant-menu-inline-collapsed .ant-menu-submenu-title {
padding-right: 16px !important;
padding-left: 16px !important;
}
}
......@@ -87,32 +89,33 @@
}
}
// .ant-menu-item {
// transition: unset;
// }
.ant-menu-item {
transition: unset;
}
// scrollbar -s tart
&-wrapper {
/* 滚动槽 */
&::-webkit-scrollbar {
width: 5px;
height: 5px;
}
// &-wrapper {
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0);
}
/* 滚动槽 */
// &::-webkit-scrollbar {
// width: 5px;
// height: 5px;
// }
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
}
// &::-webkit-scrollbar-track {
// background: rgba(0, 0, 0, 0);
// }
::-webkit-scrollbar-thumb:hover {
background: @border-color-dark;
}
}
// &::-webkit-scrollbar-thumb {
// background: rgba(255, 255, 255, 0.2);
// border-radius: 3px;
// box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
// }
// ::-webkit-scrollbar-thumb:hover {
// background: @border-color-dark;
// }
// }
// scrollbar end
......@@ -225,14 +228,6 @@
}
}
&:not(.@{basic-menu-prefix-cls}__sidebar-hor).ant-menu-inline-collapsed {
.@{basic-menu-prefix-cls}-item__level1 {
> div {
align-items: center;
}
}
}
&.ant-menu-dark:not(.@{basic-menu-prefix-cls}__sidebar-hor):not(.@{basic-menu-prefix-cls}__second) {
// Reset menu item row height
.ant-menu-item:not(.@{basic-menu-prefix-cls}-item__level1),
......@@ -255,10 +250,6 @@
background: @sider-dark-bg-color;
.active-menu-style();
// .menu-item-icon.app-iconify {
// display: inline-block !important;
// }
.ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
.ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
color: @white;
......@@ -304,10 +295,6 @@
overflow: hidden;
border-right: none;
// .menu-item-icon.app-iconify {
// display: inline-block !important;
// }
.ant-menu-item.ant-menu-item-selected.@{basic-menu-prefix-cls}-menu-item__level1,
.ant-menu-submenu-selected.@{basic-menu-prefix-cls}-menu-item__level1 {
color: @primary-color;
......@@ -332,6 +319,7 @@
align-items: center;
}
}
.@{basic-menu-prefix-cls}__tag {
position: absolute;
top: calc(50% - 8px);
......@@ -368,6 +356,20 @@
background: @warning-color;
}
}
.ant-menu-submenu,
.ant-menu-submenu-inline {
transition: unset;
}
// .ant-menu-submenu-arrow {
// transition: all 0.15s ease 0s;
// }
.ant-menu-inline.ant-menu-sub {
box-shadow: unset !important;
transition: unset;
}
}
.ant-menu-dark {
......@@ -375,7 +377,6 @@
> ul {
background: @sider-dark-bg-color;
}
.active-menu-style();
}
}
......
......@@ -3,57 +3,47 @@ import type { PropType } from 'vue';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
import { ThemeEnum } from '/@/enums/appEnum';
import { propTypes } from '/@/utils/propTypes';
export const basicProps = {
items: {
type: Array as PropType<Menu[]>,
default: () => [],
},
appendClass: {
type: Boolean as PropType<boolean>,
default: false,
},
appendClass: propTypes.bool,
collapsedShowTitle: {
type: Boolean as PropType<boolean>,
default: false,
},
collapsedShowTitle: propTypes.bool,
// 最好是4 倍数
inlineIndent: {
type: Number as PropType<number>,
default: 20,
},
inlineIndent: propTypes.number.def(20),
// 菜单组件的mode属性
mode: {
type: String as PropType<MenuModeEnum>,
default: MenuModeEnum.INLINE,
},
showLogo: {
type: Boolean as PropType<boolean>,
default: false,
},
showLogo: propTypes.bool,
type: {
type: String as PropType<MenuTypeEnum>,
default: MenuTypeEnum.MIX,
},
theme: {
type: String as PropType<string>,
default: ThemeEnum.DARK,
},
inlineCollapsed: {
type: Boolean as PropType<boolean>,
default: false,
},
theme: propTypes.string.def(ThemeEnum.DARK),
inlineCollapsed: propTypes.bool,
isHorizontal: {
type: Boolean as PropType<boolean>,
default: false,
},
accordion: {
type: Boolean as PropType<boolean>,
default: true,
},
isHorizontal: propTypes.bool,
accordion: propTypes.bool.def(true),
beforeClickFn: {
type: Function as PropType<(key: string) => Promise<boolean>>,
},
};
export const itemProps = {
item: {
type: Object as PropType<Menu>,
default: {},
},
level: propTypes.number,
theme: propTypes.oneOf(['dark', 'light']),
appendClass: propTypes.bool,
parentPath: propTypes.string,
showTitle: propTypes.bool,
isHorizontal: propTypes.bool,
};
import { ComputedRef } from 'vue';
import { ThemeEnum } from '/@/enums/appEnum';
import { MenuModeEnum } from '/@/enums/menuEnum';
// import { ComputedRef } from 'vue';
// import { ThemeEnum } from '/@/enums/appEnum';
// import { MenuModeEnum } from '/@/enums/menuEnum';
export interface MenuState {
// 默认选中的列表
defaultSelectedKeys: string[];
// 模式
mode: MenuModeEnum;
// mode: MenuModeEnum;
// 主题
theme: ComputedRef<ThemeEnum> | ThemeEnum;
// // 主题
// theme: ComputedRef<ThemeEnum> | ThemeEnum;
// 缩进
inlineIndent?: number;
......
export const SIDE_BAR_MINI_WIDTH = 58;
export const SIDE_BAR_MINI_WIDTH = 48;
export const SIDE_BAR_SHOW_TIT_MINI_WIDTH = 80;
export enum ContentEnum {
......
......@@ -34,9 +34,9 @@
border: 1px solid darken(@border-color-light, 6%);
transition: none;
&:not(.ant-tabs-tab-active)::before {
&:not(.ant-tabs-tab-active)::after {
position: absolute;
top: -1px;
bottom: -1px;
left: 50%;
width: 100%;
height: 2px;
......@@ -53,7 +53,7 @@
opacity: 1;
}
&:not(.ant-tabs-tab-active)::before {
&:not(.ant-tabs-tab-active)::after {
opacity: 1;
transform: translate(-50%, 0) scaleX(1);
transition: all 0.3s ease-in-out;
......
......@@ -42,6 +42,7 @@ export function transformMenuModule(menuModule: MenuModule): Menu {
forEach(menuList, (m) => {
!isUrl(m.path) && joinParentPath(menuList, m);
});
return menuList[0];
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册