pull/201/head
xingyu4j 2025-08-19 17:41:16 +08:00
commit 8fdbbfad22
77 changed files with 3248 additions and 2831 deletions

1
.gitignore vendored
View File

@ -49,3 +49,4 @@ vite.config.ts.*
*.sln *.sln
*.sw? *.sw?
.history .history
.cursor

View File

@ -1,5 +1,7 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response'; import { MOCK_CODES } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => { export default eventHandler((event) => {
const userinfo = verifyAccessToken(event); const userinfo = verifyAccessToken(event);

View File

@ -1,9 +1,15 @@
import { defineEventHandler, readBody, setResponseStatus } from 'h3';
import { import {
clearRefreshTokenCookie, clearRefreshTokenCookie,
setRefreshTokenCookie, setRefreshTokenCookie,
} from '~/utils/cookie-utils'; } from '~/utils/cookie-utils';
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils'; import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
import { forbiddenResponse } from '~/utils/response'; import { MOCK_USERS } from '~/utils/mock-data';
import {
forbiddenResponse,
useResponseError,
useResponseSuccess,
} from '~/utils/response';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const { password, username } = await readBody(event); const { password, username } = await readBody(event);

View File

@ -1,7 +1,9 @@
import { defineEventHandler } from 'h3';
import { import {
clearRefreshTokenCookie, clearRefreshTokenCookie,
getRefreshTokenFromCookie, getRefreshTokenFromCookie,
} from '~/utils/cookie-utils'; } from '~/utils/cookie-utils';
import { useResponseSuccess } from '~/utils/response';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const refreshToken = getRefreshTokenFromCookie(event); const refreshToken = getRefreshTokenFromCookie(event);

View File

