Browse Source

Merge branch 'dev' into refactor-identity-session

pull/1375/head
yx lin 3 months ago
committed by GitHub
parent
commit
c36048a824
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. 34
      Directory.Packages.props
  5. 6
      apps/vben5/.husky/commit-msg
  6. 3
      apps/vben5/.husky/post-merge
  7. 7
      apps/vben5/.husky/pre-commit
  8. 20
      apps/vben5/.lintstagedrc.mjs
  9. 30
      apps/vben5/.vscode/extensions.json
  10. 37
      apps/vben5/.vscode/global.code-snippets
  11. 51
      apps/vben5/.vscode/launch.json
  12. 241
      apps/vben5/.vscode/settings.json
  13. 5
      apps/vben5/apps/app-antd/package.json
  14. 8
      apps/vben5/apps/app-antd/src/adapter/component/index.ts
  15. 6
      apps/vben5/apps/app-antd/src/adapter/request/index.ts
  16. 67
      apps/vben5/apps/app-antd/src/layouts/basic.vue
  17. 17
      apps/vben5/apps/app-antd/src/locales/index.ts
  18. 4
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  19. 4
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  20. 8
      apps/vben5/apps/app-antd/src/preferences.ts
  21. 83
      apps/vben5/apps/app-antd/src/store/auth.ts
  22. 46
      apps/vben5/apps/app-antd/src/views/_core/fallback/not-found.vue
  23. 89
      apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue
  24. 262
      apps/vben5/apps/app-antd/src/views/dashboard/workspace/index.vue
  25. 1
      apps/vben5/packages/@abp/account/package.json
  26. 6
      apps/vben5/packages/@abp/account/src/api/index.ts
  27. 58
      apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts
  28. 46
      apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts
  29. 73
      apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts
  30. 37
      apps/vben5/packages/@abp/account/src/api/useScanQrCodeApi.ts
  31. 83
      apps/vben5/packages/@abp/account/src/api/useTokenApi.ts
  32. 29
      apps/vben5/packages/@abp/account/src/api/useUserInfoApi.ts
  33. 43
      apps/vben5/packages/@abp/account/src/components/MySetting.vue
  34. 4
      apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue
  35. 55
      apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue
  36. 38
      apps/vben5/packages/@abp/account/src/components/components/BindSettings.vue
  37. 9
      apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue
  38. 2
      apps/vben5/packages/@abp/account/src/hooks/index.ts
  39. 3
      apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts
  40. 28
      apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts
  41. 17
      apps/vben5/packages/@abp/account/src/types/bind.ts
  42. 32
      apps/vben5/packages/@abp/account/src/types/external-logins.ts
  43. 2
      apps/vben5/packages/@abp/account/src/types/index.ts
  44. 11
      apps/vben5/packages/@abp/account/src/types/token.ts
  45. 138
      apps/vben5/packages/@abp/account/src/utils/auth.ts
  46. 19
      apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue
  47. 96
      apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingDrawer.vue
  48. 24
      apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingTable.vue
  49. 9
      apps/vben5/packages/@abp/components/package.json
  50. 200
      apps/vben5/packages/@abp/components/src/cropper/Cropper.vue
  51. 159
      apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue
  52. 309
      apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue
  53. 2
      apps/vben5/packages/@abp/components/src/cropper/index.ts
  54. 8
      apps/vben5/packages/@abp/components/src/cropper/types.ts
  55. 20
      apps/vben5/packages/@abp/components/src/locales/index.ts
  56. 14
      apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json
  57. 14
      apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json
  58. 9
      apps/vben5/packages/@abp/core/package.json
  59. 9
      apps/vben5/packages/@abp/core/src/store/abp.ts
  60. 61
      apps/vben5/packages/@abp/core/src/utils/date.ts
  61. 42
      apps/vben5/packages/@abp/core/src/utils/file.ts
  62. 3
      apps/vben5/packages/@abp/core/src/utils/index.ts
  63. 3
      apps/vben5/packages/@abp/core/src/utils/is.ts
  64. 31
      apps/vben5/packages/@abp/core/src/utils/table.ts
  65. 42
      apps/vben5/packages/@abp/core/src/utils/uuid.ts
  66. 17
      apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue
  67. 15
      apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue
  68. 96
      apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue
  69. 64
      apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionTable.vue
  70. 13
      apps/vben5/packages/@abp/gdpr/src/components/GdprTable.vue
  71. 25
      apps/vben5/packages/@abp/identity/src/components/claim-types/ClaimTypeTable.vue
  72. 23
      apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitRoleTable.vue
  73. 54
      apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitUserTable.vue
  74. 29
      apps/vben5/packages/@abp/identity/src/components/organization-units/SelectMemberModal.vue
  75. 28
      apps/vben5/packages/@abp/identity/src/components/organization-units/SelectRoleModal.vue
  76. 21
      apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue
  77. 19
      apps/vben5/packages/@abp/identity/src/components/security-logs/SecurityLogTable.vue
  78. 19
      apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue
  79. 18
      apps/vben5/packages/@abp/identity/src/components/sessions/UserSessionTable.vue
  80. 43
      apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue
  81. 61
      apps/vben5/packages/@abp/localization/src/components/languages/LocalizationLanguageTable.vue
  82. 61
      apps/vben5/packages/@abp/localization/src/components/resources/LocalizationResourceTable.vue
  83. 61
      apps/vben5/packages/@abp/localization/src/components/texts/LocalizationTextTable.vue
  84. 60
      apps/vben5/packages/@abp/notifications/src/components/definitions/groups/NotificationGroupDefinitionTable.vue
  85. 77
      apps/vben5/packages/@abp/notifications/src/components/definitions/notifications/NotificationDefinitionTable.vue
  86. 14
      apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue
  87. 32
      apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue
  88. 22
      apps/vben5/packages/@abp/openiddict/src/components/authorizations/AuthorizationTable.vue
  89. 27
      apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeTable.vue
  90. 23
      apps/vben5/packages/@abp/openiddict/src/components/tokens/TokenTable.vue
  91. 16
      apps/vben5/packages/@abp/oss/src/components/containers/ContainerTable.vue
  92. 19
      apps/vben5/packages/@abp/oss/src/components/objects/FileList.vue
  93. 2
      apps/vben5/packages/@abp/oss/src/components/objects/FolderTree.vue
  94. 60
      apps/vben5/packages/@abp/permissions/src/components/definitions/groups/PermissionGroupDefinitionTable.vue
  95. 67
      apps/vben5/packages/@abp/permissions/src/components/definitions/permissions/PermissionDefinitionTable.vue
  96. 5
      apps/vben5/packages/@abp/platform/package.json
  97. 1
      apps/vben5/packages/@abp/platform/src/api/index.ts
  98. 62
      apps/vben5/packages/@abp/platform/src/api/useMyFavoriteMenusApi.ts
  99. 72
      apps/vben5/packages/@abp/platform/src/components/data-dictionaries/DataDictionaryItemDrawer.vue
  100. 60
      apps/vben5/packages/@abp/platform/src/components/data-dictionaries/DataDictionaryTable.vue

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.1"
automatic_release_tag: "9.3.5"

