wip: kingdee style menu

This commit is contained in:
zzs 2025-02-13 11:43:54 +08:00
parent 33c1df8f40
commit cbf3f03d46
20 changed files with 778 additions and 304 deletions

4
.env
View File

@ -2,10 +2,10 @@
VITE_PORT = 80
# 网站标题
VITE_GLOB_APP_TITLE = 芋道管理系统
VITE_GLOB_APP_TITLE = 云易贸WEB平台
# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符
VITE_GLOB_APP_SHORT_NAME = Yudao_Admin
VITE_GLOB_APP_SHORT_NAME = YEM_WEB
# 租户开关
VITE_GLOB_APP_TENANT_ENABLE = true

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ import appSearch from './src/search/AppSearch.vue'
import appSizePicker from './src/AppSizePicker.vue'
import appLocalePicker from './src/AppLocalePicker.vue'
import appDarkModeToggle from './src/AppDarkModeToggle.vue'
import appPopMenu from './src/AppPopMenu.vue'
import { withInstall } from '@/utils'
export { useAppProviderContext } from './src/useAppContext'
@ -14,3 +15,4 @@ export const AppSearch = withInstall(appSearch)
export const AppSizePicker = withInstall(appSizePicker)
export const AppLocalePicker = withInstall(appLocalePicker)
export const AppDarkModeToggle = withInstall(appDarkModeToggle)
export const AppPopMenu = withInstall(appPopMenu)

View File

@ -0,0 +1,98 @@
<script lang="ts" setup>
import { computed, ref, toRef, unref } from 'vue'
// import { useGlobSetting } from '@/hooks/setting'
import { AppstoreOutlined } from '@ant-design/icons-vue'
import { Drawer, Flex } from 'ant-design-vue'
import { primaryColor } from '../../../../build/config/themeConfig'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { useDesign } from '@/hooks/web/useDesign'
import { useSplitMenu } from '@/layouts/default/menu/useLayoutMenu'
import { propTypes } from '@/utils/propTypes'
import type { MenuModeEnum } from '@/enums/menuEnum'
import { MenuSplitTyeEnum } from '@/enums/menuEnum'
import { Icon } from '@/components/Icon'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import type { Menu } from '@/router/types'
// import { PageEnum } from '@/enums/pageEnum'
const props = defineProps({
theme: propTypes.oneOf(['light', 'dark']),
splitType: {
type: Number as PropType<MenuSplitTyeEnum>,
default: MenuSplitTyeEnum.NONE,
},
isHorizontal: propTypes.bool,
// menu Mode
menuMode: {
type: [String] as PropType<Nullable<MenuModeEnum>>,
default: '',
},
})
const { prefixCls } = useDesign('app-logo')
const { getCollapsedShowTitle } = useMenuSetting()
// const { title } = useGlobSetting()
const go = useGo()
const { t } = useI18n()
const getAppLogoClass = computed(() => [prefixCls, props.theme, { 'collapsed-show-title': unref(getCollapsedShowTitle) }])
const open = ref<boolean>(false)
const { menusRef } = useSplitMenu(toRef(props, 'splitType'))
function getMenuTitle(title: string | undefined) {
if (!title)
return ''
if (title.includes('.'))
return t(title)
return title
}
function handleNavTo(item: Menu) {
function getDeepestPath(menu: Menu): string {
if (menu.children && menu.children.length > 0)
return getDeepestPath(menu.children[0])
return menu.path
}
const targetPath = getDeepestPath(item)
go(targetPath)
open.value = !open.value
}
</script>
<template>
<div class="anticon" :class="getAppLogoClass">
<div>
<div class="h-full w-full rounded p-2 transition-all hover:bg-gray-200 dark:hover:bg-gray-700" :style="{ color: primaryColor }">
<AppstoreOutlined class="font-size-5" @click="open = !open" />
<Drawer v-model:open="open" placement="left" width="40%" class="mt-48px" :mask-style="{ opacity: 0 }" :closable="false">
<Flex wrap="wrap" gap="small">
<div v-for="(item) in menusRef" :key="item.path" @click="handleNavTo(item)">
<div v-if="!item.hideMenu" class="flex flex-col items-center justify-center">
<div class="m-2 aspect-ratio-square h-14 w-14 flex flex-row items-center justify-center rounded-md p-2" :style="{ backgroundColor: primaryColor }">
<Icon :icon="item.icon" :size="20" color="white" />
</div>
<span>{{ getMenuTitle(item.meta?.title) }}</span>
</div>
</div>
</Flex>
</Drawer>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,3 @@
import KdMenuItem from './src/KdMenuItem.vue'
export { KdMenuItem }

