Browse Source

Merge pull request #1335 from colinin/dev

chore: upgrade abp framework to 9.3.5
pull/1336/head
yx lin 4 months ago
committed by GitHub
parent
commit
ff99632a83
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/build.yml
  2. 2
      .github/workflows/publish.yml
  3. 2
      .github/workflows/release.yml
  4. 30
      Directory.Packages.props
  5. 30
      apps/vben5/.vscode/extensions.json
  6. 37
      apps/vben5/.vscode/global.code-snippets
  7. 51
      apps/vben5/.vscode/launch.json
  8. 241
      apps/vben5/.vscode/settings.json
  9. 4
      apps/vben5/apps/app-antd/package.json
  10. 8
      apps/vben5/apps/app-antd/src/adapter/component/index.ts
  11. 6
      apps/vben5/apps/app-antd/src/adapter/request/index.ts
  12. 67
      apps/vben5/apps/app-antd/src/layouts/basic.vue
  13. 15
      apps/vben5/apps/app-antd/src/locales/index.ts
  14. 4
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  15. 4
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  16. 8
      apps/vben5/apps/app-antd/src/preferences.ts
  17. 83
      apps/vben5/apps/app-antd/src/store/auth.ts
  18. 46
      apps/vben5/apps/app-antd/src/views/_core/fallback/not-found.vue
  19. 89
      apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue
  20. 262
      apps/vben5/apps/app-antd/src/views/dashboard/workspace/index.vue
  21. 6
      apps/vben5/packages/@abp/account/src/api/index.ts
  22. 58
      apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts
  23. 46
      apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts
  24. 73
      apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts
  25. 37
      apps/vben5/packages/@abp/account/src/api/useScanQrCodeApi.ts
  26. 83
      apps/vben5/packages/@abp/account/src/api/useTokenApi.ts
  27. 29
      apps/vben5/packages/@abp/account/src/api/useUserInfoApi.ts
  28. 43
      apps/vben5/packages/@abp/account/src/components/MySetting.vue
  29. 4
      apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue
  30. 38
      apps/vben5/packages/@abp/account/src/components/components/BindSettings.vue
  31. 9
      apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue
  32. 2
      apps/vben5/packages/@abp/account/src/hooks/index.ts
  33. 3
      apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts
  34. 28
      apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts
  35. 17
      apps/vben5/packages/@abp/account/src/types/bind.ts
  36. 32
      apps/vben5/packages/@abp/account/src/types/external-logins.ts
  37. 2
      apps/vben5/packages/@abp/account/src/types/index.ts
  38. 11
      apps/vben5/packages/@abp/account/src/types/token.ts
  39. 138
      apps/vben5/packages/@abp/account/src/utils/auth.ts
  40. 19
      apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue
  41. 96
      apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingDrawer.vue
  42. 24
      apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingTable.vue
  43. 9
      apps/vben5/packages/@abp/core/package.json
  44. 9
      apps/vben5/packages/@abp/core/src/store/abp.ts
  45. 61
      apps/vben5/packages/@abp/core/src/utils/date.ts
  46. 2
      apps/vben5/packages/@abp/core/src/utils/index.ts
  47. 3
      apps/vben5/packages/@abp/core/src/utils/is.ts
  48. 31
      apps/vben5/packages/@abp/core/src/utils/table.ts
  49. 42
      apps/vben5/packages/@abp/core/src/utils/uuid.ts
  50. 17
      apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue
  51. 15
      apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue
  52. 96
      apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue
  53. 64
      apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionTable.vue
  54. 13
      apps/vben5/packages/@abp/gdpr/src/components/GdprTable.vue
  55. 25
      apps/vben5/packages/@abp/identity/src/components/claim-types/ClaimTypeTable.vue
  56. 23
      apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitRoleTable.vue
  57. 54
      apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitUserTable.vue
  58. 29
      apps/vben5/packages/@abp/identity/src/components/organization-units/SelectMemberModal.vue
  59. 28
      apps/vben5/packages/@abp/identity/src/components/organization-units/SelectRoleModal.vue
  60. 21
      apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue
  61. 19
      apps/vben5/packages/@abp/identity/src/components/security-logs/SecurityLogTable.vue
  62. 19
      apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue
  63. 18
      apps/vben5/packages/@abp/identity/src/components/sessions/UserSessionTable.vue
  64. 43
      apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue
  65. 61
      apps/vben5/packages/@abp/localization/src/components/languages/LocalizationLanguageTable.vue
  66. 61
      apps/vben5/packages/@abp/localization/src/components/resources/LocalizationResourceTable.vue
  67. 61
      apps/vben5/packages/@abp/localization/src/components/texts/LocalizationTextTable.vue
  68. 60
      apps/vben5/packages/@abp/notifications/src/components/definitions/groups/NotificationGroupDefinitionTable.vue
  69. 77
      apps/vben5/packages/@abp/notifications/src/components/definitions/notifications/NotificationDefinitionTable.vue
  70. 14
      apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue
  71. 32
      apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue
  72. 22
      apps/vben5/packages/@abp/openiddict/src/components/authorizations/AuthorizationTable.vue
  73. 27
      apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeTable.vue
  74. 23
      apps/vben5/packages/@abp/openiddict/src/components/tokens/TokenTable.vue
  75. 16
      apps/vben5/packages/@abp/oss/src/components/containers/ContainerTable.vue
  76. 19
      apps/vben5/packages/@abp/oss/src/components/objects/FileList.vue
  77. 2
      apps/vben5/packages/@abp/oss/src/components/objects/FolderTree.vue
  78. 60
      apps/vben5/packages/@abp/permissions/src/components/definitions/groups/PermissionGroupDefinitionTable.vue
  79. 67
      apps/vben5/packages/@abp/permissions/src/components/definitions/permissions/PermissionDefinitionTable.vue
  80. 5
      apps/vben5/packages/@abp/platform/package.json
  81. 1
      apps/vben5/packages/@abp/platform/src/api/index.ts
  82. 62
      apps/vben5/packages/@abp/platform/src/api/useMyFavoriteMenusApi.ts
  83. 72
      apps/vben5/packages/@abp/platform/src/components/data-dictionaries/DataDictionaryItemDrawer.vue
  84. 60
      apps/vben5/packages/@abp/platform/src/components/data-dictionaries/DataDictionaryTable.vue
  85. 2
      apps/vben5/packages/@abp/platform/src/components/index.ts
  86. 22
      apps/vben5/packages/@abp/platform/src/components/layouts/LayoutTable.vue
  87. 52
      apps/vben5/packages/@abp/platform/src/components/menus/MenuDrawer.vue
  88. 79
      apps/vben5/packages/@abp/platform/src/components/menus/MenuTable.vue
  89. 15
      apps/vben5/packages/@abp/platform/src/components/menus/types.ts
  90. 26
      apps/vben5/packages/@abp/platform/src/components/messages/email/EmailMessageTable.vue
  91. 25
      apps/vben5/packages/@abp/platform/src/components/messages/sms/SmsMessageTable.vue
  92. 45
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchHeader.vue
  93. 124
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue
  94. 137
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue
  95. 64
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTodo.vue
  96. 65
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTrends.vue
  97. 218
      apps/vben5/packages/@abp/platform/src/components/workbench/index.vue
  98. 10
      apps/vben5/packages/@abp/platform/src/components/workbench/types.ts
  99. 13
      apps/vben5/packages/@abp/platform/src/hooks/useMenuTransform.ts
  100. 1
      apps/vben5/packages/@abp/platform/src/index.ts

2
.github/workflows/build.yml

@ -7,7 +7,7 @@ on:
- "**.csproj"
env:
DOTNET_VERSION: "9.0.301"
DOTNET_VERSION: "9.0.304"
jobs:
build:

2
.github/workflows/publish.yml

@ -4,7 +4,7 @@ on:
pull_request:
branches: [ main ]
env:
DOTNET_VERSION: "9.0.301"
DOTNET_VERSION: "9.0.304"
jobs:
publish:

2
.github/workflows/release.yml

@ -14,4 +14,4 @@ jobs:
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
automatic_release_tag: "9.2.3"
automatic_release_tag: "9.3.5"

30
Directory.Packages.props

@ -3,16 +3,16 @@
<DotNetCoreCAPPackageVersion>8.3.5</DotNetCoreCAPPackageVersion>
<ElsaPackageVersion>2.15.2</ElsaPackageVersion>
<ElsaNextPackageVersion>3.3.5</ElsaNextPackageVersion>
<VoloAbpPackageVersion>9.2.3</VoloAbpPackageVersion>
<LINGYUNAbpPackageVersion>9.2.3</LINGYUNAbpPackageVersion>
<MicrosoftExtensionsPackageVersion>9.0.4</MicrosoftExtensionsPackageVersion>
<MicrosoftAspNetCorePackageVersion>9.0.4</MicrosoftAspNetCorePackageVersion>
<MicrosoftEntityFrameworkCorePackageVersion>9.0.4</MicrosoftEntityFrameworkCorePackageVersion>
<VoloAbpPackageVersion>9.3.5</VoloAbpPackageVersion>
<LINGYUNAbpPackageVersion>9.3.5</LINGYUNAbpPackageVersion>
<MicrosoftExtensionsPackageVersion>9.0.5</MicrosoftExtensionsPackageVersion>
<MicrosoftAspNetCorePackageVersion>9.0.5</MicrosoftAspNetCorePackageVersion>
<MicrosoftEntityFrameworkCorePackageVersion>9.0.5</MicrosoftEntityFrameworkCorePackageVersion>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- Abp Framework -->
<ItemGroup>
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite" Version="4.2.3" />
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite" Version="4.3.5" />
<PackageVersion Include="Volo.Abp.Core" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Account.Application" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Account.Application.Contracts" Version="$(VoloAbpPackageVersion)" />
@ -63,7 +63,8 @@
<PackageVersion Include="Volo.Abp.DistributedLocking.Abstractions" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.DistributedLocking" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EntityFrameworkCore.MySql" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EntityFrameworkCore.MySQL" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EntityFrameworkCore.MySQL.Pomelo" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EntityFrameworkCore.PostgreSql" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Features" Version="$(VoloAbpPackageVersion)" />
@ -157,7 +158,7 @@
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Protocols.Json" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.5" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(MicrosoftEntityFrameworkCorePackageVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCorePackageVersion)" />
@ -174,8 +175,9 @@
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.5.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.3.efcore.9.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql.Json.Microsoft" Version="9.0.0-preview.3.efcore.9.0.0" />
<PackageVersion Include="MySql.EntityFrameworkCore" Version="9.0.6" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql.Json.Microsoft" Version="9.0.0" />
</ItemGroup>
<!-- Elsa -->
<ItemGroup>
@ -264,7 +266,7 @@
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="23.6.1" />
<PackageVersion Include="Npgsql" Version="9.0.2" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.6" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.4" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.5" />
</ItemGroup>
<!-- Other -->
<ItemGroup>
@ -276,7 +278,7 @@
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Twitter" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.9.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.12.0" />
<PackageVersion Include="aliyun-net-sdk-core" Version="1.5.10" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageVersion Include="AgileConfig.Client" Version="1.6.9" />
@ -299,8 +301,8 @@
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="NRules" Version="0.9.2" />
<PackageVersion Include="Ocelot.Provider.Polly" Version="20.0.0" />
<PackageVersion Include="OpenIddict.Server.DataProtection" Version="6.2.1" />
<PackageVersion Include="OpenIddict.Validation.DataProtection" Version="6.2.1" />
<PackageVersion Include="OpenIddict.Server.DataProtection" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Validation.DataProtection" Version="6.4.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />

30
apps/vben5/.vscode/extensions.json

@ -0,0 +1,30 @@
{
"recommendations": [
// Vue 3
"Vue.volar",
// ESLint JavaScript VS Code
"dbaeumer.vscode-eslint",
// Visual Studio Code Stylelint
"stylelint.vscode-stylelint",
// 使 Prettier
"esbenp.prettier-vscode",
// dotenv
"mikestead.dotenv",
//
"streetsidesoftware.code-spell-checker",
// Tailwind CSS VS Code
"bradlc.vscode-tailwindcss",
// iconify
"antfu.iconify",
// i18n
"Lokalise.i18n-ally",
// CSS
"vunguyentuan.vscode-css-variables",
// package.json PNPM catalog
"antfu.pnpm-catalog-lens"
],
"unwantedRecommendations": [
// volar
"octref.vetur"
]
}

37
apps/vben5/.vscode/global.code-snippets

@ -0,0 +1,37 @@
{
"import": {
"scope": "javascript,typescript",
"prefix": "im",
"body": ["import { $2 } from '$1';"],
"description": "Import a module",
},
"export-all": {
"scope": "javascript,typescript",
"prefix": "ex",
"body": ["export * from '$1';"],
"description": "Export a module",
},
"vue-script-setup": {
"scope": "vue",
"prefix": "<sc",
"body": [
"<script setup lang=\"ts\">",
"const props = defineProps<{",
" modelValue?: boolean,",
"}>()",
"$1",
"</script>",
"",
"<template>",
" <div>",
" <slot/>",
" </div>",
"</template>",
],
},
"vue-computed": {
"scope": "javascript,typescript,vue",
"prefix": "com",
"body": ["computed(() => { $1 })"],
},
}

51
apps/vben5/.vscode/launch.json

@ -0,0 +1,51 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"name": "vben admin playground dev",
"request": "launch",
"url": "http://localhost:5555",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}/playground"
},
{
"type": "chrome",
"name": "vben abp antd dev",
"request": "launch",
"url": "http://localhost:5666",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/app-antd"
},
{
"type": "chrome",
"name": "vben admin antd dev",
"request": "launch",
"url": "http://localhost:5666",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-antd"
},
{
"type": "chrome",
"name": "vben admin ele dev",
"request": "launch",
"url": "http://localhost:5777",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-ele"
},
{
"type": "chrome",
"name": "vben admin naive dev",
"request": "launch",
"url": "http://localhost:5888",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-naive"
}
]
}

241
apps/vben5/.vscode/settings.json

@ -0,0 +1,241 @@
{
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts",
// workbench
"workbench.list.smoothScrolling": true,
"workbench.startupEditor": "newUntitledFile",
"workbench.tree.indent": 10,
"workbench.editor.highlightModifiedTabs": true,
"workbench.editor.closeOnFileDelete": true,
"workbench.editor.limit.enabled": true,
"workbench.editor.limit.perEditorGroup": true,
"workbench.editor.limit.value": 5,
// editor
"editor.tabSize": 2,
"editor.detectIndentation": false,
"editor.cursorBlinking": "expand",
"editor.largeFileOptimizations": true,
"editor.accessibilitySupport": "off",
"editor.cursorSmoothCaretAnimation": "on",
"editor.guides.bracketPairs": "active",
"editor.inlineSuggest.enabled": true,
"editor.suggestSelection": "recentlyUsedByPrefix",
"editor.acceptSuggestionOnEnter": "smart",
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.stickyScroll.enabled": true,
"editor.hover.sticky": true,
"editor.suggest.insertMode": "replace",
"editor.bracketPairColorization.enabled": true,
"editor.autoClosingBrackets": "beforeWhitespace",
"editor.autoClosingDelete": "always",
"editor.autoClosingOvertype": "always",
"editor.autoClosingQuotes": "beforeWhitespace",
"editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit",
"source.organizeImports": "never"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// extensions
"extensions.ignoreRecommendations": true,
// terminal
"terminal.integrated.cursorBlinking": true,
"terminal.integrated.persistentSessionReviveProcess": "never",
"terminal.integrated.tabs.enabled": true,
"terminal.integrated.scrollback": 10000,
"terminal.integrated.stickyScroll.enabled": true,
// files
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.simpleDialog.enable": true,
"files.associations": {
"*.ejs": "html",
"*.art": "html",
"**/tsconfig.json": "jsonc",
"*.json": "jsonc",
"package.json": "json"
},
"files.exclude": {
"**/.eslintcache": true,
"**/bower_components": true,
"**/.turbo": true,
"**/.idea": true,
"**/.vitepress": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.stylelintcache": true,
"**/.DS_Store": true,
"**/vite.config.mts.*": true,
"**/tea.yaml": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
// search
"search.searchEditor.singleClickBehaviour": "peekDefinition",
"search.followSymlinks": false,
// 使/
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.github": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.vitepress/cache": true,
"**/.idea": true,
"**/.vscode": false,
"**/.yarn": true,
"**/tmp": true,
"*.xml": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"**/pnpm-lock.yaml": true,
"**/yarn.lock": true
},
"debug.onTaskErrors": "debugAnyway",
"diffEditor.ignoreTrimWhitespace": false,
"npm.packageManager": "pnpm",
"css.validate": false,
"less.validate": false,
"scss.validate": false,
// extension
"emmet.showSuggestionsAsSnippets": true,
"emmet.triggerExpansionOnTab": false,
"errorLens.enabledDiagnosticLevels": ["warning", "error"],
"errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"],
"stylelint.enable": true,
"stylelint.packageManager": "pnpm",
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
"stylelint.customSyntax": "postcss-html",
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
"typescript.inlayHints.enumMemberValues.enabled": true,
"typescript.preferences.preferTypeOnlyAutoImports": true,
"typescript.preferences.includePackageJsonAutoImports": "on",
"eslint.validate": [
"javascript",
"typescript",
"javascriptreact",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"json5"
],
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
"github.copilot.enable": {
"*": true,
"markdown": true,
"plaintext": false,
"yaml": false
},
"cssVariables.lookupFiles": ["packages/core/base/design/src/**/*.css"],
"i18n-ally.localesPaths": [
"packages/locales/src/langs",
"playground/src/locales/langs",
"apps/*/src/locales/langs"
],
"i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}",
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
//
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"*.ts": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx, $(capture).d.ts",
"*.tsx": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx,$(capture).d.ts",
"*.env": "$(capture).env.*",
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml",
"tailwind.config.mjs": "postcss.*"
},
"commentTranslate.hover.enabled": false,
"commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib",
"oxc.enable": false,
"cSpell.words": [
"archiver",
"axios",
"dotenv",
"isequal",
"jspm",
"napi",
"nolebase",
"rollup",
"vitest"
]
}

4
apps/vben5/apps/app-antd/package.json

@ -46,6 +46,7 @@
"@abp/text-templating": "workspace:*",
"@abp/ui": "workspace:*",
"@abp/webhooks": "workspace:*",
"@abp/wechat": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
@ -66,6 +67,7 @@
"dayjs": "catalog:",
"pinia": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"
"vue-router": "catalog:",
"vue3-colorpicker": "catalog:"
}
}

8
apps/vben5/apps/app-antd/src/adapter/component/index.ts