34
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.1</VoloAbpPackageVersion>
<LINGYUNAbpPackageVersion>9.2.1</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.1" />
<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,10 +278,11 @@
<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" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
<PackageVersion Include="Elastic.Apm.NetCoreAll" Version="1.31.0" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.5.0" />
<PackageVersion Include="Dapr.Client" Version="1.15.4" />
@ -299,8 +302,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" />
@ -309,6 +312,7 @@
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.12.0-beta.2" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.12.0-beta.1" />
<PackageVersion Include="Polly" Version="8.5.2" />
<PackageVersion Include="QRCoder" Version="1.5.1" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.14.0" />
@ -330,4 +334,4 @@
<PackageVersion Include="Yarp.ReverseProxy" Version="2.1.0" />
<PackageVersion Include="Yarp.Telemetry.Consumption" Version="2.1.0" />
</ItemGroup>
</Project>
</Project>

6
apps/vben5/.husky/commit-msg

@ -1,6 +0,0 @@
echo Start running commit-msg hook...
# Check whether the git commit information is standardized
pnpm exec commitlint --edit "$1"
echo Run commit-msg hook done.

3
apps/vben5/.husky/post-merge

@ -1,3 +0,0 @@
# 每次 git pull 之后, 安装依赖
pnpm install

7
apps/vben5/.husky/pre-commit

