diff --git a/.gitignore b/.gitignore index c2a8a771..3399f39c 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ vite.config.ts.* *.sln *.sw? .history +.cursor diff --git a/README.ja-JP.md b/README.ja-JP.md index f7847a1d..4ce285a7 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -140,8 +140,12 @@ pnpm build ## 貢献者 + + Contribution Leaderboard + + - Contributors + Contributors ## Discord diff --git a/README.md b/README.md index e027949a..b9dd73eb 100644 --- a/README.md +++ b/README.md @@ -140,8 +140,12 @@ If you think this project is helpful to you, you can help the author buy a cup o ## Contributors + + Contribution Leaderboard + + - Contributors + Contributors ## Discord diff --git a/README.zh-CN.md b/README.zh-CN.md index 5a6b191b..d3193ef6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -140,8 +140,12 @@ pnpm build ## 贡献者 + + Contribution Leaderboard + + - Contributors + Contributors ## Discord diff --git a/apps/backend-mock/api/auth/codes.ts b/apps/backend-mock/api/auth/codes.ts index 7ba01270..e610b338 100644 --- a/apps/backend-mock/api/auth/codes.ts +++ b/apps/backend-mock/api/auth/codes.ts @@ -1,5 +1,7 @@ +import { eventHandler } from 'h3'; 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) => { const userinfo = verifyAccessToken(event); diff --git a/apps/backend-mock/api/auth/login.post.ts b/apps/backend-mock/api/auth/login.post.ts index df5737a2..e23942c4 100644 --- a/apps/backend-mock/api/auth/login.post.ts +++ b/apps/backend-mock/api/auth/login.post.ts @@ -1,9 +1,15 @@ +import { defineEventHandler, readBody, setResponseStatus } from 'h3'; import { clearRefreshTokenCookie, setRefreshTokenCookie, } from '~/utils/cookie-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) => { const { password, username } = await readBody(event); diff --git a/apps/backend-mock/api/auth/logout.post.ts b/apps/backend-mock/api/auth/logout.post.ts index ac6afe94..74c8d315 100644 --- a/apps/backend-mock/api/auth/logout.post.ts +++ b/apps/backend-mock/api/auth/logout.post.ts @@ -1,7 +1,9 @@ +import { defineEventHandler } from 'h3'; import { clearRefreshTokenCookie, getRefreshTokenFromCookie, } from '~/utils/cookie-utils'; +import { useResponseSuccess } from '~/utils/response'; export default defineEventHandler(async (event) => { const refreshToken = getRefreshTokenFromCookie(event); diff --git a/apps/backend-mock/api/auth/refresh.post.ts b/apps/backend-mock/api/auth/refresh.post.ts index 7df4d34f..7d8d3a51 100644 --- a/apps/backend-mock/api/auth/refresh.post.ts +++ b/apps/backend-mock/api/auth/refresh.post.ts @@ -1,9 +1,11 @@ +import { defineEventHandler } from 'h3'; import { clearRefreshTokenCookie, getRefreshTokenFromCookie, setRefreshTokenCookie, } 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'; export default defineEventHandler(async (event) => { diff --git a/apps/backend-mock/api/demo/bigint.ts b/apps/backend-mock/api/demo/bigint.ts index 880cc5ea..00d6c28c 100644 --- a/apps/backend-mock/api/demo/bigint.ts +++ b/apps/backend-mock/api/demo/bigint.ts @@ -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) => { const userinfo = verifyAccessToken(event); if (!userinfo) { diff --git a/apps/backend-mock/api/menu/all.ts b/apps/backend-mock/api/menu/all.ts index 580cee4f..7923f7ca 100644 --- a/apps/backend-mock/api/menu/all.ts +++ b/apps/backend-mock/api/menu/all.ts @@ -1,5 +1,7 @@ +import { eventHandler } from 'h3'; 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) => { const userinfo = verifyAccessToken(event); diff --git a/apps/backend-mock/api/status.ts b/apps/backend-mock/api/status.ts index 41773e1d..43782095 100644 --- a/apps/backend-mock/api/status.ts +++ b/apps/backend-mock/api/status.ts @@ -1,3 +1,6 @@ +import { eventHandler, getQuery, setResponseStatus } from 'h3'; +import { useResponseError } from '~/utils/response'; + export default eventHandler((event) => { const { status } = getQuery(event); setResponseStatus(event, Number(status)); diff --git a/apps/backend-mock/api/system/dept/.post.ts b/apps/backend-mock/api/system/dept/.post.ts index c529ea1b..9a4896af 100644 --- a/apps/backend-mock/api/system/dept/.post.ts +++ b/apps/backend-mock/api/system/dept/.post.ts @@ -1,3 +1,4 @@ +import { eventHandler } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { sleep, diff --git a/apps/backend-mock/api/system/dept/[id].delete.ts b/apps/backend-mock/api/system/dept/[id].delete.ts index e48f051c..eac0f584 100644 --- a/apps/backend-mock/api/system/dept/[id].delete.ts +++ b/apps/backend-mock/api/system/dept/[id].delete.ts @@ -1,3 +1,4 @@ +import { eventHandler } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { sleep, diff --git a/apps/backend-mock/api/system/dept/[id].put.ts b/apps/backend-mock/api/system/dept/[id].put.ts index aa55c085..6805e139 100644 --- a/apps/backend-mock/api/system/dept/[id].put.ts +++ b/apps/backend-mock/api/system/dept/[id].put.ts @@ -1,3 +1,4 @@ +import { eventHandler } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { sleep, diff --git a/apps/backend-mock/api/system/dept/list.ts b/apps/backend-mock/api/system/dept/list.ts index ae819b62..a649a0d2 100644 --- a/apps/backend-mock/api/system/dept/list.ts +++ b/apps/backend-mock/api/system/dept/list.ts @@ -1,4 +1,5 @@ import { faker } from '@faker-js/faker'; +import { eventHandler } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; diff --git a/apps/backend-mock/api/system/menu/list.ts b/apps/backend-mock/api/system/menu/list.ts index 5328b2fd..ce96bb14 100644 --- a/apps/backend-mock/api/system/menu/list.ts +++ b/apps/backend-mock/api/system/menu/list.ts @@ -1,3 +1,4 @@ +import { eventHandler } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { MOCK_MENU_LIST } from '~/utils/mock-data'; import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; diff --git a/apps/backend-mock/api/system/menu/name-exists.ts b/apps/backend-mock/api/system/menu/name-exists.ts index 5599c22b..7d5551b3 100644 --- a/apps/backend-mock/api/system/menu/name-exists.ts +++ b/apps/backend-mock/api/system/menu/name-exists.ts @@ -1,6 +1,7 @@ +import { eventHandler, getQuery } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { MOCK_MENU_LIST } from '~/utils/mock-data'; -import { unAuthorizedResponse } from '~/utils/response'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; const namesMap: Record = {}; diff --git a/apps/backend-mock/api/system/menu/path-exists.ts b/apps/backend-mock/api/system/menu/path-exists.ts index 64774f79..f3c3be99 100644 --- a/apps/backend-mock/api/system/menu/path-exists.ts +++ b/apps/backend-mock/api/system/menu/path-exists.ts @@ -1,6 +1,7 @@ +import { eventHandler, getQuery } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { MOCK_MENU_LIST } from '~/utils/mock-data'; -import { unAuthorizedResponse } from '~/utils/response'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; const pathMap: Record = { '/': 0 }; diff --git a/apps/backend-mock/api/system/role/list.ts b/apps/backend-mock/api/system/role/list.ts index 4d5f923e..bad29a51 100644 --- a/apps/backend-mock/api/system/role/list.ts +++ b/apps/backend-mock/api/system/role/list.ts @@ -1,4 +1,5 @@ import { faker } from '@faker-js/faker'; +import { eventHandler, getQuery } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data'; import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; diff --git a/apps/backend-mock/api/table/list.ts b/apps/backend-mock/api/table/list.ts index 3e6f705b..6664b583 100644 --- a/apps/backend-mock/api/table/list.ts +++ b/apps/backend-mock/api/table/list.ts @@ -1,6 +1,11 @@ import { faker } from '@faker-js/faker'; +import { eventHandler, getQuery } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; +import { + sleep, + unAuthorizedResponse, + usePageResponseSuccess, +} from '~/utils/response'; function generateMockDataList(count: number) { const dataList = []; @@ -44,30 +49,69 @@ export default eventHandler(async (event) => { await sleep(600); 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); - 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) => { - if (sortOrder === 'asc') { - if (sortBy === 'price') { - return ( - Number.parseFloat(a[sortBy as string]) - - Number.parseFloat(b[sortBy as string]) - ); + const aValue = a[sortKey] as unknown; + const bValue = b[sortKey] as unknown; + + let result = 0; + + 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 { - return a[sortBy as string] > b[sortBy as string] ? 1 : -1; + result = aValue ? 1 : -1; } } else { - if (sortBy === 'price') { - return ( - Number.parseFloat(b[sortBy as string]) - - Number.parseFloat(a[sortBy as string]) - ); - } else { - return a[sortBy as string] < b[sortBy as string] ? 1 : -1; - } + const aStr = String(aValue); + const bStr = String(bValue); + const aNum = Number(aStr); + const bNum = Number(bStr); + result = + Number.isFinite(aNum) && Number.isFinite(bNum) + ? 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, + ); }); diff --git a/apps/backend-mock/api/test.get.ts b/apps/backend-mock/api/test.get.ts index ca4a500b..dc2ceef7 100644 --- a/apps/backend-mock/api/test.get.ts +++ b/apps/backend-mock/api/test.get.ts @@ -1 +1,3 @@ +import { defineEventHandler } from 'h3'; + export default defineEventHandler(() => 'Test get handler'); diff --git a/apps/backend-mock/api/test.post.ts b/apps/backend-mock/api/test.post.ts index 698cf211..0e9e337a 100644 --- a/apps/backend-mock/api/test.post.ts +++ b/apps/backend-mock/api/test.post.ts @@ -1 +1,3 @@ +import { defineEventHandler } from 'h3'; + export default defineEventHandler(() => 'Test post handler'); diff --git a/apps/backend-mock/api/upload.ts b/apps/backend-mock/api/upload.ts index 1bb9e602..436b63cb 100644 --- a/apps/backend-mock/api/upload.ts +++ b/apps/backend-mock/api/upload.ts @@ -1,5 +1,6 @@ +import { eventHandler } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse } from '~/utils/response'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; export default eventHandler((event) => { const userinfo = verifyAccessToken(event); diff --git a/apps/backend-mock/api/user/info.ts b/apps/backend-mock/api/user/info.ts index cfa2346c..138cb433 100644 --- a/apps/backend-mock/api/user/info.ts +++ b/apps/backend-mock/api/user/info.ts @@ -1,5 +1,6 @@ +import { eventHandler } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse } from '~/utils/response'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; export default eventHandler((event) => { const userinfo = verifyAccessToken(event); diff --git a/apps/backend-mock/middleware/1.api.ts b/apps/backend-mock/middleware/1.api.ts index bad9a41a..339cda4d 100644 --- a/apps/backend-mock/middleware/1.api.ts +++ b/apps/backend-mock/middleware/1.api.ts @@ -1,3 +1,4 @@ +import { defineEventHandler } from 'h3'; import { forbiddenResponse, sleep } from '~/utils/response'; export default defineEventHandler(async (event) => { diff --git a/apps/backend-mock/routes/[...].ts b/apps/backend-mock/routes/[...].ts index 99f544b6..5a22563d 100644 --- a/apps/backend-mock/routes/[...].ts +++ b/apps/backend-mock/routes/[...].ts @@ -1,3 +1,5 @@ +import { defineEventHandler } from 'h3'; + export default defineEventHandler(() => { return `