@ -31,6 +31,12 @@ const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const ColorPicker = defineAsyncComponent(() =>
import('vue3-colorpicker').then((res) => {
import('vue3-colorpicker/style.css');
return res.ColorPicker;
}),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
@ -117,6 +123,7 @@ export type ComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'ColorPicker'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
@ -182,6 +189,7 @@ async function initComponentAdapter() {
AutoComplete,
Checkbox,
CheckboxGroup,
ColorPicker,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {

6
apps/vben5/apps/app-antd/src/adapter/request/index.ts

@ -37,7 +37,8 @@ export function initRequestClient() {
async function doRefreshToken() {
const authStore = useAuthStore();
try {
return await authStore.refreshSession();
const token = await authStore.refreshSession();
return token ?? '';
} catch {
console.warn('The refresh token has expired or is unavailable.');
}
@ -60,6 +61,9 @@ export function initRequestClient() {
if (abpStore.tenantId) {
config.headers.__tenant = abpStore.tenantId;
}
if (abpStore.xsrfToken) {
config.headers.RequestVerificationToken = abpStore.xsrfToken;
}
return config;
},
});

67
apps/vben5/apps/app-antd/src/layouts/basic.vue

@ -5,14 +5,8 @@ import { computed, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import {
BookOpenText,
CircleHelp,
createIconifyIcon,
MdiGithub,
} from '@vben/icons';
import { createIconifyIcon } from '@vben/icons';
import {
BasicLayout,
LockScreen,
@ -21,7 +15,6 @@ import {
} from '@vben/layouts';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import { useAbpStore } from '@abp/core';
@ -32,36 +25,7 @@ import LoginForm from '#/views/_core/authentication/login.vue';
const UserSettingsIcon = createIconifyIcon('tdesign:user-setting');
const notifications = ref<NotificationItem[]>([
{
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
date: '3小时前',
isRead: true,
message: '描述信息描述信息描述信息',
title: '收到了 14 份新周报',
},
{
avatar: 'https://avatar.vercel.sh/1',
date: '刚刚',
isRead: false,
message: '描述信息描述信息描述信息',
title: '朱偏右 回复了你',
},
{
avatar: 'https://avatar.vercel.sh/1',
date: '2024-01-01',
isRead: false,
message: '描述信息描述信息描述信息',
title: '曲丽丽 评论了你',
},
{
avatar: 'https://avatar.vercel.sh/satori',
date: '1天前',
isRead: false,
message: '描述信息描述信息描述信息',
title: '代办提醒',
},
]);
const notifications = ref<NotificationItem[]>([]);
useSessions();
@ -83,33 +47,6 @@ const menus = computed(() => [
icon: UserSettingsIcon,
text: $t('abp.account.settings.title'),
},
{
handler: () => {
openWindow(VBEN_DOC_URL, {
target: '_blank',
});
},
icon: BookOpenText,
text: $t('ui.widgets.document'),
},
{
handler: () => {
openWindow(VBEN_GITHUB_URL, {
target: '_blank',
});
},
icon: MdiGithub,
text: 'GitHub',
},
{
handler: () => {
openWindow(`${VBEN_GITHUB_URL}/issues`, {
target: '_blank',
});
},
icon: CircleHelp,
text: $t('ui.widgets.qa'),
},
]);
const userInfo = computed(() => {

15
apps/vben5/apps/app-antd/src/locales/index.ts

@ -15,6 +15,7 @@ import { preferences } from '@vben/preferences';
import { useAbpStore } from '@abp/core';
import { useLocalizationsApi } from '@abp/localization';
import { loadPaltformMessages } from '@abp/platform';
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
@ -34,13 +35,17 @@ const localesMap = loadLocalesMapFromDir(
* @param lang
*/
async function loadMessages(lang: SupportedLanguagesType) {
const [appLocaleMessages, _, abpLocales] = await Promise.all([
localesMap[lang]?.(),
loadThirdPartyMessage(lang),
loadAbpLocale(lang),
]);
const [appLocaleMessages, platformLocales, _, abpLocales] = await Promise.all(
[
localesMap[lang]?.(),
loadPaltformMessages(lang),
loadThirdPartyMessage(lang),
loadAbpLocale(lang),
],
);
return {
...appLocaleMessages?.default,
...platformLocales?.default,
...abpLocales,
};
}

4
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -142,5 +142,9 @@
"title": "Object storage",
"containers": "Containers",
"objects": "Files"
},
"wechat": {
"title": "WeChat",
"settings": "Settings"
}
}

4
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -142,5 +142,9 @@
"title": "对象存储",
"containers": "容器管理",
"objects": "文件管理"
},
"wechat": {
"title": "微信集成",
"settings": "微信设置"
}
}

8
apps/vben5/apps/app-antd/src/preferences.ts

@ -9,7 +9,15 @@ export const overridesPreferences = defineOverridesPreferences({
// overrides
app: {
accessMode: 'backend',
defaultHomePath: '/workspace',
enableRefreshToken: true,
name: import.meta.env.VITE_APP_TITLE,
},
theme: {
mode: 'auto',
radius: '0.25',
},
widget: {
notification: false,
},
});

83
apps/vben5/apps/app-antd/src/store/auth.ts

@ -9,14 +9,7 @@ import { LOGIN_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import {
useOidcClient,
usePhoneLoginApi,
useProfileApi,
useQrCodeLoginApi,
useTokenApi,
useUserInfoApi,
} from '@abp/account';
import { useOAuthService, useProfileApi } from '@abp/account';
import { Events, useAbpStore, useEventBus } from '@abp/core';
import { notification } from 'ant-design-vue';
import { defineStore } from 'pinia';
@ -26,48 +19,35 @@ import { $t } from '#/locales';
export const useAuthStore = defineStore('auth', () => {
const { publish } = useEventBus();
const { loginApi, refreshTokenApi } = useTokenApi();
const { loginApi: qrcodeLoginApi } = useQrCodeLoginApi();
const { loginApi: phoneLoginApi } = usePhoneLoginApi();
const { getUserInfoApi } = useUserInfoApi();
const { getConfigApi } = useAbpConfigApi();
const { getPictureApi } = useProfileApi();
const accessStore = useAccessStore();
const userStore = useUserStore();
const abpStore = useAbpStore();
const router = useRouter();
const oidcClient = useOidcClient();
const oAuthService = useOAuthService();
const loginLoading = ref(false);
async function refreshSession() {
if (await oidcClient.getAccessToken()) {
const user = await oidcClient.refreshToken();
if (await oAuthService.getAccessToken()) {
const user = await oAuthService.refreshToken();
const newToken = `${user?.token_type} ${user?.access_token}`;
accessStore.setAccessToken(newToken);
if (user?.refresh_token) {
accessStore.setRefreshToken(user.refresh_token);
}
return newToken;
} else {
const { accessToken, tokenType, refreshToken } = await refreshTokenApi({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
refreshToken: accessStore.refreshToken!,
});
const newToken = `${tokenType} ${accessToken}`;
accessStore.setAccessToken(newToken);
accessStore.setRefreshToken(refreshToken);
return newToken;
}
}
async function oidcLogin() {
await oidcClient.login();
await oAuthService.login();
}
async function oidcCallback() {
try {
const user = await oidcClient.handleCallback();
const user = await oAuthService.handleCallback();
return await _loginSuccess({
accessToken: user.access_token,
tokenType: user.token_type,
@ -87,8 +67,17 @@ export const useAuthStore = defineStore('auth', () => {
) {
try {
loginLoading.value = true;
const result = await qrcodeLoginApi({ key, tenantId });
return await _loginSuccess(result, onSuccess);
const user = await oAuthService.loginByQrCode({ key, tenantId });
return await _loginSuccess(
{
accessToken: user.access_token,
tokenType: user.token_type,
refreshToken: user.refresh_token ?? '',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expiresIn: user.expires_in!,
},
onSuccess,
);
} finally {
loginLoading.value = false;
}
@ -101,8 +90,17 @@ export const useAuthStore = defineStore('auth', () => {
) {
try {
loginLoading.value = true;
const result = await phoneLoginApi({ phoneNumber, code });
return await _loginSuccess(result, onSuccess);
const user = await oAuthService.loginBySmsCode({ phoneNumber, code });
return await _loginSuccess(
{
accessToken: user.access_token,
tokenType: user.token_type,
refreshToken: user.refresh_token ?? '',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expiresIn: user.expires_in!,
},
onSuccess,
);
} finally {
loginLoading.value = false;
}
@ -119,8 +117,17 @@ export const useAuthStore = defineStore('auth', () => {
) {
try {
loginLoading.value = true;
const result = await loginApi(params as any);
return await _loginSuccess(result, onSuccess);
const user = await oAuthService.loginByPassword(params as any);
return await _loginSuccess(
{
accessToken: user.access_token,
tokenType: user.token_type,
refreshToken: user.refresh_token ?? '',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expiresIn: user.expires_in!,
},
onSuccess,
);
} finally {
loginLoading.value = false;
}
@ -128,9 +135,11 @@ export const useAuthStore = defineStore('auth', () => {
async function logout(redirect: boolean = true) {
try {
if (await oidcClient.getAccessToken()) {
if (await oAuthService.getAccessToken()) {
accessStore.setAccessToken(null);
await oidcClient.logout();
await oAuthService.logout();
} else {
await oAuthService.revokeTokens();
}
} catch {
// 不做任何处理
@ -153,7 +162,11 @@ export const useAuthStore = defineStore('auth', () => {
async function fetchUserInfo() {
let userInfo: null | (UserInfo & { [key: string]: any }) = null;
const userInfoRes = await getUserInfoApi();
let userInfoRes: { [key: string]: any } = {};
const user = await oAuthService.getUser();
if (user) {
userInfoRes = user.profile;
}
const abpConfig = await getConfigApi();
const picture = await getPictureApi();
userInfo = {

46
apps/vben5/apps/app-antd/src/views/_core/fallback/not-found.vue

@ -1,9 +1,51 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
import { useRouter } from 'vue-router';
import { Fallback, VbenButton } from '@vben/common-ui';
import { ArrowLeft, LogOut } from '@vben/icons';
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import { Modal } from 'ant-design-vue';
import { useAuthStore } from '#/store';
defineOptions({ name: 'Fallback404Demo' });
const authStore = useAuthStore();
const { push } = useRouter();
//
function back() {
push(preferences.app.defaultHomePath);
}
// 退
function logout() {
Modal.confirm({
centered: true,
title: $t('common.logout'),
content: $t('ui.widgets.logoutTip'),
async onOk() {
await authStore.logout();
},
});
}
</script>
<template>
<Fallback status="404" />
<Fallback status="404">
<template #action>
<div class="flex gap-2">
<VbenButton size="lg" @click="back">
<ArrowLeft class="mr-2 size-4" />
{{ $t('common.backToHome') }}
</VbenButton>
<VbenButton size="lg" variant="destructive" @click="logout">
<LogOut class="mr-2 size-4" />
{{ $t('common.logout') }}
</VbenButton>
</div>
</template>
</Fallback>
</template>

89
apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue

@ -1,15 +1,98 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import type { BindItem } from '@abp/account';
import { MySetting } from '@abp/account';
import { defineAsyncComponent, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { useRefresh } from '@vben/hooks';
import { $t } from '@vben/locales';
import { MySetting, useExternalLoginsApi } from '@abp/account';
import { message, Modal } from 'ant-design-vue';
defineOptions({
name: 'Vben5AccountMySettings',
});
const { bindWorkWeixinApi, getExternalLoginsApi, removeExternalLoginApi } =
useExternalLoginsApi();
const { refresh } = useRefresh();
const [WechatWorkUserBindModal, weComBindModal] = useVbenModal({
connectedComponent: defineAsyncComponent(async () => {
const component = await import('@abp/wechat');
return component.WechatWorkUserBinder;
}),
});
const externalLogins = ref<BindItem[]>([]);
async function onBindWorkWeixin(code: string) {
weComBindModal.setState({ submitting: true });
try {
await bindWorkWeixinApi({ code });
weComBindModal.close();
message.success($t('AbpAccount.BindSuccessfully'));
refresh();
} finally {
weComBindModal.setState({ submitting: false });
}
}
async function onRemoveBind(provider: string, key: string) {
Modal.confirm({
title: $t('AbpUi.AreYouSure'),
centered: true,
content: $t('AbpAccount.CancelBindWarningMessage'),
async onOk() {
await removeExternalLoginApi({
loginProvider: provider,
providerKey: key,
});
message.success($t('AbpAccount.CancelBindSuccessfully'));
},
});
}
function onBind(provider: string) {
switch (provider.toLocaleLowerCase()) {
case 'workweixin': {
weComBindModal.open();
}
}
}
async function onClick(provider: string, key?: string) {
if (key) {
await onRemoveBind(provider, key);
return;
}
onBind(provider);
}
async function onInit() {
const res = await getExternalLoginsApi();
externalLogins.value = res.externalLogins.map((item) => {
const userLogin = res.userLogins.find((x) => x.loginProvider === item.name);
return {
title: item.displayName,
description: userLogin?.providerKey ?? $t('AbpAccount.UnBind'),
buttons: [
{
title: userLogin?.providerKey
? $t('AbpAccount.CancelBind')
: $t('AbpAccount.Bind'),
type: 'link',
click: () => onClick(item.name, userLogin?.providerKey),
},
],
};
});
}
</script>
<template>
<Page>
<MySetting />
<MySetting :bind-items="externalLogins" @on-bind-init="onInit" />
<WechatWorkUserBindModal @on-login="onBindWorkWeixin" />
</Page>
</template>

262
apps/vben5/apps/app-antd/src/views/dashboard/workspace/index.vue

@ -1,266 +1,32 @@
<script lang="ts" setup>
import type {
WorkbenchProjectItem,
WorkbenchQuickNavItem,
WorkbenchTodoItem,
WorkbenchTrendItem,
} from '@vben/common-ui';
<script setup lang="ts">
import type { FavoriteMenu } from '@abp/platform';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
WorkbenchHeader,
WorkbenchProject,
WorkbenchQuickNav,
WorkbenchTodo,
WorkbenchTrends,
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
//
// url navTo
// url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
content: '不要等待机会,而要创造机会。',
date: '2021-04-01',
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
content: '现在的你决定将来的你。',
date: '2021-04-01',
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
content: '没有什么才能比努力更重要。',
date: '2021-04-01',
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
content: '热情和欲望可以突破一切难关。',
date: '2021-04-01',
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
content: '健康的身体是实现目标的基石。',
date: '2021-04-01',
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
content: '路是走出来的,而不是空想出来的。',
date: '2021-04-01',
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// url 使 http
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // URL
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
const todoItems = ref<WorkbenchTodoItem[]>([
{
completed: false,
content: `审查最近提交到Git仓库的前端代码,确保代码质量和规范。`,
date: '2024-07-30 11:00:00',
title: '审查前端代码提交',
},
{
completed: true,
content: `检查并优化系统性能,降低CPU使用率。`,
date: '2024-07-30 11:00:00',
title: '系统性能优化',
},
{
completed: false,
content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `,
date: '2024-07-30 11:00:00',
title: '安全检查',
},
{
completed: false,
content: `更新项目中的所有npm依赖包,确保使用最新版本。`,
date: '2024-07-30 11:00:00',
title: '更新项目依赖',
},
{
completed: false,
content: `修复用户报告的页面UI显示问题,确保在不同浏览器中显示一致。 `,
date: '2024-07-30 11:00:00',
title: '修复UI显示问题',
},
]);
const trendItems: WorkbenchTrendItem[] = [
{
avatar: 'svg:avatar-1',
content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
date: '刚刚',
title: '威廉',
},
{
avatar: 'svg:avatar-2',
content: `关注了 <a>威廉</a> `,
date: '1个小时前',
title: '艾文',
},
{
avatar: 'svg:avatar-3',
content: `发布了 <a>个人动态</a> `,
date: '1天前',
title: '克里斯',
},
{
avatar: 'svg:avatar-4',
content: `发表文章 <a>如何编写一个Vite插件</a> `,
date: '2天前',
title: 'Vben',
},
{
avatar: 'svg:avatar-1',
content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
date: '3天前',
title: '皮特',
},
{
avatar: 'svg:avatar-2',
content: `关闭了问题 <a>如何运行项目</a> `,
date: '1周前',
title: '杰克',
},
{
avatar: 'svg:avatar-3',
content: `发布了 <a>个人动态</a> `,
date: '1周前',
title: '威廉',
},
{
avatar: 'svg:avatar-4',
content: `推送了代码到 <a>Github</a>`,
date: '2021-04-01 20:00',
title: '威廉',
},
{
avatar: 'svg:avatar-4',
content: `发表文章 <a>如何编写使用 Admin Vben</a> `,
date: '2021-03-01 20:00',
title: 'Vben',
},
];
import { Workbench } from '@abp/platform';
const router = useRouter();
//
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
function navTo(menu: FavoriteMenu) {
if (menu.path?.startsWith('http')) {
openWindow(menu.path);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
if (menu.path?.startsWith('/')) {
router.push(menu.path).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
console.warn(
`Unknown URL for navigation item: ${menu.displayName} -> ${menu.path}`,
);
}
}
</script>
<template>
<div class="p-5">
<WorkbenchHeader
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"
>
<template #title>
早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧
</template>
<template #description> 今日晴20 - 32 </template>
</WorkbenchHeader>
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
<WorkbenchQuickNav
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
</div>
</div>
</div>
<Workbench @nav-to="navTo" />
</template>
<style scoped></style>

6
apps/vben5/packages/@abp/account/src/api/index.ts

@ -1,7 +1,5 @@
export { useAccountApi } from './useAccountApi';
export { useExternalLoginsApi } from './useExternalLoginsApi';
export { useMySessionApi } from './useMySessionApi';
export { usePhoneLoginApi } from './usePhoneLoginApi';
export { useProfileApi } from './useProfileApi';
export { useQrCodeLoginApi } from './useQrCodeLoginApi';
export { useTokenApi } from './useTokenApi';
export { useUserInfoApi } from './useUserInfoApi';
export { useScanQrCodeApi } from './useScanQrCodeApi';

58
apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts

@ -0,0 +1,58 @@
import type {
ExternalLoginResultDto,
RemoveExternalLoginInput,
WorkWeixinLoginBindInput,
} from '../types/external-logins';
import { useRequest } from '@abp/request';
export function useExternalLoginsApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
* @returns { Promise<void> }
*/
async function bindWorkWeixinApi(
input: WorkWeixinLoginBindInput,
): Promise<void> {
return await request(`/api/account/oauth/work-weixin/bind`, {
method: 'POST',
data: input,
});
}
/**
*
* @returns
*/
async function getExternalLoginsApi(): Promise<ExternalLoginResultDto> {
return await request<ExternalLoginResultDto>(
`/api/account/external-logins`,
{
method: 'GET',
},
);
}
/**
*
* @returns { Promise<void> }
*/
async function removeExternalLoginApi(
input: RemoveExternalLoginInput,
): Promise<void> {
return await request(`/api/account/external-logins/remove`, {
method: 'DELETE',
params: input,
});
}
return {
cancel,
bindWorkWeixinApi,
getExternalLoginsApi,
removeExternalLoginApi,
};
}

46
apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts

@ -1,46 +0,0 @@
import type { OAuthTokenResult, PhoneNumberTokenRequest } from '../types/token';
import { useAppConfig } from '@vben/hooks';
import { useRequest } from '@abp/request';
export function usePhoneLoginApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
* @returns token
*/
async function loginApi(input: PhoneNumberTokenRequest) {
const { audience, clientId, clientSecret } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
const result = await request<OAuthTokenResult>('/connect/token', {
data: {
client_id: clientId,
client_secret: clientSecret,
grant_type: 'phone_verify',
phone_number: input.phoneNumber,
phone_verify_code: input.code,
scope: audience,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
});
return {
accessToken: result.access_token,
expiresIn: result.expires_in,
refreshToken: result.refresh_token,
tokenType: result.token_type,
};
}
return {
cancel,
loginApi,
};
}

73
apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts

@ -1,73 +0,0 @@
import type {
GenerateQrCodeResult,
QrCodeUserInfoResult,
} from '../types/qrcode';
import type { OAuthTokenResult, QrCodeTokenRequest } from '../types/token';
import { useAppConfig } from '@vben/hooks';
import { useRequest } from '@abp/request';
export function useQrCodeLoginApi() {
const { cancel, request } = useRequest();
/**
*
* @returns
*/
function generateApi(): Promise<GenerateQrCodeResult> {
return request<GenerateQrCodeResult>('/api/account/qrcode/generate', {
method: 'POST',
});
}
/**
*
* @param key Key
* @returns
*/
function checkCodeApi(key: string): Promise<QrCodeUserInfoResult> {
return request<QrCodeUserInfoResult>(`/api/account/qrcode/${key}/check`, {
method: 'GET',
});
}
/**
*
* @param input
* @returns token
*/
async function loginApi(input: QrCodeTokenRequest) {
const { audience, clientId, clientSecret } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
const result = await request<OAuthTokenResult>('/connect/token', {
data: {
client_id: clientId,
client_secret: clientSecret,
grant_type: 'qr_code',
qrcode_key: input.key,
scope: audience,
tenant_id: input.tenantId,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
});
return {
accessToken: result.access_token,
expiresIn: result.expires_in,
refreshToken: result.refresh_token,
tokenType: result.token_type,
};
}
return {
cancel,
checkCodeApi,
generateApi,
loginApi,
};
}

37
apps/vben5/packages/@abp/account/src/api/useScanQrCodeApi.ts

@ -0,0 +1,37 @@
import type {
GenerateQrCodeResult,
QrCodeUserInfoResult,
} from '../types/qrcode';
import { useRequest } from '@abp/request';
export function useScanQrCodeApi() {
const { cancel, request } = useRequest();
/**
*
* @returns
*/
function generateApi(): Promise<GenerateQrCodeResult> {
return request<GenerateQrCodeResult>('/api/account/qrcode/generate', {
method: 'POST',
});
}
/**
*
* @param key Key
* @returns
*/
function checkCodeApi(key: string): Promise<QrCodeUserInfoResult> {
return request<QrCodeUserInfoResult>(`/api/account/qrcode/${key}/check`, {
method: 'GET',
});
}
return {
cancel,
checkCodeApi,
generateApi,
};
}

83
apps/vben5/packages/@abp/account/src/api/useTokenApi.ts

@ -1,83 +0,0 @@
import type {
OAuthTokenRefreshModel,
OAuthTokenResult,
PasswordTokenRequestModel,
TokenResult,
} from '../types';
import { useAppConfig } from '@vben/hooks';
import { useRequest } from '@abp/request';
export function useTokenApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
* @returns token
*/
async function loginApi(
input: PasswordTokenRequestModel,
): Promise<TokenResult> {
const { audience, clientId, clientSecret } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
const result = await request<OAuthTokenResult>('/connect/token', {
data: {
client_id: clientId,
client_secret: clientSecret,
grant_type: 'password',
scope: audience,
...input,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
});
return {
accessToken: result.access_token,
expiresIn: result.expires_in,
refreshToken: result.refresh_token,
tokenType: result.token_type,
};
}
/**
*
* @param input
* @returns token
*/
async function refreshTokenApi(input: OAuthTokenRefreshModel) {
const { audience, clientId, clientSecret } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
const result = await request<OAuthTokenResult>('/connect/token', {
data: {
client_id: clientId,
client_secret: clientSecret,
grant_type: 'refresh_token',
refresh_token: input.refreshToken,
scope: audience,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
});
return {
accessToken: result.access_token,
expiresIn: result.expires_in,
refreshToken: result.refresh_token,
tokenType: result.token_type,
};
}
return {
cancel,
loginApi,
refreshTokenApi,
};
}

29
apps/vben5/packages/@abp/account/src/api/useUserInfoApi.ts

@ -1,29 +0,0 @@
import type { OAuthUserInfo, UserInfo } from '../types/user';
import { useRequest } from '@abp/request';
export function useUserInfoApi() {
const { cancel, request } = useRequest();
/**
*
*/
async function getUserInfoApi(): Promise<UserInfo> {
const result = await request<OAuthUserInfo>('/connect/userinfo', {
method: 'GET',
});
return {
...result,
emailVerified: result.email_verified,
givenName: result.given_name,
phoneNumberVerified: result.phone_number_verified,
preferredUsername: result.preferred_username,
uniqueName: result.unique_name,
};
}
return {
cancel,
getUserInfoApi,
};
}

43
apps/vben5/packages/@abp/account/src/components/MySetting.vue

@ -1,8 +1,9 @@
<script setup lang="ts">
import type { BindItem } from '../types/bind';
import type { ProfileDto, UpdateProfileDto } from '../types/profile';
import type { UserInfo } from '../types/user';
import { computed, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useVbenModal } from '@vben/common-ui';
@ -13,6 +14,22 @@ import { Card, Menu, message, Modal } from 'ant-design-vue';
import { useProfileApi } from '../api/useProfileApi';
interface MenuItem {
key: string;
label: string;
}
const props = defineProps<{
bindItems?: BindItem[];
disableAuthenticator?: boolean;
disableBind?: boolean;
disableNotice?: boolean;
disablePersonalData?: boolean;
disableSecurity?: boolean;
disableSession?: boolean;
}>();
const emits = defineEmits<{
(event: 'onBindInit'): void;
}>();
const AuthenticatorSettings = defineAsyncComponent(
() => import('./components/AuthenticatorSettings.vue'),
);
@ -40,7 +57,7 @@ const { query } = useRoute();
const selectedMenuKeys = ref<string[]>(['basic']);
const myProfile = ref({} as ProfileDto);
const menuItems = reactive([
const basicMenuItems: MenuItem[] = [
{
key: 'basic',
label: $t('abp.account.settings.basic.title'),
@ -69,7 +86,19 @@ const menuItems = reactive([
key: 'personal-data',
label: $t('abp.account.settings.personalDataSettings'),
},
]);
];
const getEnabledMenus = computed(() => {
return basicMenuItems.filter((x) => {
if (x.key === 'basic') return true;
if (x.key === 'authenticator') return !props.disableAuthenticator;
if (x.key === 'bind') return !props.disableBind;
if (x.key === 'notice') return !props.disableNotice;
if (x.key === 'personal-data') return !props.disablePersonalData;
if (x.key === 'security') return !props.disableSecurity;
if (x.key === 'session') return !props.disableSession;
return true; // default case for any unexpected keys
});
});
const getUserInfo = computed((): null | UserInfo => {
if (!userStore.userInfo) {
return null;
@ -155,7 +184,7 @@ onMounted(async () => {
<div class="basis-1/6">
<Menu
v-model:selected-keys="selectedMenuKeys"
:items="menuItems"
:items="getEnabledMenus"
mode="inline"
/>
</div>
@ -166,7 +195,11 @@ onMounted(async () => {
@submit="onUpdateProfile"
@picture-change="onGetProfile"
/>
<BindSettings v-else-if="selectedMenuKeys[0] === 'bind'" />
<BindSettings
v-else-if="selectedMenuKeys[0] === 'bind'"
:items="bindItems"
@on-init="emits('onBindInit')"
/>
<SecuritySettings
v-else-if="selectedMenuKeys[0] === 'security'"
:user-info="getUserInfo"

4
apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue

@ -11,7 +11,7 @@ import { VbenButton } from '@vben-core/shadcn-ui';
import { useQRCode } from '@vueuse/integrations/useQRCode';
import { Spin } from 'ant-design-vue';
import { useQrCodeLoginApi } from '../api';
import { useScanQrCodeApi } from '../api';
import { QrCodeStatus } from '../types/qrcode';
import Title from './components/LoginTitle.vue';
@ -66,7 +66,7 @@ const emits = defineEmits<{
let interval: NodeJS.Timeout;
const router = useRouter();
const { checkCodeApi, generateApi } = useQrCodeLoginApi();
const { checkCodeApi, generateApi } = useScanQrCodeApi();
const qrcodeInfo = ref<QrCodeUserInfoResult>({
key: '',

38
apps/vben5/packages/@abp/account/src/components/components/BindSettings.vue

@ -1,10 +1,44 @@
<script setup lang="ts">
import { Card, Empty } from 'ant-design-vue';
import type { BindItem } from '../../types/bind';
import { onMounted } from 'vue';
import { Button, Card, Empty, List } from 'ant-design-vue';
defineProps<{
items?: BindItem[];
}>();
const emits = defineEmits<{
(event: 'onInit'): void;
}>();
const ListItem = List.Item;
const ListItemMeta = List.Item.Meta;
onMounted(() => {
setTimeout(() => {
emits('onInit');
}, 200);
});
</script>
<template>
<Card :bordered="false" :title="$t('abp.account.settings.bindSettings')">
<Empty />
<Empty v-if="items?.length === 0" />
<List v-else item-layout="horizontal">
<ListItem v-for="item in items" :key="item.title">
<template v-if="item.buttons?.length" #extra>
<Button
v-for="button in item.buttons"
:type="button.type"
@click="button.click"
:key="button.title"
>
{{ button.title }}
</Button>
</template>
<ListItemMeta :description="item.description" :title="item.title" />
</ListItem>
</List>
</Card>
</template>

9
apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue

@ -86,13 +86,8 @@ onMounted(onGet);
</template>
<ListItemMeta
:description="$t('abp.account.settings.security.passwordDesc')"
>
<template #title>
<a href="https://www.antdv.com/">{{
$t('abp.account.settings.security.password')
}}</a>
</template>
</ListItemMeta>
:title="$t('abp.account.settings.security.password')"
/>
</ListItem>
<!-- 手机号码 -->
<ListItem>

2
apps/vben5/packages/@abp/account/src/hooks/index.ts

@ -1,2 +1,2 @@
export * from './useOAuthError';
export * from './useOidcClient';
export * from './useOAuthService';

3
apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts

@ -23,7 +23,8 @@ export function useOAuthError() {
return $t('abp.oauth.errors.requiresTwoFactor');
}
// Token已失效
case 'The token is no longer valid.': {
case 'The token is no longer valid.':
case 'The user is no longer allowed to sign in.': {
return $t('abp.oauth.errors.tokenHasExpired');
}
// 用户尝试登录次数太多,用户被锁定

28
apps/vben5/packages/@abp/account/src/hooks/useOidcClient.ts → apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts

@ -1,14 +1,36 @@
import type {
PasswordTokenRequestModel,
PhoneNumberTokenRequest,
QrCodeTokenRequest,
} from '../types/token';
import { userManager } from '../utils/auth';
export function useOidcClient() {
export function useOAuthService() {
async function login() {
return userManager.signinRedirect();
}
async function loginByPassword(input: PasswordTokenRequestModel) {
return userManager.signinResourceOwnerCredentials(input);
}
async function loginBySmsCode(input: PhoneNumberTokenRequest) {
return userManager.signinSmsCode(input);
}
async function loginByQrCode(input: QrCodeTokenRequest) {
return userManager.signinQrCode(input);
}
async function logout() {
return userManager.signoutRedirect();
}
async function revokeTokens() {
return userManager.revokeTokens(['access_token', 'refresh_token']);
}
async function refreshToken() {
return userManager.signinSilent();
}
@ -33,8 +55,12 @@ export function useOidcClient() {
return {
login,
loginByPassword,
loginBySmsCode,
loginByQrCode,
logout,
refreshToken,
revokeTokens,
getAccessToken,
isAuthenticated,
handleCallback,

17
apps/vben5/packages/@abp/account/src/types/bind.ts

@ -0,0 +1,17 @@
import type { ButtonType } from 'ant-design-vue/lib/button';
interface BindButton {
click: () => Promise<void> | void;
title: string;
type?: ButtonType;
}
interface BindItem {
buttons?: BindButton[];
description?: string;
enable?: boolean;
slot?: string;
title: string;
}
export type { BindItem };

32
apps/vben5/packages/@abp/account/src/types/external-logins.ts

@ -0,0 +1,32 @@
interface UserLoginInfoDto {
loginProvider: string;
providerDisplayName: string;
providerKey: string;
}
interface ExternalLoginInfoDto {
displayName: string;
name: string;
}
interface WorkWeixinLoginBindInput {
code: string;
}
interface ExternalLoginResultDto {
externalLogins: ExternalLoginInfoDto[];
userLogins: UserLoginInfoDto[];
}
interface RemoveExternalLoginInput {
loginProvider: string;
providerKey: string;
}
export type {
ExternalLoginInfoDto,
ExternalLoginResultDto,
RemoveExternalLoginInput,
UserLoginInfoDto,
WorkWeixinLoginBindInput,
};

2
apps/vben5/packages/@abp/account/src/types/index.ts

@ -1,4 +1,6 @@
export * from './account';
export * from './bind';
export * from './external-logins';
export * from './profile';
export * from './token';
export * from './user';

11
apps/vben5/packages/@abp/account/src/types/token.ts

@ -16,6 +16,7 @@ interface PasswordTokenRequest extends TokenRequest {
}
/** 手机号授权请求数据模型 */
interface PhoneNumberTokenRequest {
[key: string]: any;
/** 验证码 */
code: string;
/** 手机号 */
@ -23,6 +24,7 @@ interface PhoneNumberTokenRequest {
}
/** 扫码登录授权请求数据模型 */
interface QrCodeTokenRequest {
[key: string]: any;
/** 二维码Key */
key: string;
/** 租户Id */
@ -30,11 +32,19 @@ interface QrCodeTokenRequest {
}
/** 用户密码授权请求数据模型 */
interface PasswordTokenRequestModel {
[key: string]: any;
/** 用户密码 */
password: string;
/** 用户名 */
username: string;
}
/** 令牌撤销请求数据类型 */
interface RevokeTokenRequest {
/** 令牌 */
token: string;
/** 令牌类型 */
tokenType?: 'access_token' | 'refresh_token';
}
/** 令牌返回数据模型 */
interface TokenResult {
/** 访问令牌 */
@ -89,6 +99,7 @@ export type {
PasswordTokenRequestModel,
PhoneNumberTokenRequest,
QrCodeTokenRequest,
RevokeTokenRequest,
ShouldChangePasswordError,
TokenRequest,
TokenResult,

138
apps/vben5/packages/@abp/account/src/utils/auth.ts

@ -1,8 +1,137 @@
import type { Logger, UserManagerSettings } from 'oidc-client-ts';
import type {
PasswordTokenRequestModel,
PhoneNumberTokenRequest,
QrCodeTokenRequest,
} from '../types/token';
import { useAppConfig } from '@vben/hooks';
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
import { useRequest } from '@abp/request';
import {
SigninResponse,
UserManager,
WebStorageStateStore,
} from 'oidc-client-ts';
import SecureLS from 'secure-ls';
class AbpUserManager extends UserManager {
async _fetchUser(logger: Logger, body: URLSearchParams) {
const { request } = useRequest();
const url = await this.metadataService.getTokenEndpoint(false);
if (!this.settings.omitScopeWhenRequesting) {
body.set('scope', this.settings.scope);
}
const resp = await request(url, {
data: body,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
logger.debug('got signin response');
const response = new SigninResponse(new URLSearchParams());
Object.assign(response, resp);
const user = await this._buildUser(response);
if (user.profile && user.profile.sub) {
logger.info('success, signed in subject', user.profile.sub);
} else {
logger.info('no subject');
}
return user;
}
_writeChangePasswordToken(
params: URLSearchParams,
model: Record<string, any>,
) {
if (model.ChangePasswordToken) {
params.set('ChangePasswordToken', model.ChangePasswordToken);
}
if (model.NewPassword) {
params.set('NewPassword', model.NewPassword);
}
}
_writeTenantId(params: URLSearchParams, model: Record<string, any>) {
if (model.tenantId) {
params.set('tenantId', model.tenantId);
}
}
_writeTwoFactorToken(params: URLSearchParams, model: Record<string, any>) {
if (model.TwoFactorProvider) {
params.set('TwoFactorProvider', model.TwoFactorProvider);
}
if (model.TwoFactorCode) {
params.set('TwoFactorCode', model.TwoFactorCode);
}
}
_writeUserId(params: URLSearchParams, model: Record<string, any>) {
if (model.userId) {
params.set('userId', model.userId);
}
}
async signinQrCode(params: QrCodeTokenRequest) {
const logger = this._logger.create('signinQrCode');
const client_secret = this.settings.client_secret;
if (!client_secret) {
logger.error('A client_id is required');
throw new Error('A client_id is required');
}
const body = new URLSearchParams({
key: params.key,
grant_type: 'qr_code',
client_id: this.settings.client_id,
client_secret,
});
this._writeUserId(body, params);
this._writeTenantId(body, params);
this._writeTwoFactorToken(body, params);
return await this._fetchUser(logger, body);
}
override async signinResourceOwnerCredentials(
params: PasswordTokenRequestModel,
) {
const logger = this._logger.create('signinResourceOwnerCredentials');
const client_secret = this.settings.client_secret;
if (!client_secret) {
logger.error('A client_id is required');
throw new Error('A client_id is required');
}
const body = new URLSearchParams({
username: params.username,
password: params.password,
grant_type: 'password',
client_id: this.settings.client_id,
client_secret,
});
this._writeUserId(body, params);
this._writeTwoFactorToken(body, params);
this._writeChangePasswordToken(body, params);
return await this._fetchUser(logger, body);
}
async signinSmsCode(params: PhoneNumberTokenRequest) {
const logger = this._logger.create('signinSmsCode');
const client_secret = this.settings.client_secret;
if (!client_secret) {
logger.error('A client_id is required');
throw new Error('A client_id is required');
}
const body = new URLSearchParams({
phone_number: params.phoneNumber,
phone_verify_code: params.code,
grant_type: 'phone_verify',
client_id: this.settings.client_id,
client_secret,
});
this._writeUserId(body, params);
this._writeTwoFactorToken(body, params);
return await this._fetchUser(logger, body);
}
}
const { authority, audience, clientId, clientSecret, disablePKCE } =
useAppConfig(import.meta.env, import.meta.env.PROD);
@ -17,7 +146,7 @@ const ls = new SecureLS({
// @ts-ignore secure-ls does not have a type definition for this
metaKey: `${namespace}-secure-oidc`,
});
export const userManager = new UserManager({
const oidcSettings: UserManagerSettings = {
authority,
client_id: clientId,
client_secret: clientSecret,
@ -50,4 +179,7 @@ export const userManager = new UserManager({
},
}),
disablePKCE,
});
};
const userManager = new AbpUserManager(oidcSettings);
export { oidcSettings, userManager };

19
apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import type { SortOrder } from '@abp/core';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { VbenFormProps } from '@vben/common-ui';
@ -219,8 +218,10 @@ const gridOptions: VxeGridProps<AuditLogDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -234,12 +235,15 @@ const gridOptions: VxeGridProps<AuditLogDto> = {
},
sortConfig: {
remote: true,
allowBtn: true,
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
@ -251,7 +255,9 @@ const gridEvents: VxeGridListeners<AuditLogDto> = {
checkboxChange: (params) => {
selectedKeys.value = params.records.map((x) => x.id);
},
sortChange: onSort,
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
@ -301,11 +307,6 @@ function onBulkDelete() {
});
}
function onSort(params: { field: string; order: SortOrder }) {
const sorting = params.order ? `${params.field} ${params.order}` : undefined;
gridApi.query({ sorting });
}
function onFilter(field: string, value: any) {
gridApi.formApi.setFieldValue(field, value);
gridApi.formApi.validateAndSubmitForm();

96
apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingDrawer.vue

@ -1,5 +1,7 @@
<script setup lang="ts">
import type { LogDto, LogLevel } from '../../types/loggings';
import type { VxeGridProps } from '@abp/ui';
import type { LogDto, LogExceptionDto, LogLevel } from '../../types/loggings';
import { ref } from 'vue';
@ -7,7 +9,8 @@ import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { formatToDateTime } from '@abp/core';
import { Descriptions, Tabs, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '@abp/ui';
import { Descriptions, Tabs, Tag, Textarea } from 'ant-design-vue';
import { useLoggingsApi } from '../../api/useLoggingsApi';
@ -47,6 +50,55 @@ const [Drawer, drawerApi] = useVbenDrawer({
},
title: $t('AbpAuditLogging.AuditLog'),
});
/** 异常信息表格配置 */
const exceptionsGridOptions: VxeGridProps<LogExceptionDto> = {
border: true,
columns: [
{
align: 'left',
field: 'parameters',
slots: {
content: 'parameters',
},
type: 'expand',
width: 80,
},
{
align: 'left',
field: 'class',
sortable: true,
title: $t('AbpAuditLogging.Class'),
},
],
expandConfig: {
accordion: true,
padding: true,
trigger: 'row',
},
exportConfig: {},
keepSource: true,
pagerConfig: {
enabled: false,
},
proxyConfig: {
ajax: {
query: () => {
return Promise.resolve(logModel.value.exceptions);
},
},
response: {
list: ({ data }) => {
return data;
},
},
},
toolbarConfig: {
enabled: false,
},
};
const [ExceptionsGrid] = useVbenVxeGrid({
gridOptions: exceptionsGridOptions,
});
async function onGet(id: string) {
const dto = await getApi(id);
logModel.value = dto;
@ -130,6 +182,46 @@ async function onGet(id: string) {
</DescriptionsItem>
</Descriptions>
</TabPane>
<TabPane
v-if="logModel.exceptions?.length > 0"
key="exception"
:tab="$t('AbpAuditLogging.Exceptions')"
>
<ExceptionsGrid>
<template #parameters="{ row }">
<Descriptions
:label-style="{ minWidth: '100px' }"
:colon="false"
:column="1"
bordered
size="small"
>
<DescriptionsItem :label="$t('AbpAuditLogging.Class')">
{{ row.class }}
</DescriptionsItem>
<DescriptionsItem :label="$t('AbpAuditLogging.Message')">
{{ row.message }}
</DescriptionsItem>
<DescriptionsItem :label="$t('AbpAuditLogging.Source')">
{{ row.source }}
</DescriptionsItem>
<DescriptionsItem :label="$t('AbpAuditLogging.StackTrace')">
<Textarea
readonly
:auto-size="{ minRows: 10 }"
:value="row.stackTrace"
/>
</DescriptionsItem>
<DescriptionsItem :label="$t('AbpAuditLogging.HResult')">
{{ row.hResult }}
</DescriptionsItem>
<DescriptionsItem :label="$t('AbpAuditLogging.HelpURL')">
{{ row.helpURL }}
</DescriptionsItem>
</Descriptions>
</template>
</ExceptionsGrid>
</TabPane>
</Tabs>
</div>
</Drawer>

24
apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingTable.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import type { SortOrder } from '@abp/core';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { VbenFormProps } from '@vben/common-ui';
@ -74,6 +73,11 @@ const formOptions: VbenFormProps = {
['YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss'],
],
],
commonConfig: {
componentProps: {
allowClear: true,
},
},
schema: [
{
component: 'Select',
@ -253,8 +257,10 @@ const gridOptions: VxeGridProps<LogDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -272,8 +278,9 @@ const gridOptions: VxeGridProps<LogDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
@ -285,7 +292,9 @@ const gridEvents: VxeGridListeners<LogDto> = {
checkboxChange: (params) => {
selectedKeys.value = params.records.map((x) => x.fields.id);
},
sortChange: onSort,
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
@ -302,11 +311,6 @@ function onUpdate(row: LogDto) {
logDrawerApi.open();
}
function onSort(params: { field: string; order: SortOrder }) {
const sorting = params.order ? `${params.field} ${params.order}` : undefined;
gridApi.query({ sorting });
}
function onFilter(field: string, value: any) {
gridApi.formApi.setFieldValue(field, value);
gridApi.formApi.validateAndSubmitForm();

9
apps/vben5/packages/@abp/core/package.json

@ -38,13 +38,20 @@
"@vueuse/core": "catalog:",
"dayjs": "catalog:",
"lodash.groupby": "catalog:",
"lodash.isdate": "catalog:",
"lodash.isnumber": "catalog:",
"lodash.merge": "catalog:",
"lodash.sortby": "catalog:",
"pinia": "catalog:",
"pinia-plugin-persistedstate": "catalog:",
"universal-cookie": "catalog:",
"vue": "catalog:"
},
"devDependencies": {
"@types/lodash.groupby": "catalog:",
"@types/lodash.merge": "catalog:"
"@types/lodash.isdate": "catalog:",
"@types/lodash.isnumber": "catalog:",
"@types/lodash.merge": "catalog:",
"@types/lodash.sortby": "catalog:"
}
}

9
apps/vben5/packages/@abp/core/src/store/abp.ts

@ -6,11 +6,17 @@ import type {
import { ref } from 'vue';
import { acceptHMRUpdate, defineStore } from 'pinia';
import Cookies from 'universal-cookie';
export const useAbpStore = defineStore(
'abp',
() => {
const cookies = new Cookies(null, {
domain: window.location.host,
path: '/',
});
const tenantId = ref<string>();
const xsrfToken = ref<string>();
const application = ref<ApplicationConfigurationDto>();
const localization = ref<ApplicationLocalizationDto>();
/** 获取 i18n 格式本地化文本 */
@ -52,6 +58,7 @@ export const useAbpStore = defineStore(
function setApplication(val: ApplicationConfigurationDto) {
application.value = val;
xsrfToken.value = cookies.get('XSRF-TOKEN');
}
function setLocalization(val: ApplicationLocalizationDto) {
@ -59,12 +66,14 @@ export const useAbpStore = defineStore(
}
function $reset() {
xsrfToken.value = undefined;
application.value = undefined;
}
return {
$reset,
application,
xsrfToken,
getI18nLocales,
localization,
setApplication,

61
apps/vben5/packages/@abp/core/src/utils/date.ts

@ -35,11 +35,43 @@ export function getAppointDate(days: number): dayjs.Dayjs {
return dayjs(tomorrow);
}
/**
*
* @returns
*/
export function firstDayOfWeek(): dayjs.Dayjs {
const now = new Date();
const today = now.getDay();
const dayOffset = today === 0 ? -6 : 1 - today;
const monday = new Date(now);
monday.setDate(now.getDate() + dayOffset);
return dayjs(
new Date(monday.getFullYear(), monday.getMonth(), monday.getDate()),
);
}
/**
*
* @returns
*/
export function firstDayOfMonth(): dayjs.Dayjs {
const now = new Date();
return dayjs(new Date(now.getFullYear(), now.getMonth(), 1));
}
/**
* 获取当月最后一天00:00:00
* @returns 返回当月最后一天00:00:00
*/
export function lastDayOfMonth(): dayjs.Dayjs {
const now = new Date();
return dayjs(new Date(now.getFullYear(), now.getMonth() + 1, 0));
}
/**
* 获取当月最后一天23:59:59
* @returns 返回当月最后一天23:59:59
*/
export function lastDateOfMonth(): dayjs.Dayjs {
const now = new Date();
return dayjs(
@ -47,4 +79,33 @@ export function lastDateOfMonth(): dayjs.Dayjs {
);
}
/**
*
* @returns
*/
export function firstDayOfLastMonth(): dayjs.Dayjs {
const now = new Date();
return dayjs(new Date(now.getFullYear(), now.getMonth() - 1, 1));
}
/**
* 获取上个月最后一天23:59:59
* @returns 返回上个月最后一天23:59:59
*/
export function lastDateOfLastMonth(): dayjs.Dayjs {
const now = new Date();
return dayjs(
new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999), // 当月第0天 = 上月最后一天
);
}
/**
*
* @returns
*/
export function firstDayOfYear(): dayjs.Dayjs {
const now = new Date();
return dayjs(new Date(now.getFullYear(), 0, 1));
}
export const dateUtil = dayjs;

2
apps/vben5/packages/@abp/core/src/utils/index.ts

@ -4,4 +4,6 @@ export * from './is';
export * from './mitt';
export * from './regex';
export * from './string';
export * from './table';
export * from './tree';
export * from './uuid';

3
apps/vben5/packages/@abp/core/src/utils/is.ts

@ -1,3 +1,6 @@
export { default as isDate } from 'lodash.isdate';
export { default as isNumber } from 'lodash.isnumber';
export function isNullAndUnDef(val: unknown): val is null | undefined {
return isUnDef(val) && isNull(val);
}

31
apps/vben5/packages/@abp/core/src/utils/table.ts

@ -0,0 +1,31 @@
import { isDate, isNumber } from './is';
export function sorter(
a: Record<string, any>,
b: Record<string, any>,
field: string,
): number {
if (!a[field] && !b[field]) {
return 0;
}
if (a[field] && !b[field]) {
return 1;
}
if (b[field] && !a[field]) {
return -1;
}
const va = a[field];
const vb = b[field];
if (isDate(va) && isDate(vb)) {
return va.getTime() - vb.getTime();
}
if (isNumber(va) && isNumber(vb)) {
return va - vb;
}
if (Array.isArray(va) && Array.isArray(vb)) {
return va.length - vb.length;
}
return String(va).localeCompare(String(vb));
}
export { default as sortby } from 'lodash.sortby';

42
apps/vben5/packages/@abp/core/src/utils/uuid.ts

@ -0,0 +1,42 @@
const hexList: string[] = [];
for (let i = 0; i <= 15; i++) {
hexList[i] = i.toString(16);
}
export function buildUUID(): string {
let uuid = '';
for (let i = 1; i <= 36; i++) {
switch (i) {
case 9:
case 14:
case 19:
case 24: {
uuid += '-';
break;
}
case 15: {
uuid += 4;
break;
}
case 20: {
uuid += hexList[(Math.random() * 4) | 8];
break;
}
default: {
uuid += hexList[Math.trunc(Math.random() * 16)];
}
}
}
return uuid.replaceAll('-', '');
}
let unique = 0;
export function buildShortUUID(prefix = ''): string {
const time = Date.now();
const random = Math.floor(Math.random() * 1_000_000_000);
unique++;
return `${prefix}_${random}${unique}${String(time)}`;
}

17
apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue

@ -29,24 +29,28 @@ const gridOptions: VxeGridProps<EntityTypeInfoDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('DataProtection.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('DataProtection.DisplayName:DisplayName'),
},
{
align: 'left',
field: 'typeFullName',
minWidth: 200,
sortable: true,
title: $t('DataProtection.DisplayName:TypeFullName'),
},
{
align: 'left',
field: 'isAuditEnabled',
minWidth: 150,
sortable: true,
title: $t('DataProtection.DisplayName:IsAuditEnabled'),
},
{
@ -61,8 +65,10 @@ const gridOptions: VxeGridProps<EntityTypeInfoDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
const { totalCount, items } = await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -85,14 +91,19 @@ const gridOptions: VxeGridProps<EntityTypeInfoDto> = {
},
},
toolbarConfig: {
refresh: true,
refresh: {
code: 'query',
},
},
};
const gridEvents: VxeGridListeners<EntityTypeInfoDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [Grid] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
gridEvents,
gridOptions,
});

15
apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue

@ -60,24 +60,28 @@ const baseColumns = reactive<VxeGridPropTypes.Columns<BookDto>>([
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('Demo.DisplayName:Name'),
},
{
align: 'left',
field: 'authorName',
minWidth: 150,
sortable: true,
title: $t('Demo.DisplayName:AuthorId'),
},
{
align: 'left',
field: 'publishDate',
minWidth: 200,
sortable: true,
title: $t('Demo.DisplayName:PublishDate'),
},
{
align: 'left',
field: 'price',
minWidth: 150,
sortable: true,
title: $t('Demo.DisplayName:Price'),
},
]);
@ -88,8 +92,10 @@ const gridOptions: VxeGridProps<BookDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -102,12 +108,17 @@ const gridOptions: VxeGridProps<BookDto> = {
},
},
toolbarConfig: {
refresh: true,
refresh: {
code: 'query',
},
},
};
const gridEvents: VxeGridListeners<BookDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,

96
apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue

@ -6,13 +6,14 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { FeatureDefinitionDto } from '../../../types/definitions';
import type { FeatureGroupDefinitionDto } from '../../../types/groups';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
listToTree,
sortby,
useLocalization,
useLocalizationSerializer,
} from '@abp/core';
@ -35,8 +36,8 @@ defineOptions({
name: 'FeatureDefinitionTable',
});
interface PermissionVo {
children: PermissionVo[];
interface FeatureVo {
children: FeatureVo[];
displayName: string;
groupName: string;
isEnabled: boolean;
@ -46,30 +47,24 @@ interface PermissionVo {
providers: string[];
stateCheckers: string[];
}
interface PermissionGroupVo {
interface FeatureGroupVo {
displayName: string;
features: FeatureVo[];
name: string;
permissions: PermissionVo[];
}
const permissionGroups = ref<PermissionGroupVo[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const featureGroups = ref<FeatureGroupVo[]>([]);
const { Lr } = useLocalization();
const { deserialize } = useLocalizationSerializer();
const { getListApi: getGroupsApi } = useFeatureGroupDefinitionsApi();
const { deleteApi, getListApi: getPermissionsApi } = useFeatureDefinitionsApi();
const { deleteApi, getListApi: getFeaturesApi } = useFeatureDefinitionsApi();
const formOptions: VbenFormProps = {
//
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -104,12 +99,14 @@ const gridOptions: VxeGridProps<FeatureGroupDefinitionDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:DisplayName'),
},
],
@ -119,6 +116,30 @@ const gridOptions: VxeGridProps<FeatureGroupDefinitionDto> = {
},
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(featureGroups.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: featureGroups.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -136,6 +157,7 @@ const subGridColumns: VxeGridProps<FeatureDefinitionDto>['columns'] = [
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:Name'),
treeNode: true,
},
@ -143,12 +165,14 @@ const subGridColumns: VxeGridProps<FeatureDefinitionDto>['columns'] = [
align: 'left',
field: 'displayName',
minWidth: 120,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:DisplayName'),
},
{
align: 'left',
field: 'description',
minWidth: 120,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:Description'),
},
{
@ -156,6 +180,7 @@ const subGridColumns: VxeGridProps<FeatureDefinitionDto>['columns'] = [
field: 'isVisibleToClients',
minWidth: 120,
slots: { default: 'isVisibleToClients' },
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:IsVisibleToClients'),
},
{
@ -163,6 +188,7 @@ const subGridColumns: VxeGridProps<FeatureDefinitionDto>['columns'] = [
field: 'isAvailableToHost',
minWidth: 120,
slots: { default: 'isAvailableToHost' },
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:IsAvailableToHost'),
},
{
@ -175,10 +201,8 @@ const subGridColumns: VxeGridProps<FeatureDefinitionDto>['columns'] = [
];
const gridEvents: VxeGridListeners<FeatureGroupDefinitionDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -198,17 +222,16 @@ async function onGet(input?: Record<string, string>) {
try {
gridApi.setLoading(true);
const groupRes = await getGroupsApi(input);
const permissionRes = await getPermissionsApi(input);
pageState.total = groupRes.items.length;
permissionGroups.value = groupRes.items.map((group) => {
const featureRes = await getFeaturesApi(input);
featureGroups.value = groupRes.items.map((group) => {
const localizableGroup = deserialize(group.displayName);
const permissions = permissionRes.items
.filter((permission) => permission.groupName === group.name)
.map((permission) => {
const displayName = deserialize(permission.displayName);
const description = deserialize(permission.displayName);
const features = featureRes.items
.filter((feature) => feature.groupName === group.name)
.map((feature) => {
const displayName = deserialize(feature.displayName);
const description = deserialize(feature.description);
return {
...permission,
...feature,
description: Lr(description.resourceName, description.name),
displayName: Lr(displayName.resourceName, displayName.name),
};
@ -216,13 +239,13 @@ async function onGet(input?: Record<string, string>) {
return {
...group,
displayName: Lr(localizableGroup.resourceName, localizableGroup.name),
permissions: listToTree(permissions, {
features: listToTree(features, {
id: 'name',
pid: 'parentName',
}),
};
});
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -234,21 +257,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = permissionGroups.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
modalApi.setData({});
modalApi.open();
@ -290,7 +298,7 @@ onMounted(onGet);
<template #group="{ row: group }">
<VxeGrid
:columns="subGridColumns"
:data="group.permissions"
:data="group.features"
:tree-config="{
trigger: 'row',
rowField: 'name',

64
apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionTable.vue

@ -6,14 +6,14 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { FeatureGroupDefinitionDto } from '../../../types/groups';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { useLocalization, useLocalizationSerializer } from '@abp/core';
import { sortby, useLocalization, useLocalizationSerializer } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
@ -37,12 +37,7 @@ const MenuItem = Menu.Item;
const FeaturesOutlined = createIconifyIcon('pajamas:feature-flag');
const permissionGroups = ref<FeatureGroupDefinitionDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const featureGroups = ref<FeatureGroupDefinitionDto[]>([]);
const { Lr } = useLocalization();
const { hasAccessByCodes } = useAccess();
@ -54,7 +49,6 @@ const formOptions: VbenFormProps = {
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -77,12 +71,14 @@ const gridOptions: VxeGridProps<FeatureGroupDefinitionDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:DisplayName'),
},
{
@ -95,6 +91,30 @@ const gridOptions: VxeGridProps<FeatureGroupDefinitionDto> = {
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(featureGroups.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: featureGroups.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -104,10 +124,8 @@ const gridOptions: VxeGridProps<FeatureGroupDefinitionDto> = {
};
const gridEvents: VxeGridListeners<FeatureGroupDefinitionDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -132,15 +150,14 @@ async function onGet(input?: Record<string, string>) {
try {
gridApi.setLoading(true);
const { items } = await getListApi(input);
pageState.total = items.length;
permissionGroups.value = items.map((item) => {
featureGroups.value = items.map((item) => {
const localizableString = deserialize(item.displayName);
return {
...item,
displayName: Lr(localizableString.resourceName, localizableString.name),
};
});
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -152,21 +169,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = permissionGroups.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
groupModalApi.setData({});
groupModalApi.open();

13
apps/vben5/packages/@abp/gdpr/src/components/GdprTable.vue

@ -35,11 +35,13 @@ const gridOptions: VxeGridProps<GdprRequestDto> = {
align: 'left',
field: 'readyTime',
slots: { default: 'readly' },
sortable: true,
title: $t('AbpGdpr.DisplayName:ReadyTime'),
},
{
align: 'left',
field: 'creationTime',
sortable: true,
title: $t('AbpGdpr.DisplayName:CreationTime'),
},
{
@ -54,8 +56,10 @@ const gridOptions: VxeGridProps<GdprRequestDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
const dto = await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -80,12 +84,17 @@ const gridOptions: VxeGridProps<GdprRequestDto> = {
},
},
toolbarConfig: {
refresh: true,
refresh: {
code: 'query',
},
},
};
const gridEvents: VxeGridListeners<GdprRequestDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
gridEvents,

25
apps/vben5/packages/@abp/identity/src/components/claim-types/ClaimTypeTable.vue

@ -66,6 +66,7 @@ const gridOptions: VxeGridProps<IdentityClaimTypeDto> = {
align: 'left',
field: 'name',
minWidth: 120,
sortable: true,
title: $t('AbpIdentity.IdentityClaim:Name'),
},
{
@ -90,28 +91,33 @@ const gridOptions: VxeGridProps<IdentityClaimTypeDto> = {
}
}
},
sortable: true,
title: $t('AbpIdentity.IdentityClaim:ValueType'),
},
{
align: 'left',
field: 'regex',
sortable: true,
title: $t('AbpIdentity.IdentityClaim:Regex'),
},
{
align: 'center',
field: 'required',
slots: { default: 'required' },
sortable: true,
title: $t('AbpIdentity.IdentityClaim:Required'),
},
{
align: 'center',
field: 'isStatic',
slots: { default: 'static' },
sortable: true,
title: $t('AbpIdentity.IdentityClaim:IsStatic'),
},
{
align: 'left',
field: 'description',
sortable: true,
title: $t('AbpIdentity.IdentityClaim:Description'),
},
{
@ -130,8 +136,10 @@ const gridOptions: VxeGridProps<IdentityClaimTypeDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -146,19 +154,22 @@ const gridOptions: VxeGridProps<IdentityClaimTypeDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<IdentityClaimTypeDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [ClaimTypeEditModal, roleModalApi] = useVbenModal({
connectedComponent: ClaimTypeModal,
});
const [Grid, { query }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
@ -187,7 +198,7 @@ const onDelete = (row: IdentityClaimTypeDto) => {
onOk: async () => {
await deleteApi(row.id);
message.success($t('AbpUi.DeletedSuccessfully'));
query();
gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
@ -270,7 +281,7 @@ const onMenuClick = (row: IdentityClaimTypeDto, info: MenuInfo) => {
</div>
</template>
</Grid>
<ClaimTypeEditModal @change="() => query()" />
<ClaimTypeEditModal @change="() => gridApi.query()" />
<ClaimTypeChangeDrawer />
</template>

23
apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitRoleTable.vue

@ -45,6 +45,7 @@ const gridOptions: VxeGridProps<IdentityRoleDto> = {
{
field: 'name',
minWidth: '100px',
sortable: true,
title: $t('AbpIdentity.DisplayName:RoleName'),
},
{
@ -59,14 +60,16 @@ const gridOptions: VxeGridProps<IdentityRoleDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
if (!props.selectedKey) {
return {
totalCount: 0,
items: [],
};
}
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getRoleListApi(props.selectedKey!, {
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -81,16 +84,20 @@ const gridOptions: VxeGridProps<IdentityRoleDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<IdentityRoleDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [Grid, { query, setLoading }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
gridEvents,
gridOptions,
});
@ -100,7 +107,7 @@ const [RoleModal, roleModalApi] = useVbenModal({
});
const onRefresh = () => {
return nextTick(query);
return nextTick(gridApi.query);
};
const onDelete = (row: IdentityRoleDto) => {
@ -114,11 +121,11 @@ const onDelete = (row: IdentityRoleDto) => {
},
onOk: async () => {
try {
setLoading(true);
gridApi.setLoading(true);
await removeOrganizationUnitApi(row.id, props.selectedKey!);
await onRefresh();
} finally {
setLoading(false);
gridApi.setLoading(false);
}
},
title: $t('AbpUi.AreYouSure'),
@ -141,7 +148,7 @@ const onCreateRole = async (roles: IdentityRoleDto[]) => {
roleIds: roles.map((item) => item.id),
});
roleModalApi.close();
await query();
await gridApi.query();
} finally {
roleModalApi.setState({
submitting: false,

54
apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitUserTable.vue

@ -45,11 +45,13 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
{
field: 'userName',
minWidth: '100px',
sortable: true,
title: $t('AbpIdentity.DisplayName:UserName'),
},
{
field: 'email',
minWidth: '120px',
sortable: true,
title: $t('AbpIdentity.DisplayName:Email'),
},
{
@ -64,14 +66,16 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
if (!props.selectedKey) {
return {
totalCount: 0,
items: [],
};
}
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getUserListApi(props.selectedKey!, {
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -86,16 +90,20 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<IdentityUserDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [Grid, { query, setLoading }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
gridEvents,
gridOptions,
});
@ -104,7 +112,7 @@ const [MemberModal, userModalApi] = useVbenModal({
});
const onRefresh = () => {
nextTick(query);
nextTick(gridApi.query);
};
const onDelete = (row: IdentityUserDto) => {
@ -116,11 +124,14 @@ const onDelete = (row: IdentityUserDto) => {
onCancel: () => {
cancel('User closed cancel delete modal.');
},
onOk: () => {
setLoading(true);
return removeOrganizationUnitApi(row.id, props.selectedKey!)
.then(onRefresh)
.finally(() => setLoading(false));
onOk: async () => {
gridApi.setLoading(true);
try {
await removeOrganizationUnitApi(row.id, props.selectedKey!);
await gridApi.query();
} finally {
gridApi.setLoading(false);
}
},
title: $t('AbpUi.AreYouSure'),
});
@ -133,22 +144,21 @@ const onShowMember = () => {
userModalApi.open();
};
const onCreateMember = (users: IdentityUserDto[]) => {
const onCreateMember = async (users: IdentityUserDto[]) => {
userModalApi.setState({
submitting: true,
});
addMembers(props.selectedKey!, {
userIds: users.map((item) => item.id),
})
.then(() => {
userModalApi.close();
query();
})
.finally(() => {
userModalApi.setState({
submitting: false,
});
try {
await addMembers(props.selectedKey!, {
userIds: users.map((item) => item.id),
});
userModalApi.close();
await gridApi.query();
} finally {
userModalApi.setState({
submitting: false,
});
}
};
watch(() => props.selectedKey, onRefresh);

29
apps/vben5/packages/@abp/identity/src/components/organization-units/SelectMemberModal.vue

@ -68,22 +68,24 @@ const [Modal, modalApi] = useVbenModal({
});
const gridOptions: VxeGridProps<IdentityUserDto> = {
checkboxConfig: {
highlight: true,
labelField: 'userName',
},
columns: [
{
align: 'center',
type: 'checkbox',
width: 80,
},
{
align: 'left',
field: 'userName',
minWidth: '100px',
sortable: true,
title: $t('AbpIdentity.DisplayName:UserName'),
type: 'checkbox',
},
{
align: 'left',
field: 'email',
minWidth: '120px',
sortable: true,
title: $t('AbpIdentity.DisplayName:Email'),
},
],
@ -91,12 +93,14 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
const state = modalApi.getData<Record<string, any>>();
return await getUnaddedUserListApi({
id: state.id,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
sorting,
...formValues,
});
},
@ -107,7 +111,11 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
list: 'items',
},
},
toolbarConfig: {},
toolbarConfig: {
refresh: {
code: 'query',
},
},
};
const gridEvents: VxeGridListeners<IdentityUserDto> = {
@ -117,15 +125,18 @@ const gridEvents: VxeGridListeners<IdentityUserDto> = {
checkboxChange: (e) => {
selectedUsers.value = e.records;
},
sortChange: () => {
gridApi.query();
},
};
const [Grid, { query }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
});
function onRefresh() {
nextTick(query);
nextTick(gridApi.query);
}
</script>

28
apps/vben5/packages/@abp/identity/src/components/organization-units/SelectRoleModal.vue

@ -68,27 +68,30 @@ const [Modal, modalApi] = useVbenModal({
});
const gridOptions: VxeGridProps<IdentityRoleDto> = {
checkboxConfig: {
highlight: true,
labelField: 'name',
},
columns: [
{
align: 'center',
type: 'checkbox',
width: 80,
},
{
align: 'left',
field: 'name',
sortable: true,
title: $t('AbpIdentity.DisplayName:RoleName'),
type: 'checkbox',
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
const state = modalApi.getData<Record<string, any>>();
return await getUnaddedRoleListApi({
id: state.id,
maxResultCount: page.pageSize,
sorting,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
@ -100,7 +103,11 @@ const gridOptions: VxeGridProps<IdentityRoleDto> = {
list: 'items',
},
},
toolbarConfig: {},
toolbarConfig: {
refresh: {
code: 'query',
},
},
};
const gridEvents: VxeGridListeners<IdentityRoleDto> = {
@ -110,15 +117,18 @@ const gridEvents: VxeGridListeners<IdentityRoleDto> = {
checkboxChange: (e) => {
selectedRoles.value = e.records;
},
sortChange: () => {
gridApi.query();
},
};
const [Grid, { query }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
});
function onRefresh() {
nextTick(query);
nextTick(gridApi.query);
}
</script>

21
apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue

@ -87,6 +87,7 @@ const gridOptions: VxeGridProps<IdentityRoleDto> = {
align: 'left',
field: 'name',
slots: { default: 'name' },
sortable: true,
title: $t('AbpIdentity.DisplayName:RoleName'),
},
{
@ -101,8 +102,10 @@ const gridOptions: VxeGridProps<IdentityRoleDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -117,19 +120,23 @@ const gridOptions: VxeGridProps<IdentityRoleDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<IdentityRoleDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [RoleEditModal, roleModalApi] = useVbenModal({
connectedComponent: RoleModal,
});
const [Grid, { query }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
@ -155,7 +162,7 @@ const handleDelete = (row: IdentityRoleDto) => {
onOk: async () => {
await deleteApi(row.id);
message.success($t('AbpUi.DeletedSuccessfully'));
query();
gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
@ -300,8 +307,8 @@ function onPermissionChange(_name: string, key: string) {
</div>
</template>
</Grid>
<RoleEditModal @change="() => query()" />
<RoleClaimModal @change="query" />
<RoleEditModal @change="() => gridApi.query()" />
<RoleClaimModal @change="() => gridApi.query()" />
<RolePermissionModal @change="onPermissionChange" />
<RoleChangeDrawer />
<RoleRuleDrawer />

19
apps/vben5/packages/@abp/identity/src/components/security-logs/SecurityLogTable.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import type { SortOrder } from '@abp/core';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { VbenFormProps } from '@vben/common-ui';
@ -168,8 +167,10 @@ const gridOptions: VxeGridProps<SecurityLogDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -184,8 +185,9 @@ const gridOptions: VxeGridProps<SecurityLogDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
@ -197,7 +199,9 @@ const gridEvents: VxeGridListeners<SecurityLogDto> = {
checkboxChange: (params) => {
selectedKeys.value = params.records.map((x) => x.id);
},
sortChange: onSort,
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
@ -247,11 +251,6 @@ function onBulkDelete() {
title: $t('AbpUi.AreYouSure'),
});
}
function onSort(params: { field: string; order: SortOrder }) {
const sorting = params.order ? `${params.field} ${params.order}` : undefined;
gridApi.query({ sorting });
}
</script>
<template>

19
apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue

@ -82,6 +82,7 @@ const gridOptions: VxeGridProps<IdentitySessionDto> = {
align: 'left',
field: 'sessionId',
minWidth: 150,
sortable: true,
title: $t('AbpIdentity.DisplayName:SessionId'),
},
{
@ -89,11 +90,13 @@ const gridOptions: VxeGridProps<IdentitySessionDto> = {
field: 'device',
minWidth: 120,
slots: { default: 'device' },
sortable: true,
title: $t('AbpIdentity.DisplayName:Device'),
},
{
align: 'left',
field: 'deviceInfo',
sortable: true,
title: $t('AbpIdentity.DisplayName:DeviceInfo'),
width: 'auto',
},
@ -101,24 +104,28 @@ const gridOptions: VxeGridProps<IdentitySessionDto> = {
align: 'left',
field: 'clientId',
minWidth: 120,
sortable: true,
title: $t('AbpIdentity.DisplayName:ClientId'),
},
{
align: 'left',
field: 'ipAddresses',
minWidth: 120,
sortable: true,
title: $t('AbpIdentity.DisplayName:IpAddresses'),
},
{
align: 'left',
field: 'signedIn',
minWidth: 120,
sortable: true,
title: $t('AbpIdentity.DisplayName:SignedIn'),
},
{
align: 'left',
field: 'lastAccessed',
minWidth: 120,
sortable: true,
title: $t('AbpIdentity.DisplayName:LastAccessed'),
},
{
@ -137,8 +144,10 @@ const gridOptions: VxeGridProps<IdentitySessionDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getSessionsApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -153,14 +162,18 @@ const gridOptions: VxeGridProps<IdentitySessionDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<IdentitySessionDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({

18
apps/vben5/packages/@abp/identity/src/components/sessions/UserSessionTable.vue

@ -24,17 +24,20 @@ const DescriptionItem = Descriptions.Item;
const { hasAccessByCodes } = useAccess();
const abpStore = useAbpStore();
/** 获取登录用户会话Id */
const getMySessionId = computed(() => {
return abpStore.application?.currentUser.sessionId;
/** 获取登录用户 */
const getCurrentUser = computed(() => {
return abpStore.application?.currentUser;
});
/** 获取是否允许撤销会话 */
const getAllowRevokeSession = computed(() => {
return (session: IdentitySessionDto) => {
if (getMySessionId.value === session.sessionId) {
if (getCurrentUser.value?.sessionId === session.sessionId) {
return false;
}
return hasAccessByCodes([IdentitySessionPermissions.Revoke]);
return (
getCurrentUser.value?.id === session.userId ||
hasAccessByCodes([IdentitySessionPermissions.Revoke])
);
};
});
@ -106,7 +109,10 @@ function onDelete(session: IdentitySessionDto) {
<div class="flex flex-row">
<span>{{ row.device }}</span>
<div class="pl-[5px]">
<Tag v-if="row.sessionId === getMySessionId" color="#87d068">
<Tag
v-if="row.sessionId === getCurrentUser?.sessionId"
color="#87d068"
>
{{ $t('AbpIdentity.CurrentSession') }}
</Tag>
</div>

43
apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue

@ -96,11 +96,13 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
{
field: 'isActive',
slots: { default: 'active' },
sortable: true,
title: $t('AbpIdentity.DisplayName:IsActive'),
},
{
field: 'userName',
minWidth: '100px',
sortable: true,
title: $t('AbpIdentity.DisplayName:UserName'),
},
{
@ -108,14 +110,24 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
field: 'email',
minWidth: '120px',
slots: { default: 'email' },
sortable: true,
title: $t('AbpIdentity.DisplayName:Email'),
},
{ field: 'surname', title: $t('AbpIdentity.DisplayName:Surname') },
{ field: 'name', title: $t('AbpIdentity.DisplayName:Name') },
{
field: 'surname',
sortable: true,
title: $t('AbpIdentity.DisplayName:Surname'),
},
{
field: 'name',
sortable: true,
title: $t('AbpIdentity.DisplayName:Name'),
},
{
align: 'left',
field: 'phoneNumber',
slots: { default: 'phoneNumber' },
sortable: true,
title: $t('AbpIdentity.DisplayName:PhoneNumber'),
},
{
@ -123,6 +135,7 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
formatter: ({ cellValue }) => {
return cellValue ? formatToDateTime(cellValue) : '';
},
sortable: true,
title: $t('AbpIdentity.LockoutEnd'),
},
{
@ -137,8 +150,10 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -153,14 +168,18 @@ const gridOptions: VxeGridProps<IdentityUserDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<IdentityUserDto> = {
cellClick: () => {},
sortChange: () => {
gridApi.query();
},
};
const [UserEditModal, userModalApi] = useVbenModal({
connectedComponent: UserModal,
@ -188,7 +207,7 @@ const [UserSessionDrawer, userSessionDrawerApi] = useVbenDrawer({
() => import('./UserSessionDrawer.vue'),
),
});
const [Grid, { query }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
@ -214,7 +233,7 @@ const handleDelete = (row: IdentityUserDto) => {
onOk: async () => {
await deleteApi(row.id);
message.success($t('AbpUi.DeletedSuccessfully'));
query();
await gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
@ -222,7 +241,7 @@ const handleDelete = (row: IdentityUserDto) => {
const handleUnlock = async (row: IdentityUserDto) => {
await unLockApi(row.id);
await query();
await gridApi.query();
};
const handleMenuClick = async (row: IdentityUserDto, info: MenuInfo) => {
@ -436,10 +455,10 @@ const handleMenuClick = async (row: IdentityUserDto, info: MenuInfo) => {
</div>
</template>
</Grid>
<UserLockModal @change="query" />
<UserClaimModal @change="query" />
<UserEditModal @change="() => query()" />
<UserPasswordModal @change="query" />
<UserLockModal @change="() => gridApi.query()" />
<UserClaimModal @change="() => gridApi.query()" />
<UserEditModal @change="() => gridApi.query()" />
<UserPasswordModal @change="() => gridApi.query()" />
<UserPermissionModal />
<UserSessionDrawer />
<UserChangeDrawer />

61
apps/vben5/packages/@abp/localization/src/components/languages/LocalizationLanguageTable.vue

@ -5,12 +5,12 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { LanguageDto } from '../../types/languages';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAbpStore } from '@abp/core';
import { sortby, useAbpStore } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
@ -28,11 +28,6 @@ defineOptions({
});
const dataSource = ref<LanguageDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const abpStore = useAbpStore();
const { deleteApi, getListApi } = useLanguagesApi();
@ -43,7 +38,6 @@ const formOptions: VbenFormProps = {
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -66,18 +60,21 @@ const gridOptions: VxeGridProps<LanguageDto> = {
align: 'left',
field: 'cultureName',
minWidth: 150,
sortable: true,
title: $t('AbpLocalization.DisplayName:CultureName'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpLocalization.DisplayName:DisplayName'),
},
{
align: 'left',
field: 'uiCultureName',
minWidth: 150,
sortable: true,
title: $t('AbpLocalization.DisplayName:UiCultureName'),
},
{
@ -90,6 +87,30 @@ const gridOptions: VxeGridProps<LanguageDto> = {
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(dataSource.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: dataSource.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -99,10 +120,8 @@ const gridOptions: VxeGridProps<LanguageDto> = {
};
const gridEvents: VxeGridListeners<LanguageDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -122,9 +141,8 @@ async function onGet(input?: Record<string, string>) {
try {
gridApi.setLoading(true);
const { items } = await getListApi(input);
pageState.total = items.length;
dataSource.value = items;
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -136,21 +154,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = dataSource.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
modalApi.setData({});
modalApi.open();

61
apps/vben5/packages/@abp/localization/src/components/resources/LocalizationResourceTable.vue

@ -5,12 +5,12 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { ResourceDto } from '../../types/resources';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAbpStore } from '@abp/core';
import { sortby, useAbpStore } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
@ -28,12 +28,6 @@ defineOptions({
});
const dataSource = ref<ResourceDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const abpStore = useAbpStore();
const { deleteApi, getListApi } = useResourcesApi();
const { getLocalizationApi } = useLocalizationsApi();
@ -43,7 +37,6 @@ const formOptions: VbenFormProps = {
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -66,12 +59,14 @@ const gridOptions: VxeGridProps<ResourceDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpFeatureManagement.DisplayName:DisplayName'),
},
{
@ -84,6 +79,30 @@ const gridOptions: VxeGridProps<ResourceDto> = {
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(dataSource.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: dataSource.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -93,10 +112,8 @@ const gridOptions: VxeGridProps<ResourceDto> = {
};
const gridEvents: VxeGridListeners<ResourceDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -116,9 +133,8 @@ async function onGet(input?: Record<string, string>) {
try {
gridApi.setLoading(true);
const { items } = await getListApi(input);
pageState.total = items.length;
dataSource.value = items;
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -130,21 +146,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = dataSource.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
modalApi.setData({});
modalApi.open();

61
apps/vben5/packages/@abp/localization/src/components/texts/LocalizationTextTable.vue

@ -11,7 +11,7 @@ import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAbpStore } from '@abp/core';
import { sortby, useAbpStore } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import { EditOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { Button, Select } from 'ant-design-vue';
@ -39,11 +39,6 @@ const targetValueOptions = reactive([
value: 'true',
},
]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const abpStore = useAbpStore();
const { getListApi } = useTextsApi();
@ -61,10 +56,8 @@ const formOptions: VbenFormProps = {
class: 'w-full',
},
},
compact: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -111,24 +104,28 @@ const gridOptions: VxeGridProps<TextDifferenceDto> = {
align: 'left',
field: 'key',
minWidth: 150,
sortable: true,
title: $t('AbpLocalization.DisplayName:Key'),
},
{
align: 'left',
field: 'value',
minWidth: 150,
sortable: true,
title: $t('AbpLocalization.DisplayName:Value'),
},
{
align: 'left',
field: 'targetValue',
minWidth: 150,
sortable: true,
title: $t('AbpLocalization.DisplayName:TargetValue'),
},
{
align: 'left',
field: 'resourceName',
minWidth: 150,
sortable: true,
title: $t('AbpLocalization.DisplayName:ResourceName'),
},
{
@ -141,6 +138,30 @@ const gridOptions: VxeGridProps<TextDifferenceDto> = {
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(dataSource.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: dataSource.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -150,10 +171,8 @@ const gridOptions: VxeGridProps<TextDifferenceDto> = {
};
const gridEvents: VxeGridListeners<TextDifferenceDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -186,9 +205,8 @@ async function onGet(input: Record<string, string>) {
try {
gridApi.setLoading(true);
const { items } = await getListApi(input as any);
pageState.total = items.length;
dataSource.value = items;
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -200,21 +218,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = dataSource.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
async function onCreate() {
const input = await gridApi.formApi.getValues();
modalApi.setData({

60
apps/vben5/packages/@abp/notifications/src/components/definitions/groups/NotificationGroupDefinitionTable.vue

@ -6,14 +6,14 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { NotificationGroupDefinitionDto } from '../../../types/groups';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { useLocalization, useLocalizationSerializer } from '@abp/core';
import { sortby, useLocalization, useLocalizationSerializer } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
@ -37,11 +37,6 @@ const MenuItem = Menu.Item;
const DefinitionIcon = createIconifyIcon('nimbus:notification');
const dataSource = ref<NotificationGroupDefinitionDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const { Lr } = useLocalization();
const { hasAccessByCodes } = useAccess();
@ -53,7 +48,6 @@ const formOptions: VbenFormProps = {
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -76,12 +70,14 @@ const gridOptions: VxeGridProps<NotificationGroupDefinitionDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('Notifications.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('Notifications.DisplayName:DisplayName'),
},
{
@ -94,6 +90,30 @@ const gridOptions: VxeGridProps<NotificationGroupDefinitionDto> = {
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(dataSource.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: dataSource.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -103,10 +123,8 @@ const gridOptions: VxeGridProps<NotificationGroupDefinitionDto> = {
};
const gridEvents: VxeGridListeners<NotificationGroupDefinitionDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -131,7 +149,6 @@ async function onGet(input?: Record<string, string>) {
try {
gridApi.setLoading(true);
const { items } = await getListApi(input);
pageState.total = items.length;
dataSource.value = items.map((item) => {
const localizableString = deserialize(item.displayName);
return {
@ -139,7 +156,7 @@ async function onGet(input?: Record<string, string>) {
displayName: Lr(localizableString.resourceName, localizableString.name),
};
});
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -151,21 +168,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = dataSource.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
groupModalApi.setData({});
groupModalApi.open();

77
apps/vben5/packages/@abp/notifications/src/components/definitions/notifications/NotificationDefinitionTable.vue

@ -7,7 +7,7 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { NotificationDefinitionDto } from '../../../types/definitions';
import type { NotificationGroupDefinitionDto } from '../../../types/groups';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui';
@ -16,6 +16,7 @@ import { $t } from '@vben/locales';
import {
listToTree,
sortby,
useLocalization,
useLocalizationSerializer,
} from '@abp/core';
@ -79,18 +80,12 @@ const { deleteApi, getListApi: getDefinitionsApi } =
useNotificationDefinitionsApi();
const definitionGroups = ref<DefinitionGroup[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const formOptions: VbenFormProps = {
//
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -125,12 +120,14 @@ const gridOptions: VxeGridProps<NotificationGroupDefinitionDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('Notifications.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('Notifications.DisplayName:DisplayName'),
},
],
@ -140,6 +137,30 @@ const gridOptions: VxeGridProps<NotificationGroupDefinitionDto> = {
},
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(definitionGroups.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: definitionGroups.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -157,6 +178,8 @@ const subGridColumns: VxeGridProps<NotificationDefinitionDto>['columns'] = [
align: 'left',
field: 'name',
minWidth: 150,
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:Name'),
treeNode: true,
},
@ -164,12 +187,16 @@ const subGridColumns: VxeGridProps<NotificationDefinitionDto>['columns'] = [
align: 'left',
field: 'displayName',
minWidth: 120,
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:DisplayName'),
},
{
align: 'left',
field: 'description',
minWidth: 120,
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:Description'),
},
{
@ -177,37 +204,49 @@ const subGridColumns: VxeGridProps<NotificationDefinitionDto>['columns'] = [
field: 'allowSubscriptionToClients',
minWidth: 120,
slots: { default: 'allowSubscriptionToClients' },
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:AllowSubscriptionToClients'),
},
{
align: 'left',
field: 'template',
minWidth: 150,
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:Template'),
},
{
align: 'left',
field: 'notificationLifetime',
minWidth: 150,
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:NotificationLifetime'),
},
{
align: 'left',
field: 'notificationType',
minWidth: 150,
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:NotificationType'),
},
{
align: 'left',
field: 'contentType',
minWidth: 150,
resizable: true,
sortable: true,
title: $t('Notifications.DisplayName:ContentType'),
},
{
align: 'left',
field: 'providers',
minWidth: 150,
resizable: true,
slots: { default: 'providers' },
sortable: true,
title: $t('Notifications.DisplayName:Providers'),
},
{
@ -220,10 +259,8 @@ const subGridColumns: VxeGridProps<NotificationDefinitionDto>['columns'] = [
];
const gridEvents: VxeGridListeners<NotificationGroupDefinitionDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -249,7 +286,6 @@ async function onGet(input?: Record<string, string>) {
gridApi.setLoading(true);
const groupRes = await getGroupsApi(input);
const definitionRes = await getDefinitionsApi(input);
pageState.total = groupRes.items.length;
definitionGroups.value = groupRes.items.map((group) => {
const localizableGroup = deserialize(group.displayName);
const definitions = definitionRes.items
@ -276,7 +312,7 @@ async function onGet(input?: Record<string, string>) {
}),
};
});
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -288,21 +324,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = definitionGroups.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
modalApi.setData({});
modalApi.open();

14
apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue

@ -107,6 +107,7 @@ const gridOptions: VxeGridProps<Notification> = {
}
},
minWidth: 50,
sortable: true,
title: $t('Notifications.Notifications:Type'),
},
{
@ -116,6 +117,7 @@ const gridOptions: VxeGridProps<Notification> = {
return cellValue ? formatToDateTime(cellValue) : '';
},
minWidth: 120,
sortable: true,
title: $t('Notifications.Notifications:SendTime'),
},
{
@ -144,8 +146,10 @@ const gridOptions: VxeGridProps<Notification> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
const { totalCount, items } = await getMyNotifilersApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -171,8 +175,9 @@ const gridOptions: VxeGridProps<Notification> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
@ -184,6 +189,9 @@ const gridEvents: VxeGridListeners<Notification> = {
checkboxChange: (params) => {
selectedKeys.value = params.records.map((record) => record.id);
},
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({

32
apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { VxeGridProps } from '@abp/ui';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { VbenFormProps } from '@vben/common-ui';
@ -67,41 +67,48 @@ const gridOptions: VxeGridProps<OpenIddictApplicationDto> = {
align: 'left',
field: 'clientId',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ClientId'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:DisplayName'),
},
{
align: 'center',
field: 'consentType',
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ConsentType'),
width: 200,
},
{
align: 'center',
field: 'clientType',
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ClientType'),
width: 120,
},
{
align: 'center',
field: 'applicationType',
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ApplicationType'),
width: 150,
},
{
align: 'left',
field: 'clientUri',
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ClientUri'),
width: 150,
},
{
align: 'left',
field: 'logoUri',
sortable: true,
title: $t('AbpOpenIddict.DisplayName:LogoUri'),
width: 120,
},
@ -121,8 +128,10 @@ const gridOptions: VxeGridProps<OpenIddictApplicationDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -137,11 +146,17 @@ const gridOptions: VxeGridProps<OpenIddictApplicationDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<OpenIddictApplicationDto> = {
sortChange: () => {
gridApi.query();
},
};
const [ApplicationModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./ApplicationModal.vue'),
@ -159,9 +174,10 @@ const [ApplicationChangeDrawer, applicationChangeDrawerApi] = useVbenDrawer({
connectedComponent: EntityChangeDrawer,
});
const [Grid, { query }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const onCreate = () => {
@ -181,7 +197,7 @@ const onDelete = (row: OpenIddictApplicationDto) => {
onOk: async () => {
await deleteApi(row.id);
message.success($t('AbpUi.DeletedSuccessfully'));
query();
await gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
@ -309,8 +325,8 @@ const onMenuClick = (row: OpenIddictApplicationDto, info: MenuInfo) => {
</div>
</template>
</Grid>
<ApplicationModal @change="() => query()" />
<ApplicationSecretModal @change="() => query()" />
<ApplicationModal @change="() => gridApi.query()" />
<ApplicationSecretModal @change="() => gridApi.query()" />
<ApplicationPermissionModal />
<ApplicationChangeDrawer />
</template>

22
apps/vben5/packages/@abp/openiddict/src/components/authorizations/AuthorizationTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { VxeGridProps } from '@abp/ui';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { SelectValue } from 'ant-design-vue/es/select';
import type { VbenFormProps } from '@vben/common-ui';
@ -103,24 +103,28 @@ const gridOptions: VxeGridProps<OpenIddictAuthorizationDto> = {
align: 'left',
field: 'applicationId',
minWidth: 300,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ApplicationId'),
},
{
align: 'left',
field: 'subject',
minWidth: 300,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
align: 'left',
field: 'type',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Type'),
},
{
align: 'left',
field: 'status',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Status'),
},
{
@ -130,6 +134,7 @@ const gridOptions: VxeGridProps<OpenIddictAuthorizationDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
@ -145,8 +150,10 @@ const gridOptions: VxeGridProps<OpenIddictAuthorizationDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -161,11 +168,17 @@ const gridOptions: VxeGridProps<OpenIddictAuthorizationDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<OpenIddictAuthorizationDto> = {
sortChange: () => {
gridApi.query();
},
};
const [AuthorizationModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./AuthorizationModal.vue'),
@ -175,6 +188,7 @@ const [AuthorizationModal, modalApi] = useVbenModal({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const onSearchClient = debounce(async (filter?: string) => {

27
apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { VxeGridProps } from '@abp/ui';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { VbenFormProps } from '@vben/common-ui';
@ -64,18 +64,21 @@ const gridOptions: VxeGridProps<OpenIddictScopeDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:DisplayName'),
},
{
align: 'center',
field: 'description',
minWidth: 200,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Description'),
},
{
@ -85,6 +88,7 @@ const gridOptions: VxeGridProps<OpenIddictScopeDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 120,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
@ -99,8 +103,10 @@ const gridOptions: VxeGridProps<OpenIddictScopeDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -115,11 +121,17 @@ const gridOptions: VxeGridProps<OpenIddictScopeDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<OpenIddictScopeDto> = {
sortChange: () => {
gridApi.query();
},
};
const [ScopeModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./ScopeModal.vue')),
@ -127,9 +139,10 @@ const [ScopeModal, modalApi] = useVbenModal({
const [ScopeChangeDrawer, scopeChangeDrawerApi] = useVbenDrawer({
connectedComponent: EntityChangeDrawer,
});
const [Grid, { query }] = useVbenVxeGrid({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const onCreate = () => {
@ -149,7 +162,7 @@ const onDelete = (row: OpenIddictScopeDto) => {
onOk: async () => {
await deleteApi(row.id);
message.success($t('AbpUi.DeletedSuccessfully'));
query();
await gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
@ -231,7 +244,7 @@ const onMenuClick = (row: OpenIddictScopeDto, info: MenuInfo) => {
</div>
</template>
</Grid>
<ScopeModal @change="() => query()" />
<ScopeModal @change="() => gridApi.query()" />
<ScopeChangeDrawer />
</template>

23
apps/vben5/packages/@abp/openiddict/src/components/tokens/TokenTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { VxeGridProps } from '@abp/ui';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { SelectValue } from 'ant-design-vue/es/select';
import type { VbenFormProps } from '@vben/common-ui';
@ -120,24 +120,28 @@ const gridOptions: VxeGridProps<OpenIddictTokenDto> = {
align: 'left',
field: 'applicationId',
minWidth: 300,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ApplicationId'),
},
{
align: 'left',
field: 'subject',
minWidth: 300,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Subject'),
},
{
align: 'left',
field: 'type',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Type'),
},
{
align: 'left',
field: 'status',
minWidth: 150,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:Status'),
},
{
@ -147,6 +151,7 @@ const gridOptions: VxeGridProps<OpenIddictTokenDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:CreationDate'),
},
{
@ -156,6 +161,7 @@ const gridOptions: VxeGridProps<OpenIddictTokenDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
sortable: true,
title: $t('AbpOpenIddict.DisplayName:ExpirationDate'),
},
{
@ -171,8 +177,10 @@ const gridOptions: VxeGridProps<OpenIddictTokenDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -187,11 +195,17 @@ const gridOptions: VxeGridProps<OpenIddictTokenDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<OpenIddictTokenDto> = {
sortChange: () => {
gridApi.query();
},
};
const [TokenModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./TokenModal.vue')),
});
@ -199,6 +213,7 @@ const [TokenModal, modalApi] = useVbenModal({
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const onSearchClient = debounce(async (filter?: string) => {

16
apps/vben5/packages/@abp/oss/src/components/containers/ContainerTable.vue

@ -56,6 +56,7 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpOssManagement.DisplayName:Name'),
},
{
@ -65,6 +66,7 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
return cellValue ? formatToDateTime(cellValue) : '';
},
minWidth: 120,
sortable: true,
title: $t('AbpOssManagement.DisplayName:CreationDate'),
},
{
@ -74,12 +76,14 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
return cellValue ? formatToDateTime(cellValue) : '';
},
minWidth: 120,
sortable: true,
title: $t('AbpOssManagement.DisplayName:LastModifiedDate'),
},
{
align: 'left',
field: 'size',
minWidth: 150,
sortable: true,
title: $t('AbpOssManagement.DisplayName:Size'),
},
{
@ -94,8 +98,10 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
const res = await getListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -114,8 +120,9 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
@ -127,6 +134,9 @@ const gridEvents: VxeGridListeners<OssContainerDto> = {
checkboxChange: (params) => {
selectedKeys.value = params.records.map((record) => record.name);
},
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({

19
apps/vben5/packages/@abp/oss/src/components/objects/FileList.vue

@ -48,6 +48,7 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpOssManagement.DisplayName:Name'),
},
{
@ -59,6 +60,7 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
: $t('AbpOssManagement.DisplayName:Standard');
},
minWidth: 150,
sortable: true,
title: $t('AbpOssManagement.DisplayName:FileType'),
},
{
@ -87,6 +89,7 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
return `${kb} KB`;
},
minWidth: 150,
sortable: true,
title: $t('AbpOssManagement.DisplayName:Size'),
},
{
@ -96,6 +99,7 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
return cellValue ? formatToDateTime(cellValue) : '';
},
minWidth: 120,
sortable: true,
title: $t('AbpOssManagement.DisplayName:CreationDate'),
},
{
@ -105,6 +109,7 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
return cellValue ? formatToDateTime(cellValue) : '';
},
minWidth: 120,
sortable: true,
title: $t('AbpOssManagement.DisplayName:LastModifiedDate'),
},
{
@ -119,11 +124,13 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }) => {
query: async ({ page, sort }) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
const res = await getObjectsApi({
bucket: props.bucket,
maxResultCount: page.pageSize,
prefix: props.path,
sorting,
skipCount: (page.currentPage - 1) * page.pageSize,
});
return {
@ -138,10 +145,15 @@ const gridOptions: VxeGridProps<OssContainerDto> = {
list: 'items',
},
},
sortConfig: {
remote: true,
},
toolbarConfig: {
custom: true,
export: false,
refresh: false,
refresh: {
code: 'query',
},
zoom: true,
},
};
@ -153,6 +165,9 @@ const gridEvents: VxeGridListeners<OssContainerDto> = {
checkboxChange: (params) => {
selectedKeys.value = params.records.map((record) => record.name);
},
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({

2
apps/vben5/packages/@abp/oss/src/components/objects/FolderTree.vue

@ -185,7 +185,7 @@ onMounted(onInit);
<style scoped lang="scss">
:deep(.ant-tree) {
.ant-tree-title {
word-break: break-word;
// word-break: break-word;
white-space: normal;
}
}

60
apps/vben5/packages/@abp/permissions/src/components/definitions/groups/PermissionGroupDefinitionTable.vue

@ -6,14 +6,14 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { PermissionGroupDefinitionDto } from '../../../types/groups';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { useLocalization, useLocalizationSerializer } from '@abp/core';
import { sortby, useLocalization, useLocalizationSerializer } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
@ -38,11 +38,6 @@ const MenuItem = Menu.Item;
const PermissionsOutlined = createIconifyIcon('icon-park-outline:permissions');
const permissionGroups = ref<PermissionGroupDefinitionDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const { Lr } = useLocalization();
const { hasAccessByCodes } = useAccess();
@ -54,7 +49,6 @@ const formOptions: VbenFormProps = {
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -77,12 +71,14 @@ const gridOptions: VxeGridProps<PermissionGroupDefinitionDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:DisplayName'),
},
{
@ -95,6 +91,30 @@ const gridOptions: VxeGridProps<PermissionGroupDefinitionDto> = {
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(permissionGroups.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: permissionGroups.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -104,10 +124,8 @@ const gridOptions: VxeGridProps<PermissionGroupDefinitionDto> = {
};
const gridEvents: VxeGridListeners<PermissionGroupDefinitionDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -132,7 +150,6 @@ async function onGet(input?: Record<string, string>) {
try {
gridApi.setLoading(true);
const { items } = await getListApi(input);
pageState.total = items.length;
permissionGroups.value = items.map((item) => {
const localizableString = deserialize(item.displayName);
return {
@ -140,7 +157,7 @@ async function onGet(input?: Record<string, string>) {
displayName: Lr(localizableString.resourceName, localizableString.name),
};
});
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -152,21 +169,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = permissionGroups.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
groupModalApi.setData({});
groupModalApi.open();

67
apps/vben5/packages/@abp/permissions/src/components/definitions/permissions/PermissionDefinitionTable.vue

@ -6,13 +6,14 @@ import type { VbenFormProps } from '@vben/common-ui';
import type { PermissionDefinitionDto } from '../../../types/definitions';
import type { PermissionGroupDefinitionDto } from '../../../types/groups';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
listToTree,
sortby,
useLocalization,
useLocalizationSerializer,
} from '@abp/core';
@ -52,11 +53,6 @@ interface PermissionGroupVo {
}
const permissionGroups = ref<PermissionGroupVo[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const { Lr } = useLocalization();
const { deserialize } = useLocalizationSerializer();
@ -70,7 +66,6 @@ const formOptions: VbenFormProps = {
collapsed: false,
handleReset: onReset,
async handleSubmit(params) {
pageState.current = 1;
await onGet(params);
},
schema: [
@ -105,12 +100,14 @@ const gridOptions: VxeGridProps<PermissionGroupDefinitionDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:DisplayName'),
},
],
@ -120,6 +117,30 @@ const gridOptions: VxeGridProps<PermissionGroupDefinitionDto> = {
},
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(permissionGroups.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: permissionGroups.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -137,6 +158,8 @@ const subGridColumns: VxeGridProps<PermissionDefinitionDto>['columns'] = [
align: 'left',
field: 'name',
minWidth: 150,
resizable: true,
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:Name'),
treeNode: true,
},
@ -144,20 +167,26 @@ const subGridColumns: VxeGridProps<PermissionDefinitionDto>['columns'] = [
align: 'left',
field: 'displayName',
minWidth: 120,
resizable: true,
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:DisplayName'),
},
{
align: 'center',
field: 'multiTenancySide',
minWidth: 100,
resizable: true,
slots: { default: 'tenant' },
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:MultiTenancySide'),
},
{
align: 'center',
field: 'providers',
minWidth: 100,
resizable: true,
slots: { default: 'providers' },
sortable: true,
title: $t('AbpPermissionManagement.DisplayName:Providers'),
},
{
@ -170,10 +199,8 @@ const subGridColumns: VxeGridProps<PermissionDefinitionDto>['columns'] = [
];
const gridEvents: VxeGridListeners<PermissionGroupDefinitionDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -194,7 +221,6 @@ async function onGet(input?: Record<string, string>) {
gridApi.setLoading(true);
const groupRes = await getGroupsApi(input);
const permissionRes = await getPermissionsApi(input);
pageState.total = groupRes.items.length;
permissionGroups.value = groupRes.items.map((group) => {
const localizableGroup = deserialize(group.displayName);
const permissions = permissionRes.items
@ -218,7 +244,7 @@ async function onGet(input?: Record<string, string>) {
}),
};
});
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -230,21 +256,6 @@ async function onReset() {
await onGet(input);
}
function onPageChange() {
const items = permissionGroups.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
modalApi.setData({});
modalApi.open();

5
apps/vben5/packages/@abp/platform/package.json

@ -22,19 +22,24 @@
"dependencies": {
"@abp/components": "workspace:*",
"@abp/core": "workspace:*",
"@abp/notifications": "workspace:*",
"@abp/request": "workspace:*",
"@abp/ui": "workspace:*",
"@ant-design/icons-vue": "catalog:",
"@vben-core/shadcn-ui": "workspace:*",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/preferences": "workspace:*",
"@vben/stores": "workspace:*",
"@vben/types": "workspace:*",
"ant-design-vue": "catalog:",
"lodash.clonedeep": "catalog:",
"vue": "catalog:*",
"vue3-colorpicker": "catalog:",
"vxe-table": "catalog:"
},
"devDependencies": {

1
apps/vben5/packages/@abp/platform/src/api/index.ts

@ -2,6 +2,7 @@ export { useDataDictionariesApi } from './useDataDictionariesApi';
export { useEmailMessagesApi } from './useEmailMessagesApi';
export { useLayoutsApi } from './useLayoutsApi';
export { useMenusApi } from './useMenusApi';
export { useMyFavoriteMenusApi } from './useMyFavoriteMenusApi';
export { useMyMenusApi } from './useMyMenusApi';
export { useRoleMenusApi } from './useRoleMenusApi';
export { useSmsMessagesApi } from './useSmsMessagesApi';

62
apps/vben5/packages/@abp/platform/src/api/useMyFavoriteMenusApi.ts

@ -0,0 +1,62 @@
import type { ListResultDto } from '@abp/core';
import type {
UserFavoriteMenuCreateDto,
UserFavoriteMenuDto,
} from '../types/favorites';
import { useRequest } from '@abp/request';
export function useMyFavoriteMenusApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
* @returns
*/
function createApi(
input: UserFavoriteMenuCreateDto,
): Promise<UserFavoriteMenuDto> {
return request<UserFavoriteMenuDto>(
`/api/platform/menus/favorites/my-favorite-menus`,
{
data: input,
method: 'POST',
},
);
}
/**
*
* @param id Id
*/
function deleteApi(id: string): Promise<void> {
return request(`/api/platform/menus/favorites/my-favorite-menus/${id}`, {
method: 'DELETE',
});
}
/**
*
* @param framework ui框架
* @returns
*/
function getListApi(
framework?: string,
): Promise<ListResultDto<UserFavoriteMenuDto>> {
return request<ListResultDto<UserFavoriteMenuDto>>(
`/api/platform/menus/favorites/my-favorite-menus?framework=${framework}`,
{
method: 'GET',
},
);
}
return {
cancel,
createApi,
deleteApi,
getListApi,
};
}

72
apps/vben5/packages/@abp/platform/src/components/data-dictionaries/DataDictionaryItemDrawer.vue

@ -3,12 +3,12 @@ import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { DataDto, DataItemDto } from '../../types/dataDictionaries';
import { defineAsyncComponent, h, reactive, ref } from 'vue';
import { defineAsyncComponent, h, ref } from 'vue';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { isNullOrWhiteSpace } from '@abp/core';
import { isNullOrWhiteSpace, sortby } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
CheckOutlined,
@ -25,11 +25,6 @@ import { ValueType } from '../../types/dataDictionaries';
const { deleteItemApi, getApi } = useDataDictionariesApi();
const dataItems = ref<DataItemDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const valueTypeMaps: { [key: number]: string } = {
[ValueType.Array]: 'Array',
[ValueType.Boolean]: 'Boolean',
@ -66,6 +61,7 @@ const gridOptions: VxeGridProps<DataItemDto> = {
align: 'left',
field: 'name',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:Name'),
treeNode: true,
},
@ -73,6 +69,7 @@ const gridOptions: VxeGridProps<DataItemDto> = {
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:DisplayName'),
},
{
@ -88,6 +85,7 @@ const gridOptions: VxeGridProps<DataItemDto> = {
return valueTypeMaps[row.valueType] ?? row.valueType;
},
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:ValueType'),
},
{
@ -101,6 +99,7 @@ const gridOptions: VxeGridProps<DataItemDto> = {
field: 'allowBeNull',
minWidth: 150,
slots: { default: 'allowBeNull' },
sortable: true,
title: $t('AppPlatform.DisplayName:AllowBeNull'),
},
{
@ -113,25 +112,46 @@ const gridOptions: VxeGridProps<DataItemDto> = {
],
exportConfig: {},
keepSource: true,
pagerConfig: {
pageSize: 15,
pageSizes: [10, 15, 30, 50, 100],
},
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(dataItems.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: dataItems.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
refresh: false,
zoom: true,
},
treeConfig: {
accordion: true,
parentField: 'parentId',
rowField: 'id',
transform: true,
},
height: 'auto',
};
const gridEvents: VxeGridListeners<DataItemDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
};
@ -155,8 +175,7 @@ async function onGet() {
drawerApi.setState({ loading: true });
const dto = await getApi(id);
dataItems.value = dto.items;
pageState.total = dto.items.length;
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
drawerApi.setState({
loading: false,
@ -164,21 +183,6 @@ async function onGet() {
}
}
function onPageChange() {
const items = dataItems.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
const data = drawerApi.getData<DataDto>();
itemModalApi.setData({ data });

60
apps/vben5/packages/@abp/platform/src/components/data-dictionaries/DataDictionaryTable.vue

@ -4,13 +4,13 @@ import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { DataDto } from '../../types/dataDictionaries';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { listToTree, useAuthorization } from '@abp/core';
import { listToTree, sortby, useAuthorization } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
@ -35,11 +35,6 @@ const { deleteApi, getAllApi } = useDataDictionariesApi();
const expandRowKeys = ref<string[]>([]);
const dataDictionaries = ref<DataDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const gridOptions: VxeGridProps<DataDto> = {
columns: [
@ -53,6 +48,7 @@ const gridOptions: VxeGridProps<DataDto> = {
field: 'name',
minWidth: 150,
slots: { default: 'name' },
sortable: true,
title: $t('AppPlatform.DisplayName:Name'),
treeNode: true,
},
@ -60,12 +56,14 @@ const gridOptions: VxeGridProps<DataDto> = {
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:DisplayName'),
},
{
align: 'left',
field: 'description',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:Description'),
},
{
@ -89,6 +87,30 @@ const gridOptions: VxeGridProps<DataDto> = {
rowConfig: {
keyField: 'id',
},
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(dataDictionaries.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: dataDictionaries.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -105,10 +127,8 @@ const gridOptions: VxeGridProps<DataDto> = {
},
};
const gridEvents: VxeGridListeners<DataDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
toggleTreeExpand(params) {
if (params.expanded) {
@ -146,9 +166,8 @@ async function onGet() {
id: 'id',
pid: 'parentId',
});
pageState.total = treeItems.length;
dataDictionaries.value = treeItems;
onPageChange();
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -162,21 +181,6 @@ function onExpandChange() {
});
}
function onPageChange() {
const items = dataDictionaries.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate() {
modalApi.setData({});
modalApi.open();

2
apps/vben5/packages/@abp/platform/src/components/index.ts

@ -4,3 +4,5 @@ export { default as MenuAllotModal } from './menus/MenuAllotModal.vue';
export { default as MenuTable } from './menus/MenuTable.vue';
export { default as EmailMessageTable } from './messages/email/EmailMessageTable.vue';
export { default as SmsMessageTable } from './messages/sms/SmsMessageTable.vue';
export { default as Workbench } from './workbench/index.vue';
export * from './workbench/types';

22
apps/vben5/packages/@abp/platform/src/components/layouts/LayoutTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { VxeGridProps } from '@abp/ui';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { VbenFormProps } from '@vben/common-ui';
@ -68,36 +68,42 @@ const gridOptions: VxeGridProps<LayoutDto> = {
field: 'name',
fixed: 'left',
minWidth: 180,
sortable: true,
title: $t('AppPlatform.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:DisplayName'),
},
{
align: 'center',
field: 'path',
minWidth: 200,
sortable: true,
title: $t('AppPlatform.DisplayName:Path'),
},
{
align: 'left',
field: 'framework',
minWidth: 180,
sortable: true,
title: $t('AppPlatform.DisplayName:UIFramework'),
},
{
align: 'left',
field: 'description',
minWidth: 220,
sortable: true,
title: $t('AppPlatform.DisplayName:Description'),
},
{
align: 'left',
field: 'redirect',
minWidth: 160,
sortable: true,
title: $t('AppPlatform.DisplayName:Redirect'),
},
{
@ -120,8 +126,10 @@ const gridOptions: VxeGridProps<LayoutDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -136,14 +144,22 @@ const gridOptions: VxeGridProps<LayoutDto> = {
toolbarConfig: {
custom: true,
export: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<LayoutDto> = {
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [LayoutModal, modalApi] = useVbenModal({

52
apps/vben5/packages/@abp/platform/src/components/menus/MenuDrawer.vue

@ -1,18 +1,14 @@
<script setup lang="ts">
import type { MenuDto } from '../../types';
import type { DataItemDto } from '../../types/dataDictionaries';
import type { MenuDrawerState } from './types';
import { ref } from 'vue';
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
formatToDate,
formatToDateTime,
isNullOrWhiteSpace,
listToTree,
} from '@abp/core';
import { formatToDate, formatToDateTime, isNullOrWhiteSpace } from '@abp/core';
import { Button, Card, message, Steps } from 'ant-design-vue';
import { useMenusApi } from '../../api';
@ -38,7 +34,7 @@ const parentMenu = ref<MenuDto>();
const menuMetas = ref<DataItemDto[]>([]);
const activeTabKey = ref<TabKey>('basic');
const { createApi, getAllApi, getApi, updateApi } = useMenusApi();
const { createApi, getApi, updateApi } = useMenusApi();
const { getApi: getLayoutApi, getPagedListApi: getLayoutsApi } =
useLayoutsApi();
const { getApi: getDataDictionaryApi } = useDataDictionariesApi();
@ -85,20 +81,14 @@ const [BasicForm, basicFormApi] = useVbenForm({
},
},
{
component: 'ApiTreeSelect',
component: 'TreeSelect',
componentProps: {
allowClear: true,
api: async () => {
const { items } = await getAllApi();
return listToTree(items, {
id: 'id',
pid: 'parentId',
});
fieldNames: {
label: 'displayName',
value: 'id',
},
labelField: 'displayName',
onChange: onParentIdChange,
valueField: 'id',
childrenField: 'children',
},
fieldName: 'parentId',
label: $t('AppPlatform.DisplayName:ParentMenu'),
@ -183,6 +173,7 @@ async function onNextStep() {
async function onInit() {
metaFormApi.removeSchemaByFields(menuMetas.value.map((x) => x.name));
const { editMenu, rootMenus } = drawerApi.getData<MenuDrawerState>();
basicFormApi.resetForm();
metaFormApi.resetForm();
activeTabKey.value = 'basic';
@ -191,18 +182,18 @@ async function onInit() {
submiting.value = false;
menuMetas.value = [];
currentStep.value = 0;
const { id, layoutId, parentId } = drawerApi.getData<MenuDto>();
if (!isNullOrWhiteSpace(layoutId)) {
await onLayoutChange(layoutId);
await basicFormApi.setFieldValue('layoutId', layoutId);
onInitRootMenus(rootMenus);
if (!isNullOrWhiteSpace(editMenu?.layoutId)) {
await onLayoutChange(editMenu!.layoutId);
await basicFormApi.setFieldValue('layoutId', editMenu!.layoutId);
}
if (isNullOrWhiteSpace(id)) {
await basicFormApi.setFieldValue('parentId', parentId);
await onParentIdChange(parentId);
if (isNullOrWhiteSpace(editMenu?.id)) {
await basicFormApi.setFieldValue('parentId', editMenu!.parentId);
await onParentIdChange(editMenu!.parentId);
drawerApi.setState({ title: $t('AppPlatform.Menu:AddNew') });
return;
}
const dto = await getApi(id);
const dto = await getApi(editMenu!.id);
await basicFormApi.setValues(dto);
// 使
await onParentIdChange(undefined);
@ -252,6 +243,17 @@ async function onLayoutChange(layoutId?: string) {
}
}
function onInitRootMenus(rootMenus: MenuDto[]) {
basicFormApi.updateSchema([
{
fieldName: 'parentId',
componentProps: {
treeData: rootMenus,
},
},
]);
}
function onInitMetaFormSchemas() {
metaFormApi.removeSchemaByFields(menuMetas.value.map((x) => x.name));
const metaValues: Record<string, any> = {};

79
apps/vben5/packages/@abp/platform/src/components/menus/MenuTable.vue

@ -4,14 +4,15 @@ import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { VbenFormProps } from '@vben/common-ui';
import type { MenuDto } from '../../types/menus';
import type { MenuDrawerState } from './types';
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue';
import { defineAsyncComponent, h, onMounted, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { listToTree, useAuthorization } from '@abp/core';
import { listToTree, sortby, useAuthorization } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import {
DeleteOutlined,
@ -33,12 +34,7 @@ const { deleteApi, getAllApi } = useMenusApi();
const { getPagedListApi: getLayoutsApi } = useLayoutsApi();
const expandRowKeys = ref<string[]>([]);
const dataDictionaries = ref<MenuDto[]>([]);
const pageState = reactive({
current: 1,
size: 10,
total: 0,
});
const menus = ref<MenuDto[]>([]);
const formOptions: VbenFormProps = {
//
@ -94,6 +90,7 @@ const gridOptions: VxeGridProps<MenuDto> = {
field: 'name',
minWidth: 250,
slots: { default: 'name' },
sortable: true,
title: $t('AppPlatform.DisplayName:Name'),
treeNode: true,
},
@ -101,12 +98,14 @@ const gridOptions: VxeGridProps<MenuDto> = {
align: 'left',
field: 'displayName',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:DisplayName'),
},
{
align: 'left',
field: 'description',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:Description'),
},
{
@ -131,6 +130,30 @@ const gridOptions: VxeGridProps<MenuDto> = {
rowConfig: {
keyField: 'id',
},
proxyConfig: {
ajax: {
query: async ({ page, sort }) => {
let items = sortby(menus.value, sort.field);
if (sort.order === 'desc') {
items = items.reverse();
}
const result = {
totalCount: menus.value.length,
items: items.slice(
(page.currentPage - 1) * page.pageSize,
page.currentPage * page.pageSize,
),
};
return new Promise((resolve) => {
resolve(result);
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
@ -148,10 +171,8 @@ const gridOptions: VxeGridProps<MenuDto> = {
};
const gridEvents: VxeGridListeners<MenuDto> = {
pageChange(params) {
pageState.current = params.currentPage;
pageState.size = params.pageSize;
onPageChange();
sortChange: () => {
gridApi.query();
},
toggleTreeExpand(params) {
if (params.expanded) {
@ -184,9 +205,8 @@ async function onGet() {
id: 'id',
pid: 'parentId',
});
pageState.total = treeItems.length;
dataDictionaries.value = treeItems;
onPageChange();
menus.value = treeItems;
setTimeout(() => gridApi.reload(), 100);
} finally {
gridApi.setLoading(false);
}
@ -200,31 +220,22 @@ function onExpandChange() {
});
}
function onPageChange() {
const items = dataDictionaries.value.slice(
(pageState.current - 1) * pageState.size,
pageState.current * pageState.size,
);
gridApi.setGridOptions({
data: items,
pagerConfig: {
currentPage: pageState.current,
pageSize: pageState.size,
total: pageState.total,
},
});
}
function onCreate(row?: MenuDto) {
drawerApi.setData({
layoutId: row?.layoutId,
parentId: row?.id,
drawerApi.setData<MenuDrawerState>({
editMenu: {
layoutId: row?.layoutId,
parentId: row?.id,
},
rootMenus: menus.value,
});
drawerApi.open();
}
function onUpdate(row: MenuDto) {
drawerApi.setData(row);
drawerApi.setData<MenuDrawerState>({
editMenu: row,
rootMenus: menus.value,
});
drawerApi.open();
}

15
apps/vben5/packages/@abp/platform/src/components/menus/types.ts

@ -1,3 +1,16 @@
import type { MenuDto } from '../../types/menus';
type MenuSubject = 'role' | 'user';
export type { MenuSubject };
type EditMenu = {
id?: string;
layoutId?: string;
parentId?: string;
};
type MenuDrawerState = {
editMenu?: EditMenu;
rootMenus: MenuDto[];
};
export type { MenuDrawerState, MenuSubject };

26
apps/vben5/packages/@abp/platform/src/components/messages/email/EmailMessageTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { VxeGridProps } from '@abp/ui';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { VbenFormProps } from '@vben/common-ui';
@ -103,6 +103,7 @@ const gridOptions: VxeGridProps<EmailMessageDto> = {
align: 'left',
field: 'provider',
minWidth: 180,
sortable: true,
title: $t('AppPlatform.DisplayName:Provider'),
},
{
@ -110,6 +111,7 @@ const gridOptions: VxeGridProps<EmailMessageDto> = {
field: 'status',
minWidth: 150,
slots: { default: 'status' },
sortable: true,
title: $t('AppPlatform.DisplayName:Status'),
},
{
@ -119,36 +121,42 @@ const gridOptions: VxeGridProps<EmailMessageDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:SendTime'),
},
{
align: 'center',
field: 'sendCount',
minWidth: 100,
sortable: true,
title: $t('AppPlatform.DisplayName:SendCount'),
},
{
align: 'left',
field: 'subject',
minWidth: 180,
sortable: true,
title: $t('AppPlatform.DisplayName:Subject'),
},
{
align: 'left',
field: 'content',
minWidth: 220,
sortable: true,
title: $t('AppPlatform.DisplayName:Content'),
},
{
align: 'left',
field: 'from',
minWidth: 220,
sortable: true,
title: $t('AppPlatform.DisplayName:From'),
},
{
align: 'left',
field: 'receiver',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:Receiver'),
},
{
@ -158,12 +166,14 @@ const gridOptions: VxeGridProps<EmailMessageDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
sortable: true,
title: $t('AppPlatform.DisplayName:CreationTime'),
},
{
align: 'left',
field: 'reason',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:Reason'),
},
{
@ -179,8 +189,10 @@ const gridOptions: VxeGridProps<EmailMessageDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -195,14 +207,22 @@ const gridOptions: VxeGridProps<EmailMessageDto> = {
toolbarConfig: {
custom: true,
export: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<EmailMessageDto> = {
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [EmailMessageModal, modalApi] = useVbenModal({

25
apps/vben5/packages/@abp/platform/src/components/messages/sms/SmsMessageTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { VxeGridProps } from '@abp/ui';
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { VbenFormProps } from '@vben/common-ui';
@ -98,6 +98,7 @@ const gridOptions: VxeGridProps<SmsMessageDto> = {
align: 'left',
field: 'provider',
minWidth: 180,
sortable: true,
title: $t('AppPlatform.DisplayName:Provider'),
},
{
@ -105,6 +106,7 @@ const gridOptions: VxeGridProps<SmsMessageDto> = {
field: 'status',
minWidth: 150,
slots: { default: 'status' },
sortable: true,
title: $t('AppPlatform.DisplayName:Status'),
},
{
@ -114,24 +116,28 @@ const gridOptions: VxeGridProps<SmsMessageDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:SendTime'),
},
{
align: 'left',
field: 'content',
minWidth: 220,
sortable: true,
title: $t('AppPlatform.DisplayName:Content'),
},
{
align: 'left',
field: 'receiver',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:Receiver'),
},
{
align: 'center',
field: 'sendCount',
minWidth: 100,
sortable: true,
title: $t('AppPlatform.DisplayName:SendCount'),
},
{
@ -141,12 +147,14 @@ const gridOptions: VxeGridProps<SmsMessageDto> = {
return cellValue ? formatToDateTime(cellValue) : cellValue;
},
minWidth: 200,
sortable: true,
title: $t('AppPlatform.DisplayName:CreationTime'),
},
{
align: 'left',
field: 'reason',
minWidth: 150,
sortable: true,
title: $t('AppPlatform.DisplayName:Reason'),
},
{
@ -162,8 +170,10 @@ const gridOptions: VxeGridProps<SmsMessageDto> = {
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
query: async ({ page, sort }, formValues) => {
const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined;
return await getPagedListApi({
sorting,
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
@ -178,15 +188,22 @@ const gridOptions: VxeGridProps<SmsMessageDto> = {
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
refresh: {
code: 'query',
},
zoom: true,
},
};
const gridEvents: VxeGridListeners<SmsMessageDto> = {
sortChange: () => {
gridApi.query();
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
function onDelete(row: SmsMessageDto) {

45
apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchHeader.vue

@ -0,0 +1,45 @@
<script lang="ts" setup>
import { VbenAvatar } from '@vben-core/shadcn-ui';
interface Props {
avatar?: string;
notifierCount?: number;
text?: string;
}
defineOptions({
name: 'WorkbenchHeader',
});
withDefaults(defineProps<Props>(), {
avatar: '',
text: '',
notifierCount: 0,
});
</script>
<template>
<div class="card-box p-4 py-6 lg:flex">
<VbenAvatar :alt="text" :src="avatar" class="size-20" />
<div
v-if="$slots.title || $slots.description"
class="flex flex-col justify-center md:ml-6 md:mt-0"
>
<h1 v-if="$slots.title" class="text-md font-semibold md:text-xl">
<slot name="title"></slot>
</h1>
<span v-if="$slots.description" class="text-foreground/80 mt-1">
<slot name="description"></slot>
</span>
</div>
<div class="mt-4 flex flex-1 justify-end md:mt-0">
<div class="flex flex-col justify-center text-right">
<span class="text-foreground/80">
{{ $t('workbench.header.notifier.title') }}
</span>
<a class="text-2xl">{{
$t('workbench.header.notifier.count', [notifierCount])
}}</a>
</div>
</div>
</div>
</template>

124
apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue

@ -0,0 +1,124 @@
<script setup lang="ts">
import type { FavoriteMenu } from '../types';
import { computed, h } from 'vue';
import { $t } from '@vben/locales';
import {
Card,
CardContent,
CardHeader,
CardTitle,
VbenIcon,
} from '@vben-core/shadcn-ui';
import { DeleteOutlined } from '@ant-design/icons-vue';
import { Dropdown, Menu, Modal } from 'ant-design-vue';
interface Props {
items?: FavoriteMenu[];
title: string;
}
defineOptions({
name: 'WorkbenchQuickNav',
});
const props = withDefaults(defineProps<Props>(), {
items: () => [],
});
const emits = defineEmits<{
(event: 'click', menu: FavoriteMenu): void;
(event: 'delete', menu: FavoriteMenu): void;
(event: 'add'): void;
}>();
const MenuItem = Menu.Item;
const getFavoriteMenus = computed(() => {
const addMenu: FavoriteMenu = {
id: 'addMenu',
displayName: $t('workbench.content.favoriteMenu.create'),
icon: 'ion:add-outline',
color: '#00bfff',
isDefault: true,
};
return [...props.items, addMenu];
});
function onClick(menu: FavoriteMenu) {
if (menu.id === 'addMenu') {
emits('add');
return;
}
emits('click', menu);
}
function onMenuClick(key: string, menu: FavoriteMenu) {
switch (key) {
case 'delete': {
Modal.confirm({
centered: true,
iconType: 'warning',
title: $t('AbpUi.AreYouSure'),
content: $t('AbpUi.ItemWillBeDeletedMessage'),
okCancel: true,
onOk: () => {
emits('delete', menu);
},
});
}
}
}
</script>
<template>
<Card>
<CardHeader class="py-4">
<CardTitle class="text-lg">{{ title }}</CardTitle>
</CardHeader>
<CardContent class="flex flex-wrap p-0">
<template
v-for="(item, index) in getFavoriteMenus"
:key="item.displayName"
>
<Dropdown :trigger="['contextmenu']">
<div
:class="{
'border-r-0': index % 3 === 2,
'border-b-0': index < 3,
'pb-4': index > 2,
'rounded-bl-xl': index === items.length - 3,
'rounded-br-xl': index === items.length - 1,
}"
class="flex-col-center border-border group w-1/3 cursor-pointer border-r border-t py-8 hover:shadow-xl"
@click="onClick(item)"
>
<VbenIcon
:color="item.color"
:icon="item.icon"
class="size-7 transition-all duration-300 group-hover:scale-125"
/>
<span class="text-md mt-2 truncate">{{ item.displayName }}</span>
</div>
<template #overlay>
<Menu
v-if="!item.isDefault"
@click="
({ key: menuKey }) => onMenuClick(menuKey.toString(), item)
"
>
<MenuItem key="delete" :icon="h(DeleteOutlined)">
{{ $t('workbench.content.favoriteMenu.delete') }}
</MenuItem>
</Menu>
</template>
</Dropdown>
</template>
</CardContent>
</Card>
</template>
<style scoped></style>

137
apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue

@ -0,0 +1,137 @@
<script setup lang="ts">
import type { MenuDto, UserFavoriteMenuDto } from '../../../types';
import { defineAsyncComponent, ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { useAppConfig } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { listToTree } from '@abp/core';
import { message, TreeSelect } from 'ant-design-vue';
import { useMyFavoriteMenusApi } from '../../../api/useMyFavoriteMenusApi';
import { useMyMenusApi } from '../../../api/useMyMenusApi';
const emits = defineEmits<{
(event: 'change', data: UserFavoriteMenuDto): void;
}>();
const ColorPicker = defineAsyncComponent(() =>
import('vue3-colorpicker').then((res) => {
import('vue3-colorpicker/style.css');
return res.ColorPicker;
}),
);
const availableMenus = ref<MenuDto[]>([]);
const { getAllApi } = useMyMenusApi();
const { createApi } = useMyFavoriteMenusApi();
const { uiFramework } = useAppConfig(import.meta.env, import.meta.env.PROD);
const [Form, formApi] = useVbenForm({
schema: [
{
label: $t('workbench.content.favoriteMenu.select'),
fieldName: 'menuId',
component: 'TreeSelect',
rules: 'selectRequired',
},
{
label: $t('workbench.content.favoriteMenu.color'),
fieldName: 'color',
component: 'ColorPicker',
defaultValue: '#000000',
modelPropName: 'pureColor',
},
{
label: $t('workbench.content.favoriteMenu.alias'),
fieldName: 'aliasName',
component: 'Input',
},
{
label: $t('workbench.content.favoriteMenu.icon'),
fieldName: 'icon',
component: 'IconPicker',
},
],
showDefaultActions: false,
handleSubmit: onSubmit,
commonConfig: {
colon: true,
componentProps: {
class: 'w-full',
},
},
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen) {
if (isOpen) {
await onInitMenus();
}
},
});
async function onInitMenus() {
const { items } = await getAllApi({
framework: uiFramework,
});
const menus = listToTree<MenuDto>(items, { id: 'id', pid: 'parentId' });
availableMenus.value = menus;
}
async function onSubmit(values: Record<string, any>) {
try {
modalApi.setState({ submitting: true });
const menuDto = await createApi({
framework: uiFramework,
menuId: values.menuId,
color: values.color,
icon: values.icon,
aliasName: values.aliasName,
});
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', menuDto);
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal :title="$t('workbench.content.favoriteMenu.manage')">
<Form>
<template #color="slotProps">
<div class="flex flex-row items-center">
<ColorPicker v-bind="slotProps" format="hex" />
<span>({{ slotProps.value }})</span>
</div>
</template>
<template #menuId="slotProps">
<TreeSelect
allow-clear
class="w-full"
tree-icon
v-bind="slotProps"
:field-names="{ label: 'displayName', value: 'id' }"
:tree-data="availableMenus"
>
<template #title="item">
<div class="flex flex-row items-center gap-1">
<IconifyIcon v-if="item.meta?.icon" :icon="item.meta.icon" />
<span>{{ item.displayName }}</span>
</div>
</template>
</TreeSelect>
</template>
</Form>
</Modal>
</template>
<style scoped></style>

64
apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTodo.vue

@ -0,0 +1,64 @@
<script setup lang="ts">
import type { WorkbenchTodoItem } from '@vben/common-ui';
import {
Card,
CardContent,
CardHeader,
CardTitle,
VbenCheckbox,
} from '@vben-core/shadcn-ui';
interface Props {
items?: WorkbenchTodoItem[];
title: string;
}
defineOptions({
name: 'WorkbenchTodo',
});
withDefaults(defineProps<Props>(), {
items: () => [],
});
</script>
<template>
<Card>
<CardHeader class="py-4">
<CardTitle class="text-lg">{{ title }}</CardTitle>
</CardHeader>
<slot v-if="items.length === 0" name="empty"></slot>
<CardContent v-else class="flex flex-wrap p-5 pt-0">
<ul class="divide-border w-full divide-y" role="list">
<li
v-for="item in items"
:key="item.title"
:class="{
'select-none line-through opacity-60': item.completed,
}"
class="flex cursor-pointer justify-between gap-x-6 py-5"
>
<div class="flex min-w-0 items-center gap-x-4">
<VbenCheckbox v-model:checked="item.completed" name="completed" />
<div class="min-w-0 flex-auto">
<p class="text-foreground text-sm font-semibold leading-6">
{{ item.title }}
</p>
<!-- eslint-disable vue/no-v-html -->
<p
class="text-foreground/80 *:text-primary mt-1 truncate text-xs leading-5"
v-html="item.content"
></p>
</div>
</div>
<div class="hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end">
<span class="text-foreground/80 mt-6 text-xs leading-6">
{{ item.date }}
</span>
</div>
</li>
</ul>
</CardContent>
</Card>
</template>

65
apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTrends.vue

@ -0,0 +1,65 @@
<script setup lang="ts">
import type { WorkbenchTrendItem } from '@vben/common-ui';
import {
Card,
CardContent,
CardHeader,
CardTitle,
VbenIcon,
} from '@vben-core/shadcn-ui';
interface Props {
items?: WorkbenchTrendItem[];
title: string;
}
defineOptions({
name: 'WorkbenchTrends',
});
withDefaults(defineProps<Props>(), {
items: () => [],
});
</script>
<template>
<Card>
<CardHeader class="py-4">
<CardTitle class="text-lg">{{ title }}</CardTitle>
</CardHeader>
<slot v-if="items.length === 0" name="empty"></slot>
<CardContent v-else class="flex flex-wrap p-5 pt-0">
<ul class="divide-border w-full divide-y" role="list">
<li
v-for="item in items"
:key="item.title"
class="flex justify-between gap-x-6 py-5"
>
<div class="flex min-w-0 items-center gap-x-4">
<VbenIcon
:icon="item.avatar"
alt=""
class="size-10 flex-none rounded-full"
/>
<div class="min-w-0 flex-auto">
<p class="text-foreground text-sm font-semibold leading-6">
{{ item.title }}
</p>
<!-- eslint-disable vue/no-v-html -->
<p
class="text-foreground/80 *:text-primary mt-1 truncate text-xs leading-5"
v-html="item.content"
></p>
</div>
</div>
<div class="hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end">
<span class="text-foreground/80 mt-6 text-xs leading-6">
{{ item.date }}
</span>
</div>
</li>
</ul>
</CardContent>
</Card>
</template>

218
apps/vben5/packages/@abp/platform/src/components/workbench/index.vue

@ -0,0 +1,218 @@
<script setup lang="ts">
import type { WorkbenchTodoItem, WorkbenchTrendItem } from '@vben/common-ui';
import type { FavoriteMenu } from './types';
import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useAppConfig } from '@vben/hooks';
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { formatToDateTime } from '@abp/core';
import {
NotificationReadState,
useMyNotifilersApi,
useNotificationSerializer,
} from '@abp/notifications';
import { Empty, message } from 'ant-design-vue';
import { useMyFavoriteMenusApi } from '../../api/useMyFavoriteMenusApi';
import WorkbenchHeader from './components/WorkbenchHeader.vue';
import WorkbenchQuickNav from './components/WorkbenchQuickNav.vue';
import WorkbenchTodo from './components/WorkbenchTodo.vue';
import WorkbenchTrends from './components/WorkbenchTrends.vue';
defineEmits<{
(event: 'navTo', menu: FavoriteMenu): void;
}>();
const userStore = useUserStore();
const { getMyNotifilersApi } = useMyNotifilersApi();
const { getListApi: getFavoriteMenusApi, deleteApi: deleteFavoriteMenuApi } =
useMyFavoriteMenusApi();
const { deserialize } = useNotificationSerializer();
const { uiFramework } = useAppConfig(import.meta.env, import.meta.env.PROD);
const defaultMenus: FavoriteMenu[] = [
{
id: '1',
color: '#1fdaca',
icon: 'ion:home-outline',
displayName: $t('workbench.content.favoriteMenu.home'),
path: '/',
isDefault: true,
},
{
id: '2',
color: '#bf0c2c',
icon: 'ion:grid-outline',
displayName: $t('workbench.content.favoriteMenu.dashboard'),
path: '/',
isDefault: true,
},
{
id: '3',
color: '#00d8ff',
icon: 'ant-design:notification-outlined',
displayName: $t('workbench.content.favoriteMenu.notifiers'),
path: '/manage/notifications/my-notifilers',
isDefault: true,
},
{
id: '4',
color: '#4daf1bc9',
icon: 'tdesign:user-setting',
displayName: $t('workbench.content.favoriteMenu.settings'),
path: '/account/my-settings',
isDefault: true,
},
{
id: '5',
color: '#3fb27f',
icon: 'hugeicons:profile-02',
displayName: $t('workbench.content.favoriteMenu.profile'),
path: '/account/profile',
isDefault: true,
},
];
const unReadNotifilerCount = ref(0);
const unReadNotifilers = ref<WorkbenchTrendItem[]>([]);
const favoriteMenus = ref<FavoriteMenu[]>([]);
const todoList = ref<WorkbenchTodoItem[]>([]);
const getFavoriteMenus = computed(() => {
return [...defaultMenus, ...favoriteMenus.value];
});
const getWelcomeTitle = computed(() => {
const now = new Date();
const hour = now.getHours();
if (hour < 12) {
return $t('workbench.header.welcome.morning', [
userStore.userInfo?.realName,
]);
}
if (hour < 14) {
return $t('workbench.header.welcome.atoon', [userStore.userInfo?.realName]);
}
if (hour < 17) {
return $t('workbench.header.welcome.afternoon', [
userStore.userInfo?.realName,
]);
}
if (hour < 24) {
return $t('workbench.header.welcome.evening', [
userStore.userInfo?.realName,
]);
}
return '';
});
const [WorkbenchQuickNavModal, quickNavModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./components/WorkbenchQuickNavModal.vue'),
),
});
async function onInit() {
await Promise.all([
onInitFavoriteMenus(),
onInitNotifiers(),
onInitTodoList(),
]);
}
async function onInitFavoriteMenus() {
const { items } = await getFavoriteMenusApi(uiFramework);
favoriteMenus.value = items.map((item) => {
return {
...item,
id: item.menuId,
isDefault: false,
};
});
}
async function onInitNotifiers() {
const { items, totalCount } = await getMyNotifilersApi({
maxResultCount: 10,
readState: NotificationReadState.UnRead,
});
unReadNotifilers.value = items.map((item) => {
const notifier = deserialize(item);
return {
avatar: '',
date: formatToDateTime(item.creationTime),
title: notifier.title,
content: notifier.message,
};
});
unReadNotifilerCount.value = totalCount;
}
async function onInitTodoList() {
// TODO:
todoList.value = [];
}
function onCreatingFavoriteMenu() {
quickNavModalApi.open();
}
async function onDeleteFavoriteMenu(menu: FavoriteMenu) {
await deleteFavoriteMenuApi(menu.id);
await onInitFavoriteMenus();
message.success($t('AbpUi.SuccessfullyDeleted'));
}
onMounted(onInit);
</script>
<template>
<div class="p-5">
<WorkbenchHeader
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"
:text="userStore.userInfo?.realName"
:notifier-count="unReadNotifilerCount"
>
<template #title>
{{ getWelcomeTitle }}
</template>
<template #description> 今日晴20 - 32 </template>
</WorkbenchHeader>
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchQuickNav
:items="getFavoriteMenus"
class="mt-5 lg:mt-0"
:title="$t('workbench.content.favoriteMenu.title')"
@add="onCreatingFavoriteMenu"
@delete="onDeleteFavoriteMenu"
@click="(menu: FavoriteMenu) => $emit('navTo', menu)"
/>
<WorkbenchTodo
:items="todoList"
class="mt-5"
:title="$t('workbench.content.todo.title')"
>
<template #empty>
<Empty />
</template>
</WorkbenchTodo>
</div>
<div class="w-full lg:w-2/5">
<WorkbenchTrends
:items="unReadNotifilers"
:title="$t('workbench.content.trends.title')"
>
<template #empty>
<Empty />
</template>
</WorkbenchTrends>
</div>
</div>
<WorkbenchQuickNavModal @change="onInitFavoriteMenus" />
</div>
</template>
<style scoped></style>

10
apps/vben5/packages/@abp/platform/src/components/workbench/types.ts

@ -0,0 +1,10 @@
interface FavoriteMenu {
color?: string;
displayName: string;
icon?: string;
id: string;
isDefault: boolean;
path?: string;
}
export type { FavoriteMenu };

13
apps/vben5/packages/@abp/platform/src/hooks/useMenuTransform.ts

@ -2,9 +2,12 @@ import type { RouteRecordStringComponent } from '@vben/types';
import type { MenuDto } from '../types';
import { useUserStore } from '@vben/stores';
import { listToTree } from '@abp/core';
export function useMenuTransform() {
const userStore = useUserStore();
function mapMetaString(meta: Record<string, any>, key: string) {
if (!meta[key]) {
return undefined;
@ -30,6 +33,16 @@ export function useMenuTransform() {
return Array.isArray(meta[key]) ? meta[key] : String(meta[key]).split(',');
}
function transformRoutes(menus: MenuDto[]): RouteRecordStringComponent[] {
const startupMenus = menus.filter((x) => x.startup);
if (startupMenus.length > 0) {
userStore.$patch((state) => {
state.userInfo && (state.userInfo.homePath = startupMenus[0]?.path);
});
} else {
userStore.$patch((state) => {
state.userInfo && (state.userInfo.homePath = undefined);
});
}
const combMenus = menus.map((item) => {
return {
component: item.component.includes('BasicLayout')

1
apps/vben5/packages/@abp/platform/src/index.ts

@ -1,4 +1,5 @@
export * from './api';
export * from './components';
export * from './hooks';
export * from './locales';
export * from './types';

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save