@ -1,7 +0,0 @@
# update `.vscode/vben-admin.code-workspace` file
pnpm vsh code-workspace --auto-commit
# Format and submit code according to lintstagedrc.js configuration
pnpm exec lint-staged
echo Run pre-commit hook done.

20
apps/vben5/.lintstagedrc.mjs

@ -1,20 +0,0 @@
export default {
'*.md': ['prettier --cache --ignore-unknown --write'],
'*.vue': [
'prettier --write',
'eslint --cache --fix',
'stylelint --fix --allow-empty-input',
],
'*.{js,jsx,ts,tsx}': [
'prettier --cache --ignore-unknown --write',
'eslint --cache --fix',
],
'*.{scss,less,styl,html,vue,css}': [
'prettier --cache --ignore-unknown --write',
'stylelint --fix --allow-empty-input',
],
'package.json': ['prettier --cache --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
'prettier --cache --write--parser json',
],
};

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"
]
}

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

@ -28,6 +28,7 @@
"dependencies": {
"@abp/account": "workspace:*",
"@abp/auditing": "workspace:*",
"@abp/components": "workspace:*",
"@abp/core": "workspace:*",
"@abp/data-protection": "workspace:*",
"@abp/demo": "workspace:*",
@ -46,6 +47,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 +68,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(() => {

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

@ -13,8 +13,10 @@ import {
} from '@vben/locales';
import { preferences } from '@vben/preferences';
import { loadComponentMessages } from '@abp/components/locales';
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 +36,18 @@ const localesMap = loadLocalesMapFromDir(
* @param lang
*/
async function loadMessages(lang: SupportedLanguagesType) {
const [appLocaleMessages, _, abpLocales] = await Promise.all([
localesMap[lang]?.(),
loadThirdPartyMessage(lang),
loadAbpLocale(lang),
]);
const [appLocaleMessages, compLocales, platformLocales, _, abpLocales] =
await Promise.all([
localesMap[lang]?.(),
loadComponentMessages(lang),
loadPaltformMessages(lang),
loadThirdPartyMessage(lang),
loadAbpLocale(lang),
]);
return {
...appLocaleMessages?.default,
...compLocales?.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>

1
apps/vben5/packages/@abp/account/package.json

@ -20,6 +20,7 @@
}
},
"dependencies": {
"@abp/components": "workspace:*",
"@abp/core": "workspace:*",
"@abp/gdpr": "workspace:*",
"@abp/identity": "workspace:*",

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: '',

55
apps/vben5/packages/@abp/account/src/components/components/BasicSettings.vue

@ -1,7 +1,4 @@
<script setup lang="ts">
import type { UploadChangeParam } from 'ant-design-vue';
import type { FileType } from 'ant-design-vue/es/upload/interface';
import type { ProfileDto, UpdateProfileDto } from '../../types/profile';
import { computed, ref, toValue, watchEffect } from 'vue';
@ -10,20 +7,14 @@ import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { useSettings } from '@abp/core';
import { UploadOutlined } from '@ant-design/icons-vue';
import {
Avatar,
Button,
Card,
Form,
Input,
message,
Upload,
} from 'ant-design-vue';
import { CropperAvatar } from '@abp/components/cropper';
import { buildUUID, useSettings } from '@abp/core';
import { Button, Card, Form, Input, message } from 'ant-design-vue';
import { useProfileApi } from '../../api/useProfileApi';
type ApiFunParams = { file: Blob; fileName?: string; name: string };
const props = defineProps<{
profile: ProfileDto;
}>();
@ -43,33 +34,32 @@ const pictureState = ref<{
const userStore = useUserStore();
const { isTrue } = useSettings();
const { changePictureApi, getPictureApi } = useProfileApi();
const { changePictureApi } = useProfileApi();
const avatar = computed(() => {
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
});
async function onAvatarChange(_param: UploadChangeParam) {
async function onUploadAvatar(params: ApiFunParams) {
pictureState.value.uploading = true;
try {
await changePictureApi({
file: pictureState.value.file,
const fileName = `${buildUUID()}.${params.file.type.slice(6)}`;
const file = new File([params.file], fileName, {
type: params.file.type,
});
await changePictureApi({ file });
if (userStore.userInfo?.avatar) {
URL.revokeObjectURL(userStore.userInfo.avatar);
}
const picture = await getPictureApi();
userStore.$patch((state) => {
state.userInfo && (state.userInfo.avatar = URL.createObjectURL(picture));
});
message.success($t('AbpUi.SavedSuccessfully'));
emits('pictureChange');
} finally {
pictureState.value.uploading = false;
}
}
function onBeforeUpload(file: FileType) {
pictureState.value.file = file;
return false;
async function onAvatarChange(url: string) {
userStore.$patch((state) => {
state.userInfo && (state.userInfo.avatar = url);
});
emits('pictureChange');
}
function onSubmit() {
emits('submit', toValue(formModel));
@ -132,7 +122,7 @@ watchEffect(() => {
<div class="basis-2/4">
<div class="flex flex-col items-center">
<p>{{ $t('AbpUi.ProfilePicture') }}</p>
<Avatar :size="100">
<!-- <Avatar :size="100">
<template #icon>
<img :src="avatar" alt="" />
</template>
@ -147,7 +137,16 @@ watchEffect(() => {
<UploadOutlined />
{{ $t('abp.account.settings.changeAvatar') }}
</Button>
</Upload>
</Upload> -->
<div class="mb-8 block rounded-[50%]">
<CropperAvatar
:value="avatar"
:btn-text="$t('AbpAccount.AvatarChanged')"
width="150"
:upload-api="onUploadAvatar"
@change="onAvatarChange"
/>
</div>
</div>
</div>
</div>

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/components/package.json

@ -22,6 +22,14 @@
"types": "./src/codemirror/index.ts",
"default": "./src/codemirror/index.ts"
},
"./locales": {
"types": "./src/locales/index.ts",
"default": "./src/locales/index.ts"
},
"./cropper": {
"types": "./src/cropper/index.ts",
"default": "./src/cropper/index.ts"
},
"./tinymce": {
"types": "./src/tinymce/index.ts",
"default": "./src/tinymce/index.ts"
@ -48,6 +56,7 @@
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"codemirror": "catalog:",
"cropperjs": "catalog:",
"lodash.isnumber": "catalog:",
"tinymce": "catalog:",
"vditor": "catalog:",

200
apps/vben5/packages/@abp/components/src/cropper/Cropper.vue

@ -0,0 +1,200 @@
<script lang="ts" setup>
import type { CSSProperties, PropType } from 'vue';
import type { Nullable } from '@vben/types';
import {
computed,
onMounted,
onUnmounted,
ref,
unref,
useAttrs,
useTemplateRef,
} from 'vue';
import { useNamespace } from '@vben/hooks';
import { useDebounceFn } from '@vueuse/core';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
type Options = Cropper.Options;
type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>;
const props = defineProps({
src: { type: String, required: true },
alt: { type: String, default: '' },
circled: { type: Boolean, default: false },
realTimePreview: { type: Boolean, default: true },
height: { type: [String, Number], default: '360px' },
crossorigin: {
type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
default: undefined,
},
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
options: { type: Object as PropType<Options>, default: () => ({}) },
});
const emits = defineEmits(['cropend', 'ready', 'cropendError']);
const defaultOptions: Options = {
aspectRatio: 1,
zoomable: true,
zoomOnTouch: true,
zoomOnWheel: true,
cropBoxMovable: true,
cropBoxResizable: true,
toggleDragModeOnDblclick: true,
autoCrop: true,
background: true,
highlight: true,
center: true,
responsive: true,
restore: true,
checkCrossOrigin: true,
checkOrientation: true,
scalable: true,
modal: true,
guides: true,
movable: true,
rotatable: true,
};
const attrs = useAttrs();
const imgElRef = useTemplateRef<ElRef<HTMLImageElement>>('imgElRef');
const cropper = ref<Nullable<Cropper>>();
const isReady = ref(false);
const { b, is } = useNamespace('cropper-image');
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);
const getImageStyle = computed((): CSSProperties => {
return {
height: props.height,
maxWidth: '100%',
...props.imageStyle,
};
});
const getClass = computed(() => {
return [b(), attrs.class, is('circled', props.circled)];
});
const getWrapperStyle = computed((): CSSProperties => {
return { height: `${`${props.height}`.replace(/px/, '')}px` };
});
onMounted(init);
onUnmounted(() => {
cropper.value?.destroy();
});
async function init() {
const imgEl = unref(imgElRef);
if (!imgEl) {
return;
}
cropper.value = new Cropper(imgEl, {
...defaultOptions,
ready: () => {
isReady.value = true;
realTimeCroppered();
emits('ready', cropper.value);
},
crop() {
debounceRealTimeCroppered();
},
zoom() {
debounceRealTimeCroppered();
},
cropmove() {
debounceRealTimeCroppered();
},
...props.options,
});
}
// Real-time display preview
function realTimeCroppered() {
props.realTimePreview && croppered();
}
// event: return base64 and width and height information after cropping
function croppered() {
if (!cropper.value) {
return;
}
const imgInfo = cropper.value.getData();
const canvas = props.circled
? getRoundedCanvas()
: cropper.value.getCroppedCanvas();
canvas.toBlob((blob) => {
if (!blob) {
return;
}
const fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onloadend = (e) => {
emits('cropend', {
imgBase64: e.target?.result ?? '',
imgInfo,
});
};
// eslint-disable-next-line unicorn/prefer-add-event-listener
fileReader.onerror = () => {
emits('cropendError');
};
}, 'image/png');
}
// Get a circular picture canvas
function getRoundedCanvas() {
const sourceCanvas = cropper.value!.getCroppedCanvas();
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d')!;
const width = sourceCanvas.width;
const height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.arc(
width / 2,
height / 2,
Math.min(width, height) / 2,
0,
2 * Math.PI,
true,
);
context.fill();
return canvas;
}
</script>
<template>
<div :class="getClass" :style="getWrapperStyle">
<img
v-show="isReady"
ref="imgElRef"
:src="src"
:alt="alt"
:crossorigin="crossorigin"
:style="getImageStyle"
/>
</div>
</template>
<style scoped lang="scss">
$namespace: vben;
.#{$namespace}-cropper-image {
&.is-circled {
.cropper-view-box,
.cropper-face {
border-radius: 50%;
}
}
}
</style>

159
apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue

@ -0,0 +1,159 @@
<script setup lang="ts">
import type { ButtonProps } from 'ant-design-vue/es/button';
import type { CSSProperties, PropType } from 'vue';
import { computed, ref, unref, watch, watchEffect } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useNamespace } from '@vben/hooks';
import { createIconifyIcon } from '@vben/icons';
import { useI18n } from '@vben/locales';
import { Button } from 'ant-design-vue';
import CropperModal from './CropperModal.vue';
interface File {
file: Blob;
fileName?: string;
name: string;
}
const props = defineProps({
width: { type: [String, Number], default: '200px' },
value: { type: String, default: '' },
showBtn: { type: Boolean, default: true },
btnProps: { type: Object as PropType<ButtonProps>, default: undefined },
btnText: { type: String, default: '' },
uploadApi: {
type: Function as PropType<(file: File) => Promise<void>>,
default: undefined,
},
});
const emits = defineEmits(['update:value', 'change']);
const UploadIcon = createIconifyIcon('ant-design:cloud-upload-outlined');
const sourceValue = ref(props.value || '');
const { b, e } = useNamespace('cropper-avatar');
const [Modal, modalApi] = useVbenModal({
connectedComponent: CropperModal,
});
const { t } = useI18n();
const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`);
const getIconWidth = computed(
() => `${Number.parseInt(`${props.width}`.replace(/px/, '')) / 2}px`,
);
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
);
watchEffect(() => {
sourceValue.value = props.value || '';
});
watch(
() => sourceValue.value,
(v: string) => {
emits('update:value', v);
},
);
function handleUploadSuccess(url: string) {
sourceValue.value = url;
emits('change', url);
}
function openModal() {
modalApi.open();
}
function closeModal() {
modalApi.close();
}
defineExpose({ openModal, closeModal });
</script>
<template>
<div :class="b()" :style="getStyle">
<div
:class="e(`image-wrapper`)"
:style="getImageWrapperStyle"
@click="openModal"
>
<div :class="e(`image-mask`)" :style="getImageWrapperStyle">
<UploadIcon
:width="getIconWidth"
:style="getImageWrapperStyle"
color="#d6d6d6"
/>
</div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div>
<Button
:class="e(`upload-btn`)"
@click="openModal"
v-if="showBtn"
v-bind="btnProps"
>
{{ btnText ? btnText : t('cropper.selectImage') }}
</Button>
<Modal
@upload-success="handleUploadSuccess"
:upload-api="uploadApi"
:src="sourceValue"
/>
</div>
</template>
<style scoped lang="scss">
$namespace: vben;
.#{$namespace}-cropper-avatar {
display: inline-block;
text-align: center;
&__image-wrapper {
overflow: hidden;
cursor: pointer;
border: var(--border);
border-radius: 50%;
img {
width: 100%;
// height: 100%;
}
}
&__image-mask {
position: absolute;
width: inherit;
height: inherit;
cursor: pointer;
background: rgb(0 0 0 / 40%);
border: inherit;
border-radius: inherit;
opacity: 0;
transition: opacity 0.4s;
::v-deep(svg) {
margin: auto;
}
}
&__image-mask:hover {
opacity: 40;
}
&__upload-btn {
margin: 10px auto;
}
}
</style>

309
apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue

@ -0,0 +1,309 @@
<script setup lang="ts">
import type { UploadProps } from 'ant-design-vue';
import type { PropType } from 'vue';
import type { CropendResult, Cropper } from './types';
import { h, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useNamespace } from '@vben/hooks';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { isFunction } from '@vben/utils';
import { dataURLtoBlob } from '@abp/core';
import { Avatar, Button, Space, Tooltip, Upload } from 'ant-design-vue';
import CropperImage from './Cropper.vue';
type ApiFunParams = { file: Blob; fileName: string; name: string };
const props = defineProps({
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<(params: ApiFunParams) => Promise<any>>,
default: undefined,
},
src: { type: String, default: '' },
});
const emits = defineEmits<{
(event: 'uploadSuccess', url: string): void;
}>();
const UploadIcon = createIconifyIcon('ant-design:upload-outlined');
const ResetIcon = createIconifyIcon('ant-design:reload-outlined');
const RotateLeftIcon = createIconifyIcon('ant-design:rotate-left-outlined');
const RotateRightIcon = createIconifyIcon('ant-design:rotate-right-outlined');
const ScaleXIcon = createIconifyIcon('vaadin:arrows-long-h');
const ScaleYIcon = createIconifyIcon('vaadin:arrows-long-v');
const ZoomInIcon = createIconifyIcon('ant-design:zoom-in-outlined');
const ZoomOutIcon = createIconifyIcon('ant-design:zoom-out-outlined');
let fileName = '';
const src = ref(props.src || '');
const previewSource = ref('');
const fileList = ref<UploadProps['fileList']>([]);
const cropper = ref<Cropper>();
let scaleX = 1;
let scaleY = 1;
const { b, e } = useNamespace('cropper-am');
const [Modal, modalApi] = useVbenModal({
class: 'w-[800px]',
fullscreen: false,
fullscreenButton: false,
confirmText: $t('cropper.confirmText'),
onConfirm: handleOk,
title: $t('cropper.title'),
});
function handleBeforeUpload(file: File) {
const reader = new FileReader();
reader.readAsDataURL(file);
src.value = '';
previewSource.value = '';
reader.addEventListener('load', (e) => {
src.value = (e.target?.result as string) ?? '';
fileName = file.name;
});
return false;
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64;
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance;
}
function handlerToolbar(event: string, arg?: number) {
if (!cropper.value) {
return;
}
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
switch (event) {
case 'reset': {
return cropper.value.reset();
}
case 'rotate': {
return cropper.value.rotate(arg!);
}
case 'scaleX': {
return cropper.value.scaleX(scaleX);
}
case 'scaleY': {
return cropper.value.scaleY(scaleY);
}
case 'zoom': {
return cropper.value.zoom(arg!);
}
}
}
async function handleOk() {
const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value);
try {
modalApi.setState({ submitting: true });
await uploadApi({ name: 'file', file: blob, fileName });
emits('uploadSuccess', previewSource.value);
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
}
</script>
<template>
<Modal>
<div :class="b()">
<div :class="e('left')">
<div :class="e('cropper')">
<CropperImage
v-if="src"
:src="src"
height="300px"
:circled="circled"
@cropend="handleCropend"
@ready="handleReady"
/>
</div>
<div :class="e('toolbar')">
<Upload
:file-list="fileList"
accept="image/*"
:before-upload="handleBeforeUpload"
>
<Tooltip :title="$t('cropper.selectImage')" placement="bottom">
<Button size="small" :icon="h(UploadIcon)" type="primary" />
</Tooltip>
</Upload>
<Space>
<Tooltip :title="$t('cropper.btn_reset')" placement="bottom">
<Button
type="primary"
:icon="h(ResetIcon)"
size="small"
:disabled="!src"
@click="handlerToolbar('reset')"
/>
</Tooltip>
<Tooltip :title="$t('cropper.btn_rotate_left')" placement="bottom">
<Button
type="primary"
:icon="h(RotateLeftIcon)"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', -45)"
/>
</Tooltip>
<Tooltip :title="$t('cropper.btn_rotate_right')" placement="bottom">
<Button
type="primary"
:icon="h(RotateRightIcon)"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', 45)"
/>
</Tooltip>
<Tooltip :title="$t('cropper.btn_scale_x')" placement="bottom">
<Button
type="primary"
:icon="h(ScaleXIcon)"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleX')"
/>
</Tooltip>
<Tooltip :title="$t('cropper.btn_scale_y')" placement="bottom">
<Button
type="primary"
:icon="h(ScaleYIcon)"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleY')"
/>
</Tooltip>
<Tooltip :title="$t('cropper.btn_zoom_in')" placement="bottom">
<Button
type="primary"
:icon="h(ZoomInIcon)"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', 0.1)"
/>
</Tooltip>
<Tooltip :title="$t('cropper.btn_zoom_out')" placement="bottom">
<Button
type="primary"
:icon="h(ZoomOutIcon)"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', -0.1)"
/>
</Tooltip>
</Space>
</div>
</div>
<div :class="e('right')">
<div :class="e(`preview`)">
<img
:src="previewSource"
v-if="previewSource"
:alt="$t('cropper.preview')"
/>
</div>
<template v-if="previewSource">
<div :class="e(`group`)">
<Avatar :src="previewSource" size="large" />
<Avatar :src="previewSource" :size="48" />
<Avatar :src="previewSource" :size="64" />
<Avatar :src="previewSource" :size="80" />
</div>
</template>
</div>
</div>
</Modal>
</template>
<style scoped lang="scss">
$namespace: vben;
.#{$namespace}-cropper-am {
display: flex;
&__left,
&__right {
height: 340px;
}
&__left {
width: 55%;
}
&__right {
width: 45%;
}
&__cropper {
height: 300px;
background: #eee;
background-image:
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
),
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
);
background-position:
0 0,
12px 12px;
background-size: 24px 24px;
}
&__toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
}
&__preview {
width: 220px;
height: 220px;
margin: 0 auto;
overflow: hidden;
border: var(--border);
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
&__group {
display: flex;
align-items: center;
justify-content: space-around;
padding-top: 8px;
margin-top: 8px;
border-top: var(--border);
}
}
</style>

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

@ -0,0 +1,2 @@
export { default as CropperAvatar } from './CropperAvatar.vue';
export { default as CropperModal } from './CropperModal.vue';

8
apps/vben5/packages/@abp/components/src/cropper/types.ts

@ -0,0 +1,8 @@
import type Cropper from 'cropperjs';
export interface CropendResult {
imgBase64: string;
imgInfo: Cropper.Data;
}
export type { Cropper };

20
apps/vben5/packages/@abp/components/src/locales/index.ts

@ -0,0 +1,20 @@
import type { SupportedLanguagesType } from '@vben/locales';
import { loadLocalesMapFromDir } from '@vben/locales';
const modules = import.meta.glob('./langs/**/*.json');
const localesMap = loadLocalesMapFromDir(
/\.\/langs\/([^/]+)\/(.*)\.json$/,
modules,
);
/**
*
* @param lang
* @returns
*/
export async function loadComponentMessages(lang: SupportedLanguagesType) {
const locales = localesMap[lang]?.();
return locales;
}

14
apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json

@ -0,0 +1,14 @@
{
"confirmText": "Confirm and upload",
"title": "Avatar upload",
"selectImage": "Select Image",
"btn_rotate_left": "Counterclockwise rotation",
"btn_rotate_right": "Clockwise rotation",
"btn_scale_x": "Flip horizontal",
"btn_scale_y": "Flip vertical",
"btn_zoom_in": "Zoom in",
"btn_zoom_out": "Zoom out",
"btn_reset": "Reset",
"preview": "Preivew",
"uploadSuccess": "Uploaded success!"
}

14
apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json

@ -0,0 +1,14 @@
{
"confirmText": "确认并上传",
"title": "头像上传",
"selectImage": "选择图片",
"btn_rotate_left": "逆时针旋转",
"btn_rotate_right": "顺时针旋转",
"btn_scale_x": "水平翻转",
"btn_scale_y": "垂直翻转",
"btn_zoom_in": "放大",
"btn_zoom_out": "缩小",
"btn_reset": "重置",
"preview": "预览",
"uploadSuccess": "上传成功!"
}

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;

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

@ -0,0 +1,42 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/**
* @description: base64 to blob
*/
export function dataURLtoBlob(base64Buf: string): Blob {
const arr = base64Buf.split(',');
const typeItem = arr[0];
const mime = typeItem?.match(/:(.*?);/)?.[1];
const bstr = window.atob(arr[1]!);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.codePointAt(n)!;
}
return new Blob([u8arr], { type: mime });
}
/**
* img url to base64
* @param url
*/
export function urlToBase64(url: string, mineType?: string): Promise<string> {
return new Promise((resolve, reject) => {
let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null;
const ctx = canvas!.getContext('2d');
const img = new Image();
img.crossOrigin = '';
img.addEventListener('load', () => {
if (!canvas || !ctx) {
return reject(new Error('canvas or ctx is null!'));
}
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL(mineType || 'image/png');
canvas = null;
resolve(dataURL);
});
img.src = url;
});
}

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

@ -1,7 +1,10 @@
export * from './array';
export * from './date';
export * from './file';
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();

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

Loading…
Cancel
Save