@ -1,9 +1,11 @@
import { defineEventHandler } from 'h3';
import { import {
clearRefreshTokenCookie, clearRefreshTokenCookie,
getRefreshTokenFromCookie, getRefreshTokenFromCookie,
setRefreshTokenCookie, setRefreshTokenCookie,
} from '~/utils/cookie-utils'; } from '~/utils/cookie-utils';
import { verifyRefreshToken } from '~/utils/jwt-utils'; import { generateAccessToken, verifyRefreshToken } from '~/utils/jwt-utils';
import { MOCK_USERS } from '~/utils/mock-data';
import { forbiddenResponse } from '~/utils/response'; import { forbiddenResponse } from '~/utils/response';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {

View File

@ -1,3 +1,7 @@
import { eventHandler, setHeader } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler(async (event) => { export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event); const userinfo = verifyAccessToken(event);
if (!userinfo) { if (!userinfo) {

View File

@ -1,5 +1,7 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response'; import { MOCK_MENUS } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler(async (event) => { export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event); const userinfo = verifyAccessToken(event);

View File

@ -1,3 +1,6 @@
import { eventHandler, getQuery, setResponseStatus } from 'h3';
import { useResponseError } from '~/utils/response';
export default eventHandler((event) => { export default eventHandler((event) => {
const { status } = getQuery(event); const { status } = getQuery(event);
setResponseStatus(event, Number(status)); setResponseStatus(event, Number(status));

View File

@ -1,3 +1,4 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { import {
sleep, sleep,

View File

@ -1,3 +1,4 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { import {
sleep, sleep,

View File

@ -1,3 +1,4 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { import {
sleep, sleep,

View File

@ -1,4 +1,5 @@
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';

View File

@ -1,3 +1,4 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data'; import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';

View File

@ -1,6 +1,7 @@
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data'; import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse } from '~/utils/response'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const namesMap: Record<string, any> = {}; const namesMap: Record<string, any> = {};

View File

@ -1,6 +1,7 @@
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data'; import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse } from '~/utils/response'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const pathMap: Record<string, any> = { '/': 0 }; const pathMap: Record<string, any> = { '/': 0 };

View File

@ -1,4 +1,5 @@
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data'; import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';

View File

@ -1,6 +1,11 @@
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; import {
sleep,
unAuthorizedResponse,
usePageResponseSuccess,
} from '~/utils/response';
function generateMockDataList(count: number) { function generateMockDataList(count: number) {
const dataList = []; const dataList = [];
@ -44,30 +49,69 @@ export default eventHandler(async (event) => {
await sleep(600); await sleep(600);
const { page, pageSize, sortBy, sortOrder } = getQuery(event); const { page, pageSize, sortBy, sortOrder } = getQuery(event);
// 规范化分页参数,处理 string[]
const pageRaw = Array.isArray(page) ? page[0] : page;
const pageSizeRaw = Array.isArray(pageSize) ? pageSize[0] : pageSize;
const pageNumber = Math.max(
1,
Number.parseInt(String(pageRaw ?? '1'), 10) || 1,
);
const pageSizeNumber = Math.min(
100,
Math.max(1, Number.parseInt(String(pageSizeRaw ?? '10'), 10) || 10),
);
const listData = structuredClone(mockData); const listData = structuredClone(mockData);
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
// 规范化 query 入参,兼容 string[]
const sortKeyRaw = Array.isArray(sortBy) ? sortBy[0] : sortBy;
const sortOrderRaw = Array.isArray(sortOrder) ? sortOrder[0] : sortOrder;
// 检查 sortBy 是否是 listData 元素的合法属性键
if (
typeof sortKeyRaw === 'string' &&
listData[0] &&
Object.prototype.hasOwnProperty.call(listData[0], sortKeyRaw)
) {
// 定义数组元素的类型
type ItemType = (typeof listData)[0];
const sortKey = sortKeyRaw as keyof ItemType; // 将 sortBy 断言为合法键
const isDesc = sortOrderRaw === 'desc';
listData.sort((a, b) => { listData.sort((a, b) => {
if (sortOrder === 'asc') { const aValue = a[sortKey] as unknown;
if (sortBy === 'price') { const bValue = b[sortKey] as unknown;
return (
Number.parseFloat(a[sortBy as string]) - let result = 0;
Number.parseFloat(b[sortBy as string])
); if (typeof aValue === 'number' && typeof bValue === 'number') {
result = aValue - bValue;
} else if (aValue instanceof Date && bValue instanceof Date) {
result = aValue.getTime() - bValue.getTime();
} else if (typeof aValue === 'boolean' && typeof bValue === 'boolean') {
if (aValue === bValue) {
result = 0;
} else { } else {
return a[sortBy as string] > b[sortBy as string] ? 1 : -1; result = aValue ? 1 : -1;
} }
} else { } else {
if (sortBy === 'price') { const aStr = String(aValue);
return ( const bStr = String(bValue);
Number.parseFloat(b[sortBy as string]) - const aNum = Number(aStr);
Number.parseFloat(a[sortBy as string]) const bNum = Number(bStr);
); result =
} else { Number.isFinite(aNum) && Number.isFinite(bNum)
return a[sortBy as string] < b[sortBy as string] ? 1 : -1; ? aNum - bNum
} : aStr.localeCompare(bStr, undefined, {
numeric: true,
sensitivity: 'base',
});
} }
return isDesc ? -result : result;
}); });
} }
return usePageResponseSuccess(page as string, pageSize as string, listData); return usePageResponseSuccess(
String(pageNumber),
String(pageSizeNumber),
listData,
);
}); });

View File

@ -1 +1,3 @@
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => 'Test get handler'); export default defineEventHandler(() => 'Test get handler');

View File

@ -1 +1,3 @@
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => 'Test post handler'); export default defineEventHandler(() => 'Test post handler');

View File

@ -1,5 +1,6 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => { export default eventHandler((event) => {
const userinfo = verifyAccessToken(event); const userinfo = verifyAccessToken(event);

View File

@ -1,5 +1,6 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils'; import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => { export default eventHandler((event) => {
const userinfo = verifyAccessToken(event); const userinfo = verifyAccessToken(event);

View File

@ -1,3 +1,4 @@
import { defineEventHandler } from 'h3';
import { forbiddenResponse, sleep } from '~/utils/response'; import { forbiddenResponse, sleep } from '~/utils/response';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {

View File

@ -1,3 +1,5 @@
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => { export default defineEventHandler(() => {
return ` return `
<h1>Hello Vben Admin</h1> <h1>Hello Vben Admin</h1>

View File

@ -1,5 +1,7 @@
import type { EventHandlerRequest, H3Event } from 'h3'; import type { EventHandlerRequest, H3Event } from 'h3';
import { deleteCookie, getCookie, setCookie } from 'h3';
export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) { export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
deleteCookie(event, 'jwt', { deleteCookie(event, 'jwt', {
httpOnly: true, httpOnly: true,

View File

@ -1,8 +1,11 @@
import type { EventHandlerRequest, H3Event } from 'h3'; import type { EventHandlerRequest, H3Event } from 'h3';
import type { UserInfo } from './mock-data';
import { getHeader } from 'h3';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { UserInfo } from './mock-data'; import { MOCK_USERS } from './mock-data';
// TODO: Replace with your own secret key // TODO: Replace with your own secret key
const ACCESS_TOKEN_SECRET = 'access_token_secret'; const ACCESS_TOKEN_SECRET = 'access_token_secret';
@ -31,12 +34,22 @@ export function verifyAccessToken(
return null; return null;
} }
const token = authHeader.split(' ')[1]; const tokenParts = authHeader.split(' ');
if (tokenParts.length !== 2) {
return null;
}
const token = tokenParts[1] as string;
try { try {
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload; const decoded = jwt.verify(
token,
ACCESS_TOKEN_SECRET,
) as unknown as UserPayload;
const username = decoded.username; const username = decoded.username;
const user = MOCK_USERS.find((item) => item.username === username); const user = MOCK_USERS.find((item) => item.username === username);
if (!user) {
return null;
}
const { password: _pwd, ...userinfo } = user; const { password: _pwd, ...userinfo } = user;
return userinfo; return userinfo;
} catch { } catch {
@ -50,7 +63,12 @@ export function verifyRefreshToken(
try { try {
const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload; const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
const username = decoded.username; const username = decoded.username;
const user = MOCK_USERS.find((item) => item.username === username); const user = MOCK_USERS.find(
(item) => item.username === username,
) as UserInfo;
if (!user) {
return null;
}
const { password: _pwd, ...userinfo } = user; const { password: _pwd, ...userinfo } = user;
return userinfo; return userinfo;
} catch { } catch {

View File

@ -1,5 +1,7 @@
import type { EventHandlerRequest, H3Event } from 'h3'; import type { EventHandlerRequest, H3Event } from 'h3';
import { setResponseStatus } from 'h3';
export function useResponseSuccess<T = any>(data: T) { export function useResponseSuccess<T = any>(data: T) {
return { return {
code: 0, code: 0,

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/web-antd", "name": "@vben/web-antd",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/web-ele", "name": "@vben/web-ele",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/web-naive", "name": "@vben/web-naive",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/docs", "name": "@vben/docs",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "vitepress build", "build": "vitepress build",

View File

@ -90,30 +90,52 @@ import { h } from 'vue';
import { globalShareState, IconPicker } from '@vben/common-ui'; import { globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { const AutoComplete = defineAsyncComponent(
AutoComplete, () => import('ant-design-vue/es/auto-complete'),
Button, );
Checkbox, const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
CheckboxGroup, const Checkbox = defineAsyncComponent(
DatePicker, () => import('ant-design-vue/es/checkbox'),
Divider, );
Input, const CheckboxGroup = defineAsyncComponent(() =>
InputNumber, import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
InputPassword, );
Mentions, const DatePicker = defineAsyncComponent(
notification, () => import('ant-design-vue/es/date-picker'),
Radio, );
RadioGroup, const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
RangePicker, const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
Rate, const InputNumber = defineAsyncComponent(
Select, () => import('ant-design-vue/es/input-number'),
Space, );
Switch, const InputPassword = defineAsyncComponent(() =>
Textarea, import('ant-design-vue/es/input').then((res) => res.InputPassword),
TimePicker, );
TreeSelect, const Mentions = defineAsyncComponent(
Upload, () => import('ant-design-vue/es/mentions'),
} from 'ant-design-vue'; );
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const withDefaultPlaceholder = <T extends Component>( const withDefaultPlaceholder = <T extends Component>(
component: T, component: T,
@ -304,10 +326,12 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
| 属性名 | 描述 | 类型 | 默认值 | | 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| layout | 表单项布局 | `'horizontal' \| 'vertical'` | `horizontal` | | layout | 表单项布局 | `'horizontal' \| 'vertical'\| 'inline'` | `horizontal` |
| showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` | | showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` |
| wrapperClass | 表单的布局基于tailwindcss | `any` | - | | wrapperClass | 表单的布局基于tailwindcss | `any` | - |
| actionWrapperClass | 表单操作区域class | `any` | - | | actionWrapperClass | 表单操作区域class | `any` | - |
| actionLayout | 表单操作按钮位置 | `'newLine' \| 'rowEnd' \| 'inline'` | `rowEnd` |
| actionPosition | 表单操作按钮对齐方式 | `'left' \| 'center' \| 'right'` | `right` |
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - | | handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - | | handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - | | handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/commitlint-config", "name": "@vben/commitlint-config",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/stylelint-config", "name": "@vben/stylelint-config",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/node-utils", "name": "@vben/node-utils",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/tailwind-config", "name": "@vben/tailwind-config",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/tsconfig", "name": "@vben/tsconfig",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/vite-config", "name": "@vben/vite-config",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{ {
"name": "vben-admin-monorepo", "name": "vben-admin-monorepo",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"keywords": [ "keywords": [
"monorepo", "monorepo",
@ -98,7 +98,7 @@
"node": ">=20.10.0", "node": ">=20.10.0",
"pnpm": ">=9.12.0" "pnpm": ">=9.12.0"
}, },
"packageManager": "pnpm@10.12.4", "packageManager": "pnpm@10.14.0",
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {
"allowedVersions": { "allowedVersions": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/design", "name": "@vben-core/design",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/icons", "name": "@vben-core/icons",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/shared", "name": "@vben-core/shared",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -30,7 +30,7 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void {
function openRouteInNewWindow(path: string) { function openRouteInNewWindow(path: string) {
const { hash, origin } = location; const { hash, origin } = location;
const fullPath = path.startsWith('/') ? path : `/${path}`; const fullPath = path.startsWith('/') ? path : `/${path}`;
const url = `${origin}${hash ? '/#' : ''}${fullPath}`; const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`;
openWindow(url, { target: '_blank' }); openWindow(url, { target: '_blank' });
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/typings", "name": "@vben-core/typings",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/composables", "name": "@vben-core/composables",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/preferences", "name": "@vben-core/preferences",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/form-ui", "name": "@vben-core/form-ui",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -34,17 +34,6 @@ const submitButtonOptions = computed(() => {
// return !!unref(rootProps).showCollapseButton; // return !!unref(rootProps).showCollapseButton;
// }); // });
const queryFormStyle = computed(() => {
if (!unref(rootProps).actionWrapperClass) {
return {
'grid-column': `-2 / -1`,
marginLeft: 'auto',
};
}
return {};
});
async function handleSubmit(e: Event) { async function handleSubmit(e: Event) {
e?.preventDefault(); e?.preventDefault();
e?.stopPropagation(); e?.stopPropagation();
@ -86,22 +75,59 @@ watch(
}, },
); );
const actionWrapperClass = computed(() => {
const props = unref(rootProps);
const actionLayout = props.actionLayout || 'rowEnd';
const actionPosition = props.actionPosition || 'right';
const cls = [
'flex',
'items-center',
'gap-3',
props.compact ? 'pb-2' : 'pb-4',
props.layout === 'vertical' ? 'self-end' : 'self-center',
props.layout === 'inline' ? '' : 'w-full',
props.actionWrapperClass,
];
switch (actionLayout) {
case 'newLine': {
cls.push('col-span-full');
break;
}
case 'rowEnd': {
cls.push('col-[-2/-1]');
break;
}
// 'inline'
}
switch (actionPosition) {
case 'center': {
cls.push('justify-center');
break;
}
case 'left': {
cls.push('justify-start');
break;
}
default: {
// case 'right':
cls.push('justify-end');
break;
}
}
return cls.join(' ');
});
defineExpose({ defineExpose({
handleReset, handleReset,
handleSubmit, handleSubmit,
}); });
</script> </script>
<template> <template>
<div <div :class="cn(actionWrapperClass)">
:class="
cn(
'col-span-full w-full text-right',
rootProps.compact ? 'pb-2' : 'pb-6',
rootProps.actionWrapperClass,
)
"
:style="queryFormStyle"
>
<template v-if="rootProps.actionButtonsReverse"> <template v-if="rootProps.actionButtonsReverse">
<!-- 提交按钮前 --> <!-- 提交按钮前 -->
<slot name="submit-before"></slot> <slot name="submit-before"></slot>
@ -109,7 +135,6 @@ defineExpose({
<component <component
:is="COMPONENT_MAP.PrimaryButton" :is="COMPONENT_MAP.PrimaryButton"
v-if="submitButtonOptions.show" v-if="submitButtonOptions.show"
class="ml-3"
type="button" type="button"
@click="handleSubmit" @click="handleSubmit"
v-bind="submitButtonOptions" v-bind="submitButtonOptions"
@ -124,7 +149,6 @@ defineExpose({
<component <component
:is="COMPONENT_MAP.DefaultButton" :is="COMPONENT_MAP.DefaultButton"
v-if="resetButtonOptions.show" v-if="resetButtonOptions.show"
class="ml-3"
type="button" type="button"
@click="handleReset" @click="handleReset"
v-bind="resetButtonOptions" v-bind="resetButtonOptions"
@ -139,7 +163,6 @@ defineExpose({
<component <component
:is="COMPONENT_MAP.PrimaryButton" :is="COMPONENT_MAP.PrimaryButton"
v-if="submitButtonOptions.show" v-if="submitButtonOptions.show"
class="ml-3"
type="button" type="button"
@click="handleSubmit" @click="handleSubmit"
v-bind="submitButtonOptions" v-bind="submitButtonOptions"
@ -152,9 +175,9 @@ defineExpose({
<slot name="expand-before"></slot> <slot name="expand-before"></slot>
<VbenExpandableArrow <VbenExpandableArrow
class="ml-[-0.3em]"
v-if="rootProps.showCollapseButton" v-if="rootProps.showCollapseButton"
v-model:model-value="collapsed" v-model:model-value="collapsed"
class="ml-2"
> >
<span>{{ collapsed ? $t('expand') : $t('collapse') }}</span> <span>{{ collapsed ? $t('expand') : $t('collapse') }}</span>
</VbenExpandableArrow> </VbenExpandableArrow>

View File

@ -59,7 +59,7 @@ const values = useFormValues();
const errors = useFieldError(fieldName); const errors = useFieldError(fieldName);
const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef'); const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
const formApi = formRenderProps.form; const formApi = formRenderProps.form;
const compact = formRenderProps.compact; const compact = computed(() => formRenderProps.compact);
const isInValid = computed(() => errors.value?.length > 0); const isInValid = computed(() => errors.value?.length > 0);
const FieldComponent = computed(() => { const FieldComponent = computed(() => {
@ -297,7 +297,7 @@ onUnmounted(() => {
'form-is-required': shouldRequired, 'form-is-required': shouldRequired,
'flex-col': isVertical, 'flex-col': isVertical,
'flex-row items-center': !isVertical, 'flex-row items-center': !isVertical,
'pb-6': !compact, 'pb-4': !compact,
'pb-2': compact, 'pb-2': compact,
}" }"
class="relative flex" class="relative flex"
@ -388,7 +388,7 @@ onUnmounted(() => {
</div> </div>
<Transition name="slide-up" v-if="!compact"> <Transition name="slide-up" v-if="!compact">
<FormMessage class="absolute bottom-1" /> <FormMessage class="absolute" />
</Transition> </Transition>
</div> </div>
</FormItem> </FormItem>

View File

@ -41,6 +41,18 @@ const emits = defineEmits<{
submit: [event: any]; submit: [event: any];
}>(); }>();
const wrapperClass = computed(() => {
const cls = ['flex'];
if (props.layout === 'vertical') {
cls.push(props.compact ? 'gap-x-2' : 'gap-x-4', 'flex-col grid');
} else if (props.layout === 'inline') {
cls.push('flex-wrap gap-2');
} else {
cls.push('gap-2 flex-col grid');
}
return cn(...cls, props.wrapperClass);
});
provideFormRenderProps(props); provideFormRenderProps(props);
const { isCalculated, keepFormItemIndex, wrapperRef } = useExpandable(props); const { isCalculated, keepFormItemIndex, wrapperRef } = useExpandable(props);
@ -160,7 +172,7 @@ const computedSchema = computed(
<template> <template>
<component :is="formComponent" v-bind="formComponentProps"> <component :is="formComponent" v-bind="formComponentProps">
<div ref="wrapperRef" :class="wrapperClass" class="grid"> <div ref="wrapperRef" :class="wrapperClass">
<template v-for="cSchema in computedSchema" :key="cSchema.fieldName"> <template v-for="cSchema in computedSchema" :key="cSchema.fieldName">
<!-- <div v-if="$slots[cSchema.fieldName]" :class="cSchema.formItemClass"> <!-- <div v-if="$slots[cSchema.fieldName]" :class="cSchema.formItemClass">
<slot :definition="cSchema" :name="cSchema.fieldName"> </slot> <slot :definition="cSchema" :name="cSchema.fieldName"> </slot>

View File

@ -8,7 +8,7 @@ import type { ClassType, MaybeComputedRef } from '@vben-core/typings';
import type { FormApi } from './form-api'; import type { FormApi } from './form-api';
export type FormLayout = 'horizontal' | 'vertical'; export type FormLayout = 'horizontal' | 'inline' | 'vertical';
export type BaseFormComponentType = export type BaseFormComponentType =
| 'DefaultButton' | 'DefaultButton'
@ -356,6 +356,15 @@ export interface VbenFormProps<
* *
*/ */
actionButtonsReverse?: boolean; actionButtonsReverse?: boolean;
/**
*
* newLine: rowEnd: inline: 使grid
*/
actionLayout?: 'inline' | 'newLine' | 'rowEnd';
/**
*
*/
actionPosition?: 'center' | 'left' | 'right';
/** /**
* class * class
*/ */

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/layout-ui", "name": "@vben-core/layout-ui",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/menu-ui", "name": "@vben-core/menu-ui",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/shadcn-ui", "name": "@vben-core/shadcn-ui",
"version": "5.5.8", "version": "5.5.9",
"#main": "./dist/index.mjs", "#main": "./dist/index.mjs",
"#module": "./dist/index.mjs", "#module": "./dist/index.mjs",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben-core/tabs-ui", "name": "@vben-core/tabs-ui",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/constants", "name": "@vben/constants",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/access", "name": "@vben/access",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/common-ui", "name": "@vben/common-ui",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/hooks", "name": "@vben/hooks",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/layouts", "name": "@vben/layouts",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -29,7 +29,8 @@ function useNavigation() {
return true; return true;
} }
const route = routeMetaMap.get(path); const route = routeMetaMap.get(path);
return route?.meta?.openInNewWindow ?? false; // 如果有外链或者设置了在新窗口打开,返回 true
return !!(route?.meta?.link || route?.meta?.openInNewWindow);
}; };
const resolveHref = (path: string): string => { const resolveHref = (path: string): string => {
@ -39,7 +40,13 @@ function useNavigation() {
const navigation = async (path: string) => { const navigation = async (path: string) => {
try { try {
const route = routeMetaMap.get(path); const route = routeMetaMap.get(path);
const { openInNewWindow = false, query = {} } = route?.meta ?? {}; const { openInNewWindow = false, query = {}, link } = route?.meta ?? {};
// 检查是否有外链
if (link && typeof link === 'string') {
openWindow(link, { target: '_blank' });
return;
}
if (isHttpUrl(path)) { if (isHttpUrl(path)) {
openWindow(path, { target: '_blank' }); openWindow(path, { target: '_blank' });

View File

@ -98,7 +98,7 @@ async function handleEnter() {
} }
const to = result[index]; const to = result[index];
if (to) { if (to) {
searchHistory.value.push(to); searchHistory.value = uniqueByField([...searchHistory.value, to], 'path');
handleClose(); handleClose();
await nextTick(); await nextTick();
if (isHttpUrl(to.path)) { if (isHttpUrl(to.path)) {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/plugins", "name": "@vben/plugins",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/request", "name": "@vben/request",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/icons", "name": "@vben/icons",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/locales", "name": "@vben/locales",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/preferences", "name": "@vben/preferences",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/stores", "name": "@vben/stores",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/styles", "name": "@vben/styles",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/types", "name": "@vben/types",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/utils", "name": "@vben/utils",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://github.com/vbenjs/vue-vben-admin", "homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/playground", "name": "@vben/playground",
"version": "5.5.8", "version": "5.5.9",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {

View File

@ -86,6 +86,62 @@ const [QueryForm] = useVbenForm({
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
}); });
const [InlineForm] = useVbenForm({
layout: 'inline',
schema: [
{
// #/adapter.ts
component: 'Input',
//
componentProps: {
placeholder: '请输入用户名',
},
//
fieldName: 'username',
// label
label: '字符串',
},
{
component: 'InputPassword',
componentProps: {
placeholder: '请输入密码',
},
fieldName: 'password',
label: '密码',
},
{
component: 'InputNumber',
componentProps: {
placeholder: '请输入',
},
fieldName: 'number',
label: '数字(带后缀)',
suffix: () => '¥',
},
{
component: 'Select',
componentProps: {
allowClear: true,
filterOption: true,
options: [
{
label: '选项1',
value: '1',
},
{
label: '选项2',
value: '2',
},
],
placeholder: '请选择',
showSearch: true,
},
fieldName: 'options',
label: '下拉选',
},
],
});
const [QueryForm1] = useVbenForm({ const [QueryForm1] = useVbenForm({
// //
collapsed: true, collapsed: true,
@ -125,6 +181,70 @@ const [QueryForm1] = useVbenForm({
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
}); });
const [QueryForm2] = useVbenForm({
// newLine: rowEnd: inline: 使grid
actionLayout: 'newLine',
actionPosition: 'left', //
//
collapsed: true,
collapsedRows: 3,
//
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
//
handleSubmit: onSubmit,
// labelinputvertical
// labelinput
layout: 'vertical',
schema: [
{
// #/adapter.ts
component: 'Input',
//
componentProps: {
placeholder: '请输入用户名',
},
//
fieldName: 'username',
// label
label: '字符串',
},
{
component: 'InputPassword',
componentProps: {
placeholder: '请输入密码',
},
fieldName: 'password',
label: '密码',
},
{
component: 'InputNumber',
componentProps: {
placeholder: '请输入',
},
fieldName: 'number',
label: '数字(带后缀)',
suffix: () => '¥',
},
{
component: 'DatePicker',
fieldName: 'datePicker',
label: '日期选择框',
},
],
//
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 321
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
});
function onSubmit(values: Record<string, any>) { function onSubmit(values: Record<string, any>) {
message.success({ message.success({
content: `form values: ${JSON.stringify(values)}`, content: `form values: ${JSON.stringify(values)}`,
@ -140,6 +260,15 @@ function onSubmit(values: Record<string, any>) {
<Card class="mb-5" title="查询表单,默认展开"> <Card class="mb-5" title="查询表单,默认展开">
<QueryForm /> <QueryForm />
</Card> </Card>
<Card class="mb-5" title="查询表单,单行表单">
<InlineForm />
</Card>
<Card class="mb-5" title="查询表单,默认展开,垂直布局">
<QueryForm2 />
</Card>
<Card title="查询表单默认折叠折叠时保留2行"> <Card title="查询表单默认折叠折叠时保留2行">
<QueryForm1 /> <QueryForm1 />
</Card> </Card>

File diff suppressed because it is too large Load Diff

View File

@ -67,8 +67,8 @@ catalog:
'@typescript-eslint/parser': ^8.35.1 '@typescript-eslint/parser': ^8.35.1
'@vee-validate/zod': ^4.15.1 '@vee-validate/zod': ^4.15.1
'@vite-pwa/vitepress': ^1.0.0 '@vite-pwa/vitepress': ^1.0.0
'@vitejs/plugin-vue': ^5.2.4 '@vitejs/plugin-vue': ^6.0.1
'@vitejs/plugin-vue-jsx': ^4.2.0 '@vitejs/plugin-vue-jsx': ^5.0.1
'@vue/reactivity': ^3.5.17 '@vue/reactivity': ^3.5.17
'@vue/shared': ^3.5.17 '@vue/shared': ^3.5.17
'@vue/test-utils': ^2.4.6 '@vue/test-utils': ^2.4.6
@ -184,10 +184,10 @@ catalog:
tippy.js: ^6.3.7 tippy.js: ^6.3.7
turbo: ^2.5.4 turbo: ^2.5.4
typescript: ^5.8.3 typescript: ^5.8.3
unbuild: ^3.5.0 unbuild: ^3.6.1
unplugin-element-plus: ^0.10.0 unplugin-element-plus: ^0.10.0
vee-validate: ^4.15.1 vee-validate: ^4.15.1
vite: ^6.3.5 vite: ^7.1.2
vite-plugin-compression: ^0.5.1 vite-plugin-compression: ^0.5.1
vite-plugin-dts: ^4.5.4 vite-plugin-dts: ^4.5.4
vite-plugin-html: ^3.2.2 vite-plugin-html: ^3.2.2

View File

@ -3,30 +3,104 @@ import { join, normalize } from 'node:path';
const rootDir = process.cwd(); const rootDir = process.cwd();
// 控制并发数量,避免创建过多的并发任务
const CONCURRENCY_LIMIT = 10;
// 需要跳过的目录,避免进入这些目录进行清理
const SKIP_DIRS = new Set(['.DS_Store', '.git', '.idea', '.vscode']);
/** /**
* 递归查找并删除目标目录 * 处理单个文件/目录项
* @param {string} currentDir - 当前目录路径
* @param {string} item - 文件/目录名
* @param {string[]} targets - 要删除的目标列表
* @param {number} _depth - 当前递归深度
* @returns {Promise<boolean>} - 是否需要进一步递归处理
*/
async function processItem(currentDir, item, targets, _depth) {
// 跳过特殊目录
if (SKIP_DIRS.has(item)) {
return false;
}
try {
const itemPath = normalize(join(currentDir, item));
if (targets.includes(item)) {
// 匹配到目标目录或文件时直接删除
await fs.rm(itemPath, { force: true, recursive: true });
console.log(`✅ Deleted: ${itemPath}`);
return false; // 已删除,无需递归
}
// 使用 readdir 的 withFileTypes 选项,避免额外的 lstat 调用
return true; // 可能需要递归,由调用方决定
} catch (error) {
// 更详细的错误信息
if (error.code === 'ENOENT') {
// 文件不存在,可能已被删除,这是正常情况
return false;
} else if (error.code === 'EPERM' || error.code === 'EACCES') {
console.error(`❌ Permission denied: ${item} in ${currentDir}`);
} else {
console.error(
`❌ Error handling item ${item} in ${currentDir}: ${error.message}`,
);
}
return false;
}
}
/**
* 递归查找并删除目标目录并发优化版本
* @param {string} currentDir - 当前遍历的目录路径 * @param {string} currentDir - 当前遍历的目录路径
* @param {string[]} targets - 要删除的目标列表 * @param {string[]} targets - 要删除的目标列表
* @param {number} depth - 当前递归深度避免过深递归
*/ */
async function cleanTargetsRecursively(currentDir, targets) { async function cleanTargetsRecursively(currentDir, targets, depth = 0) {
const items = await fs.readdir(currentDir); // 限制递归深度,避免无限递归
if (depth > 10) {
console.warn(`Max recursion depth reached at: ${currentDir}`);
return;
}
for (const item of items) { let dirents;
try { try {
const itemPath = normalize(join(currentDir, item)); // 使用 withFileTypes 选项,一次性获取文件类型信息,避免后续 lstat 调用
const stat = await fs.lstat(itemPath); dirents = await fs.readdir(currentDir, { withFileTypes: true });
} catch (error) {
// 如果无法读取目录,可能已被删除或权限不足
console.warn(`Cannot read directory ${currentDir}: ${error.message}`);
return;
}
if (targets.includes(item)) { // 分批处理,控制并发数量
// 匹配到目标目录或文件时直接删除 for (let i = 0; i < dirents.length; i += CONCURRENCY_LIMIT) {
await fs.rm(itemPath, { force: true, recursive: true }); const batch = dirents.slice(i, i + CONCURRENCY_LIMIT);
console.log(`Deleted: ${itemPath}`);
} else if (stat.isDirectory()) { const tasks = batch.map(async (dirent) => {
// 只对目录进行递归处理 const item = dirent.name;
await cleanTargetsRecursively(itemPath, targets); const shouldRecurse = await processItem(currentDir, item, targets, depth);
// 如果是目录且没有被删除,则递归处理
if (shouldRecurse && dirent.isDirectory()) {
const itemPath = normalize(join(currentDir, item));
return cleanTargetsRecursively(itemPath, targets, depth + 1);
} }
} catch (error) {
console.error( return null;
`Error handling item ${item} in ${currentDir}: ${error.message}`, });
// 并发执行当前批次的任务
const results = await Promise.allSettled(tasks);
// 检查是否有失败的任务(可选:用于调试)
const failedTasks = results.filter(
(result) => result.status === 'rejected',
);
if (failedTasks.length > 0) {
console.warn(
`${failedTasks.length} tasks failed in batch starting at index ${i} in directory: ${currentDir}`,
); );
} }
} }
@ -43,14 +117,25 @@ async function cleanTargetsRecursively(currentDir, targets) {
} }
console.log( console.log(
`Starting cleanup of targets: ${cleanupTargets.join(', ')} from root: ${rootDir}`, `🚀 Starting cleanup of targets: ${cleanupTargets.join(', ')} from root: ${rootDir}`,
); );
const startTime = Date.now();
try { try {
// 先统计要删除的目标数量
console.log('📊 Scanning for cleanup targets...');
await cleanTargetsRecursively(rootDir, cleanupTargets); await cleanTargetsRecursively(rootDir, cleanupTargets);
console.log('Cleanup process completed successfully.');
const endTime = Date.now();
const duration = (endTime - startTime) / 1000;
console.log(
`✨ Cleanup process completed successfully in ${duration.toFixed(2)}s`,
);
} catch (error) { } catch (error) {
console.error(`Unexpected error during cleanup: ${error.message}`); console.error(`💥 Unexpected error during cleanup: ${error.message}`);
process.exit(1); process.exit(1);
} }
})(); })();

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/turbo-run", "name": "@vben/turbo-run",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",

View File

@ -1,6 +1,6 @@
{ {
"name": "@vben/vsh", "name": "@vben/vsh",
"version": "5.5.8", "version": "5.5.9",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",