diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac4fe231d..60742282a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: - "**.csproj" env: - DOTNET_VERSION: "9.0.301" + DOTNET_VERSION: "9.0.304" jobs: build: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a5cf75644..9020906b2 100644 --- a/.github/workflows/publish.yml +++ b/.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: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b998f21e5..b68f94f9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,4 +14,4 @@ jobs: with: repo_token: "${{ secrets.GITHUB_TOKEN }}" prerelease: false - automatic_release_tag: "9.2.1" + automatic_release_tag: "9.3.5" diff --git a/Directory.Packages.props b/Directory.Packages.props index 82b823da1..86a62af08 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,16 +3,16 @@ 8.3.5 2.15.2 3.3.5 - 9.2.1 - 9.2.1 - 9.0.4 - 9.0.4 - 9.0.4 + 9.3.5 + 9.3.5 + 9.0.5 + 9.0.5 + 9.0.5 true - + @@ -63,7 +63,8 @@ - + + @@ -157,7 +158,7 @@ - + @@ -174,8 +175,9 @@ - - + + + @@ -264,7 +266,7 @@ - + @@ -276,10 +278,11 @@ - + + @@ -299,8 +302,8 @@ - - + + @@ -309,6 +312,7 @@ + @@ -330,4 +334,4 @@ - \ No newline at end of file + diff --git a/apps/vben5/.husky/commit-msg b/apps/vben5/.husky/commit-msg deleted file mode 100644 index 270ebb8c1..000000000 --- a/apps/vben5/.husky/commit-msg +++ /dev/null @@ -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. diff --git a/apps/vben5/.husky/post-merge b/apps/vben5/.husky/post-merge deleted file mode 100644 index 83fa775d5..000000000 --- a/apps/vben5/.husky/post-merge +++ /dev/null @@ -1,3 +0,0 @@ -# 每次 git pull 之后, 安装依赖 - -pnpm install diff --git a/apps/vben5/.husky/pre-commit b/apps/vben5/.husky/pre-commit deleted file mode 100644 index 5dccee288..000000000 --- a/apps/vben5/.husky/pre-commit +++ /dev/null @@ -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. diff --git a/apps/vben5/.lintstagedrc.mjs b/apps/vben5/.lintstagedrc.mjs deleted file mode 100644 index 94b0192a7..000000000 --- a/apps/vben5/.lintstagedrc.mjs +++ /dev/null @@ -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', - ], -}; diff --git a/apps/vben5/.vscode/extensions.json b/apps/vben5/.vscode/extensions.json new file mode 100644 index 000000000..e8dc9ed9b --- /dev/null +++ b/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" + ] +} diff --git a/apps/vben5/.vscode/global.code-snippets b/apps/vben5/.vscode/global.code-snippets new file mode 100644 index 000000000..7604b0148 --- /dev/null +++ b/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": "", + "const props = defineProps<{", + " modelValue?: boolean,", + "}>()", + "$1", + "", + "", + "", + " ", + " ", + " ", + "", + ], + }, + "vue-computed": { + "scope": "javascript,typescript,vue", + "prefix": "com", + "body": ["computed(() => { $1 })"], + }, +} diff --git a/apps/vben5/.vscode/launch.json b/apps/vben5/.vscode/launch.json new file mode 100644 index 000000000..d868ef52b --- /dev/null +++ b/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" + } + ] +} diff --git a/apps/vben5/.vscode/settings.json b/apps/vben5/.vscode/settings.json new file mode 100644 index 000000000..f38c42781 --- /dev/null +++ b/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" + ] +} diff --git a/apps/vben5/apps/app-antd/package.json b/apps/vben5/apps/app-antd/package.json index 03fa292ee..6b1852596 100644 --- a/apps/vben5/apps/app-antd/package.json +++ b/apps/vben5/apps/app-antd/package.json @@ -28,6 +28,7 @@ "dependencies": { "@abp/account": "workspace:*", "@abp/auditing": "workspace:*", + "@abp/components": "workspace:*", "@abp/core": "workspace:*", "@abp/data-protection": "workspace:*", "@abp/demo": "workspace:*", @@ -46,6 +47,7 @@ "@abp/text-templating": "workspace:*", "@abp/ui": "workspace:*", "@abp/webhooks": "workspace:*", + "@abp/wechat": "workspace:*", "@vben-core/shadcn-ui": "workspace:*", "@vben/access": "workspace:*", "@vben/common-ui": "workspace:*", @@ -66,6 +68,7 @@ "dayjs": "catalog:", "pinia": "catalog:", "vue": "catalog:", - "vue-router": "catalog:" + "vue-router": "catalog:", + "vue3-colorpicker": "catalog:" } } diff --git a/apps/vben5/apps/app-antd/src/adapter/component/index.ts b/apps/vben5/apps/app-antd/src/adapter/component/index.ts index 4ab140011..e98ba7f61 100644 --- a/apps/vben5/apps/app-antd/src/adapter/component/index.ts +++ b/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 }) => { diff --git a/apps/vben5/apps/app-antd/src/adapter/request/index.ts b/apps/vben5/apps/app-antd/src/adapter/request/index.ts index e0c13d6a5..1fe3242a1 100644 --- a/apps/vben5/apps/app-antd/src/adapter/request/index.ts +++ b/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; }, }); diff --git a/apps/vben5/apps/app-antd/src/layouts/basic.vue b/apps/vben5/apps/app-antd/src/layouts/basic.vue index 99d4b38f8..a9059508d 100644 --- a/apps/vben5/apps/app-antd/src/layouts/basic.vue +++ b/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([ - { - 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([]); 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(() => { diff --git a/apps/vben5/apps/app-antd/src/locales/index.ts b/apps/vben5/apps/app-antd/src/locales/index.ts index f221600e7..434984cfd 100644 --- a/apps/vben5/apps/app-antd/src/locales/index.ts +++ b/apps/vben5/apps/app-antd/src/locales/index.ts @@ -13,8 +13,10 @@ import { } from '@vben/locales'; import { preferences } from '@vben/preferences'; +import { loadComponentMessages } from '@abp/components/locales'; import { useAbpStore } from '@abp/core'; import { useLocalizationsApi } from '@abp/localization'; +import { loadPaltformMessages } from '@abp/platform'; import antdEnLocale from 'ant-design-vue/es/locale/en_US'; import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN'; import dayjs from 'dayjs'; @@ -34,13 +36,18 @@ const localesMap = loadLocalesMapFromDir( * @param lang */ async function loadMessages(lang: SupportedLanguagesType) { - const [appLocaleMessages, _, abpLocales] = await Promise.all([ - localesMap[lang]?.(), - loadThirdPartyMessage(lang), - loadAbpLocale(lang), - ]); + const [appLocaleMessages, compLocales, platformLocales, _, abpLocales] = + await Promise.all([ + localesMap[lang]?.(), + loadComponentMessages(lang), + loadPaltformMessages(lang), + loadThirdPartyMessage(lang), + loadAbpLocale(lang), + ]); return { ...appLocaleMessages?.default, + ...compLocales?.default, + ...platformLocales?.default, ...abpLocales, }; } diff --git a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json index 277f4585d..49dced4c6 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json +++ b/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" } } diff --git a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json index 0665783bc..16bb7ec01 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json +++ b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json @@ -142,5 +142,9 @@ "title": "对象存储", "containers": "容器管理", "objects": "文件管理" + }, + "wechat": { + "title": "微信集成", + "settings": "微信设置" } } diff --git a/apps/vben5/apps/app-antd/src/preferences.ts b/apps/vben5/apps/app-antd/src/preferences.ts index 4ee0c5515..94d2a3ca8 100644 --- a/apps/vben5/apps/app-antd/src/preferences.ts +++ b/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, + }, }); diff --git a/apps/vben5/apps/app-antd/src/store/auth.ts b/apps/vben5/apps/app-antd/src/store/auth.ts index 7492529a5..3b3452809 100644 --- a/apps/vben5/apps/app-antd/src/store/auth.ts +++ b/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 = { diff --git a/apps/vben5/apps/app-antd/src/views/_core/fallback/not-found.vue b/apps/vben5/apps/app-antd/src/views/_core/fallback/not-found.vue index 4d178e9cb..9fa96d7f1 100644 --- a/apps/vben5/apps/app-antd/src/views/_core/fallback/not-found.vue +++ b/apps/vben5/apps/app-antd/src/views/_core/fallback/not-found.vue @@ -1,9 +1,51 @@ - + + + + + + {{ $t('common.backToHome') }} + + + + {{ $t('common.logout') }} + + + + diff --git a/apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue b/apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue index 29ccab460..5dda522cc 100644 --- a/apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue +++ b/apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue @@ -1,15 +1,98 @@ - + + diff --git a/apps/vben5/apps/app-antd/src/views/dashboard/workspace/index.vue b/apps/vben5/apps/app-antd/src/views/dashboard/workspace/index.vue index b95d61381..9055b39d8 100644 --- a/apps/vben5/apps/app-antd/src/views/dashboard/workspace/index.vue +++ b/apps/vben5/apps/app-antd/src/views/dashboard/workspace/index.vue @@ -1,266 +1,32 @@ - - - - - 早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧! - - 今日晴,20℃ - 32℃! - - - - - - - - - - - - - - - - + + + diff --git a/apps/vben5/packages/@abp/account/package.json b/apps/vben5/packages/@abp/account/package.json index 0b92c7213..c742f9666 100644 --- a/apps/vben5/packages/@abp/account/package.json +++ b/apps/vben5/packages/@abp/account/package.json @@ -20,6 +20,7 @@ } }, "dependencies": { + "@abp/components": "workspace:*", "@abp/core": "workspace:*", "@abp/gdpr": "workspace:*", "@abp/identity": "workspace:*", diff --git a/apps/vben5/packages/@abp/account/src/api/index.ts b/apps/vben5/packages/@abp/account/src/api/index.ts index 54e3e8ead..3810e2eb2 100644 --- a/apps/vben5/packages/@abp/account/src/api/index.ts +++ b/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'; diff --git a/apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts b/apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts new file mode 100644 index 000000000..ff2b0122f --- /dev/null +++ b/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 } + */ + async function bindWorkWeixinApi( + input: WorkWeixinLoginBindInput, + ): Promise { + return await request(`/api/account/oauth/work-weixin/bind`, { + method: 'POST', + data: input, + }); + } + + /** + * 获取外部登录提供者列表 + * @returns 外部登录提供者列表 + */ + async function getExternalLoginsApi(): Promise { + return await request( + `/api/account/external-logins`, + { + method: 'GET', + }, + ); + } + + /** + * 移除外部登录提供者 + * @returns { Promise } + */ + async function removeExternalLoginApi( + input: RemoveExternalLoginInput, + ): Promise { + return await request(`/api/account/external-logins/remove`, { + method: 'DELETE', + params: input, + }); + } + + return { + cancel, + bindWorkWeixinApi, + getExternalLoginsApi, + removeExternalLoginApi, + }; +} diff --git a/apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts b/apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts deleted file mode 100644 index 0d6be68b3..000000000 --- a/apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts +++ /dev/null @@ -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('/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, - }; -} diff --git a/apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts b/apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts deleted file mode 100644 index 3acf36957..000000000 --- a/apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts +++ /dev/null @@ -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 { - return request('/api/account/qrcode/generate', { - method: 'POST', - }); - } - - /** - * 检查二维码状态 - * @param key 二维码Key - * @returns 二维码信息 - */ - function checkCodeApi(key: string): Promise { - return request(`/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('/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, - }; -} diff --git a/apps/vben5/packages/@abp/account/src/api/useScanQrCodeApi.ts b/apps/vben5/packages/@abp/account/src/api/useScanQrCodeApi.ts new file mode 100644 index 000000000..c28fff84b --- /dev/null +++ b/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 { + return request('/api/account/qrcode/generate', { + method: 'POST', + }); + } + + /** + * 检查二维码状态 + * @param key 二维码Key + * @returns 二维码信息 + */ + function checkCodeApi(key: string): Promise { + return request(`/api/account/qrcode/${key}/check`, { + method: 'GET', + }); + } + + return { + cancel, + checkCodeApi, + generateApi, + }; +} diff --git a/apps/vben5/packages/@abp/account/src/api/useTokenApi.ts b/apps/vben5/packages/@abp/account/src/api/useTokenApi.ts deleted file mode 100644 index e41e84c10..000000000 --- a/apps/vben5/packages/@abp/account/src/api/useTokenApi.ts +++ /dev/null @@ -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 { - const { audience, clientId, clientSecret } = useAppConfig( - import.meta.env, - import.meta.env.PROD, - ); - const result = await request('/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('/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, - }; -} diff --git a/apps/vben5/packages/@abp/account/src/api/useUserInfoApi.ts b/apps/vben5/packages/@abp/account/src/api/useUserInfoApi.ts deleted file mode 100644 index aa7c5adb3..000000000 --- a/apps/vben5/packages/@abp/account/src/api/useUserInfoApi.ts +++ /dev/null @@ -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 { - const result = await request('/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, - }; -} diff --git a/apps/vben5/packages/@abp/account/src/components/MySetting.vue b/apps/vben5/packages/@abp/account/src/components/MySetting.vue index ee412c254..848cf6630 100644 --- a/apps/vben5/packages/@abp/account/src/components/MySetting.vue +++ b/apps/vben5/packages/@abp/account/src/components/MySetting.vue @@ -1,8 +1,9 @@ - + + + + + + {{ button.title }} + + + + + diff --git a/apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue b/apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue index 65ac3940b..ce65c2368 100644 --- a/apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue +++ b/apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue @@ -86,13 +86,8 @@ onMounted(onGet); - - {{ - $t('abp.account.settings.security.password') - }} - - + :title="$t('abp.account.settings.security.password')" + /> diff --git a/apps/vben5/packages/@abp/account/src/hooks/index.ts b/apps/vben5/packages/@abp/account/src/hooks/index.ts index 25bce3f27..556dbb37b 100644 --- a/apps/vben5/packages/@abp/account/src/hooks/index.ts +++ b/apps/vben5/packages/@abp/account/src/hooks/index.ts @@ -1,2 +1,2 @@ export * from './useOAuthError'; -export * from './useOidcClient'; +export * from './useOAuthService'; diff --git a/apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts b/apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts index 972f8274d..a1e821002 100644 --- a/apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts +++ b/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'); } // 用户尝试登录次数太多,用户被锁定 diff --git a/apps/vben5/packages/@abp/account/src/hooks/useOidcClient.ts b/apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts similarity index 53% rename from apps/vben5/packages/@abp/account/src/hooks/useOidcClient.ts rename to apps/vben5/packages/@abp/account/src/hooks/useOAuthService.ts index f4090b44e..92e064aeb 100644 --- a/apps/vben5/packages/@abp/account/src/hooks/useOidcClient.ts +++ b/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, diff --git a/apps/vben5/packages/@abp/account/src/types/bind.ts b/apps/vben5/packages/@abp/account/src/types/bind.ts new file mode 100644 index 000000000..b85215d3d --- /dev/null +++ b/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; + title: string; + type?: ButtonType; +} + +interface BindItem { + buttons?: BindButton[]; + description?: string; + enable?: boolean; + slot?: string; + title: string; +} + +export type { BindItem }; diff --git a/apps/vben5/packages/@abp/account/src/types/external-logins.ts b/apps/vben5/packages/@abp/account/src/types/external-logins.ts new file mode 100644 index 000000000..2d8ce8587 --- /dev/null +++ b/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, +}; diff --git a/apps/vben5/packages/@abp/account/src/types/index.ts b/apps/vben5/packages/@abp/account/src/types/index.ts index 1dd5f3324..0f182a66b 100644 --- a/apps/vben5/packages/@abp/account/src/types/index.ts +++ b/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'; diff --git a/apps/vben5/packages/@abp/account/src/types/token.ts b/apps/vben5/packages/@abp/account/src/types/token.ts index 78df62c18..653b5cde8 100644 --- a/apps/vben5/packages/@abp/account/src/types/token.ts +++ b/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, diff --git a/apps/vben5/packages/@abp/account/src/utils/auth.ts b/apps/vben5/packages/@abp/account/src/utils/auth.ts index 30a7d616f..39260906b 100644 --- a/apps/vben5/packages/@abp/account/src/utils/auth.ts +++ b/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, + ) { + if (model.ChangePasswordToken) { + params.set('ChangePasswordToken', model.ChangePasswordToken); + } + if (model.NewPassword) { + params.set('NewPassword', model.NewPassword); + } + } + _writeTenantId(params: URLSearchParams, model: Record) { + if (model.tenantId) { + params.set('tenantId', model.tenantId); + } + } + _writeTwoFactorToken(params: URLSearchParams, model: Record) { + if (model.TwoFactorProvider) { + params.set('TwoFactorProvider', model.TwoFactorProvider); + } + if (model.TwoFactorCode) { + params.set('TwoFactorCode', model.TwoFactorCode); + } + } + _writeUserId(params: URLSearchParams, model: Record) { + 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 }; diff --git a/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue b/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue index cc97d7b5b..5eb7e7e92 100644 --- a/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue +++ b/apps/vben5/packages/@abp/auditing/src/components/audit-logs/AuditLogTable.vue @@ -1,5 +1,4 @@ + + + + + + diff --git a/apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue b/apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue new file mode 100644 index 000000000..c420f8d9b --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/CropperAvatar.vue @@ -0,0 +1,159 @@ + + + + + + + + + + + + {{ btnText ? btnText : t('cropper.selectImage') }} + + + + + + + diff --git a/apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue b/apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue new file mode 100644 index 000000000..f8e263e56 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/CropperModal.vue @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/vben5/packages/@abp/components/src/cropper/index.ts b/apps/vben5/packages/@abp/components/src/cropper/index.ts new file mode 100644 index 000000000..8708c5109 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/index.ts @@ -0,0 +1,2 @@ +export { default as CropperAvatar } from './CropperAvatar.vue'; +export { default as CropperModal } from './CropperModal.vue'; diff --git a/apps/vben5/packages/@abp/components/src/cropper/types.ts b/apps/vben5/packages/@abp/components/src/cropper/types.ts new file mode 100644 index 000000000..e76cc6f8e --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/cropper/types.ts @@ -0,0 +1,8 @@ +import type Cropper from 'cropperjs'; + +export interface CropendResult { + imgBase64: string; + imgInfo: Cropper.Data; +} + +export type { Cropper }; diff --git a/apps/vben5/packages/@abp/components/src/locales/index.ts b/apps/vben5/packages/@abp/components/src/locales/index.ts new file mode 100644 index 000000000..de3bd1b24 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/locales/index.ts @@ -0,0 +1,20 @@ +import type { SupportedLanguagesType } from '@vben/locales'; + +import { loadLocalesMapFromDir } from '@vben/locales'; + +const modules = import.meta.glob('./langs/**/*.json'); + +const localesMap = loadLocalesMapFromDir( + /\.\/langs\/([^/]+)\/(.*)\.json$/, + modules, +); + +/** + * 加载自定义组件本地化资源 + * @param lang 当前语言 + * @returns 资源集合 + */ +export async function loadComponentMessages(lang: SupportedLanguagesType) { + const locales = localesMap[lang]?.(); + return locales; +} diff --git a/apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json b/apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json new file mode 100644 index 000000000..5ad017f87 --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/locales/langs/en-US/cropper.json @@ -0,0 +1,14 @@ +{ + "confirmText": "Confirm and upload", + "title": "Avatar upload", + "selectImage": "Select Image", + "btn_rotate_left": "Counterclockwise rotation", + "btn_rotate_right": "Clockwise rotation", + "btn_scale_x": "Flip horizontal", + "btn_scale_y": "Flip vertical", + "btn_zoom_in": "Zoom in", + "btn_zoom_out": "Zoom out", + "btn_reset": "Reset", + "preview": "Preivew", + "uploadSuccess": "Uploaded success!" +} diff --git a/apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json b/apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json new file mode 100644 index 000000000..f3bba22eb --- /dev/null +++ b/apps/vben5/packages/@abp/components/src/locales/langs/zh-CN/cropper.json @@ -0,0 +1,14 @@ +{ + "confirmText": "确认并上传", + "title": "头像上传", + "selectImage": "选择图片", + "btn_rotate_left": "逆时针旋转", + "btn_rotate_right": "顺时针旋转", + "btn_scale_x": "水平翻转", + "btn_scale_y": "垂直翻转", + "btn_zoom_in": "放大", + "btn_zoom_out": "缩小", + "btn_reset": "重置", + "preview": "预览", + "uploadSuccess": "上传成功!" +} diff --git a/apps/vben5/packages/@abp/core/package.json b/apps/vben5/packages/@abp/core/package.json index da1a27134..0e8d0eb10 100644 --- a/apps/vben5/packages/@abp/core/package.json +++ b/apps/vben5/packages/@abp/core/package.json @@ -38,13 +38,20 @@ "@vueuse/core": "catalog:", "dayjs": "catalog:", "lodash.groupby": "catalog:", + "lodash.isdate": "catalog:", + "lodash.isnumber": "catalog:", "lodash.merge": "catalog:", + "lodash.sortby": "catalog:", "pinia": "catalog:", "pinia-plugin-persistedstate": "catalog:", + "universal-cookie": "catalog:", "vue": "catalog:" }, "devDependencies": { "@types/lodash.groupby": "catalog:", - "@types/lodash.merge": "catalog:" + "@types/lodash.isdate": "catalog:", + "@types/lodash.isnumber": "catalog:", + "@types/lodash.merge": "catalog:", + "@types/lodash.sortby": "catalog:" } } diff --git a/apps/vben5/packages/@abp/core/src/store/abp.ts b/apps/vben5/packages/@abp/core/src/store/abp.ts index c3d9e4f44..567c8ca95 100644 --- a/apps/vben5/packages/@abp/core/src/store/abp.ts +++ b/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(); + const xsrfToken = ref(); const application = ref(); const localization = ref(); /** 获取 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, diff --git a/apps/vben5/packages/@abp/core/src/utils/date.ts b/apps/vben5/packages/@abp/core/src/utils/date.ts index c594642cf..c388d0834 100644 --- a/apps/vben5/packages/@abp/core/src/utils/date.ts +++ b/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; diff --git a/apps/vben5/packages/@abp/core/src/utils/file.ts b/apps/vben5/packages/@abp/core/src/utils/file.ts new file mode 100644 index 000000000..559c36b7b --- /dev/null +++ b/apps/vben5/packages/@abp/core/src/utils/file.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/** + * @description: base64 to blob + */ +export function dataURLtoBlob(base64Buf: string): Blob { + const arr = base64Buf.split(','); + const typeItem = arr[0]; + const mime = typeItem?.match(/:(.*?);/)?.[1]; + const bstr = window.atob(arr[1]!); + let n = bstr.length; + const u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.codePointAt(n)!; + } + return new Blob([u8arr], { type: mime }); +} + +/** + * img url to base64 + * @param url + */ +export function urlToBase64(url: string, mineType?: string): Promise { + return new Promise((resolve, reject) => { + let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null; + const ctx = canvas!.getContext('2d'); + + const img = new Image(); + img.crossOrigin = ''; + img.addEventListener('load', () => { + if (!canvas || !ctx) { + return reject(new Error('canvas or ctx is null!')); + } + canvas.height = img.height; + canvas.width = img.width; + ctx.drawImage(img, 0, 0); + const dataURL = canvas.toDataURL(mineType || 'image/png'); + canvas = null; + resolve(dataURL); + }); + img.src = url; + }); +} diff --git a/apps/vben5/packages/@abp/core/src/utils/index.ts b/apps/vben5/packages/@abp/core/src/utils/index.ts index 4c7bf48a5..b00fa27c5 100644 --- a/apps/vben5/packages/@abp/core/src/utils/index.ts +++ b/apps/vben5/packages/@abp/core/src/utils/index.ts @@ -1,7 +1,10 @@ export * from './array'; export * from './date'; +export * from './file'; export * from './is'; export * from './mitt'; export * from './regex'; export * from './string'; +export * from './table'; export * from './tree'; +export * from './uuid'; diff --git a/apps/vben5/packages/@abp/core/src/utils/is.ts b/apps/vben5/packages/@abp/core/src/utils/is.ts index c760f4afa..970ed341e 100644 --- a/apps/vben5/packages/@abp/core/src/utils/is.ts +++ b/apps/vben5/packages/@abp/core/src/utils/is.ts @@ -1,3 +1,6 @@ +export { default as isDate } from 'lodash.isdate'; +export { default as isNumber } from 'lodash.isnumber'; + export function isNullAndUnDef(val: unknown): val is null | undefined { return isUnDef(val) && isNull(val); } diff --git a/apps/vben5/packages/@abp/core/src/utils/table.ts b/apps/vben5/packages/@abp/core/src/utils/table.ts new file mode 100644 index 000000000..58f609877 --- /dev/null +++ b/apps/vben5/packages/@abp/core/src/utils/table.ts @@ -0,0 +1,31 @@ +import { isDate, isNumber } from './is'; + +export function sorter( + a: Record, + b: Record, + field: string, +): number { + if (!a[field] && !b[field]) { + return 0; + } + if (a[field] && !b[field]) { + return 1; + } + if (b[field] && !a[field]) { + return -1; + } + const va = a[field]; + const vb = b[field]; + if (isDate(va) && isDate(vb)) { + return va.getTime() - vb.getTime(); + } + if (isNumber(va) && isNumber(vb)) { + return va - vb; + } + if (Array.isArray(va) && Array.isArray(vb)) { + return va.length - vb.length; + } + return String(va).localeCompare(String(vb)); +} + +export { default as sortby } from 'lodash.sortby'; diff --git a/apps/vben5/packages/@abp/core/src/utils/uuid.ts b/apps/vben5/packages/@abp/core/src/utils/uuid.ts new file mode 100644 index 000000000..81c49a09d --- /dev/null +++ b/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)}`; +} diff --git a/apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue b/apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue index bec7e4482..37b37e797 100644 --- a/apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue +++ b/apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue @@ -29,24 +29,28 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('DataProtection.DisplayName:Name'), }, { align: 'left', field: 'displayName', minWidth: 150, + sortable: true, title: $t('DataProtection.DisplayName:DisplayName'), }, { align: 'left', field: 'typeFullName', minWidth: 200, + sortable: true, title: $t('DataProtection.DisplayName:TypeFullName'), }, { align: 'left', field: 'isAuditEnabled', minWidth: 150, + sortable: true, title: $t('DataProtection.DisplayName:IsAuditEnabled'), }, { @@ -61,8 +65,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; const { totalCount, items } = await getPagedListApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -85,14 +91,19 @@ const gridOptions: VxeGridProps = { }, }, toolbarConfig: { - refresh: true, + refresh: { + code: 'query', + }, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; -const [Grid] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions, }); diff --git a/apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue b/apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue index 109675b16..c524544e0 100644 --- a/apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue +++ b/apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue @@ -60,24 +60,28 @@ const baseColumns = reactive>([ align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('Demo.DisplayName:Name'), }, { align: 'left', field: 'authorName', minWidth: 150, + sortable: true, title: $t('Demo.DisplayName:AuthorId'), }, { align: 'left', field: 'publishDate', minWidth: 200, + sortable: true, title: $t('Demo.DisplayName:PublishDate'), }, { align: 'left', field: 'price', minWidth: 150, + sortable: true, title: $t('Demo.DisplayName:Price'), }, ]); @@ -88,8 +92,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getPagedListApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -102,12 +108,17 @@ const gridOptions: VxeGridProps = { }, }, toolbarConfig: { - refresh: true, + refresh: { + code: 'query', + }, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; const [Grid, gridApi] = useVbenVxeGrid({ formOptions, diff --git a/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue b/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue index 192d61fb1..59dab9a76 100644 --- a/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue +++ b/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue @@ -6,13 +6,14 @@ import type { VbenFormProps } from '@vben/common-ui'; import type { FeatureDefinitionDto } from '../../../types/definitions'; import type { FeatureGroupDefinitionDto } from '../../../types/groups'; -import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; +import { defineAsyncComponent, h, onMounted, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { listToTree, + sortby, useLocalization, useLocalizationSerializer, } from '@abp/core'; @@ -35,8 +36,8 @@ defineOptions({ name: 'FeatureDefinitionTable', }); -interface PermissionVo { - children: PermissionVo[]; +interface FeatureVo { + children: FeatureVo[]; displayName: string; groupName: string; isEnabled: boolean; @@ -46,30 +47,24 @@ interface PermissionVo { providers: string[]; stateCheckers: string[]; } -interface PermissionGroupVo { +interface FeatureGroupVo { displayName: string; + features: FeatureVo[]; name: string; - permissions: PermissionVo[]; } -const permissionGroups = ref([]); -const pageState = reactive({ - current: 1, - size: 10, - total: 0, -}); +const featureGroups = ref([]); const { Lr } = useLocalization(); const { deserialize } = useLocalizationSerializer(); const { getListApi: getGroupsApi } = useFeatureGroupDefinitionsApi(); -const { deleteApi, getListApi: getPermissionsApi } = useFeatureDefinitionsApi(); +const { deleteApi, getListApi: getFeaturesApi } = useFeatureDefinitionsApi(); const formOptions: VbenFormProps = { // 默认展开 collapsed: false, handleReset: onReset, async handleSubmit(params) { - pageState.current = 1; await onGet(params); }, schema: [ @@ -104,12 +99,14 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:Name'), }, { align: 'left', field: 'displayName', minWidth: 150, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:DisplayName'), }, ], @@ -119,6 +116,30 @@ const gridOptions: VxeGridProps = { }, exportConfig: {}, keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page, sort }) => { + let items = sortby(featureGroups.value, sort.field); + if (sort.order === 'desc') { + items = items.reverse(); + } + const result = { + totalCount: featureGroups.value.length, + items: items.slice( + (page.currentPage - 1) * page.pageSize, + page.currentPage * page.pageSize, + ), + }; + return new Promise((resolve) => { + resolve(result); + }); + }, + }, + response: { + total: 'totalCount', + list: 'items', + }, + }, toolbarConfig: { custom: true, export: true, @@ -136,6 +157,7 @@ const subGridColumns: VxeGridProps['columns'] = [ align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:Name'), treeNode: true, }, @@ -143,12 +165,14 @@ const subGridColumns: VxeGridProps['columns'] = [ align: 'left', field: 'displayName', minWidth: 120, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:DisplayName'), }, { align: 'left', field: 'description', minWidth: 120, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:Description'), }, { @@ -156,6 +180,7 @@ const subGridColumns: VxeGridProps['columns'] = [ field: 'isVisibleToClients', minWidth: 120, slots: { default: 'isVisibleToClients' }, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:IsVisibleToClients'), }, { @@ -163,6 +188,7 @@ const subGridColumns: VxeGridProps['columns'] = [ field: 'isAvailableToHost', minWidth: 120, slots: { default: 'isAvailableToHost' }, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:IsAvailableToHost'), }, { @@ -175,10 +201,8 @@ const subGridColumns: VxeGridProps['columns'] = [ ]; const gridEvents: VxeGridListeners = { - pageChange(params) { - pageState.current = params.currentPage; - pageState.size = params.pageSize; - onPageChange(); + sortChange: () => { + gridApi.query(); }, }; @@ -198,17 +222,16 @@ async function onGet(input?: Record) { try { gridApi.setLoading(true); const groupRes = await getGroupsApi(input); - const permissionRes = await getPermissionsApi(input); - pageState.total = groupRes.items.length; - permissionGroups.value = groupRes.items.map((group) => { + const featureRes = await getFeaturesApi(input); + featureGroups.value = groupRes.items.map((group) => { const localizableGroup = deserialize(group.displayName); - const permissions = permissionRes.items - .filter((permission) => permission.groupName === group.name) - .map((permission) => { - const displayName = deserialize(permission.displayName); - const description = deserialize(permission.displayName); + const features = featureRes.items + .filter((feature) => feature.groupName === group.name) + .map((feature) => { + const displayName = deserialize(feature.displayName); + const description = deserialize(feature.description); return { - ...permission, + ...feature, description: Lr(description.resourceName, description.name), displayName: Lr(displayName.resourceName, displayName.name), }; @@ -216,13 +239,13 @@ async function onGet(input?: Record) { return { ...group, displayName: Lr(localizableGroup.resourceName, localizableGroup.name), - permissions: listToTree(permissions, { + features: listToTree(features, { id: 'name', pid: 'parentName', }), }; }); - onPageChange(); + setTimeout(() => gridApi.reload(), 100); } finally { gridApi.setLoading(false); } @@ -234,21 +257,6 @@ async function onReset() { await onGet(input); } -function onPageChange() { - const items = permissionGroups.value.slice( - (pageState.current - 1) * pageState.size, - pageState.current * pageState.size, - ); - gridApi.setGridOptions({ - data: items, - pagerConfig: { - currentPage: pageState.current, - pageSize: pageState.size, - total: pageState.total, - }, - }); -} - function onCreate() { modalApi.setData({}); modalApi.open(); @@ -290,7 +298,7 @@ onMounted(onGet); query()" /> + gridApi.query()" /> diff --git a/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitRoleTable.vue b/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitRoleTable.vue index bc501fd02..1475e6845 100644 --- a/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitRoleTable.vue +++ b/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitRoleTable.vue @@ -45,6 +45,7 @@ const gridOptions: VxeGridProps = { { field: 'name', minWidth: '100px', + sortable: true, title: $t('AbpIdentity.DisplayName:RoleName'), }, { @@ -59,14 +60,16 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { if (!props.selectedKey) { return { totalCount: 0, items: [], }; } + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getRoleListApi(props.selectedKey!, { + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -81,16 +84,20 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; -const [Grid, { query, setLoading }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions, }); @@ -100,7 +107,7 @@ const [RoleModal, roleModalApi] = useVbenModal({ }); const onRefresh = () => { - return nextTick(query); + return nextTick(gridApi.query); }; const onDelete = (row: IdentityRoleDto) => { @@ -114,11 +121,11 @@ const onDelete = (row: IdentityRoleDto) => { }, onOk: async () => { try { - setLoading(true); + gridApi.setLoading(true); await removeOrganizationUnitApi(row.id, props.selectedKey!); await onRefresh(); } finally { - setLoading(false); + gridApi.setLoading(false); } }, title: $t('AbpUi.AreYouSure'), @@ -141,7 +148,7 @@ const onCreateRole = async (roles: IdentityRoleDto[]) => { roleIds: roles.map((item) => item.id), }); roleModalApi.close(); - await query(); + await gridApi.query(); } finally { roleModalApi.setState({ submitting: false, diff --git a/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitUserTable.vue b/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitUserTable.vue index ce69c6c82..70a828d36 100644 --- a/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitUserTable.vue +++ b/apps/vben5/packages/@abp/identity/src/components/organization-units/OrganizationUnitUserTable.vue @@ -45,11 +45,13 @@ const gridOptions: VxeGridProps = { { field: 'userName', minWidth: '100px', + sortable: true, title: $t('AbpIdentity.DisplayName:UserName'), }, { field: 'email', minWidth: '120px', + sortable: true, title: $t('AbpIdentity.DisplayName:Email'), }, { @@ -64,14 +66,16 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { if (!props.selectedKey) { return { totalCount: 0, items: [], }; } + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getUserListApi(props.selectedKey!, { + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -86,16 +90,20 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; -const [Grid, { query, setLoading }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions, }); @@ -104,7 +112,7 @@ const [MemberModal, userModalApi] = useVbenModal({ }); const onRefresh = () => { - nextTick(query); + nextTick(gridApi.query); }; const onDelete = (row: IdentityUserDto) => { @@ -116,11 +124,14 @@ const onDelete = (row: IdentityUserDto) => { onCancel: () => { cancel('User closed cancel delete modal.'); }, - onOk: () => { - setLoading(true); - return removeOrganizationUnitApi(row.id, props.selectedKey!) - .then(onRefresh) - .finally(() => setLoading(false)); + onOk: async () => { + gridApi.setLoading(true); + try { + await removeOrganizationUnitApi(row.id, props.selectedKey!); + await gridApi.query(); + } finally { + gridApi.setLoading(false); + } }, title: $t('AbpUi.AreYouSure'), }); @@ -133,22 +144,21 @@ const onShowMember = () => { userModalApi.open(); }; -const onCreateMember = (users: IdentityUserDto[]) => { +const onCreateMember = async (users: IdentityUserDto[]) => { userModalApi.setState({ submitting: true, }); - addMembers(props.selectedKey!, { - userIds: users.map((item) => item.id), - }) - .then(() => { - userModalApi.close(); - query(); - }) - .finally(() => { - userModalApi.setState({ - submitting: false, - }); + try { + await addMembers(props.selectedKey!, { + userIds: users.map((item) => item.id), + }); + userModalApi.close(); + await gridApi.query(); + } finally { + userModalApi.setState({ + submitting: false, }); + } }; watch(() => props.selectedKey, onRefresh); diff --git a/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectMemberModal.vue b/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectMemberModal.vue index 7e3efbc4a..e3d584027 100644 --- a/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectMemberModal.vue +++ b/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectMemberModal.vue @@ -68,22 +68,24 @@ const [Modal, modalApi] = useVbenModal({ }); const gridOptions: VxeGridProps = { - checkboxConfig: { - highlight: true, - labelField: 'userName', - }, columns: [ + { + align: 'center', + type: 'checkbox', + width: 80, + }, { align: 'left', field: 'userName', minWidth: '100px', + sortable: true, title: $t('AbpIdentity.DisplayName:UserName'), - type: 'checkbox', }, { align: 'left', field: 'email', minWidth: '120px', + sortable: true, title: $t('AbpIdentity.DisplayName:Email'), }, ], @@ -91,12 +93,14 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; const state = modalApi.getData>(); return await getUnaddedUserListApi({ id: state.id, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, + sorting, ...formValues, }); }, @@ -107,7 +111,11 @@ const gridOptions: VxeGridProps = { list: 'items', }, }, - toolbarConfig: {}, + toolbarConfig: { + refresh: { + code: 'query', + }, + }, }; const gridEvents: VxeGridListeners = { @@ -117,15 +125,18 @@ const gridEvents: VxeGridListeners = { checkboxChange: (e) => { selectedUsers.value = e.records; }, + sortChange: () => { + gridApi.query(); + }, }; -const [Grid, { query }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridEvents, gridOptions, }); function onRefresh() { - nextTick(query); + nextTick(gridApi.query); } diff --git a/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectRoleModal.vue b/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectRoleModal.vue index ec5e33f3f..9f34ab20f 100644 --- a/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectRoleModal.vue +++ b/apps/vben5/packages/@abp/identity/src/components/organization-units/SelectRoleModal.vue @@ -68,27 +68,30 @@ const [Modal, modalApi] = useVbenModal({ }); const gridOptions: VxeGridProps = { - checkboxConfig: { - highlight: true, - labelField: 'name', - }, columns: [ + { + align: 'center', + type: 'checkbox', + width: 80, + }, { align: 'left', field: 'name', + sortable: true, title: $t('AbpIdentity.DisplayName:RoleName'), - type: 'checkbox', }, ], exportConfig: {}, keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; const state = modalApi.getData>(); return await getUnaddedRoleListApi({ id: state.id, maxResultCount: page.pageSize, + sorting, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, }); @@ -100,7 +103,11 @@ const gridOptions: VxeGridProps = { list: 'items', }, }, - toolbarConfig: {}, + toolbarConfig: { + refresh: { + code: 'query', + }, + }, }; const gridEvents: VxeGridListeners = { @@ -110,15 +117,18 @@ const gridEvents: VxeGridListeners = { checkboxChange: (e) => { selectedRoles.value = e.records; }, + sortChange: () => { + gridApi.query(); + }, }; -const [Grid, { query }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridEvents, gridOptions, }); function onRefresh() { - nextTick(query); + nextTick(gridApi.query); } diff --git a/apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue b/apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue index 1bc0d6eaf..85aad1ba7 100644 --- a/apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue +++ b/apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue @@ -87,6 +87,7 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'name', slots: { default: 'name' }, + sortable: true, title: $t('AbpIdentity.DisplayName:RoleName'), }, { @@ -101,8 +102,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getPagedListApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -117,19 +120,23 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; const [RoleEditModal, roleModalApi] = useVbenModal({ connectedComponent: RoleModal, }); -const [Grid, { query }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridEvents, gridOptions, @@ -155,7 +162,7 @@ const handleDelete = (row: IdentityRoleDto) => { onOk: async () => { await deleteApi(row.id); message.success($t('AbpUi.DeletedSuccessfully')); - query(); + gridApi.query(); }, title: $t('AbpUi.AreYouSure'), }); @@ -300,8 +307,8 @@ function onPermissionChange(_name: string, key: string) { - query()" /> - + gridApi.query()" /> + gridApi.query()" /> diff --git a/apps/vben5/packages/@abp/identity/src/components/security-logs/SecurityLogTable.vue b/apps/vben5/packages/@abp/identity/src/components/security-logs/SecurityLogTable.vue index fc3d3ae42..ef0910f31 100644 --- a/apps/vben5/packages/@abp/identity/src/components/security-logs/SecurityLogTable.vue +++ b/apps/vben5/packages/@abp/identity/src/components/security-logs/SecurityLogTable.vue @@ -1,5 +1,4 @@ diff --git a/apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue b/apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue index 6830f98f1..249a37b21 100644 --- a/apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue +++ b/apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue @@ -82,6 +82,7 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'sessionId', minWidth: 150, + sortable: true, title: $t('AbpIdentity.DisplayName:SessionId'), }, { @@ -89,11 +90,13 @@ const gridOptions: VxeGridProps = { field: 'device', minWidth: 120, slots: { default: 'device' }, + sortable: true, title: $t('AbpIdentity.DisplayName:Device'), }, { align: 'left', field: 'deviceInfo', + sortable: true, title: $t('AbpIdentity.DisplayName:DeviceInfo'), width: 'auto', }, @@ -101,24 +104,28 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'clientId', minWidth: 120, + sortable: true, title: $t('AbpIdentity.DisplayName:ClientId'), }, { align: 'left', field: 'ipAddresses', minWidth: 120, + sortable: true, title: $t('AbpIdentity.DisplayName:IpAddresses'), }, { align: 'left', field: 'signedIn', minWidth: 120, + sortable: true, title: $t('AbpIdentity.DisplayName:SignedIn'), }, { align: 'left', field: 'lastAccessed', minWidth: 120, + sortable: true, title: $t('AbpIdentity.DisplayName:LastAccessed'), }, { @@ -137,8 +144,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getSessionsApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -153,14 +162,18 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; const [Grid, gridApi] = useVbenVxeGrid({ diff --git a/apps/vben5/packages/@abp/identity/src/components/sessions/UserSessionTable.vue b/apps/vben5/packages/@abp/identity/src/components/sessions/UserSessionTable.vue index b2c02d0b7..aa0c99a7f 100644 --- a/apps/vben5/packages/@abp/identity/src/components/sessions/UserSessionTable.vue +++ b/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) { {{ row.device }} - + {{ $t('AbpIdentity.CurrentSession') }} diff --git a/apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue b/apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue index 7d68dcf17..520e3d659 100644 --- a/apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue +++ b/apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue @@ -96,11 +96,13 @@ const gridOptions: VxeGridProps = { { field: 'isActive', slots: { default: 'active' }, + sortable: true, title: $t('AbpIdentity.DisplayName:IsActive'), }, { field: 'userName', minWidth: '100px', + sortable: true, title: $t('AbpIdentity.DisplayName:UserName'), }, { @@ -108,14 +110,24 @@ const gridOptions: VxeGridProps = { field: 'email', minWidth: '120px', slots: { default: 'email' }, + sortable: true, title: $t('AbpIdentity.DisplayName:Email'), }, - { field: 'surname', title: $t('AbpIdentity.DisplayName:Surname') }, - { field: 'name', title: $t('AbpIdentity.DisplayName:Name') }, + { + field: 'surname', + sortable: true, + title: $t('AbpIdentity.DisplayName:Surname'), + }, + { + field: 'name', + sortable: true, + title: $t('AbpIdentity.DisplayName:Name'), + }, { align: 'left', field: 'phoneNumber', slots: { default: 'phoneNumber' }, + sortable: true, title: $t('AbpIdentity.DisplayName:PhoneNumber'), }, { @@ -123,6 +135,7 @@ const gridOptions: VxeGridProps = { formatter: ({ cellValue }) => { return cellValue ? formatToDateTime(cellValue) : ''; }, + sortable: true, title: $t('AbpIdentity.LockoutEnd'), }, { @@ -137,8 +150,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getPagedListApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -153,14 +168,18 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; const [UserEditModal, userModalApi] = useVbenModal({ connectedComponent: UserModal, @@ -188,7 +207,7 @@ const [UserSessionDrawer, userSessionDrawerApi] = useVbenDrawer({ () => import('./UserSessionDrawer.vue'), ), }); -const [Grid, { query }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridEvents, gridOptions, @@ -214,7 +233,7 @@ const handleDelete = (row: IdentityUserDto) => { onOk: async () => { await deleteApi(row.id); message.success($t('AbpUi.DeletedSuccessfully')); - query(); + await gridApi.query(); }, title: $t('AbpUi.AreYouSure'), }); @@ -222,7 +241,7 @@ const handleDelete = (row: IdentityUserDto) => { const handleUnlock = async (row: IdentityUserDto) => { await unLockApi(row.id); - await query(); + await gridApi.query(); }; const handleMenuClick = async (row: IdentityUserDto, info: MenuInfo) => { @@ -436,10 +455,10 @@ const handleMenuClick = async (row: IdentityUserDto, info: MenuInfo) => { - - - query()" /> - + gridApi.query()" /> + gridApi.query()" /> + gridApi.query()" /> + gridApi.query()" /> diff --git a/apps/vben5/packages/@abp/localization/src/components/languages/LocalizationLanguageTable.vue b/apps/vben5/packages/@abp/localization/src/components/languages/LocalizationLanguageTable.vue index e70fc11fe..5e1620476 100644 --- a/apps/vben5/packages/@abp/localization/src/components/languages/LocalizationLanguageTable.vue +++ b/apps/vben5/packages/@abp/localization/src/components/languages/LocalizationLanguageTable.vue @@ -5,12 +5,12 @@ import type { VbenFormProps } from '@vben/common-ui'; import type { LanguageDto } from '../../types/languages'; -import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; +import { defineAsyncComponent, h, onMounted, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { $t } from '@vben/locales'; -import { useAbpStore } from '@abp/core'; +import { sortby, useAbpStore } from '@abp/core'; import { useVbenVxeGrid } from '@abp/ui'; import { DeleteOutlined, @@ -28,11 +28,6 @@ defineOptions({ }); const dataSource = ref([]); -const pageState = reactive({ - current: 1, - size: 10, - total: 0, -}); const abpStore = useAbpStore(); const { deleteApi, getListApi } = useLanguagesApi(); @@ -43,7 +38,6 @@ const formOptions: VbenFormProps = { collapsed: false, handleReset: onReset, async handleSubmit(params) { - pageState.current = 1; await onGet(params); }, schema: [ @@ -66,18 +60,21 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'cultureName', minWidth: 150, + sortable: true, title: $t('AbpLocalization.DisplayName:CultureName'), }, { align: 'left', field: 'displayName', minWidth: 150, + sortable: true, title: $t('AbpLocalization.DisplayName:DisplayName'), }, { align: 'left', field: 'uiCultureName', minWidth: 150, + sortable: true, title: $t('AbpLocalization.DisplayName:UiCultureName'), }, { @@ -90,6 +87,30 @@ const gridOptions: VxeGridProps = { ], exportConfig: {}, keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page, sort }) => { + let items = sortby(dataSource.value, sort.field); + if (sort.order === 'desc') { + items = items.reverse(); + } + const result = { + totalCount: dataSource.value.length, + items: items.slice( + (page.currentPage - 1) * page.pageSize, + page.currentPage * page.pageSize, + ), + }; + return new Promise((resolve) => { + resolve(result); + }); + }, + }, + response: { + total: 'totalCount', + list: 'items', + }, + }, toolbarConfig: { custom: true, export: true, @@ -99,10 +120,8 @@ const gridOptions: VxeGridProps = { }; const gridEvents: VxeGridListeners = { - pageChange(params) { - pageState.current = params.currentPage; - pageState.size = params.pageSize; - onPageChange(); + sortChange: () => { + gridApi.query(); }, }; @@ -122,9 +141,8 @@ async function onGet(input?: Record) { try { gridApi.setLoading(true); const { items } = await getListApi(input); - pageState.total = items.length; dataSource.value = items; - onPageChange(); + setTimeout(() => gridApi.reload(), 100); } finally { gridApi.setLoading(false); } @@ -136,21 +154,6 @@ async function onReset() { await onGet(input); } -function onPageChange() { - const items = dataSource.value.slice( - (pageState.current - 1) * pageState.size, - pageState.current * pageState.size, - ); - gridApi.setGridOptions({ - data: items, - pagerConfig: { - currentPage: pageState.current, - pageSize: pageState.size, - total: pageState.total, - }, - }); -} - function onCreate() { modalApi.setData({}); modalApi.open(); diff --git a/apps/vben5/packages/@abp/localization/src/components/resources/LocalizationResourceTable.vue b/apps/vben5/packages/@abp/localization/src/components/resources/LocalizationResourceTable.vue index 3a1c6f7d0..17588471e 100644 --- a/apps/vben5/packages/@abp/localization/src/components/resources/LocalizationResourceTable.vue +++ b/apps/vben5/packages/@abp/localization/src/components/resources/LocalizationResourceTable.vue @@ -5,12 +5,12 @@ import type { VbenFormProps } from '@vben/common-ui'; import type { ResourceDto } from '../../types/resources'; -import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; +import { defineAsyncComponent, h, onMounted, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { $t } from '@vben/locales'; -import { useAbpStore } from '@abp/core'; +import { sortby, useAbpStore } from '@abp/core'; import { useVbenVxeGrid } from '@abp/ui'; import { DeleteOutlined, @@ -28,12 +28,6 @@ defineOptions({ }); const dataSource = ref([]); -const pageState = reactive({ - current: 1, - size: 10, - total: 0, -}); - const abpStore = useAbpStore(); const { deleteApi, getListApi } = useResourcesApi(); const { getLocalizationApi } = useLocalizationsApi(); @@ -43,7 +37,6 @@ const formOptions: VbenFormProps = { collapsed: false, handleReset: onReset, async handleSubmit(params) { - pageState.current = 1; await onGet(params); }, schema: [ @@ -66,12 +59,14 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:Name'), }, { align: 'left', field: 'displayName', minWidth: 150, + sortable: true, title: $t('AbpFeatureManagement.DisplayName:DisplayName'), }, { @@ -84,6 +79,30 @@ const gridOptions: VxeGridProps = { ], exportConfig: {}, keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page, sort }) => { + let items = sortby(dataSource.value, sort.field); + if (sort.order === 'desc') { + items = items.reverse(); + } + const result = { + totalCount: dataSource.value.length, + items: items.slice( + (page.currentPage - 1) * page.pageSize, + page.currentPage * page.pageSize, + ), + }; + return new Promise((resolve) => { + resolve(result); + }); + }, + }, + response: { + total: 'totalCount', + list: 'items', + }, + }, toolbarConfig: { custom: true, export: true, @@ -93,10 +112,8 @@ const gridOptions: VxeGridProps = { }; const gridEvents: VxeGridListeners = { - pageChange(params) { - pageState.current = params.currentPage; - pageState.size = params.pageSize; - onPageChange(); + sortChange: () => { + gridApi.query(); }, }; @@ -116,9 +133,8 @@ async function onGet(input?: Record) { try { gridApi.setLoading(true); const { items } = await getListApi(input); - pageState.total = items.length; dataSource.value = items; - onPageChange(); + setTimeout(() => gridApi.reload(), 100); } finally { gridApi.setLoading(false); } @@ -130,21 +146,6 @@ async function onReset() { await onGet(input); } -function onPageChange() { - const items = dataSource.value.slice( - (pageState.current - 1) * pageState.size, - pageState.current * pageState.size, - ); - gridApi.setGridOptions({ - data: items, - pagerConfig: { - currentPage: pageState.current, - pageSize: pageState.size, - total: pageState.total, - }, - }); -} - function onCreate() { modalApi.setData({}); modalApi.open(); diff --git a/apps/vben5/packages/@abp/localization/src/components/texts/LocalizationTextTable.vue b/apps/vben5/packages/@abp/localization/src/components/texts/LocalizationTextTable.vue index 4b0dada4e..dac88ef0c 100644 --- a/apps/vben5/packages/@abp/localization/src/components/texts/LocalizationTextTable.vue +++ b/apps/vben5/packages/@abp/localization/src/components/texts/LocalizationTextTable.vue @@ -11,7 +11,7 @@ import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { $t } from '@vben/locales'; -import { useAbpStore } from '@abp/core'; +import { sortby, useAbpStore } from '@abp/core'; import { useVbenVxeGrid } from '@abp/ui'; import { EditOutlined, PlusOutlined } from '@ant-design/icons-vue'; import { Button, Select } from 'ant-design-vue'; @@ -39,11 +39,6 @@ const targetValueOptions = reactive([ value: 'true', }, ]); -const pageState = reactive({ - current: 1, - size: 10, - total: 0, -}); const abpStore = useAbpStore(); const { getListApi } = useTextsApi(); @@ -61,10 +56,8 @@ const formOptions: VbenFormProps = { class: 'w-full', }, }, - compact: false, handleReset: onReset, async handleSubmit(params) { - pageState.current = 1; await onGet(params); }, schema: [ @@ -111,24 +104,28 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'key', minWidth: 150, + sortable: true, title: $t('AbpLocalization.DisplayName:Key'), }, { align: 'left', field: 'value', minWidth: 150, + sortable: true, title: $t('AbpLocalization.DisplayName:Value'), }, { align: 'left', field: 'targetValue', minWidth: 150, + sortable: true, title: $t('AbpLocalization.DisplayName:TargetValue'), }, { align: 'left', field: 'resourceName', minWidth: 150, + sortable: true, title: $t('AbpLocalization.DisplayName:ResourceName'), }, { @@ -141,6 +138,30 @@ const gridOptions: VxeGridProps = { ], exportConfig: {}, keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page, sort }) => { + let items = sortby(dataSource.value, sort.field); + if (sort.order === 'desc') { + items = items.reverse(); + } + const result = { + totalCount: dataSource.value.length, + items: items.slice( + (page.currentPage - 1) * page.pageSize, + page.currentPage * page.pageSize, + ), + }; + return new Promise((resolve) => { + resolve(result); + }); + }, + }, + response: { + total: 'totalCount', + list: 'items', + }, + }, toolbarConfig: { custom: true, export: true, @@ -150,10 +171,8 @@ const gridOptions: VxeGridProps = { }; const gridEvents: VxeGridListeners = { - pageChange(params) { - pageState.current = params.currentPage; - pageState.size = params.pageSize; - onPageChange(); + sortChange: () => { + gridApi.query(); }, }; @@ -186,9 +205,8 @@ async function onGet(input: Record) { try { gridApi.setLoading(true); const { items } = await getListApi(input as any); - pageState.total = items.length; dataSource.value = items; - onPageChange(); + setTimeout(() => gridApi.reload(), 100); } finally { gridApi.setLoading(false); } @@ -200,21 +218,6 @@ async function onReset() { await onGet(input); } -function onPageChange() { - const items = dataSource.value.slice( - (pageState.current - 1) * pageState.size, - pageState.current * pageState.size, - ); - gridApi.setGridOptions({ - data: items, - pagerConfig: { - currentPage: pageState.current, - pageSize: pageState.size, - total: pageState.total, - }, - }); -} - async function onCreate() { const input = await gridApi.formApi.getValues(); modalApi.setData({ diff --git a/apps/vben5/packages/@abp/notifications/src/components/definitions/groups/NotificationGroupDefinitionTable.vue b/apps/vben5/packages/@abp/notifications/src/components/definitions/groups/NotificationGroupDefinitionTable.vue index 67abc62ee..06e245ec9 100644 --- a/apps/vben5/packages/@abp/notifications/src/components/definitions/groups/NotificationGroupDefinitionTable.vue +++ b/apps/vben5/packages/@abp/notifications/src/components/definitions/groups/NotificationGroupDefinitionTable.vue @@ -6,14 +6,14 @@ import type { VbenFormProps } from '@vben/common-ui'; import type { NotificationGroupDefinitionDto } from '../../../types/groups'; -import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; +import { defineAsyncComponent, h, onMounted, ref } from 'vue'; import { useAccess } from '@vben/access'; import { useVbenModal } from '@vben/common-ui'; import { createIconifyIcon } from '@vben/icons'; import { $t } from '@vben/locales'; -import { useLocalization, useLocalizationSerializer } from '@abp/core'; +import { sortby, useLocalization, useLocalizationSerializer } from '@abp/core'; import { useVbenVxeGrid } from '@abp/ui'; import { DeleteOutlined, @@ -37,11 +37,6 @@ const MenuItem = Menu.Item; const DefinitionIcon = createIconifyIcon('nimbus:notification'); const dataSource = ref([]); -const pageState = reactive({ - current: 1, - size: 10, - total: 0, -}); const { Lr } = useLocalization(); const { hasAccessByCodes } = useAccess(); @@ -53,7 +48,6 @@ const formOptions: VbenFormProps = { collapsed: false, handleReset: onReset, async handleSubmit(params) { - pageState.current = 1; await onGet(params); }, schema: [ @@ -76,12 +70,14 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('Notifications.DisplayName:Name'), }, { align: 'left', field: 'displayName', minWidth: 150, + sortable: true, title: $t('Notifications.DisplayName:DisplayName'), }, { @@ -94,6 +90,30 @@ const gridOptions: VxeGridProps = { ], exportConfig: {}, keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page, sort }) => { + let items = sortby(dataSource.value, sort.field); + if (sort.order === 'desc') { + items = items.reverse(); + } + const result = { + totalCount: dataSource.value.length, + items: items.slice( + (page.currentPage - 1) * page.pageSize, + page.currentPage * page.pageSize, + ), + }; + return new Promise((resolve) => { + resolve(result); + }); + }, + }, + response: { + total: 'totalCount', + list: 'items', + }, + }, toolbarConfig: { custom: true, export: true, @@ -103,10 +123,8 @@ const gridOptions: VxeGridProps = { }; const gridEvents: VxeGridListeners = { - pageChange(params) { - pageState.current = params.currentPage; - pageState.size = params.pageSize; - onPageChange(); + sortChange: () => { + gridApi.query(); }, }; @@ -131,7 +149,6 @@ async function onGet(input?: Record) { try { gridApi.setLoading(true); const { items } = await getListApi(input); - pageState.total = items.length; dataSource.value = items.map((item) => { const localizableString = deserialize(item.displayName); return { @@ -139,7 +156,7 @@ async function onGet(input?: Record) { displayName: Lr(localizableString.resourceName, localizableString.name), }; }); - onPageChange(); + setTimeout(() => gridApi.reload(), 100); } finally { gridApi.setLoading(false); } @@ -151,21 +168,6 @@ async function onReset() { await onGet(input); } -function onPageChange() { - const items = dataSource.value.slice( - (pageState.current - 1) * pageState.size, - pageState.current * pageState.size, - ); - gridApi.setGridOptions({ - data: items, - pagerConfig: { - currentPage: pageState.current, - pageSize: pageState.size, - total: pageState.total, - }, - }); -} - function onCreate() { groupModalApi.setData({}); groupModalApi.open(); diff --git a/apps/vben5/packages/@abp/notifications/src/components/definitions/notifications/NotificationDefinitionTable.vue b/apps/vben5/packages/@abp/notifications/src/components/definitions/notifications/NotificationDefinitionTable.vue index 96762869f..dc49327ba 100644 --- a/apps/vben5/packages/@abp/notifications/src/components/definitions/notifications/NotificationDefinitionTable.vue +++ b/apps/vben5/packages/@abp/notifications/src/components/definitions/notifications/NotificationDefinitionTable.vue @@ -7,7 +7,7 @@ import type { VbenFormProps } from '@vben/common-ui'; import type { NotificationDefinitionDto } from '../../../types/definitions'; import type { NotificationGroupDefinitionDto } from '../../../types/groups'; -import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; +import { defineAsyncComponent, h, onMounted, ref } from 'vue'; import { useAccess } from '@vben/access'; import { useVbenModal } from '@vben/common-ui'; @@ -16,6 +16,7 @@ import { $t } from '@vben/locales'; import { listToTree, + sortby, useLocalization, useLocalizationSerializer, } from '@abp/core'; @@ -79,18 +80,12 @@ const { deleteApi, getListApi: getDefinitionsApi } = useNotificationDefinitionsApi(); const definitionGroups = ref([]); -const pageState = reactive({ - current: 1, - size: 10, - total: 0, -}); const formOptions: VbenFormProps = { // 默认展开 collapsed: false, handleReset: onReset, async handleSubmit(params) { - pageState.current = 1; await onGet(params); }, schema: [ @@ -125,12 +120,14 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('Notifications.DisplayName:Name'), }, { align: 'left', field: 'displayName', minWidth: 150, + sortable: true, title: $t('Notifications.DisplayName:DisplayName'), }, ], @@ -140,6 +137,30 @@ const gridOptions: VxeGridProps = { }, exportConfig: {}, keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page, sort }) => { + let items = sortby(definitionGroups.value, sort.field); + if (sort.order === 'desc') { + items = items.reverse(); + } + const result = { + totalCount: definitionGroups.value.length, + items: items.slice( + (page.currentPage - 1) * page.pageSize, + page.currentPage * page.pageSize, + ), + }; + return new Promise((resolve) => { + resolve(result); + }); + }, + }, + response: { + total: 'totalCount', + list: 'items', + }, + }, toolbarConfig: { custom: true, export: true, @@ -157,6 +178,8 @@ const subGridColumns: VxeGridProps['columns'] = [ align: 'left', field: 'name', minWidth: 150, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:Name'), treeNode: true, }, @@ -164,12 +187,16 @@ const subGridColumns: VxeGridProps['columns'] = [ align: 'left', field: 'displayName', minWidth: 120, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:DisplayName'), }, { align: 'left', field: 'description', minWidth: 120, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:Description'), }, { @@ -177,37 +204,49 @@ const subGridColumns: VxeGridProps['columns'] = [ field: 'allowSubscriptionToClients', minWidth: 120, slots: { default: 'allowSubscriptionToClients' }, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:AllowSubscriptionToClients'), }, { align: 'left', field: 'template', minWidth: 150, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:Template'), }, { align: 'left', field: 'notificationLifetime', minWidth: 150, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:NotificationLifetime'), }, { align: 'left', field: 'notificationType', minWidth: 150, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:NotificationType'), }, { align: 'left', field: 'contentType', minWidth: 150, + resizable: true, + sortable: true, title: $t('Notifications.DisplayName:ContentType'), }, { align: 'left', field: 'providers', minWidth: 150, + resizable: true, slots: { default: 'providers' }, + sortable: true, title: $t('Notifications.DisplayName:Providers'), }, { @@ -220,10 +259,8 @@ const subGridColumns: VxeGridProps['columns'] = [ ]; const gridEvents: VxeGridListeners = { - pageChange(params) { - pageState.current = params.currentPage; - pageState.size = params.pageSize; - onPageChange(); + sortChange: () => { + gridApi.query(); }, }; @@ -249,7 +286,6 @@ async function onGet(input?: Record) { gridApi.setLoading(true); const groupRes = await getGroupsApi(input); const definitionRes = await getDefinitionsApi(input); - pageState.total = groupRes.items.length; definitionGroups.value = groupRes.items.map((group) => { const localizableGroup = deserialize(group.displayName); const definitions = definitionRes.items @@ -276,7 +312,7 @@ async function onGet(input?: Record) { }), }; }); - onPageChange(); + setTimeout(() => gridApi.reload(), 100); } finally { gridApi.setLoading(false); } @@ -288,21 +324,6 @@ async function onReset() { await onGet(input); } -function onPageChange() { - const items = definitionGroups.value.slice( - (pageState.current - 1) * pageState.size, - pageState.current * pageState.size, - ); - gridApi.setGridOptions({ - data: items, - pagerConfig: { - currentPage: pageState.current, - pageSize: pageState.size, - total: pageState.total, - }, - }); -} - function onCreate() { modalApi.setData({}); modalApi.open(); diff --git a/apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue b/apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue index e59bb8a3e..7d824f144 100644 --- a/apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue +++ b/apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue @@ -107,6 +107,7 @@ const gridOptions: VxeGridProps = { } }, minWidth: 50, + sortable: true, title: $t('Notifications.Notifications:Type'), }, { @@ -116,6 +117,7 @@ const gridOptions: VxeGridProps = { return cellValue ? formatToDateTime(cellValue) : ''; }, minWidth: 120, + sortable: true, title: $t('Notifications.Notifications:SendTime'), }, { @@ -144,8 +146,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; const { totalCount, items } = await getMyNotifilersApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -171,8 +175,9 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; @@ -184,6 +189,9 @@ const gridEvents: VxeGridListeners = { checkboxChange: (params) => { selectedKeys.value = params.records.map((record) => record.id); }, + sortChange: () => { + gridApi.query(); + }, }; const [Grid, gridApi] = useVbenVxeGrid({ diff --git a/apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue b/apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue index 9c32c2566..76d5f5bec 100644 --- a/apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue +++ b/apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue @@ -1,5 +1,5 @@ + + + + + + + + + + + + + + + {{ $t('workbench.header.notifier.title') }} + + {{ + $t('workbench.header.notifier.count', [notifierCount]) + }} + + + + diff --git a/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue new file mode 100644 index 000000000..8479356db --- /dev/null +++ b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue @@ -0,0 +1,124 @@ + + + + + + {{ title }} + + + + + + + {{ item.displayName }} + + + onMenuClick(menuKey.toString(), item) + " + > + + {{ $t('workbench.content.favoriteMenu.delete') }} + + + + + + + + + + diff --git a/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue new file mode 100644 index 000000000..3bd56cc3e --- /dev/null +++ b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue @@ -0,0 +1,137 @@ + + + + + + + + + ({{ slotProps.value }}) + + + + + + + + {{ item.displayName }} + + + + + + + + + diff --git a/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTodo.vue b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTodo.vue new file mode 100644 index 000000000..8751f8b79 --- /dev/null +++ b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTodo.vue @@ -0,0 +1,64 @@ + + + + + + {{ title }} + + + + + + + + + + {{ item.title }} + + + + + + + + {{ item.date }} + + + + + + + diff --git a/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTrends.vue b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTrends.vue new file mode 100644 index 000000000..48267cdb4 --- /dev/null +++ b/apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchTrends.vue @@ -0,0 +1,65 @@ + + + + + + {{ title }} + + + + + + + + + + {{ item.title }} + + + + + + + + {{ item.date }} + + + + + + + diff --git a/apps/vben5/packages/@abp/platform/src/components/workbench/index.vue b/apps/vben5/packages/@abp/platform/src/components/workbench/index.vue new file mode 100644 index 000000000..d531d34c2 --- /dev/null +++ b/apps/vben5/packages/@abp/platform/src/components/workbench/index.vue @@ -0,0 +1,218 @@ + + + + + + + {{ getWelcomeTitle }} + + 今日晴,20℃ - 32℃! + + + + + $emit('navTo', menu)" + /> + + + + + + + + + + + + + + + + + + + diff --git a/apps/vben5/packages/@abp/platform/src/components/workbench/types.ts b/apps/vben5/packages/@abp/platform/src/components/workbench/types.ts new file mode 100644 index 000000000..13dea780f --- /dev/null +++ b/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 }; diff --git a/apps/vben5/packages/@abp/platform/src/hooks/useMenuTransform.ts b/apps/vben5/packages/@abp/platform/src/hooks/useMenuTransform.ts index a7fdb9177..8c6ffc0b3 100644 --- a/apps/vben5/packages/@abp/platform/src/hooks/useMenuTransform.ts +++ b/apps/vben5/packages/@abp/platform/src/hooks/useMenuTransform.ts @@ -2,9 +2,12 @@ import type { RouteRecordStringComponent } from '@vben/types'; import type { MenuDto } from '../types'; +import { useUserStore } from '@vben/stores'; + import { listToTree } from '@abp/core'; export function useMenuTransform() { + const userStore = useUserStore(); function mapMetaString(meta: Record, key: string) { if (!meta[key]) { return undefined; @@ -30,6 +33,16 @@ export function useMenuTransform() { return Array.isArray(meta[key]) ? meta[key] : String(meta[key]).split(','); } function transformRoutes(menus: MenuDto[]): RouteRecordStringComponent[] { + const startupMenus = menus.filter((x) => x.startup); + if (startupMenus.length > 0) { + userStore.$patch((state) => { + state.userInfo && (state.userInfo.homePath = startupMenus[0]?.path); + }); + } else { + userStore.$patch((state) => { + state.userInfo && (state.userInfo.homePath = undefined); + }); + } const combMenus = menus.map((item) => { return { component: item.component.includes('BasicLayout') diff --git a/apps/vben5/packages/@abp/platform/src/index.ts b/apps/vben5/packages/@abp/platform/src/index.ts index 14fa9fe25..ca7a7275a 100644 --- a/apps/vben5/packages/@abp/platform/src/index.ts +++ b/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'; diff --git a/apps/vben5/packages/@abp/platform/src/locales/index.ts b/apps/vben5/packages/@abp/platform/src/locales/index.ts new file mode 100644 index 000000000..ccf0b963b --- /dev/null +++ b/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; +} diff --git a/apps/vben5/packages/@abp/platform/src/locales/langs/en-US/workbench.json b/apps/vben5/packages/@abp/platform/src/locales/langs/en-US/workbench.json new file mode 100644 index 000000000..63034d52b --- /dev/null +++ b/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" + } + } +} diff --git a/apps/vben5/packages/@abp/platform/src/locales/langs/zh-CN/workbench.json b/apps/vben5/packages/@abp/platform/src/locales/langs/zh-CN/workbench.json new file mode 100644 index 000000000..c2561e71d --- /dev/null +++ b/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": "待办事项" + } + } +} diff --git a/apps/vben5/packages/@abp/platform/src/types/favorites.ts b/apps/vben5/packages/@abp/platform/src/types/favorites.ts new file mode 100644 index 000000000..221f13f48 --- /dev/null +++ b/apps/vben5/packages/@abp/platform/src/types/favorites.ts @@ -0,0 +1,34 @@ +import type { AuditedEntityDto, IHasConcurrencyStamp } from '@abp/core'; + +interface UserFavoriteMenuDto extends AuditedEntityDto { + 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, +}; diff --git a/apps/vben5/packages/@abp/platform/src/types/index.ts b/apps/vben5/packages/@abp/platform/src/types/index.ts index 73954c6db..3277e6293 100644 --- a/apps/vben5/packages/@abp/platform/src/types/index.ts +++ b/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'; diff --git a/apps/vben5/packages/@abp/saas/src/components/editions/EditionTable.vue b/apps/vben5/packages/@abp/saas/src/components/editions/EditionTable.vue index 601b3ff8f..e8ed79ccd 100644 --- a/apps/vben5/packages/@abp/saas/src/components/editions/EditionTable.vue +++ b/apps/vben5/packages/@abp/saas/src/components/editions/EditionTable.vue @@ -21,6 +21,7 @@ import { DeleteOutlined, EditOutlined, EllipsisOutlined, + PlusOutlined, } from '@ant-design/icons-vue'; import { Button, Dropdown, Menu, message, Modal } from 'ant-design-vue'; @@ -65,6 +66,7 @@ const gridOptions: VxeGridProps = { { align: 'left', field: 'displayName', + sortable: true, title: $t('AbpSaas.DisplayName:EditionName'), }, { @@ -83,8 +85,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getPagedListApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -99,19 +103,23 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; const [EditionModal, modalApi] = useVbenModal({ connectedComponent: defineAsyncComponent(() => import('./EditionModal.vue')), }); -const [Grid, { query }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridEvents, gridOptions, @@ -145,7 +153,7 @@ const onDelete = (row: EditionDto) => { onOk: async () => { await deleteApi(row.id); message.success($t('AbpUi.DeletedSuccessfully')); - query(); + await gridApi.query(); }, title: $t('AbpUi.AreYouSure'), }); @@ -181,6 +189,7 @@ const onMenuClick = (row: EditionDto, info: MenuInfo) => { type="primary" v-access:code="[EditionsPermissions.Create]" @click="onCreate" + :icon="h(PlusOutlined)" > {{ $t('AbpSaas.NewEdition') }} @@ -233,7 +242,7 @@ const onMenuClick = (row: EditionDto, info: MenuInfo) => { - query()" /> + gridApi.query()" /> diff --git a/apps/vben5/packages/@abp/saas/src/components/tenants/TenantTable.vue b/apps/vben5/packages/@abp/saas/src/components/tenants/TenantTable.vue index 46d8cd856..e7973cee5 100644 --- a/apps/vben5/packages/@abp/saas/src/components/tenants/TenantTable.vue +++ b/apps/vben5/packages/@abp/saas/src/components/tenants/TenantTable.vue @@ -21,6 +21,7 @@ import { DeleteOutlined, EditOutlined, EllipsisOutlined, + PlusOutlined, } from '@ant-design/icons-vue'; import { Button, Dropdown, Menu, message, Modal } from 'ant-design-vue'; @@ -77,12 +78,14 @@ const gridOptions: VxeGridProps = { align: 'center', field: 'isActive', slots: { default: 'isActive' }, + sortable: true, title: $t('AbpSaas.DisplayName:IsActive'), width: 120, }, { align: 'left', field: 'name', + sortable: true, title: $t('AbpSaas.DisplayName:Name'), }, { @@ -107,8 +110,10 @@ const gridOptions: VxeGridProps = { keepSource: true, proxyConfig: { ajax: { - query: async ({ page }, formValues) => { + query: async ({ page, sort }, formValues) => { + const sorting = sort.order ? `${sort.field} ${sort.order}` : undefined; return await getPagedListApi({ + sorting, maxResultCount: page.pageSize, skipCount: (page.currentPage - 1) * page.pageSize, ...formValues, @@ -123,14 +128,18 @@ const gridOptions: VxeGridProps = { toolbarConfig: { custom: true, export: true, - // import: true, - refresh: true, + refresh: { + code: 'query', + }, zoom: true, }, }; const gridEvents: VxeGridListeners = { cellClick: () => {}, + sortChange: () => { + gridApi.query(); + }, }; const [TenantModal, modalApi] = useVbenModal({ connectedComponent: defineAsyncComponent(() => import('./TenantModal.vue')), @@ -146,7 +155,7 @@ const [TenantChangeDrawer, entityChangeDrawerApi] = useVbenDrawer({ const [TenantFeatureModal, tenantFeatureModalApi] = useVbenModal({ connectedComponent: FeatureModal, }); -const [Grid, { query }] = useVbenVxeGrid({ +const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridEvents, gridOptions, @@ -172,7 +181,7 @@ const onDelete = (row: TenantDto) => { onOk: async () => { await deleteApi(row.id); message.success($t('AbpUi.DeletedSuccessfully')); - query(); + await gridApi.query(); }, title: $t('AbpUi.AreYouSure'), }); @@ -213,6 +222,7 @@ const onMenuClick = (row: TenantDto, info: MenuInfo) => { type="primary" v-access:code="[TenantsPermissions.Create]" @click="onCreate" + :icon="h(PlusOutlined)" > {{ $t('AbpSaas.NewTenant') }} @@ -280,7 +290,10 @@ const onMenuClick = (row: TenantDto, info: MenuInfo) => { - query()" /> + gridApi.query()" + /> diff --git a/apps/vben5/packages/@abp/settings/src/components/definitions/SettingDefinitionTable.vue b/apps/vben5/packages/@abp/settings/src/components/definitions/SettingDefinitionTable.vue index c101d788b..86f8ace2b 100644 --- a/apps/vben5/packages/@abp/settings/src/components/definitions/SettingDefinitionTable.vue +++ b/apps/vben5/packages/@abp/settings/src/components/definitions/SettingDefinitionTable.vue @@ -5,12 +5,12 @@ import type { VbenFormProps } from '@vben/common-ui'; import type { SettingDefinitionDto } from '../../types/definitions'; -import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; +import { defineAsyncComponent, h, onMounted, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { $t } from '@vben/locales'; -import { useLocalization, useLocalizationSerializer } from '@abp/core'; +import { sortby, useLocalization, useLocalizationSerializer } from '@abp/core'; import { useVbenVxeGrid } from '@abp/ui'; import { DeleteOutlined, @@ -26,12 +26,7 @@ defineOptions({ name: 'SettingDefinitionTable', }); -const permissionGroups = ref([]); -const pageState = reactive({ - current: 1, - size: 10, - total: 0, -}); +const settingGroups = ref([]); const { Lr } = useLocalization(); const { deserialize } = useLocalizationSerializer(); @@ -42,7 +37,6 @@ const formOptions: VbenFormProps = { collapsed: false, handleReset: onReset, async handleSubmit(params) { - pageState.current = 1; await onGet(params); }, schema: [ @@ -65,12 +59,14 @@ const gridOptions: VxeGridProps = { align: 'left', field: 'name', minWidth: 150, + sortable: true, title: $t('AbpSettingManagement.DisplayName:Name'), }, { align: 'left', field: 'displayName', minWidth: 150, + sortable: true, title: $t('AbpSettingManagement.DisplayName:DisplayName'), }, { @@ -83,6 +79,30 @@ const gridOptions: VxeGridProps = { ], exportConfig: {}, keepSource: true, + proxyConfig: { + ajax: { + query: async ({ page, sort }) => { + let items = sortby(settingGroups.value, sort.field); + if (sort.order === 'desc') { + items = items.reverse(); + } + const result = { + totalCount: settingGroups.value.length, + items: items.slice( + (page.currentPage - 1) * page.pageSize, + page.currentPage * page.pageSize, + ), + }; + return new Promise((resolve) => { + resolve(result); + }); + }, + }, + response: { + total: 'totalCount', + list: 'items', + }, + }, toolbarConfig: { custom: true, export: true, @@ -92,10 +112,8 @@ const gridOptions: VxeGridProps = { }; const gridEvents: VxeGridListeners = { - pageChange(params) { - pageState.current = params.currentPage; - pageState.size = params.pageSize; - onPageChange(); + sortChange: () => { + gridApi.query(); }, }; @@ -115,15 +133,14 @@ async function onGet(input?: Record) { try { gridApi.setLoading(true); const { items } = await getListApi(input); - pageState.total = items.length; - permissionGroups.value = items.map((item) => { + settingGroups.value = items.map((item) => { const localizableString = deserialize(item.displayName); return { ...item, displayName: Lr(localizableString.resourceName, localizableString.name), }; }); - onPageChange(); + setTimeout(() => gridApi.reload(), 100); } finally { gridApi.setLoading(false); } @@ -135,21 +152,6 @@ async function onReset() { await onGet(input); } -function onPageChange() { - const items = permissionGroups.value.slice( - (pageState.current - 1) * pageState.size, - pageState.current * pageState.size, - ); - gridApi.setGridOptions({ - data: items, - pagerConfig: { - currentPage: pageState.current, - pageSize: pageState.size, - total: pageState.total, - }, - }); -} - function onCreate() { modalApi.setData({}); modalApi.open(); diff --git a/apps/vben5/packages/@abp/settings/src/components/index.ts b/apps/vben5/packages/@abp/settings/src/components/index.ts index aaa211b75..53dbe96f7 100644 --- a/apps/vben5/packages/@abp/settings/src/components/index.ts +++ b/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'; diff --git a/apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue b/apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue index 4849331c9..2caa47d4f 100644 --- a/apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue +++ b/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" diff --git a/apps/vben5/packages/@abp/settings/src/hooks/useDefineSettings.ts b/apps/vben5/packages/@abp/settings/src/hooks/useDefineSettings.ts index 628397795..502323bb3 100644 --- a/apps/vben5/packages/@abp/settings/src/hooks/useDefineSettings.ts +++ b/apps/vben5/packages/@abp/settings/src/hooks/useDefineSettings.ts @@ -67,7 +67,7 @@ export function useDefineSettings(): UserDefineSettingProvider { initlize, isTrue(name: string) { const value = getOrDefault(name, 'false'); - return value.toLowerCase() === 'true'; + return value?.toLowerCase() === 'true'; }, }; } diff --git a/apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoDrawer.vue b/apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoDrawer.vue index c33b9c114..715ffb614 100644 --- a/apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoDrawer.vue +++ b/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, diff --git a/apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoTable.vue b/apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoTable.vue index 23427239d..f656debe9 100644 --- a/apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoTable.vue +++ b/apps/vben5/packages/@abp/tasks/src/components/job-infos/JobInfoTable.vue @@ -1,5 +1,4 @@ + + + + + + + + diff --git a/apps/vben5/packages/@abp/wechat/src/components/index.ts b/apps/vben5/packages/@abp/wechat/src/components/index.ts new file mode 100644 index 000000000..8e84134b5 --- /dev/null +++ b/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'; diff --git a/apps/vben5/packages/@abp/wechat/src/components/settings/index.vue b/apps/vben5/packages/@abp/wechat/src/components/settings/index.vue new file mode 100644 index 000000000..e1f3e30a6 --- /dev/null +++ b/apps/vben5/packages/@abp/wechat/src/components/settings/index.vue @@ -0,0 +1,37 @@ + + + + + + + diff --git a/apps/vben5/packages/@abp/wechat/src/index.ts b/apps/vben5/packages/@abp/wechat/src/index.ts new file mode 100644 index 000000000..0ef464305 --- /dev/null +++ b/apps/vben5/packages/@abp/wechat/src/index.ts @@ -0,0 +1,2 @@ +export * from './api'; +export * from './components'; diff --git a/apps/vben5/packages/@abp/wechat/src/types/js-sdk.ts b/apps/vben5/packages/@abp/wechat/src/types/js-sdk.ts new file mode 100644 index 000000000..44031b6b5 --- /dev/null +++ b/apps/vben5/packages/@abp/wechat/src/types/js-sdk.ts @@ -0,0 +1,6 @@ +interface AgentConfigDto { + agentId: string; + corpId: string; +} + +export type { AgentConfigDto }; diff --git a/apps/vben5/packages/@abp/wechat/tsconfig.json b/apps/vben5/packages/@abp/wechat/tsconfig.json new file mode 100644 index 000000000..ce1a891fb --- /dev/null +++ b/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"] +} diff --git a/apps/vben5/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue b/apps/vben5/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue index f3eb6735a..9844c409d 100644 --- a/apps/vben5/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue +++ b/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 ?? {}); diff --git a/apps/vben5/pnpm-workspace.yaml b/apps/vben5/pnpm-workspace.yaml index 4acb3954f..5bd673e69 100644 --- a/apps/vben5/pnpm-workspace.yaml +++ b/apps/vben5/pnpm-workspace.yaml @@ -51,10 +51,12 @@ catalog: '@types/lodash.debounce': ^4.0.9 '@types/lodash.get': ^4.4.9 '@types/lodash.groupby': ^4.6.9 + '@types/lodash.isdate': ^4.0.9 '@types/lodash.isequal': ^4.5.8 '@types/lodash.isnumber': ^3.0.9 '@types/lodash.merge': ^4.6.9 '@types/lodash.set': ^4.3.9 + '@types/lodash.sortby': ^4.7.9 '@types/node': ^22.15.3 '@types/nprogress': ^0.2.3 '@types/postcss-import': ^14.0.3 @@ -73,6 +75,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 @@ -87,6 +90,7 @@ catalog: codemirror: ^5.65.3 commitlint-plugin-function-rules: ^4.0.1 consola: ^3.4.2 + cropperjs: ^1.5.12 cross-env: ^7.0.3 cspell: ^8.19.3 cssnano: ^7.0.6 @@ -130,10 +134,12 @@ catalog: lodash.debounce: ^4.0.8 lodash.get: ^4.4.2 lodash.groupby: ^4.6.0 + lodash.isdate: ^4.0.1 lodash.isequal: ^4.5.0 lodash.isnumber: ^3.0.3 lodash.merge: ^4.6.2 lodash.set: ^4.3.2 + lodash.sortby: ^4.7.0 lucide-vue-next: ^0.507.0 medium-zoom: ^1.1.0 naive-ui: ^2.41.0 @@ -205,8 +211,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 diff --git a/apps/vue/src/router/routes/basic.ts b/apps/vue/src/router/routes/basic.ts index a8fcb220e..c18196a75 100644 --- a/apps/vue/src/router/routes/basic.ts +++ b/apps/vue/src/router/routes/basic.ts @@ -10,7 +10,7 @@ import { // 404 on a page export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { path: '/:path(.*)*', - name: PAGE_NOT_FOUND_NAME, + name: 'ErrorPage', component: LAYOUT, meta: { title: 'ErrorPage', diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index 708d441b2..e8de430b4 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -795,8 +795,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web.IdentityServer", "modules\account\LINGYUN.Abp.Account.Web.IdentityServer\LINGYUN.Abp.Account.Web.IdentityServer.csproj", "{0FF0A04C-B580-4A56-9171-CF2988B5DE5A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Emailing", "modules\account\LINGYUN.Abp.Account.Emailing\LINGYUN.Abp.Account.Emailing.csproj", "{29A87F09-CC03-4DB8-B584-98073AB50AA4}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web.OpenIddict", "modules\account\LINGYUN.Abp.Account.Web.OpenIddict\LINGYUN.Abp.Account.Web.OpenIddict.csproj", "{F810C8A8-1256-440F-BAAF-7F3588291963}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.HttpApi.Client", "modules\account\LINGYUN.Abp.Account.HttpApi.Client\LINGYUN.Abp.Account.HttpApi.Client.csproj", "{FFBE3EC6-F11B-4B7C-9BAF-AFBBB12BEF59}" @@ -849,6 +847,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.Pdf.Li EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Exporter.Pdf.SpireLib", "framework\exporter\LINGYUN.Abp.Exporter.Pdf.SpireLib\LINGYUN.Abp.Exporter.Pdf.SpireLib.csproj", "{9950639D-AA4C-4FF1-A65E-9790EB561C8A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Security", "modules\account\LINGYUN.Abp.Account.Security\LINGYUN.Abp.Account.Security.csproj", "{5FA85E8E-3276-43DF-CC93-6A9847905166}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.AspNetCore.MultiTenancy", "framework\tenants\LINGYUN.Abp.AspNetCore.MultiTenancy\LINGYUN.Abp.AspNetCore.MultiTenancy.csproj", "{AEEA81D6-B282-93CF-862B-9FCF1A5052F7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -2103,10 +2105,6 @@ Global {0FF0A04C-B580-4A56-9171-CF2988B5DE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FF0A04C-B580-4A56-9171-CF2988B5DE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU {0FF0A04C-B580-4A56-9171-CF2988B5DE5A}.Release|Any CPU.Build.0 = Release|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29A87F09-CC03-4DB8-B584-98073AB50AA4}.Release|Any CPU.Build.0 = Release|Any CPU {F810C8A8-1256-440F-BAAF-7F3588291963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F810C8A8-1256-440F-BAAF-7F3588291963}.Debug|Any CPU.Build.0 = Debug|Any CPU {F810C8A8-1256-440F-BAAF-7F3588291963}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -2203,6 +2201,14 @@ Global {9950639D-AA4C-4FF1-A65E-9790EB561C8A}.Debug|Any CPU.Build.0 = Debug|Any CPU {9950639D-AA4C-4FF1-A65E-9790EB561C8A}.Release|Any CPU.ActiveCfg = Release|Any CPU {9950639D-AA4C-4FF1-A65E-9790EB561C8A}.Release|Any CPU.Build.0 = Release|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FA85E8E-3276-43DF-CC93-6A9847905166}.Release|Any CPU.Build.0 = Release|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2588,7 +2594,6 @@ Global {AE00DA82-B33A-CAF7-D9CD-D5E26608741B} = {9D1302BE-3886-49F8-B0CD-35D2AC1E5A37} {F9A0D88F-53AE-4AC7-8E15-163C34386E7C} = {9E72FEB9-A626-4312-892B-CDD043879758} {0FF0A04C-B580-4A56-9171-CF2988B5DE5A} = {9E72FEB9-A626-4312-892B-CDD043879758} - {29A87F09-CC03-4DB8-B584-98073AB50AA4} = {9E72FEB9-A626-4312-892B-CDD043879758} {F810C8A8-1256-440F-BAAF-7F3588291963} = {9E72FEB9-A626-4312-892B-CDD043879758} {FFBE3EC6-F11B-4B7C-9BAF-AFBBB12BEF59} = {9E72FEB9-A626-4312-892B-CDD043879758} {C95DE287-9D21-4DCD-9281-A060B6D99774} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} @@ -2615,6 +2620,8 @@ Global {546E4417-5409-40F4-A125-E08329DD82BB} = {A4633711-7FB6-411A-8D08-BB9A0A778046} {738A72FB-ED83-4127-AA3B-59BF90635F8F} = {A4633711-7FB6-411A-8D08-BB9A0A778046} {9950639D-AA4C-4FF1-A65E-9790EB561C8A} = {A4633711-7FB6-411A-8D08-BB9A0A778046} + {5FA85E8E-3276-43DF-CC93-6A9847905166} = {9E72FEB9-A626-4312-892B-CDD043879758} + {AEEA81D6-B282-93CF-862B-9FCF1A5052F7} = {A5543E56-DA53-494D-A531-DA75091D46FF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/cleanup-logs.bat b/aspnet-core/cleanup-logs.bat index ef5471574..5ffd61025 100644 --- a/aspnet-core/cleanup-logs.bat +++ b/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 diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/FodyWeavers.xml b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN.Abp.AspNetCore.Auditing.csproj b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN.Abp.AspNetCore.Auditing.csproj new file mode 100644 index 000000000..4589f07e1 --- /dev/null +++ b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN.Abp.AspNetCore.Auditing.csproj @@ -0,0 +1,20 @@ + + + + + + + net9.0 + LINGYUN.Abp.AspNetCore.Auditing + LINGYUN.Abp.AspNetCore.Auditing + false + false + false + + + + + + + + diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingHeaderOptions.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingHeaderOptions.cs new file mode 100644 index 000000000..647ea909a --- /dev/null +++ b/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 +{ + /// + /// 是否在审计日志中记录Http请求头,默认: true + /// + public bool IsEnabled { get; set; } + /// + /// 要记录的Http请求头 + /// + public IList HttpHeaders { get; } + public AbpAspNetCoreAuditingHeaderOptions() + { + IsEnabled = true; + HttpHeaders = new List(); + } +} diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingModule.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AbpAspNetCoreAuditingModule.cs new file mode 100644 index 000000000..119d69635 --- /dev/null +++ b/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(options => + { + options.Contributors.Add(new AspNetCoreRecordHeaderAuditLogContributor()); + }); + } +} diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AspNetCoreRecordHeaderAuditLogContributor.cs b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/LINGYUN/Abp/AspNetCore/Auditing/AspNetCoreRecordHeaderAuditLogContributor.cs new file mode 100644 index 000000000..67f4a59d3 --- /dev/null +++ b/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>(); + if (!options.Value.IsEnabled) + { + return; + } + + var httpContext = context.ServiceProvider.GetRequiredService().HttpContext; + if (httpContext == null) + { + return; + } + + if (context.AuditInfo.HasProperty(HttpHeaderRecordKey)) + { + return; + } + + var headerRcords = new Dictionary(); + 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); + } +} diff --git a/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/README.md b/aspnet-core/framework/auditing/LINGYUN.Abp.AspNetCore.Auditing/README.md new file mode 100644 index 000000000..de6175010 --- /dev/null +++ b/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请求头列表 diff --git a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Sms.Tencent/LINGYUN/Abp/Sms/Tencent/TencentCloudSmsSender.cs b/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Sms.Tencent/LINGYUN/Abp/Sms/Tencent/TencentCloudSmsSender.cs index 501283863..0ffa5d62d 100644 --- a/aspnet-core/framework/cloud-tencent/LINGYUN.Abp.Sms.Tencent/LINGYUN/Abp/Sms/Tencent/TencentCloudSmsSender.cs +++ b/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 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(); diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj index 3a4a9a829..2f50762b8 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.csproj @@ -10,10 +10,9 @@ false false false - - Cap分布式事件总线 true - $(SolutionDir)framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.xml + Cap分布式事件总线 + diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPBootstrapper.cs b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPBootstrapper.cs new file mode 100644 index 000000000..55d753571 --- /dev/null +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPBootstrapper.cs @@ -0,0 +1,156 @@ +using DotNetCore.CAP; +using DotNetCore.CAP.Internal; +using DotNetCore.CAP.Persistence; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.EventBus.CAP; + +public class AbpCAPBootstrapper : IBootstrapper +{ + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + private CancellationTokenSource _cts; + private bool _disposed; + private IEnumerable _processors = default!; + + public bool IsStarted => !_cts?.IsCancellationRequested ?? false; + + public AbpCAPBootstrapper(IServiceProvider serviceProvider, ILogger logger) + { + _serviceProvider = serviceProvider; + _logger = logger; + } + + public async Task BootstrapAsync(CancellationToken cancellationToken = default) + { + if (_cts != null) + { + _logger.LogInformation("### CAP background task is already started!"); + + return; + } + + _logger.LogDebug("### CAP background task is starting."); + + _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + CheckRequirement(); + + _processors = _serviceProvider.GetServices(); + + try + { + await _serviceProvider.GetRequiredService().InitializeAsync(_cts.Token).ConfigureAwait(false); + } + catch (Exception e) + { + if (e is InvalidOperationException) throw; + _logger.LogError(e, "Initializing the storage structure failed!"); + } + + _cts.Token.Register(() => + { + _logger.LogDebug("### CAP background task is stopping."); + + + foreach (var item in _processors) + try + { + item.Dispose(); + } + catch (OperationCanceledException ex) + { + _logger.LogWarning(ex, $"Expected an OperationCanceledException, but found '{ex.Message}'."); + } + }); + + await BootstrapCoreAsync().ConfigureAwait(false); + + _disposed = false; + _logger.LogInformation("### CAP started!"); + } + + protected virtual async Task BootstrapCoreAsync() + { + foreach (var item in _processors) + { + try + { + _cts!.Token.ThrowIfCancellationRequested(); + + await item.Start(_cts!.Token); + } + catch (OperationCanceledException) + { + // ignore + } + catch (Exception ex) + { + _logger.LogError(ex, "Starting the processors throw an exception."); + } + } + } + + public virtual void Dispose() + { + if (_disposed) return; + + _cts?.Cancel(); + _cts?.Dispose(); + _cts = null; + _disposed = true; + } + + public virtual async Task ExecuteAsync(CancellationToken stoppingToken) + { + await BootstrapAsync(stoppingToken).ConfigureAwait(false); + } + + public virtual Task StopAsync(CancellationToken cancellationToken) + { + _cts?.Cancel(); + + return Task.CompletedTask; + } + + private void CheckRequirement() + { + var marker = _serviceProvider.GetService(); + if (marker == null) + throw new InvalidOperationException( + "AddCap() must be added on the service collection. eg: services.AddCap(...)"); + + var messageQueueMarker = _serviceProvider.GetService(); + if (messageQueueMarker == null) + throw new InvalidOperationException( + "You must be config transport provider for CAP!" + Environment.NewLine + + "==================================================================================" + + Environment.NewLine + + "======== eg: services.AddCap( options => { options.UseRabbitMQ(...) }); ========" + + Environment.NewLine + + "=================================================================================="); + + var databaseMarker = _serviceProvider.GetService(); + if (databaseMarker == null) + throw new InvalidOperationException( + "You must be config storage provider for CAP!" + Environment.NewLine + + "===================================================================================" + + Environment.NewLine + + "======== eg: services.AddCap( options => { options.UseSqlServer(...) }); ========" + + Environment.NewLine + + "==================================================================================="); + } + + public ValueTask DisposeAsync() + { + Dispose(); + + return ValueTask.CompletedTask; + } +} diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs index 39310dfd5..25b7fa275 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN/Abp/EventBus/CAP/AbpCAPEventBusModule.cs @@ -1,8 +1,12 @@ using DotNetCore.CAP; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp; using Volo.Abp.EventBus; using Volo.Abp.Modularity; +using Volo.Abp.Threading; namespace LINGYUN.Abp.EventBus.CAP; @@ -12,6 +16,7 @@ namespace LINGYUN.Abp.EventBus.CAP; [DependsOn(typeof(AbpEventBusModule))] public class AbpCAPEventBusModule : AbpModule { + private readonly CancellationTokenSource _cancellationTokenSource = new(); /// /// ConfigureServices /// @@ -48,4 +53,24 @@ public class AbpCAPEventBusModule : AbpModule } }); } + + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(() => OnPreApplicationInitializationAsync(context)); + } + + public async override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) + { + await context + .ServiceProvider + .GetRequiredService() + .BootstrapAsync(_cancellationTokenSource.Token); + } + + public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) + { + _cancellationTokenSource.Cancel(); + + return Task.CompletedTask; + } } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs index 445567aff..81ae8168e 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs @@ -2,7 +2,9 @@ using DotNetCore.CAP.Internal; using DotNetCore.CAP.Serialization; using LINGYUN.Abp.EventBus.CAP; +using Microsoft.Extensions.Hosting; using System; +using System.Collections.Generic; namespace Microsoft.Extensions.DependencyInjection; @@ -23,6 +25,33 @@ public static class ServiceCollectionExtensions // 替换为自己的实现 services.AddSingleton(); services.AddSingleton(); + + // 移除默认CAP启动接口 + services.RemoveAll(service => + { + if (service.ServiceType.IsAssignableFrom(typeof(IBootstrapper))) + { + return true; + } + // 默认Bootstrapper + if (service.ImplementationType != null && + service.ImplementationType.IsAssignableTo(typeof(IBootstrapper))) + { + return true; + } + // 默认Bootstrapper HostService + if (service.ImplementationFactory != null && + service.ImplementationFactory.Method.ReturnType.IsAssignableTo(typeof(IBootstrapper))) + { + return true; + } + + return false; + }); + // 使用重写的接口,不使用BackgroundService + services.AddSingleton(); + services.AddSingleton(sp => sp.GetRequiredService()); + return services; } } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.xml b/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.xml deleted file mode 100644 index 35b6776bb..000000000 --- a/aspnet-core/framework/common/LINGYUN.Abp.EventBus.CAP/framework/common/LINGYUN.Abp.EventBus.CAP/LINGYUN.Abp.EventBus.CAP.xml +++ /dev/null @@ -1,397 +0,0 @@ - - - - LINGYUN.Abp.EventBus.CAP - - - - - 消费者查找器 - - - - - CAP配置 - - - - - Abp分布式事件配置 - - - - - 服务提供者 - - - - - Creates a new . - - - - - 查找消费者集合 - - - - - - - 获取事件处理器集合 - - - - - - - - AbpCAPEventBusModule - - - - - ConfigureServices - - - - - - 过期消息清理配置项 - - - - - 发布消息处理失败通知 - default: false - - - - - AbpECAPExecutionFailedException - - - - - MessageType - - - - - Message - - - - - constructor - - - - - - - constructor - - - - - - - - constructor - - - - - - - - - CAP消息扩展 - - - - - 尝试获取消息标头中的租户标识 - - - - - - - - 获取消息标头中的租户标识 - - - - - - - 尝试获取消息标头中的链路标识 - - - - - - - - 获取消息标头中的链路标识 - - - - - - - 重写 ISubscribeInvoker 实现 Abp 租户集成 - - - - - AbpCAPSubscribeInvoker - - - - - - - - - - 调用订阅者方法 - - - - - - - - - - - - - - - - - - 获取事件处理类实例 - - - - - - - - 通过给定的类型实例与参数调用订阅者方法 - - - - - - - - - CAP分布式事件总线 - - - - - CAP消息发布接口 - - - - - 自定义事件注册接口 - - - - - 本地事件处理器工厂对象集合 - - - - - 本地事件集合 - - - - - 当前用户 - - - - - 当前客户端 - - - - - typeof - - - - - 取消令牌 - - - - - constructor - - - - - - - - - - - - - - - - - - - - 订阅事件 - - - - - - - - 退订事件 - - 事件类型 - - - - - 退订事件 - - 事件类型 - 事件处理器 - - - - 退订事件 - - 事件类型 - 事件处理器工厂 - - - - 退订所有事件 - - 事件类型 - - - - 发布事件 - - 事件类型 - 事件数据对象 - - - - - 获取事件处理器工厂列表 - - - - - - - 自定义事件订阅者 - - - - - 订阅事件 - - - - - - - 取消订阅 - - - - - - - Executes the configured method on . This can be used whether or not - the configured method is asynchronous. - - - Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than - ExecuteAsync if you know at compile time what the return type is, because then you can directly - "await" that value (via a cast), and then the generated code will be able to reference the - resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated - code will have to treat the resulting awaitable as a boxed object, because it doesn't know at - compile time what type it would be. - - The object whose method is to be executed. - Parameters to pass to the method. - The method return value. - - - - Executes the configured method on . This can only be used if the configured - method is asynchronous. - - - If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, - which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations - as compared with using Execute and then using "await" on the result value typecasted to the known - awaitable type. The possible extra heap allocations are for: - 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally - it's a reference type, and you normally create a new instance per call). - 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance - of it, and if it is, it will have to be boxed so the calling code can reference it as an object). - 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling - code doesn't know what type it's going to be). - - The object whose method is to be executed. - Parameters to pass to the method. - An object that you can "await" to get the method return value. - - - - Provides a common awaitable structure that can - return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an - application-defined custom awaitable. - - - - - Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying - an for mapping instances of that type to a C# awaitable. - - - The main design goal here is to avoid taking a compile-time dependency on - FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references - to FSharp types have to be constructed dynamically at runtime. - - - - - CAP ServiceCollectionExtensions - - - - - Adds and configures the consistence services for the consistency. - - - - - - - diff --git a/aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs b/aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs index ab95964e9..863470a46 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs +++ b/aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/AbpIP2RegionModule.cs @@ -29,7 +29,7 @@ public class AbpIP2RegionModule : AbpModule Configure(options => { - options.IPLocationResolvers.Add(new IP2RegionIPLocationResolveContributorBase()); + options.IPLocationResolvers.Add(new IP2RegionIPLocationResolveContributor()); }); } } diff --git a/aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributorBase.cs b/aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributor.cs similarity index 96% rename from aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributorBase.cs rename to aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributor.cs index 0cc4c2b44..4abb90200 100644 --- a/aspnet-core/framework/common/LINGYUN.Abp.IP2Region/LINGYUN/Abp/IP2Region/IP2RegionIPLocationResolveContributorBase.cs +++ b/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; diff --git a/aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/System/Linq/Expressions/ObjectQueryableExtensions.cs b/aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/System/Linq/Expressions/ObjectQueryableExtensions.cs index a7a2baeb6..b31c2bf9e 100644 --- a/aspnet-core/framework/dynamic-queryable/LINGYUN.Linq.Dynamic.Queryable/System/Linq/Expressions/ObjectQueryableExtensions.cs +++ b/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(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: 非空字段此处返回默认值 diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs index 74a2d6889..149d3e5c1 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs @@ -17,7 +17,7 @@ public class AppDescriptor /// /// 应用token /// - public string AppToken { get; set; } + public string? AppToken { get; set; } /// /// 签名有效时间 /// 单位: 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; diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xml b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xml new file mode 100644 index 000000000..5d6962159 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xsd b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN.Abp.AspNetCore.MultiTenancy.csproj b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN.Abp.AspNetCore.MultiTenancy.csproj new file mode 100644 index 000000000..89b162615 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN.Abp.AspNetCore.MultiTenancy.csproj @@ -0,0 +1,21 @@ + + + + + + + net9.0 + LINGYUN.Abp.AspNetCore.MultiTenancy + LINGYUN.Abp.AspNetCore.MultiTenancy + false + false + false + Library + enable + + + + + + + diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs new file mode 100644 index 000000000..61668fc97 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; +using VoloAbpAspNetCoreMultiTenancyModule = Volo.Abp.AspNetCore.MultiTenancy.AbpAspNetCoreMultiTenancyModule; + +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +[DependsOn(typeof(VoloAbpAspNetCoreMultiTenancyModule))] +public class AbpAspNetCoreMultiTenancyModule : AbpModule +{ + +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyResolveOptions.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyResolveOptions.cs new file mode 100644 index 000000000..1b7d0abc8 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyResolveOptions.cs @@ -0,0 +1,13 @@ +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +public class AbpAspNetCoreMultiTenancyResolveOptions +{ + /// + /// 仅解析域名中的租户, 默认: true + /// + public bool OnlyResolveDomain { get; set; } + public AbpAspNetCoreMultiTenancyResolveOptions() + { + OnlyResolveDomain = true; + } +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyOptionsExtensions.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyOptionsExtensions.cs new file mode 100644 index 000000000..d78531af5 --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyOptionsExtensions.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +public static class AbpMultiTenancyOptionsExtensions +{ + public static void AddOnlyDomainTenantResolver(this AbpTenantResolveOptions options, string domainFormat) + { + options.TenantResolvers.InsertAfter( + r => r is CurrentUserTenantResolveContributor, + new OnlyDomainTenantResolveContributor(domainFormat) + ); + } +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/OnlyDomainTenantResolveContributor.cs b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/OnlyDomainTenantResolveContributor.cs new file mode 100644 index 000000000..b272971ff --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/LINGYUN/Abp/AspNetCore/MultiTenancy/OnlyDomainTenantResolveContributor.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Net; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Text.Formatting; + +namespace LINGYUN.Abp.AspNetCore.MultiTenancy; + +public class OnlyDomainTenantResolveContributor : HttpTenantResolveContributorBase +{ + public const string ContributorName = "Domain"; + + public override string Name => ContributorName; + + private static readonly string[] ProtocolPrefixes = { "http://", "https://" }; + + private readonly string _domainFormat; + + public OnlyDomainTenantResolveContributor(string domainFormat) + { + _domainFormat = domainFormat.RemovePreFix(ProtocolPrefixes); + } + + protected override Task GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) + { + if (!httpContext.Request.Host.HasValue) + { + return Task.FromResult(null); + } + + var options = httpContext.RequestServices.GetRequiredService>(); + if (options.Value.OnlyResolveDomain) + { + // 仅仅解析域名, 如果请求的是IP地址, 则不使用这个解析贡献者 + if (IPAddress.TryParse(httpContext.Request.Host.Host, out var _)) + { + return Task.FromResult(null); + } + } + + var hostName = httpContext.Request.Host.Value.RemovePreFix(ProtocolPrefixes); + var extractResult = FormattedStringValueExtracter.Extract(hostName, _domainFormat, ignoreCase: true); + + context.Handled = true; + + return Task.FromResult(extractResult.IsMatch ? extractResult.Matches[0].Value : null); + } +} diff --git a/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json new file mode 100644 index 000000000..8ac6471cc --- /dev/null +++ b/aspnet-core/framework/tenants/LINGYUN.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "LINGYUN.Abp.AspNetCore.MultiTenancy": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:61811;http://localhost:61812" + } + } +} \ No newline at end of file diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs index 504f8ec3c..fc86054cc 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/Models/BatchJobResultEvent.cs @@ -4,7 +4,7 @@ using Volo.Abp.EventBus; namespace LINGYUN.Abp.WeChat.Work.Common.Messages.Models; /// -/// 进入应用事件 +/// 异步任务完成事件 /// [EventName("batch_job_result")] public class BatchJobResultEvent : WeChatWorkEventMessage diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs index 4caaba554..b4d337b00 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkEventResolveContributor.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; /// -/// 微信公众号事件处理器 +/// 企业微信事件处理器 /// public class WeChatWorkEventResolveContributor : WeChatWorkMessageResolveContributorBase { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs index bd358de45..0a5be5f72 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Common/LINGYUN/Abp/WeChat/Work/Common/Messages/WeChatWorkMessageResolveContributor.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace LINGYUN.Abp.WeChat.Work.Common.Messages; /// -/// 微信公众号消息处理器 +/// 企业微信消息处理器 /// public class WeChatWorkMessageResolveContributor : WeChatWorkMessageResolveContributorBase { diff --git a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs index af5ef347c..3c078f657 100644 --- a/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs +++ b/aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Work.Handlers/LINGYUN/Abp/WeChat/Work/Handlers/Messages/WeChatWorkEventEventHandler.cs @@ -21,6 +21,7 @@ public class WeChatWorkEventEventHandler : IDistributedEventHandler>, IDistributedEventHandler>, IDistributedEventHandler>, + IDistributedEventHandler>, IDistributedEventHandler>, IDistributedEventHandler>, IDistributedEventHandler>, @@ -113,6 +114,11 @@ public class WeChatWorkEventEventHandler : await _messageHandler.HandleEventAsync(eventData.Event); } + public async virtual Task HandleEventAsync(WeChatWorkEventMessageEto eventData) + { + await _messageHandler.HandleEventAsync(eventData.Event); + } + public async virtual Task HandleEventAsync(WeChatWorkEventMessageEto eventData) { await _messageHandler.HandleEventAsync(eventData.Event); diff --git a/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.csproj b/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.csproj index 37733ad57..cafad2948 100644 --- a/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.csproj +++ b/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.csproj @@ -12,7 +12,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20250813012035_Upgrade-Abp-Framework-To-9.3.1.Designer.cs b/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20250813012035_Upgrade-Abp-Framework-To-9.3.1.Designer.cs new file mode 100644 index 000000000..503871227 --- /dev/null +++ b/aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20250813012035_Upgrade-Abp-Framework-To-9.3.1.Designer.cs @@ -0,0 +1,5470 @@ +// +using System; +using LY.MicroService.Applications.Single.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; + +#nullable disable + +namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migrations +{ + [DbContext(typeof(SingleMigrationsDbContext))] + [Migration("20250813012035_Upgrade-Abp-Framework-To-9.3.1")] + partial class UpgradeAbpFrameworkTo931 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.MySql) + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("LINGYUN.Abp.DataProtectionManagement.EntityEnumInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)") + .HasColumnName("DisplayName"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)") + .HasColumnName("Name"); + + b.Property("PropertyInfoId") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasColumnName("Value"); + + b.HasKey("Id"); + + b.HasIndex("PropertyInfoId", "Name"); + + b.ToTable("AbpAuthEntityEnums", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.DataProtectionManagement.EntityPropertyInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)") + .HasColumnName("DisplayName"); + + b.Property("JavaScriptType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)") + .HasColumnName("JavaScriptType"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)") + .HasColumnName("Name"); + + b.Property("TypeFullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)") + .HasColumnName("TypeFullName"); + + b.Property("TypeInfoId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("TypeInfoId", "TypeFullName"); + + b.ToTable("AbpAuthEntityProperties", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.DataProtectionManagement.EntityTypeInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)") + .HasColumnName("DisplayName"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("ExtraProperties"); + + b.Property("IsAuditEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("char(36)") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)") + .HasColumnName("Name"); + + b.Property("TypeFullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)") + .HasColumnName("TypeFullName"); + + b.HasKey("Id"); + + b.HasIndex("TypeFullName"); + + b.ToTable("AbpAuthEntitites", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.DataProtectionManagement.OrganizationUnitEntityRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessedProperties") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("AccessedProperties"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("EntityTypeFullName") + .HasColumnType("longtext"); + + b.Property("EntityTypeId") + .HasColumnType("char(36)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("ExtraProperties"); + + b.Property("FilterGroup") + .HasColumnType("longtext") + .HasColumnName("FilterGroup"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("char(36)") + .HasColumnName("LastModifierId"); + + b.Property("Operation") + .HasColumnType("int"); + + b.Property("OrgCode") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)") + .HasColumnName("OrgCode"); + + b.Property("OrgId") + .HasColumnType("char(36)"); + + b.Property("TenantId") + .HasColumnType("char(36)") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("EntityTypeId"); + + b.ToTable("AbpAuthOrganizationUnitEntityRules", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.DataProtectionManagement.RoleEntityRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessedProperties") + .HasMaxLength(512) + .HasColumnType("varchar(512)") + .HasColumnName("AccessedProperties"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("EntityTypeFullName") + .HasColumnType("longtext"); + + b.Property("EntityTypeId") + .HasColumnType("char(36)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("ExtraProperties"); + + b.Property("FilterGroup") + .HasColumnType("longtext") + .HasColumnName("FilterGroup"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("char(36)") + .HasColumnName("LastModifierId"); + + b.Property("Operation") + .HasColumnType("int"); + + b.Property("RoleId") + .HasColumnType("char(36)"); + + b.Property("RoleName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)") + .HasColumnName("RoleName"); + + b.Property("TenantId") + .HasColumnType("char(36)") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.HasIndex("EntityTypeId"); + + b.ToTable("AbpAuthRoleEntityRules", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.DataProtectionManagement.SubjectStrategy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("ExtraProperties"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("char(36)") + .HasColumnName("LastModifierId"); + + b.Property("Strategy") + .HasColumnType("int"); + + b.Property("SubjectId") + .HasMaxLength(64) + .HasColumnType("varchar(64)") + .HasColumnName("SubjectId"); + + b.Property("SubjectName") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)") + .HasColumnName("SubjectName"); + + b.Property("TenantId") + .HasColumnType("char(36)") + .HasColumnName("TenantId"); + + b.HasKey("Id"); + + b.ToTable("AbpAuthSubjectStrategys", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.Demo.Authors.Author", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BirthDate") + .HasColumnType("datetime(6)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("char(36)") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)") + .HasColumnName("DeletionTime"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("ExtraProperties"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("char(36)") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("ShortBio") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("Demo_Authors", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.Demo.Books.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AuthorId") + .HasColumnType("char(36)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)") + .HasColumnName("ConcurrencyStamp"); + + b.Property("CreationTime") + .HasColumnType("datetime(6)") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("CreatorId"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("ExtraProperties"); + + b.Property("LastModificationTime") + .HasColumnType("datetime(6)") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("char(36)") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("Price") + .HasColumnType("float"); + + b.Property("PublishDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.ToTable("Demo_Books", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.Demo.Books.BookAuth", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("EntityId") + .HasMaxLength(64) + .HasColumnType("char(64)") + .HasColumnName("EntityId"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("varchar(128)") + .HasColumnName("EntityType"); + + b.Property("OrganizationUnit") + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasColumnName("OrganizationUnit"); + + b.Property("Role") + .HasMaxLength(32) + .HasColumnType("varchar(32)") + .HasColumnName("Role"); + + b.Property("TenantId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("EntityId"); + + b.HasIndex("OrganizationUnit"); + + b.HasIndex("Role"); + + b.ToTable("Demo_BooksAuths", (string)null); + }); + + modelBuilder.Entity("LINGYUN.Abp.Gdpr.GdprInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property
+ {{ item.title }} +