Hello Vben Admin

diff --git a/apps/backend-mock/utils/cookie-utils.ts b/apps/backend-mock/utils/cookie-utils.ts index 78f3aab7..187ce2f0 100644 --- a/apps/backend-mock/utils/cookie-utils.ts +++ b/apps/backend-mock/utils/cookie-utils.ts @@ -1,5 +1,7 @@ import type { EventHandlerRequest, H3Event } from 'h3'; +import { deleteCookie, getCookie, setCookie } from 'h3'; + export function clearRefreshTokenCookie(event: H3Event) { deleteCookie(event, 'jwt', { httpOnly: true, diff --git a/apps/backend-mock/utils/jwt-utils.ts b/apps/backend-mock/utils/jwt-utils.ts index 8cfc6843..71858307 100644 --- a/apps/backend-mock/utils/jwt-utils.ts +++ b/apps/backend-mock/utils/jwt-utils.ts @@ -1,8 +1,11 @@ import type { EventHandlerRequest, H3Event } from 'h3'; +import type { UserInfo } from './mock-data'; + +import { getHeader } from 'h3'; import jwt from 'jsonwebtoken'; -import { UserInfo } from './mock-data'; +import { MOCK_USERS } from './mock-data'; // TODO: Replace with your own secret key const ACCESS_TOKEN_SECRET = 'access_token_secret'; @@ -31,12 +34,22 @@ export function verifyAccessToken( return null; } - const token = authHeader.split(' ')[1]; + const tokenParts = authHeader.split(' '); + if (tokenParts.length !== 2) { + return null; + } + const token = tokenParts[1] as string; 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 user = MOCK_USERS.find((item) => item.username === username); + if (!user) { + return null; + } const { password: _pwd, ...userinfo } = user; return userinfo; } catch { @@ -50,7 +63,12 @@ export function verifyRefreshToken( try { const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload; 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; return userinfo; } catch { diff --git a/apps/backend-mock/utils/mock-data.ts b/apps/backend-mock/utils/mock-data.ts index 192f30a0..689de2a1 100644 --- a/apps/backend-mock/utils/mock-data.ts +++ b/apps/backend-mock/utils/mock-data.ts @@ -276,7 +276,7 @@ export const MOCK_MENU_LIST = [ children: [ { id: 20_401, - pid: 201, + pid: 202, name: 'SystemDeptCreate', status: 1, type: 'button', @@ -285,7 +285,7 @@ export const MOCK_MENU_LIST = [ }, { id: 20_402, - pid: 201, + pid: 202, name: 'SystemDeptEdit', status: 1, type: 'button', @@ -294,7 +294,7 @@ export const MOCK_MENU_LIST = [ }, { id: 20_403, - pid: 201, + pid: 202, name: 'SystemDeptDelete', status: 1, type: 'button', diff --git a/apps/backend-mock/utils/response.ts b/apps/backend-mock/utils/response.ts index 2a5a908f..2d4242e9 100644 --- a/apps/backend-mock/utils/response.ts +++ b/apps/backend-mock/utils/response.ts @@ -1,5 +1,7 @@ import type { EventHandlerRequest, H3Event } from 'h3'; +import { setResponseStatus } from 'h3'; + export function useResponseSuccess(data: T) { return { code: 0, diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index 5b6cbeb3..db7d0070 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-antd", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/web-antd/src/layouts/basic.vue b/apps/web-antd/src/layouts/basic.vue index 1481dc5a..805b8a73 100644 --- a/apps/web-antd/src/layouts/basic.vue +++ b/apps/web-antd/src/layouts/basic.vue @@ -6,7 +6,7 @@ import { computed, ref, watch } from 'vue'; import { AuthenticationLoginExpiredModal } from '@vben/common-ui'; import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants'; import { useWatermark } from '@vben/hooks'; -import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons'; +import { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons'; import { BasicLayout, LockScreen, @@ -76,7 +76,7 @@ const menus = computed(() => [ target: '_blank', }); }, - icon: MdiGithub, + icon: SvgGithubIcon, text: 'GitHub', }, { @@ -106,11 +106,16 @@ function handleMakeAll() { notifications.value.forEach((item) => (item.isRead = true)); } watch( - () => preferences.app.watermark, - async (enable) => { + () => ({ + enable: preferences.app.watermark, + content: preferences.app.watermarkContent, + }), + async ({ enable, content }) => { if (enable) { await updateWatermark({ - content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, + content: + content || + `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, }); } else { destroyWatermark(); diff --git a/apps/web-ele/package.json b/apps/web-ele/package.json index 0e5aa1aa..abbedb63 100644 --- a/apps/web-ele/package.json +++ b/apps/web-ele/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-ele", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/web-ele/src/layouts/basic.vue b/apps/web-ele/src/layouts/basic.vue index 1481dc5a..805b8a73 100644 --- a/apps/web-ele/src/layouts/basic.vue +++ b/apps/web-ele/src/layouts/basic.vue @@ -6,7 +6,7 @@ import { computed, ref, watch } from 'vue'; import { AuthenticationLoginExpiredModal } from '@vben/common-ui'; import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants'; import { useWatermark } from '@vben/hooks'; -import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons'; +import { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons'; import { BasicLayout, LockScreen, @@ -76,7 +76,7 @@ const menus = computed(() => [ target: '_blank', }); }, - icon: MdiGithub, + icon: SvgGithubIcon, text: 'GitHub', }, { @@ -106,11 +106,16 @@ function handleMakeAll() { notifications.value.forEach((item) => (item.isRead = true)); } watch( - () => preferences.app.watermark, - async (enable) => { + () => ({ + enable: preferences.app.watermark, + content: preferences.app.watermarkContent, + }), + async ({ enable, content }) => { if (enable) { await updateWatermark({ - content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, + content: + content || + `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, }); } else { destroyWatermark(); diff --git a/apps/web-naive/package.json b/apps/web-naive/package.json index 515763b0..ff69374f 100644 --- a/apps/web-naive/package.json +++ b/apps/web-naive/package.json @@ -1,6 +1,6 @@ { "name": "@vben/web-naive", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/apps/web-naive/src/layouts/basic.vue b/apps/web-naive/src/layouts/basic.vue index 69189384..0e9747be 100644 --- a/apps/web-naive/src/layouts/basic.vue +++ b/apps/web-naive/src/layouts/basic.vue @@ -6,7 +6,7 @@ import { computed, ref, watch } from 'vue'; import { AuthenticationLoginExpiredModal } from '@vben/common-ui'; import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants'; import { useWatermark } from '@vben/hooks'; -import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons'; +import { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons'; import { BasicLayout, LockScreen, @@ -76,7 +76,7 @@ const menus = computed(() => [ target: '_blank', }); }, - icon: MdiGithub, + icon: SvgGithubIcon, text: 'GitHub', }, { @@ -107,11 +107,16 @@ function handleMakeAll() { } watch( - () => preferences.app.watermark, - async (enable) => { + () => ({ + enable: preferences.app.watermark, + content: preferences.app.watermarkContent, + }), + async ({ enable, content }) => { if (enable) { await updateWatermark({ - content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, + content: + content || + `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, }); } else { destroyWatermark(); diff --git a/docs/package.json b/docs/package.json index 639ca07a..864e450a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@vben/docs", - "version": "5.5.8", + "version": "5.5.9", "private": true, "scripts": { "build": "vitepress build", diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index 6d1dc4eb..48772bfa 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/docs/src/components/common-ui/vben-form.md @@ -90,30 +90,52 @@ import { h } from 'vue'; import { globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; -import { - AutoComplete, - Button, - Checkbox, - CheckboxGroup, - DatePicker, - Divider, - Input, - InputNumber, - InputPassword, - Mentions, - notification, - Radio, - RadioGroup, - RangePicker, - Rate, - Select, - Space, - Switch, - Textarea, - TimePicker, - TreeSelect, - Upload, -} from 'ant-design-vue'; +const AutoComplete = defineAsyncComponent( + () => import('ant-design-vue/es/auto-complete'), +); +const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); +const Checkbox = defineAsyncComponent( + () => import('ant-design-vue/es/checkbox'), +); +const CheckboxGroup = defineAsyncComponent(() => + import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup), +); +const DatePicker = defineAsyncComponent( + () => import('ant-design-vue/es/date-picker'), +); +const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider')); +const Input = defineAsyncComponent(() => import('ant-design-vue/es/input')); +const InputNumber = defineAsyncComponent( + () => import('ant-design-vue/es/input-number'), +); +const InputPassword = defineAsyncComponent(() => + import('ant-design-vue/es/input').then((res) => res.InputPassword), +); +const Mentions = defineAsyncComponent( + () => import('ant-design-vue/es/mentions'), +); +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 = ( component: T, @@ -304,7 +326,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单 | 属性名 | 描述 | 类型 | 默认值 | | --- | --- | --- | --- | -| layout | 表单项布局 | `'horizontal' \| 'vertical'` | `horizontal` | +| layout | 表单项布局 | `'horizontal' \| 'vertical'\| 'inline'` | `horizontal` | | showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` | | wrapperClass | 表单的布局,基于tailwindcss | `any` | - | | actionWrapperClass | 表单操作区域class | `any` | - | @@ -451,6 +473,8 @@ export interface FormSchema< fieldName: string; /** 帮助信息 */ help?: CustomRenderType; + /** 是否隐藏表单项 */ + hide?: boolean; /** 表单的标签(如果是一个string,会用于默认必选规则的消息提示) */ label?: CustomRenderType; /** 自定义组件内部渲染 */ diff --git a/internal/lint-configs/commitlint-config/package.json b/internal/lint-configs/commitlint-config/package.json index 16984c2f..0c3a3aa7 100644 --- a/internal/lint-configs/commitlint-config/package.json +++ b/internal/lint-configs/commitlint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/commitlint-config", - "version": "5.5.8", + "version": "5.5.9", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/lint-configs/stylelint-config/package.json b/internal/lint-configs/stylelint-config/package.json index 00f9b1d1..f8bc1cce 100644 --- a/internal/lint-configs/stylelint-config/package.json +++ b/internal/lint-configs/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/stylelint-config", - "version": "5.5.8", + "version": "5.5.9", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/node-utils/package.json b/internal/node-utils/package.json index 490df9e9..fe4a6bab 100644 --- a/internal/node-utils/package.json +++ b/internal/node-utils/package.json @@ -1,6 +1,6 @@ { "name": "@vben/node-utils", - "version": "5.5.8", + "version": "5.5.9", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json index 72c63819..07b26be4 100644 --- a/internal/tailwind-config/package.json +++ b/internal/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tailwind-config", - "version": "5.5.8", + "version": "5.5.9", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/tsconfig/package.json b/internal/tsconfig/package.json index 74cfb915..143bd32c 100644 --- a/internal/tsconfig/package.json +++ b/internal/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@vben/tsconfig", - "version": "5.5.8", + "version": "5.5.9", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json index c37777f2..db1fb492 100644 --- a/internal/vite-config/package.json +++ b/internal/vite-config/package.json @@ -1,6 +1,6 @@ { "name": "@vben/vite-config", - "version": "5.5.8", + "version": "5.5.9", "private": true, "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", diff --git a/package.json b/package.json index 487cff52..f7c29406 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vben-admin-monorepo", - "version": "5.5.8", + "version": "5.5.9", "private": true, "keywords": [ "monorepo", @@ -98,7 +98,7 @@ "node": ">=20.10.0", "pnpm": ">=9.12.0" }, - "packageManager": "pnpm@10.12.4", + "packageManager": "pnpm@10.14.0", "pnpm": { "peerDependencyRules": { "allowedVersions": { diff --git a/packages/@core/base/design/package.json b/packages/@core/base/design/package.json index 13d2fefc..ed40f4b5 100644 --- a/packages/@core/base/design/package.json +++ b/packages/@core/base/design/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/design", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/design/src/css/ui.css b/packages/@core/base/design/src/css/ui.css index f7119c8b..a1bf0242 100644 --- a/packages/@core/base/design/src/css/ui.css +++ b/packages/@core/base/design/src/css/ui.css @@ -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); } @@ -37,7 +37,7 @@ @keyframes slide-down { from { opacity: 0; - transform: translateY(-10px); + transform: translateY(50px); } to { @@ -49,7 +49,7 @@ @keyframes slide-left { from { opacity: 0; - transform: translateX(-10px); + transform: translateX(-50px); } to { @@ -61,7 +61,7 @@ @keyframes slide-right { from { opacity: 0; - transform: translateX(-10px); + transform: translateX(50px); } to { @@ -73,7 +73,7 @@ @keyframes slide-up { from { opacity: 0; - transform: translateY(10px); + transform: translateY(-50px); } to { @@ -85,3 +85,17 @@ .z-popup { z-index: var(--popup-z-index); } + +@keyframes shrink { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(0.9); + } + + 100% { + transform: scale(1); + } +} diff --git a/packages/@core/base/icons/package.json b/packages/@core/base/icons/package.json index 1e6e5250..3c6775fb 100644 --- a/packages/@core/base/icons/package.json +++ b/packages/@core/base/icons/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/icons", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts index 70e6a426..a167aea0 100644 --- a/packages/@core/base/icons/src/lucide.ts +++ b/packages/@core/base/icons/src/lucide.ts @@ -32,6 +32,7 @@ export { Grip, GripVertical, Menu as IconDefault, + Inbox, Info, InspectionPanel, Languages, diff --git a/packages/@core/base/shared/package.json b/packages/@core/base/shared/package.json index 6599c54c..c57a0acf 100644 --- a/packages/@core/base/shared/package.json +++ b/packages/@core/base/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/shared", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/base/shared/src/utils/window.ts b/packages/@core/base/shared/src/utils/window.ts index 4608f4be..2d8697de 100644 --- a/packages/@core/base/shared/src/utils/window.ts +++ b/packages/@core/base/shared/src/utils/window.ts @@ -30,7 +30,7 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void { function openRouteInNewWindow(path: string) { const { hash, origin } = location; const fullPath = path.startsWith('/') ? path : `/${path}`; - const url = `${origin}${hash ? '/#' : ''}${fullPath}`; + const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`; openWindow(url, { target: '_blank' }); } diff --git a/packages/@core/base/typings/package.json b/packages/@core/base/typings/package.json index e0e2c372..6464e695 100644 --- a/packages/@core/base/typings/package.json +++ b/packages/@core/base/typings/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/typings", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/composables/package.json b/packages/@core/composables/package.json index 39ca108f..be8ee337 100644 --- a/packages/@core/composables/package.json +++ b/packages/@core/composables/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/composables", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap index 1cccbbb2..b3f15980 100644 --- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap @@ -22,6 +22,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "enableCheckUpdates": true, "enablePreferences": true, "enableRefreshToken": false, + "enableStickyPreferencesNavigationBar": true, "isMobile": false, "layout": "sidebar-nav", "locale": "zh-CN", @@ -29,6 +30,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "name": "Vben Admin", "preferencesButtonPosition": "auto", "watermark": false, + "watermarkContent": "", "zIndex": 200, }, "breadcrumb": { diff --git a/packages/@core/preferences/package.json b/packages/@core/preferences/package.json index 786e6c2c..75a5a5a1 100644 --- a/packages/@core/preferences/package.json +++ b/packages/@core/preferences/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/preferences", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts index 835eed55..f6c1c2b7 100644 --- a/packages/@core/preferences/src/config.ts +++ b/packages/@core/preferences/src/config.ts @@ -1,4 +1,4 @@ -import type { Preferences } from './types'; +import type { Preferences } from "./types"; const defaultPreferences: Preferences = { app: { @@ -22,6 +22,7 @@ const defaultPreferences: Preferences = { enableCheckUpdates: true, enablePreferences: true, enableRefreshToken: false, + enableStickyPreferencesNavigationBar: true, isMobile: false, layout: 'sidebar-nav', locale: 'zh-CN', @@ -29,7 +30,9 @@ const defaultPreferences: Preferences = { name: 'Vben Admin', preferencesButtonPosition: 'auto', watermark: false, + watermarkContent: '', zIndex: 200, + }, breadcrumb: { enable: true, diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts index e640edb5..0c90da80 100644 --- a/packages/@core/preferences/src/types.ts +++ b/packages/@core/preferences/src/types.ts @@ -59,6 +59,10 @@ interface AppPreferences { * @zh_CN 是否开启refreshToken */ enableRefreshToken: boolean; + /** + * @zh_CN 是否开启首选项导航栏吸顶效果 + */ + enableStickyPreferencesNavigationBar: boolean; /** 是否移动端 */ isMobile: boolean; /** 布局方式 */ @@ -75,6 +79,10 @@ interface AppPreferences { * @zh_CN 是否开启水印 */ watermark: boolean; + /** + * @zh_CN 水印文案 + */ + watermarkContent: string; /** z-index */ zIndex: number; } diff --git a/packages/@core/ui-kit/form-ui/package.json b/packages/@core/ui-kit/form-ui/package.json index 497da77a..ce7466d1 100644 --- a/packages/@core/ui-kit/form-ui/package.json +++ b/packages/@core/ui-kit/form-ui/package.json @@ -1,6 +1,6 @@ { "name": "@vben-core/form-ui", - "version": "5.5.8", + "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { diff --git a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue index cc42a161..42101c12 100644 --- a/packages/@core/ui-kit/form-ui/src/components/form-actions.vue +++ b/packages/@core/ui-kit/form-ui/src/components/form-actions.vue @@ -82,11 +82,11 @@ const actionWrapperClass = computed(() => { const cls = [ 'flex', - 'w-full', 'items-center', 'gap-3', props.compact ? 'pb-2' : 'pb-4', props.layout === 'vertical' ? 'self-end' : 'self-center', + props.layout === 'inline' ? '' : 'w-full', props.actionWrapperClass, ]; diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index bdc44de7..401748c3 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -342,13 +342,12 @@ export class FormApi { isObject(obj[key]) && !isDayjsObject(obj[key]) && !isDate(obj[key]) - ? fieldMergeFn(obj[key], value) + ? fieldMergeFn(value, obj[key]) : value; } return true; }); const filteredFields = fieldMergeFn(fields, form.values); - this.handleStringToArrayFields(filteredFields); form.setValues(filteredFields, shouldValidate); } @@ -358,7 +357,6 @@ export class FormApi { const form = await this.getForm(); await form.submitForm(); const rawValues = toRaw(await this.getValues()); - this.handleArrayToStringFields(rawValues); await this.state?.handleSubmit?.(rawValues); return rawValues; @@ -458,16 +456,31 @@ export class FormApi { return this.form; } - private handleArrayToStringFields = (originValues: Record) => { + private handleMultiFields = (originValues: Record) => { const arrayToStringFields = this.state?.arrayToStringFields; if (!arrayToStringFields || !Array.isArray(arrayToStringFields)) { return; } const processFields = (fields: string[], separator: string = ',') => { - this.processFields(fields, separator, originValues, (value, sep) => - Array.isArray(value) ? value.join(sep) : value, - ); + this.processFields(fields, separator, originValues, (value, sep) => { + if (Array.isArray(value)) { + return value.join(sep); + } else if (typeof value === 'string') { + // 处理空字符串的情况 + if (value === '') { + return []; + } + // 处理复杂分隔符的情况 + const escapedSeparator = sep.replaceAll( + /[.*+?^${}()|[\]\\]/g, + String.raw`\$&`, + ); + return value.split(new RegExp(escapedSeparator)); + } else { + return value; + } + }); }; // 处理简单数组格式 ['field1', 'field2', ';'] 或 ['field1', 'field2'] @@ -503,8 +516,7 @@ export class FormApi { const values = { ...originValues }; const fieldMappingTime = this.state?.fieldMappingTime; - this.handleStringToArrayFields(values); - + this.handleMultiFields(values); if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) { return values; } @@ -550,65 +562,6 @@ export class FormApi { return values; }; - private handleStringToArrayFields = (originValues: Record) => { - const arrayToStringFields = this.state?.arrayToStringFields; - if (!arrayToStringFields || !Array.isArray(arrayToStringFields)) { - return; - } - - const processFields = (fields: string[], separator: string = ',') => { - this.processFields(fields, separator, originValues, (value, sep) => { - if (typeof value !== 'string') { - return value; - } - // 处理空字符串的情况 - if (value === '') { - return []; - } - // 处理复杂分隔符的情况 - const escapedSeparator = sep.replaceAll( - /[.*+?^${}()|[\]\\]/g, - String.raw`\$&`, - ); - return value.split(new RegExp(escapedSeparator)); - }); - }; - - // 处理简单数组格式 ['field1', 'field2', ';'] 或 ['field1', 'field2'] - if (arrayToStringFields.every((item) => typeof item === 'string')) { - const lastItem = - arrayToStringFields[arrayToStringFields.length - 1] || ''; - const fields = - lastItem.length === 1 - ? arrayToStringFields.slice(0, -1) - : arrayToStringFields; - const separator = lastItem.length === 1 ? lastItem : ','; - processFields(fields, separator); - return; - } - - // 处理嵌套数组格式 [['field1'], ';'] - arrayToStringFields.forEach((fieldConfig) => { - if (Array.isArray(fieldConfig)) { - const [fields, separator = ','] = fieldConfig; - if (Array.isArray(fields)) { - processFields(fields, separator); - } else if (typeof originValues[fields] === 'string') { - const value = originValues[fields]; - if (value === '') { - originValues[fields] = []; - } else { - const escapedSeparator = separator.replaceAll( - /[.*+?^${}()|[\]\\]/g, - String.raw`\$&`, - ); - originValues[fields] = value.split(new RegExp(escapedSeparator)); - } - } - } - }); - }; - private processFields = ( fields: string[], separator: string, diff --git a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue index 67ea58a5..86ca8166 100644 --- a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue +++ b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue @@ -41,6 +41,7 @@ const { emptyStateValue, fieldName, formFieldProps, + hide, label, labelClass, labelWidth, @@ -59,7 +60,7 @@ const values = useFormValues(); const errors = useFieldError(fieldName); const fieldComponentRef = useTemplateRef('fieldComponentRef'); const formApi = formRenderProps.form; -const compact = formRenderProps.compact; +const compact = computed(() => formRenderProps.compact); const isInValid = computed(() => errors.value?.length > 0); const FieldComponent = computed(() => { @@ -95,7 +96,7 @@ const currentRules = computed(() => { }); const visible = computed(() => { - return isIf.value && isShow.value; + return !hide && isIf.value && isShow.value; }); const shouldRequired = computed(() => { @@ -283,7 +284,7 @@ onUnmounted(() => {