Skip to content
6 changes: 3 additions & 3 deletions apps/backend-mock/utils/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export const MOCK_MENU_LIST = [
children: [
{
id: 20_401,
pid: 201,
pid: 202,
name: 'SystemDeptCreate',
status: 1,
type: 'button',
Expand All @@ -285,7 +285,7 @@ export const MOCK_MENU_LIST = [
},
{
id: 20_402,
pid: 201,
pid: 202,
name: 'SystemDeptEdit',
status: 1,
type: 'button',
Expand All @@ -294,7 +294,7 @@ export const MOCK_MENU_LIST = [
},
{
id: 20_403,
pid: 201,
pid: 202,
name: 'SystemDeptDelete',
status: 1,
type: 'button',
Expand Down
10 changes: 5 additions & 5 deletions packages/@core/base/design/src/css/ui.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.side-content {
animation-duration: 0.2s;
animation-duration: 0.3s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

Expand Down Expand Up @@ -37,7 +37,7 @@
@keyframes slide-down {
from {
opacity: 0;
transform: translateY(-10px);
transform: translateY(50px);
}

to {
Expand All @@ -49,7 +49,7 @@
@keyframes slide-left {
from {
opacity: 0;
transform: translateX(-10px);
transform: translateX(-50px);
}

to {
Expand All @@ -61,7 +61,7 @@
@keyframes slide-right {
from {
opacity: 0;
transform: translateX(-10px);
transform: translateX(50px);
}

to {
Expand All @@ -73,7 +73,7 @@
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(10px);
transform: translateY(-50px);
}

to {
Expand Down
5 changes: 3 additions & 2 deletions packages/@core/ui-kit/layout-ui/src/vben-layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
useLayoutFooterStyle,
useLayoutHeaderStyle,
} from '@vben-core/composables';
import { Menu } from '@vben-core/icons';
import { IconifyIcon } from '@vben-core/icons';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Icon state is bound to availability, not collapse; mobile handler only opens

  • The icon condition uses showSidebar (availability) instead of collapsed state, so it can display “fold” even when the sidebar is already collapsed, especially on mobile.
  • On mobile, handleHeaderToggle only opens (sidebarCollapse = false), so clicking again won’t close, while the “fold” icon suggests it would.

Fix by binding the icon to sidebarCollapse and truly toggling on mobile:

@@
-          <VbenIconButton
+          <VbenIconButton
             v-if="showHeaderToggleButton"
             class="my-0 mr-1 rounded-md"
             @click="handleHeaderToggle"
           >
-            <IconifyIcon v-if="showSidebar" icon="ep:fold" />
-            <IconifyIcon v-else icon="ep:expand" />
+            <IconifyIcon :icon="sidebarCollapse ? 'ep:expand' : 'ep:fold'" />
           </VbenIconButton>

And update the handler to actually toggle on mobile:

@@
 function handleHeaderToggle() {
-  if (props.isMobile) {
-    sidebarCollapse.value = false;
-  } else {
+  if (props.isMobile) {
+    sidebarCollapse.value = !sidebarCollapse.value;
+  } else {
     emit('toggleSidebar');
-  }
+  }
 }

Also applies to: 562-564

🤖 Prompt for AI Agents
In packages/@core/ui-kit/layout-ui/src/vben-layout.vue around line 13 and also
at lines 562-564, the icon binding and mobile header toggle are incorrect:
change the icon condition to use the collapsed state (sidebarCollapse) instead
of showSidebar so the icon reflects collapse/expand, and modify
handleHeaderToggle to truly toggle sidebarCollapse on mobile (invert the
boolean) instead of only setting sidebarCollapse = false; apply the same fix to
the other occurrence at lines 562-564 so both icon rendering and mobile toggle
behavior are consistent.

import { VbenIconButton } from '@vben-core/shadcn-ui';
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';

Expand Down Expand Up @@ -559,7 +559,8 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
class="my-0 mr-1 rounded-md"
@click="handleHeaderToggle"
>
<Menu class="size-4" />
<IconifyIcon v-if="showSidebar" icon="ep:fold" />
<IconifyIcon v-else icon="ep:expand" />
</VbenIconButton>
</template>
<slot name="header"></slot>
Expand Down
12 changes: 9 additions & 3 deletions packages/@core/ui-kit/popup-ui/src/modal/modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ function escapeKeyDown(e: KeyboardEvent) {
}
}

function handerOpenAutoFocus(e: Event) {
function handleOpenAutoFocus(e: Event) {
if (!openAutoFocus.value) {
e?.preventDefault();
}
Expand Down Expand Up @@ -209,6 +209,12 @@ const getForceMount = computed(() => {
return !unref(destroyOnClose) && unref(firstOpened);
});

const handleOpened = () => {
requestAnimationFrame(() => {
props.modalApi?.onOpened();
});
};

function handleClosed() {
isClosed.value = true;
props.modalApi?.onClosed();
Expand Down Expand Up @@ -253,8 +259,8 @@ function handleClosed() {
@escape-key-down="escapeKeyDown"
@focus-outside="handleFocusOutside"
@interact-outside="interactOutside"
@open-auto-focus="handerOpenAutoFocus"
@opened="() => modalApi?.onOpened()"
@open-auto-focus="handleOpenAutoFocus"
@opened="handleOpened"
@pointer-down-outside="pointerDownOutside"
>
<DialogHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
v-bind="forwarded"
:class="
cn(
'focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground border-border peer h-4 w-4 shrink-0 rounded-sm border transition focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
'focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground border-border hover:border-primary peer h-4 w-4 shrink-0 rounded-sm border transition focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
props.class,
)
"
Expand Down
2 changes: 1 addition & 1 deletion packages/@core/ui-kit/shadcn-ui/src/ui/input/Input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const modelValue = useVModel(props, 'modelValue', emits, {
v-model="modelValue"
:class="
cn(
'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
'border-input bg-background ring-offset-background placeholder:text-muted-foreground/50 focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
props.class,
)
"
Expand Down
2 changes: 1 addition & 1 deletion packages/@core/ui-kit/shadcn-ui/src/ui/tabs/TabsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const delegatedProps = computed(() => {
v-bind="delegatedProps"
:class="
cn(
'bg-muted text-muted-foreground inline-flex h-9 items-center justify-center rounded-lg p-1',
'bg-muted text-muted-foreground inline-flex h-9 items-center justify-center rounded-md p-1',
props.class,
)
"
Expand Down
15 changes: 12 additions & 3 deletions packages/effects/layouts/src/authentication/authentication.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
<AuthenticationFormView
v-if="authPanelLeft"
class="min-h-full w-2/5 flex-1"
transition-name="slide-left"
data-side="left"
>
<template v-if="copyright" #copyright>
<slot name="copyright">
Expand Down Expand Up @@ -86,7 +86,14 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
class="bg-background-deep absolute inset-0 h-full w-full dark:bg-[#070709]"
>
<div class="login-background absolute left-0 top-0 size-full"></div>
<div class="flex-col-center -enter-x mr-20 h-full">
<div
:key="authPanelLeft ? 'left' : authPanelRight ? 'right' : 'center'"
class="flex-col-center mr-20 h-full"
:class="{
'enter-x': authPanelLeft,
'-enter-x': authPanelRight,
}"
>
<template v-if="sloganImage">
<img
:alt="appName"
Expand All @@ -110,6 +117,7 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
<div class="login-background absolute left-0 top-0 size-full"></div>
<AuthenticationFormView
class="md:bg-background shadow-primary/5 shadow-float w-full rounded-3xl pb-20 md:w-2/3 lg:w-1/2 xl:w-[36%]"
data-side="bottom"
>
<template v-if="copyright" #copyright>
<slot name="copyright">
Expand All @@ -125,7 +133,8 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
<!-- 右侧认证面板 -->
<AuthenticationFormView
v-if="authPanelRight"
class="min-h-full w-[34%] flex-1"
class="min-h-full w-2/5 flex-1"
data-side="right"
Comment on lines +136 to +137
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Right panel width + data-side LGTM; verify breakpoints.

w-2/5 changes layout density; quickly validate on common breakpoints (sm/md/lg).


🏁 Script executed:

#!/bin/bash
# Grep for any conflicting width utilities around auth panels
rg -n -C2 -S $'AuthenticationFormView|w-2/5|w-1/2|w-1/3' --type=vue

Length of output: 27


I need to correct the shell script approach since the --type=vue flag isn't recognized. Let me search for the file and examine its content:




🏁 Script executed:

#!/bin/bash
# Find the authentication.vue file
fd -e vue authentication

# If found, examine the file content around lines 136-137
if fd -e vue authentication | grep -q .; then
  cat "packages/effects/layouts/src/authentication/authentication.vue" | head -150 | tail -30
fi

Length of output: 798


🏁 Script executed:

#!/bin/bash
# Broader search for width utilities in Vue files (Tailwind classes)
rg -n 'w-2/5|w-1/2|w-1/3|flex-1|sm:w-|md:w-|lg:w-' packages/effects/layouts/src/authentication/ || echo "Search completed with no matches or directory not found"

Length of output: 1222


Missing responsive breakpoint utilities on side panels.

The right panel at line 136 uses w-2/5 flex-1 without responsive variants. This fixed width applies across all breakpoints, causing layout density and usability issues on mobile/tablet screens (sm/md breakpoints).

Compare against the center form at line 119, which properly implements responsive widths: w-full md:w-2/3 lg:w-1/2 xl:w-[36%]. Similarly, the left panel at line 52 has the same issue.

Recommend applying responsive variants to both panels, e.g., w-full md:w-2/5 or a similar pattern aligned with the design system, so they adapt to smaller viewports.

🤖 Prompt for AI Agents
In packages/effects/layouts/src/authentication/authentication.vue around lines
136-137, the right panel uses fixed classes "w-2/5 flex-1" without responsive
variants causing poor mobile/tablet layouts; update the width classes to include
responsive breakpoints (for example use w-full md:w-2/5 or a design-aligned
pattern) so the panel collapses to full width on small screens while retaining
2/5 on medium+ screens; apply the same responsive adjustment to the left panel
(around line 52) to keep layout consistent.

>
<template v-if="copyright" #copyright>
<slot name="copyright">
Expand Down
7 changes: 6 additions & 1 deletion packages/effects/layouts/src/authentication/form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
defineOptions({
name: 'AuthenticationFormView',
});

defineProps<{
dataSide?: 'bottom' | 'left' | 'right' | 'top';
}>();
</script>

<template>
Expand All @@ -16,7 +20,8 @@ defineOptions({
<component
:is="Component"
:key="route.fullPath"
class="enter-x mt-6 w-full sm:mx-auto md:max-w-md"
class="side-content mt-6 w-full sm:mx-auto md:max-w-md"
:data-side="dataSide"
/>
</KeepAlive>
</Transition>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,30 @@ const emit = defineEmits<{
submit: [Recordable<any>];
}>();

const [Form, { resetForm, validate, getValues }] = useVbenForm(
reactive({
commonConfig: {
hideLabel: true,
hideRequiredMark: true,
},
schema: computed(() => [
{
component: 'VbenInputPassword' as const,
componentProps: {
placeholder: $t('ui.widgets.lockScreen.placeholder'),
},
fieldName: 'lockScreenPassword',
formFieldProps: { validateOnBlur: false },
label: $t('authentication.password'),
rules: z
.string()
.min(1, { message: $t('ui.widgets.lockScreen.placeholder') }),
const [Form, { resetForm, validate, getValues, getFieldComponentRef }] =
useVbenForm(
reactive({
commonConfig: {
hideLabel: true,
hideRequiredMark: true,
},
]),
showDefaultActions: false,
}),
);
schema: computed(() => [
{
component: 'VbenInputPassword' as const,
componentProps: {
placeholder: $t('ui.widgets.lockScreen.placeholder'),
},
fieldName: 'lockScreenPassword',
formFieldProps: { validateOnBlur: false },
label: $t('authentication.password'),
rules: z
.string()
.min(1, { message: $t('ui.widgets.lockScreen.placeholder') }),
},
]),
showDefaultActions: false,
}),
);

const [Modal] = useVbenModal({
onConfirm() {
Expand All @@ -60,6 +61,13 @@ const [Modal] = useVbenModal({
resetForm();
}
},
onOpened() {
requestAnimationFrame(() => {
getFieldComponentRef('lockScreenPassword')
?.$el?.querySelector('[name="lockScreenPassword"]')
?.focus();
});
},
});

async function handleSubmit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const date = useDateFormat(now, 'YYYY-MM-DD dddd', { locales: locale.value });
const showUnlockForm = ref(false);
const { lockScreenPassword } = storeToRefs(accessStore);

const [Form, { form, validate }] = useVbenForm(
const [Form, { form, validate, getFieldComponentRef }] = useVbenForm(
reactive({
commonConfig: {
hideLabel: true,
Expand Down Expand Up @@ -75,6 +75,13 @@ async function handleSubmit() {

function toggleUnlockForm() {
showUnlockForm.value = !showUnlockForm.value;
if (showUnlockForm.value) {
requestAnimationFrame(() => {
getFieldComponentRef('password')
?.$el?.querySelector('[name="password"]')
?.focus();
});
}
}

useScrollLock();
Expand Down
4 changes: 2 additions & 2 deletions playground/src/locales/langs/zh-CN/system.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"title": "系统管理",
"dept": {
"list": "部门列表",
"createTime": "创建时间",
Expand Down Expand Up @@ -62,6 +63,5 @@
"operation": "操作",
"permissions": "权限",
"setPermissions": "授权"
},
"title": "系统管理"
}
}
10 changes: 7 additions & 3 deletions playground/src/views/examples/layout/col-page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const props = reactive({
leftWidth: 30,
resizable: true,
rightWidth: 70,
splitHandle: false,
splitLine: false,
splitHandle: true,
splitLine: true,
});
const leftMinWidth = ref(props.leftMinWidth || 1);
const leftMaxWidth = ref(props.leftMaxWidth || 100);
Expand All @@ -42,7 +42,11 @@ const leftMaxWidth = ref(props.leftMaxWidth || 100);
<template #left="{ isCollapsed, expand }">
<div v-if="isCollapsed" @click="expand">
<Tooltip title="点击展开左侧">
<Button shape="circle" type="primary">
<Button
shape="circle"
type="primary"
class="flex items-center justify-center"
>
<template #icon>
<IconifyIcon class="text-2xl" icon="bi:arrow-right" />
</template>
Expand Down
Loading