Browse Source

Merge branch 'dev' into remove-vben2x-menu-depend

pull/1326/head
yx lin 5 months ago
committed by GitHub
parent
commit
f73cc9fabf
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. 31
      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. 4
      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. 15
      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. 6
      apps/vben5/packages/@abp/account/src/api/index.ts
  26. 58
      apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts
  27. 46
      apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts
  28. 73
      apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts
  29. 37
      apps/vben5/packages/@abp/account/src/api/useScanQrCodeApi.ts
  30. 83
      apps/vben5/packages/@abp/account/src/api/useTokenApi.ts
  31. 29
      apps/vben5/packages/@abp/account/src/api/useUserInfoApi.ts
  32. 43
      apps/vben5/packages/@abp/account/src/components/MySetting.vue
  33. 4
      apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue
  34. 38
      apps/vben5/packages/@abp/account/src/components/components/BindSettings.vue
  35. 9
      apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue
  36. 2
      apps/vben5/packages/@abp/account/src/hooks/index.ts
  37. 3
      apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts
  38. 28
      apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts
  39. 17
      apps/vben5/packages/@abp/account/src/types/bind.ts
  40. 32
      apps/vben5/packages/@abp/account/src/types/external-logins.ts
  41. 2
      apps/vben5/packages/@abp/account/src/types/index.ts
  42. 11
      apps/vben5/packages/@abp/account/src/types/token.ts
  43. 138
      apps/vben5/packages/@abp/account/src/utils/auth.ts
  44. 96
      apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingDrawer.vue
  45. 5
      apps/vben5/packages/@abp/auditing/src/components/loggings/LoggingTable.vue
  46. 9
      apps/vben5/packages/@abp/core/src/store/abp.ts
  47. 61
      apps/vben5/packages/@abp/core/src/utils/date.ts
  48. 1
      apps/vben5/packages/@abp/core/src/utils/index.ts
  49. 42
      apps/vben5/packages/@abp/core/src/utils/uuid.ts
  50. 3
      apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue
  51. 18
      apps/vben5/packages/@abp/identity/src/components/sessions/UserSessionTable.vue
  52. 5
      apps/vben5/packages/@abp/platform/package.json
  53. 1
      apps/vben5/packages/@abp/platform/src/api/index.ts
  54. 62
      apps/vben5/packages/@abp/platform/src/api/useMyFavoriteMenusApi.ts
  55. 2
      apps/vben5/packages/@abp/platform/src/components/index.ts
  56. 45
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchHeader.vue
  57. 124
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue
  58. 137
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue
  59. 64
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTodo.vue
  60. 65
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTrends.vue
  61. 218
      apps/vben5/packages/@abp/platform/src/components/workbench/index.vue
  62. 10
      apps/vben5/packages/@abp/platform/src/components/workbench/types.ts
  63. 1
      apps/vben5/packages/@abp/platform/src/index.ts
  64. 20
      apps/vben5/packages/@abp/platform/src/locales/index.ts
  65. 37
      apps/vben5/packages/@abp/platform/src/locales/langs/en-US/workbench.json
  66. 37
      apps/vben5/packages/@abp/platform/src/locales/langs/zh-CN/workbench.json
  67. 34
      apps/vben5/packages/@abp/platform/src/types/favorites.ts
  68. 1
      apps/vben5/packages/@abp/platform/src/types/index.ts
  69. 1
      apps/vben5/packages/@abp/settings/src/components/index.ts
  70. 6
      apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue
  71. 1
      apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoDrawer.vue
  72. 2
      apps/vben5/packages/@abp/ui/src/components/vxe-table/use-vxe-grid.ts
  73. 2
      apps/vben5/packages/@abp/ui/src/components/vxe-table/use-vxe-grid.vue
  74. 40
      apps/vben5/packages/@abp/wechat/package.json
  75. 2
      apps/vben5/packages/@abp/wechat/src/api/index.ts
  76. 40
      apps/vben5/packages/@abp/wechat/src/api/useWechatSettingsApi.ts
  77. 22
      apps/vben5/packages/@abp/wechat/src/api/userWorkWeixinJsSdkApi.ts
  78. 62
      apps/vben5/packages/@abp/wechat/src/components/bind-user/index.vue
  79. 2
      apps/vben5/packages/@abp/wechat/src/components/index.ts
  80. 37
      apps/vben5/packages/@abp/wechat/src/components/settings/index.vue
  81. 2
      apps/vben5/packages/@abp/wechat/src/index.ts
  82. 6
      apps/vben5/packages/@abp/wechat/src/types/js-sdk.ts
  83. 6
      apps/vben5/packages/@abp/wechat/tsconfig.json
  84. 2
      apps/vben5/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
  85. 6
      apps/vben5/pnpm-workspace.yaml
  86. 5
      aspnet-core/cleanup-logs.bat
  87. 3
      aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/FodyWeavers.xml
  88. 20
      aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN.Abp.AspNetCore.Auditing.csproj
  89. 19
      aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingHeaderOptions.cs
  90. 17
      aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingModule.cs
  91. 51
      aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AspNetCoreRecordHeaderAuditLogContributor.cs
  92. 19
      aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/README.md
  93. 6
      aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Sms.Tencent/LINGYUN/Abp/Sms/Tencent/TencentCloudSmsSender.cs
  94. 2
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs
  95. 2
      aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributor.cs
  96. 196
      aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/System/Linq/Expressions/ObjectQueryableExtensions.cs
  97. 4
      aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs
  98. 2
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.csproj
  99. 5470
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20250813012035_Upgrade-Abp-Framework-To-9.3.1.Designer.cs
  100. 63
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20250813012035_Upgrade-Abp-Framework-To-9.3.1.cs

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.1"