View File

@ -0,0 +1,39 @@
<script setup lang="ts">
import { defineProps } from 'vue'
import { Button } from 'ant-design-vue'
import { useGo } from '@/hooks/web/usePage'
const props = defineProps({
menu: {
type: Object,
required: true,
},
level: {
type: Number,
default: 0,
},
})
const go = useGo()
</script>
<template>
<div class="menu-item">
<Button @click="go(props.menu.path)">
{{ props.menu.meta?.title || props.menu.name }}
</Button>
<!-- <div v-if="props.menu.children && menu.children.length"> -->
<!-- <template v-for="child in props.menu.children" :key="child.id"> -->
<!-- <KdMenuItem :menu="child" :level="level + 1" /> -->
<!-- </template> -->
<!-- </div> -->
</div>
</template>
<style scoped>
.menu-item {
display: flex;
gap: 8px;
align-items: center;
}
</style>

View File

@ -10,6 +10,9 @@ export enum MenuTypeEnum {
MIX = 'mix',
// top menu
TOP_MENU = 'top-menu',
KINGDEE_NEW = 'kingdee-new',
KINGDEE_OLD = 'kingdee-old',
}
// 折叠触发器位置

View File

@ -114,6 +114,11 @@ export function useMenuSetting() {
collapsed: !unref(getCollapsed),
})
}
const getIsKingdeeNew = computed(() => {
return unref(getMenuType) === MenuTypeEnum.KINGDEE_NEW
})
return {
setMenuSetting,
@ -149,5 +154,6 @@ export function useMenuSetting() {
getMixSideTrigger,
getMixSideFixed,
mixSideHasChildren,
getIsKingdeeNew,
}
}

View File

