├── .coderabbit.yaml ├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .env.example ├── .gitattributes ├── .gitignore ├── .php-cs-fixer.php ├── .phpstorm.meta.php ├── Dockerfile ├── LICENSE ├── README-en.md ├── README.md ├── app ├── Command │ └── UpdateCommand.php ├── Exception │ ├── BusinessException.php │ ├── Handler │ │ ├── AbstractHandler.php │ │ ├── AppExceptionHandler.php │ │ ├── BusinessExceptionHandler.php │ │ ├── JwtExceptionHandler.php │ │ ├── ModeNotFoundHandler.php │ │ ├── UnauthorizedExceptionHandler.php │ │ └── ValidationExceptionHandler.php │ └── JwtInBlackException.php ├── Http │ ├── Admin │ │ ├── Controller │ │ │ ├── AbstractController.php │ │ │ ├── AttachmentController.php │ │ │ ├── Logstash │ │ │ │ ├── UserLoginLogController.php │ │ │ │ └── UserOperationLogController.php │ │ │ ├── PassportController.php │ │ │ ├── Permission │ │ │ │ ├── MenuController.php │ │ │ │ ├── RoleController.php │ │ │ │ └── UserController.php │ │ │ └── PermissionController.php │ │ ├── Middleware │ │ │ └── PermissionMiddleware.php │ │ ├── Request │ │ │ ├── PassportLoginRequest.php │ │ │ ├── Permission │ │ │ │ ├── BatchGrantPermissionsForRoleRequest.php │ │ │ │ ├── BatchGrantRolesForUserRequest.php │ │ │ │ ├── MenuRequest.php │ │ │ │ ├── PermissionRequest.php │ │ │ │ ├── RoleRequest.php │ │ │ │ └── UserRequest.php │ │ │ └── UploadRequest.php │ │ ├── Subscriber │ │ │ └── Logstash │ │ │ │ ├── UserLoginSubscriber.php │ │ │ │ └── UserOperationSubscriber.php │ │ └── Vo │ │ │ └── PassportLoginVo.php │ ├── Api │ │ ├── Controller │ │ │ └── V1 │ │ │ │ └── UserController.php │ │ ├── Middleware │ │ │ └── TokenMiddleware.php │ │ └── Request │ │ │ └── V1 │ │ │ └── UserRequest.php │ ├── Common │ │ ├── Controller │ │ │ └── AbstractController.php │ │ ├── Event │ │ │ └── RequestOperationEvent.php │ │ ├── Middleware │ │ │ ├── AccessTokenMiddleware.php │ │ │ ├── OperationMiddleware.php │ │ │ └── RefreshTokenMiddleware.php │ │ ├── Request │ │ │ └── Traits │ │ │ │ ├── ActionRulesTrait.php │ │ │ │ ├── HttpMethodTrait.php │ │ │ │ └── NoAuthorizeTrait.php │ │ ├── Result.php │ │ ├── ResultCode.php │ │ └── Swagger │ │ │ └── Server.php │ └── CurrentUser.php ├── Model │ ├── Attachment.php │ ├── Casts │ │ └── MetaCast.php │ ├── Enums │ │ └── User │ │ │ ├── Status.php │ │ │ └── Type.php │ ├── Permission │ │ ├── Menu.php │ │ ├── Meta.php │ │ ├── Role.php │ │ └── User.php │ ├── UserLoginLog.php │ └── UserOperationLog.php ├── Repository │ ├── AttachmentRepository.php │ ├── IRepository.php │ ├── Logstash │ │ ├── UserLoginLogRepository.php │ │ └── UserOperationLogRepository.php │ ├── Permission │ │ ├── MenuRepository.php │ │ ├── RoleRepository.php │ │ └── UserRepository.php │ └── Traits │ │ ├── BootTrait.php │ │ └── RepositoryOrderByTrait.php ├── Schema │ ├── AttachmentSchema.php │ ├── MenuMetaSchema.php │ ├── MenuSchema.php │ ├── RoleSchema.php │ ├── UserLoginLogSchema.php │ ├── UserOperationLogSchema.php │ └── UserSchema.php └── Service │ ├── AttachmentService.php │ ├── IService.php │ ├── LogStash │ ├── UserLoginLogService.php │ └── UserOperationLogService.php │ ├── PassportService.php │ ├── Permission │ ├── MenuService.php │ ├── RoleService.php │ └── UserService.php │ └── PermissionService.php ├── bin └── hyperf.php ├── composer.json ├── config ├── autoload │ ├── annotations.php │ ├── aspects.php │ ├── cache.php │ ├── casbin │ │ └── rbac-model.conf │ ├── commands.php │ ├── databases.php │ ├── dependencies.php │ ├── devtool.php │ ├── exceptions.php │ ├── file.php │ ├── generator.php │ ├── jwt.php │ ├── listeners.php │ ├── logger.php │ ├── middlewares.php │ ├── mine-extension.php │ ├── permission.php │ ├── processes.php │ ├── redis.php │ ├── server.php │ ├── swagger.php │ ├── translation.php │ └── watcher.php ├── config.php ├── container.php └── routes.php ├── databases ├── migrations │ ├── 2020_07_22_213202_create_rules_table.php │ ├── 2021_04_12_160526_create_user_table.php │ ├── 2021_04_18_215320_create_menu_table.php │ ├── 2021_04_18_215515_create_role_table.php │ ├── 2021_06_24_111216_create_attachment_table.php │ ├── 2024_09_22_205304_create_user_login_log.php │ ├── 2024_09_22_205631_create_user_operation_log.php │ ├── 2024_10_31_193302_create_user_belongs_role.php │ └── 2024_10_31_204004_create_role_belongs_menu.php └── seeders │ ├── menu_seeder_20240926.php │ ├── menu_update_20241029.php │ ├── menu_update_20241031.php │ └── user_seeder_20240926.php ├── deploy.test.yml ├── docker-compose.yml ├── kill ├── phpstan.neon.dist ├── phpunit.xml.dist ├── plugin └── mine-admin │ └── app-store │ ├── install.lock │ ├── mine.json │ └── src │ ├── ConfigProvider.php │ ├── Controller │ ├── AbstractController.php │ └── IndexController.php │ └── Service │ └── Service.php ├── storage ├── index.html ├── languages │ ├── en │ │ ├── app-store.php │ │ ├── attachment.php │ │ ├── auth.php │ │ ├── config.php │ │ ├── config_group.php │ │ ├── jwt.php │ │ ├── menu.php │ │ ├── result.php │ │ ├── role.php │ │ ├── user.php │ │ └── validation.php │ ├── zh_CN │ │ ├── app-store.php │ │ ├── attachment.php │ │ ├── auth.php │ │ ├── jwt.php │ │ ├── menu.php │ │ ├── result.php │ │ ├── role.php │ │ ├── user.php │ │ └── validation.php │ └── zh_TW │ │ ├── app-store.php │ │ ├── attachment.php │ │ ├── auth.php │ │ ├── jwt.php │ │ ├── menu.php │ │ ├── result.php │ │ ├── role.php │ │ ├── user.php │ │ └── validation.php └── swagger │ └── index.html ├── tests ├── Feature │ ├── Admin │ │ ├── ControllerCase.php │ │ ├── CrudControllerCase.php │ │ ├── DataCenter │ │ │ └── AttachmentControllerTest.php │ │ ├── GetTokenTrait.php │ │ ├── PassportControllerTest.php │ │ ├── Permission │ │ │ ├── MenuControllerTest.php │ │ │ ├── RoleControllerTest.php │ │ │ └── UserControllerTest.php │ │ ├── PermissionControllerTest.php │ │ ├── UserLoginLogControllerTest.php │ │ └── UserOperationLogControllerTest.php │ ├── Command │ │ └── ApplicationInstallCommandTest.php │ └── Repository │ │ ├── AbstractTestRepository.php │ │ ├── AttachmentRepositoryTest.php │ │ ├── MenuRepositoryTest.php │ │ ├── RoleRepositoryTest.php │ │ ├── UserLoginLogRepositoryTest.php │ │ ├── UserOperationLogRepositoryTest.php │ │ └── UserRepositoryTest.php ├── HttpTestCase.php └── bootstrap.php ├── watch └── web ├── .commitlintrc.js ├── .editorconfig ├── .env.development ├── .env.production ├── .gitignore ├── .lintstagedrc ├── .node-version ├── .npmrc ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── eslint.config.js ├── index.html ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── font │ └── alibaba-pu-hui-ti-3 │ └── AlibabaPuHuiTi-3-55-Regular.woff2 ├── scripts ├── gen.icons.mts └── plugin.publish.mts ├── src ├── App.vue ├── assets │ ├── icons │ │ └── .gitkeep │ ├── images │ │ ├── .gitkeep │ │ ├── 403.svg │ │ ├── 404.svg │ │ ├── defaultAvatar.jpg │ │ └── logo.svg │ └── styles │ │ ├── globals.scss │ │ ├── nprogress.scss │ │ └── resources │ │ ├── element.scss │ │ ├── utils.scss │ │ └── variables.scss ├── bootstrap.ts ├── components │ ├── ma-auth │ │ └── index.vue │ ├── ma-city-select │ │ ├── index.vue │ │ ├── lib │ │ │ └── cn.json │ │ └── type.ts │ ├── ma-col-card │ │ └── index.vue │ ├── ma-dialog │ │ └── index.vue │ ├── ma-dict-picker │ │ ├── ma-dict-checkbox.vue │ │ ├── ma-dict-radio.vue │ │ └── ma-dict-select.vue │ ├── ma-drawer │ │ └── index.vue │ ├── ma-icon-picker │ │ ├── index.vue │ │ └── ma-icon-panel.vue │ ├── ma-key-value │ │ ├── components │ │ │ └── form.vue │ │ ├── index.vue │ │ └── utils │ │ │ └── formatJson.ts │ ├── ma-remote-select │ │ └── index.vue │ ├── ma-remote-sfc-loader │ │ └── index.vue │ ├── ma-resource-picker │ │ ├── index.vue │ │ ├── panel.vue │ │ └── type.ts │ ├── ma-select-table │ │ └── index.vue │ ├── ma-svg-icon │ │ └── index.vue │ ├── ma-tree │ │ └── index.vue │ ├── ma-upload-file │ │ └── index.vue │ ├── ma-upload-image │ │ └── index.vue │ └── ma-verify-code │ │ └── index.vue ├── directives │ ├── copy │ │ └── index.ts │ ├── index.ts │ └── permission │ │ ├── auth │ │ └── index.ts │ │ ├── role │ │ └── index.ts │ │ └── user │ │ └── index.ts ├── hooks │ ├── auto-imports │ │ ├── useDayjs.ts │ │ ├── useDefaultSetting.ts │ │ ├── useGlobal.ts │ │ ├── useHttp.ts │ │ └── useTrans.ts │ ├── useCache.ts │ ├── useDialog.ts │ ├── useDrawer.ts │ ├── useEcharts.ts │ ├── useForm.ts │ ├── useImageViewer.ts │ ├── useLocalTrans.ts │ ├── useMessage.ts │ ├── useParentNode.ts │ ├── useResourcePicker.ts │ ├── useTabCollection.ts │ ├── useTable.ts │ ├── useThemeColor.ts │ └── useWatermark.ts ├── iconify │ ├── data.json │ ├── index.json │ └── index.ts ├── layouts │ ├── [...all].tsx │ ├── components │ │ ├── back-top │ │ │ └── index.tsx │ │ ├── bars │ │ │ ├── breadcrumb │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── tabbar │ │ │ │ └── index.tsx │ │ │ └── toolbar │ │ │ │ ├── components │ │ │ │ ├── dropdownMenuComponents │ │ │ │ │ ├── shortcuts-desc.tsx │ │ │ │ │ └── system-info.tsx │ │ │ │ ├── fullscreen.tsx │ │ │ │ ├── notification.tsx │ │ │ │ ├── right-bar.tsx │ │ │ │ ├── search.tsx │ │ │ │ ├── settings.tsx │ │ │ │ ├── switch-mode.tsx │ │ │ │ ├── translate.tsx │ │ │ │ └── user-bar.tsx │ │ │ │ └── index.tsx │ │ ├── footer │ │ │ └── index.tsx │ │ ├── header │ │ │ └── index.tsx │ │ ├── iframe │ │ │ └── index.tsx │ │ ├── logo │ │ │ └── index.tsx │ │ ├── main-aside │ │ │ └── index.tsx │ │ ├── menu │ │ │ ├── index.tsx │ │ │ ├── item.tsx │ │ │ ├── sub.tsx │ │ │ └── types.ts │ │ ├── search-panel │ │ │ └── index.tsx │ │ └── sub-aside │ │ │ └── index.tsx │ ├── index.tsx │ ├── provider.tsx │ ├── style │ │ ├── footer.scss │ │ ├── header.scss │ │ ├── index.scss │ │ ├── logo.scss │ │ ├── main-aside.scss │ │ ├── menu.scss │ │ ├── search-panel.scss │ │ ├── sub-aside.scss │ │ ├── tabbar.scss │ │ ├── toolbar.scss │ │ └── uc.scss │ └── uc.tsx ├── locales │ ├── en[English].yaml │ ├── zh_CN[简体中文].yaml │ └── zh_TW[繁體中文].yaml ├── main.ts ├── modules │ └── base │ │ ├── api │ │ ├── attachment.ts │ │ ├── log.ts │ │ ├── menu.ts │ │ ├── permission.ts │ │ ├── role.ts │ │ └── user.ts │ │ ├── locales │ │ ├── en[English].yaml │ │ ├── zh_CN[简体中文].yaml │ │ └── zh_TW[繁體中文].yaml │ │ └── views │ │ ├── dashboard │ │ ├── analysis.vue │ │ ├── components │ │ │ ├── analysis │ │ │ │ ├── analysis-content-publish.vue │ │ │ │ ├── analysis-content-timer.vue │ │ │ │ ├── analysis-hot-author.vue │ │ │ │ └── analysis-item.vue │ │ │ ├── report │ │ │ │ ├── report-content-classify.vue │ │ │ │ ├── report-items.vue │ │ │ │ ├── report-overview.vue │ │ │ │ ├── report-publish-source.vue │ │ │ │ └── report-user-action.vue │ │ │ └── workbench │ │ │ │ ├── workbench-fast.vue │ │ │ │ └── workbench-login.vue │ │ ├── datas │ │ │ ├── analysis.ts │ │ │ └── report.ts │ │ ├── report.vue │ │ └── workbench.vue │ │ ├── dataCenter │ │ └── attachment │ │ │ └── index.vue │ │ ├── log │ │ ├── userLogin.vue │ │ ├── userLoginLogData │ │ │ ├── UserLoginLogColumn.tsx │ │ │ └── UserLoginLogSearch.tsx │ │ ├── userOperation.vue │ │ └── userOperationLogData │ │ │ ├── UserOperationLogColumn.tsx │ │ │ └── UserOperationLogSearch.tsx │ │ ├── login │ │ ├── components │ │ │ ├── copyright.vue │ │ │ ├── dashed.vue │ │ │ ├── light.vue │ │ │ ├── login-form.vue │ │ │ ├── logo.vue │ │ │ ├── one-word.vue │ │ │ └── slogan.vue │ │ ├── index.vue │ │ └── style.scss │ │ ├── permission │ │ ├── menu │ │ │ ├── button-permission.vue │ │ │ ├── index.vue │ │ │ ├── menu-form.vue │ │ │ └── menu-tree.vue │ │ ├── role │ │ │ ├── data │ │ │ │ ├── getFormItems.tsx │ │ │ │ ├── getSearchItems.tsx │ │ │ │ └── getTableColumns.tsx │ │ │ ├── form.vue │ │ │ ├── index.vue │ │ │ └── setPermissionForm.vue │ │ └── user │ │ │ ├── data │ │ │ ├── getFormItems.tsx │ │ │ ├── getSearchItems.tsx │ │ │ └── getTableColumns.tsx │ │ │ ├── form.vue │ │ │ ├── index.vue │ │ │ └── setRoleForm.vue │ │ ├── uc │ │ ├── components │ │ │ ├── container.vue │ │ │ ├── modify-info.vue │ │ │ ├── password-form.vue │ │ │ ├── title.vue │ │ │ └── userinfo-form.vue │ │ └── index.vue │ │ └── welcome │ │ └── index.vue ├── plugins │ ├── .gitkeep │ └── mine-admin │ │ ├── app-store │ │ ├── api │ │ │ └── app.ts │ │ ├── index.ts │ │ ├── style │ │ │ └── preview.css │ │ ├── utils │ │ │ ├── discount.ts │ │ │ └── versionCompare.ts │ │ └── views │ │ │ ├── detail.vue │ │ │ ├── filter.vue │ │ │ ├── index.vue │ │ │ ├── list.vue │ │ │ ├── localList.vue │ │ │ └── notice.vue │ │ ├── basic-ui │ │ ├── components │ │ │ ├── button │ │ │ │ └── index.vue │ │ │ ├── drawer │ │ │ │ └── index.vue │ │ │ ├── dropdown │ │ │ │ ├── divider.vue │ │ │ │ ├── index.vue │ │ │ │ ├── item.vue │ │ │ │ └── symbols.ts │ │ │ ├── input │ │ │ │ └── index.vue │ │ │ ├── modal │ │ │ │ └── index.vue │ │ │ ├── switch │ │ │ │ └── index.vue │ │ │ ├── tab │ │ │ │ ├── index.vue │ │ │ │ └── type.ts │ │ │ ├── textarea │ │ │ │ └── index.vue │ │ │ └── tooltip │ │ │ │ └── index.vue │ │ ├── index.ts │ │ └── utils │ │ │ └── mergeClassName.ts │ │ └── demo │ │ └── index.ts ├── provider │ ├── dictionary │ │ ├── data │ │ │ ├── base-userType.ts │ │ │ ├── system-state.ts │ │ │ └── system-status.ts │ │ └── index.ts │ ├── echarts │ │ ├── index.ts │ │ └── themes │ │ │ └── mineDark.project.json │ ├── mine-core │ │ └── index.ts │ ├── plugins │ │ ├── config │ │ │ └── .gitkeep │ │ └── index.ts │ └── settings │ │ ├── index.ts │ │ └── settings.config.ts ├── router │ ├── index.ts │ └── static-routes │ │ ├── dashboardRoute.ts │ │ ├── rootRoute.ts │ │ ├── ucChildren │ │ └── index.ts │ │ └── welcomeRoute.ts ├── store │ ├── index.ts │ └── modules │ │ ├── useDictStore.ts │ │ ├── useIframeKeepAliveStore.ts │ │ ├── useKeepAliveStore.ts │ │ ├── useMenuStore.ts │ │ ├── usePluginStore.ts │ │ ├── useResourceStore.ts │ │ ├── useRouteStore.ts │ │ ├── useSettingStore.ts │ │ ├── useTabStore.ts │ │ └── useUserStore.ts └── utils │ ├── ResultCode.ts │ ├── checkRouteIsRedirect.ts │ ├── copyright.ts │ ├── download.ts │ ├── getOnlyWorkAreaHeight.ts │ ├── handleResize.ts │ ├── hasIncludesByArray.ts │ ├── http.ts │ ├── injectionKeys.ts │ ├── isSuperAdmin.ts │ ├── menuGotoHandle.ts │ ├── permission │ ├── hasAuth.ts │ ├── hasRole.ts │ └── hasUser.ts │ ├── recursionGetKey.ts │ ├── toolbars.ts │ └── uploadLocal.ts ├── stylelint.config.js ├── tsconfig.json ├── tsconfig.node.json ├── types ├── auto-imports.d.ts ├── components.d.ts ├── global.d.ts └── shims.d.ts ├── uno.config.ts ├── vite.config.ts └── vite ├── archiver.ts ├── auto-import.ts ├── chunk.ts ├── components.ts ├── compression.ts ├── devtools.ts ├── i18n-message.ts ├── index.ts ├── optimize.ts ├── start-info.ts ├── svg-icon.ts └── unocss.ts /.coderabbit.yaml: -------------------------------------------------------------------------------- 1 | language: "zh-CN" 2 | early_access: false 3 | reviews: 4 | profile: "chill" 5 | request_changes_workflow: true 6 | high_level_summary: true 7 | auto_title_placeholder: "PR title" 8 | commit_status: true 9 | poem: true 10 | review_status: true 11 | collapse_walkthrough: true 12 | auto_review: 13 | enabled: true 14 | drafts: false 15 | base_branches: ["master"] 16 | tools: 17 | phpstan: 18 | enabled: true 19 | level: "5" 20 | paths: [ "tests" ] 21 | 22 | chat: 23 | auto_reply: true -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MineAdmin", 3 | "build": { 4 | "dockerfile": "../Dockerfile", 5 | "context": ".." 6 | } 7 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ** 2 | !app/ 3 | !bin/ 4 | !config/ 5 | !databases/ 6 | !plugin/ 7 | !storage/ 8 | !composer.* 9 | !.env.example 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=MineAdmin 2 | APP_ENV=dev 3 | APP_DEBUG=false 4 | 5 | DB_DRIVER=mysql 6 | DB_HOST=mysql 7 | DB_PORT=3306 8 | DB_DATABASE=mineadmin 9 | DB_USERNAME=root 10 | DB_PASSWORD=root 11 | DB_CHARSET=utf8mb4 12 | DB_COLLATION=utf8mb4_unicode_ci 13 | DB_PREFIX= 14 | 15 | REDIS_HOST=redis 16 | REDIS_AUTH= 17 | REDIS_PORT=6379 18 | REDIS_DB=0 19 | 20 | APP_URL = http://127.0.0.1:9501 21 | 22 | JWT_SECRET=azOVxsOWt3r0ozZNz8Ss429ht0T8z6OpeIJAIwNp6X0xqrbEY2epfIWyxtC1qSNM8eD6/LQ/SahcQi2ByXa/2A== 23 | 24 | MINE_ACCESS_TOKEN=(null) # Your MINE_ACCESS_TOKEN 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.github export-ignore 2 | /.tmp export-ignore 3 | SECURITY.md export-ignore 4 | CONTRIBUTING.md export-ignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .settings/ 3 | .project 4 | *.patch 5 | .idea/ 6 | .git/ 7 | runtime/ 8 | vendor/ 9 | .phpintel/ 10 | .env 11 | .DS_Store 12 | .phpunit* 13 | *.cache 14 | .vscode/ 15 | tests/cover 16 | tests/coverage.xml 17 | tests/coverage 18 | tests/coding_standard.xml 19 | tests/junit.xml 20 | public 21 | !web/public 22 | *.lock 23 | storage/swagger/http.json -------------------------------------------------------------------------------- /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | '@'])); 6 | override(\Hyperf\Context\Context::get(0), map(['' => '@'])); 7 | override(\make(0), map(['' => '@'])); 8 | override(\di(0), map(['' => '@'])); 9 | override(\Hyperf\Support\make(0), map(['' => '@'])); 10 | override(\Hyperf\Support\optional(0), type(0)); 11 | override(\Hyperf\Tappable\tap(0), type(0)); 12 | } -------------------------------------------------------------------------------- /app/Exception/BusinessException.php: -------------------------------------------------------------------------------- 1 | response = new Result($code, $message, $data); 25 | } 26 | 27 | public function getResponse(): Result 28 | { 29 | return $this->response; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Exception/Handler/AppExceptionHandler.php: -------------------------------------------------------------------------------- 1 | stopPropagation(); 23 | return new Result( 24 | code: ResultCode::FAIL, 25 | message: $throwable->getMessage() 26 | ); 27 | } 28 | 29 | public function isValid(\Throwable $throwable): bool 30 | { 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Exception/Handler/BusinessExceptionHandler.php: -------------------------------------------------------------------------------- 1 | stopPropagation(); 26 | return $throwable->getResponse(); 27 | } 28 | 29 | public function isValid(\Throwable $throwable): bool 30 | { 31 | return $throwable instanceof BusinessException; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Exception/Handler/JwtExceptionHandler.php: -------------------------------------------------------------------------------- 1 | stopPropagation(); 24 | return match (true) { 25 | $throwable->getMessage() === 'The token is expired' => new Result( 26 | code: ResultCode::UNAUTHORIZED, 27 | message: trans('jwt.expired'), 28 | ), 29 | default => new Result( 30 | code: ResultCode::UNAUTHORIZED, 31 | message: trans('jwt.unauthorized'), 32 | data: [ 33 | 'error' => $throwable->getMessage(), 34 | ] 35 | ), 36 | }; 37 | } 38 | 39 | public function isValid(\Throwable $throwable): bool 40 | { 41 | return $throwable instanceof Exception; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Exception/Handler/ModeNotFoundHandler.php: -------------------------------------------------------------------------------- 1 | stopPropagation(); 24 | return new Result( 25 | code: ResultCode::NOT_FOUND 26 | ); 27 | } 28 | 29 | public function isValid(\Throwable $throwable): bool 30 | { 31 | return $throwable instanceof ModelNotFoundException; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Exception/Handler/UnauthorizedExceptionHandler.php: -------------------------------------------------------------------------------- 1 | stopPropagation(); 27 | return new Result( 28 | code: ResultCode::UNPROCESSABLE_ENTITY, 29 | message: $throwable->validator->errors()->first() 30 | ); 31 | } 32 | 33 | public function isValid(\Throwable $throwable): bool 34 | { 35 | return $throwable instanceof ValidationException; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Exception/JwtInBlackException.php: -------------------------------------------------------------------------------- 1 | getRequest()->input('page', 1); 24 | } 25 | 26 | protected function getPageSize(): int 27 | { 28 | return (int) $this->getRequest()->input('page_size', 10); 29 | } 30 | 31 | protected function getRequestData(): array 32 | { 33 | return $this->getRequest()->all(); 34 | } 35 | 36 | protected function getRequest(): RequestInterface 37 | { 38 | return ApplicationContext::getContainer()->get(RequestInterface::class); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Admin/Request/Permission/BatchGrantPermissionsForRoleRequest.php: -------------------------------------------------------------------------------- 1 | 'sometimes|array', 34 | 'permissions.*' => 'string|exists:menu,name', 35 | ]; 36 | } 37 | 38 | public function attributes(): array 39 | { 40 | return [ 41 | 'permissions' => trans('menu.name'), 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Admin/Request/Permission/BatchGrantRolesForUserRequest.php: -------------------------------------------------------------------------------- 1 | 'required|array', 34 | 'role_codes.*' => 'string|exists:role,code', 35 | ]; 36 | } 37 | 38 | public function attributes(): array 39 | { 40 | return [ 41 | 'role_codes' => trans('role.code'), 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Admin/Request/UploadRequest.php: -------------------------------------------------------------------------------- 1 | 'required|file', 34 | ]; 35 | } 36 | 37 | public function attributes(): array 38 | { 39 | return [ 40 | 'file' => trans('attachment.file'), 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Admin/Subscriber/Logstash/UserLoginSubscriber.php: -------------------------------------------------------------------------------- 1 | getUser(); 39 | Coroutine::create(fn () => $this->userService->save([ 40 | 'username' => $user->username, 41 | 'ip' => $event->getIp(), 42 | 'os' => $event->getOs(), 43 | 'browser' => $event->getBrowser(), 44 | 'status' => $event->isLogin() ? 1 : 2, 45 | ])); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Http/Admin/Vo/PassportLoginVo.php: -------------------------------------------------------------------------------- 1 | success( 38 | $this->passportService->login( 39 | $request->input('username'), 40 | $request->input('password') 41 | ) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Api/Middleware/TokenMiddleware.php: -------------------------------------------------------------------------------- 1 | jwtFactory->get('api'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Api/Request/V1/UserRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:16', 33 | 'password' => 'required|string|max:32', 34 | ]; 35 | } 36 | 37 | public function attributes(): array 38 | { 39 | return [ 40 | 'username' => trans('user.username'), 41 | 'password' => trans('user.password'), 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Common/Controller/AbstractController.php: -------------------------------------------------------------------------------- 1 | operation; 29 | } 30 | 31 | public function getUserId(): int 32 | { 33 | return $this->userId; 34 | } 35 | 36 | public function getIp(): string 37 | { 38 | return $this->ip; 39 | } 40 | 41 | public function getPath(): string 42 | { 43 | return $this->path; 44 | } 45 | 46 | public function getRemark(): string 47 | { 48 | return $this->remark; 49 | } 50 | 51 | public function getMethod(): string 52 | { 53 | return $this->method; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Common/Middleware/AccessTokenMiddleware.php: -------------------------------------------------------------------------------- 1 | jwtFactory->get(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Common/Request/Traits/HttpMethodTrait.php: -------------------------------------------------------------------------------- 1 | isMethod('POST'); 25 | } 26 | 27 | public function isUpdate(): bool 28 | { 29 | return $this->isMethod('PUT') || $this->isMethod('PATCH'); 30 | } 31 | 32 | public function isDelete(): bool 33 | { 34 | return $this->isMethod('DELETE'); 35 | } 36 | 37 | public function isSearch(): bool 38 | { 39 | return $this->isMethod('GET'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Common/Request/Traits/NoAuthorizeTrait.php: -------------------------------------------------------------------------------- 1 | message === null) { 36 | $this->message = ResultCode::getMessage($this->code->value); 37 | } 38 | } 39 | 40 | public function toArray(): array 41 | { 42 | return [ 43 | 'code' => $this->code->value, 44 | 'message' => $this->message, 45 | 'data' => $this->data, 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Http/Common/ResultCode.php: -------------------------------------------------------------------------------- 1 | 'integer', 'created_by' => 'integer', 'updated_by' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime']; 45 | } 46 | -------------------------------------------------------------------------------- /app/Repository/Permission/RoleRepository.php: -------------------------------------------------------------------------------- 1 | when(Arr::get($params, 'name'), static function (Builder $query, $name) { 29 | $query->where('name', 'like', '%' . $name . '%'); 30 | })->when(Arr::get($params, 'code'), static function (Builder $query, $code) { 31 | $query->whereIn('code', Arr::wrap($code)); 32 | })->when(Arr::has($params, 'status'), static function (Builder $query) use ($params) { 33 | $query->where('status', $params['status']); 34 | })->when(Arr::get($params, 'created_at'), static function (Builder $query, $createdAt) { 35 | $query->whereBetween('created_at', $createdAt); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Repository/Traits/BootTrait.php: -------------------------------------------------------------------------------- 1 | {$method}(...$params); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Repository/Traits/RepositoryOrderByTrait.php: -------------------------------------------------------------------------------- 1 | enablePageOrderBy()) { 22 | $orderByField = $params[$this->getOrderByParamName()] ?? $query->getModel()->getKeyName(); 23 | $orderByDirection = $params[$this->getOrderByDirectionParamName()] ?? 'desc'; 24 | $query->orderBy($orderByField, $orderByDirection); 25 | } 26 | return $query; 27 | } 28 | 29 | protected function bootRepositoryOrderByTrait(Builder $query, array $params): void 30 | { 31 | $this->handleOrderBy($query, $params); 32 | } 33 | 34 | protected function getOrderByParamName(): string 35 | { 36 | return 'order_by'; 37 | } 38 | 39 | protected function getOrderByDirectionParamName(): string 40 | { 41 | return 'order_by_direction'; 42 | } 43 | 44 | protected function enablePageOrderBy(): bool 45 | { 46 | return true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Service/LogStash/UserLoginLogService.php: -------------------------------------------------------------------------------- 1 | get(Hyperf\Contract\ApplicationInterface::class); 27 | $application->run(); 28 | })(); 29 | -------------------------------------------------------------------------------- /config/autoload/annotations.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'paths' => [ 15 | BASE_PATH . '/app', 16 | BASE_PATH . '/kernel', 17 | ], 18 | 'collectors' => [], 19 | 'ignore_annotations' => [], 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /config/autoload/aspects.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'driver' => RedisDriver::class, 18 | 'packer' => PhpSerializerPacker::class, 19 | 'prefix' => 'MineAdmin:', 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /config/autoload/casbin/rbac-model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _, _ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.sub == "SuperAdmin" -------------------------------------------------------------------------------- /config/autoload/commands.php: -------------------------------------------------------------------------------- 1 | Factory::class, 19 | CheckTokenInterface::class => PassportService::class, 20 | ]; 21 | -------------------------------------------------------------------------------- /config/autoload/devtool.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'amqp' => [ 15 | 'consumer' => [ 16 | 'namespace' => 'App\Amqp\Consumer', 17 | ], 18 | 'producer' => [ 19 | 'namespace' => 'App\Amqp\Producer', 20 | ], 21 | ], 22 | 'aspect' => [ 23 | 'namespace' => 'App\Aspect', 24 | ], 25 | 'command' => [ 26 | 'namespace' => 'App\Command', 27 | ], 28 | 'controller' => [ 29 | 'namespace' => 'App\Http\Controller', 30 | ], 31 | 'job' => [ 32 | 'namespace' => 'App\Job', 33 | ], 34 | 'listener' => [ 35 | 'namespace' => 'App\Listener', 36 | ], 37 | 'middleware' => [ 38 | 'namespace' => 'App\Middleware', 39 | ], 40 | 'Process' => [ 41 | 'namespace' => 'App\Processes', 42 | ], 43 | ], 44 | ]; 45 | -------------------------------------------------------------------------------- /config/autoload/exceptions.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'http' => [ 22 | ModeNotFoundHandler::class, 23 | // 处理业务异常 24 | BusinessExceptionHandler::class, 25 | // 处理未授权异常 26 | UnauthorizedExceptionHandler::class, 27 | // 处理验证器异常 28 | ValidationExceptionHandler::class, 29 | // 处理JWT异常 30 | JwtExceptionHandler::class, 31 | // 处理应用异常 32 | AppExceptionHandler::class, 33 | ], 34 | ], 35 | ]; 36 | -------------------------------------------------------------------------------- /config/autoload/generator.php: -------------------------------------------------------------------------------- 1 | [ 19 | // 请求ID中间件 20 | RequestIdMiddleware::class, 21 | // 多语言识别中间件 22 | TranslationMiddleware::class, 23 | // 跨域中间件,正式环境建议关闭。使用 Nginx 等代理服务器处理跨域问题。 24 | CorsMiddleware::class, 25 | // 验证器中间件,处理 formRequest 验证器 26 | ValidationMiddleware::class, 27 | ], 28 | ]; 29 | -------------------------------------------------------------------------------- /config/autoload/permission.php: -------------------------------------------------------------------------------- 1 | [ 19 | // Available Settings: "file", "text" 20 | 'type' => 'file', 21 | 22 | 'path' => __DIR__ . '/casbin/rbac-model.conf', 23 | 24 | 'text' => '', 25 | ], 26 | 27 | /* 28 | * Casbin adapter . 29 | */ 30 | // 'adapter' => DatabaseAdapter::class, 31 | 32 | /* 33 | * Database setting. 34 | */ 35 | 'database' => [ 36 | // Database connection for following tables. 37 | 'connection' => 'default', 38 | 39 | // Rule table name. 40 | 'table' => 'rules', 41 | ], 42 | 43 | 'log' => [ 44 | // changes whether Lauthz will log messages to the Logger. 45 | 'enabled' => false, 46 | ], 47 | ]; 48 | -------------------------------------------------------------------------------- /config/autoload/processes.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'host' => env('REDIS_HOST', 'localhost'), 15 | 'auth' => env('REDIS_AUTH', null), 16 | 'port' => (int) env('REDIS_PORT', 6379), 17 | 'db' => (int) env('REDIS_DB', 0), 18 | 'pool' => [ 19 | 'min_connections' => 1, 20 | 'max_connections' => 10, 21 | 'connect_timeout' => 10.0, 22 | 'wait_timeout' => 3.0, 23 | 'heartbeat' => -1, 24 | 'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 60), 25 | ], 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /config/autoload/swagger.php: -------------------------------------------------------------------------------- 1 | true, 16 | 'port' => 9503, 17 | 'json_dir' => BASE_PATH . '/storage/swagger', 18 | 'html' => file_get_contents(BASE_PATH . '/storage/swagger/index.html'), 19 | 'url' => '/swagger', 20 | 'auto_generate' => true, 21 | 'scan' => [ 22 | 'paths' => [ 23 | Finder::create() 24 | ->in([BASE_PATH . '/app/Http', BASE_PATH . '/app/Schema']) 25 | ->name('*.php') 26 | ->getIterator() 27 | ], 28 | ], 29 | 'processors' => [], 30 | ]; 31 | -------------------------------------------------------------------------------- /config/autoload/translation.php: -------------------------------------------------------------------------------- 1 | 'zh_CN', 14 | 'fallback_locale' => 'zh_CN', 15 | 'path' => BASE_PATH . '/storage/languages', 16 | ]; 17 | -------------------------------------------------------------------------------- /config/autoload/watcher.php: -------------------------------------------------------------------------------- 1 | ScanFileDriver::class, 16 | 'bin' => 'php', 17 | 'watch' => [ 18 | 'dir' => ['app', 'config'], 19 | 'file' => ['.env'], 20 | 'scan_interval' => 2000, 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'MineAdmin'), 17 | 'scan_cacheable' => ! env('APP_DEBUG', false), 18 | 'debug' => env('APP_DEBUG', false), 19 | StdoutLoggerInterface::class => [ 20 | 'log_level' => [ 21 | LogLevel::ALERT, 22 | LogLevel::CRITICAL, 23 | // LogLevel::DEBUG, 24 | LogLevel::EMERGENCY, 25 | LogLevel::ERROR, 26 | LogLevel::INFO, 27 | LogLevel::NOTICE, 28 | LogLevel::WARNING, 29 | ], 30 | ], 31 | ]; 32 | -------------------------------------------------------------------------------- /config/container.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 25 | $table->string('ptype')->nullable(); 26 | $table->string('v0')->nullable(); 27 | $table->string('v1')->nullable(); 28 | $table->string('v2')->nullable(); 29 | $table->string('v3')->nullable(); 30 | $table->string('v4')->nullable(); 31 | $table->string('v5')->nullable(); 32 | $table->timestamps(); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | */ 39 | public function down() 40 | { 41 | Schema::dropIfExists(config('permission.database.table')); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /databases/migrations/2021_04_18_215515_create_role_table.php: -------------------------------------------------------------------------------- 1 | comment('角色信息表'); 25 | $table->bigIncrements('id')->comment('主键'); 26 | $table->string('name', 30)->comment('角色名称'); 27 | $table->string('code', 100)->comment('角色代码')->unique(); 28 | $table->tinyInteger('status')->comment('状态:1=正常,2=停用')->default(1); 29 | $table->smallInteger('sort')->comment('排序')->default(0); 30 | $table->authorBy(); 31 | $table->datetimes(); 32 | $table->string('remark')->comment('备注')->default(''); 33 | }); 34 | } 35 | 36 | /** 37 | * Reverse the migrations. 38 | */ 39 | public function down(): void 40 | { 41 | Schema::dropIfExists('role'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /databases/migrations/2024_10_31_193302_create_user_belongs_role.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 24 | $table->bigInteger('user_id')->comment('用户id'); 25 | $table->bigInteger('role_id')->comment('角色id'); 26 | $table->datetimes(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | */ 33 | public function down(): void 34 | { 35 | Schema::dropIfExists('user_belongs_role'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /databases/migrations/2024_10_31_204004_create_role_belongs_menu.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 24 | $table->bigInteger('role_id')->comment('角色id'); 25 | $table->bigInteger('menu_id')->comment('菜单id'); 26 | $table->datetimes(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | */ 33 | public function down(): void 34 | { 35 | Schema::dropIfExists('role_belongs_menu'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /databases/seeders/user_seeder_20240926.php: -------------------------------------------------------------------------------- 1 | 'admin', 27 | 'user_type' => '100', 28 | 'nickname' => '创始人', 29 | 'email' => 'admin@adminmine.com', 30 | 'phone' => '16858888988', 31 | 'signed' => '广阔天地,大有所为', 32 | 'created_by' => 0, 33 | 'updated_by' => 0, 34 | 'status' => 1, 35 | 'created_at' => date('Y-m-d H:i:s'), 36 | 'updated_at' => date('Y-m-d H:i:s'), 37 | ]); 38 | $role = Role::create([ 39 | 'name' => '超级管理员', 40 | 'code' => 'SuperAdmin', 41 | ]); 42 | $entity->roles()->sync($role); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deploy.test.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | hyperf: 4 | image: hyperf/hyperf:8.1-alpine-v3.18-swoole 5 | environment: 6 | - "APP_PROJECT=MineAdmin" 7 | - "APP_ENV=test" 8 | ports: 9 | - 9501:9501 10 | deploy: 11 | replicas: 1 12 | restart_policy: 13 | condition: on-failure 14 | delay: 5s 15 | max_attempts: 5 16 | update_config: 17 | parallelism: 2 18 | delay: 5s 19 | order: start-first 20 | configs: 21 | - source: hyperf_v1.0 22 | target: /opt/www/.env 23 | configs: 24 | hyperf_v1.0: 25 | external: true 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: MineAdmin 2 | 3 | volumes: 4 | mine_redis_data: 5 | mine_mysql_data: 6 | 7 | services: 8 | redis: 9 | image: redis:7.2-alpine 10 | ports: 11 | - "6379:6379" 12 | volumes: 13 | - mine_redis_data:/data 14 | command: redis-server --appendonly yes 15 | deploy: 16 | resources: 17 | limits: 18 | memory: 1G 19 | healthcheck: 20 | test: ["CMD", "redis-cli", "ping"] 21 | interval: 10s 22 | timeout: 5s 23 | retries: 3 24 | environment: 25 | - TZ=Asia/Shanghai 26 | 27 | mysql: 28 | image: mysql:5.7 29 | volumes: 30 | - mine_mysql_data:/var/lib/mysql 31 | ports: 32 | - "3306:3306" 33 | command: --default-authentication-plugin=mysql_native_password 34 | environment: 35 | MYSQL_ROOT_PASSWORD: root 36 | MYSQL_DATABASE: mineadmin 37 | MYSQL_CHARACTER_SET_SERVER: utf8mb4 38 | MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci 39 | TZ: Asia/Shanghai 40 | 41 | hyperf: 42 | image: hyperf/hyperf:8.1-alpine-v3.18-swoole 43 | volumes: 44 | - ./:/www 45 | working_dir: /www 46 | ports: 47 | - "9501:9501" 48 | - "9503:9503" 49 | environment: 50 | - TZ=Asia/Shanghai 51 | - APP_NAME=MineAdmin 52 | command: 53 | - sh 54 | - -c 55 | - | 56 | ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 57 | tail -F /dev/null 58 | -------------------------------------------------------------------------------- /kill: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | 15 | ./app 16 | ./databases 17 | ./plugin 18 | 19 | 20 | ./app/Schema 21 | ./databases 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /plugin/mine-admin/app-store/install.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mineadmin/MineAdmin/3cf0a44f97bebaae335b3b95501e97e656db1683/plugin/mine-admin/app-store/install.lock -------------------------------------------------------------------------------- /plugin/mine-admin/app-store/mine.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mine-admin/app-store", 3 | "description": "MineAdmin 应用商店可视化管理插件", 4 | "version": "1.0.0", 5 | "author": [ 6 | { 7 | "name": "MineAdmin" 8 | } 9 | ], 10 | "package": { 11 | "dependencies": { 12 | 13 | } 14 | }, 15 | "composer": { 16 | "require": { 17 | }, 18 | "psr-4": { 19 | "Plugin\\MineAdmin\\AppStore\\": "src" 20 | }, 21 | "config": "Plugin\\MineAdmin\\AppStore\\ConfigProvider" 22 | } 23 | } -------------------------------------------------------------------------------- /plugin/mine-admin/app-store/src/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'scan' => [ 23 | 'paths' => [ 24 | __DIR__, 25 | ], 26 | ], 27 | ], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugin/mine-admin/app-store/src/Controller/AbstractController.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /storage/languages/en/app-store.php: -------------------------------------------------------------------------------- 1 | 'Access token not configured', 14 | 'Store ' => [ 15 | 'response_fail' => 'Request for plugin server failed', 16 | ], 17 | 'params_fail' => 'Please check whether the space, identifier, version parameters are correct', 18 | 'download_fail' => 'App download failed', 19 | 'app_installed' => 'App installed', 20 | 'app_not_installed' => 'App not installed', 21 | ]; 22 | -------------------------------------------------------------------------------- /storage/languages/en/attachment.php: -------------------------------------------------------------------------------- 1 | 'File', 14 | 'upload_failed' => 'Upload failed', 15 | 'upload_not_open' => 'Upload not open', 16 | 'attachment_not_exist' => 'Attachment does not exist', 17 | ]; 18 | -------------------------------------------------------------------------------- /storage/languages/en/auth.php: -------------------------------------------------------------------------------- 1 | 'password error', 14 | ]; 15 | -------------------------------------------------------------------------------- /storage/languages/en/config.php: -------------------------------------------------------------------------------- 1 | 'Group ID', 14 | 'key' => 'Key', 15 | 'value' => 'Value', 16 | 'name' => 'Name', 17 | 'input_type' => 'Input Type', 18 | 'config_select_data' => 'Config Select Data', 19 | 'sort' => 'Sort', 20 | 'remark' => 'Remark', 21 | 'not_found' => 'Config not found', 22 | ]; 23 | -------------------------------------------------------------------------------- /storage/languages/en/config_group.php: -------------------------------------------------------------------------------- 1 | 'Group name', 14 | 'code' => 'Group code', 15 | 'remark' => 'Remark', 16 | 'not_found' => 'Group not found', 17 | ]; 18 | -------------------------------------------------------------------------------- /storage/languages/en/jwt.php: -------------------------------------------------------------------------------- 1 | 'Unauthorized', 14 | 'forbidden' => 'Forbidden', 15 | 'expired' => 'Expired', 16 | 'validation_failed' => 'User information verification failed', 17 | ]; 18 | -------------------------------------------------------------------------------- /storage/languages/en/menu.php: -------------------------------------------------------------------------------- 1 | 'parent ID', 14 | 'name' => 'name', 15 | 'component' => 'component', 16 | 'path' => 'path', 17 | 'redirect' => 'redirect', 18 | 'type' => 'type', 19 | 'status' => 'status', 20 | 'sort' => 'sort', 21 | 'remark' => 'remark', 22 | 'meta' => [ 23 | 'title' => 'title', 24 | 'i18n' => 'internationalization', 25 | 'badge' => 'badge', 26 | 'icon' => 'icon', 27 | 'affix' => 'is affixed', 28 | 'hidden' => 'is hidden', 29 | 'type' => 'type', 30 | 'cache' => 'is cached', 31 | 'link' => 'link', 32 | ], 33 | ]; 34 | -------------------------------------------------------------------------------- /storage/languages/en/result.php: -------------------------------------------------------------------------------- 1 | 'Success', 14 | 'fail' => 'Fail', 15 | 'unauthorized' => 'Unauthorized', 16 | 'forbidden' => 'Forbidden', 17 | 'not_found' => 'Not Found', 18 | 'method_not_allowed' => 'Method Not Allowed', 19 | 'not_acceptable' => 'Not Acceptable', 20 | 'conflict' => 'Request parameter error', 21 | 'disabled' => 'Account disabled' 22 | ]; 23 | -------------------------------------------------------------------------------- /storage/languages/en/role.php: -------------------------------------------------------------------------------- 1 | 'Role Id', 14 | 'name' => 'Role name', 15 | 'code' => 'Role code', 16 | 'status' => 'Status', 17 | 'sort' => 'Sort', 18 | 'remark' => 'Remark', 19 | 'permission_ids' => 'Permission ID', 20 | 'code_exist' => 'Role code already exists', 21 | ]; 22 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/app-store.php: -------------------------------------------------------------------------------- 1 | '未配置 access token', 14 | 'store' => [ 15 | 'response_fail' => '请求插件服务器失败', 16 | ], 17 | 'params_fail' => '请检查space、identifier、version参数是否正确', 18 | 'download_fail' => '应用下载失败', 19 | 'app_installed' => '应用已安装', 20 | 'app_not_installed' => '应用未安装', 21 | ]; 22 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/attachment.php: -------------------------------------------------------------------------------- 1 | '文件', 14 | 'upload_failed' => '上传失败', 15 | 'upload_not_open' => '上传未开启', 16 | 'attachment_not_exist' => '附件不存在', 17 | ]; 18 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/auth.php: -------------------------------------------------------------------------------- 1 | '密码错误', 14 | ]; 15 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/jwt.php: -------------------------------------------------------------------------------- 1 | '未授权', 14 | 'forbidden' => '禁止访问', 15 | 'validation_failed' => '用户信息验证失败', 16 | 'expired' => '已过期', 17 | ]; 18 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/menu.php: -------------------------------------------------------------------------------- 1 | '父级ID', 14 | 'name' => '名称', 15 | 'component' => '组件', 16 | 'redirect' => '重定向', 17 | 'path' => '路径', 18 | 'type' => '类型', 19 | 'status' => '状态', 20 | 'sort' => '排序', 21 | 'remark' => '备注', 22 | 'meta' => [ 23 | 'title' => '标题', 24 | 'i18n' => '国际化', 25 | 'badge' => '徽章', 26 | 'icon' => '图标', 27 | 'affix' => '是否固定', 28 | 'hidden' => '是否隐藏', 29 | 'type' => '类型', 30 | 'cache' => '是否缓存', 31 | 'link' => '链接', 32 | ], 33 | ]; 34 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/result.php: -------------------------------------------------------------------------------- 1 | '成功', 14 | 'fail' => '失败', 15 | 'unauthorized' => '未登录', 16 | 'disabled' => '账号已禁用', 17 | 'forbidden' => '无权限', 18 | 'not_found' => '数据不存在', 19 | 'method_not_allowed' => '方法不允许', 20 | 'not_acceptable' => '不可接受', 21 | 'conflict' => '请求参数错误', 22 | ]; 23 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/role.php: -------------------------------------------------------------------------------- 1 | '角色id', 14 | 'name' => '角色名称', 15 | 'code' => '角色编码', 16 | 'status' => '状态', 17 | 'sort' => '排序', 18 | 'remark' => '备注', 19 | 'permission_ids' => '权限ID', 20 | 'code_exist' => '角色编码已存在', 21 | ]; 22 | -------------------------------------------------------------------------------- /storage/languages/zh_CN/user.php: -------------------------------------------------------------------------------- 1 | '用户ID,主键', 14 | 'username' => '用户名', 15 | 'user_type' => '用户类型:(100系统用户)', 16 | 'nickname' => '用户昵称', 17 | 'phone' => '手机', 18 | 'email' => '用户邮箱', 19 | 'avatar' => '用户头像', 20 | 'signed' => '个人签名', 21 | 'dashboard' => '后台首页类型', 22 | 'status' => '状态 (1正常 2停用)', 23 | 'login_ip' => '最后登陆IP', 24 | 'login_time' => '最后登陆时间', 25 | 'backend_setting' => '后台设置数据', 26 | 'created_by' => '创建者', 27 | 'updated_by' => '更新者', 28 | 'created_at' => '创建时间', 29 | 'updated_at' => '更新时间', 30 | 'remark' => '备注', 31 | 'username_exist' => '用户名已存在', 32 | 'enums' => [ 33 | 'type' => [ 34 | 100 => '系统用户', 35 | 200 => '普通用户', 36 | ], 37 | 'status' => [ 38 | 1 => '正常', 39 | 2 => '停用', 40 | ], 41 | ], 42 | 'disable' => '账号已停用', 43 | 'password' => '密码', 44 | 'old_password_error' => '旧密码错误', 45 | 'old_password' => '旧密码', 46 | 'password_confirmation' => '确认密码', 47 | ]; 48 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/app-store.php: -------------------------------------------------------------------------------- 1 | '未配置 access token', 14 | 'store' => [ 15 | 'response_fail' => '請求插件伺服器失敗', 16 | ], 17 | 'params_fail' => '請檢查 space、identifier、version 參數是否正確', 18 | 'download_fail' => '應用下載失敗', 19 | 'app_installed' => '應用已安裝', 20 | 'app_not_installed' => '應用未安裝', 21 | ]; 22 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/attachment.php: -------------------------------------------------------------------------------- 1 | '檔案', 14 | 'upload_failed' => '上傳失敗', 15 | 'upload_not_open' => '上傳未開啟', 16 | 'attachment_not_exist' => '附件不存在', 17 | ]; 18 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/auth.php: -------------------------------------------------------------------------------- 1 | '密碼錯誤', 14 | ]; 15 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/jwt.php: -------------------------------------------------------------------------------- 1 | '未授权', 14 | 'forbidden' => '禁止访问', 15 | 'validation_failed' => '用户信息验证失败', 16 | 'expired' => '已过期', 17 | ]; 18 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/menu.php: -------------------------------------------------------------------------------- 1 | '父級ID', 14 | 'name' => '名稱', 15 | 'component' => '組件', 16 | 'redirect' => '重定向', 17 | 'path' => '路徑', 18 | 'type' => '類型', 19 | 'status' => '狀態', 20 | 'sort' => '排序', 21 | 'remark' => '備註', 22 | 'meta' => [ 23 | 'title' => '標題', 24 | 'i18n' => '國際化', 25 | 'badge' => '徽章', 26 | 'icon' => '圖標', 27 | 'affix' => '是否固定', 28 | 'hidden' => '是否隱藏', 29 | 'type' => '類型', 30 | 'cache' => '是否緩存', 31 | 'link' => '鏈接', 32 | ], 33 | ]; 34 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/result.php: -------------------------------------------------------------------------------- 1 | '成功', 14 | 'fail' => '失敗', 15 | 'unauthorized' => '未登入', 16 | 'forbidden' => '無權限', 17 | 'not_found' => '未找到', 18 | 'method_not_allowed' => '方法不允許', 19 | 'not_acceptable' => '不可接受', 20 | 'conflict' => '請求參數錯誤', 21 | 'disabled' => '帳號已禁用' 22 | ]; 23 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/role.php: -------------------------------------------------------------------------------- 1 | '角色ID', 14 | 'name' => '角色名稱', 15 | 'code' => '角色編碼', 16 | 'status' => '狀態', 17 | 'sort' => '排序', 18 | 'remark' => '備註', 19 | 'permission_ids' => '權限ID', 20 | 'code_exist' => '角色編碼已存在', 21 | ]; 22 | -------------------------------------------------------------------------------- /storage/languages/zh_TW/user.php: -------------------------------------------------------------------------------- 1 | '用戶ID,主鍵', 14 | 'username' => '用戶名', 15 | 'user_type' => '用戶類型:(100系統用戶)', 16 | 'nickname' => '用戶暱稱', 17 | 'phone' => '手機', 18 | 'email' => '用戶郵箱', 19 | 'avatar' => '用戶頭像', 20 | 'signed' => '個人簽名', 21 | 'dashboard' => '後台首頁類型', 22 | 'status' => '狀態 (1正常 2停用)', 23 | 'login_ip' => '最後登錄IP', 24 | 'login_time' => '最後登錄時間', 25 | 'backend_setting' => '後台設置數據', 26 | 'created_by' => '創建者', 27 | 'updated_by' => '更新者', 28 | 'created_at' => '創建時間', 29 | 'updated_at' => '更新時間', 30 | 'remark' => '備註', 31 | 'username_exist' => '用戶名已存在', 32 | 'enums' => [ 33 | 'type' => [ 34 | 100 => '系統用戶', 35 | 200 => '普通用戶', 36 | ], 37 | 'status' => [ 38 | 1 => '正常', 39 | 2 => '停用', 40 | ], 41 | ], 42 | 'password' => '密碼', 43 | 'disable' => '賬號已停用', 44 | 'old_password_error' => '舊密碼錯誤', 45 | 'old_password' => '舊密碼', 46 | 'password_confirmation' => '確認密碼', 47 | ]; 48 | -------------------------------------------------------------------------------- /storage/swagger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | SwaggerUI 11 | 12 | 13 | 14 |
15 | 16 | 17 | 40 | 41 | -------------------------------------------------------------------------------- /tests/Feature/Admin/GetTokenTrait.php: -------------------------------------------------------------------------------- 1 | Str::random(10), 30 | 'password' => 123456, 31 | ]); 32 | } 33 | 34 | public function getToken(User $user): string 35 | { 36 | $result = $this->post('/admin/passport/login', [ 37 | 'username' => $user->username, 38 | 'password' => '123456', 39 | ]); 40 | if (!is_array($result)){ 41 | Assert::fail('Get token failed.'); 42 | } 43 | if (! Arr::has($result, 'data.access_token')) { 44 | Assert::fail('Get token failed.'); 45 | } 46 | return Arr::get($result, 'data.access_token'); 47 | } 48 | 49 | protected function getPassword(): string 50 | { 51 | if (property_exists($this, 'password')) { 52 | return $this->password; 53 | } 54 | return '123456'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Feature/Command/ApplicationInstallCommandTest.php: -------------------------------------------------------------------------------- 1 | get(ApplicationInterface::class); 32 | $app->setAutoExit(false); 33 | $app->run(new ArrayInput(['migrate']), new ConsoleOutput()); 34 | $app->run(new ArrayInput(['db:seed']), new ConsoleOutput()); 35 | self::assertTrue(true); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/HttpTestCase.php: -------------------------------------------------------------------------------- 1 | client = make(Client::class); 44 | } 45 | 46 | public function __call($name, $arguments) 47 | { 48 | return $this->client->{$name}(...$arguments); 49 | } 50 | 51 | protected function fakerGenerator(): Generator 52 | { 53 | return Factory::create(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | get(ApplicationInterface::class); 43 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /web/.env.development: -------------------------------------------------------------------------------- 1 | # 页面标题 2 | VITE_APP_TITLE = MineAdmin 3 | # 端口 4 | VITE_APP_PORT = 2888 5 | # 应用根路径 6 | VITE_APP_ROOT_BASE = / 7 | # 接口请求地址,会设置到 axios 的 baseURL 参数上 8 | VITE_APP_API_BASEURL = http://127.0.0.1:9501 9 | # 路由模式: history 和 hash 两种,默认hash,带#号那种 10 | VITE_APP_ROUTE_MODE = hash 11 | 12 | # 存储前缀 13 | VITE_APP_STORAGE_PREFIX = mine_ 14 | 15 | # 是否开启代理 16 | VITE_OPEN_PROXY = true 17 | # 代理前缀标识 18 | VITE_PROXY_PREFIX = /dev 19 | # 是否开启vConsole (手机端调式可开启) 20 | VITE_OPEN_vCONSOLE = false 21 | # 是否开启开发者工具 22 | VITE_OPEN_DEVTOOLS = false 23 | -------------------------------------------------------------------------------- /web/.env.production: -------------------------------------------------------------------------------- 1 | # 页面标题 2 | VITE_APP_TITLE = MineAdmin 3 | # 端口 4 | VITE_APP_PORT = 2888 5 | # 应用根路径 6 | VITE_APP_ROOT_BASE = / 7 | # 接口请求地址,会设置到 axios 的 baseURL 参数上 8 | VITE_APP_API_BASEURL = http://hyperf:9501 9 | # 路由模式: history 和 hash 两种,默认hash,带#号那种 10 | VITE_APP_ROUTE_MODE = hash 11 | 12 | # 存储前缀 13 | VITE_APP_STORAGE_PREFIX = mine_ 14 | 15 | # 是否开启代理 16 | VITE_OPEN_PROXY = true 17 | # 代理前缀标识 18 | VITE_PROXY_PREFIX = /prod 19 | 20 | # 是否在打包时生成 sourcemap 21 | VITE_BUILD_SOURCEMAP = false 22 | # 是否在打包时开启压缩,支持 gzip 和 brotli 23 | VITE_BUILD_COMPRESS = gzip,brotli 24 | # 是否在打包后生成存档,支持 zip 和 tar 25 | VITE_BUILD_ARCHIVE = 26 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist* 4 | dist-ssr 5 | *.local 6 | .eslintcache 7 | .stylelintcache 8 | pnpm-lock.yaml 9 | .idea* 10 | -------------------------------------------------------------------------------- /web/.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts,tsx,vue}": "eslint --cache --fix", 3 | "*.{css,scss,vue}": "stylelint --cache --fix" 4 | } 5 | -------------------------------------------------------------------------------- /web/.node-version: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /web/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | engine-strict=true 4 | -------------------------------------------------------------------------------- /web/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.0.0 (2024-09-30) 4 | 5 | ### ✨ 全新重构版本发布 6 | -------------------------------------------------------------------------------- /web/Dockerfile: -------------------------------------------------------------------------------- 1 | #FROM node:18.16.0-alpine3.16 AS build 2 | FROM node:20-alpine3.20 AS build 3 | 4 | WORKDIR /opt/www 5 | COPY . /opt/www/ 6 | 7 | RUN npm install -g pnpm 8 | 9 | ARG MINE_NODE_ENV=production 10 | 11 | RUN echo "MINE_NODE_ENV=$MINE_NODE_ENV" 12 | 13 | RUN pnpm install 14 | RUN if [ "$MINE_NODE_ENV" = "development" ]; then pnpm build --mode development; fi && \ 15 | if [ "$MINE_NODE_ENV" = "production" ]; then pnpm build --mode production; fi 16 | 17 | 18 | FROM nginx:alpine AS production 19 | 20 | COPY --from=build /opt/www/dist /usr/share/nginx/html 21 | -------------------------------------------------------------------------------- /web/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MineAdmin-UI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu( 4 | { 5 | unocss: true, 6 | ignores: [ 7 | 'public', 8 | 'dist*', 9 | ], 10 | }, 11 | { 12 | rules: { 13 | 'no-undefined': 'off', 14 | 'vue/custom-event-name-casing': 'off', 15 | 'vue/no-unused-refs': 'off', 16 | 'perfectionist/sort-imports': 'off', 17 | 'vue/component-definition-name-casing': 'off', 18 | 'eslint-comments/no-unlimited-disable': 'off', 19 | 'curly': ['error', 'all'], 20 | 'antfu/consistent-list-newline': 'off', 21 | 'ts/no-unused-expressions': 'off', 22 | 'no-console': 'off', 23 | 'vue/require-v-for-key': 'off', 24 | 'array-callback-return': 'off', 25 | }, 26 | }, 27 | { 28 | files: [ 29 | 'src/**/*.vue', 30 | 'src/**/*.tsx', 31 | ], 32 | rules: { 33 | 'vue/block-order': ['error', { 34 | order: ['route', 'i18n', 'script', 'template', 'style'], 35 | }], 36 | }, 37 | }, 38 | ) 39 | -------------------------------------------------------------------------------- /web/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mineadmin/MineAdmin/3cf0a44f97bebaae335b3b95501e97e656db1683/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/font/alibaba-pu-hui-ti-3/AlibabaPuHuiTi-3-55-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mineadmin/MineAdmin/3cf0a44f97bebaae335b3b95501e97e656db1683/web/public/font/alibaba-pu-hui-ti-3/AlibabaPuHuiTi-3-55-Regular.woff2 -------------------------------------------------------------------------------- /web/scripts/plugin.publish.mts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | 11 | import process from 'node:process' 12 | import fs from 'node:fs' 13 | import picocolors from 'picocolors' 14 | 15 | const pluginName = process.argv.slice(2)[0] 16 | 17 | function copyFile(source: string, destination: string, callback: (err?: Error) => void) { 18 | if (fs.existsSync(source)) { 19 | fs.readFile(source, (err, data) => { 20 | if (err) { 21 | callback(err) 22 | } 23 | else { 24 | fs.writeFile(destination, data, (err) => { 25 | callback(err) 26 | }) 27 | } 28 | }) 29 | } 30 | else { 31 | callback(`${pluginName} 插件没有配置文件可发布,已跳过`) 32 | } 33 | } 34 | 35 | if (pluginName === undefined) { 36 | console.error('发布插件配置文件缺少指定插件参数,例:pnpm plugin:publish mine-admin/ui') 37 | } 38 | else { 39 | const source = `src/plugins/${pluginName}/config.ts` 40 | const target = `src/provider/plugins/config/${pluginName.replace('/', '.')}.config.ts` 41 | copyFile(source, target, (err) => { 42 | if (err) { 43 | console.error(err) 44 | } 45 | else { 46 | console.log(picocolors.green(`插件 ${pluginName} 配置文件发布成功`)) 47 | } 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /web/src/assets/icons/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mineadmin/MineAdmin/3cf0a44f97bebaae335b3b95501e97e656db1683/web/src/assets/icons/.gitkeep -------------------------------------------------------------------------------- /web/src/assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mineadmin/MineAdmin/3cf0a44f97bebaae335b3b95501e97e656db1683/web/src/assets/images/.gitkeep -------------------------------------------------------------------------------- /web/src/assets/images/defaultAvatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mineadmin/MineAdmin/3cf0a44f97bebaae335b3b95501e97e656db1683/web/src/assets/images/defaultAvatar.jpg -------------------------------------------------------------------------------- /web/src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/src/assets/styles/nprogress.scss: -------------------------------------------------------------------------------- 1 | #nprogress { 2 | pointer-events: none; 3 | 4 | .bar { 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | z-index: 2000; 9 | width: 100%; 10 | height: 2px; 11 | background: rgb(var(--ui-primary)); 12 | } 13 | 14 | .peg { 15 | position: absolute; 16 | right: 0; 17 | display: block; 18 | width: 100px; 19 | height: 100%; 20 | box-shadow: 0 0 10px rgb(var(--ui-primary)), 0 0 5px rgb(var(--ui-primary)); 21 | opacity: 1; 22 | transform: rotate(3deg) translate(0, -4px); 23 | } 24 | 25 | .spinner { 26 | position: fixed; 27 | top: 11px; 28 | right: 14px; 29 | z-index: 2000; 30 | display: block; 31 | 32 | .spinner-icon { 33 | box-sizing: border-box; 34 | width: 18px; 35 | height: 18px; 36 | border: solid 2px transparent; 37 | border-top-color: rgb(var(--ui-primary)); 38 | border-left-color: rgb(var(--ui-primary)); 39 | border-radius: 50%; 40 | animation: nprogress-spinner 400ms linear infinite; 41 | } 42 | } 43 | } 44 | 45 | .nprogress-custom-parent { 46 | position: relative; 47 | overflow: hidden; 48 | 49 | #nprogress .spinner, 50 | #nprogress .bar { 51 | position: absolute; 52 | } 53 | } 54 | 55 | @keyframes nprogress-spinner { 56 | 0% { transform: rotate(0deg); } 57 | 100% { transform: rotate(360deg); } 58 | } 59 | 60 | @keyframes nprogress-spinner { 61 | 0% { transform: rotate(0deg); } 62 | 100% { transform: rotate(360deg); } 63 | } 64 | -------------------------------------------------------------------------------- /web/src/assets/styles/resources/element.scss: -------------------------------------------------------------------------------- 1 | .el-menu { 2 | .el-menu-item:hover { 3 | @apply 4 | dark-bg-[var(--el-color-primary-light-1)] 5 | dark-text-[var(--el-color-primary-light-9)] 6 | ; 7 | } 8 | } 9 | 10 | .el-table { 11 | @apply rounded; 12 | } 13 | 14 | .el-table th.el-table__cell{ 15 | @apply font-500 text-dark-9 dark-text-gray-1 16 | !bg-gray-100 17 | !dark:bg-dark-5; 18 | } 19 | 20 | .el-tree-node__label { 21 | @apply flex flex-1 h-full; 22 | } 23 | 24 | .el-tree { 25 | 26 | .el-tree-node__content { 27 | @apply rounded mt-0.5 h-35px; 28 | } 29 | 30 | .mine-tree-node { 31 | @apply flex items-center justify-between h-full pr-2 w-full relative; 32 | 33 | .label { 34 | @apply flex items-center h-full gap-x-1.5; 35 | } 36 | .do { 37 | @apply absolute right-3 hidden; 38 | } 39 | } 40 | 41 | .mine-tree-node:hover .do { 42 | @apply inline-block; 43 | } 44 | } 45 | 46 | .el-dialog__headerbtn, .el-drawer__close-btn { 47 | @apply top-10px right-10px transition-all duration-200 w-30px h-30px flex items-center justify-center rounded 48 | bg-gray-1 hover:bg-gray-2 dark:bg-dark-4 dark-hover:bg-dark-5 49 | ; 50 | 51 | .el-icon { 52 | @apply text-dark-6 dark-text-white transition-all duration-200 ; 53 | } 54 | 55 | .el-icon:hover { 56 | @apply text-[rgb(var(--ui-primary))]; 57 | } 58 | } 59 | 60 | .el-tag { 61 | border: none; 62 | } 63 | 64 | -------------------------------------------------------------------------------- /web/src/assets/styles/resources/utils.scss: -------------------------------------------------------------------------------- 1 | // @mixin 通过 @include 调用使用 2 | // % 通过 @extend 调用使用 3 | 4 | // 文字超出隐藏,默认为单行超出隐藏,可设置多行 5 | @mixin text-overflow($line: 1, $fixed-width: true) { 6 | @if $line == 1 and $fixed-width == true { 7 | overflow: hidden; 8 | text-overflow: ellipsis; 9 | white-space: nowrap; 10 | } @else { 11 | display: box; 12 | overflow: hidden; 13 | -webkit-box-orient: vertical; 14 | -webkit-line-clamp: $line; 15 | } 16 | } 17 | 18 | // 定位居中,默认水平居中,可选择垂直居中,或者水平垂直都居中 19 | @mixin position-center($type: x) { 20 | position: absolute; 21 | 22 | @if $type == x { 23 | left: 50%; 24 | transform: translateX(-50%); 25 | } 26 | 27 | @if $type == y { 28 | top: 50%; 29 | transform: translateY(-50%); 30 | } 31 | 32 | @if $type == xy { 33 | top: 50%; 34 | left: 50%; 35 | transform: translateX(-50%) translateY(-50%); 36 | } 37 | } 38 | 39 | // 文字两端对齐 40 | %justify-align { 41 | text-align: justify; 42 | text-align-last: justify; 43 | } 44 | 45 | // 清除浮动 46 | %clearfix { 47 | zoom: 1; 48 | 49 | &::before, 50 | &::after { 51 | display: block; 52 | clear: both; 53 | content: ""; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /web/src/assets/styles/resources/variables.scss: -------------------------------------------------------------------------------- 1 | // 全局变量 2 | -------------------------------------------------------------------------------- /web/src/components/ma-auth/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /web/src/components/ma-city-select/type.ts: -------------------------------------------------------------------------------- 1 | export interface ModelType { 2 | province?: string 3 | city?: string | undefined 4 | area?: string | undefined 5 | } 6 | 7 | export interface Area { 8 | code: string 9 | name: string 10 | children?: Area 11 | } 12 | -------------------------------------------------------------------------------- /web/src/components/ma-key-value/utils/formatJson.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | export default function formatJson(json: Record): string { 11 | try { 12 | return JSON.stringify(json, null, 2) 13 | } 14 | catch (error) { 15 | // 如果解析失败,返回原始字符串并附带错误信息 16 | console.error('Invalid JSON string:', error) 17 | return `/* Invalid JSON: ${json} */` 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/src/components/ma-resource-picker/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 35 | 36 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /web/src/components/ma-resource-picker/type.ts: -------------------------------------------------------------------------------- 1 | import type { DialogEmits } from 'element-plus' 2 | import type { MTabsOptionItems } from '$/mine-admin/basic-ui/components/tab/type.ts' 3 | 4 | export interface Resource { 5 | id?: number 6 | storage_mode?: number 7 | origin_name?: string 8 | object_name?: string 9 | hash?: string 10 | mime_type?: string 11 | storage_path?: string 12 | suffix?: string 13 | size_byte?: number 14 | size_info?: string 15 | url?: string 16 | } 17 | 18 | export interface FileType extends MTabsOptionItems { 19 | value: string 20 | label: string | (() => string) 21 | suffix: string 22 | } 23 | 24 | // 定义 Props 类型 25 | export interface ResourcePanelProps { 26 | multiple?: boolean 27 | limit?: number 28 | pageSize?: number 29 | showAction?: boolean 30 | dbClickConfirm?: boolean 31 | defaultFileType?: string 32 | fileTypes?: FileType[] 33 | } 34 | 35 | export interface ResourcePanelEmits { 36 | cancel: () => void 37 | confirm: (value: Resource[]) => void 38 | } 39 | 40 | export interface ResourcePickerProps extends ResourcePanelProps { 41 | visible: boolean 42 | } 43 | export interface ResourcePickerEmits extends ResourcePanelEmits, DialogEmits { 44 | 45 | } 46 | -------------------------------------------------------------------------------- /web/src/directives/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | export * from './copy' 11 | export * from './permission/auth' 12 | export * from './permission/role' 13 | export * from './permission/user' 14 | -------------------------------------------------------------------------------- /web/src/directives/permission/auth/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import type { Directive, DirectiveBinding } from 'vue' 11 | import hasAuth from '@/utils/permission/hasAuth' 12 | 13 | export const auth = { 14 | mounted(el: HTMLElement, binding: DirectiveBinding) { 15 | const { value } = binding 16 | if (value) { 17 | hasAuth(value) || el.parentNode?.removeChild(el) 18 | } 19 | else { 20 | throw new Error( 21 | '[Directive: auth]: please provide a value, like v-auth="[\'user:add\',\'user:edit\']"', 22 | ) 23 | } 24 | }, 25 | } as Directive 26 | -------------------------------------------------------------------------------- /web/src/directives/permission/role/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import type { Directive, DirectiveBinding } from 'vue' 11 | import hasRole from '@/utils/permission/hasRole' 12 | 13 | export const role = { 14 | mounted(el: HTMLElement, binding: DirectiveBinding) { 15 | const { value } = binding 16 | if (value) { 17 | hasRole(value) || el.parentNode?.removeChild(el) 18 | } 19 | else { 20 | throw new Error( 21 | '[Directive: role]: please provide a value, like v-role="[\'superAdmin\',\'other\']"', 22 | ) 23 | } 24 | }, 25 | } as Directive 26 | -------------------------------------------------------------------------------- /web/src/directives/permission/user/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import type { Directive, DirectiveBinding } from 'vue' 11 | import hasUser from '@/utils/permission/hasUser' 12 | 13 | export const user = { 14 | mounted(el: HTMLElement, binding: DirectiveBinding) { 15 | const { value } = binding 16 | if (value) { 17 | hasUser(value) || el.parentNode?.removeChild(el) 18 | } 19 | else { 20 | throw new Error( 21 | '[Directive: user]: please provide a value, like v-user="[\'张三\',\'李四\']"', 22 | ) 23 | } 24 | }, 25 | } as Directive 26 | -------------------------------------------------------------------------------- /web/src/hooks/auto-imports/useDayjs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import dayjs from 'dayjs' 11 | import 'dayjs/locale/zh-cn' 12 | import relativeTime from 'dayjs/plugin/relativeTime' 13 | 14 | dayjs.extend(relativeTime) 15 | dayjs.locale('zh-cn') 16 | 17 | export default function useDayjs(date?: dayjs.ConfigType, origin: boolean = false): any { 18 | return origin ? dayjs : dayjs(date) 19 | } 20 | -------------------------------------------------------------------------------- /web/src/hooks/auto-imports/useDefaultSetting.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import type { SystemSettings } from '#/global' 11 | 12 | export default function useDefaultSetting(): SystemSettings.all { 13 | return inject('defaultSetting') as SystemSettings.all 14 | } 15 | -------------------------------------------------------------------------------- /web/src/hooks/auto-imports/useGlobal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import type { ComponentInternalInstance } from 'vue' 11 | 12 | export default function useGlobal() { 13 | const { appContext } = getCurrentInstance() as ComponentInternalInstance 14 | return appContext.config.globalProperties 15 | } 16 | -------------------------------------------------------------------------------- /web/src/hooks/auto-imports/useHttp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import type { AxiosInstance } from 'axios' 11 | import request from '@/utils/http' 12 | 13 | export default function useHttp(): AxiosInstance { 14 | return request.http 15 | } 16 | -------------------------------------------------------------------------------- /web/src/hooks/auto-imports/useTrans.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | 11 | import { useI18n } from 'vue-i18n' 12 | import type { ComposerTranslation } from 'vue-i18n' 13 | 14 | export interface TransType { 15 | globalTrans: ComposerTranslation 16 | localTrans: ComposerTranslation 17 | } 18 | 19 | export function useTrans(key: any | null = null): TransType | string | any { 20 | const global = useI18n() 21 | const local = useI18n({ 22 | inheritLocale: true, 23 | useScope: 'local', 24 | }) 25 | 26 | if (key === null) { 27 | return { 28 | localTrans: local.t, 29 | globalTrans: global.t, 30 | } 31 | } 32 | else { 33 | return global.te(key) ? global.t(key) : local.te(key) ? local.t(key) : key 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/src/hooks/useEcharts.ts: -------------------------------------------------------------------------------- 1 | import useEcharts from '@mineadmin/echarts' 2 | import { useColorMode } from '@vueuse/core' 3 | 4 | const colorMode = useColorMode() 5 | 6 | function themeMode() { 7 | return colorMode.value === 'dark' ? 'mineDark' : 'default' 8 | } 9 | 10 | export { 11 | themeMode, 12 | useEcharts, 13 | } 14 | -------------------------------------------------------------------------------- /web/src/hooks/useForm.ts: -------------------------------------------------------------------------------- 1 | import { useForm as useMaForm } from '@mineadmin/form' 2 | import type { MaFormExpose } from '@mineadmin/form' 3 | 4 | export default function useForm(refName: string): Promise { 5 | return useMaForm(refName) 6 | } 7 | -------------------------------------------------------------------------------- /web/src/hooks/useImageViewer.ts: -------------------------------------------------------------------------------- 1 | import { h, render } from 'vue' 2 | import type { ImageViewerProps } from 'element-plus' 3 | import { ElImageViewer } from 'element-plus' 4 | 5 | type Options = Partial> 6 | export function useImageViewer(images: string[], options?: Options) { 7 | const imageViewerDom = document.createElement('div') 8 | 9 | const viewerProps = { 10 | urlList: images, 11 | hideOnClickModal: true, 12 | zIndex: 2500, 13 | initialIndex: 0, 14 | ...options, 15 | onClose: () => { 16 | render(null, imageViewerDom) 17 | if (document.body.contains(imageViewerDom)) { 18 | document.body.removeChild(imageViewerDom) 19 | } 20 | }, 21 | } 22 | 23 | const vnode = h(ElImageViewer, viewerProps) 24 | document.body.appendChild(imageViewerDom) 25 | render(vnode, imageViewerDom) 26 | } 27 | -------------------------------------------------------------------------------- /web/src/hooks/useLocalTrans.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | 11 | import { useI18n } from 'vue-i18n' 12 | import type { ComposerTranslation } from 'vue-i18n' 13 | 14 | export function useLocalTrans(key: any | null = null): string | ComposerTranslation | any { 15 | const { t } = useI18n({ 16 | inheritLocale: true, 17 | useScope: 'local', 18 | }) 19 | return key === null ? t as ComposerTranslation : t(key) as string 20 | } 21 | -------------------------------------------------------------------------------- /web/src/hooks/useParentNode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | export default function useParentNode(e: PointerEvent | MouseEvent, labelName: string) { 11 | let node: any = e.target 12 | while (node.tagName !== labelName.toUpperCase()) { 13 | node = node?.parentNode 14 | } 15 | return node 16 | } 17 | -------------------------------------------------------------------------------- /web/src/hooks/useResourcePicker.ts: -------------------------------------------------------------------------------- 1 | import { h, render } from 'vue' 2 | import MaResourcePicker from '@/components/ma-resource-picker/index.vue' 3 | import type { ResourcePickerEmits, ResourcePickerProps } from '@/components/ma-resource-picker/type.ts' 4 | 5 | export type WithOnEventListeners = { 6 | [K in keyof T as `on${Capitalize}`]?: T[K]; 7 | } 8 | 9 | type Options = Partial> 10 | export function useResourcePicker(options?: Omit) { 11 | const resourcePickerDom = document.createElement('div') 12 | 13 | const props: Options = { 14 | ...options, 15 | visible: true, 16 | onClosed() { 17 | render(null, resourcePickerDom) 18 | if (document.body.contains(resourcePickerDom)) { 19 | document.body.removeChild(resourcePickerDom) 20 | } 21 | return true 22 | }, 23 | } 24 | 25 | const vnode = h(MaResourcePicker, props) 26 | document.body.appendChild(resourcePickerDom) 27 | render(vnode, resourcePickerDom) 28 | } 29 | -------------------------------------------------------------------------------- /web/src/hooks/useTable.ts: -------------------------------------------------------------------------------- 1 | import { useTable as useMaTable } from '@mineadmin/table' 2 | import type { MaTableExpose } from '@mineadmin/table' 3 | 4 | export default function useTable(refName: string): Promise { 5 | return useMaTable(refName) 6 | } 7 | -------------------------------------------------------------------------------- /web/src/iconify/index.json: -------------------------------------------------------------------------------- 1 | { "collections": ["ant-design", "ep", "flagpack", "heroicons", "mdi", "ri", "logos", "twemoji", "vscode-icons"], "isOfflineUse": false } 2 | -------------------------------------------------------------------------------- /web/src/iconify/index.ts: -------------------------------------------------------------------------------- 1 | import { addCollection } from '@iconify/vue' 2 | import data from './data.json' 3 | 4 | export async function downloadAndInstall(name: string) { 5 | const data = Object.freeze(await fetch(`./icons/${name}-raw.json`).then(r => r.json())) 6 | addCollection(data) 7 | } 8 | 9 | export const icons = data?.sort((a, b) => a.info.name.localeCompare(b.info.name)) 10 | -------------------------------------------------------------------------------- /web/src/layouts/[...all].tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import image403 from '@/assets/images/403.svg' 11 | import image404 from '@/assets/images/404.svg' 12 | 13 | export default defineComponent({ 14 | name: 'MineSystemError', 15 | setup() { 16 | const route = useRoute() 17 | const router = useRouter() 18 | return () => ( 19 |
20 |
21 | {route.fullPath !== '/403' && 404} 22 | {route.fullPath === '/403' && 404} 23 |
24 | router.replace('/')} 26 | > 27 | 28 | {useTrans('mineAdmin.goHome')} 29 | 30 |
31 |
32 | ) 33 | }, 34 | }) 35 | -------------------------------------------------------------------------------- /web/src/layouts/components/bars/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import MineTabbar from './tabbar' 11 | import MineToolbar from './toolbar' 12 | 13 | export default defineComponent({ 14 | name: 'Bars', 15 | setup() { 16 | const settingStore = useSettingStore() 17 | return () => ( 18 |
19 | 20 | {settingStore.getSettings('tabbar').enable && } 21 |
22 | ) 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /web/src/layouts/components/bars/toolbar/components/fullscreen.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import { useFullscreen } from '@vueuse/core' 11 | 12 | export default defineComponent({ 13 | name: 'fullscreen', 14 | setup() { 15 | const { isFullscreen, toggle } = useFullscreen(document.body, { autoExit: true }) 16 | return () => ( 17 | 25 | ) 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /web/src/layouts/components/bars/toolbar/components/right-bar.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import type { Component } from 'vue' 11 | import MineUserBar from './user-bar.tsx' 12 | import type { MineToolbar } from '#/global' 13 | 14 | export default defineComponent({ 15 | name: 'RightBar', 16 | setup() { 17 | const settingStore = useSettingStore() 18 | const toolbarHook = useGlobal().$toolbars 19 | const toolbars = ref([]) 20 | // 计算处理后的工具栏数据 21 | const toolbarList = computed(() => 22 | toolbarHook.toolbars.value.map((item: MineToolbar) => ({ 23 | ...item, 24 | show: settingStore.getSettings('toolBars')?.find((setting: MineToolbar) => setting.name === item.name)?.show ?? item.show, 25 | })), 26 | ) 27 | watch(() => toolbarList.value, () => { 28 | toolbarHook.state = false 29 | toolbarHook.render().then((res: any) => { 30 | toolbars.value = res as Component[] 31 | }) 32 | toolbarHook.state = true 33 | }, { immediate: true, deep: true }) 34 | return () => ( 35 |
36 | {toolbars.value} 37 | 38 |
39 | ) 40 | }, 41 | }) 42 | -------------------------------------------------------------------------------- /web/src/layouts/components/bars/toolbar/components/search.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | export default defineComponent({ 11 | name: 'search', 12 | setup() { 13 | const openSearchPanel = async () => { 14 | useSettingStore().setSearchPanelEnable(true) 15 | await nextTick() 16 | const dom = document.querySelector('.mine-search-input') as HTMLElement 17 | dom?.focus() 18 | } 19 | return () => ( 20 | openSearchPanel()} 25 | /> 26 | ) 27 | }, 28 | }) 29 | -------------------------------------------------------------------------------- /web/src/layouts/components/bars/toolbar/components/switch-mode.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | export default defineComponent({ 11 | name: 'switchMode', 12 | setup() { 13 | const settingStore = useSettingStore() 14 | const icon = computed(() => { 15 | return (settingStore.colorMode === 'autoMode') 16 | ? 'lets-icons:color-mode-light' 17 | : settingStore.colorMode === 'dark' 18 | ? 'material-symbols:dark-mode-outline' 19 | : 'material-symbols:sunny-outline-rounded' 20 | }) 21 | return () => ( 22 | 30 | ) 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /web/src/layouts/components/footer/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import '@/layouts/style/footer.scss' 11 | 12 | export default defineComponent({ 13 | name: 'Footer', 14 | setup() { 15 | const settingStore = useSettingStore() 16 | const footerSetting = settingStore.getSettings('copyright') 17 | const route = useRoute() 18 | return () => ( 19 | 33 | ) 34 | }, 35 | }) 36 | -------------------------------------------------------------------------------- /web/src/layouts/components/header/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import { Transition } from 'vue' 11 | import '@/layouts/style/header.scss' 12 | import Logo from '@/layouts/components/logo' 13 | import MainAside from '@/layouts/components/main-aside' 14 | 15 | export default defineComponent({ 16 | name: 'Header', 17 | setup() { 18 | const settingStore = useSettingStore() 19 | return () => { 20 | return ( 21 | 22 | {settingStore.showMineHeader() && ( 23 | 29 | )} 30 | 31 | ) 32 | } 33 | }, 34 | }) 35 | -------------------------------------------------------------------------------- /web/src/layouts/components/iframe/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * MineAdmin is committed to providing solutions for quickly building web applications 3 | * Please view the LICENSE file that was distributed with this source code, 4 | * For the full copyright and license information. 5 | * Thank you very much for using MineAdmin. 6 | * 7 | * @Author X.Mo 8 | * @Link https://github.com/mineadmin 9 | */ 10 | import getOnlyWorkAreaHeight from '@/utils/getOnlyWorkAreaHeight.ts' 11 | 12 | export default defineComponent({ 13 | name: 'MineIframe', 14 | setup() { 15 | const route = useRoute() 16 | const routers = useRouter().getRoutes() 17 | const iframeStore = useIframeKeepAliveStore() 18 | const list = computed(() => iframeStore.iframeList ?? []) 19 | onMounted(() => { 20 | const iframeArea = document.querySelector('.mine-iframe-area') as HTMLDivElement 21 | iframeArea.classList.add('overflow-hidden') 22 | iframeArea.style.height = `${getOnlyWorkAreaHeight() + 48}px` 23 | }) 24 | return () => ( 25 |
26 | {iframeStore.iframeList?.map((name: string) => { 27 | return ( 28 |