31
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.1</VoloAbpPackageVersion>
<LINGYUNAbpPackageVersion>9.3.1</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.1" />
<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-rc.1.efcore.9.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql.Json.Microsoft" Version="9.0.0-rc.1.efcore.9.0.0" />
</ItemGroup>
<!-- Elsa -->
<ItemGroup>
@ -264,7 +266,7 @@
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="23.6.1" />
<PackageVersion Include="Npgsql" Version="9.0.2" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.6" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.4" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.5" />
</ItemGroup>
<!-- Other -->
<ItemGroup>
@ -276,7 +278,7 @@
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Twitter" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.9.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.12.0" />
<PackageVersion Include="aliyun-net-sdk-core" Version="1.5.10" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageVersion Include="AgileConfig.Client" Version="1.6.9" />
@ -299,8 +301,8 @@
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="NRules" Version="0.9.2" />
<PackageVersion Include="Ocelot.Provider.Polly" Version="20.0.0" />
<PackageVersion Include="OpenIddict.Server.DataProtection" Version="6.2.1" />
<PackageVersion Include="OpenIddict.Validation.DataProtection" Version="6.2.1" />
<PackageVersion Include="OpenIddict.Server.DataProtection" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Validation.DataProtection" Version="6.4.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
@ -309,6 +311,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" />

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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>

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