@ -1,6 +1,5 @@
<script lang="ts" setup>
import { FloatButton } from 'ant-design-vue'
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
import { computed, unref } from 'vue'
import { SettingButtonPositionEnum } from '@/enums/appEnum'
@ -9,8 +8,6 @@ import { useRootSetting } from '@/hooks/setting/useRootSetting'
import { useUserStoreWithOut } from '@/store/modules/user'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
import SessionTimeoutLogin from '@/views/base/login/SessionTimeoutLogin.vue'
import { openWindow } from '@/utils'
import { SITE_URL } from '@/settings/siteSetting'
defineOptions({ name: 'LayoutFeatures' })
const LayoutLockPage = createAsyncComponent(() => import('@/views/base/lock/index.vue'))
@ -41,17 +38,17 @@ const getIsFixedSettingDrawer = computed(() => {
<template>
<LayoutLockPage />
<FloatButton.BackTop v-if="getUseOpenBackTop" :target="getTarget" />
<FloatButton
shape="circle"
type="primary"
:badge="{ dot: true }"
:style="{ right: '64px' }"
@click="openWindow(SITE_URL)"
>
<template #icon>
<QuestionCircleOutlined />
</template>
</FloatButton>
<!-- <FloatButton -->
<!-- shape="circle" -->
<!-- type="primary" -->
<!-- :badge="{ dot: true }" -->
<!-- :style="{ right: '64px' }" -->
<!-- @click="openWindow(SITE_URL)" -->
<!-- > -->
<!-- <template #icon> -->
<!-- <QuestionCircleOutlined /> -->
<!-- </template> -->
<!-- </FloatButton> -->
<SettingDrawer
v-if="getIsFixedSettingDrawer"
class="absolute top-[45%] z-10 flex cursor-pointer items-center justify-items-center rounded-l-md rounded-r-none p-2.5"

View File

@ -0,0 +1,146 @@
<script lang="ts" setup>
import { computed, unref } from 'vue'
import { Layout } from 'ant-design-vue'
import { ErrorAction, FullScreen, LayoutBreadcrumb, Notify, UserDropDown } from '../../components'
import LayoutTrigger from '../../../trigger/index.vue'
import { propTypes } from '@/utils/propTypes'
// import { AppLocalePicker, AppLogo, AppSearch, AppSizePicker } from '@/components/Application'
import {
AppLocalePicker,
AppLogo,
AppPopMenu,
AppSearch,
AppSizePicker,
} from '@/components/Application'
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { useRootSetting } from '@/hooks/setting/useRootSetting'
// import { MenuModeEnum, MenuSplitTyeEnum } from '@/enums/menuEnum'
import { SettingButtonPositionEnum } from '@/enums/appEnum'
import { useAppInject } from '@/hooks/web/useAppInject'
import { useDesign } from '@/hooks/web/useDesign'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
import { useLocale } from '@/locales/useLocale'
defineOptions({ name: 'LayoutHeader' })
const props = defineProps({
fixed: propTypes.bool,
})
const Header = Layout.Header
const SettingDrawer = createAsyncComponent(() => import('@/layouts/default/setting/index.vue'), {
loading: true,
})
const { prefixCls } = useDesign('layout-header')
const { getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting()
// const { getIsMixMode, getMenuWidth } = useMenuSetting()
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting()
const { getHeaderTheme, getShowFullScreen, getShowNotice, getShowContent, getShowBread, getShowHeaderLogo, getShowHeader, getShowSearch }
= useHeaderSetting()
const { getShowLocalePicker } = useLocale()
const { getIsMobile } = useAppInject()
const getHeaderClass = computed(() => {
const theme = unref(getHeaderTheme)
return [
prefixCls,
{
[`${prefixCls}--fixed`]: props.fixed,
[`${prefixCls}--mobile`]: unref(getIsMobile),
[`${prefixCls}--${theme}`]: theme,
},
]
})
const getShowSetting = computed(() => {
if (!unref(getShowSettingButton))
return false
const settingButtonPosition = unref(getSettingButtonPosition)
if (settingButtonPosition === SettingButtonPositionEnum.AUTO)
return unref(getShowHeader)
return settingButtonPosition === SettingButtonPositionEnum.HEADER
})
const getLogoWidth = computed(() => {
if (!unref(getIsMixMode) || unref(getIsMobile))
return {}
const width = unref(getMenuWidth) < 180 ? 180 : unref(getMenuWidth)
return { width: `${width}px` }
})
// const getSplitType = computed(() => {
// return unref(getSplit) ? MenuSplitTyeEnum.TOP : MenuSplitTyeEnum.NONE
// })
//
// const getMenuMode = computed(() => {
// return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null
// })
</script>
<template>
<Header :class="getHeaderClass">
<!-- left start -->
<div :class="`${prefixCls}-left`">
<!-- logo -->
<!-- <AppPopMenu -->
<!-- v-if="getShowHeaderLogo || getIsMobile" :class="`${prefixCls}-logo`" :theme="getHeaderTheme" -->
<!-- :style="getLogoWidth" -->
<!-- /> -->
<AppPopMenu />
<AppLogo
v-if="getShowHeaderLogo || getIsMobile" :class="`${prefixCls}-logo`" :theme="getHeaderTheme"
:style="getLogoWidth"
/>
<LayoutTrigger
v-if="(getShowContent && getShowHeaderTrigger && !getSplit && !getIsMixSidebar) || getIsMobile"
:theme="getHeaderTheme" :sider="false"
/>
<LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" />
</div>
<!-- left end -->
<!-- menu start -->
<!-- <div v-if="getShowTopMenu && !getIsMobile" :class="`${prefixCls}-menu`"> -->
<!-- <LayoutMenu :is-horizontal="true" :theme="getHeaderTheme" :split-type="getSplitType" :menu-mode="getMenuMode" /> -->
<!-- </div> -->
<!-- menu-end -->
<!-- action -->
<div :class="`${prefixCls}-action`">
<AppSearch v-if="getShowSearch" :class="`${prefixCls}-action__item search-item`" />
<ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" />
<Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
<FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
<AppSizePicker :show-text="false" :class="`${prefixCls}-action__item size-item`" />
<AppLocalePicker
v-if="getShowLocalePicker" :reload="true" :show-text="false"
:class="`${prefixCls}-action__item locale-item`"
/>
<UserDropDown :theme="getHeaderTheme" />
<SettingDrawer v-if="getShowSetting" :class="`${prefixCls}-action__item`" />
</div>
</Header>
</template>
<style lang="less">
@import '../../index.less';
</style>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
</script>
<template>
<div>
todo old style
</div>
</template>
<style scoped lang="less">
</style>

View File

@ -16,6 +16,7 @@ import { useLockPage } from '@/hooks/web/useLockPage'
import { useAppInject } from '@/hooks/web/useAppInject'
import { useMultipleTabSetting } from '@/hooks/setting/useMultipleTabSetting'
import KingdeeNewHeader from '/src/layouts/default/header/kingdee/new/KingdeeNewHeader.vue'
defineOptions({ name: 'DefaultLayout' })
@ -25,7 +26,7 @@ const LayoutFooter = createAsyncComponent(() => import('@/layouts/default/footer
const { prefixCls } = useDesign('default-layout')
const { getIsMobile } = useAppInject()
const { getShowFullHeaderRef } = useHeaderSetting()
const { getShowSidebar, getIsMixSidebar, getShowMenu } = useMenuSetting()
const { getShowSidebar, getIsMixSidebar, getShowMenu, getIsKingdeeNew } = useMenuSetting()
const { getAutoCollapse } = useMultipleTabSetting()
// Create a lock screen monitor
@ -46,9 +47,10 @@ const layoutClass = computed(() => {
<template>
<Layout :class="prefixCls" v-bind="lockEvents">
<LayoutFeatures />
<LayoutHeader v-if="getShowFullHeaderRef" fixed />
<KingdeeNewHeader v-if="getIsKingdeeNew" fixed />
<LayoutHeader v-if="getShowFullHeaderRef && !getIsKingdeeNew" fixed />
<Layout :class="[layoutClass, `${prefixCls}-out`]">
<LayoutSideBar v-if="getShowSidebar || getIsMobile" />
<LayoutSideBar v-if="(getShowSidebar || getIsMobile)" />
<Layout :class="`${prefixCls}-main`">
<LayoutMultipleHeader />
<LayoutContent />

View File

@ -138,6 +138,16 @@ export const menuTypeList = [
mode: MenuModeEnum.INLINE,
type: MenuTypeEnum.MIX_SIDEBAR,
},
{
title: t('layout.setting.menuTypeKingdeeNew'),
mode: MenuModeEnum.INLINE,
type: MenuTypeEnum.KINGDEE_NEW,
},
// {
// title: t('layout.setting.menuTypeKingdeeOld'),
// mode: MenuModeEnum.INLINE,
// type: MenuTypeEnum.KINGDEE_OLD,
// },
]
export const mixSidebarTriggerOptions = [

View File

@ -0,0 +1,149 @@
<script lang="ts" setup>
import type { CSSProperties } from 'vue'
import { computed, h, ref, unref } from 'vue'
import { Layout } from 'ant-design-vue'
import LayoutMenu from '../menu/index.vue'
import { useDragLine, useSiderEvent, useTrigger } from './useLayoutSider'
import DragBar from './DragBar.vue'
import LayoutTrigger from '@/layouts/default/trigger/index.vue'
import { MenuModeEnum, MenuSplitTyeEnum } from '@/enums/menuEnum'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { useAppInject } from '@/hooks/web/useAppInject'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'LayoutSideBar' })
const Sider = Layout.Sider
const dragBarRef = ref<ElRef>(null)
const sideRef = ref<ElRef>(null)
const { getCollapsed, getMenuWidth, getSplit, getMenuTheme, getRealWidth, getMenuHidden, getMenuFixed, getIsMixMode } = useMenuSetting()
const { prefixCls } = useDesign('layout-kingdee-sideBar')
const { getIsMobile } = useAppInject()
const { getTriggerAttr, getShowTrigger } = useTrigger(getIsMobile)
useDragLine(sideRef, dragBarRef)
const { getCollapsedWidth, onBreakpointChange } = useSiderEvent()
const getMode = computed(() => {
return unref(getSplit) ? MenuModeEnum.INLINE : null
})
const getSplitType = computed(() => {
return unref(getSplit) ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE
})
const showClassSideBarRef = computed(() => {
return unref(getSplit) ? !unref(getMenuHidden) : true
})
const getSiderClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--fixed`]: unref(getMenuFixed),
[`${prefixCls}--mix`]: unref(getIsMixMode) && !unref(getIsMobile),
},
]
})
const getHiddenDomStyle = computed((): CSSProperties => {
const width = `${unref(getRealWidth)}px`
return {
width,
overflow: 'hidden',
flex: `0 0 ${width}`,
maxWidth: width,
minWidth: width,
transition: 'all 0.2s',
}
})
// 使sider
// andv trigger
const getTrigger = h(LayoutTrigger)
</script>
<template>
<div v-if="getMenuFixed && !getIsMobile" v-show="showClassSideBarRef" :style="getHiddenDomStyle" />
<Sider
v-show="showClassSideBarRef"
ref="sideRef"
breakpoint="lg"
collapsible
:class="getSiderClass"
:width="getMenuWidth"
:collapsed="getCollapsed"
:collapsed-width="getCollapsedWidth"
:theme="getMenuTheme"
:trigger="getTrigger"
v-bind="getTriggerAttr"
@breakpoint="onBreakpointChange"
>
<template v-if="getShowTrigger" #trigger>
<LayoutTrigger />
</template>
<LayoutMenu :theme="getMenuTheme" :menu-mode="getMode" :split-type="getSplitType" />
<DragBar ref="dragBarRef" />
</Sider>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-layout-kingdee-sideBar';
.@{prefix-cls} {
z-index: @layout-sider-fixed-z-index;
&--fixed {
position: fixed !important;
top: 48px;
left: 0;
height: 100%;
}
&--mix {
top: @header-height;
height: calc(100% - @header-height);
}
&.ant-layout-sider-dark {
background-color: @sider-dark-bg-color;
.ant-layout-sider-trigger {
color: darken(@white, 25%);
background-color: @trigger-dark-bg-color;
&:hover {
color: @white;
background-color: @trigger-dark-hover-bg-color;
}
}
}
&:not(.ant-layout-sider-dark) {
// box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
.ant-layout-sider-trigger {
border-top: 1px solid var(--border-color);
}
}
.ant-layout-sider-zero-width-trigger {
top: 40%;
z-index: 10;
}
& .ant-layout-sider-trigger {
height: 36px;
line-height: 36px;
}
}
</style>

View File

@ -7,11 +7,13 @@ import { useAppInject } from '@/hooks/web/useAppInject'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { useDesign } from '@/hooks/web/useDesign'
import KingdeeSider from '@/layouts/default/sider/KingdeeSider.vue'
defineOptions({ name: 'SiderWrapper' })
const { prefixCls } = useDesign('layout-sider-wrapper')
const { getIsMobile } = useAppInject()
const { setMenuSetting, getCollapsed, getMenuWidth, getIsMixSidebar } = useMenuSetting()
const { setMenuSetting, getCollapsed, getMenuWidth, getIsMixSidebar, getIsKingdeeNew } = useMenuSetting()
function handleClose() {
setMenuSetting({
@ -32,6 +34,7 @@ function handleClose() {
>
<Sider />
</Drawer>
<KingdeeSider v-else-if="getIsKingdeeNew" />
<MixSider v-else-if="getIsMixSidebar" />
<Sider v-else />
</template>

View File

@ -64,6 +64,8 @@
"menuTypeMixSidebar": "Left menu mixed mode",
"menuTypeSidebar": "Left menu mode",
"menuTypeTopMenu": "Top menu mode",
"menuTypeKingdeeNew": "Kingdee like new mode",
"menuTypeKingdeeOld": "Kingdee like old mode",
"minute": "Minute",
"mixSidebarFixed": "Fixed expanded menu",
"mixSidebarTrigger": "Mixed menu Trigger",

View File

@ -64,6 +64,8 @@
"menuTypeMixSidebar": "左侧菜单混合模式",
"menuTypeSidebar": "左侧菜单模式",
"menuTypeTopMenu": "顶部菜单模式",
"menuTypeKingdeeNew": "金蝶新版模式",
"menuTypeKingdeeOld": "金蝶旧版模式",
"minute": "分钟",
"mixSidebarFixed": "固定展开菜单",
"mixSidebarTrigger": "混合菜单触发方式",

View File

@ -81,9 +81,9 @@
"registerButton": "注册",
"rememberMe": "记住我",
"scanSign": "扫码后点击\"确认\",即可完成登录",
"signInDesc": "输入您的个人详细信息开始使用!",
"signInDesc": "欢迎使用!",
"signInFormTitle": "登录",
"signInTitle": "开箱即用的中后台管理系统",
"signInTitle": "云易贸WEB平台",
"signUpFormTitle": "注册",
"smsCode": "短信验证码",
"smsPlaceholder": "请输入验证码",

View File

@ -50,7 +50,7 @@ const setting: ProjectConfig = {
// 是否显示logo
showLogo: true,
// 是否显示底部信息 copyright
showFooter: true,
showFooter: false,
// 头部配置
headerSetting: {
// 背景色
@ -95,7 +95,7 @@ const setting: ProjectConfig = {
// 菜单模式
mode: MenuModeEnum.INLINE,
// 菜单类型
type: MenuTypeEnum.SIDEBAR,
type: MenuTypeEnum.MIX_SIDEBAR,
// 菜单主题
theme: ThemeEnum.DARK,
// 分割菜单

View File

@ -1,8 +1,7 @@
<script lang="ts" setup>
import { computed, reactive, ref, unref } from 'vue'
import { Checkbox, Col, Divider, Form, Input, Row } from 'ant-design-vue'
import { AlipayCircleFilled, GithubFilled, WechatFilled } from '@ant-design/icons-vue'
import { Checkbox, Col, Form, Input, Row } from 'ant-design-vue'
import LoginFormTitle from './LoginFormTitle.vue'
import { LoginStateEnum, useFormRules, useFormValid, useLoginState } from './useLogin'
@ -194,36 +193,36 @@ async function handleLogin(params) {
</Col>
</Row>
<Divider class="enter-x">
{{ t('sys.login.otherSignIn') }}
</Divider>
<!-- <Divider class="enter-x"> -->
<!-- {{ t('sys.login.otherSignIn') }} -->
<!-- </Divider> -->
<div class="enter-x flex justify-evenly" :class="`${prefixCls}-sign-in-way`">
<GithubFilled />
<WechatFilled />
<AlipayCircleFilled />
<!-- <GoogleCircleFilled /> -->
<!-- <TwitterCircleFilled /> -->
</div>
<!-- <div class="enter-x flex justify-evenly" :class="`${prefixCls}-sign-in-way`"> -->
<!-- <GithubFilled /> -->
<!-- <WechatFilled /> -->
<!-- <AlipayCircleFilled /> -->
<!-- &lt;!&ndash; <GoogleCircleFilled /> &ndash;&gt; -->
<!-- &lt;!&ndash; <TwitterCircleFilled /> &ndash;&gt; -->
<!-- </div> -->
<!-- 萌新必读 -->
<Divider class="enter-x">
萌新必读
</Divider>
<div class="enter-x flex justify-evenly" :class="`${prefixCls}-sign-in-way`">
<a-button href="https://doc.iocoder.cn/" target="_blank" class="w-1/4">
📚开发指南
</a-button>
<a-button href="https://doc.iocoder.cn/video/" target="_blank" class="w-1/4 pl-1">
🔥视频教程
</a-button>
<a-button href="https://www.iocoder.cn/Interview/good-collection/" target="_blank" class="w-1/4 pl-1">
面试手册
</a-button>
<a-button href="http://static.yudao.iocoder.cn/mp/xinyu370.jpeg" target="_blank" class="w-1/4 pl-1">
🤝外包咨询
</a-button>
</div>
<!-- &lt;!&ndash; 萌新必读 &ndash;&gt; -->
<!-- <Divider class="enter-x"> -->
<!-- 萌新必读 -->
<!-- </Divider> -->
<!-- <div class="enter-x flex justify-evenly" :class="`${prefixCls}-sign-in-way`"> -->
<!-- <a-button href="https://doc.iocoder.cn/" target="_blank" class="w-1/4"> -->
<!-- 📚开发指南 -->
<!-- </a-button> -->
<!-- <a-button href="https://doc.iocoder.cn/video/" target="_blank" class="w-1/4 pl-1"> -->
<!-- 🔥视频教程 -->
<!-- </a-button> -->
<!-- <a-button href="https://www.iocoder.cn/Interview/good-collection/" target="_blank" class="w-1/4 pl-1"> -->
<!-- 面试手册 -->
<!-- </a-button> -->
<!-- <a-button href="http://static.yudao.iocoder.cn/mp/xinyu370.jpeg" target="_blank" class="w-1/4 pl-1"> -->
<!-- 🤝外包咨询 -->
<!-- </a-button> -->
<!-- </div> -->
</Form>
<Verify ref="verify" mode="pop" :captcha-type="captchaType" :img-size="{ width: '360px', height: '180px' }" @success="handleLogin" />
</template>