├── .gitignore ├── LICENSE ├── README.md ├── backend ├── .gitignore ├── README.md ├── alembic.ini ├── alembic │ ├── README │ ├── env.py │ └── script.py.mako ├── common │ ├── __init__.py │ ├── fu_async_crud.py │ ├── fu_auth.py │ ├── fu_middleware.py │ ├── fu_model.py │ ├── fu_pagination.py │ ├── fu_schema.py │ └── utils │ │ ├── dabase │ │ ├── __init__.py │ │ ├── fu_mysql.py │ │ ├── fu_postgres.py │ │ └── type_mapping.py │ │ ├── excel_utils.py │ │ ├── list_to_tree.py │ │ ├── request_util.py │ │ ├── server │ │ ├── linux.py │ │ ├── public.json │ │ ├── system.py │ │ └── windows.py │ │ └── system.py ├── config │ ├── __init__.py │ ├── dev_env.py │ ├── prd_env.py │ └── uat_env.py ├── main.py ├── requirements.txt ├── sql │ ├── fast-api-mysql.sql │ └── fast-api-postgres.sql ├── system │ ├── __init__.py │ ├── button │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── dept │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── dict │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── dict_item │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── file │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── log_login │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── log_operation │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── login │ │ ├── __init__.py │ │ ├── api.py │ │ └── schema.py │ ├── menu │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── monitor │ │ ├── __init__.py │ │ └── api.py │ ├── post │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── role │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py │ ├── router.py │ └── user │ │ ├── __init__.py │ │ ├── api.py │ │ ├── model.py │ │ └── schema.py └── test_main.http └── web ├── .browserslistrc ├── .commitlintrc.js ├── .dockerignore ├── .editorconfig ├── .env ├── .env.analyze ├── .env.development ├── .env.docker ├── .env.production ├── .env.test ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .gitpod.yml ├── .husky ├── commit-msg ├── common.sh └── pre-commit ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .stylelintignore ├── .stylelintrc.js ├── CHANGELOG.en_US.md ├── CHANGELOG.md ├── CHANGELOG.zh_CN.md ├── CNAME ├── Dockerfile ├── LICENSE ├── README.md ├── README.zh-CN.md ├── apps ├── portal-view │ └── .gitkeep └── test-server │ ├── README.md │ ├── controller │ ├── FileController.ts │ └── UserController.ts │ ├── ecosystem.config.js │ ├── index.ts │ ├── nodemon.json │ ├── package.json │ ├── routes.ts │ ├── service │ ├── FileService.ts │ └── UserService.ts │ ├── tsconfig.json │ └── utils.ts ├── index.html ├── internal ├── eslint-config │ ├── .eslintrc.js │ ├── build.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── strict.ts │ └── tsconfig.json ├── stylelint-config │ ├── .eslintrc.js │ ├── build.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── ts-config │ ├── base.json │ ├── node-server.json │ ├── node.json │ ├── package.json │ └── vue-app.json └── vite-config │ ├── .eslintrc.js │ ├── build.config.ts │ ├── package.json │ ├── src │ ├── config │ │ ├── application.ts │ │ ├── common.ts │ │ └── package.ts │ ├── index.ts │ ├── plugins │ │ ├── appConfig.ts │ │ ├── compress.ts │ │ ├── html.ts │ │ ├── index.ts │ │ ├── mock.ts │ │ ├── svgSprite.ts │ │ └── visualizer.ts │ └── utils │ │ ├── env.ts │ │ ├── hash.ts │ │ └── modifyVars.ts │ └── tsconfig.json ├── mock ├── _createProductionServer.ts ├── _util.ts ├── demo │ ├── account.ts │ ├── api-cascader.ts │ ├── select-demo.ts │ ├── system.ts │ ├── table-demo.ts │ └── tree-demo.ts └── sys │ ├── menu.ts │ └── user.ts ├── nginx.conf ├── package.json ├── packages ├── .gitkeep ├── hooks │ ├── .eslintrc.js │ ├── build.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── onMountedOrActivated.ts │ │ ├── useAttrs.ts │ │ ├── useRefs.ts │ │ ├── useScrollTo.ts │ │ └── useWindowSizeFn.ts │ └── tsconfig.json └── types │ ├── .eslintrc.js │ ├── build.config.ts │ ├── package.json │ ├── src │ ├── index.ts │ └── utils.ts │ └── tsconfig.json ├── pnpm-workspace.yaml ├── public ├── favicon.ico ├── logo.png └── resource │ └── tinymce │ ├── langs │ ├── en.js │ └── zh_CN.js │ └── skins │ └── ui │ ├── oxide-dark │ ├── content.inline.min.css │ ├── content.min.css │ ├── content.mobile.min.css │ ├── skin.min.css │ ├── skin.mobile.min.css │ └── skin.shadowdom.min.css │ └── oxide │ ├── content.inline.min.css │ ├── content.min.css │ ├── content.mobile.min.css │ ├── fonts │ └── tinymce-mobile.woff │ ├── skin.min.css │ ├── skin.mobile.min.css │ └── skin.shadowdom.min.css ├── src ├── App.vue ├── api │ ├── demo │ │ ├── account.ts │ │ ├── cascader.ts │ │ ├── error.ts │ │ ├── model │ │ │ ├── accountModel.ts │ │ │ ├── areaModel.ts │ │ │ ├── optionsModel.ts │ │ │ ├── systemModel.ts │ │ │ └── tableModel.ts │ │ ├── select.ts │ │ ├── system.ts │ │ ├── table.ts │ │ └── tree.ts │ ├── model │ │ └── baseModel.ts │ └── sys │ │ ├── menu.ts │ │ ├── model │ │ ├── menuModel.ts │ │ ├── uploadModel.ts │ │ └── userModel.ts │ │ ├── upload.ts │ │ └── user.ts ├── assets │ ├── icons │ │ ├── concur.svg │ │ ├── download-count.svg │ │ ├── dynamic-avatar-1.svg │ │ ├── dynamic-avatar-2.svg │ │ ├── dynamic-avatar-3.svg │ │ ├── dynamic-avatar-4.svg │ │ ├── dynamic-avatar-5.svg │ │ ├── dynamic-avatar-6.svg │ │ ├── fu │ │ │ ├── ADLS.svg │ │ │ ├── DB2.svg │ │ │ ├── DB2_dark.svg │ │ │ ├── HANA.svg │ │ │ ├── S3.svg │ │ │ ├── apache.svg │ │ │ ├── apacheDerby.svg │ │ │ ├── apacheDerby_dark.svg │ │ │ ├── apacheIgnite.svg │ │ │ ├── apacheIgnite_dark.svg │ │ │ ├── apachePhoenix.svg │ │ │ ├── apache_dark.svg │ │ │ ├── athena.svg │ │ │ ├── azure.svg │ │ │ ├── bea.png │ │ │ ├── bigQuery.svg │ │ │ ├── cassandra.svg │ │ │ ├── cassandra_dark.svg │ │ │ ├── clickHouse.svg │ │ │ ├── cockroachDB.svg │ │ │ ├── couchbase.svg │ │ │ ├── duckDB.svg │ │ │ ├── duckDB_dark.svg │ │ │ ├── eclipse.svg │ │ │ ├── eclipse_dark.svg │ │ │ ├── elasticsearch.svg │ │ │ ├── exasol.svg │ │ │ ├── exasol_dark.svg │ │ │ ├── firebird.svg │ │ │ ├── googleCloudSpanner.svg │ │ │ ├── greenplum.svg │ │ │ ├── h2.svg │ │ │ ├── h2_dark.svg │ │ │ ├── hibernate.svg │ │ │ ├── hive.svg │ │ │ ├── hive_dark.svg │ │ │ ├── hsqldb.svg │ │ │ ├── ibm.svg │ │ │ ├── ibm_dark.svg │ │ │ ├── impala.svg │ │ │ ├── impala_dark.svg │ │ │ ├── informix.svg │ │ │ ├── intersystemCache.svg │ │ │ ├── intersystemCache_dark.svg │ │ │ ├── liquibase.svg │ │ │ ├── mariadb.svg │ │ │ ├── mariadb_dark.svg │ │ │ ├── microsoft.svg │ │ │ ├── mimerSQL.svg │ │ │ ├── mimerSQL_dark.svg │ │ │ ├── mongoDB.svg │ │ │ ├── mysql.svg │ │ │ ├── mysql_dark.svg │ │ │ ├── netezza.svg │ │ │ ├── netezza_dark.svg │ │ │ ├── openedge.svg │ │ │ ├── oracle-svgrepo-com.svg │ │ │ ├── oracle.svg │ │ │ ├── postgresql.svg │ │ │ ├── postgresql_dark.svg │ │ │ ├── presto.svg │ │ │ ├── presto_dark.svg │ │ │ ├── redis.svg │ │ │ ├── redis_dark.svg │ │ │ ├── redshift.svg │ │ │ ├── singlestore.svg │ │ │ ├── snowflake.svg │ │ │ ├── spark.svg │ │ │ ├── sqlServer.svg │ │ │ ├── sqlServer_dark.svg │ │ │ ├── sqlite.svg │ │ │ ├── sqlite_dark.svg │ │ │ ├── sun.svg │ │ │ ├── sun_dark.svg │ │ │ ├── sybase.svg │ │ │ ├── tarantool.svg │ │ │ ├── teradata.svg │ │ │ ├── tibero.svg │ │ │ ├── tibero_dark.svg │ │ │ ├── tidb.svg │ │ │ ├── trino.svg │ │ │ ├── trino_dark.svg │ │ │ ├── vertica.svg │ │ │ ├── vertica_dark.svg │ │ │ ├── yugabyte.svg │ │ │ └── yugabyte_dark.svg │ │ ├── jci.svg │ │ ├── moon.svg │ │ ├── onestream.svg │ │ ├── servicenow.svg │ │ ├── sharepoint.svg │ │ ├── sun.svg │ │ ├── test.svg │ │ ├── total-sales.svg │ │ ├── transaction.svg │ │ ├── unlock.svg │ │ ├── visit-count.svg │ │ └── workday.svg │ ├── images │ │ ├── demo.png │ │ ├── header.jpg │ │ └── logo.png │ └── svg │ │ ├── illustration.svg │ │ ├── login-bg-dark.svg │ │ ├── login-bg.svg │ │ ├── login-box-bg.svg │ │ ├── net-error.svg │ │ ├── no-data.svg │ │ └── preview │ │ ├── p-rotate.svg │ │ ├── resume.svg │ │ ├── scale.svg │ │ ├── unrotate.svg │ │ └── unscale.svg ├── components │ ├── Application │ │ ├── index.ts │ │ └── src │ │ │ ├── AppDarkModeToggle.vue │ │ │ ├── AppLocalePicker.vue │ │ │ ├── AppLogo.vue │ │ │ ├── AppProvider.vue │ │ │ ├── search │ │ │ ├── AppSearch.vue │ │ │ ├── AppSearchFooter.vue │ │ │ ├── AppSearchKeyItem.vue │ │ │ ├── AppSearchModal.vue │ │ │ └── useMenuSearch.ts │ │ │ └── useAppContext.ts │ ├── Authority │ │ ├── index.ts │ │ └── src │ │ │ └── Authority.vue │ ├── Basic │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicArrow.vue │ │ │ ├── BasicHelp.vue │ │ │ └── BasicTitle.vue │ ├── Button │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicButton.vue │ │ │ ├── PopConfirmButton.vue │ │ │ └── props.ts │ ├── CardList │ │ ├── index.ts │ │ └── src │ │ │ ├── CardList.vue │ │ │ └── data.ts │ ├── ClickOutSide │ │ ├── index.ts │ │ └── src │ │ │ └── ClickOutSide.vue │ ├── CodeEditor │ │ ├── index.ts │ │ └── src │ │ │ ├── CodeEditor.vue │ │ │ ├── codemirror │ │ │ ├── CodeMirror.vue │ │ │ ├── codeMirror.ts │ │ │ └── codemirror.css │ │ │ ├── json-preview │ │ │ └── JsonPreview.vue │ │ │ └── typing.ts │ ├── Container │ │ ├── index.ts │ │ └── src │ │ │ ├── ScrollContainer.vue │ │ │ ├── collapse │ │ │ ├── CollapseContainer.vue │ │ │ └── CollapseHeader.vue │ │ │ └── typing.ts │ ├── ContextMenu │ │ ├── index.ts │ │ └── src │ │ │ ├── ContextMenu.vue │ │ │ ├── createContextMenu.ts │ │ │ └── typing.ts │ ├── CountDown │ │ ├── index.ts │ │ └── src │ │ │ ├── CountButton.vue │ │ │ ├── CountdownInput.vue │ │ │ └── useCountdown.ts │ ├── CountTo │ │ ├── index.ts │ │ └── src │ │ │ └── CountTo.vue │ ├── Cropper │ │ ├── index.ts │ │ └── src │ │ │ ├── Cropper.vue │ │ │ ├── CropperAvatar.vue │ │ │ ├── CropperModal.vue │ │ │ └── typing.ts │ ├── Description │ │ ├── index.ts │ │ └── src │ │ │ ├── Description.vue │ │ │ ├── typing.ts │ │ │ └── useDescription.ts │ ├── Drawer │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicDrawer.vue │ │ │ ├── components │ │ │ ├── DrawerFooter.vue │ │ │ └── DrawerHeader.vue │ │ │ ├── props.ts │ │ │ ├── typing.ts │ │ │ └── useDrawer.ts │ ├── Dropdown │ │ ├── index.ts │ │ └── src │ │ │ ├── Dropdown.vue │ │ │ └── typing.ts │ ├── EllipsisText │ │ ├── index.ts │ │ └── src │ │ │ ├── EllipsisText.vue │ │ │ ├── Tooltip.vue │ │ │ └── _utils.ts │ ├── Excel │ │ ├── index.ts │ │ └── src │ │ │ ├── Export2Excel.ts │ │ │ ├── ExportExcelModal.vue │ │ │ ├── ImportExcel.vue │ │ │ └── typing.ts │ ├── FlowChart │ │ ├── index.ts │ │ └── src │ │ │ ├── FlowChart.vue │ │ │ ├── FlowChartToolbar.vue │ │ │ ├── adpterForTurbo.ts │ │ │ ├── config.ts │ │ │ ├── enum.ts │ │ │ ├── types.ts │ │ │ └── useFlowContext.ts │ ├── Form │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicForm.vue │ │ │ ├── componentMap.ts │ │ │ ├── components │ │ │ ├── ApiCascader.vue │ │ │ ├── ApiRadioGroup.vue │ │ │ ├── ApiSelect.vue │ │ │ ├── ApiTransfer.vue │ │ │ ├── ApiTree.vue │ │ │ ├── ApiTreeSelect.vue │ │ │ ├── DictSelect.vue │ │ │ ├── FormAction.vue │ │ │ ├── FormItem.vue │ │ │ ├── ImageUpload.vue │ │ │ ├── RadioButtonGroup.vue │ │ │ └── UserSelect.vue │ │ │ ├── helper.ts │ │ │ ├── hooks │ │ │ ├── useAdvanced.ts │ │ │ ├── useAutoFocus.ts │ │ │ ├── useComponentRegister.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormContext.ts │ │ │ ├── useFormEvents.ts │ │ │ ├── useFormValues.ts │ │ │ └── useLabelWidth.ts │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── form.ts │ │ │ ├── formItem.ts │ │ │ ├── hooks.ts │ │ │ └── index.ts │ ├── Icon │ │ ├── Icon.vue │ │ ├── data │ │ │ └── icons.data.ts │ │ ├── index.ts │ │ └── src │ │ │ ├── IconPicker.vue │ │ │ └── SvgIcon.vue │ ├── Loading │ │ ├── index.ts │ │ └── src │ │ │ ├── Loading.vue │ │ │ ├── createLoading.ts │ │ │ ├── typing.ts │ │ │ └── useLoading.ts │ ├── Markdown │ │ ├── index.ts │ │ └── src │ │ │ ├── Markdown.vue │ │ │ ├── MarkdownViewer.vue │ │ │ ├── getTheme.ts │ │ │ └── typing.ts │ ├── Menu │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicMenu.vue │ │ │ ├── components │ │ │ ├── BasicMenuItem.vue │ │ │ ├── BasicSubMenuItem.vue │ │ │ └── MenuItemContent.vue │ │ │ ├── index.less │ │ │ ├── props.ts │ │ │ ├── types.ts │ │ │ └── useOpenKeys.ts │ ├── Modal │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicModal.vue │ │ │ ├── components │ │ │ ├── Modal.tsx │ │ │ ├── ModalClose.vue │ │ │ ├── ModalFooter.vue │ │ │ ├── ModalHeader.vue │ │ │ └── ModalWrapper.vue │ │ │ ├── hooks │ │ │ ├── useModal.ts │ │ │ ├── useModalContext.ts │ │ │ ├── useModalDrag.ts │ │ │ └── useModalFullScreen.ts │ │ │ ├── index.less │ │ │ ├── props.ts │ │ │ └── typing.ts │ ├── Page │ │ ├── index.ts │ │ └── src │ │ │ ├── PageFooter.vue │ │ │ └── PageWrapper.vue │ ├── Preview │ │ ├── index.ts │ │ └── src │ │ │ ├── Functional.vue │ │ │ ├── Preview.vue │ │ │ ├── functional.ts │ │ │ └── typing.ts │ ├── Prompt │ │ ├── dialog.vue │ │ ├── index.ts │ │ └── state.ts │ ├── Qrcode │ │ ├── index.ts │ │ └── src │ │ │ ├── Qrcode.vue │ │ │ ├── drawCanvas.ts │ │ │ ├── drawLogo.ts │ │ │ ├── qrcodePlus.ts │ │ │ ├── toCanvas.ts │ │ │ └── typing.ts │ ├── Scrollbar │ │ ├── index.ts │ │ └── src │ │ │ ├── Scrollbar.vue │ │ │ ├── bar.ts │ │ │ ├── types.d.ts │ │ │ └── util.ts │ ├── SimpleMenu │ │ ├── index.ts │ │ └── src │ │ │ ├── SimpleMenu.vue │ │ │ ├── SimpleMenuTag.vue │ │ │ ├── SimpleSubMenu.vue │ │ │ ├── components │ │ │ ├── Menu.vue │ │ │ ├── MenuCollapseTransition.vue │ │ │ ├── MenuItem.vue │ │ │ ├── SubMenuItem.vue │ │ │ ├── menu.less │ │ │ ├── types.ts │ │ │ ├── useMenu.ts │ │ │ └── useSimpleMenuContext.ts │ │ │ ├── index.less │ │ │ ├── types.ts │ │ │ └── useOpenKeys.ts │ ├── StrengthMeter │ │ ├── index.ts │ │ └── src │ │ │ └── StrengthMeter.vue │ ├── Table │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicTable.vue │ │ │ ├── componentMap.ts │ │ │ ├── components │ │ │ ├── EditTableHeaderIcon.vue │ │ │ ├── HeaderCell.vue │ │ │ ├── TableAction.vue │ │ │ ├── TableFooter.vue │ │ │ ├── TableHeader.vue │ │ │ ├── TableImg.vue │ │ │ ├── TableTitle.vue │ │ │ ├── editable │ │ │ │ ├── CellComponent.ts │ │ │ │ ├── EditableCell.vue │ │ │ │ ├── helper.ts │ │ │ │ └── index.ts │ │ │ └── settings │ │ │ │ ├── ColumnSetting.vue │ │ │ │ ├── FullScreenSetting.vue │ │ │ │ ├── RedoSetting.vue │ │ │ │ ├── SizeSetting.vue │ │ │ │ └── index.vue │ │ │ ├── const.ts │ │ │ ├── hooks │ │ │ ├── useColumns.ts │ │ │ ├── useCustomRow.ts │ │ │ ├── useDataSource.ts │ │ │ ├── useLoading.ts │ │ │ ├── usePagination.tsx │ │ │ ├── useRowSelection.ts │ │ │ ├── useScrollTo.ts │ │ │ ├── useTable.ts │ │ │ ├── useTableContext.ts │ │ │ ├── useTableExpand.ts │ │ │ ├── useTableFooter.ts │ │ │ ├── useTableForm.ts │ │ │ ├── useTableHeader.ts │ │ │ ├── useTableScroll.ts │ │ │ └── useTableStyle.ts │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── column.ts │ │ │ ├── componentType.ts │ │ │ ├── pagination.ts │ │ │ ├── table.ts │ │ │ └── tableAction.ts │ ├── Time │ │ ├── index.ts │ │ └── src │ │ │ └── Time.vue │ ├── Tinymce │ │ ├── index.ts │ │ └── src │ │ │ ├── Editor.vue │ │ │ ├── ImgUpload.vue │ │ │ ├── helper.ts │ │ │ └── tinymce.ts │ ├── Transition │ │ ├── index.ts │ │ └── src │ │ │ ├── CollapseTransition.vue │ │ │ ├── CreateTransition.tsx │ │ │ └── ExpandTransition.ts │ ├── Tree │ │ ├── index.ts │ │ ├── src │ │ │ ├── BasicTree.vue │ │ │ ├── TreeIcon.ts │ │ │ ├── components │ │ │ │ └── TreeHeader.vue │ │ │ ├── hooks │ │ │ │ └── useTree.ts │ │ │ └── types │ │ │ │ └── tree.ts │ │ └── style │ │ │ ├── index.less │ │ │ └── index.ts │ ├── Upload │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicUpload.vue │ │ │ ├── FileList.vue │ │ │ ├── ThumbUrl.vue │ │ │ ├── UploadModal.vue │ │ │ ├── UploadPreviewModal.vue │ │ │ ├── data.tsx │ │ │ ├── helper.ts │ │ │ ├── props.ts │ │ │ ├── typing.ts │ │ │ └── useUpload.ts │ ├── Verify │ │ ├── index.ts │ │ └── src │ │ │ ├── DragVerify.vue │ │ │ ├── ImgRotate.vue │ │ │ ├── props.ts │ │ │ └── typing.ts │ ├── VirtualScroll │ │ ├── index.ts │ │ └── src │ │ │ └── VirtualScroll.vue │ ├── VxeTable │ │ ├── index.ts │ │ └── src │ │ │ ├── VxeBasicTable.tsx │ │ │ ├── componentMap.ts │ │ │ ├── componentType.ts │ │ │ ├── components │ │ │ ├── AApiSelect.tsx │ │ │ ├── AApiTreeSelect.tsx │ │ │ ├── AAutoComplete.tsx │ │ │ ├── AButton.tsx │ │ │ ├── AButtonGroup.tsx │ │ │ ├── ACascader.tsx │ │ │ ├── ACheckboxGroup.tsx │ │ │ ├── ADatePicker.tsx │ │ │ ├── AEmpty.tsx │ │ │ ├── AInput.tsx │ │ │ ├── AInputNumber.tsx │ │ │ ├── AInputSearch.tsx │ │ │ ├── AMonthPicker.tsx │ │ │ ├── ARadioGroup.tsx │ │ │ ├── ARangePicker.tsx │ │ │ ├── ARate.tsx │ │ │ ├── ASelect.tsx │ │ │ ├── ASwitch.tsx │ │ │ ├── ATimePicker.tsx │ │ │ ├── ATreeSelect.tsx │ │ │ ├── AWeekPicker.tsx │ │ │ ├── AYearPicker.tsx │ │ │ ├── common.tsx │ │ │ └── index.tsx │ │ │ ├── const.ts │ │ │ ├── css │ │ │ ├── common.scss │ │ │ ├── component.scss │ │ │ ├── index.scss │ │ │ ├── scrollbar.scss │ │ │ ├── toolbar.scss │ │ │ └── variable.scss │ │ │ ├── emits.ts │ │ │ ├── helper.ts │ │ │ ├── methods.ts │ │ │ ├── props.ts │ │ │ ├── setting.ts │ │ │ └── types.ts │ └── registerGlobComp.ts ├── design │ ├── ant │ │ ├── btn.less │ │ ├── index.less │ │ ├── input.less │ │ ├── pagination.less │ │ └── table.less │ ├── color.less │ ├── config.less │ ├── dark.less │ ├── entry.css │ ├── index.less │ ├── public.less │ ├── theme.less │ ├── transition │ │ ├── base.less │ │ ├── fade.less │ │ ├── index.less │ │ ├── scale.less │ │ ├── scroll.less │ │ ├── slide.less │ │ └── zoom.less │ └── var │ │ ├── breakpoint.less │ │ ├── easing.less │ │ └── index.less ├── directives │ ├── clickOutside.ts │ ├── ellipsis.ts │ ├── index.ts │ ├── loading.ts │ ├── permission.ts │ ├── repeatClick.ts │ └── ripple │ │ ├── index.less │ │ └── index.ts ├── enums │ ├── appEnum.ts │ ├── breakpointEnum.ts │ ├── cacheEnum.ts │ ├── exceptionEnum.ts │ ├── httpEnum.ts │ ├── menuEnum.ts │ ├── pageEnum.ts │ ├── roleEnum.ts │ └── sizeEnum.ts ├── hooks │ ├── component │ │ ├── useFormItem.ts │ │ └── usePageContext.ts │ ├── core │ │ └── useContext.ts │ ├── event │ │ ├── useBreakpoint.ts │ │ ├── useEventListener.ts │ │ └── useScroll.ts │ ├── setting │ │ ├── index.ts │ │ ├── useDarkModeTheme.ts │ │ ├── useHeaderSetting.ts │ │ ├── useMenuSetting.ts │ │ ├── useMultipleTabSetting.ts │ │ ├── useRootSetting.ts │ │ └── useTransitionSetting.ts │ └── web │ │ ├── useAppInject.ts │ │ ├── useContentHeight.ts │ │ ├── useContextMenu.ts │ │ ├── useDesign.ts │ │ ├── useECharts.ts │ │ ├── useFullContent.ts │ │ ├── useI18n.ts │ │ ├── useLockPage.ts │ │ ├── useMessage.tsx │ │ ├── usePage.ts │ │ ├── usePagination.ts │ │ ├── usePermission.ts │ │ ├── useScript.ts │ │ ├── useSortable.ts │ │ ├── useTabs.ts │ │ ├── useTitle.ts │ │ └── useWatermark.ts ├── layouts │ ├── default │ │ ├── content │ │ │ ├── index.vue │ │ │ ├── useContentContext.ts │ │ │ └── useContentViewHeight.ts │ │ ├── feature │ │ │ └── index.vue │ │ ├── footer │ │ │ └── index.vue │ │ ├── header │ │ │ ├── MultipleHeader.vue │ │ │ ├── components │ │ │ │ ├── Breadcrumb.vue │ │ │ │ ├── ChangeApi │ │ │ │ │ └── index.vue │ │ │ │ ├── ErrorAction.vue │ │ │ │ ├── FullScreen.vue │ │ │ │ ├── index.ts │ │ │ │ ├── lock │ │ │ │ │ └── LockModal.vue │ │ │ │ ├── notify │ │ │ │ │ ├── NoticeList.vue │ │ │ │ │ ├── data.ts │ │ │ │ │ └── index.vue │ │ │ │ └── user-dropdown │ │ │ │ │ ├── DropMenuItem.vue │ │ │ │ │ └── index.vue │ │ │ ├── index.less │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── menu │ │ │ ├── index.vue │ │ │ └── useLayoutMenu.ts │ │ ├── setting │ │ │ ├── SettingDrawer.tsx │ │ │ ├── components │ │ │ │ ├── InputNumberItem.vue │ │ │ │ ├── SelectItem.vue │ │ │ │ ├── SettingFooter.vue │ │ │ │ ├── SwitchItem.vue │ │ │ │ ├── ThemeColorPicker.vue │ │ │ │ ├── TypePicker.vue │ │ │ │ └── index.ts │ │ │ ├── enum.ts │ │ │ ├── handler.ts │ │ │ └── index.vue │ │ ├── sider │ │ │ ├── DragBar.vue │ │ │ ├── LayoutSider.vue │ │ │ ├── MixSider.vue │ │ │ ├── index.vue │ │ │ └── useLayoutSider.ts │ │ ├── tabs │ │ │ ├── components │ │ │ │ ├── FoldButton.vue │ │ │ │ ├── TabContent.vue │ │ │ │ └── TabRedo.vue │ │ │ ├── index.less │ │ │ ├── index.vue │ │ │ ├── types.ts │ │ │ ├── useMultipleTabs.ts │ │ │ └── useTabDropdown.ts │ │ └── trigger │ │ │ ├── HeaderTrigger.vue │ │ │ ├── SiderTrigger.vue │ │ │ └── index.vue │ ├── iframe │ │ ├── index.vue │ │ └── useFrameKeepAlive.ts │ └── page │ │ ├── index.vue │ │ └── transition.ts ├── locales │ ├── helper.ts │ ├── lang │ │ ├── en.ts │ │ ├── en │ │ │ ├── common.json │ │ │ ├── common.ts │ │ │ ├── component.json │ │ │ ├── component.ts │ │ │ ├── layout.json │ │ │ ├── layout.ts │ │ │ ├── routes │ │ │ │ ├── basic.json │ │ │ │ ├── basic.ts │ │ │ │ ├── dashboard.json │ │ │ │ ├── dashboard.ts │ │ │ │ ├── demo.json │ │ │ │ └── demo.ts │ │ │ ├── sys.json │ │ │ └── sys.ts │ │ ├── zh-CN │ │ │ ├── antdLocale │ │ │ │ └── DatePicker.json │ │ │ ├── common.json │ │ │ ├── common.ts │ │ │ ├── component.json │ │ │ ├── component.ts │ │ │ ├── layout.json │ │ │ ├── layout.ts │ │ │ ├── routes │ │ │ │ ├── basic.json │ │ │ │ ├── basic.ts │ │ │ │ ├── dashboard.json │ │ │ │ ├── dashboard.ts │ │ │ │ ├── demo.json │ │ │ │ └── demo.ts │ │ │ ├── sys.json │ │ │ └── sys.ts │ │ └── zh_CN.ts │ ├── setupI18n.ts │ └── useLocale.ts ├── logics │ ├── error-handle │ │ └── index.ts │ ├── initAppConfig.ts │ ├── mitt │ │ └── routeChange.ts │ └── theme │ │ ├── dark.ts │ │ ├── index.ts │ │ ├── updateBackground.ts │ │ ├── updateColorWeak.ts │ │ ├── updateGrayMode.ts │ │ └── util.ts ├── main.ts ├── router │ ├── constant.ts │ ├── guard │ │ ├── index.ts │ │ ├── paramMenuGuard.ts │ │ ├── permissionGuard.ts │ │ └── stateGuard.ts │ ├── helper │ │ ├── menuHelper.ts │ │ └── routeHelper.ts │ ├── index.ts │ ├── menus │ │ └── index.ts │ ├── routes │ │ ├── basic.ts │ │ ├── index.ts │ │ ├── mainOut.ts │ │ └── modules │ │ │ ├── about.ts │ │ │ ├── dashboard.ts │ │ │ └── form-design │ │ │ └── main.ts │ └── types.ts ├── settings │ ├── componentSetting.ts │ ├── designSetting.ts │ ├── encryptionSetting.ts │ ├── localeSetting.ts │ ├── projectSetting.ts │ └── siteSetting.ts ├── store │ ├── index.ts │ ├── modules │ │ ├── app.ts │ │ ├── errorLog.ts │ │ ├── locale.ts │ │ ├── lock.ts │ │ ├── multipleTab.ts │ │ ├── permission.ts │ │ └── user.ts │ └── plugin │ │ └── persist.ts ├── utils │ ├── __test__ │ │ └── index.test.ts │ ├── auth │ │ └── index.ts │ ├── bem.ts │ ├── cache │ │ ├── index.ts │ │ ├── memory.ts │ │ ├── persistent.ts │ │ └── storageCache.ts │ ├── cipher.ts │ ├── cipherOld.ts │ ├── color.ts │ ├── copyTextToClipboard.ts │ ├── dateUtil.ts │ ├── domUtils.ts │ ├── env.ts │ ├── event │ │ └── index.ts │ ├── factory │ │ └── createAsyncComponent.tsx │ ├── file │ │ ├── base64Conver.ts │ │ └── download.ts │ ├── helper │ │ ├── treeHelper.ts │ │ ├── tsxHelper.tsx │ │ └── validator.ts │ ├── http │ │ └── axios │ │ │ ├── Axios.ts │ │ │ ├── axiosCancel.ts │ │ │ ├── axiosRetry.ts │ │ │ ├── axiosTransform.ts │ │ │ ├── checkStatus.ts │ │ │ ├── helper.ts │ │ │ └── index.ts │ ├── index.ts │ ├── is.ts │ ├── lib │ │ └── echarts.ts │ ├── log.ts │ ├── mitt.ts │ ├── propTypes.ts │ ├── props.ts │ ├── types.ts │ └── uuid.ts └── views │ ├── dashboard │ ├── analysis │ │ ├── components │ │ │ ├── GrowCard.vue │ │ │ ├── SalesProductPie.vue │ │ │ ├── SiteAnalysis.vue │ │ │ ├── VisitAnalysis.vue │ │ │ ├── VisitAnalysisBar.vue │ │ │ ├── VisitRadar.vue │ │ │ ├── VisitSource.vue │ │ │ └── props.ts │ │ ├── data.ts │ │ └── index.vue │ └── workbench │ │ ├── components │ │ ├── DynamicInfo.vue │ │ ├── ProjectCard.vue │ │ ├── QuickNav.vue │ │ ├── SaleRadar.vue │ │ ├── WorkbenchHeader.vue │ │ └── data.ts │ │ └── index.vue │ ├── fuadmin │ ├── charts │ │ ├── Line.vue │ │ ├── Map.vue │ │ ├── Pie.vue │ │ ├── SaleRadar.vue │ │ ├── china.json │ │ ├── data.ts │ │ └── map │ │ │ ├── Baidu.vue │ │ │ ├── Gaode.vue │ │ │ └── Google.vue │ ├── main-out │ │ └── index.vue │ └── system │ │ ├── account │ │ ├── AccountDetail.vue │ │ ├── AccountDrawer.vue │ │ ├── DeptTree.vue │ │ ├── account.api.ts │ │ ├── account.data.ts │ │ ├── defaultAvatar.ts │ │ ├── index.vue │ │ └── setting │ │ │ ├── AccountBind.vue │ │ │ ├── BaseSetting.vue │ │ │ ├── MsgNotify.vue │ │ │ ├── SecureSetting.vue │ │ │ ├── data.ts │ │ │ └── index.vue │ │ ├── app │ │ ├── AppDrawer.vue │ │ ├── api.ts │ │ ├── data.ts │ │ └── index.vue │ │ ├── button │ │ ├── ButtonDrawer.vue │ │ ├── button.api.ts │ │ ├── button.data.ts │ │ └── index.vue │ │ ├── category_dict │ │ ├── CategoryDictDrawer.vue │ │ ├── category_dict.api.ts │ │ ├── category_dict.data.ts │ │ └── index.vue │ │ ├── data-dict │ │ ├── DictDrawer.vue │ │ ├── dict.api.ts │ │ ├── dict.data.ts │ │ ├── dict_item │ │ │ ├── DictItemDrawer.vue │ │ │ ├── dict_item.api.ts │ │ │ ├── dict_item.data.ts │ │ │ └── index.vue │ │ └── index.vue │ │ ├── dept │ │ ├── DeptDrawer.vue │ │ ├── dept.api.ts │ │ ├── dept.data.ts │ │ └── index.vue │ │ ├── file │ │ ├── Drawer.vue │ │ ├── api.ts │ │ ├── data.ts │ │ └── index.vue │ │ ├── log │ │ ├── celery_log │ │ │ ├── Drawer.vue │ │ │ ├── api.ts │ │ │ ├── data.ts │ │ │ └── index.vue │ │ ├── login-log │ │ │ ├── Drawer.vue │ │ │ ├── api.ts │ │ │ ├── data.ts │ │ │ └── index.vue │ │ ├── login_log │ │ │ ├── Drawer.vue │ │ │ ├── api.ts │ │ │ ├── data.ts │ │ │ └── index.vue │ │ ├── operation-log │ │ │ ├── Drawer.vue │ │ │ ├── api.ts │ │ │ ├── data.ts │ │ │ └── index.vue │ │ └── operation_log │ │ │ ├── Drawer.vue │ │ │ ├── api.ts │ │ │ ├── data.ts │ │ │ └── index.vue │ │ ├── menu │ │ ├── MenuDrawer.vue │ │ ├── add_button │ │ │ ├── MenuButtonDrawer.vue │ │ │ ├── MenuColumnFieldDrawer.vue │ │ │ ├── MenuColumnQuickDrawer.vue │ │ │ ├── index.vue │ │ │ ├── menu_button.api.ts │ │ │ ├── menu_button.data.ts │ │ │ ├── menu_column_field.api.ts │ │ │ └── menu_column_field.data.ts │ │ ├── index.vue │ │ ├── menu.api.ts │ │ └── menu.data.ts │ │ ├── monitor │ │ ├── api.ts │ │ ├── component │ │ │ ├── echartCard.vue │ │ │ ├── lineEchart.vue │ │ │ ├── props.ts │ │ │ └── statusCard.vue │ │ └── server.vue │ │ ├── password │ │ ├── index.vue │ │ └── pwd.data.ts │ │ ├── post │ │ ├── PostDrawer.vue │ │ ├── index.vue │ │ ├── post.api.ts │ │ └── post.data.ts │ │ └── role │ │ ├── PermissionDrawer │ │ ├── index.vue │ │ └── step │ │ │ ├── ButtonPermission.vue │ │ │ ├── ColumnPermission.vue │ │ │ ├── DataPermission.vue │ │ │ └── MenuPermission.vue │ │ ├── RoleDrawer.vue │ │ ├── index.vue │ │ ├── role-user │ │ ├── Drawer.vue │ │ ├── api.ts │ │ ├── data.ts │ │ └── index.vue │ │ ├── role.api.ts │ │ └── role.data.ts │ └── sys │ ├── about │ └── index.vue │ ├── error-log │ ├── DetailModal.vue │ ├── data.tsx │ └── index.vue │ ├── exception │ ├── Exception.vue │ └── index.ts │ ├── form-design │ ├── components │ │ ├── VFormCreate │ │ │ ├── components │ │ │ │ └── FormRender.vue │ │ │ └── index.vue │ │ ├── VFormDesign │ │ │ ├── components │ │ │ │ ├── CodeModal.vue │ │ │ │ ├── ComponentProps.vue │ │ │ │ ├── FormItemColumnProps.vue │ │ │ │ ├── FormItemProps.vue │ │ │ │ ├── FormNode.vue │ │ │ │ ├── FormNodeOperate.vue │ │ │ │ ├── FormOptions.vue │ │ │ │ ├── FormProps.vue │ │ │ │ ├── ImportJsonModal.vue │ │ │ │ ├── JsonModal.vue │ │ │ │ ├── LayoutItem.vue │ │ │ │ ├── PreviewCode.vue │ │ │ │ └── RuleProps.vue │ │ │ ├── config │ │ │ │ ├── componentPropsConfig.ts │ │ │ │ └── formItemPropsConfig.ts │ │ │ ├── index.vue │ │ │ ├── modules │ │ │ │ ├── CollapseItem.vue │ │ │ │ ├── FormComponentPanel.vue │ │ │ │ ├── PropsPanel.vue │ │ │ │ └── Toolbar.vue │ │ │ └── styles │ │ │ │ ├── drag.less │ │ │ │ └── variable.less │ │ ├── VFormItem │ │ │ ├── index.vue │ │ │ └── vFormItem.vue │ │ ├── VFormPreview │ │ │ ├── index.vue │ │ │ └── useForm.vue │ │ └── index.ts │ ├── core │ │ ├── formItemConfig.ts │ │ └── iconConfig.ts │ ├── examples │ │ └── baseForm.vue │ ├── hooks │ │ ├── useFormDesignState.ts │ │ ├── useFormInstanceMethods.ts │ │ └── useVFormMethods.ts │ ├── index.vue │ ├── tests │ │ └── import1.json │ ├── typings │ │ ├── base-type.ts │ │ ├── form-type.ts │ │ └── v-form-component.ts │ └── utils │ │ ├── index.ts │ │ └── message.ts │ ├── iframe │ ├── FrameBlank.vue │ └── index.vue │ ├── lock │ ├── LockPage.vue │ ├── index.vue │ └── useNow.ts │ ├── login │ ├── ForgetPasswordForm.vue │ ├── Login.vue │ ├── LoginForm.vue │ ├── LoginFormTitle.vue │ ├── MobileForm.vue │ ├── QrCodeForm.vue │ ├── RegisterForm.vue │ ├── SessionTimeoutLogin.vue │ └── useLogin.ts │ └── redirect │ └── index.vue ├── tsconfig.json ├── turbo.json ├── types ├── axios.d.ts ├── config.d.ts ├── global.d.ts ├── index.d.ts ├── module.d.ts ├── store.d.ts ├── utils.d.ts └── vue-router.d.ts └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | /backend/.venv 2 | /backend/.idea 3 | /web/.idea 4 | .idea 5 | 6 | .history/ -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | alembic/versions/ 2 | .idea/ 3 | logs/ 4 | static/ 5 | .venv/ -------------------------------------------------------------------------------- /backend/alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /backend/alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from typing import Sequence, Union 9 | 10 | from alembic import op 11 | import sqlalchemy as sa 12 | ${imports if imports else ""} 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = ${repr(up_revision)} 16 | down_revision: Union[str, None] = ${repr(down_revision)} 17 | branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} 18 | depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} 19 | 20 | 21 | def upgrade() -> None: 22 | ${upgrades if upgrades else "pass"} 23 | 24 | 25 | def downgrade() -> None: 26 | ${downgrades if downgrades else "pass"} 27 | -------------------------------------------------------------------------------- /backend/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/common/__init__.py -------------------------------------------------------------------------------- /backend/common/fu_pagination.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from typing import TypeVar 7 | 8 | from fastapi import Query 9 | from fastapi_pagination import Page, Params 10 | from fastapi_pagination.customization import ( 11 | CustomizedPage, 12 | UseParams, 13 | ) 14 | 15 | T = TypeVar("T") 16 | 17 | 18 | class PaginationParams(Params): 19 | page: int = Query(1, ge=1, description="Page number") 20 | size: int = Query(10, ge=1, le=100, alias="pageSize", description="Page size") 21 | 22 | 23 | FuPage = CustomizedPage[ 24 | Page[T], 25 | UseParams(PaginationParams) 26 | ] -------------------------------------------------------------------------------- /backend/common/fu_schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from datetime import datetime 7 | from pydantic import BaseModel, Field 8 | 9 | 10 | class FuSchema(BaseModel): 11 | id: str 12 | # sys_creator: str | None 13 | # sys_modifier: str | None 14 | sys_create_datetime: datetime | None = None 15 | sys_update_datetime: datetime | None = None 16 | sys_flag: bool | None = None 17 | sort: int | None = None 18 | 19 | 20 | class ImportSchema(BaseModel): 21 | path: str 22 | 23 | 24 | class IdSchema(BaseModel): 25 | id: str 26 | 27 | 28 | class FuFilters(BaseModel): 29 | sys_creator: str | None = Field(None) 30 | 31 | 32 | def response_success(data="success"): 33 | return {"detail": data} -------------------------------------------------------------------------------- /backend/common/utils/dabase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/common/utils/dabase/__init__.py -------------------------------------------------------------------------------- /backend/common/utils/server/public.json: -------------------------------------------------------------------------------- 1 | { 2 | "ERROR": "操作失败", 3 | "SUCCESS": "操作成功", 4 | "START": "启动", 5 | "STOP": "停止", 6 | "OFF": "停用", 7 | "ON": "启用", 8 | "OPEN": "打开", 9 | "CLOSE": "关闭", 10 | "SYS_EXEC_SUCCESS": "执行成功!", 11 | "SYS_REBOOT": "命令发送成功!" 12 | } -------------------------------------------------------------------------------- /backend/config/dev_env.py: -------------------------------------------------------------------------------- 1 | # ************** mysql数据库 配置 ************** # 2 | # ================================================= # 3 | # 数据库类型 MYSQL/SQLSERVER/SQLITE3/POSTGRESQL 4 | DATABASE_TYPE = "MYSQL" 5 | # 数据库地址 6 | DATABASE_HOST = "127.0.0.1" 7 | # 数据库端口 8 | DATABASE_PORT = 3306 9 | # 数据库用户名 10 | DATABASE_USER = "fuadmin" 11 | # 数据库密码 12 | DATABASE_PASSWORD = "fuadmin" 13 | # 数据库名 14 | DATABASE_NAME = "fu-fastapi-vue" 15 | 16 | IS_DEMO = False -------------------------------------------------------------------------------- /backend/config/prd_env.py: -------------------------------------------------------------------------------- 1 | # ************** mysql数据库 配置 ************** # 2 | # ================================================= # 3 | # 数据库类型 MYSQL/SQLSERVER/SQLITE3/POSTGRESQL 4 | DATABASE_TYPE = "POSTGRESQL" 5 | # 数据库地址 6 | DATABASE_HOST = "127.0.0.1" 7 | # 数据库端口 8 | DATABASE_PORT = 5432 9 | # 数据库用户名 10 | DATABASE_USER = "postgres" 11 | # 数据库密码 12 | DATABASE_PASSWORD = "Zhl939589097..." 13 | # 数据库名 14 | DATABASE_NAME = "fu-fastapi-vue" 15 | 16 | IS_DEMO = True -------------------------------------------------------------------------------- /backend/config/uat_env.py: -------------------------------------------------------------------------------- 1 | # ************** mysql数据库 配置 ************** # 2 | # ================================================= # 3 | # 数据库类型 MYSQL/SQLSERVER/SQLITE3/POSTGRESQL 4 | DATABASE_TYPE = "POSTGRESQL" 5 | # 数据库地址 6 | DATABASE_HOST = "124.222.210.96" 7 | # 数据库端口 8 | DATABASE_PORT = 5432 9 | # 数据库用户名 10 | DATABASE_USER = "fuadmin" 11 | # 数据库密码 12 | DATABASE_PASSWORD = "fuadmin" 13 | # 数据库名 14 | DATABASE_NAME = "fu-fastapi-vue" 15 | 16 | IS_DEMO = False -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.112.1 2 | fastapi-pagination==0.12.26 3 | SQLAlchemy~=2.0.32 4 | pydantic~=2.8.2 5 | alembic~=1.13.2 6 | uvicorn~=0.30.6 7 | python-jose==3.3.0 8 | cryptography==42.0.8 9 | # psycopg2-binary==2.9.9 10 | openpyxl==3.0.10 11 | # PostgreSQL异步驱动 12 | asyncpg==0.29.0 13 | # MySql异步驱动 14 | aiomysql==0.2.0 15 | PyJWT==2.9.0 16 | passlib[bcrypt]==1.7.4 17 | fastapi-async-sqlalchemy==0.6.1 18 | aiohttp==3.10.5 19 | psutil~=5.9.1 20 | redis~=5.0.8 21 | starlette~=0.37.2 22 | chardet==5.2.0 23 | psycopg2-binary==2.9.9 24 | mysqlclient==2.2.4 25 | user-agents==2.2.0 26 | python-multipart==0.0.9 -------------------------------------------------------------------------------- /backend/system/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/__init__.py -------------------------------------------------------------------------------- /backend/system/button/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/button/__init__.py -------------------------------------------------------------------------------- /backend/system/button/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from sqlalchemy import Column, String, Integer, ForeignKey 7 | from sqlalchemy.orm import relationship 8 | 9 | from common.fu_model import CoreModel 10 | from system.role.model import role_button_association 11 | 12 | 13 | class Button(CoreModel): 14 | __tablename__ = "sys_button" 15 | 16 | name = Column(String(64), unique=True, comment="岗位名称") 17 | code = Column(String(64), unique=True, comment="岗位代码") 18 | api = Column(String(200), comment="接口地址") 19 | method = Column(Integer, comment="请求方式") 20 | menu_id = Column(ForeignKey("sys_menu.id", ondelete="SET NULL"), comment="菜单ID") 21 | 22 | # 定义多对一关系 23 | menu = relationship("Menu", back_populates="button") 24 | 25 | # 定义多对多关系 26 | role = relationship( 27 | "Role", 28 | secondary=role_button_association, 29 | back_populates="button", 30 | ) -------------------------------------------------------------------------------- /backend/system/button/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from pydantic import BaseModel, Field 7 | 8 | from common.fu_schema import FuSchema, FuFilters 9 | 10 | 11 | class ButtonFilters(FuFilters): 12 | menu_id: str | None = Field(default=None, alias="menu_id") 13 | 14 | 15 | class ButtonBase(BaseModel): 16 | name: str = Field(default=None, alias="name") 17 | code: str = Field(default=None, alias="code") 18 | sort: int = Field(default=None, alias="sort") 19 | method: int | None = Field(default=None, alias="method") 20 | api: str | None = Field(default=None, alias="api") 21 | menu_id: str | None = Field(default=None, alias="menu_id") 22 | 23 | 24 | class ButtonIn(ButtonBase): 25 | pass 26 | 27 | 28 | class ButtonOut(ButtonBase, FuSchema): 29 | 30 | class Config: 31 | from_attributes = True -------------------------------------------------------------------------------- /backend/system/dept/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/dept/__init__.py -------------------------------------------------------------------------------- /backend/system/dept/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from sqlalchemy import ( 7 | Boolean, 8 | Column, 9 | String, 10 | ) 11 | 12 | from common.fu_model import CoreModel, UUIDStr 13 | 14 | 15 | class Dept(CoreModel): 16 | __tablename__ = "sys_dept" 17 | 18 | name = Column(String(64), nullable=False, comment="Department Name") 19 | owner = Column(String(32), nullable=True, comment="Department Owner") 20 | phone = Column(String(32), nullable=True, comment="Department Phone") 21 | email = Column(String(32), nullable=True, comment="Department Email") 22 | status = Column(Boolean, default=True, comment="Department Status") 23 | parent_id = Column(UUIDStr, nullable=True, comment="Department Parent") -------------------------------------------------------------------------------- /backend/system/dept/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from pydantic import BaseModel, Field 7 | 8 | from common.fu_schema import FuSchema, FuFilters 9 | 10 | 11 | class DeptFilters(FuFilters): 12 | name__like: str | None = Field(default=None, alias="name") 13 | 14 | 15 | class DeptBase(BaseModel): 16 | name: str 17 | owner: str | None = None 18 | phone: str | None = None 19 | email: str | None = None 20 | status: bool = True 21 | parent_id: str | None = None 22 | 23 | 24 | class DeptIn(DeptBase): 25 | pass 26 | 27 | 28 | class DeptOut(DeptBase, FuSchema): 29 | 30 | class Config: 31 | from_attributes = True -------------------------------------------------------------------------------- /backend/system/dict/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/dict/__init__.py -------------------------------------------------------------------------------- /backend/system/dict/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from sqlalchemy import Column, String, Boolean 7 | from common.fu_model import CoreModel 8 | 9 | 10 | class Dict(CoreModel): 11 | __tablename__ = "sys_dict" 12 | 13 | name = Column(String(100), unique=True, comment="数据字典名称") 14 | code = Column(String(100), unique=True, comment="数据字典代码") 15 | status = Column(Boolean, default=True, comment="数据字典状态") 16 | remark = Column(String(2000), comment="数据字典备注") -------------------------------------------------------------------------------- /backend/system/dict/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from pydantic import BaseModel, Field 7 | 8 | from common.fu_schema import FuSchema, FuFilters 9 | 10 | 11 | class DictFilters(FuFilters): 12 | name__like: str | None = Field(default=None, alias="name") 13 | code: str | None = Field(default=None, alias="code") 14 | 15 | 16 | class DictBase(BaseModel): 17 | name: str 18 | code: str 19 | status: bool 20 | sort: int 21 | 22 | 23 | class DictIn(DictBase): 24 | pass 25 | 26 | 27 | class DictOut(DictBase, FuSchema): 28 | 29 | class Config: 30 | from_attributes = True -------------------------------------------------------------------------------- /backend/system/dict_item/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/dict_item/__init__.py -------------------------------------------------------------------------------- /backend/system/dict_item/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from sqlalchemy import Column, String, ForeignKey, Boolean 7 | from sqlalchemy.orm import relationship 8 | 9 | from common.fu_model import CoreModel 10 | 11 | 12 | class DictItem(CoreModel): 13 | __tablename__ = "sys_dict_item" 14 | 15 | icon = Column(String(100), comment="数据字典项ICON") 16 | label = Column(String(100), comment="数据字典项名称") 17 | value = Column(String(100), comment="数据字典项值") 18 | status = Column(Boolean, default=True, comment="数据字典项状态") 19 | dict_id = Column( 20 | ForeignKey("sys_dict.id", ondelete="SET NULL"), comment="数据字典ID" 21 | ) -------------------------------------------------------------------------------- /backend/system/dict_item/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from pydantic import BaseModel, Field 7 | 8 | from common.fu_schema import FuSchema, FuFilters 9 | 10 | 11 | class DictItemFilters(FuFilters): 12 | name__like: str | None = Field(default=None, alias="name") 13 | code: str | None = Field(default=None, alias="code") 14 | 15 | 16 | class DictItemBase(BaseModel): 17 | icon: str 18 | label: str 19 | value: str 20 | status: bool 21 | dict_id: str 22 | 23 | 24 | class DictItemIn(DictItemBase): 25 | pass 26 | 27 | 28 | class DictItemOut(DictItemBase, FuSchema): 29 | 30 | class Config: 31 | from_attributes = True -------------------------------------------------------------------------------- /backend/system/file/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/file/__init__.py -------------------------------------------------------------------------------- /backend/system/file/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from sqlalchemy import Column, String, BigInteger 7 | from sqlalchemy.orm import relationship 8 | 9 | from common.fu_model import CoreModel, UUIDStr 10 | from system.user.model import user_post_association 11 | 12 | 13 | class File(CoreModel): 14 | __tablename__ = "sys_file" 15 | 16 | file_name = Column(String(255), comment="实际名称") 17 | save_name = Column(String(255), comment="存储名称") 18 | file_type = Column(String(50), comment="文件类型") 19 | url = Column(String(255), comment="文件路径") 20 | size = Column(BigInteger, comment="大小") 21 | md5sum = Column(UUIDStr, comment="文件md5") -------------------------------------------------------------------------------- /backend/system/file/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from pydantic import BaseModel, Field 7 | from common.fu_schema import FuSchema, FuFilters 8 | 9 | 10 | class FileFilters(FuFilters): 11 | file_name__like: str | None = Field(default=None, alias="name") 12 | 13 | 14 | class FileBase(BaseModel): 15 | file_name: str 16 | save_name: str 17 | size: int 18 | url: str 19 | 20 | 21 | class FileIn(BaseModel): 22 | name: str = Field(None, alias="name") 23 | url: str = Field(None, alias="url") 24 | 25 | 26 | class FileOut(FileBase, FuSchema): 27 | 28 | class Config: 29 | from_attributes = True 30 | 31 | 32 | class UploadFileIn(BaseModel): 33 | folder: str = Field("", alias="folder") -------------------------------------------------------------------------------- /backend/system/log_login/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/log_login/__init__.py -------------------------------------------------------------------------------- /backend/system/log_login/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from fastapi import Depends, APIRouter 7 | from common.fu_async_crud import retrieve 8 | 9 | from common.fu_pagination import FuPage 10 | from .model import LogLogin 11 | from .schema import LogLoginOut, LogLoginFilters 12 | 13 | router = APIRouter(tags=["LogLogin"]) 14 | 15 | 16 | @router.get("/login_log") 17 | async def query_login_logs(params: LogLoginFilters = Depends()) -> FuPage[LogLoginOut]: 18 | instance_list = await retrieve(LogLogin, LogLoginOut, filters=params) 19 | return instance_list -------------------------------------------------------------------------------- /backend/system/log_operation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/log_operation/__init__.py -------------------------------------------------------------------------------- /backend/system/log_operation/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from fastapi import Depends, APIRouter 7 | from common.fu_async_crud import retrieve 8 | 9 | from common.fu_pagination import FuPage 10 | from .model import LogOperation 11 | from .schema import LogOperationOut, LogOperationFilters 12 | 13 | router = APIRouter(tags=["LogOperation"]) 14 | 15 | 16 | @router.get("/operation_log") 17 | async def query_login_logs( 18 | params: LogOperationFilters = Depends(), 19 | ) -> FuPage[LogOperationOut]: 20 | instance_list = await retrieve(LogOperation, LogOperationOut, filters=params) 21 | return instance_list -------------------------------------------------------------------------------- /backend/system/login/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/login/__init__.py -------------------------------------------------------------------------------- /backend/system/login/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from pydantic import BaseModel 7 | 8 | 9 | class LoginOut(BaseModel): 10 | id: str 11 | token: str 12 | access_token: str 13 | expireTime: int 14 | username: str 15 | name: str 16 | 17 | 18 | class LoginIn(BaseModel): 19 | username: str = None 20 | password: str = None 21 | 22 | 23 | class UserInfoOut(BaseModel): 24 | username: str = None 25 | name: str = None 26 | avatar: str | None = None 27 | id: str = None -------------------------------------------------------------------------------- /backend/system/menu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/menu/__init__.py -------------------------------------------------------------------------------- /backend/system/monitor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/monitor/__init__.py -------------------------------------------------------------------------------- /backend/system/monitor/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from fastapi import APIRouter 7 | 8 | from common.utils.system import System 9 | 10 | router = APIRouter(tags=["Monitor"]) 11 | 12 | 13 | @router.get("/monitor") 14 | def monitor(): 15 | info = System().GetSystemAllInfo() 16 | return info -------------------------------------------------------------------------------- /backend/system/post/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/post/__init__.py -------------------------------------------------------------------------------- /backend/system/post/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from sqlalchemy import Column, String 7 | from sqlalchemy.orm import relationship 8 | 9 | from common.fu_model import CoreModel 10 | from system.user.model import user_post_association 11 | 12 | 13 | class Post(CoreModel): 14 | __tablename__ = "sys_post" 15 | 16 | name = Column(String(64), unique=True, comment="岗位名称") 17 | code = Column(String(64), unique=True, comment="岗位代码") 18 | 19 | user = relationship( 20 | "User", 21 | secondary=user_post_association, 22 | back_populates="post", 23 | ) -------------------------------------------------------------------------------- /backend/system/post/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @QQ : 939589097 3 | # @Time : 2024/9/14 00:13 4 | # @Author : 臧成龙 5 | # @Software: PyCharm 6 | from pydantic import BaseModel, Field 7 | from common.fu_schema import FuSchema, FuFilters 8 | 9 | 10 | class PostFilters(FuFilters): 11 | name__like: str | None = Field(default=None, alias="name") 12 | code: str | None = Field(default=None, alias="code") 13 | 14 | 15 | class PostBase(BaseModel): 16 | name: str 17 | code: str 18 | sort: int 19 | user: list | None = Field(default=None) 20 | 21 | 22 | class PostIn(PostBase): 23 | pass 24 | 25 | 26 | class PostOut(PostBase, FuSchema): 27 | 28 | class Config: 29 | from_attributes = True -------------------------------------------------------------------------------- /backend/system/role/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/role/__init__.py -------------------------------------------------------------------------------- /backend/system/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/backend/system/user/__init__.py -------------------------------------------------------------------------------- /backend/test_main.http: -------------------------------------------------------------------------------- 1 | # Test your FastAPI endpoints 2 | 3 | GET http://127.0.0.1:8000/ 4 | Accept: application/json 5 | 6 | ### 7 | 8 | GET http://127.0.0.1:8000/hello/User 9 | Accept: application/json 10 | 11 | ### 12 | 13 | -------------------------------------------------------------------------------- /web/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 5 | -------------------------------------------------------------------------------- /web/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset=utf-8 5 | end_of_line=lf 6 | insert_final_newline=true 7 | indent_style=space 8 | indent_size=2 9 | max_line_length = 100 10 | 11 | [*.{yml,yaml,json}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [Makefile] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /web/.env: -------------------------------------------------------------------------------- 1 | # spa-title 2 | VITE_GLOB_APP_TITLE = Fu Fast Api Vue 3 | -------------------------------------------------------------------------------- /web/.env.analyze: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Whether to enable gzip or brotli compression 8 | # Optional: gzip | brotli | none 9 | # If you need multiple forms, you can use `,` to separate 10 | VITE_BUILD_COMPRESS = 'none' 11 | 12 | 13 | # Basic interface address SPA 14 | VITE_GLOB_API_URL=/basic-api 15 | 16 | # File upload address, optional 17 | # It can be forwarded by nginx or write the actual address directly 18 | VITE_GLOB_UPLOAD_URL=/upload 19 | 20 | # Interface prefix 21 | VITE_GLOB_API_URL_PREFIX= 22 | 23 | VITE_ENABLE_ANALYZE = true 24 | -------------------------------------------------------------------------------- /web/.env.development: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = false 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Basic interface address SPA 8 | VITE_GLOB_API_URL=/basic-api 9 | 10 | # File upload address, optional 11 | VITE_GLOB_UPLOAD_URL=/upload 12 | 13 | # Interface prefix 14 | VITE_GLOB_API_URL_PREFIX= 15 | -------------------------------------------------------------------------------- /web/.env.docker: -------------------------------------------------------------------------------- 1 | # 是否开启mock 2 | VITE_USE_MOCK=false 3 | # 接口地址 可以由nginx做转发或者直接写实际地址 4 | VITE_GLOB_API_URL=http://124.222.210.96:8080 5 | # 文件上传地址 可以由nginx做转发或者直接写实际地址 6 | VITE_GLOB_UPLOAD_URL=http://124.222.210.96:8080/basic-api/api/system/upload 7 | # 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换 8 | VITE_GLOB_API_URL_PREFIX=/basic-api 9 | # 是否删除Console.log 10 | VITE_DROP_CONSOLE=true 11 | # 资源公共路径,需要以 / 开头和结尾 12 | VITE_PUBLIC_PATH=/ 13 | # 打包是否输出gz|br文件 14 | # 可选: gzip | brotli | none 15 | # 也可以有多个, 例如 ‘gzip’|'brotli',这样会同时生成 .gz和.br文件 16 | VITE_BUILD_COMPRESS = 'gzip' 17 | # 打包是否压缩图片 18 | VITE_USE_IMAGEMIN = false 19 | # 打包是否开启pwa功能 20 | VITE_USE_PWA = false 21 | # 是否兼容旧版浏览器。开启后打包时间会慢一倍左右。会多打出旧浏览器兼容包,且会根据浏览器兼容性自动使用相应的版本 22 | VITE_LEGACY = false 23 | -------------------------------------------------------------------------------- /web/.env.production: -------------------------------------------------------------------------------- 1 | # 是否开启mock 2 | VITE_USE_MOCK=false 3 | # 接口地址 可以由nginx做转发或者直接写实际地址 4 | VITE_GLOB_API_URL=http://124.222.210.96:9090 5 | # 文件上传地址 可以由nginx做转发或者直接写实际地址 6 | VITE_GLOB_UPLOAD_URL=http://124.222.210.96:9090/basic-api/api/system/upload 7 | # 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换 8 | VITE_GLOB_API_URL_PREFIX=/basic-api 9 | # 是否删除Console.log 10 | VITE_DROP_CONSOLE=true 11 | # 资源公共路径,需要以 / 开头和结尾 12 | VITE_PUBLIC_PATH=/ 13 | # 打包是否输出gz|br文件 14 | # 可选: gzip | brotli | none 15 | # 也可以有多个, 例如 ‘gzip’|'brotli',这样会同时生成 .gz和.br文件 16 | VITE_BUILD_COMPRESS = 'gzip' 17 | # 打包是否压缩图片 18 | VITE_USE_IMAGEMIN = false 19 | # 打包是否开启pwa功能 20 | VITE_USE_PWA = false 21 | # 是否兼容旧版浏览器。开启后打包时间会慢一倍左右。会多打出旧浏览器兼容包,且会根据浏览器兼容性自动使用相应的版本 22 | VITE_LEGACY = false 23 | -------------------------------------------------------------------------------- /web/.env.test: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | # Whether to open mock 3 | VITE_USE_MOCK = true 4 | 5 | # public path 6 | VITE_PUBLIC_PATH = / 7 | 8 | # Whether to enable gzip or brotli compression 9 | # Optional: gzip | brotli | none 10 | # If you need multiple forms, you can use `,` to separate 11 | VITE_BUILD_COMPRESS = 'none' 12 | 13 | # Basic interface address SPA 14 | VITE_GLOB_API_URL=/basic-api 15 | 16 | # File upload address, optional 17 | # It can be forwarded by nginx or write the actual address directly 18 | VITE_GLOB_UPLOAD_URL=/upload 19 | 20 | # Interface prefix 21 | VITE_GLOB_API_URL_PREFIX= 22 | -------------------------------------------------------------------------------- /web/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | *.md 5 | *.woff 6 | *.ttf 7 | .vscode 8 | .idea 9 | dist 10 | /public 11 | /docs 12 | .husky 13 | .local 14 | /bin 15 | Dockerfile 16 | -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben'], 4 | rules: { 5 | 'no-undef': 'off', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /web/.gitattributes: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings 2 | 3 | # Automatically normalize line endings (to LF) for all text-based files. 4 | * text=auto eol=lf 5 | 6 | # Declare files that will always have CRLF line endings on checkout. 7 | *.{cmd,[cC][mM][dD]} text eol=crlf 8 | *.{bat,[bB][aA][tT]} text eol=crlf 9 | 10 | # Denote all files that are truly binary and should not be modified. 11 | *.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | .cache 5 | .turbo 6 | 7 | tests/server/static 8 | tests/server/static/upload 9 | 10 | .local 11 | # local env files 12 | .env.local 13 | .env.*.local 14 | .eslintcache 15 | 16 | # Log files 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | pnpm-debug.log* 21 | 22 | # Editor directories and files 23 | .idea 24 | # .vscode 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | 31 | package-lock.json 32 | pnpm-lock.yaml 33 | 34 | .history 35 | -------------------------------------------------------------------------------- /web/.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 3344 3 | onOpen: open-preview 4 | tasks: 5 | - init: pnpm install 6 | command: pnpm run dev 7 | -------------------------------------------------------------------------------- /web/.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | PATH="/usr/local/bin:$PATH" 7 | 8 | npx --no-install commitlint --edit "$1" 9 | -------------------------------------------------------------------------------- /web/.husky/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command_exists () { 3 | command -v "$1" >/dev/null 2>&1 4 | } 5 | 6 | # Workaround for Windows 10, Git Bash and Yarn 7 | if command_exists winpty && test -t 1; then 8 | exec < /dev/tty 9 | fi 10 | -------------------------------------------------------------------------------- /web/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | [ -n "$CI" ] && exit 0 6 | 7 | PATH="/usr/local/bin:$PATH" 8 | 9 | # Format and submit code according to lintstagedrc.js configuration 10 | pnpm exec lint-staged 11 | -------------------------------------------------------------------------------- /web/.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=husky 2 | public-hoist-pattern[]=*eslint* 3 | public-hoist-pattern[]=*prettier* 4 | public-hoist-pattern[]=lint-staged 5 | public-hoist-pattern[]=*stylelint* 6 | public-hoist-pattern[]=@commitlint/cli 7 | public-hoist-pattern[]=@vben/eslint-config 8 | -------------------------------------------------------------------------------- /web/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .local 3 | .output.js 4 | node_modules 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | public 10 | .npmrc 11 | 12 | *-lock.yaml 13 | -------------------------------------------------------------------------------- /web/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | semi: true, 4 | vueIndentScriptAndStyle: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | proseWrap: 'never', 8 | htmlWhitespaceSensitivity: 'strict', 9 | endOfLine: 'auto', 10 | plugins: ['prettier-plugin-packagejson'], 11 | overrides: [ 12 | { 13 | files: '.*rc', 14 | options: { 15 | parser: 'json', 16 | }, 17 | }, 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /web/.stylelintignore: -------------------------------------------------------------------------------- 1 | dist 2 | public 3 | -------------------------------------------------------------------------------- /web/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/stylelint-config'], 4 | }; 5 | -------------------------------------------------------------------------------- /web/CNAME: -------------------------------------------------------------------------------- 1 | vben.vvbin.cn 2 | -------------------------------------------------------------------------------- /web/apps/portal-view/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/apps/portal-view/.gitkeep -------------------------------------------------------------------------------- /web/apps/test-server/README.md: -------------------------------------------------------------------------------- 1 | # Test Server 2 | 3 | It is used to start the test interface service, which can test the upload, websocket, login and other interfaces. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | 9 | cd ./test/server 10 | 11 | pnpm install 12 | 13 | pnpm run start 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /web/apps/test-server/controller/FileController.ts: -------------------------------------------------------------------------------- 1 | import FileService from '../service/FileService'; 2 | 3 | class FileController { 4 | private service: FileService = new FileService(); 5 | 6 | upload = async (ctx) => { 7 | const files = ctx.request.files.file; 8 | console.log(files); 9 | 10 | if (files.length === undefined) { 11 | this.service.upload(ctx, files, false); 12 | } else { 13 | this.service.upload(ctx, files, true); 14 | } 15 | }; 16 | } 17 | 18 | export default new FileController(); 19 | -------------------------------------------------------------------------------- /web/apps/test-server/controller/UserController.ts: -------------------------------------------------------------------------------- 1 | import UserService from '../service/UserService'; 2 | 3 | class UserController { 4 | private service: UserService = new UserService(); 5 | 6 | login = async (ctx) => { 7 | ctx.body = await this.service.login(); 8 | }; 9 | 10 | getUserInfoById = async (ctx) => { 11 | ctx.body = await this.service.getUserInfoById(); 12 | }; 13 | } 14 | 15 | export default new UserController(); 16 | -------------------------------------------------------------------------------- /web/apps/test-server/ecosystem.config.js: -------------------------------------------------------------------------------- 1 | const { name } = require('./package.json'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | apps: [ 6 | { 7 | name, 8 | script: path.resolve(__dirname, './dist/index.js'), 9 | instances: require('os').cpus().length, 10 | autorestart: true, 11 | watch: true, 12 | env_production: { 13 | NODE_ENV: 'production', 14 | PORT: 8080, 15 | }, 16 | }, 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /web/apps/test-server/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "exec": "ts-node -r tsconfig-paths/register index.ts", 5 | "events": { 6 | "restart": "clear" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/apps/test-server/routes.ts: -------------------------------------------------------------------------------- 1 | import UserController from './controller/UserController'; 2 | import FileController from './controller/FileController'; 3 | 4 | export default [ 5 | // user 6 | { 7 | path: '/login', 8 | method: 'post', 9 | action: UserController.login, 10 | }, 11 | { 12 | path: '/getUserInfoById', 13 | method: 'get', 14 | action: UserController.getUserInfoById, 15 | }, 16 | 17 | // file 18 | { 19 | path: '/upload', 20 | method: 'post', 21 | action: FileController.upload, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /web/apps/test-server/service/UserService.ts: -------------------------------------------------------------------------------- 1 | import { Result } from '../utils'; 2 | 3 | const fakeUserInfo = { 4 | userId: '1', 5 | username: 'vben', 6 | realName: 'Fu FastApi Vue', 7 | desc: 'manager', 8 | password: '123456', 9 | token: 'fakeToken1', 10 | roles: [ 11 | { 12 | roleName: 'Super Admin', 13 | value: 'super', 14 | }, 15 | ], 16 | }; 17 | export default class UserService { 18 | async login() { 19 | return Result.success(fakeUserInfo); 20 | } 21 | 22 | async getUserInfoById() { 23 | return Result.success(fakeUserInfo); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/apps/test-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node-server.json", 4 | "compilerOptions": { 5 | "noImplicitAny": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/apps/test-server/utils.ts: -------------------------------------------------------------------------------- 1 | export class Result { 2 | static success(data: any) { 3 | return { 4 | code: 0, 5 | success: true, 6 | result: data, 7 | }; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /web/internal/eslint-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /web/internal/eslint-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index', 'src/strict'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /web/internal/eslint-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /web/internal/stylelint-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /web/internal/stylelint-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /web/internal/stylelint-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /web/internal/ts-config/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Base", 4 | "compilerOptions": { 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "declaration": true, 10 | "noImplicitOverride": true, 11 | "noUnusedLocals": true, 12 | "esModuleInterop": true, 13 | "useUnknownInCatchVariables": false, 14 | "composite": false, 15 | "declarationMap": false, 16 | "forceConsistentCasingInFileNames": true, 17 | "inlineSources": false, 18 | "isolatedModules": true, 19 | "skipLibCheck": true, 20 | "noUnusedParameters": false, 21 | "preserveWatchOutput": true, 22 | "experimentalDecorators": true, 23 | "resolveJsonModule": true, 24 | "removeComments": true 25 | }, 26 | "exclude": ["**/node_modules/**", "**/dist/**"] 27 | } 28 | -------------------------------------------------------------------------------- /web/internal/ts-config/node-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node Server Config", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "module": "commonjs", 7 | "declaration": false, 8 | "removeComments": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": false, 13 | "esModuleInterop": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./" 16 | }, 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /web/internal/ts-config/node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node Config", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["ESNext"], 7 | "noImplicitAny": true, 8 | "sourceMap": true, 9 | "noEmit": true, 10 | "baseUrl": "./" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /web/internal/ts-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vben/ts-config", 3 | "version": "1.0.0", 4 | "private": true, 5 | "homepage": "https://github.com/vbenjs/vue-vben-admin", 6 | "bugs": { 7 | "url": "https://github.com/vbenjs/vue-vben-admin/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/vbenjs/vue-vben-admin.git", 12 | "directory": "internal/ts-config" 13 | }, 14 | "license": "MIT", 15 | "files": [ 16 | "base.json", 17 | "node.json", 18 | "vue-app.json", 19 | "node-server.json" 20 | ], 21 | "dependencies": { 22 | "@types/node": "^20.4.0", 23 | "vite": "^4.4.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/internal/ts-config/vue-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Vue Application", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "jsx": "preserve", 7 | "lib": ["ESNext", "DOM"], 8 | "noImplicitAny": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/internal/vite-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /web/internal/vite-config/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /web/internal/vite-config/src/config/common.ts: -------------------------------------------------------------------------------- 1 | import { presetTypography, presetUno } from 'unocss'; 2 | import UnoCSS from 'unocss/vite'; 3 | import { type UserConfig } from 'vite'; 4 | 5 | const commonConfig: (mode: string) => UserConfig = (mode) => ({ 6 | server: { 7 | host: true, 8 | }, 9 | esbuild: { 10 | drop: mode === 'production' ? ['console', 'debugger'] : [], 11 | }, 12 | build: { 13 | reportCompressedSize: false, 14 | chunkSizeWarningLimit: 1500, 15 | rollupOptions: { 16 | // TODO: Prevent memory overflow 17 | maxParallelFileOps: 3, 18 | }, 19 | }, 20 | plugins: [ 21 | UnoCSS({ 22 | presets: [presetUno(), presetTypography()], 23 | }), 24 | ], 25 | }); 26 | 27 | export { commonConfig }; 28 | -------------------------------------------------------------------------------- /web/internal/vite-config/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config/application'; 2 | export * from './config/package'; 3 | -------------------------------------------------------------------------------- /web/internal/vite-config/src/plugins/html.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Plugin to minimize and use ejs template syntax in index.html. 3 | * https://github.com/anncwb/vite-plugin-html 4 | */ 5 | import type { PluginOption } from 'vite'; 6 | import { createHtmlPlugin } from 'vite-plugin-html'; 7 | 8 | export function configHtmlPlugin({ isBuild }: { isBuild: boolean }) { 9 | const htmlPlugin: PluginOption[] = createHtmlPlugin({ 10 | minify: isBuild, 11 | }); 12 | return htmlPlugin; 13 | } 14 | -------------------------------------------------------------------------------- /web/internal/vite-config/src/plugins/mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock plugin for development and production. 3 | * https://github.com/anncwb/vite-plugin-mock 4 | */ 5 | import { viteMockServe } from 'vite-plugin-mock'; 6 | 7 | export function configMockPlugin({ isBuild }: { isBuild: boolean }) { 8 | return viteMockServe({ 9 | ignore: /^_/, 10 | mockPath: 'mock', 11 | localEnabled: !isBuild, 12 | prodEnabled: isBuild, 13 | injectCode: ` 14 | import { setupProdMockServer } from '../mock/_createProductionServer'; 15 | 16 | setupProdMockServer(); 17 | `, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /web/internal/vite-config/src/plugins/svgSprite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vite Plugin for fast creating SVG sprites. 3 | * https://github.com/anncwb/vite-plugin-svg-icons 4 | */ 5 | 6 | import { resolve } from 'node:path'; 7 | 8 | import type { PluginOption } from 'vite'; 9 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; 10 | 11 | export function configSvgIconsPlugin({ isBuild }: { isBuild: boolean }) { 12 | const svgIconsPlugin = createSvgIconsPlugin({ 13 | iconDirs: [resolve(process.cwd(), 'src/assets/icons')], 14 | svgoOptions: isBuild, 15 | }); 16 | return svgIconsPlugin as PluginOption; 17 | } 18 | -------------------------------------------------------------------------------- /web/internal/vite-config/src/plugins/visualizer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Package file volume analysis 3 | */ 4 | import visualizer from 'rollup-plugin-visualizer'; 5 | import { type PluginOption } from 'vite'; 6 | 7 | export function configVisualizerConfig() { 8 | return visualizer({ 9 | filename: './node_modules/.cache/visualizer/stats.html', 10 | open: true, 11 | gzipSize: true, 12 | brotliSize: true, 13 | }) as PluginOption; 14 | } 15 | -------------------------------------------------------------------------------- /web/internal/vite-config/src/utils/hash.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from 'node:crypto'; 2 | 3 | function createContentHash(content: string, hashLSize = 12) { 4 | const hash = createHash('sha256').update(content); 5 | return hash.digest('hex').slice(0, hashLSize); 6 | } 7 | function strToHex(str: string) { 8 | const result: string[] = []; 9 | for (let i = 0; i < str.length; ++i) { 10 | const hex = str.charCodeAt(i).toString(16); 11 | result.push(('000' + hex).slice(-4)); 12 | } 13 | return result.join('').toUpperCase(); 14 | } 15 | 16 | export { createContentHash, strToHex }; 17 | -------------------------------------------------------------------------------- /web/internal/vite-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/node.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /web/mock/demo/select-demo.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | import { resultSuccess } from '../_util'; 3 | 4 | const demoList = (keyword, count = 20) => { 5 | const result = { 6 | list: [] as any[], 7 | }; 8 | for (let index = 0; index < count; index++) { 9 | result.list.push({ 10 | name: `${keyword ?? ''}选项${index}`, 11 | id: `${index}`, 12 | }); 13 | } 14 | return result; 15 | }; 16 | 17 | export default [ 18 | { 19 | url: '/basic-api/select/getDemoOptions', 20 | timeout: 1000, 21 | method: 'get', 22 | response: ({ query }) => { 23 | const { keyword, count } = query; 24 | console.log(keyword); 25 | return resultSuccess(demoList(keyword, count)); 26 | }, 27 | }, 28 | ] as MockMethod[]; 29 | -------------------------------------------------------------------------------- /web/packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/packages/.gitkeep -------------------------------------------------------------------------------- /web/packages/hooks/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /web/packages/hooks/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /web/packages/hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vben/hooks", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/vbenjs/vue-vben-admin", 5 | "bugs": { 6 | "url": "https://github.com/vbenjs/vue-vben-admin/issues" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/vbenjs/vue-vben-admin.git", 11 | "directory": "packages/hooks" 12 | }, 13 | "license": "MIT", 14 | "sideEffects": false, 15 | "exports": { 16 | ".": { 17 | "default": "./src/index.ts" 18 | } 19 | }, 20 | "main": "./src/index.ts", 21 | "module": "./src/index.ts", 22 | "files": [ 23 | "dist" 24 | ], 25 | "scripts": { 26 | "//build": "pnpm unbuild", 27 | "//stub": "pnpm unbuild --stub", 28 | "clean": "pnpm rimraf .turbo node_modules dist", 29 | "lint": "pnpm eslint ." 30 | }, 31 | "dependencies": { 32 | "@vueuse/core": "^10.2.1", 33 | "vue": "^3.3.4" 34 | }, 35 | "devDependencies": { 36 | "@vben/types": "workspace:*" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/packages/hooks/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './onMountedOrActivated'; 2 | export * from './useAttrs'; 3 | export * from './useRefs'; 4 | export * from './useScrollTo'; 5 | export * from './useWindowSizeFn'; 6 | export { useTimeoutFn } from '@vueuse/core'; 7 | -------------------------------------------------------------------------------- /web/packages/hooks/src/onMountedOrActivated.ts: -------------------------------------------------------------------------------- 1 | import { type AnyFunction } from '@vben/types'; 2 | import { nextTick, onActivated, onMounted } from 'vue'; 3 | 4 | /** 5 | * 在 OnMounted 或者 OnActivated 时触发 6 | * @param hook 任何函数(包括异步函数) 7 | */ 8 | function onMountedOrActivated(hook: AnyFunction) { 9 | let mounted: boolean; 10 | 11 | onMounted(() => { 12 | hook(); 13 | nextTick(() => { 14 | mounted = true; 15 | }); 16 | }); 17 | 18 | onActivated(() => { 19 | if (mounted) { 20 | hook(); 21 | } 22 | }); 23 | } 24 | 25 | export { onMountedOrActivated }; 26 | -------------------------------------------------------------------------------- /web/packages/hooks/src/useRefs.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentPublicInstance, Ref } from 'vue'; 2 | import { onBeforeUpdate, shallowRef } from 'vue'; 3 | 4 | function useRefs(): { 5 | refs: Ref; 6 | setRefs: (index: number) => (el: Element | ComponentPublicInstance | null) => void; 7 | } { 8 | const refs = shallowRef([]) as Ref; 9 | 10 | onBeforeUpdate(() => { 11 | refs.value = []; 12 | }); 13 | 14 | const setRefs = (index: number) => (el: Element | ComponentPublicInstance | null) => { 15 | refs.value[index] = el as T; 16 | }; 17 | 18 | return { 19 | refs, 20 | setRefs, 21 | }; 22 | } 23 | 24 | export { useRefs }; 25 | -------------------------------------------------------------------------------- /web/packages/hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/vue-app.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /web/packages/types/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /web/packages/types/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | entries: ['src/index'], 6 | declaration: true, 7 | rollup: { 8 | emitCJS: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /web/packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vben/types", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/vbenjs/vue-vben-admin", 5 | "bugs": { 6 | "url": "https://github.com/vbenjs/vue-vben-admin/issues" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/vbenjs/vue-vben-admin.git", 11 | "directory": "packages/types" 12 | }, 13 | "license": "MIT", 14 | "sideEffects": false, 15 | "exports": { 16 | ".": { 17 | "default": "./src/index.ts" 18 | } 19 | }, 20 | "main": "./src/index.ts", 21 | "module": "./src/index.ts", 22 | "files": [ 23 | "dist" 24 | ], 25 | "scripts": { 26 | "//build": "pnpm unbuild", 27 | "//stub": "pnpm unbuild --stub", 28 | "clean": "pnpm rimraf .turbo node_modules dist", 29 | "lint": "pnpm eslint ." 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /web/packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /web/packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/vue-app.json", 4 | "include": ["src"] 5 | } 6 | -------------------------------------------------------------------------------- /web/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'internal/*' 3 | - 'packages/*' 4 | - 'apps/*' 5 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/public/logo.png -------------------------------------------------------------------------------- /web/public/resource/tinymce/skins/ui/oxide-dark/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | -------------------------------------------------------------------------------- /web/public/resource/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 8 | -------------------------------------------------------------------------------- /web/public/resource/tinymce/skins/ui/oxide/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | -------------------------------------------------------------------------------- /web/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /web/public/resource/tinymce/skins/ui/oxide/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 8 | -------------------------------------------------------------------------------- /web/src/api/demo/account.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { GetAccountInfoModel } from './model/accountModel'; 3 | 4 | enum Api { 5 | ACCOUNT_INFO = '/account/getAccountInfo', 6 | SESSION_TIMEOUT = '/user/sessionTimeout', 7 | TOKEN_EXPIRED = '/user/tokenExpired', 8 | } 9 | 10 | // Get personal center-basic settings 11 | 12 | export const accountInfoApi = () => defHttp.get({ url: Api.ACCOUNT_INFO }); 13 | 14 | export const sessionTimeoutApi = () => defHttp.post({ url: Api.SESSION_TIMEOUT }); 15 | 16 | export const tokenExpiredApi = () => defHttp.post({ url: Api.TOKEN_EXPIRED }); 17 | -------------------------------------------------------------------------------- /web/src/api/demo/cascader.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel'; 3 | 4 | enum Api { 5 | AREA_RECORD = '/cascader/getAreaRecord', 6 | } 7 | 8 | export const areaRecord = (data: AreaParams) => 9 | defHttp.post({ url: Api.AREA_RECORD, data }); 10 | -------------------------------------------------------------------------------- /web/src/api/demo/error.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | 3 | enum Api { 4 | // The address does not exist 5 | Error = '/error', 6 | } 7 | 8 | /** 9 | * @description: Trigger ajax error 10 | */ 11 | 12 | export const fireErrorApi = () => defHttp.get({ url: Api.Error }); 13 | -------------------------------------------------------------------------------- /web/src/api/demo/model/accountModel.ts: -------------------------------------------------------------------------------- 1 | export interface GetAccountInfoModel { 2 | email: string; 3 | name: string; 4 | introduction: string; 5 | phone: string; 6 | address: string; 7 | } 8 | -------------------------------------------------------------------------------- /web/src/api/demo/model/areaModel.ts: -------------------------------------------------------------------------------- 1 | export interface AreaModel { 2 | id: string; 3 | code: string; 4 | parentCode: string; 5 | name: string; 6 | levelType: number; 7 | [key: string]: string | number; 8 | } 9 | 10 | export interface AreaParams { 11 | parentCode: string; 12 | } 13 | -------------------------------------------------------------------------------- /web/src/api/demo/model/optionsModel.ts: -------------------------------------------------------------------------------- 1 | import { BasicFetchResult } from '/@/api/model/baseModel'; 2 | 3 | export interface DemoOptionsItem { 4 | name: string; 5 | id: string; 6 | } 7 | 8 | export interface selectParams { 9 | id: number | string; 10 | } 11 | 12 | /** 13 | * @description: Request list return value 14 | */ 15 | export type DemoOptionsGetResultModel = BasicFetchResult; 16 | -------------------------------------------------------------------------------- /web/src/api/demo/model/tableModel.ts: -------------------------------------------------------------------------------- 1 | import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel'; 2 | /** 3 | * @description: Request list interface parameters 4 | */ 5 | export type DemoParams = Partial; 6 | 7 | export interface DemoListItem { 8 | id: string; 9 | beginTime: string; 10 | endTime: string; 11 | address: string; 12 | name: string; 13 | no: number; 14 | status: number; 15 | } 16 | 17 | /** 18 | * @description: Request list return value 19 | */ 20 | export type DemoListGetResultModel = BasicFetchResult; 21 | -------------------------------------------------------------------------------- /web/src/api/demo/select.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { DemoOptionsItem, selectParams } from './model/optionsModel'; 3 | 4 | enum Api { 5 | OPTIONS_LIST = '/select/getDemoOptions', 6 | } 7 | 8 | /** 9 | * @description: Get sample options value 10 | */ 11 | export const optionsListApi = (params?: selectParams) => 12 | defHttp.get({ url: Api.OPTIONS_LIST, params }); 13 | -------------------------------------------------------------------------------- /web/src/api/demo/table.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { DemoParams, DemoListGetResultModel } from './model/tableModel'; 3 | 4 | enum Api { 5 | DEMO_LIST = '/table/getDemoList', 6 | } 7 | 8 | /** 9 | * @description: Get sample list value 10 | */ 11 | 12 | export const demoListApi = (params: DemoParams) => 13 | defHttp.get({ 14 | url: Api.DEMO_LIST, 15 | params, 16 | headers: { 17 | // @ts-ignore 18 | ignoreCancelToken: true, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /web/src/api/demo/tree.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | 3 | enum Api { 4 | TREE_OPTIONS_LIST = '/tree/getDemoOptions', 5 | } 6 | 7 | /** 8 | * @description: Get sample options value 9 | */ 10 | export const treeOptionsListApi = (params?: Recordable) => 11 | defHttp.get({ url: Api.TREE_OPTIONS_LIST, params }); 12 | -------------------------------------------------------------------------------- /web/src/api/model/baseModel.ts: -------------------------------------------------------------------------------- 1 | export interface BasicPageParams { 2 | page: number; 3 | pageSize: number; 4 | } 5 | 6 | export interface BasicFetchResult { 7 | items: T[]; 8 | total: number; 9 | } 10 | -------------------------------------------------------------------------------- /web/src/api/sys/menu.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '/@/utils/http/axios'; 2 | import { getMenuListResultModel } from './model/menuModel'; 3 | 4 | enum Api { 5 | GetMenuList = '/api/system/menu/route/tree', 6 | } 7 | 8 | /** 9 | * @description: Get user menu based on id 10 | */ 11 | 12 | export const getMenuList = () => { 13 | return defHttp.get({ url: Api.GetMenuList }); 14 | }; 15 | -------------------------------------------------------------------------------- /web/src/api/sys/model/menuModel.ts: -------------------------------------------------------------------------------- 1 | import type { RouteMeta } from 'vue-router'; 2 | 3 | export interface RouteItem { 4 | path: string; 5 | component: any; 6 | meta: RouteMeta; 7 | name?: string; 8 | alias?: string | string[]; 9 | redirect?: string; 10 | caseSensitive?: boolean; 11 | children?: RouteItem[]; 12 | } 13 | 14 | /** 15 | * @description: Get menu return value 16 | */ 17 | export type getMenuListResultModel = RouteItem[]; 18 | -------------------------------------------------------------------------------- /web/src/api/sys/model/uploadModel.ts: -------------------------------------------------------------------------------- 1 | export interface UploadApiResult { 2 | message: string; 3 | code: number; 4 | url: string; 5 | } 6 | -------------------------------------------------------------------------------- /web/src/api/sys/model/userModel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Login interface parameters 3 | */ 4 | export interface LoginParams { 5 | username: string; 6 | password: string; 7 | } 8 | 9 | export interface RoleInfo { 10 | roleName: string; 11 | value: string; 12 | } 13 | 14 | /** 15 | * @description: Login interface return value 16 | */ 17 | export interface LoginResultModel { 18 | userId: string | number; 19 | token: string; 20 | roles: RoleInfo[]; 21 | } 22 | 23 | /** 24 | * @description: Get user information return value 25 | */ 26 | export interface GetUserInfoModel { 27 | roles: RoleInfo[]; 28 | // 用户id 29 | userId: string | number; 30 | // 用户账号 31 | username: string; 32 | // 用户姓名 33 | name: string; 34 | // 头像 35 | avatar: string; 36 | // 介绍 37 | desc?: string; 38 | } 39 | -------------------------------------------------------------------------------- /web/src/api/sys/upload.ts: -------------------------------------------------------------------------------- 1 | import { UploadApiResult } from './model/uploadModel'; 2 | import { defHttp } from '/@/utils/http/axios'; 3 | import { UploadFileParams } from '/#/axios'; 4 | import { useGlobSetting } from '/@/hooks/setting'; 5 | import { AxiosProgressEvent } from 'axios'; 6 | 7 | const { uploadUrl = '' } = useGlobSetting(); 8 | 9 | /** 10 | * @description: Upload interface 11 | */ 12 | export function uploadApi( 13 | params: UploadFileParams, 14 | onUploadProgress: (progressEvent: AxiosProgressEvent) => void, 15 | ) { 16 | return defHttp.uploadFile( 17 | { 18 | url: uploadUrl, 19 | onUploadProgress, 20 | }, 21 | params, 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/ADLS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/bea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/src/assets/icons/fu/bea.png -------------------------------------------------------------------------------- /web/src/assets/icons/fu/clickHouse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/duckDB.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/duckDB_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/exasol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/exasol_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/hibernate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/intersystemCache.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/intersystemCache_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/microsoft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/netezza.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/netezza_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/openedge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/oracle-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/oracle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/sybase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/teradata.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/tibero.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/tibero_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/tidb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/vertica.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/src/assets/icons/fu/vertica_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/src/assets/icons/onestream.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/src/assets/images/demo.png -------------------------------------------------------------------------------- /web/src/assets/images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/src/assets/images/header.jpg -------------------------------------------------------------------------------- /web/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuAdmin/fu-fastapi-vue/3472d67ce2964228d02c00d70c54dfaa3775c00f/web/src/assets/images/logo.png -------------------------------------------------------------------------------- /web/src/assets/svg/preview/resume.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | 3 | import appLogo from './src/AppLogo.vue'; 4 | import appProvider from './src/AppProvider.vue'; 5 | import appSearch from './src/search/AppSearch.vue'; 6 | import appLocalePicker from './src/AppLocalePicker.vue'; 7 | import appDarkModeToggle from './src/AppDarkModeToggle.vue'; 8 | 9 | export { useAppProviderContext } from './src/useAppContext'; 10 | 11 | export const AppLogo = withInstall(appLogo); 12 | export const AppProvider = withInstall(appProvider); 13 | export const AppSearch = withInstall(appSearch); 14 | export const AppLocalePicker = withInstall(appLocalePicker); 15 | export const AppDarkModeToggle = withInstall(appDarkModeToggle); 16 | -------------------------------------------------------------------------------- /web/src/components/Application/src/search/AppSearchKeyItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 13 | -------------------------------------------------------------------------------- /web/src/components/Application/src/useAppContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey, Ref } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface AppProviderContextProps { 5 | prefixCls: Ref; 6 | isMobile: Ref; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createAppProviderContext(context: AppProviderContextProps) { 12 | return createContext(context, key); 13 | } 14 | 15 | export function useAppProviderContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /web/src/components/Authority/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import authority from './src/Authority.vue'; 3 | 4 | export const Authority = withInstall(authority); 5 | -------------------------------------------------------------------------------- /web/src/components/Basic/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import basicArrow from './src/BasicArrow.vue'; 3 | import basicTitle from './src/BasicTitle.vue'; 4 | import basicHelp from './src/BasicHelp.vue'; 5 | 6 | export const BasicArrow = withInstall(basicArrow); 7 | export const BasicTitle = withInstall(basicTitle); 8 | export const BasicHelp = withInstall(basicHelp); 9 | -------------------------------------------------------------------------------- /web/src/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import type { ExtractPropTypes } from 'vue'; 3 | import button from './src/BasicButton.vue'; 4 | import popConfirmButton from './src/PopConfirmButton.vue'; 5 | import { buttonProps } from './src/props'; 6 | 7 | export const Button = withInstall(button); 8 | export const PopConfirmButton = withInstall(popConfirmButton); 9 | export declare type ButtonProps = Partial>; 10 | -------------------------------------------------------------------------------- /web/src/components/Button/src/props.ts: -------------------------------------------------------------------------------- 1 | const validColors = ['primary', 'error', 'warning', 'success', ''] as const; 2 | type ButtonColorType = (typeof validColors)[number]; 3 | 4 | export const buttonProps = { 5 | color: { 6 | type: String as PropType, 7 | validator: (v) => validColors.includes(v), 8 | default: '', 9 | }, 10 | loading: { type: Boolean }, 11 | disabled: { type: Boolean }, 12 | /** 13 | * Text before icon. 14 | */ 15 | preIcon: { type: String }, 16 | /** 17 | * Text after icon. 18 | */ 19 | postIcon: { type: String }, 20 | /** 21 | * preIcon and postIcon icon size. 22 | * @default: 14 23 | */ 24 | iconSize: { type: Number, default: 14 }, 25 | onClick: { type: [Function, Array] as PropType<(() => any) | (() => any)[]>, default: null }, 26 | }; 27 | -------------------------------------------------------------------------------- /web/src/components/CardList/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import cardList from './src/CardList.vue'; 3 | 4 | export const CardList = withInstall(cardList); 5 | -------------------------------------------------------------------------------- /web/src/components/CardList/src/data.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | // 每行个数 3 | export const grid = ref(12); 4 | // slider属性 5 | export const useSlider = (min = 6, max = 12) => { 6 | // 每行显示个数滑动条 7 | const getMarks = () => { 8 | const l = {}; 9 | for (let i = min; i < max + 1; i++) { 10 | l[i] = { 11 | style: { 12 | color: '#fff', 13 | }, 14 | label: i, 15 | }; 16 | } 17 | return l; 18 | }; 19 | return { 20 | min, 21 | max, 22 | marks: getMarks(), 23 | step: 1, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /web/src/components/ClickOutSide/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import clickOutSide from './src/ClickOutSide.vue'; 3 | 4 | export const ClickOutSide = withInstall(clickOutSide); 5 | -------------------------------------------------------------------------------- /web/src/components/ClickOutSide/src/ClickOutSide.vue: -------------------------------------------------------------------------------- 1 | 6 | 21 | -------------------------------------------------------------------------------- /web/src/components/CodeEditor/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import codeEditor from './src/CodeEditor.vue'; 3 | import jsonPreview from './src/json-preview/JsonPreview.vue'; 4 | 5 | export const CodeEditor = withInstall(codeEditor); 6 | export const JsonPreview = withInstall(jsonPreview); 7 | 8 | export * from './src/typing'; 9 | -------------------------------------------------------------------------------- /web/src/components/CodeEditor/src/codemirror/codeMirror.ts: -------------------------------------------------------------------------------- 1 | import CodeMirror from 'codemirror'; 2 | import './codemirror.css'; 3 | import 'codemirror/theme/idea.css'; 4 | import 'codemirror/theme/material-palenight.css'; 5 | // import 'codemirror/addon/lint/lint.css'; 6 | 7 | // modes 8 | import 'codemirror/mode/javascript/javascript'; 9 | import 'codemirror/mode/css/css'; 10 | import 'codemirror/mode/htmlmixed/htmlmixed'; 11 | // addons 12 | // import 'codemirror/addon/edit/closebrackets'; 13 | // import 'codemirror/addon/edit/closetag'; 14 | // import 'codemirror/addon/comment/comment'; 15 | // import 'codemirror/addon/fold/foldcode'; 16 | // import 'codemirror/addon/fold/foldgutter'; 17 | // import 'codemirror/addon/fold/brace-fold'; 18 | // import 'codemirror/addon/fold/indent-fold'; 19 | // import 'codemirror/addon/lint/json-lint'; 20 | // import 'codemirror/addon/fold/comment-fold'; 21 | export { CodeMirror }; 22 | -------------------------------------------------------------------------------- /web/src/components/CodeEditor/src/json-preview/JsonPreview.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /web/src/components/CodeEditor/src/typing.ts: -------------------------------------------------------------------------------- 1 | export enum MODE { 2 | JSON = 'application/json', 3 | HTML = 'htmlmixed', 4 | JS = 'javascript', 5 | } 6 | -------------------------------------------------------------------------------- /web/src/components/Container/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import collapseContainer from './src/collapse/CollapseContainer.vue'; 3 | import scrollContainer from './src/ScrollContainer.vue'; 4 | 5 | export const CollapseContainer = withInstall(collapseContainer); 6 | export const ScrollContainer = withInstall(scrollContainer); 7 | 8 | export * from './src/typing'; 9 | -------------------------------------------------------------------------------- /web/src/components/Container/src/typing.ts: -------------------------------------------------------------------------------- 1 | export type ScrollType = 'default' | 'main'; 2 | 3 | export interface CollapseContainerOptions { 4 | canExpand?: boolean; 5 | title?: string; 6 | helpMessage?: Array | string; 7 | } 8 | export interface ScrollContainerOptions { 9 | enableScroll?: boolean; 10 | type?: ScrollType; 11 | } 12 | 13 | export type ScrollActionType = RefType<{ 14 | scrollBottom: () => void; 15 | getScrollWrap: () => Nullable; 16 | scrollTo: (top: number) => void; 17 | }>; 18 | -------------------------------------------------------------------------------- /web/src/components/ContextMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { createContextMenu, destroyContextMenu } from './src/createContextMenu'; 2 | 3 | export * from './src/typing'; 4 | -------------------------------------------------------------------------------- /web/src/components/ContextMenu/src/typing.ts: -------------------------------------------------------------------------------- 1 | export interface Axis { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | export interface ContextMenuItem { 7 | label: string; 8 | icon?: string; 9 | hidden?: boolean; 10 | disabled?: boolean; 11 | handler?: Fn; 12 | divider?: boolean; 13 | children?: ContextMenuItem[]; 14 | } 15 | export interface CreateContextOptions { 16 | event: MouseEvent; 17 | icon?: string; 18 | styles?: any; 19 | items?: ContextMenuItem[]; 20 | } 21 | 22 | export interface ContextMenuProps { 23 | event?: MouseEvent; 24 | styles?: any; 25 | items: ContextMenuItem[]; 26 | customEvent?: MouseEvent; 27 | axis?: Axis; 28 | width?: number; 29 | showIcon?: boolean; 30 | } 31 | 32 | export interface ItemContentProps { 33 | showIcon: boolean | undefined; 34 | item: ContextMenuItem; 35 | handler: Fn; 36 | } 37 | -------------------------------------------------------------------------------- /web/src/components/CountDown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import countButton from './src/CountButton.vue'; 3 | import countdownInput from './src/CountdownInput.vue'; 4 | 5 | export const CountdownInput = withInstall(countdownInput); 6 | export const CountButton = withInstall(countButton); 7 | -------------------------------------------------------------------------------- /web/src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import countTo from './src/CountTo.vue'; 3 | 4 | export const CountTo = withInstall(countTo); 5 | -------------------------------------------------------------------------------- /web/src/components/Cropper/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import cropperImage from './src/Cropper.vue'; 3 | import avatarCropper from './src/CropperAvatar.vue'; 4 | 5 | export * from './src/typing'; 6 | export const CropperImage = withInstall(cropperImage); 7 | export const CropperAvatar = withInstall(avatarCropper); 8 | -------------------------------------------------------------------------------- /web/src/components/Cropper/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type Cropper from 'cropperjs'; 2 | 3 | export interface CropendResult { 4 | imgBase64: string; 5 | imgInfo: Cropper.Data; 6 | } 7 | 8 | export type { Cropper }; 9 | -------------------------------------------------------------------------------- /web/src/components/Description/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import description from './src/Description.vue'; 3 | 4 | export * from './src/typing'; 5 | export { useDescription } from './src/useDescription'; 6 | export const Description = withInstall(description); 7 | -------------------------------------------------------------------------------- /web/src/components/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import basicDrawer from './src/BasicDrawer.vue'; 3 | 4 | export const BasicDrawer = withInstall(basicDrawer); 5 | export * from './src/typing'; 6 | export { useDrawer, useDrawerInner } from './src/useDrawer'; 7 | -------------------------------------------------------------------------------- /web/src/components/Dropdown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import dropdown from './src/Dropdown.vue'; 3 | 4 | export * from './src/typing'; 5 | export const Dropdown = withInstall(dropdown); 6 | -------------------------------------------------------------------------------- /web/src/components/Dropdown/src/typing.ts: -------------------------------------------------------------------------------- 1 | export interface DropMenu { 2 | onClick?: Fn; 3 | to?: string; 4 | icon?: string; 5 | event: string | number; 6 | text: string; 7 | disabled?: boolean; 8 | divider?: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /web/src/components/EllipsisText/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | import ellipsisText from './src/EllipsisText.vue'; 3 | 4 | export const EllipsisText = withInstall(ellipsisText); 5 | -------------------------------------------------------------------------------- /web/src/components/Excel/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import impExcel from './src/ImportExcel.vue'; 3 | import expExcelModal from './src/ExportExcelModal.vue'; 4 | 5 | export const ImpExcel = withInstall(impExcel); 6 | export const ExpExcelModal = withInstall(expExcelModal); 7 | export * from './src/typing'; 8 | export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel'; 9 | -------------------------------------------------------------------------------- /web/src/components/FlowChart/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import flowChart from './src/FlowChart.vue'; 3 | 4 | export const FlowChart = withInstall(flowChart); 5 | -------------------------------------------------------------------------------- /web/src/components/FlowChart/src/enum.ts: -------------------------------------------------------------------------------- 1 | export enum ToolbarTypeEnum { 2 | ZOOM_IN = 'zoomIn', 3 | ZOOM_OUT = 'zoomOut', 4 | RESET_ZOOM = 'resetZoom', 5 | 6 | UNDO = 'undo', 7 | REDO = 'redo', 8 | 9 | SNAPSHOT = 'snapshot', 10 | VIEW_DATA = 'viewData', 11 | } 12 | -------------------------------------------------------------------------------- /web/src/components/FlowChart/src/types.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfig } from '@logicflow/core'; 2 | import { ToolbarTypeEnum } from './enum'; 3 | 4 | export interface NodeItem extends NodeConfig { 5 | icon: string; 6 | } 7 | 8 | export interface ToolbarConfig { 9 | type?: string | ToolbarTypeEnum; 10 | tooltip?: string | boolean; 11 | icon?: string; 12 | disabled?: boolean; 13 | separate?: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /web/src/components/FlowChart/src/useFlowContext.ts: -------------------------------------------------------------------------------- 1 | import type LogicFlow from '@logicflow/core'; 2 | 3 | import { provide, inject } from 'vue'; 4 | 5 | const key = Symbol('flow-chart'); 6 | 7 | type Instance = { 8 | logicFlow: LogicFlow; 9 | }; 10 | 11 | export function createFlowChartContext(instance: Instance) { 12 | provide(key, instance); 13 | } 14 | 15 | export function useFlowChartContext(): Instance { 16 | return inject(key) as Instance; 17 | } 18 | -------------------------------------------------------------------------------- /web/src/components/Form/src/hooks/useComponentRegister.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentType } from '../types/index'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | import { add, del } from '../componentMap'; 4 | import type { Component } from 'vue'; 5 | 6 | export function useComponentRegister(compName: ComponentType, comp: Component) { 7 | add(compName, comp); 8 | tryOnUnmounted(() => { 9 | del(compName); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /web/src/components/Form/src/hooks/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface FormContextProps { 5 | resetAction: () => Promise; 6 | submitAction: () => Promise; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createFormContext(context: FormContextProps) { 12 | return createContext(context, key); 13 | } 14 | 15 | export function useFormContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /web/src/components/Form/src/types/hooks.ts: -------------------------------------------------------------------------------- 1 | export interface AdvanceState { 2 | isAdvanced: boolean; 3 | hideAdvanceBtn: boolean; 4 | isLoad: boolean; 5 | actionSpan: number; 6 | } 7 | -------------------------------------------------------------------------------- /web/src/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import SvgIcon from './src/SvgIcon.vue'; 3 | import IconPicker from './src/IconPicker.vue'; 4 | 5 | export { IconPicker, SvgIcon }; 6 | -------------------------------------------------------------------------------- /web/src/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | import Loading from './src/Loading.vue'; 2 | 3 | export { Loading }; 4 | export { useLoading } from './src/useLoading'; 5 | export { createLoading } from './src/createLoading'; 6 | -------------------------------------------------------------------------------- /web/src/components/Loading/src/typing.ts: -------------------------------------------------------------------------------- 1 | import { SizeEnum } from '/@/enums/sizeEnum'; 2 | 3 | export interface LoadingProps { 4 | tip: string; 5 | size: SizeEnum; 6 | absolute: boolean; 7 | loading: boolean; 8 | background: string; 9 | theme: 'dark' | 'light'; 10 | } 11 | -------------------------------------------------------------------------------- /web/src/components/Markdown/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import markDown from './src/Markdown.vue'; 3 | import markDownViewer from './src/MarkdownViewer.vue'; 4 | 5 | export const MarkDown = withInstall(markDown); 6 | export const MarkdownViewer = withInstall(markDownViewer); 7 | export * from './src/typing'; 8 | -------------------------------------------------------------------------------- /web/src/components/Markdown/src/getTheme.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取主题类型 深色浅色模式 对应的值 3 | * @param darkModeVal 深色模式值 4 | * @param themeMode 主题类型——外观(默认), 内容, 代码块 5 | */ 6 | export const getTheme = ( 7 | darkModeVal: 'light' | 'dark' | string, 8 | themeMode: 'default' | 'content' | 'code' = 'default', 9 | ) => { 10 | const isDark = darkModeVal === 'dark'; 11 | switch (themeMode) { 12 | case 'default': 13 | return isDark ? 'dark' : 'classic'; 14 | case 'content': 15 | return isDark ? 'dark' : 'light'; 16 | case 'code': 17 | return isDark ? 'dracula' : 'github'; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /web/src/components/Markdown/src/typing.ts: -------------------------------------------------------------------------------- 1 | import Vditor from 'vditor'; 2 | 3 | export interface MarkDownActionType { 4 | getVditor: () => Vditor; 5 | } 6 | -------------------------------------------------------------------------------- /web/src/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import BasicMenu from './src/BasicMenu.vue'; 2 | 3 | export { BasicMenu }; 4 | -------------------------------------------------------------------------------- /web/src/components/Menu/src/components/BasicMenuItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 22 | -------------------------------------------------------------------------------- /web/src/components/Menu/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Key = string | number; 2 | export interface MenuState { 3 | // 默认选中的列表 4 | defaultSelectedKeys: Key[]; 5 | 6 | // 缩进 7 | inlineIndent?: number; 8 | 9 | // 展开数组 10 | openKeys: Key[]; 11 | 12 | // 当前选中的菜单项 key 数组 13 | selectedKeys: Key[]; 14 | 15 | // 收缩状态下展开的数组 16 | collapsedOpenKeys: Key[]; 17 | } 18 | -------------------------------------------------------------------------------- /web/src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import './src/index.less'; 3 | import basicModal from './src/BasicModal.vue'; 4 | 5 | export const BasicModal = withInstall(basicModal); 6 | export { useModalContext } from './src/hooks/useModalContext'; 7 | export { useModal, useModalInner } from './src/hooks/useModal'; 8 | export * from './src/typing'; 9 | -------------------------------------------------------------------------------- /web/src/components/Modal/src/hooks/useModalContext.ts: -------------------------------------------------------------------------------- 1 | import { InjectionKey } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface ModalContextProps { 5 | redoModalHeight: () => void; 6 | } 7 | 8 | const key: InjectionKey = Symbol(); 9 | 10 | export function createModalContext(context: ModalContextProps) { 11 | return createContext(context, key); 12 | } 13 | 14 | export function useModalContext() { 15 | return useContext(key); 16 | } 17 | -------------------------------------------------------------------------------- /web/src/components/Page/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | 3 | import pageFooter from './src/PageFooter.vue'; 4 | import pageWrapper from './src/PageWrapper.vue'; 5 | 6 | export const PageFooter = withInstall(pageFooter); 7 | export const PageWrapper = withInstall(pageWrapper); 8 | -------------------------------------------------------------------------------- /web/src/components/Preview/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImagePreview } from './src/Preview.vue'; 2 | export { createImgPreview } from './src/functional'; 3 | -------------------------------------------------------------------------------- /web/src/components/Preview/src/functional.ts: -------------------------------------------------------------------------------- 1 | import type { Options, Props } from './typing'; 2 | import ImgPreview from './Functional.vue'; 3 | import { isClient } from '/@/utils/is'; 4 | import { createVNode, render } from 'vue'; 5 | 6 | let instance: ReturnType | null = null; 7 | export function createImgPreview(options: Options) { 8 | if (!isClient) return; 9 | const propsData: Partial = {}; 10 | const container = document.createElement('div'); 11 | Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options); 12 | 13 | instance = createVNode(ImgPreview, propsData); 14 | render(instance, container); 15 | document.body.appendChild(container); 16 | return instance.component?.exposed; 17 | } 18 | -------------------------------------------------------------------------------- /web/src/components/Qrcode/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import qrCode from './src/Qrcode.vue'; 3 | 4 | export const QrCode = withInstall(qrCode); 5 | export * from './src/typing'; 6 | -------------------------------------------------------------------------------- /web/src/components/Qrcode/src/qrcodePlus.ts: -------------------------------------------------------------------------------- 1 | // 参考 qr-code-with-logo 进行ts版本修改 2 | import { toCanvas } from './toCanvas'; 3 | 4 | export * from './typing'; 5 | export { toCanvas }; 6 | -------------------------------------------------------------------------------- /web/src/components/Qrcode/src/toCanvas.ts: -------------------------------------------------------------------------------- 1 | import { renderQrCode } from './drawCanvas'; 2 | import { drawLogo } from './drawLogo'; 3 | import { RenderQrCodeParams } from './typing'; 4 | 5 | export const toCanvas = (options: RenderQrCodeParams) => { 6 | return renderQrCode(options) 7 | .then(() => { 8 | return options; 9 | }) 10 | .then(drawLogo) as Promise; 11 | }; 12 | -------------------------------------------------------------------------------- /web/src/components/Scrollbar/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * copy from element-ui 3 | */ 4 | 5 | import Scrollbar from './src/Scrollbar.vue'; 6 | 7 | export { Scrollbar }; 8 | export type { ScrollbarType } from './src/types'; 9 | -------------------------------------------------------------------------------- /web/src/components/Scrollbar/src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface BarMapItem { 2 | offset: string; 3 | scroll: string; 4 | scrollSize: string; 5 | size: string; 6 | key: string; 7 | axis: string; 8 | client: string; 9 | direction: string; 10 | } 11 | export interface BarMap { 12 | vertical: BarMapItem; 13 | horizontal: BarMapItem; 14 | } 15 | 16 | export interface ScrollbarType { 17 | wrap: ElRef; 18 | } 19 | -------------------------------------------------------------------------------- /web/src/components/SimpleMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SimpleMenu } from './src/SimpleMenu.vue'; 2 | -------------------------------------------------------------------------------- /web/src/components/SimpleMenu/src/components/types.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from 'vue'; 2 | 3 | export interface Props { 4 | theme: string; 5 | activeName?: string | number | undefined; 6 | openNames: string[]; 7 | accordion: boolean; 8 | width: string; 9 | collapsedWidth: string; 10 | indentSize: number; 11 | collapse: boolean; 12 | activeSubMenuNames: (string | number)[]; 13 | } 14 | 15 | export interface SubMenuProvider { 16 | addSubMenu: (name: string | number, update?: boolean) => void; 17 | removeSubMenu: (name: string | number, update?: boolean) => void; 18 | removeAll: () => void; 19 | sliceIndex: (index: number) => void; 20 | isRemoveAllPopup: Ref; 21 | getOpenNames: () => (string | number)[]; 22 | handleMouseleave?: Fn; 23 | level: number; 24 | props: Props; 25 | } 26 | -------------------------------------------------------------------------------- /web/src/components/SimpleMenu/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface MenuState { 2 | activeName: string; 3 | openNames: string[]; 4 | activeSubMenuNames: string[]; 5 | } 6 | -------------------------------------------------------------------------------- /web/src/components/StrengthMeter/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import strengthMeter from './src/StrengthMeter.vue'; 3 | 4 | export const StrengthMeter = withInstall(strengthMeter); 5 | -------------------------------------------------------------------------------- /web/src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/BasicTable.vue'; 2 | export { default as TableAction } from './src/components/TableAction.vue'; 3 | export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; 4 | export { default as TableImg } from './src/components/TableImg.vue'; 5 | 6 | export * from './src/types/table'; 7 | export * from './src/types/pagination'; 8 | export * from './src/types/tableAction'; 9 | export { useTable } from './src/hooks/useTable'; 10 | export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'; 11 | export type { EditRecordRow } from './src/components/editable'; 12 | -------------------------------------------------------------------------------- /web/src/components/Table/src/components/EditTableHeaderIcon.vue: -------------------------------------------------------------------------------- 1 | 8 | 18 | -------------------------------------------------------------------------------- /web/src/components/Table/src/components/editable/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from '../../types/componentType'; 2 | import { useI18n } from '/@/hooks/web/useI18n'; 3 | 4 | const { t } = useI18n(); 5 | 6 | /** 7 | * @description: 生成placeholder 8 | */ 9 | export function createPlaceholderMessage(component: ComponentType) { 10 | if (component.includes('Input') || component.includes('AutoComplete')) { 11 | return t('common.inputText'); 12 | } 13 | if (component.includes('Picker')) { 14 | return t('common.chooseText'); 15 | } 16 | 17 | if ( 18 | component.includes('Select') || 19 | component.includes('Checkbox') || 20 | component.includes('Radio') || 21 | component.includes('Switch') || 22 | component.includes('DatePicker') || 23 | component.includes('TimePicker') 24 | ) { 25 | return t('common.chooseText'); 26 | } 27 | return ''; 28 | } 29 | -------------------------------------------------------------------------------- /web/src/components/Table/src/components/settings/RedoSetting.vue: -------------------------------------------------------------------------------- 1 | 9 | 34 | -------------------------------------------------------------------------------- /web/src/components/Table/src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { ref, ComputedRef, unref, computed, watch } from 'vue'; 2 | import type { BasicTableProps } from '../types/table'; 3 | 4 | export function useLoading(props: ComputedRef) { 5 | const loadingRef = ref(unref(props).loading); 6 | 7 | watch( 8 | () => unref(props).loading, 9 | (loading) => { 10 | loadingRef.value = loading; 11 | }, 12 | ); 13 | 14 | const getLoading = computed(() => unref(loadingRef)); 15 | 16 | function setLoading(loading: boolean) { 17 | loadingRef.value = loading; 18 | } 19 | 20 | return { getLoading, setLoading }; 21 | } 22 | -------------------------------------------------------------------------------- /web/src/components/Table/src/hooks/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import type { BasicTableProps, TableActionType } from '../types/table'; 3 | import { provide, inject, ComputedRef } from 'vue'; 4 | 5 | const key = Symbol('basic-table'); 6 | 7 | type Instance = TableActionType & { 8 | wrapRef: Ref>; 9 | getBindValues: ComputedRef; 10 | }; 11 | 12 | type RetInstance = Omit & { 13 | getBindValues: ComputedRef; 14 | }; 15 | 16 | export function createTableContext(instance: Instance) { 17 | provide(key, instance); 18 | } 19 | 20 | export function useTableContext(): RetInstance { 21 | return inject(key) as RetInstance; 22 | } 23 | -------------------------------------------------------------------------------- /web/src/components/Table/src/hooks/useTableStyle.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef } from 'vue'; 2 | import type { BasicTableProps, TableCustomRecord } from '../types/table'; 3 | import { unref } from 'vue'; 4 | import { isFunction } from '/@/utils/is'; 5 | 6 | export function useTableStyle(propsRef: ComputedRef, prefixCls: string) { 7 | function getRowClassName(record: TableCustomRecord, index: number) { 8 | const { striped, rowClassName } = unref(propsRef); 9 | const classNames: string[] = []; 10 | if (striped) { 11 | classNames.push((index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : ''); 12 | } 13 | if (rowClassName && isFunction(rowClassName)) { 14 | classNames.push(rowClassName(record, index)); 15 | } 16 | return classNames.filter((cls) => !!cls).join(' '); 17 | } 18 | 19 | return { getRowClassName }; 20 | } 21 | -------------------------------------------------------------------------------- /web/src/components/Table/src/types/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'Input' 3 | | 'InputNumber' 4 | | 'Select' 5 | | 'ApiSelect' 6 | | 'DictSelect' 7 | | 'UserSelect' 8 | | 'AutoComplete' 9 | | 'ApiTreeSelect' 10 | | 'Checkbox' 11 | | 'Switch' 12 | | 'DatePicker' 13 | | 'TimePicker' 14 | | 'RadioGroup' 15 | | 'RadioButtonGroup' 16 | | 'ApiRadioGroup'; 17 | -------------------------------------------------------------------------------- /web/src/components/Time/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import time from './src/Time.vue'; 3 | 4 | export const Time = withInstall(time); 5 | -------------------------------------------------------------------------------- /web/src/components/Tinymce/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import tinymce from './src/Editor.vue'; 3 | 4 | export const Tinymce = withInstall(tinymce); 5 | -------------------------------------------------------------------------------- /web/src/components/Tinymce/src/tinymce.ts: -------------------------------------------------------------------------------- 1 | // Any plugins you want to setting has to be imported 2 | // Detail plugins list see https://www.tinymce.com/docs/plugins/ 3 | // Custom builds see https://www.tinymce.com/download/custom-builds/ 4 | // colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration 5 | 6 | export const plugins = [ 7 | 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus template textpattern visualblocks visualchars wordcount', 8 | ]; 9 | 10 | export const toolbar = [ 11 | 'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 12 | 'hr bullist numlist link preview anchor pagebreak insertdatetime media forecolor backcolor fullscreen', 13 | ]; 14 | -------------------------------------------------------------------------------- /web/src/components/Tree/index.ts: -------------------------------------------------------------------------------- 1 | import BasicTree from './src/BasicTree.vue'; 2 | import './style'; 3 | 4 | export { BasicTree }; 5 | export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; 6 | export * from './src/types/tree'; 7 | -------------------------------------------------------------------------------- /web/src/components/Tree/src/TreeIcon.ts: -------------------------------------------------------------------------------- 1 | import type { VNode } from 'vue'; 2 | import { h } from 'vue'; 3 | import { isString } from 'lodash-es'; 4 | import Icon from '@/components/Icon/Icon.vue'; 5 | 6 | export const TreeIcon = ({ icon }: { icon: VNode | string | undefined }) => { 7 | if (!icon) return null; 8 | if (isString(icon)) { 9 | return h(Icon, { icon, class: 'mr-1' }); 10 | } 11 | return h(Icon); 12 | }; 13 | -------------------------------------------------------------------------------- /web/src/components/Tree/style/index.ts: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | -------------------------------------------------------------------------------- /web/src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import basicUpload from './src/BasicUpload.vue'; 3 | 4 | export const BasicUpload = withInstall(basicUpload); 5 | -------------------------------------------------------------------------------- /web/src/components/Upload/src/ThumbUrl.vue: -------------------------------------------------------------------------------- 1 | 6 | 19 | 30 | -------------------------------------------------------------------------------- /web/src/components/Upload/src/helper.ts: -------------------------------------------------------------------------------- 1 | export function checkFileType(file: File, accepts: string[]) { 2 | const newTypes = accepts.join('|'); 3 | // const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i; 4 | const reg = new RegExp('\\.(' + newTypes + ')$', 'i'); 5 | 6 | return reg.test(file.name); 7 | } 8 | 9 | export function checkImgType(file: File) { 10 | return isImgTypeByName(file.name); 11 | } 12 | 13 | export function isImgTypeByName(name: string) { 14 | return /\.(jpg|jpeg|png|gif|webp)$/i.test(name); 15 | } 16 | 17 | export function getBase64WithFile(file: File) { 18 | return new Promise<{ 19 | result: string; 20 | file: File; 21 | }>((resolve, reject) => { 22 | const reader = new FileReader(); 23 | reader.readAsDataURL(file); 24 | reader.onload = () => resolve({ result: reader.result as string, file }); 25 | reader.onerror = (error) => reject(error); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /web/src/components/Verify/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import basicDragVerify from './src/DragVerify.vue'; 3 | import rotateDragVerify from './src/ImgRotate.vue'; 4 | 5 | export const BasicDragVerify = withInstall(basicDragVerify); 6 | export const RotateDragVerify = withInstall(rotateDragVerify); 7 | export * from './src/typing'; 8 | -------------------------------------------------------------------------------- /web/src/components/Verify/src/typing.ts: -------------------------------------------------------------------------------- 1 | export interface DragVerifyActionType { 2 | resume: () => void; 3 | } 4 | 5 | export interface PassingData { 6 | isPassing: boolean; 7 | time: number; 8 | } 9 | 10 | export interface MoveData { 11 | event: MouseEvent | TouchEvent; 12 | moveDistance: number; 13 | moveX: number; 14 | } 15 | -------------------------------------------------------------------------------- /web/src/components/VirtualScroll/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils/index'; 2 | import vScroll from './src/VirtualScroll.vue'; 3 | 4 | export const VScroll = withInstall(vScroll); 5 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '/@/utils'; 2 | import vxeBasicTable from './src/VxeBasicTable'; 3 | import { VXETable } from 'vxe-table'; 4 | import VXETablePluginAntd from './src/components'; 5 | import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx'; 6 | import './src/setting'; 7 | 8 | export const VxeBasicTable = withInstall(vxeBasicTable); 9 | export * from 'vxe-table'; 10 | export * from './src/types'; 11 | 12 | VXETable.use(VXETablePluginAntd).use(VXETablePluginExportXLSX); 13 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'AInput' 3 | | 'AInputNumber' 4 | | 'ASelect' 5 | | 'AApiSelect' 6 | | 'ATreeSelect' 7 | | 'AApiTreeSelect' 8 | | 'ARadioGroup' 9 | | 'ACheckboxGroup' 10 | | 'AAutoComplete' 11 | | 'ACascader' 12 | | 'ADatePicker' 13 | | 'AMonthPicker' 14 | | 'ARangePicker' 15 | | 'AWeekPicker' 16 | | 'ATimePicker' 17 | | 'AYearPicker' 18 | | 'ASwitch' 19 | | 'ARate' 20 | | 'AInputSearch' 21 | | 'AButton' 22 | | 'AEmpty'; 23 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AApiSelect.tsx: -------------------------------------------------------------------------------- 1 | import XEUtils from 'xe-utils'; 2 | import { createDefaultRender, createEditRender, createFormItemRender } from './common'; 3 | 4 | export default { 5 | renderDefault: createDefaultRender({}, (_, params) => { 6 | return { 7 | params: XEUtils.get(params, 'row'), 8 | }; 9 | }), 10 | renderEdit: createEditRender({}, (_, params) => { 11 | return { 12 | params: XEUtils.get(params, 'row'), 13 | }; 14 | }), 15 | renderItemContent: createFormItemRender({}, (_, params) => { 16 | return { 17 | params: XEUtils.get(params, 'row'), 18 | }; 19 | }), 20 | }; 21 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AApiTreeSelect.tsx: -------------------------------------------------------------------------------- 1 | import XEUtils from 'xe-utils'; 2 | import { createDefaultRender, createEditRender, createFormItemRender } from './common'; 3 | 4 | export default { 5 | renderDefault: createDefaultRender({}, (_, params) => { 6 | return { 7 | params: XEUtils.get(params, 'row'), 8 | }; 9 | }), 10 | renderEdit: createEditRender({}, (_, params) => { 11 | return { 12 | params: XEUtils.get(params, 'row'), 13 | }; 14 | }), 15 | renderItemContent: createFormItemRender({}, (_, params) => { 16 | return { 17 | params: XEUtils.get(params, 'row'), 18 | }; 19 | }), 20 | }; 21 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AAutoComplete.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createEditRender, 3 | createDefaultRender, 4 | createFilterRender, 5 | createDefaultFilterRender, 6 | createFormItemRender, 7 | } from './common'; 8 | 9 | export default { 10 | autofocus: 'input.ant-input', 11 | renderDefault: createDefaultRender(), 12 | renderEdit: createEditRender(), 13 | renderFilter: createFilterRender(), 14 | defaultFilterMethod: createDefaultFilterRender(), 15 | renderItemContent: createFormItemRender(), 16 | }; 17 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/ACheckboxGroup.tsx: -------------------------------------------------------------------------------- 1 | import { createFormItemRender } from './common'; 2 | 3 | export default { 4 | renderItemContent: createFormItemRender(), 5 | }; 6 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AEmpty.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { VxeGlobalRendererHandles } from 'vxe-table'; 3 | import { getComponent } from './common'; 4 | 5 | function createEmptyRender() { 6 | return function (renderOpts: VxeGlobalRendererHandles.RenderEmptyOptions) { 7 | const { name, attrs, props } = renderOpts; 8 | 9 | const Component = getComponent(name); 10 | return [ 11 | h( 12 | 'div', 13 | { 14 | class: 'flex items-center justify-center', 15 | }, 16 | h(Component, { 17 | ...attrs, 18 | ...props, 19 | }), 20 | ), 21 | ]; 22 | }; 23 | } 24 | 25 | export default { 26 | renderEmpty: createEmptyRender(), 27 | }; 28 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AInput.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createEditRender, 3 | createDefaultRender, 4 | createFilterRender, 5 | createDefaultFilterRender, 6 | createFormItemRender, 7 | } from './common'; 8 | 9 | export default { 10 | autofocus: 'input.ant-input', 11 | renderDefault: createDefaultRender(), 12 | renderEdit: createEditRender(), 13 | renderFilter: createFilterRender(), 14 | defaultFilterMethod: createDefaultFilterRender(), 15 | renderItemContent: createFormItemRender(), 16 | }; 17 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AInputNumber.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createEditRender, 3 | createFilterRender, 4 | createFormItemRender, 5 | createDefaultFilterRender, 6 | createDefaultRender, 7 | } from './common'; 8 | 9 | export default { 10 | autofocus: 'input.ant-input-number-input', 11 | renderDefault: createDefaultRender(), 12 | renderEdit: createEditRender(), 13 | renderFilter: createFilterRender(), 14 | defaultFilterMethod: createDefaultFilterRender(), 15 | renderItemContent: createFormItemRender(), 16 | }; 17 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AInputSearch.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createEditRender, 3 | createDefaultRender, 4 | createFilterRender, 5 | createDefaultFilterRender, 6 | createFormItemRender, 7 | createToolbarToolRender, 8 | } from './common'; 9 | 10 | export default { 11 | renderDefault: createDefaultRender(), 12 | renderEdit: createEditRender(), 13 | renderFilter: createFilterRender(), 14 | defaultFilterMethod: createDefaultFilterRender(), 15 | renderItemContent: createFormItemRender(), 16 | renderToolbarTool: createToolbarToolRender(), 17 | }; 18 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AMonthPicker.tsx: -------------------------------------------------------------------------------- 1 | import { getDatePickerCellValue } from './ADatePicker'; 2 | import { 3 | createCellRender, 4 | createEditRender, 5 | createExportMethod, 6 | createFormItemRender, 7 | } from './common'; 8 | 9 | export default { 10 | renderEdit: createEditRender(), 11 | renderCell: createCellRender(getDatePickerCellValue, () => { 12 | return ['YYYY-MM']; 13 | }), 14 | renderItemContent: createFormItemRender(), 15 | exportMethod: createExportMethod(getDatePickerCellValue, () => { 16 | return ['YYYY-MM']; 17 | }), 18 | }; 19 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/ARadioGroup.tsx: -------------------------------------------------------------------------------- 1 | import { createFormItemRender } from './common'; 2 | 3 | export default { 4 | renderItemContent: createFormItemRender(), 5 | }; 6 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/ARate.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createEditRender, 3 | createDefaultRender, 4 | createFilterRender, 5 | createDefaultFilterRender, 6 | createFormItemRender, 7 | } from './common'; 8 | 9 | export default { 10 | renderDefault: createDefaultRender(), 11 | renderEdit: createEditRender(), 12 | renderFilter: createFilterRender(), 13 | defaultFilterMethod: createDefaultFilterRender(), 14 | renderItemContent: createFormItemRender(), 15 | }; 16 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/ATimePicker.tsx: -------------------------------------------------------------------------------- 1 | import { getDatePickerCellValue } from './ADatePicker'; 2 | import { 3 | createEditRender, 4 | createCellRender, 5 | createFormItemRender, 6 | createExportMethod, 7 | } from './common'; 8 | 9 | export default { 10 | renderEdit: createEditRender(), 11 | renderCell: createCellRender(getDatePickerCellValue, () => { 12 | return ['HH:mm:ss']; 13 | }), 14 | renderItemContent: createFormItemRender(), 15 | exportMethod: createExportMethod(getDatePickerCellValue, () => { 16 | return ['HH:mm:ss']; 17 | }), 18 | }; 19 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AWeekPicker.tsx: -------------------------------------------------------------------------------- 1 | import { getDatePickerCellValue } from './ADatePicker'; 2 | import { 3 | createEditRender, 4 | createCellRender, 5 | createFormItemRender, 6 | createExportMethod, 7 | } from './common'; 8 | 9 | export default { 10 | renderEdit: createEditRender(), 11 | renderCell: createCellRender(getDatePickerCellValue, () => { 12 | return ['YYYY-WW周']; 13 | }), 14 | renderItemContent: createFormItemRender(), 15 | exportMethod: createExportMethod(getDatePickerCellValue, () => { 16 | return ['YYYY-WW周']; 17 | }), 18 | }; 19 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/components/AYearPicker.tsx: -------------------------------------------------------------------------------- 1 | import { getDatePickerCellValue } from './ADatePicker'; 2 | import { 3 | createEditRender, 4 | createCellRender, 5 | createFormItemRender, 6 | createExportMethod, 7 | } from './common'; 8 | 9 | export default { 10 | renderEdit: createEditRender(), 11 | renderCell: createCellRender(getDatePickerCellValue, () => { 12 | return ['YYYY']; 13 | }), 14 | renderItemContent: createFormItemRender(), 15 | exportMethod: createExportMethod(getDatePickerCellValue, () => { 16 | return ['YYYY']; 17 | }), 18 | }; 19 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/const.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 传给vxe-table 时需要忽略的prop 3 | */ 4 | export const ignorePropKeys = ['tableClass', 'tableStyle']; 5 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/css/common.scss: -------------------------------------------------------------------------------- 1 | *, 2 | ::before, 3 | ::after { 4 | box-sizing: border-box; 5 | border-width: 0; 6 | border-style: solid; 7 | border-color: initial; 8 | } 9 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/css/index.scss: -------------------------------------------------------------------------------- 1 | @import './common'; 2 | @import './variable'; 3 | @import './scrollbar'; 4 | @import './toolbar'; 5 | @import './component'; 6 | @import 'vxe-table/styles/index'; 7 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/css/scrollbar.scss: -------------------------------------------------------------------------------- 1 | .vxe-grid_scrollbar { 2 | ::-webkit-scrollbar { 3 | width: 8px; 4 | height: 8px; 5 | } 6 | 7 | ::-webkit-scrollbar-track { 8 | background-color: #fff; 9 | } 10 | 11 | ::-webkit-scrollbar-thumb { 12 | border: 1px solid #f1f1f1; 13 | border-radius: 5px; 14 | background-color: rgb(0 0 0 / 10%); 15 | box-shadow: inset 0 0 6px rgb(0 0 0 / 30%); 16 | } 17 | 18 | ::-webkit-scrollbar-thumb:hover { 19 | background-color: #a8a8a8; 20 | } 21 | 22 | ::-webkit-scrollbar-thumb:active { 23 | background-color: #a8a8a8; 24 | } 25 | 26 | ::-webkit-scrollbar-corner { 27 | background-color: #fff; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/css/toolbar.scss: -------------------------------------------------------------------------------- 1 | .vxe-toolbar .vxe-custom--option-wrapper .vxe-custom--footer { 2 | display: flex; 3 | } 4 | 5 | .vxe-toolbar .vxe-tools--wrapper, 6 | .vxe-toolbar .vxe-tools--operate button:first-child { 7 | margin: 0; 8 | margin-left: 10px; 9 | } 10 | 11 | .vxe-toolbar .vxe-tools--wrapper, 12 | .vxe-toolbar .vxe-tools--operate .vxe-button { 13 | margin-left: 1px; 14 | border-radius: 0 !important; 15 | } 16 | 17 | .vxe-toolbar .vxe-tools--wrapper, 18 | .vxe-toolbar .vxe-tools--operate .vxe-custom--wrapper { 19 | margin-left: 1px; 20 | border-radius: 0 !important; 21 | } 22 | 23 | .vxe-toolbar .vxe-tools--wrapper, 24 | .vxe-toolbar .vxe-tools--operate .vxe-custom--wrapper .vxe-button { 25 | margin-left: 1px; 26 | } 27 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/css/variable.scss: -------------------------------------------------------------------------------- 1 | $vxe-primary-color: rgb(9 96 189) !default; 2 | $vxe-table-row-current-background-color: rgb(9 96 189 / 30%); 3 | $vxe-table-row-hover-current-background-color: rgb(9 96 189 / 20%); 4 | $vxe-table-column-hover-background-color: rgb(9 96 189 / 30%); 5 | $vxe-table-column-current-background-color: rgb(9 96 189 / 20%); 6 | $vxe-table-validate-error-color: #f56c6c; 7 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/emits.ts: -------------------------------------------------------------------------------- 1 | import tableEmits from 'vxe-table/es/table/src/emits'; 2 | 3 | export const basicEmits = [ 4 | ...tableEmits, 5 | 'page-change', 6 | 'form-submit', 7 | 'form-submit-invalid', 8 | 'form-reset', 9 | 'form-collapse', 10 | 'form-toggle-collapse', 11 | 'toolbar-button-click', 12 | 'toolbar-tool-click', 13 | 'zoom', 14 | 15 | //... 如有缺少在此处追加 16 | // xxx 17 | ]; 18 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from './componentType'; 2 | import { useI18n } from '/@/hooks/web/useI18n'; 3 | 4 | const { t } = useI18n(); 5 | 6 | /** 7 | * @description: 生成placeholder 8 | */ 9 | export function createPlaceholderMessage(component: ComponentType) { 10 | if (!component) return; 11 | if (component.includes('RangePicker')) { 12 | return [t('common.chooseText'), t('common.chooseText')]; 13 | } 14 | if (component.includes('Input') || component.includes('Complete') || component.includes('Rate')) { 15 | return t('common.inputText'); 16 | } else { 17 | return t('common.chooseText'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/setting.ts: -------------------------------------------------------------------------------- 1 | import { VXETable } from '..'; 2 | import componentSetting from '/@/settings/componentSetting'; 3 | 4 | VXETable.setup(componentSetting.vxeTable); 5 | -------------------------------------------------------------------------------- /web/src/components/VxeTable/src/types.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'vue'; 2 | import { VxeGridProps } from 'vxe-table'; 3 | 4 | export type BasicTableProps = VxeGridProps & { 5 | tableClass?: string; 6 | tableStyle?: CSSProperties; 7 | }; 8 | -------------------------------------------------------------------------------- /web/src/components/registerGlobComp.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { Button } from './Button'; 3 | import { Input, Layout } from 'ant-design-vue'; 4 | import VXETable from 'vxe-table'; 5 | 6 | export function registerGlobComp(app: App) { 7 | app.use(Input).use(Button).use(Layout).use(VXETable); 8 | } 9 | -------------------------------------------------------------------------------- /web/src/design/ant/input.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../color.less'; 2 | 3 | // input 4 | .ant-input { 5 | &-number, 6 | &-number-group-wrapper { 7 | width: 100% !important; 8 | min-width: 110px; 9 | max-width: 100%; 10 | } 11 | } 12 | 13 | .ant-input-affix-wrapper .ant-input-suffix { 14 | right: 9px; 15 | } 16 | 17 | .ant-input-clear-icon { 18 | margin-right: 5px; 19 | } 20 | 21 | .ant-input-affix-wrapper-textarea-with-clear-btn { 22 | padding: 0 !important; 23 | 24 | textarea.ant-input { 25 | padding: 4px; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/src/design/config.less: -------------------------------------------------------------------------------- 1 | @import (reference) 'color.less'; 2 | @import (reference) 'var/index.less'; 3 | -------------------------------------------------------------------------------- /web/src/design/theme.less: -------------------------------------------------------------------------------- 1 | .bg-white { 2 | background-color: @component-background !important; 3 | } 4 | 5 | html[data-theme='light'] { 6 | .text-secondary { 7 | color: rgb(0 0 0 / 45%); 8 | } 9 | 10 | .ant-alert-success { 11 | border: 1px solid #b7eb8f; 12 | background-color: #f6ffed; 13 | } 14 | 15 | .ant-alert-error { 16 | border: 1px solid #ffccc7; 17 | background-color: #fff2f0; 18 | } 19 | 20 | .ant-alert-warning { 21 | border: 1px solid #ffe58f; 22 | background-color: #fffbe6; 23 | } 24 | 25 | :not(:root):fullscreen::backdrop { 26 | background-color: @layout-body-background !important; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/src/design/transition/base.less: -------------------------------------------------------------------------------- 1 | .transition-default() { 2 | &-enter-active, 3 | &-leave-active { 4 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 5 | } 6 | 7 | &-move { 8 | transition: transform 0.4s; 9 | } 10 | } 11 | 12 | .expand-transition { 13 | .transition-default(); 14 | } 15 | 16 | .expand-x-transition { 17 | .transition-default(); 18 | } 19 | -------------------------------------------------------------------------------- /web/src/design/transition/index.less: -------------------------------------------------------------------------------- 1 | @import './base.less'; 2 | @import './fade.less'; 3 | @import './scale.less'; 4 | @import './slide.less'; 5 | @import './scroll.less'; 6 | @import './zoom.less'; 7 | 8 | .collapse-transition { 9 | transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; 10 | } 11 | -------------------------------------------------------------------------------- /web/src/design/transition/scale.less: -------------------------------------------------------------------------------- 1 | .scale-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave, 6 | &-leave-to { 7 | transform: scale(0); 8 | opacity: 0; 9 | } 10 | } 11 | 12 | .scale-rotate-transition { 13 | .transition-default(); 14 | 15 | &-enter-from, 16 | &-leave, 17 | &-leave-to { 18 | transform: scale(0) rotate(-45deg); 19 | opacity: 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web/src/design/transition/slide.less: -------------------------------------------------------------------------------- 1 | .slide-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | transform: translateY(-15px); 7 | opacity: 0; 8 | } 9 | } 10 | 11 | .slide-y-reverse-transition { 12 | .transition-default(); 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | transform: translateY(15px); 17 | opacity: 0; 18 | } 19 | } 20 | 21 | .slide-x-transition { 22 | .transition-default(); 23 | 24 | &-enter-from, 25 | &-leave-to { 26 | transform: translateX(-15px); 27 | opacity: 0; 28 | } 29 | } 30 | 31 | .slide-x-reverse-transition { 32 | .transition-default(); 33 | 34 | &-enter-from, 35 | &-leave-to { 36 | transform: translateX(15px); 37 | opacity: 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/src/design/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; 5 | } 6 | 7 | .zoom-out-enter-from, 8 | .zoom-out-leave-to { 9 | transform: scale(0); 10 | opacity: 0; 11 | } 12 | 13 | // zoom-fade 14 | .zoom-fade-enter-active, 15 | .zoom-fade-leave-active { 16 | transition: transform 0.2s, opacity 0.3s ease-out; 17 | } 18 | 19 | .zoom-fade-enter-from { 20 | transform: scale(0.92); 21 | opacity: 0; 22 | } 23 | 24 | .zoom-fade-leave-to { 25 | transform: scale(1.06); 26 | opacity: 0; 27 | } 28 | -------------------------------------------------------------------------------- /web/src/design/var/breakpoint.less: -------------------------------------------------------------------------------- 1 | // ================================= 2 | // ==============屏幕断点============ 3 | // ================================= 4 | 5 | // Extra small screen / phone 6 | @screen-xs: 480px; 7 | @screen-xs-min: @screen-xs; 8 | 9 | // Small screen / tablet 10 | @screen-sm: 576px; 11 | @screen-sm-min: @screen-sm; 12 | 13 | // Medium screen / desktop 14 | @screen-md: 768px; 15 | @screen-md-min: @screen-md; 16 | 17 | // Large screen / wide desktop 18 | @screen-lg: 992px; 19 | @screen-lg-min: @screen-lg; 20 | 21 | // Extra large screen / full hd 22 | @screen-xl: 1200px; 23 | @screen-xl-min: @screen-xl; 24 | 25 | // Extra extra large screen / large desktop 26 | @screen-2xl: 1600px; 27 | @screen-2xl-min: @screen-2xl; 28 | 29 | @screen-xs-max: (@screen-sm-min - 1px); 30 | @screen-sm-max: (@screen-md-min - 1px); 31 | @screen-md-max: (@screen-lg-min - 1px); 32 | @screen-lg-max: (@screen-xl-min - 1px); 33 | @screen-xl-max: (@screen-2xl-min - 1px); 34 | -------------------------------------------------------------------------------- /web/src/design/var/easing.less: -------------------------------------------------------------------------------- 1 | // ================================= 2 | // ==============动画函数-=========== 3 | // ================================= 4 | 5 | @ease-base-out: cubic-bezier(0.7, 0.3, 0.1, 1); 6 | @ease-base-in: cubic-bezier(0.9, 0, 0.3, 0.7); 7 | @ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); 8 | @ease-in: cubic-bezier(0.55, 0.055, 0.675, 0.19); 9 | @ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); 10 | @ease-out-back: cubic-bezier(0.12, 0.4, 0.29, 1.46); 11 | @ease-in-back: cubic-bezier(0.71, -0.46, 0.88, 0.6); 12 | @ease-in-out-back: cubic-bezier(0.71, -0.46, 0.29, 1.46); 13 | @ease-out-circ: cubic-bezier(0.08, 0.82, 0.17, 1); 14 | @ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.34); 15 | @ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); 16 | @ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); 17 | @ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); 18 | @ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1); 19 | -------------------------------------------------------------------------------- /web/src/design/var/index.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../color.less'; 2 | @import 'easing'; 3 | @import 'breakpoint'; 4 | 5 | @namespace: vben; 6 | 7 | // tabs 8 | @multiple-height: 30px; 9 | 10 | // headers 11 | @header-height: 48px; 12 | 13 | // logo width 14 | @logo-width: 32px; 15 | 16 | // 17 | @side-drag-z-index: 200; 18 | 19 | @page-loading-z-index: 10000; 20 | 21 | @lock-page-z-index: 3000; 22 | 23 | @layout-header-fixed-z-index: 500; 24 | 25 | @multiple-tab-fixed-z-index: 505; 26 | 27 | @layout-sider-fixed-z-index: 510; 28 | 29 | @layout-mix-sider-fixed-z-index: 550; 30 | 31 | @preview-comp-z-index: 1000; 32 | 33 | @page-footer-z-index: 99; 34 | 35 | .bem(@n; @content) { 36 | @{namespace}-@{n} { 37 | @content(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /web/src/directives/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure and register global directives 3 | */ 4 | import type { App } from 'vue'; 5 | import { setupPermissionDirective } from './permission'; 6 | import { setupLoadingDirective } from './loading'; 7 | import { setupEllipsisDirective } from './ellipsis'; 8 | 9 | export function setupGlobDirectives(app: App) { 10 | setupPermissionDirective(app); 11 | setupLoadingDirective(app); 12 | setupEllipsisDirective(app); 13 | } 14 | -------------------------------------------------------------------------------- /web/src/directives/permission.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Global authority directive 3 | * Used for fine-grained control of component permissions 4 | * @Example v-auth="RoleEnum.TEST" 5 | */ 6 | import type { App, Directive, DirectiveBinding } from 'vue'; 7 | 8 | import { usePermission } from '/@/hooks/web/usePermission'; 9 | 10 | function isAuth(el: Element, binding: any) { 11 | const { hasPermission } = usePermission(); 12 | 13 | const value = binding.value; 14 | if (!value) return; 15 | if (!hasPermission(value)) { 16 | el.parentNode?.removeChild(el); 17 | } 18 | } 19 | 20 | const mounted = (el: Element, binding: DirectiveBinding) => { 21 | isAuth(el, binding); 22 | }; 23 | 24 | const authDirective: Directive = { 25 | mounted, 26 | }; 27 | 28 | export function setupPermissionDirective(app: App) { 29 | app.directive('auth', authDirective); 30 | } 31 | 32 | export default authDirective; 33 | -------------------------------------------------------------------------------- /web/src/directives/ripple/index.less: -------------------------------------------------------------------------------- 1 | .ripple-container { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 0; 6 | height: 0; 7 | overflow: hidden; 8 | pointer-events: none; 9 | } 10 | 11 | .ripple-effect { 12 | position: relative; 13 | z-index: 9999; 14 | width: 1px; 15 | height: 1px; 16 | margin-top: 0; 17 | margin-left: 0; 18 | transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); 19 | border-radius: 50%; 20 | pointer-events: none; 21 | } 22 | -------------------------------------------------------------------------------- /web/src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum sizeEnum { 2 | XS = 'XS', 3 | SM = 'SM', 4 | MD = 'MD', 5 | LG = 'LG', 6 | XL = 'XL', 7 | XXL = 'XXL', 8 | } 9 | 10 | export enum screenEnum { 11 | XS = 480, 12 | SM = 576, 13 | MD = 768, 14 | LG = 992, 15 | XL = 1200, 16 | XXL = 1600, 17 | } 18 | 19 | const screenMap = new Map(); 20 | 21 | screenMap.set(sizeEnum.XS, screenEnum.XS); 22 | screenMap.set(sizeEnum.SM, screenEnum.SM); 23 | screenMap.set(sizeEnum.MD, screenEnum.MD); 24 | screenMap.set(sizeEnum.LG, screenEnum.LG); 25 | screenMap.set(sizeEnum.XL, screenEnum.XL); 26 | screenMap.set(sizeEnum.XXL, screenEnum.XXL); 27 | 28 | export { screenMap }; 29 | -------------------------------------------------------------------------------- /web/src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN__'; 3 | 4 | export const LOCALE_KEY = 'LOCALE__'; 5 | 6 | // user info key 7 | export const USER_INFO_KEY = 'USER__INFO__'; 8 | 9 | // role info key 10 | export const ROLES_KEY = 'ROLES__KEY__'; 11 | 12 | // project config key 13 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 14 | export const API_ADDRESS = 'API_ADDRESS__'; 15 | 16 | // lock info 17 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 18 | 19 | export const MULTIPLE_TABS_KEY = 'MULTIPLE_TABS__KEY__'; 20 | 21 | export const APP_DARK_MODE_KEY = '__APP__DARK__MODE__'; 22 | 23 | // base global local key 24 | export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__'; 25 | 26 | // base global session key 27 | export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__'; 28 | 29 | export enum CacheTypeEnum { 30 | SESSION, 31 | LOCAL, 32 | } 33 | -------------------------------------------------------------------------------- /web/src/enums/exceptionEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Exception related enumeration 3 | */ 4 | export enum ExceptionEnum { 5 | // page not access 6 | PAGE_NOT_ACCESS = 403, 7 | 8 | // page not found 9 | PAGE_NOT_FOUND = 404, 10 | 11 | // error 12 | ERROR = 500, 13 | 14 | // net work error 15 | NET_WORK_ERROR = 10000, 16 | 17 | // No data on the page. In fact, it is not an exception page 18 | PAGE_NOT_DATA = 10100, 19 | } 20 | 21 | export enum ErrorTypeEnum { 22 | VUE = 'vue', 23 | SCRIPT = 'script', 24 | RESOURCE = 'resource', 25 | AJAX = 'ajax', 26 | PROMISE = 'promise', 27 | } 28 | -------------------------------------------------------------------------------- /web/src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 2000, 6 | ERROR = -1, 7 | TIMEOUT = 401, 8 | TYPE = 'success', 9 | } 10 | 11 | /** 12 | * @description: request method 13 | */ 14 | export enum RequestEnum { 15 | GET = 'GET', 16 | POST = 'POST', 17 | PUT = 'PUT', 18 | DELETE = 'DELETE', 19 | } 20 | 21 | /** 22 | * @description: contentType 23 | */ 24 | export enum ContentTypeEnum { 25 | // json 26 | JSON = 'application/json;charset=UTF-8', 27 | // form-data qs 28 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 29 | // form-data upload 30 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 31 | } 32 | -------------------------------------------------------------------------------- /web/src/enums/menuEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: menu type 3 | */ 4 | export enum MenuTypeEnum { 5 | // left menu 6 | SIDEBAR = 'sidebar', 7 | 8 | MIX_SIDEBAR = 'mix-sidebar', 9 | // mixin menu 10 | MIX = 'mix', 11 | // top menu 12 | TOP_MENU = 'top-menu', 13 | } 14 | 15 | // 折叠触发器位置 16 | export enum TriggerEnum { 17 | // 不显示 18 | NONE = 'NONE', 19 | // 菜单底部 20 | FOOTER = 'FOOTER', 21 | // 头部 22 | HEADER = 'HEADER', 23 | } 24 | 25 | export type Mode = 'vertical' | 'vertical-right' | 'horizontal' | 'inline'; 26 | 27 | // menu mode 28 | export enum MenuModeEnum { 29 | VERTICAL = 'vertical', 30 | HORIZONTAL = 'horizontal', 31 | VERTICAL_RIGHT = 'vertical-right', 32 | INLINE = 'inline', 33 | } 34 | 35 | export enum MenuSplitTyeEnum { 36 | NONE, 37 | TOP, 38 | LEFT, 39 | } 40 | 41 | export enum TopMenuAlignEnum { 42 | CENTER = 'center', 43 | START = 'start', 44 | END = 'end', 45 | } 46 | 47 | export enum MixSidebarTriggerEnum { 48 | HOVER = 'hover', 49 | CLICK = 'click', 50 | } 51 | -------------------------------------------------------------------------------- /web/src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum PageEnum { 2 | // basic login path 3 | BASE_LOGIN = '/login', 4 | // basic home path 5 | BASE_HOME = '/dashboard', 6 | // error page path 7 | ERROR_PAGE = '/exception', 8 | // error log page path 9 | ERROR_LOG_PAGE = '/error-log/list', 10 | } 11 | export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight'; 12 | -------------------------------------------------------------------------------- /web/src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // super admin 3 | SUPER = 'super', 4 | 5 | // tester 6 | TEST = 'test', 7 | } 8 | -------------------------------------------------------------------------------- /web/src/enums/sizeEnum.ts: -------------------------------------------------------------------------------- 1 | export enum SizeEnum { 2 | DEFAULT = 'default', 3 | SMALL = 'small', 4 | LARGE = 'large', 5 | } 6 | -------------------------------------------------------------------------------- /web/src/hooks/component/usePageContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, ComputedRef, Ref } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface PageContextProps { 5 | contentHeight: ComputedRef; 6 | pageHeight: Ref; 7 | setPageHeight: (height: number) => Promise; 8 | } 9 | 10 | const key: InjectionKey = Symbol(); 11 | 12 | export function createPageContext(context: PageContextProps) { 13 | return createContext(context, key, { native: true }); 14 | } 15 | 16 | export function usePageContext() { 17 | return useContext(key); 18 | } 19 | -------------------------------------------------------------------------------- /web/src/hooks/setting/index.ts: -------------------------------------------------------------------------------- 1 | import type { GlobConfig } from '/#/config'; 2 | 3 | import { getAppEnvConfig } from '/@/utils/env'; 4 | 5 | export const useGlobSetting = (): Readonly => { 6 | const { VITE_GLOB_APP_TITLE, VITE_GLOB_API_URL, VITE_GLOB_API_URL_PREFIX, VITE_GLOB_UPLOAD_URL } = 7 | getAppEnvConfig(); 8 | 9 | // Take global configuration 10 | const glob: Readonly = { 11 | title: VITE_GLOB_APP_TITLE, 12 | apiUrl: VITE_GLOB_API_URL, 13 | shortName: VITE_GLOB_APP_TITLE.replace(/\s/g, '_').replace(/-/g, '_'), 14 | urlPrefix: VITE_GLOB_API_URL_PREFIX, 15 | uploadUrl: VITE_GLOB_UPLOAD_URL, 16 | }; 17 | return glob as Readonly; 18 | }; 19 | -------------------------------------------------------------------------------- /web/src/hooks/setting/useDarkModeTheme.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { theme } from 'ant-design-vue'; 3 | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; 4 | import { ThemeEnum } from '/@/enums/appEnum'; 5 | 6 | export function useDarkModeTheme() { 7 | const { getDarkMode } = useRootSetting(); 8 | const { darkAlgorithm } = theme; 9 | const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK); 10 | const darkTheme = { 11 | algorithm: [darkAlgorithm], 12 | }; 13 | 14 | return { 15 | isDark, 16 | darkTheme, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /web/src/hooks/setting/useMultipleTabSetting.ts: -------------------------------------------------------------------------------- 1 | import type { MultiTabsSetting } from '/#/config'; 2 | 3 | import { computed } from 'vue'; 4 | 5 | import { useAppStore } from '/@/store/modules/app'; 6 | 7 | export function useMultipleTabSetting() { 8 | const appStore = useAppStore(); 9 | 10 | const getShowMultipleTab = computed(() => appStore.getMultiTabsSetting.show); 11 | 12 | const getShowQuick = computed(() => appStore.getMultiTabsSetting.showQuick); 13 | 14 | const getShowRedo = computed(() => appStore.getMultiTabsSetting.showRedo); 15 | 16 | const getShowFold = computed(() => appStore.getMultiTabsSetting.showFold); 17 | 18 | function setMultipleTabSetting(multiTabsSetting: Partial) { 19 | appStore.setProjectConfig({ multiTabsSetting }); 20 | } 21 | return { 22 | setMultipleTabSetting, 23 | getShowMultipleTab, 24 | getShowQuick, 25 | getShowRedo, 26 | getShowFold, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /web/src/hooks/web/useAppInject.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '/@/components/Application'; 2 | import { computed, unref } from 'vue'; 3 | 4 | export function useAppInject() { 5 | const values = useAppProviderContext(); 6 | 7 | return { 8 | getIsMobile: computed(() => unref(values.isMobile)), 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /web/src/hooks/web/useContextMenu.ts: -------------------------------------------------------------------------------- 1 | import { onUnmounted, getCurrentInstance } from 'vue'; 2 | import { createContextMenu, destroyContextMenu } from '/@/components/ContextMenu'; 3 | import type { ContextMenuItem } from '/@/components/ContextMenu'; 4 | 5 | export type { ContextMenuItem }; 6 | export function useContextMenu(authRemove = true) { 7 | if (getCurrentInstance() && authRemove) { 8 | onUnmounted(() => { 9 | destroyContextMenu(); 10 | }); 11 | } 12 | return [createContextMenu, destroyContextMenu]; 13 | } 14 | -------------------------------------------------------------------------------- /web/src/hooks/web/useDesign.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '/@/components/Application'; 2 | // import { computed } from 'vue'; 3 | // import { lowerFirst } from 'lodash-es'; 4 | export function useDesign(scope: string) { 5 | const values = useAppProviderContext(); 6 | // const $style = cssModule ? useCssModule() : {}; 7 | 8 | // const style: Record = {}; 9 | // if (cssModule) { 10 | // Object.keys($style).forEach((key) => { 11 | // // const moduleCls = $style[key]; 12 | // const k = key.replace(new RegExp(`^${values.prefixCls}-?`, 'ig'), ''); 13 | // style[lowerFirst(k)] = $style[key]; 14 | // }); 15 | // } 16 | return { 17 | // prefixCls: computed(() => `${values.prefixCls}-${scope}`), 18 | prefixCls: `${values.prefixCls}-${scope}`, 19 | prefixVar: values.prefixCls, 20 | // style, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /web/src/hooks/web/useFullContent.ts: -------------------------------------------------------------------------------- 1 | import { computed, unref } from 'vue'; 2 | 3 | import { useAppStore } from '/@/store/modules/app'; 4 | 5 | import { useRouter } from 'vue-router'; 6 | 7 | /** 8 | * @description: Full screen display content 9 | */ 10 | export const useFullContent = () => { 11 | const appStore = useAppStore(); 12 | const router = useRouter(); 13 | const { currentRoute } = router; 14 | 15 | // Whether to display the content in full screen without displaying the menu 16 | const getFullContent = computed(() => { 17 | // Query parameters, the full screen is displayed when the address bar has a full parameter 18 | const route = unref(currentRoute); 19 | const query = route.query; 20 | if (query && Reflect.has(query, '__full__')) { 21 | return true; 22 | } 23 | // Return to the configuration in the configuration file 24 | return appStore.getProjectConfig.fullContent; 25 | }); 26 | 27 | return { getFullContent }; 28 | }; 29 | -------------------------------------------------------------------------------- /web/src/hooks/web/useSortable.ts: -------------------------------------------------------------------------------- 1 | import { nextTick, unref } from 'vue'; 2 | import type { Ref } from 'vue'; 3 | import type { Options } from 'sortablejs'; 4 | 5 | export function useSortable(el: HTMLElement | Ref, options?: Options) { 6 | function initSortable() { 7 | nextTick(async () => { 8 | if (!el) return; 9 | 10 | const Sortable = (await import('sortablejs')).default; 11 | Sortable.create(unref(el), { 12 | animation: 500, 13 | delay: 400, 14 | delayOnTouchOnly: true, 15 | ...options, 16 | }); 17 | }); 18 | } 19 | 20 | return { initSortable }; 21 | } 22 | -------------------------------------------------------------------------------- /web/src/layouts/default/content/useContentContext.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, ComputedRef } from 'vue'; 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; 3 | 4 | export interface ContentContextProps { 5 | contentHeight: ComputedRef; 6 | setPageHeight: (height: number) => Promise; 7 | } 8 | 9 | const key: InjectionKey = Symbol(); 10 | 11 | export function createContentContext(context: ContentContextProps) { 12 | return createContext(context, key, { native: true }); 13 | } 14 | 15 | export function useContentContext() { 16 | return useContext(key); 17 | } 18 | -------------------------------------------------------------------------------- /web/src/layouts/default/header/components/index.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; 2 | import FullScreen from './FullScreen.vue'; 3 | 4 | export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { 5 | loading: true, 6 | }); 7 | 8 | export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')); 9 | 10 | export const Notify = createAsyncComponent(() => import('./notify/index.vue')); 11 | 12 | export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue')); 13 | 14 | export { FullScreen }; 15 | -------------------------------------------------------------------------------- /web/src/layouts/default/setting/components/index.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; 2 | 3 | export const TypePicker = createAsyncComponent(() => import('./TypePicker.vue')); 4 | export const ThemeColorPicker = createAsyncComponent(() => import('./ThemeColorPicker.vue')); 5 | export const SettingFooter = createAsyncComponent(() => import('./SettingFooter.vue')); 6 | export const SwitchItem = createAsyncComponent(() => import('./SwitchItem.vue')); 7 | export const SelectItem = createAsyncComponent(() => import('./SelectItem.vue')); 8 | export const InputNumberItem = createAsyncComponent(() => import('./InputNumberItem.vue')); 9 | -------------------------------------------------------------------------------- /web/src/layouts/default/setting/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 27 | -------------------------------------------------------------------------------- /web/src/layouts/default/tabs/types.ts: -------------------------------------------------------------------------------- 1 | import type { DropMenu } from '/@/components/Dropdown/index'; 2 | import type { RouteLocationNormalized } from 'vue-router'; 3 | 4 | export enum TabContentEnum { 5 | TAB_TYPE, 6 | EXTRA_TYPE, 7 | } 8 | 9 | export type { DropMenu }; 10 | 11 | export interface TabContentProps { 12 | tabItem: RouteLocationNormalized; 13 | type?: TabContentEnum; 14 | trigger?: ('click' | 'hover' | 'contextmenu')[]; 15 | } 16 | 17 | export enum MenuEventEnum { 18 | REFRESH_PAGE, 19 | CLOSE_CURRENT, 20 | CLOSE_LEFT, 21 | CLOSE_RIGHT, 22 | CLOSE_OTHER, 23 | CLOSE_ALL, 24 | SCALE, 25 | } 26 | -------------------------------------------------------------------------------- /web/src/layouts/default/trigger/HeaderTrigger.vue: -------------------------------------------------------------------------------- 1 | 6 | 18 | -------------------------------------------------------------------------------- /web/src/layouts/default/trigger/SiderTrigger.vue: -------------------------------------------------------------------------------- 1 | 7 | 13 | -------------------------------------------------------------------------------- /web/src/layouts/default/trigger/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | -------------------------------------------------------------------------------- /web/src/layouts/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 30 | -------------------------------------------------------------------------------- /web/src/layouts/page/transition.ts: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent } from 'vue'; 2 | import type { RouteLocation } from 'vue-router'; 3 | 4 | export interface DefaultContext { 5 | Component: FunctionalComponent & { type: Recordable }; 6 | route: RouteLocation; 7 | } 8 | 9 | export function getTransitionName({ 10 | route, 11 | openCache, 12 | cacheTabs, 13 | enableTransition, 14 | def, 15 | }: Pick & { 16 | enableTransition: boolean; 17 | openCache: boolean; 18 | def: string; 19 | cacheTabs: string[]; 20 | }): string | undefined { 21 | if (!enableTransition) { 22 | return undefined; 23 | } 24 | 25 | const isInCache = cacheTabs.includes(route.name as string); 26 | const transitionName = 'fade-slide'; 27 | let name: string | undefined = transitionName; 28 | 29 | if (openCache) { 30 | name = isInCache && route.meta.loaded ? transitionName : undefined; 31 | } 32 | return name || (route.meta.transitionName as string) || def; 33 | } 34 | -------------------------------------------------------------------------------- /web/src/locales/lang/en.ts: -------------------------------------------------------------------------------- 1 | import { genMessage } from '../helper'; 2 | import antdLocale from 'ant-design-vue/es/locale/en_US'; 3 | 4 | const modules = import.meta.globEager('./en/**/*.ts'); 5 | export default { 6 | message: { 7 | ...genMessage(modules, 'en'), 8 | antdLocale, 9 | }, 10 | dateLocale: null, 11 | dateLocaleName: 'en', 12 | }; 13 | -------------------------------------------------------------------------------- /web/src/locales/lang/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "okText": "OK", 3 | "closeText": "Close", 4 | "cancelText": "Cancel", 5 | "loadingText": "Loading...", 6 | "saveText": "Save", 7 | "delText": "Delete", 8 | "resetText": "Reset", 9 | "searchText": "Search", 10 | "queryText": "Search", 11 | "inputText": "Please enter ", 12 | "chooseText": "Please choose ", 13 | "redo": "Refresh", 14 | "back": "Back", 15 | "light": "Light", 16 | "dark": "Dark" 17 | } -------------------------------------------------------------------------------- /web/src/locales/lang/en/routes/basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "Login", 3 | "errorLogList": "Error Log" 4 | } -------------------------------------------------------------------------------- /web/src/locales/lang/en/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: 'Login', 3 | errorLogList: 'Error Log', 4 | }; 5 | -------------------------------------------------------------------------------- /web/src/locales/lang/en/routes/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "dashboard": "Dashboard", 3 | "about": "About", 4 | "workbench": "Workbench", 5 | "analysis": "Analysis" 6 | } 7 | -------------------------------------------------------------------------------- /web/src/locales/lang/en/routes/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dashboard: 'Dashboard', 3 | about: 'About', 4 | workbench: 'Workbench', 5 | analysis: 'Analysis', 6 | }; 7 | -------------------------------------------------------------------------------- /web/src/locales/lang/zh-CN/antdLocale/DatePicker.json: -------------------------------------------------------------------------------- 1 | { 2 | "lang": { 3 | "shortWeekDays": ["一", "二", "三", "四", "五", "六", "日"], 4 | "shortMonths": [ 5 | "1月", 6 | "2月", 7 | "3月", 8 | "4月", 9 | "5月", 10 | "6月", 11 | "7月", 12 | "8月", 13 | "9月", 14 | "10月", 15 | "11月", 16 | "12月" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/src/locales/lang/zh-CN/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "okText": "确认", 3 | "closeText": "关闭", 4 | "cancelText": "取消", 5 | "loadingText": "加载中...", 6 | "saveText": "保存", 7 | "delText": "删除", 8 | "resetText": "重置", 9 | "searchText": "搜索", 10 | "queryText": "查询", 11 | 12 | "inputText": "请输入", 13 | "chooseText": "请选择", 14 | 15 | "redo": "刷新", 16 | "back": "返回", 17 | 18 | "light": "亮色主题", 19 | "dark": "黑暗主题" 20 | } 21 | -------------------------------------------------------------------------------- /web/src/locales/lang/zh-CN/routes/basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "登录", 3 | "errorLogList": "错误日志列表" 4 | } 5 | -------------------------------------------------------------------------------- /web/src/locales/lang/zh-CN/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: '登录', 3 | errorLogList: '错误日志列表', 4 | }; 5 | -------------------------------------------------------------------------------- /web/src/locales/lang/zh-CN/routes/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "dashboard": "Dashboard", 3 | "about": "关于", 4 | "workbench": "工作台", 5 | "analysis": "分析页" 6 | } 7 | -------------------------------------------------------------------------------- /web/src/locales/lang/zh-CN/routes/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dashboard: 'Dashboard', 3 | about: '关于', 4 | workbench: '工作台', 5 | analysis: '分析页', 6 | }; 7 | -------------------------------------------------------------------------------- /web/src/locales/lang/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import { genMessage } from '../helper'; 2 | import antdLocale from 'ant-design-vue/es/locale/zh_CN'; 3 | 4 | const modules = import.meta.globEager('./zh-CN/**/*.ts'); 5 | export default { 6 | message: { 7 | ...genMessage(modules, 'zh-CN'), 8 | antdLocale, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /web/src/logics/theme/index.ts: -------------------------------------------------------------------------------- 1 | export async function changeTheme(_color: string) {} 2 | -------------------------------------------------------------------------------- /web/src/logics/theme/updateColorWeak.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from './util'; 2 | 3 | /** 4 | * Change the status of the project's color weakness mode 5 | * @param colorWeak 6 | */ 7 | export function updateColorWeak(colorWeak: boolean) { 8 | toggleClass(colorWeak, 'color-weak', document.documentElement); 9 | } 10 | -------------------------------------------------------------------------------- /web/src/logics/theme/updateGrayMode.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from './util'; 2 | 3 | /** 4 | * Change project gray mode status 5 | * @param gray 6 | */ 7 | export function updateGrayMode(gray: boolean) { 8 | toggleClass(gray, 'gray-mode', document.documentElement); 9 | } 10 | -------------------------------------------------------------------------------- /web/src/logics/theme/util.ts: -------------------------------------------------------------------------------- 1 | const docEle = document.documentElement; 2 | export function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) { 3 | const targetEl = target || document.body; 4 | let { className } = targetEl; 5 | className = className.replace(clsName, ''); 6 | targetEl.className = flag ? `${className} ${clsName} ` : className; 7 | } 8 | 9 | export function setCssVar(prop: string, val: any, dom = docEle) { 10 | dom.style.setProperty(prop, val); 11 | } 12 | -------------------------------------------------------------------------------- /web/src/router/constant.ts: -------------------------------------------------------------------------------- 1 | export const REDIRECT_NAME = 'Redirect'; 2 | 3 | export const PARENT_LAYOUT_NAME = 'ParentLayout'; 4 | 5 | export const PAGE_NOT_FOUND_NAME = 'PageNotFound'; 6 | 7 | export const EXCEPTION_COMPONENT = () => import('/@/views/sys/exception/Exception.vue'); 8 | 9 | /** 10 | * @description: default layout 11 | */ 12 | export const LAYOUT = () => import('/@/layouts/default/index.vue'); 13 | 14 | /** 15 | * @description: parent-layout 16 | */ 17 | export const getParentLayout = (_name?: string) => { 18 | return () => 19 | new Promise((resolve) => { 20 | resolve({ 21 | name: _name || PARENT_LAYOUT_NAME, 22 | }); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /web/src/router/routes/mainOut.ts: -------------------------------------------------------------------------------- 1 | /** 2 | The routing of this file will not show the layout. 3 | It is an independent new page. 4 | the contents of the file still need to log in to access 5 | */ 6 | import type { AppRouteModule } from '/@/router/types'; 7 | 8 | // test 9 | // http:ip:port/main-out 10 | export const mainOutRoutes: AppRouteModule[] = [ 11 | { 12 | path: '/main-out', 13 | name: 'MainOut', 14 | component: () => import('/@/views/demo/main-out/index.vue'), 15 | meta: { 16 | title: 'MainOut', 17 | ignoreAuth: true, 18 | }, 19 | }, 20 | ]; 21 | 22 | export const mainOutRouteNames = mainOutRoutes.map((item) => item.name); 23 | -------------------------------------------------------------------------------- /web/src/router/routes/modules/about.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '/@/router/types'; 2 | 3 | import { LAYOUT } from '/@/router/constant'; 4 | import { t } from '/@/hooks/web/useI18n'; 5 | 6 | const about: AppRouteModule = { 7 | path: '/about', 8 | name: 'About', 9 | component: LAYOUT, 10 | redirect: '/about/index', 11 | meta: { 12 | hideChildrenInMenu: true, 13 | icon: 'simple-icons:about-dot-me', 14 | title: t('routes.dashboard.about'), 15 | orderNo: 100000, 16 | }, 17 | children: [ 18 | { 19 | path: 'index', 20 | name: 'AboutPage', 21 | component: () => import('/@/views/sys/about/index.vue'), 22 | meta: { 23 | title: t('routes.dashboard.about'), 24 | icon: 'simple-icons:about-dot-me', 25 | hideMenu: true, 26 | }, 27 | }, 28 | ], 29 | }; 30 | 31 | export default about; 32 | -------------------------------------------------------------------------------- /web/src/router/routes/modules/form-design/main.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteModule } from '/@/router/types'; 2 | 3 | import { LAYOUT } from '/@/router/constant'; 4 | 5 | const permission: AppRouteModule = { 6 | path: '/form-designer', 7 | name: 'Form-designer', 8 | component: LAYOUT, 9 | meta: { 10 | orderNo: 10000, 11 | icon: 'ion:build-outline', 12 | title: '表单设计', 13 | }, 14 | children: [ 15 | { 16 | path: 'design', 17 | name: 'Design', 18 | meta: { 19 | title: '表单设计', 20 | }, 21 | component: () => import('/src/views/sys/form-design/index.vue'), 22 | }, 23 | { 24 | path: 'example1', 25 | name: 'Example1', 26 | meta: { 27 | title: '示例', 28 | }, 29 | component: () => import('/src/views/sys/form-design/examples/baseForm.vue'), 30 | }, 31 | ], 32 | }; 33 | 34 | export default permission; 35 | -------------------------------------------------------------------------------- /web/src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { ThemeEnum } from '../enums/appEnum'; 2 | 3 | export const prefixCls = 'vben'; 4 | 5 | export const darkMode = ThemeEnum.LIGHT; 6 | 7 | // app theme preset color 8 | export const APP_PRESET_COLOR_LIST: string[] = [ 9 | '#0960bd', 10 | '#0084f4', 11 | '#009688', 12 | '#536dfe', 13 | '#ff5c93', 14 | '#ee4f12', 15 | '#0096c7', 16 | '#9c27b0', 17 | '#ff9800', 18 | ]; 19 | 20 | // header preset color 21 | export const HEADER_PRESET_BG_COLOR_LIST: string[] = [ 22 | '#ffffff', 23 | '#151515', 24 | '#009688', 25 | '#5172DC', 26 | '#018ffb', 27 | '#409eff', 28 | '#e74c3c', 29 | '#24292e', 30 | '#394664', 31 | '#001529', 32 | '#383f45', 33 | ]; 34 | 35 | // sider preset color 36 | export const SIDE_BAR_BG_COLOR_LIST: string[] = [ 37 | '#1976d2', 38 | '#001529', 39 | '#212121', 40 | '#273352', 41 | '#ffffff', 42 | '#191b24', 43 | '#191a23', 44 | '#304156', 45 | '#001628', 46 | '#28333E', 47 | '#344058', 48 | '#383f45', 49 | ]; 50 | -------------------------------------------------------------------------------- /web/src/settings/encryptionSetting.ts: -------------------------------------------------------------------------------- 1 | import { isDevMode } from '/@/utils/env'; 2 | 3 | // System default cache time, in seconds 4 | export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; 5 | 6 | // aes encryption key 7 | export const cacheCipher = { 8 | key: '_11111000001111@', 9 | iv: '@11111000001111_', 10 | }; 11 | 12 | // Whether the system cache is encrypted using aes 13 | export const enableStorageEncryption = !isDevMode(); 14 | -------------------------------------------------------------------------------- /web/src/settings/localeSetting.ts: -------------------------------------------------------------------------------- 1 | import type { DropMenu } from '../components/Dropdown'; 2 | import type { LocaleSetting, LocaleType } from '/#/config'; 3 | 4 | export const LOCALE: { [key: string]: LocaleType } = { 5 | ZH_CN: 'zh_CN', 6 | EN_US: 'en', 7 | }; 8 | 9 | export const localeSetting: LocaleSetting = { 10 | showPicker: true, 11 | // Locale 12 | locale: LOCALE.ZH_CN, 13 | // Default locale 14 | fallback: LOCALE.ZH_CN, 15 | // available Locales 16 | availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US], 17 | }; 18 | 19 | // locale list 20 | export const localeList: DropMenu[] = [ 21 | { 22 | text: '简体中文', 23 | event: LOCALE.ZH_CN, 24 | }, 25 | { 26 | text: 'English', 27 | event: LOCALE.EN_US, 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /web/src/settings/siteSetting.ts: -------------------------------------------------------------------------------- 1 | // github repo url 2 | export const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin'; 3 | 4 | // vue-vben-admin-next-doc 5 | export const DOC_URL = 'https://doc.vvbin.cn/'; 6 | 7 | // site url 8 | export const SITE_URL = 'https://vben.vvbin.cn/'; 9 | -------------------------------------------------------------------------------- /web/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | import { registerPiniaPersistPlugin } from '@/store/plugin/persist'; 4 | 5 | const store = createPinia(); 6 | registerPiniaPersistPlugin(store); 7 | 8 | export function setupStore(app: App) { 9 | app.use(store); 10 | } 11 | 12 | export { store }; 13 | -------------------------------------------------------------------------------- /web/src/utils/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { Persistent, BasicKeys } from '/@/utils/cache/persistent'; 2 | import { CacheTypeEnum, TOKEN_KEY } from '/@/enums/cacheEnum'; 3 | import projectSetting from '/@/settings/projectSetting'; 4 | 5 | const { permissionCacheType } = projectSetting; 6 | const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; 7 | 8 | export function getToken() { 9 | return getAuthCache(TOKEN_KEY); 10 | } 11 | 12 | export function getAuthCache(key: BasicKeys) { 13 | const fn = isLocal ? Persistent.getLocal : Persistent.getSession; 14 | return fn(key) as T; 15 | } 16 | 17 | export function setAuthCache(key: BasicKeys, value) { 18 | const fn = isLocal ? Persistent.setLocal : Persistent.setSession; 19 | return fn(key, value, true); 20 | } 21 | 22 | export function clearAuthCache(immediate = true) { 23 | const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession; 24 | return fn(immediate); 25 | } 26 | -------------------------------------------------------------------------------- /web/src/utils/copyTextToClipboard.ts: -------------------------------------------------------------------------------- 1 | import { message } from 'ant-design-vue'; 2 | 3 | export function copyText(text: string, prompt: string | null = '已成功复制到剪切板!') { 4 | navigator.clipboard.writeText(text).then( 5 | function () { 6 | prompt && message.success(prompt); 7 | }, 8 | function (error: Error) { 9 | message.error('复制失败!' + error.message); 10 | }, 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /web/src/utils/dateUtil.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Independent time operation tool to facilitate subsequent switch to dayjs 3 | */ 4 | import dayjs from 'dayjs'; 5 | 6 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; 7 | const DATE_FORMAT = 'YYYY-MM-DD'; 8 | 9 | export function formatToDateTime(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string { 10 | return dayjs(date).format(format); 11 | } 12 | 13 | export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string { 14 | return dayjs(date).format(format); 15 | } 16 | 17 | export const dateUtil = dayjs; 18 | -------------------------------------------------------------------------------- /web/src/utils/http/axios/axiosRetry.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError, AxiosInstance } from 'axios'; 2 | /** 3 | * 请求重试机制 4 | */ 5 | 6 | export class AxiosRetry { 7 | /** 8 | * 重试 9 | */ 10 | retry(axiosInstance: AxiosInstance, error: AxiosError) { 11 | // @ts-ignore 12 | const { config } = error.response; 13 | const { waitTime, count } = config?.requestOptions?.retryRequest ?? {}; 14 | config.__retryCount = config.__retryCount || 0; 15 | if (config.__retryCount >= count) { 16 | return Promise.reject(error); 17 | } 18 | config.__retryCount += 1; 19 | return this.delay(waitTime).then(() => axiosInstance(config)); 20 | } 21 | 22 | /** 23 | * 延迟 24 | */ 25 | private delay(waitTime: number) { 26 | return new Promise((resolve) => setTimeout(resolve, waitTime)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE; 2 | 3 | export function warn(message: string) { 4 | console.warn(`[${projectName} warn]:${message}`); 5 | } 6 | 7 | export function error(message: string) { 8 | throw new Error(`[${projectName} error]:${message}`); 9 | } 10 | -------------------------------------------------------------------------------- /web/src/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | const hexList: string[] = []; 2 | for (let i = 0; i <= 15; i++) { 3 | hexList[i] = i.toString(16); 4 | } 5 | 6 | export function buildUUID(): string { 7 | let uuid = ''; 8 | for (let i = 1; i <= 36; i++) { 9 | if (i === 9 || i === 14 || i === 19 || i === 24) { 10 | uuid += '-'; 11 | } else if (i === 15) { 12 | uuid += 4; 13 | } else if (i === 20) { 14 | uuid += hexList[(Math.random() * 4) | 8]; 15 | } else { 16 | uuid += hexList[(Math.random() * 16) | 0]; 17 | } 18 | } 19 | return uuid.replace(/-/g, ''); 20 | } 21 | 22 | let unique = 0; 23 | export function buildShortUUID(prefix = ''): string { 24 | const time = Date.now(); 25 | const random = Math.floor(Math.random() * 1000000000); 26 | unique++; 27 | return prefix + '_' + random + unique + String(time); 28 | } 29 | -------------------------------------------------------------------------------- /web/src/views/dashboard/analysis/components/SiteAnalysis.vue: -------------------------------------------------------------------------------- 1 | 16 | 39 | -------------------------------------------------------------------------------- /web/src/views/dashboard/analysis/components/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from 'vue'; 2 | 3 | export interface BasicProps { 4 | width: string; 5 | height: string; 6 | } 7 | export const basicProps = { 8 | width: { 9 | type: String as PropType, 10 | default: '100%', 11 | }, 12 | height: { 13 | type: String as PropType, 14 | default: '280px', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /web/src/views/dashboard/analysis/data.ts: -------------------------------------------------------------------------------- 1 | export interface GrowCardItem { 2 | icon: string; 3 | title: string; 4 | value: number; 5 | total: number; 6 | color: string; 7 | action: string; 8 | } 9 | 10 | export const growCardList: GrowCardItem[] = [ 11 | { 12 | title: '访问数', 13 | icon: 'visit-count|svg', 14 | value: 2000, 15 | total: 120000, 16 | color: 'green', 17 | action: '月', 18 | }, 19 | { 20 | title: '成交额', 21 | icon: 'total-sales|svg', 22 | value: 20000, 23 | total: 500000, 24 | color: 'blue', 25 | action: '月', 26 | }, 27 | { 28 | title: '下载数', 29 | icon: 'download-count|svg', 30 | value: 8000, 31 | total: 120000, 32 | color: 'orange', 33 | action: '周', 34 | }, 35 | { 36 | title: '成交数', 37 | icon: 'transaction|svg', 38 | value: 5000, 39 | total: 50000, 40 | color: 'purple', 41 | action: '年', 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /web/src/views/dashboard/workbench/components/QuickNav.vue: -------------------------------------------------------------------------------- 1 | 11 | 18 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/main-out/index.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/button/button.api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2022/4/3 22:35 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | 8 | import { defHttp } from '/@/utils/http/axios'; 9 | 10 | enum DeptApi { 11 | prefix = '/api/system/button', 12 | } 13 | 14 | /** 15 | * 获取 16 | */ 17 | 18 | export const getList = () => { 19 | return defHttp.get({ url: DeptApi.prefix }); 20 | }; 21 | 22 | /** 23 | * 获取all 24 | */ 25 | 26 | export const getAllList = () => { 27 | return defHttp.get({ url: DeptApi.prefix + '/all/list' }); 28 | }; 29 | 30 | /** 31 | * 保存或更新 32 | */ 33 | 34 | export const createOrUpdate = (params, isUpdate) => { 35 | if (isUpdate) { 36 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 37 | } else { 38 | return defHttp.post({ url: DeptApi.prefix, params }); 39 | } 40 | }; 41 | 42 | /** 43 | * 删除 44 | */ 45 | 46 | export const deleteItem = (id) => { 47 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 48 | }; 49 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/category_dict/category_dict.api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2022/4/8 00:12 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '/@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/category_dict', 11 | GetDeptList = '/api/system/category_dict/list/tree', 12 | } 13 | 14 | /** 15 | * 获取 16 | */ 17 | 18 | export const getList = (params) => { 19 | return defHttp.get({ url: DeptApi.GetDeptList, params }); 20 | }; 21 | 22 | /** 23 | * 保存或更新 24 | */ 25 | 26 | export const createOrUpdate = (params, isUpdate) => { 27 | if (isUpdate) { 28 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 29 | } else { 30 | return defHttp.post({ url: DeptApi.prefix, params }); 31 | } 32 | }; 33 | 34 | /** 35 | * 删除 36 | */ 37 | 38 | export const deleteItem = (id) => { 39 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 40 | }; 41 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/data-dict/dict.api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2024/4/28 23:56 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/dict', 11 | } 12 | 13 | /** 14 | * 获取 15 | */ 16 | 17 | export const getList = (params) => { 18 | return defHttp.get({ url: DeptApi.prefix, params }); 19 | }; 20 | 21 | /** 22 | * 获取all 23 | */ 24 | 25 | export const getAllList = () => { 26 | return defHttp.get({ url: DeptApi.prefix + '/all/list' }); 27 | }; 28 | 29 | /** 30 | * 保存或更新 31 | */ 32 | 33 | export const createOrUpdate = (params, isUpdate) => { 34 | if (isUpdate) { 35 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 36 | } else { 37 | return defHttp.post({ url: DeptApi.prefix, params }); 38 | } 39 | }; 40 | 41 | /** 42 | * 删除 43 | */ 44 | 45 | export const deleteItem = (id) => { 46 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 47 | }; 48 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/data-dict/dict_item/dict_item.api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2024/4/28 21:11 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/dict_item', 11 | } 12 | 13 | /** 14 | * 获取 15 | */ 16 | 17 | export const getList = (params) => { 18 | return defHttp.get({ url: DeptApi.prefix, params }); 19 | }; 20 | 21 | export const getListByCode = (dictCode) => { 22 | return defHttp.get({ url: DeptApi.prefix + '/by/dict_code/' + dictCode }); 23 | }; 24 | 25 | /** 26 | * 保存或更新 27 | */ 28 | 29 | export const createOrUpdate = (params, isUpdate) => { 30 | if (isUpdate) { 31 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 32 | } else { 33 | return defHttp.post({ url: DeptApi.prefix, params }); 34 | } 35 | }; 36 | 37 | /** 38 | * 删除 39 | */ 40 | 41 | export const deleteItem = (id) => { 42 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 43 | }; 44 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/dept/dept.api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2024/5/3 23:52 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/dept', 11 | GetDeptList = '/api/system/dept/list/tree', 12 | } 13 | 14 | /** 15 | * 获取菜单 16 | */ 17 | 18 | export const getDeptList = (params?) => { 19 | return defHttp.get({ url: DeptApi.GetDeptList, params }); 20 | }; 21 | 22 | /** 23 | * 保存或更新 24 | */ 25 | 26 | export const createOrUpdate = (params, isUpdate) => { 27 | if (isUpdate) { 28 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 29 | } else { 30 | return defHttp.post({ url: DeptApi.prefix, params }); 31 | } 32 | }; 33 | 34 | /** 35 | * 删除 36 | */ 37 | 38 | export const deleteItem = (id) => { 39 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 40 | }; 41 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/log/celery_log/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2022/5/7 22:57 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '/@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/celery_log', 11 | } 12 | 13 | export const getList = (params) => { 14 | return defHttp.get({ url: DeptApi.prefix, params }); 15 | }; 16 | 17 | export const getAllList = () => { 18 | return defHttp.get({ url: DeptApi.prefix + '/all/list' }); 19 | }; 20 | 21 | /** 22 | * 保存或更新 23 | */ 24 | 25 | export const createOrUpdate = (params, isUpdate) => { 26 | if (isUpdate) { 27 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 28 | } else { 29 | return defHttp.post({ url: DeptApi.prefix, params }); 30 | } 31 | }; 32 | 33 | /** 34 | * 删除 35 | */ 36 | 37 | export const deleteItem = (id) => { 38 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 39 | }; 40 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/log/login-log/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 20245/8 21:29 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/login_log', 11 | } 12 | 13 | export const getList = (params) => { 14 | return defHttp.get({ url: DeptApi.prefix, params }); 15 | }; 16 | 17 | export const getAllList = () => { 18 | return defHttp.get({ url: DeptApi.prefix + '/all/list' }); 19 | }; 20 | 21 | /** 22 | * 保存或更新 23 | */ 24 | 25 | export const createOrUpdate = (params, isUpdate) => { 26 | if (isUpdate) { 27 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 28 | } else { 29 | return defHttp.post({ url: DeptApi.prefix, params }); 30 | } 31 | }; 32 | 33 | /** 34 | * 删除 35 | */ 36 | 37 | export const deleteItem = (id) => { 38 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 39 | }; 40 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/log/login_log/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2022/5/8 21:29 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '/@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/login_log', 11 | } 12 | 13 | export const getList = (params) => { 14 | return defHttp.get({ url: DeptApi.prefix, params }); 15 | }; 16 | 17 | export const getAllList = () => { 18 | return defHttp.get({ url: DeptApi.prefix + '/all/list' }); 19 | }; 20 | 21 | /** 22 | * 保存或更新 23 | */ 24 | 25 | export const createOrUpdate = (params, isUpdate) => { 26 | if (isUpdate) { 27 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 28 | } else { 29 | return defHttp.post({ url: DeptApi.prefix, params }); 30 | } 31 | }; 32 | 33 | /** 34 | * 删除 35 | */ 36 | 37 | export const deleteItem = (id) => { 38 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 39 | }; 40 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/log/operation-log/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 20245/11 23:32 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/operation_log', 11 | } 12 | 13 | export const getList = (params) => { 14 | return defHttp.get({ url: DeptApi.prefix, params }); 15 | }; 16 | 17 | export const getAllList = () => { 18 | return defHttp.get({ url: DeptApi.prefix + '/all/list' }); 19 | }; 20 | 21 | /** 22 | * 保存或更新 23 | */ 24 | 25 | export const createOrUpdate = (params, isUpdate) => { 26 | if (isUpdate) { 27 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 28 | } else { 29 | return defHttp.post({ url: DeptApi.prefix, params }); 30 | } 31 | }; 32 | 33 | /** 34 | * 删除 35 | */ 36 | 37 | export const deleteItem = (id) => { 38 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 39 | }; 40 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/log/operation_log/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2022/5/11 23:32 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '/@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/operation_log', 11 | } 12 | 13 | export const getList = (params) => { 14 | return defHttp.get({ url: DeptApi.prefix, params }); 15 | }; 16 | 17 | export const getAllList = () => { 18 | return defHttp.get({ url: DeptApi.prefix + '/all/list' }); 19 | }; 20 | 21 | /** 22 | * 保存或更新 23 | */ 24 | 25 | export const createOrUpdate = (params, isUpdate) => { 26 | if (isUpdate) { 27 | return defHttp.put({ url: DeptApi.prefix + '/' + params.id, params }); 28 | } else { 29 | return defHttp.post({ url: DeptApi.prefix, params }); 30 | } 31 | }; 32 | 33 | /** 34 | * 删除 35 | */ 36 | 37 | export const deleteItem = (id) => { 38 | return defHttp.delete({ url: DeptApi.prefix + '/' + id }); 39 | }; 40 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/monitor/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2022/5/22 23:43 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/monitor', 11 | } 12 | 13 | export const getSystemInfo = () => { 14 | return defHttp.get({ url: DeptApi.prefix }); 15 | }; 16 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/monitor/component/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from 'vue'; 2 | 3 | export interface BasicProps { 4 | width: string; 5 | height: string; 6 | } 7 | export const basicProps = { 8 | width: { 9 | type: String as PropType, 10 | default: '100%', 11 | }, 12 | height: { 13 | type: String as PropType, 14 | default: '280px', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /web/src/views/fuadmin/system/role/role-user/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- coding: utf-8 -*- 3 | * time: 2022/4/28 21:11 4 | * author: 臧成龙 5 | * QQ: 939589097 6 | */ 7 | import { defHttp } from '/@/utils/http/axios'; 8 | 9 | enum DeptApi { 10 | prefix = '/api/system/role/users/by/role_id', 11 | } 12 | 13 | /** 14 | * 获取 15 | */ 16 | 17 | export const getList = (params) => { 18 | return defHttp.get({ url: DeptApi.prefix, params }); 19 | }; 20 | 21 | export const getUserListByRoleCode = (params) => { 22 | return defHttp.get({ url: DeptApi.prefix, params }); 23 | }; 24 | 25 | /** 26 | * 保存 27 | */ 28 | 29 | export const addUserToRole = (params) => { 30 | return defHttp.post({ url: DeptApi.prefix, params }); 31 | }; 32 | 33 | /** 34 | * 删除 35 | */ 36 | 37 | export const removeUserFromRole = (params) => { 38 | return defHttp.delete({ url: DeptApi.prefix, params }); 39 | }; 40 | -------------------------------------------------------------------------------- /web/src/views/sys/error-log/DetailModal.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /web/src/views/sys/exception/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Exception } from './Exception.vue'; 2 | -------------------------------------------------------------------------------- /web/src/views/sys/form-design/components/VFormDesign/styles/variable.less: -------------------------------------------------------------------------------- 1 | // 表单设计器样式 2 | @primary-color: #13c2c2; 3 | @layout-color: #9867f7; 4 | 5 | @primary-background-color: fade(@primary-color, 6%); 6 | @primary-hover-bg-color: fade(@primary-color, 20%); 7 | @layout-background-color: fade(@layout-color, 12%); 8 | @layout-hover-bg-color: fade(@layout-color, 24%); 9 | 10 | @title-text-color: #fff; 11 | @border-color: #ccc; 12 | 13 | @left-right-width: 280px; 14 | @header-height: 56px; 15 | @operating-area-height: 45px; 16 | -------------------------------------------------------------------------------- /web/src/views/sys/form-design/examples/baseForm.vue: -------------------------------------------------------------------------------- 1 | 4 | 38 | -------------------------------------------------------------------------------- /web/src/views/sys/form-design/hooks/useFormDesignState.ts: -------------------------------------------------------------------------------- 1 | import { inject, Ref } from 'vue'; 2 | import { IFormDesignMethods } from '../typings/form-type'; 3 | import { IFormConfig } from '../typings/v-form-component'; 4 | 5 | /** 6 | * 获取formDesign状态 7 | */ 8 | export function useFormDesignState() { 9 | const formConfig = inject('formConfig') as Ref; 10 | const formDesignMethods = inject('formDesignMethods') as IFormDesignMethods; 11 | return { formConfig, formDesignMethods }; 12 | } 13 | 14 | export function useFormModelState() { 15 | const formModel = inject('formModel') as Ref<{}>; 16 | const setFormModel = inject('setFormModelMethod') as (key: String, value: any) => void; 17 | return { formModel, setFormModel }; 18 | } 19 | -------------------------------------------------------------------------------- /web/src/views/sys/form-design/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/src/views/sys/form-design/typings/base-type.ts: -------------------------------------------------------------------------------- 1 | export interface IAnyObject { 2 | [key: string]: T; 3 | } 4 | 5 | export interface IInputEvent { 6 | target: { 7 | value: any; 8 | checked: boolean; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /web/src/views/sys/form-design/utils/message.ts: -------------------------------------------------------------------------------- 1 | import { useMessage } from '/@/hooks/web/useMessage'; 2 | 3 | const { createMessage } = useMessage(); 4 | const message = Object.assign({ 5 | success: (msg: string) => { 6 | createMessage.success(msg); 7 | }, 8 | error: (msg: string) => { 9 | createMessage.error(msg); 10 | }, 11 | warning: (msg: string) => { 12 | createMessage.warning(msg); 13 | }, 14 | info: (msg: string) => { 15 | createMessage.info(msg); 16 | }, 17 | }); 18 | 19 | export default message; 20 | -------------------------------------------------------------------------------- /web/src/views/sys/iframe/FrameBlank.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /web/src/views/sys/lock/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | -------------------------------------------------------------------------------- /web/src/views/sys/login/LoginFormTitle.vue: -------------------------------------------------------------------------------- 1 | 6 | 26 | -------------------------------------------------------------------------------- /web/src/views/sys/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 31 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "@vben/ts-config/vue-app.json", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "declaration": false, 7 | "types": ["vite/client"], 8 | "paths": { 9 | "/@/*": ["src/*"], 10 | "/#/*": ["types/*"], 11 | "@/*": ["src/*"], 12 | "#/*": ["types/*"] 13 | } 14 | }, 15 | "include": [ 16 | "tests/**/*.ts", 17 | "src/**/*.ts", 18 | "src/**/*.d.ts", 19 | "src/**/*.tsx", 20 | "src/**/*.vue", 21 | "types/**/*.d.ts", 22 | "types/**/*.ts", 23 | "build/**/*.ts", 24 | "build/**/*.d.ts", 25 | "mock/**/*.ts", 26 | "vite.config.ts" 27 | ], 28 | "exclude": ["node_modules", "tests/server/**/*.ts", "dist", "**/*.js"] 29 | } 30 | -------------------------------------------------------------------------------- /web/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**"] 7 | }, 8 | "stub": {}, 9 | "lint": {}, 10 | "clean": { 11 | "cache": false 12 | }, 13 | "dev": { 14 | "cache": false, 15 | "persistent": true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R; 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise; 7 | } 8 | 9 | declare type RefType = T | null; 10 | 11 | declare type LabelValueOptions = { 12 | label: string; 13 | value: any; 14 | [key: string]: string | number | boolean; 15 | }[]; 16 | 17 | declare type EmitType = ReturnType; 18 | 19 | declare type TargetContext = '_self' | '_blank'; 20 | 21 | declare interface ComponentElRef { 22 | $el: T; 23 | } 24 | 25 | declare type ComponentRef = ComponentElRef | null; 26 | 27 | declare type ElRef = Nullable; 28 | -------------------------------------------------------------------------------- /web/types/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue'; 3 | 4 | const Component: DefineComponent<{}, {}, any>; 5 | export default Component; 6 | } 7 | 8 | declare module 'ant-design-vue/es/locale/*' { 9 | import { Locale } from 'ant-design-vue/types/locale-provider'; 10 | 11 | const locale: Locale & ReadonlyRecordable; 12 | export default locale as Locale & ReadonlyRecordable; 13 | } 14 | 15 | declare module 'virtual:*' { 16 | const result: any; 17 | export default result; 18 | } 19 | -------------------------------------------------------------------------------- /web/types/utils.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue'; 2 | 3 | export type DynamicProps = { 4 | [P in keyof T]: Ref | T[P] | ComputedRef; 5 | }; 6 | --------------------------------------------------------------------------------