@ -74,6 +74,11 @@ const formOptions: VbenFormProps = {
['YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss'],
],
],
commonConfig: {
componentProps: {
allowClear: true,
},
},
schema: [
{
component: 'Select',

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;

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

@ -5,3 +5,4 @@ export * from './mitt';
export * from './regex';
export * from './string';
export * from './tree';
export * from './uuid';

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)}`;
}

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

@ -37,6 +37,7 @@ const MenuOutlined = createIconifyIcon('heroicons-outline:menu-alt-3');
const ClaimOutlined = createIconifyIcon('la:id-card-solid');
const PermissionsOutlined = createIconifyIcon('icon-park-outline:permissions');
const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const ProtectedIcon = createIconifyIcon('mdi:protected-outline');
const RoleModal = defineAsyncComponent(() => import('./RoleModal.vue'));
const ClaimModal = defineAsyncComponent(() => import('./RoleClaimModal.vue'));
@ -290,7 +291,7 @@ function onPermissionChange(_name: string, key: string) {
>
{{ $t('AbpAuditLogging.EntitiesChanged') }}
</MenuItem>
<MenuItem key="entity-rules">
<MenuItem key="entity-rules" :icon="h(ProtectedIcon)">
{{ '数据权限' }}
</MenuItem>
</Menu>

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>

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,
};
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

20
apps/vben5/packages/@abp/platform/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 loadPaltformMessages(lang: SupportedLanguagesType) {
const locales = localesMap[lang]?.();
return locales;
}

37
apps/vben5/packages/@abp/platform/src/locales/langs/en-US/workbench.json

@ -0,0 +1,37 @@
{
"header": {
"welcome": {
"atoon": "Good afternoon, {0}, pay attention to rest oh~",
"afternoon": "Good afternoon, {0}, relax in time, can improve work efficiency~",
"evening": "Good evening, {0}. Still at work? The off work~",
"morning": "Good morning, {0}. Begin your day~"
},
"notifier": {
"title": "Notifier",
"count": "({0})"
}
},
"content": {
"favoriteMenu": {
"title": "Favorite Menus",
"home": "Home",
"dashboard": "Dashboard",
"profile": "Personal Profile",
"settings": "Personal Settings",
"notifiers": "Notifiers",
"manage": "Manage menu",
"create": "New menu",
"delete": "Delete Menu",
"select": "Select Menu",
"color": "Select Color",
"alias": "Alias Name",
"icon": "Icon"
},
"trends": {
"title": "Latest News"
},
"todo": {
"title": "Todo List"
}
}
}

37
apps/vben5/packages/@abp/platform/src/locales/langs/zh-CN/workbench.json

@ -0,0 +1,37 @@
{
"header": {
"welcome": {
"atoon": "中午好, {0}, 注意休息哦~",
"afternoon": "下午好, {0}, 适时放松,可以提高工作效率~",
"evening": "晚上好, {0}, 还在工作么?该下班了~",
"morning": "早安, {0}, 开始您一天的工作吧~"
},
"notifier": {
"title": "通知",
"count": "({0})"
}
},
"content": {
"favoriteMenu": {
"title": "常用",
"home": "首页",
"dashboard": "仪表盘",
"profile": "个人中心",
"settings": "个人设置",
"notifiers": "通知消息",
"manage": "管理菜单",
"create": "添加菜单",
"delete": "删除菜单",
"select": "选择菜单",
"color": "选择颜色",
"alias": "自定义别名",
"icon": "自定义图标"
},
"trends": {
"title": "最新消息"
},
"todo": {
"title": "待办事项"
}
}
}

34
apps/vben5/packages/@abp/platform/src/types/favorites.ts

@ -0,0 +1,34 @@
import type { AuditedEntityDto, IHasConcurrencyStamp } from '@abp/core';
interface UserFavoriteMenuDto extends AuditedEntityDto<string> {
aliasName?: string;
color?: string;
displayName: string;
framework: string;
icon?: string;
menuId: string;
name: string;
path?: string;
userId: string;
}
interface UserFavoriteMenuCreateOrUpdateDto {
aliasName?: string;
color?: string;
icon?: string;
menuId: string;
}
interface UserFavoriteMenuCreateDto extends UserFavoriteMenuCreateOrUpdateDto {
framework: string;
}
interface UserFavoriteMenuUpdateDto
extends IHasConcurrencyStamp,
UserFavoriteMenuCreateOrUpdateDto {}
export type {
UserFavoriteMenuCreateDto,
UserFavoriteMenuDto,
UserFavoriteMenuUpdateDto,
};

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

@ -1,4 +1,5 @@
export * from './dataDictionaries';
export * from './favorites';
export * from './layouts';
export * from './menus';
export * from './messages';

1
apps/vben5/packages/@abp/settings/src/components/index.ts

@ -1,3 +1,4 @@
export { default as SettingDefinitionTable } from './definitions/SettingDefinitionTable.vue';
export { default as SettingForm } from './settings/SettingForm.vue';
export { default as SystemSetting } from './settings/SystemSetting.vue';
export { default as UserSetting } from './settings/UserSetting.vue';

6
apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue

@ -71,7 +71,7 @@ async function onSubmit() {
const input = toValue(settingsUpdateInput);
await props.submitApi(input);
emits('change', input);
message.success($t('AbpSettingManagement.SuccessfullySaved'));
message.success($t('AbpSettingManagement.SavedSuccessfully'));
} finally {
submiting.value = false;
}
@ -142,8 +142,8 @@ onMounted(onGet);
v-if="detail.slot"
:change="
detail.valueType === ValueType.Boolean
? onCheckChange(detail)
: onValueChange(detail)
? onCheckChange
: onValueChange
"
:detail="detail"
:name="detail.slot"

1
apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoDrawer.vue

@ -94,6 +94,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
if (isOpen) {
try {
formModel.value = {
args: {},
beginTime: formatToDate(new Date()),
isEnabled: true,
jobType: JobType.Once,

2
apps/vben5/packages/@abp/ui/src/components/vxe-table/use-vxe-grid.ts

@ -24,8 +24,8 @@ export function useVbenVxeGrid(options: VxeGridProps) {
return () => h(VxeGrid, { ...props, ...attrs, api: extendedApi }, slots);
},
{
inheritAttrs: false,
name: 'VbenVxeGrid',
inheritAttrs: false,
},
);
// Add reactivity support

2
apps/vben5/packages/@abp/ui/src/components/vxe-table/use-vxe-grid.vue

@ -249,7 +249,7 @@ async function init() {
const enableProxyConfig = options.value.proxyConfig?.enabled;
if (enableProxyConfig && autoLoad) {
props.api.grid.commitProxy?.(
'_init',
'initial',
formOptions.value ? ((await formApi.getValues()) ?? {}) : {},
);
// props.api.reload(formApi.form?.values ?? {});

40
apps/vben5/packages/@abp/wechat/package.json

@ -0,0 +1,40 @@
{
"name": "@abp/wechat",
"version": "9.2.0",
"homepage": "https://github.com/colinin/abp-next-admin",
"bugs": "https://github.com/colinin/abp-next-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/colinin/abp-next-admin.git",
"directory": "packages/@abp/wechat"
},
"license": "MIT",
"type": "module",
"sideEffects": [
"**/*.css"
],
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"dependencies": {
"@abp/core": "workspace:*",
"@abp/features": "workspace:*",
"@abp/request": "workspace:*",
"@abp/settings": "workspace:*",
"@abp/ui": "workspace:*",
"@ant-design/icons-vue": "catalog:",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@wecom/jssdk": "catalog:",
"ant-design-vue": "catalog:",
"dayjs": "catalog:",
"vue": "catalog:*"
}
}

2
apps/vben5/packages/@abp/wechat/src/api/index.ts

@ -0,0 +1,2 @@
export { userWorkWeixinJsSdkApi } from './userWorkWeixinJsSdkApi';
export { useWechatSettingsApi } from './useWechatSettingsApi';

40
apps/vben5/packages/@abp/wechat/src/api/useWechatSettingsApi.ts

@ -0,0 +1,40 @@
import type { ListResultDto } from '@abp/core';
import type { SettingGroup } from '@abp/settings';
import { useRequest } from '@abp/request';
export function useWechatSettingsApi() {
const { cancel, request } = useRequest();
/**
*
* @returns
*/
function getGlobalSettingsApi(): Promise<ListResultDto<SettingGroup>> {
return request<ListResultDto<SettingGroup>>(
`/api/wechat/setting-management/by-global`,
{
method: 'GET',
},
);
}
/**
*
* @returns
*/
function getTenantSettingsApi(): Promise<ListResultDto<SettingGroup>> {
return request<ListResultDto<SettingGroup>>(
`/api/wechat/setting-management/by-current-tenant`,
{
method: 'GET',
},
);
}
return {
cancel,
getGlobalSettingsApi,
getTenantSettingsApi,
};
}

22
apps/vben5/packages/@abp/wechat/src/api/userWorkWeixinJsSdkApi.ts

@ -0,0 +1,22 @@
import type { AgentConfigDto } from '../types/js-sdk';
import { useRequest } from '@abp/request';
export function userWorkWeixinJsSdkApi() {
const { cancel, request } = useRequest();
/**
*
* @returns Dto
*/
function getAgentConfigApi(): Promise<AgentConfigDto> {
return request<AgentConfigDto>(`/api/wechat/work/jssdk/agent-config`, {
method: 'GET',
});
}
return {
cancel,
getAgentConfigApi,
};
}

62
apps/vben5/packages/@abp/wechat/src/components/bind-user/index.vue

@ -0,0 +1,62 @@
<script setup lang="ts">
import { useTemplateRef } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { buildUUID } from '@abp/core';
import { WWLoginRedirectType, WWLoginType } from '@wecom/jssdk';
import * as ww from '@wecom/jssdk';
import { userWorkWeixinJsSdkApi } from '../../api/userWorkWeixinJsSdkApi';
const emits = defineEmits<{
/**
* 用户扫码登录成功回调事件
* @params code 企业微信授权码
*/
(event: 'onLogin', code: string): void;
}>();
const wxLoginRef = useTemplateRef<Element>('wxLogin');
const { getAgentConfigApi } = userWorkWeixinJsSdkApi();
const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen) {
if (isOpen) {
setTimeout(onInitLogin, 200);
}
},
});
async function onInitLogin() {
try {
modalApi.setState({ loading: true });
const agentConfig = await getAgentConfigApi();
ww.createWWLoginPanel({
el: wxLoginRef.value!,
params: {
login_type: WWLoginType.corpApp,
appid: agentConfig.corpId,
agentid: agentConfig.agentId,
// TODO: ? , .
redirect_uri: window.location.href,
state: buildUUID(),
redirect_type: WWLoginRedirectType.callback,
},
onLoginSuccess(res) {
emits('onLogin', res.code);
},
});
} finally {
modalApi.setState({ loading: false });
}
}
</script>
<template>
<Modal :title="$t('AbpAccountOAuth.OAuth:WorkWeixin')">
<div ref="wxLogin"></div>
</Modal>
</template>
<style scoped></style>

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

@ -0,0 +1,2 @@
export { default as WechatWorkUserBinder } from './bind-user/index.vue';
export { default as WechatSettings } from './settings/index.vue';

37
apps/vben5/packages/@abp/wechat/src/components/settings/index.vue

@ -0,0 +1,37 @@
<script setup lang="ts">
import type { SettingsUpdateInput } from '@abp/settings';
import { useAbpStore } from '@abp/core';
import { SettingForm, useSettingsApi } from '@abp/settings';
import { useWechatSettingsApi } from '../../api/useWechatSettingsApi';
defineOptions({
name: 'MaterialInspectSettings',
});
const abpStore = useAbpStore();
const { getGlobalSettingsApi, getTenantSettingsApi } = useWechatSettingsApi();
const { setGlobalSettingsApi, setTenantSettingsApi } = useSettingsApi();
async function onGet() {
const getSettingsApi = abpStore.application?.currentTenant.isAvailable
? getTenantSettingsApi
: getGlobalSettingsApi;
const { items } = await getSettingsApi();
return items;
}
async function onSubmit(input: SettingsUpdateInput) {
const setSettingsApi = abpStore.application?.currentTenant.isAvailable
? setTenantSettingsApi
: setGlobalSettingsApi;
await setSettingsApi(input);
}
</script>
<template>
<SettingForm :get-api="onGet" :submit-api="onSubmit" />
</template>
<style scoped></style>

2
apps/vben5/packages/@abp/wechat/src/index.ts

@ -0,0 +1,2 @@
export * from './api';
export * from './components';

6
apps/vben5/packages/@abp/wechat/src/types/js-sdk.ts

@ -0,0 +1,6 @@
interface AgentConfigDto {
agentId: string;
corpId: string;
}
export type { AgentConfigDto };

6
apps/vben5/packages/@abp/wechat/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}

2
apps/vben5/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue

@ -300,7 +300,7 @@ async function init() {
const enableProxyConfig = options.value.proxyConfig?.enabled;
if (enableProxyConfig && autoLoad) {
props.api.grid.commitProxy?.(
'_init',
'initial',
formOptions.value ? ((await formApi.getValues()) ?? {}) : {},
);
// props.api.reload(formApi.form?.values ?? {});

6
apps/vben5/pnpm-workspace.yaml

@ -73,6 +73,7 @@ catalog:
'@vueuse/core': ^13.1.0
'@vueuse/integrations': ^13.1.0
'@vueuse/motion': ^3.0.3
'@wecom/jssdk': ^2.3.1
ant-design-vue: ^4.2.6
archiver: ^7.0.1
autoprefixer: ^10.4.21
@ -205,8 +206,9 @@ catalog:
vue-simple-uploader: ^1.0.3
vue-tippy: ^6.7.0
vue-tsc: 2.2.10
vxe-pc-ui: ^4.5.35
vxe-table: ^4.13.16
vue3-colorpicker: ^2.3.0
vxe-pc-ui: ^4.7.12
vxe-table: ^4.14.4
watermark-js-plus: ^1.6.0
zod: ^3.24.3
zod-defaults: ^0.1.3

5
aspnet-core/cleanup-logs.bat

@ -4,12 +4,17 @@ chcp 65001
echo. 清理所有服务日志
del .\services\LY.MicroService.Applications.Single\Logs /Q
del .\services\LY.MicroService.BackendAdmin.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.AuthServer\Logs /Q
del .\services\LY.MicroService.AuthServer.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.identityServer\Logs /Q
del .\services\LY.MicroService.identityServer.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.LocalizationManagement.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.PlatformManagement.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.RealtimeMessage.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.TaskManagement.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.WebhooksManagement.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.WechatManagement.HttpApi.Host\Logs /Q
del .\services\LY.MicroService.WorkflowManagement.HttpApi.Host\Logs /Q

3
aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

20
aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN.Abp.AspNetCore.Auditing.csproj

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>LINGYUN.Abp.AspNetCore.Auditing</AssemblyName>
<PackageId>LINGYUN.Abp.AspNetCore.Auditing</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore" />
</ItemGroup>
</Project>

19
aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingHeaderOptions.cs

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace LINGYUN.Abp.AspNetCore.Auditing;
public class AbpAspNetCoreAuditingHeaderOptions
{
/// <summary>
/// 是否在审计日志中记录Http请求头,默认: true
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// 要记录的Http请求头
/// </summary>
public IList<string> HttpHeaders { get; }
public AbpAspNetCoreAuditingHeaderOptions()
{
IsEnabled = true;
HttpHeaders = new List<string>();
}
}

17
aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingModule.cs

@ -0,0 +1,17 @@
using Volo.Abp.AspNetCore;
using Volo.Abp.Auditing;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.AspNetCore.Auditing;
[DependsOn(typeof(AbpAspNetCoreModule))]
public class AbpAspNetCoreAuditingModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAuditingOptions>(options =>
{
options.Contributors.Add(new AspNetCoreRecordHeaderAuditLogContributor());
});
}
}

51
aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AspNetCoreRecordHeaderAuditLogContributor.cs

@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Collections.Immutable;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.AspNetCore.Auditing;
public class AspNetCoreRecordHeaderAuditLogContributor : AuditLogContributor, ITransientDependency
{
private const string HttpHeaderRecordKey = "HttpHeaders";
public AspNetCoreRecordHeaderAuditLogContributor()
{
}
public override void PreContribute(AuditLogContributionContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreAuditingHeaderOptions>>();
if (!options.Value.IsEnabled)
{
return;
}
var httpContext = context.ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (httpContext == null)
{
return;
}
if (context.AuditInfo.HasProperty(HttpHeaderRecordKey))
{
return;
}
var headerRcords = new Dictionary<string, string>();
var httpHeaders = httpContext.Request.Headers.ToImmutableDictionary();
foreach (var headerKey in options.Value.HttpHeaders)
{
if (httpHeaders.TryGetValue(headerKey, out var headers))
{
headerRcords[headerKey] = headers.JoinAsString(";");
}
}
context.AuditInfo.SetProperty(HttpHeaderRecordKey, headerRcords);
}
}

19
aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/README.md

@ -0,0 +1,19 @@
# LINGYUN.Abp.AspNetCore.Auditing
审计日期扩展模块, 用于在审计日志中加入特定的Http请求头记录
## 模块引用
```csharp
[DependsOn(typeof(AbpAspNetCoreAuditingModule))]
public class YouProjectModule : AbpModule
{
// other
}
```
## 配置项
* AbpAspNetCoreAuditingHeaderOptions.IsEnabled 是否在审计日志中记录Http请求头,默认: true
* AbpAspNetCoreAuditingHeaderOptions.HttpHeaders 需要在审计日志中记录的Http请求头列表

6
aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Sms.Tencent/LINGYUN/Abp/Sms/Tencent/TencentCloudSmsSender.cs

@ -48,6 +48,9 @@ public class TencentCloudSmsSender : ISmsSender, ITransientDependency
Check.NotNullOrWhiteSpace(appId, TencentCloudSettingNames.Sms.AppId);
// 短信模板相关参数
List<string> templateParams = ["TemplateCode", "SignName"];
// 统一使用 TemplateCode作为模板参数, 解决不一样的sms提供商参数差异
if (!smsMessage.Properties.TryGetValue("TemplateCode", out var templateId))
{
@ -69,7 +72,8 @@ public class TencentCloudSmsSender : ISmsSender, ITransientDependency
if (smsMessage.Properties.Any())
{
request.TemplateParamSet = smsMessage.Properties.Select(x => x.Value.ToString()).ToArray();
// 去掉短信模板相关参数,只保留要用的变量
request.TemplateParamSet = smsMessage.Properties.Where(x => !templateParams.Contains(x.Key)).Select(x => x.Value.ToString()).ToArray();
}
var smsClient = await TencentCloudClientFactory.CreateAsync();

2
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs

@ -29,7 +29,7 @@ public class AbpIP2RegionModule : AbpModule
Configure<AbpIPLocationResolveOptions>(options =>
{
options.IPLocationResolvers.Add(new IP2RegionIPLocationResolveContributorBase());
options.IPLocationResolvers.Add(new IP2RegionIPLocationResolveContributor());
});
}
}

2
aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributorBase.cs → aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributor.cs

@ -5,7 +5,7 @@ using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.IP2Region;
public class IP2RegionIPLocationResolveContributorBase : IPLocationResolveContributorBase
public class IP2RegionIPLocationResolveContributor : IPLocationResolveContributorBase
{
public const string ContributorName = "IP2Region";
public override string Name => ContributorName;

196
aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/System/Linq/Expressions/ObjectQueryableExtensions.cs

@ -41,38 +41,30 @@ public static class ObjectQueryableExtensions
// For example(MySql):
// ...Other (Field <> Value)
exp = Expression.NotEqual(
leftParamter,
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
leftParamter,
GetValue(paramter, propertyType));
break;
case DynamicComparison.LessThan:
// For example(MySql):
// ...Other (Field < Value)
exp = Expression.LessThan(
leftParamter,
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
exp = BuildLessThanExpression(paramter, leftParamter, propertyType);
break;
case DynamicComparison.LessThanOrEqual:
// For example(MySql):
// ...Other (Field <= Value)
exp = Expression.LessThanOrEqual(
leftParamter,
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
exp = BuildLessThanOrEqualExpression(paramter, leftParamter, propertyType);
break;
case DynamicComparison.GreaterThan:
// For example(MySql):
// ...Other (Field > Value)
exp = Expression.GreaterThan(
leftParamter,
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
exp = BuildGreaterThanExpression(paramter, leftParamter, propertyType);
break;
case DynamicComparison.GreaterThanOrEqual:
// For example(MySql):
// ...Other (Field >= Value)
exp = Expression.GreaterThanOrEqual(
leftParamter,
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
exp = BuildGreaterThanOrEqualExpression(paramter, leftParamter, propertyType);
break;
case DynamicComparison.StartsWith:
// For example(MySql):
@ -80,7 +72,7 @@ public static class ObjectQueryableExtensions
exp = Expression.Call(
leftParamter,
typeof(string).GetMethod(nameof(String.StartsWith), new[] { typeof(string) }),
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
GetValue(paramter, propertyType));
// TODO: 单元测试通过
// For example(MySql):
@ -106,7 +98,7 @@ public static class ObjectQueryableExtensions
Expression.Call(
leftParamter,
typeof(string).GetMethod(nameof(String.StartsWith), new[] { typeof(string) }),
Expression.Convert(Expression.Constant(paramter.Value), propertyType)));
GetValue(paramter, propertyType)));
// TODO: 单元测试通过
// For example(MySql):
@ -129,7 +121,7 @@ public static class ObjectQueryableExtensions
exp = Expression.Call(
leftParamter,
typeof(string).GetMethod(nameof(String.EndsWith), new[] { typeof(string) }),
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
GetValue(paramter, propertyType));
// TODO: 单元测试通过
// For example(MySql):
@ -153,7 +145,7 @@ public static class ObjectQueryableExtensions
Expression.Call(
leftParamter,
typeof(string).GetMethod(nameof(String.EndsWith), new[] { typeof(string) }),
Expression.Convert(Expression.Constant(paramter.Value), propertyType)));
GetValue(paramter, propertyType)));
// TODO: 单元测试通过
// For example(MySql):
@ -176,7 +168,7 @@ public static class ObjectQueryableExtensions
exp = Expression.Call(
leftParamter,
typeof(string).GetMethod(nameof(String.Contains), new[] { typeof(string) }),
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
GetValue(paramter, propertyType));
// TODO: 单元测试通过
// For example(MySql):
@ -200,7 +192,7 @@ public static class ObjectQueryableExtensions
Expression.Call(
leftParamter,
typeof(string).GetMethod(nameof(String.Contains), new[] { typeof(string) }),
Expression.Convert(Expression.Constant(paramter.Value), propertyType)));
GetValue(paramter, propertyType)));
// TODO: 单元测试通过
// For example(MySql):
// ...Other ((Field IS NULL) OR (Field NOT LIKE '%Value%'))
@ -223,16 +215,14 @@ public static class ObjectQueryableExtensions
// 非空字段设定为比对默认值
exp = Expression.Equal(leftParamter,
Expression.Convert(
Expression.Constant(GetDefaultValue(propertyType)), propertyType));
Expression.Constant(GetDefaultValue(propertyType)));
break;
case DynamicComparison.NotNull:
// For example(MySql):
// ...Other (Field IS NOT NULL)
exp = Expression.NotEqual(leftParamter,
Expression.Convert(
Expression.Constant(GetDefaultValue(propertyType)), propertyType));
Expression.Constant(GetDefaultValue(propertyType)));
break;
default:
case DynamicComparison.Equal:
@ -240,8 +230,8 @@ public static class ObjectQueryableExtensions
// ...Other (Field = Value)
exp = Expression.Equal(
leftParamter,
Expression.Convert(Expression.Constant(paramter.Value), propertyType));
leftParamter,
GetValue(paramter, propertyType));
break;
}
expressions.Push(exp);
@ -267,6 +257,160 @@ public static class ObjectQueryableExtensions
return Expression.Lambda<T>(expressions.Pop(), condition.Parameters.ToArray());
}
private static Expression BuildLessThanExpression(DynamicParamter paramter, MemberExpression member, Type propertyType)
{
if (propertyType == typeof(string))
{
// 字符串比较: Field < Value
return Expression.LessThan(
Expression.Call(
member,
typeof(string).GetMethod("CompareTo", new[] { typeof(string) }),
Expression.Constant(Convert.ToString(paramter.Value))),
Expression.Constant(0));
}
if (propertyType.IsNullableType())
{
// 可空类型比较: Field < Value
var underlyingType = Nullable.GetUnderlyingType(propertyType);
var hasValue = Expression.Property(member, "HasValue");
var value = Expression.Property(member, "Value");
return Expression.AndAlso(
hasValue,
Expression.LessThan(
value,
GetValue(paramter, underlyingType)));
}
else
{
// 数值比较: Field < Value
return Expression.LessThan(
member,
GetValue(paramter, propertyType));
}
}
private static Expression BuildLessThanOrEqualExpression(DynamicParamter paramter, MemberExpression member, Type propertyType)
{
if (propertyType == typeof(string))
{
// 字符串比较: Field <= Value
return Expression.LessThanOrEqual(
Expression.Call(
member,
typeof(string).GetMethod("CompareTo", new[] { typeof(string) }),
Expression.Constant(Convert.ToString(paramter.Value))),
Expression.Constant(0));
}
if (propertyType.IsNullableType())
{
// 可空类型比较: Field <= Value
var underlyingType = Nullable.GetUnderlyingType(propertyType);
var hasValue = Expression.Property(member, "HasValue");
var value = Expression.Property(member, "Value");
return Expression.AndAlso(
hasValue,
Expression.LessThanOrEqual(
value,
GetValue(paramter, underlyingType)));
}
else
{
// 数值比较: Field <= Value
return Expression.LessThanOrEqual(
member,
GetValue(paramter, propertyType));
}
}
private static Expression BuildGreaterThanExpression(DynamicParamter paramter, MemberExpression member, Type propertyType)
{
if (propertyType == typeof(string))
{
// 字符串比较: Field > Value
return Expression.GreaterThan(
Expression.Call(
member,
typeof(string).GetMethod("CompareTo", new[] { typeof(string) }),
Expression.Constant(Convert.ToString(paramter.Value))),
Expression.Constant(0));
}
if (propertyType.IsNullableType())
{
// 可空类型比较: Field > Value
var underlyingType = Nullable.GetUnderlyingType(propertyType);
var hasValue = Expression.Property(member, "HasValue");
var value = Expression.Property(member, "Value");
return Expression.AndAlso(
hasValue,
Expression.GreaterThan(
value,
GetValue(paramter, underlyingType)));
}
else
{
// 数值比较: Field > Value
return Expression.GreaterThan(
member,
GetValue(paramter, propertyType));
}
}
private static Expression BuildGreaterThanOrEqualExpression(DynamicParamter paramter, MemberExpression member, Type propertyType)
{
if (propertyType == typeof(string))
{
// 字符串比较: Field >= Value
return Expression.GreaterThanOrEqual(
Expression.Call(
member,
typeof(string).GetMethod("CompareTo", new[] { typeof(string) }),
Expression.Constant(Convert.ToString(paramter.Value))),
Expression.Constant(0));
}
if (propertyType.IsNullableType())
{
// 可空类型比较: Field >= Value
var underlyingType = Nullable.GetUnderlyingType(propertyType);
var hasValue = Expression.Property(member, "HasValue");
var value = Expression.Property(member, "Value");
return Expression.AndAlso(
hasValue,
Expression.GreaterThanOrEqual(
value,
GetValue(paramter, underlyingType)));
}
else
{
// 数值比较: Field >= Value
return Expression.GreaterThanOrEqual(
member,
GetValue(paramter, propertyType));
}
}
private static ConstantExpression GetValue(DynamicParamter paramter, Type propertyType)
{
object typedValue;
if (propertyType.IsNullableType())
{
propertyType = Nullable.GetUnderlyingType(propertyType);
}
typedValue = Convert.ChangeType(paramter.Value, propertyType);
return Expression.Constant(typedValue, propertyType);
}
private static object GetDefaultValue(Type type)
{
// TODO: 非空字段此处返回默认值

4
aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs

@ -17,7 +17,7 @@ public class AppDescriptor
/// <summary>
/// 应用token
/// </summary>
public string AppToken { get; set; }
public string? AppToken { get; set; }
/// <summary>
/// 签名有效时间
/// 单位: s
@ -29,7 +29,7 @@ public class AppDescriptor
string appName,
string appKey,
string appSecret,
string appToken = null,
string? appToken = null,
int? signLifeTime = null)
{
AppName = appName;

2
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.csproj

@ -12,7 +12,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Volo.Abp.EntityFrameworkCore.MySql" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL.Pomelo" />
<PackageReference Include="DotNetCore.CAP.MySql" />
</ItemGroup>

5470
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20250813012035_Upgrade-Abp-Framework-To-9.3.1.Designer.cs

File diff suppressed because it is too large

63
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20250813012035_Upgrade-Abp-Framework-To-9.3.1.cs

@ -0,0 +1,63 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migrations
{
/// <inheritdoc />
public partial class UpgradeAbpFrameworkTo931 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<Guid>(
name: "EntityId",
table: "Demo_BooksAuths",
type: "char(64)",
maxLength: 64,
nullable: false,
collation: "ascii_general_ci",
oldClrType: typeof(string),
oldType: "char(64)",
oldMaxLength: 64)
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AbpAuditLogExcelFiles",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
FileName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreationTime = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CreatorId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_AbpAuditLogExcelFiles", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpAuditLogExcelFiles");
migrationBuilder.AlterColumn<string>(
name: "EntityId",
table: "Demo_BooksAuths",
type: "char(64)",
maxLength: 64,
nullable: false,
oldClrType: typeof(Guid),
oldType: "char(64)",
oldMaxLength: 64)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
}
}
}

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

Loading…
Cancel
Save