├── .browserslistrc ├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .commitlintrc.cjs ├── .dockerignore ├── .editorconfig ├── .env ├── .env.analyze ├── .env.development ├── .env.production ├── .env.test ├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .github └── workflows │ ├── eslint.yml │ └── node.js.yml ├── .gitignore ├── .gitpod.yml ├── .husky └── pre-commit ├── .npmrc ├── .originallicense └── LICENSE ├── .prettierignore ├── .prettierrc.cjs ├── .stylelintignore ├── .stylelintrc.cjs ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.En.md ├── README.md ├── deploy └── default.conf ├── index.html ├── internal ├── eslint-config │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── build.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── strict.ts │ └── tsconfig.json ├── stylelint-config │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── build.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── ts-config │ ├── .eslintignore │ ├── base.json │ ├── node-server.json │ ├── node.json │ ├── package.json │ └── vue-app.json └── vite-config │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── 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 └── sys │ └── user.ts ├── package.json ├── packages ├── .gitkeep ├── hooks │ ├── .eslintrc.cjs │ ├── build.config.ts │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── onMountedOrActivated.ts │ │ ├── useAttrs.ts │ │ ├── useRefs.ts │ │ ├── useScrollTo.ts │ │ └── useWindowSizeFn.ts │ └── tsconfig.json └── types │ ├── .eslintrc.cjs │ ├── build.config.ts │ ├── package.json │ ├── src │ ├── index.ts │ └── utils.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── public ├── favicon.ico └── resource │ ├── img │ ├── pwa-192x192.png │ └── pwa-512x512.png │ └── tinymce │ ├── icons │ └── default │ │ └── icons.min.js │ ├── langs │ ├── README.md │ └── zh-Hans.js │ ├── license.txt │ ├── models │ └── dom │ │ └── model.min.js │ ├── plugins │ ├── accordion │ │ └── plugin.min.js │ ├── advlist │ │ └── plugin.min.js │ ├── anchor │ │ └── plugin.min.js │ ├── autolink │ │ └── plugin.min.js │ ├── autoresize │ │ └── plugin.min.js │ ├── autosave │ │ └── plugin.min.js │ ├── charmap │ │ └── plugin.min.js │ ├── code │ │ └── plugin.min.js │ ├── codesample │ │ └── plugin.min.js │ ├── directionality │ │ └── plugin.min.js │ ├── emoticons │ │ ├── js │ │ │ ├── emojiimages.js │ │ │ ├── emojiimages.min.js │ │ │ ├── emojis.js │ │ │ └── emojis.min.js │ │ └── plugin.min.js │ ├── fullscreen │ │ └── plugin.min.js │ ├── help │ │ ├── js │ │ │ └── i18n │ │ │ │ └── keynav │ │ │ │ ├── ar.js │ │ │ │ ├── bg_BG.js │ │ │ │ ├── ca.js │ │ │ │ ├── cs.js │ │ │ │ ├── da.js │ │ │ │ ├── de.js │ │ │ │ ├── el.js │ │ │ │ ├── en.js │ │ │ │ ├── es.js │ │ │ │ ├── eu.js │ │ │ │ ├── fa.js │ │ │ │ ├── fi.js │ │ │ │ ├── fr_FR.js │ │ │ │ ├── he_IL.js │ │ │ │ ├── hi.js │ │ │ │ ├── hr.js │ │ │ │ ├── hu_HU.js │ │ │ │ ├── id.js │ │ │ │ ├── it.js │ │ │ │ ├── ja.js │ │ │ │ ├── kk.js │ │ │ │ ├── ko_KR.js │ │ │ │ ├── ms.js │ │ │ │ ├── nb_NO.js │ │ │ │ ├── nl.js │ │ │ │ ├── pl.js │ │ │ │ ├── pt_BR.js │ │ │ │ ├── pt_PT.js │ │ │ │ ├── ro.js │ │ │ │ ├── ru.js │ │ │ │ ├── sk.js │ │ │ │ ├── sl_SI.js │ │ │ │ ├── sv_SE.js │ │ │ │ ├── th_TH.js │ │ │ │ ├── tr.js │ │ │ │ ├── uk.js │ │ │ │ ├── vi.js │ │ │ │ ├── zh_CN.js │ │ │ │ └── zh_TW.js │ │ └── plugin.min.js │ ├── image │ │ └── plugin.min.js │ ├── importcss │ │ └── plugin.min.js │ ├── insertdatetime │ │ └── plugin.min.js │ ├── link │ │ └── plugin.min.js │ ├── lists │ │ └── plugin.min.js │ ├── media │ │ └── plugin.min.js │ ├── nonbreaking │ │ └── plugin.min.js │ ├── pagebreak │ │ └── plugin.min.js │ ├── preview │ │ └── plugin.min.js │ ├── quickbars │ │ └── plugin.min.js │ ├── save │ │ └── plugin.min.js │ ├── searchreplace │ │ └── plugin.min.js │ ├── table │ │ └── plugin.min.js │ ├── template │ │ └── plugin.min.js │ ├── visualblocks │ │ └── plugin.min.js │ ├── visualchars │ │ └── plugin.min.js │ └── wordcount │ │ └── plugin.min.js │ ├── skins │ ├── content │ │ ├── dark │ │ │ ├── content.js │ │ │ └── content.min.css │ │ ├── default │ │ │ ├── content.js │ │ │ └── content.min.css │ │ ├── document │ │ │ ├── content.js │ │ │ └── content.min.css │ │ ├── tinymce-5-dark │ │ │ ├── content.js │ │ │ └── content.min.css │ │ ├── tinymce-5 │ │ │ ├── content.js │ │ │ └── content.min.css │ │ └── writer │ │ │ ├── content.js │ │ │ └── content.min.css │ └── ui │ │ ├── oxide-dark │ │ ├── content.inline.js │ │ ├── content.inline.min.css │ │ ├── content.js │ │ ├── content.min.css │ │ ├── skin.js │ │ ├── skin.min.css │ │ ├── skin.shadowdom.js │ │ └── skin.shadowdom.min.css │ │ ├── oxide │ │ ├── content.inline.js │ │ ├── content.inline.min.css │ │ ├── content.js │ │ ├── content.min.css │ │ ├── skin.js │ │ ├── skin.min.css │ │ ├── skin.shadowdom.js │ │ └── skin.shadowdom.min.css │ │ ├── tinymce-5-dark │ │ ├── content.inline.js │ │ ├── content.inline.min.css │ │ ├── content.js │ │ ├── content.min.css │ │ ├── skin.js │ │ ├── skin.min.css │ │ ├── skin.shadowdom.js │ │ └── skin.shadowdom.min.css │ │ └── tinymce-5 │ │ ├── content.inline.js │ │ ├── content.inline.min.css │ │ ├── content.js │ │ ├── content.min.css │ │ ├── skin.js │ │ ├── skin.min.css │ │ ├── skin.shadowdom.js │ │ └── skin.shadowdom.min.css │ ├── themes │ └── silver │ │ └── theme.min.js │ ├── tinymce.d.ts │ └── tinymce.min.js ├── src ├── App.vue ├── api │ ├── fms │ │ ├── cloudFile.ts │ │ ├── cloudFileTag.ts │ │ ├── file.ts │ │ ├── fileTag.ts │ │ ├── initialize.ts │ │ ├── model │ │ │ ├── cloudFileModel.ts │ │ │ ├── cloudFileTagModel.ts │ │ │ ├── fileModel.ts │ │ │ ├── fileTagModel.ts │ │ │ └── storageProviderModel.ts │ │ └── storageProvider.ts │ ├── mcms │ │ ├── emailLog.ts │ │ ├── emailProvider.ts │ │ ├── messageSender.ts │ │ ├── model │ │ │ ├── emailLogModel.ts │ │ │ ├── emailProviderModel.ts │ │ │ ├── messageModel.ts │ │ │ ├── smsLogModel.ts │ │ │ └── smsProviderModel.ts │ │ ├── smsLog.ts │ │ └── smsProvider.ts │ ├── member │ │ ├── initialize.ts │ │ ├── member.ts │ │ ├── memberRank.ts │ │ ├── model │ │ │ ├── memberModel.ts │ │ │ └── memberRankModel.ts │ │ ├── oauthProvider.ts │ │ └── token.ts │ ├── model │ │ └── baseModel.ts │ └── sys │ │ ├── api.ts │ │ ├── authority.ts │ │ ├── captcha.ts │ │ ├── configuration.ts │ │ ├── department.ts │ │ ├── dictionary.ts │ │ ├── dictionaryDetail.ts │ │ ├── initialize.ts │ │ ├── menu.ts │ │ ├── model │ │ ├── apiModel.ts │ │ ├── authorityModel.ts │ │ ├── captcha.ts │ │ ├── configurationModel.ts │ │ ├── departmentModel.ts │ │ ├── dictionaryDetailModel.ts │ │ ├── dictionaryModel.ts │ │ ├── menuModel.ts │ │ ├── oauthProviderModel.ts │ │ ├── positionModel.ts │ │ ├── roleModel.ts │ │ ├── taskLogModel.ts │ │ ├── taskModel.ts │ │ ├── tokenModel.ts │ │ ├── uploadModel.ts │ │ └── userModel.ts │ │ ├── oauthProvider.ts │ │ ├── position.ts │ │ ├── role.ts │ │ ├── task.ts │ │ ├── taskLog.ts │ │ ├── token.ts │ │ ├── upload.ts │ │ └── user.ts ├── assets │ ├── icons │ │ ├── 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 │ │ ├── moon.svg │ │ ├── sun.svg │ │ ├── test.svg │ │ ├── total-sales.svg │ │ ├── transaction.svg │ │ └── visit-count.svg │ ├── images │ │ ├── header.jpg │ │ ├── login-page-bg-2.png │ │ └── 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 │ │ │ └── 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 │ │ │ ├── ApiMultipleSelect.vue │ │ │ ├── ApiRadioGroup.vue │ │ │ ├── ApiSelect.vue │ │ │ ├── ApiTransfer.vue │ │ │ ├── ApiTree.vue │ │ │ ├── ApiTreeSelect.vue │ │ │ ├── DictionarySelect.vue │ │ │ ├── FormAction.vue │ │ │ ├── FormItem.vue │ │ │ ├── RadioButtonGroup.vue │ │ │ ├── SimpleRangePicker.vue │ │ │ └── SimpleTimePicker.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 │ ├── JsonPreview │ │ ├── index.ts │ │ └── src │ │ │ ├── JsonPreview.vue │ │ │ └── typing.ts │ ├── 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 │ ├── 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 │ │ │ ├── DictionaryTag.vue │ │ │ ├── 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 │ │ │ ├── helper.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 │ │ │ ├── components │ │ │ ├── FileList.vue │ │ │ ├── ImageUpload.vue │ │ │ ├── ThumbUrl.vue │ │ │ ├── UploadModal.vue │ │ │ ├── UploadPreviewModal.vue │ │ │ └── data.tsx │ │ │ ├── helper.ts │ │ │ ├── hooks │ │ │ └── useUpload.ts │ │ │ ├── props.ts │ │ │ └── types │ │ │ └── typing.ts │ ├── Verify │ │ ├── index.ts │ │ └── src │ │ │ ├── DragVerify.vue │ │ │ ├── ImgRotate.vue │ │ │ ├── props.ts │ │ │ └── typing.ts │ ├── VirtualScroll │ │ ├── index.ts │ │ └── src │ │ │ └── VirtualScroll.vue │ └── registerGlobComp.ts ├── design │ ├── ant │ │ ├── btn.less │ │ ├── index.less │ │ ├── input.less │ │ ├── pagination.less │ │ ├── popconfirm.less │ │ └── table.less │ ├── color.less │ ├── config.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 │ ├── commonEnum.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 │ │ │ │ ├── themeChange │ │ │ │ │ └── ThemeIcon.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 │ │ │ │ ├── SettingButton.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.ts │ │ │ ├── component.ts │ │ │ ├── fms.ts │ │ │ ├── layout.ts │ │ │ ├── mcms.ts │ │ │ ├── routes │ │ │ │ ├── basic.ts │ │ │ │ └── system.ts │ │ │ └── sys.ts │ │ ├── zh-CN │ │ │ ├── antdLocale │ │ │ │ └── DatePicker.ts │ │ │ ├── common.ts │ │ │ ├── component.ts │ │ │ ├── fms.ts │ │ │ ├── layout.ts │ │ │ ├── mcms.ts │ │ │ ├── routes │ │ │ │ ├── basic.ts │ │ │ │ └── system.ts │ │ │ └── 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 │ └── types.ts ├── settings │ ├── componentSetting.ts │ ├── designSetting.ts │ ├── encryptionSetting.ts │ ├── localeSetting.ts │ ├── projectSetting.ts │ └── siteSetting.ts ├── store │ ├── index.ts │ └── modules │ │ ├── app.ts │ │ ├── dictionary.ts │ │ ├── dynamicConfig.ts │ │ ├── errorLog.ts │ │ ├── locale.ts │ │ ├── lock.ts │ │ ├── multipleTab.ts │ │ ├── permission.ts │ │ ├── role.ts │ │ └── user.ts ├── utils │ ├── bem.ts │ ├── cipher.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 │ ├── 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 │ ├── object.ts │ ├── propTypes.ts │ ├── tree.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 │ │ ├── ProjectCard.vue │ │ ├── QuickNav.vue │ │ ├── WorkbenchHeader.vue │ │ └── data.ts │ │ └── index.vue │ ├── fms │ ├── cloudFile │ │ ├── CloudFileDrawer.vue │ │ ├── cloudFile.data.ts │ │ └── index.vue │ ├── cloudFileTag │ │ ├── CloudFileTagDrawer.vue │ │ ├── cloudFileTag.data.ts │ │ └── index.vue │ ├── file │ │ ├── FileDrawer.vue │ │ ├── file.data.ts │ │ └── index.vue │ ├── fileTag │ │ ├── TagDrawer.vue │ │ ├── index.vue │ │ └── tag.data.ts │ └── storageProvider │ │ ├── StorageProviderDrawer.vue │ │ ├── index.vue │ │ └── storageProvider.data.ts │ ├── mcms │ ├── emailProvider │ │ ├── EmailProviderDrawer.vue │ │ ├── LogModal.vue │ │ ├── email.data.ts │ │ ├── emailLog.data.ts │ │ ├── emailProvider.data.ts │ │ └── index.vue │ └── smsProvider │ │ ├── LogModal.vue │ │ ├── SmsProviderDrawer.vue │ │ ├── index.vue │ │ ├── sms.data.ts │ │ ├── smsLog.data.ts │ │ └── smsProvider.data.ts │ ├── mms │ ├── member │ │ ├── MemberDrawer.vue │ │ ├── RankTree.vue │ │ ├── index.vue │ │ └── member.data.ts │ ├── memberRank │ │ ├── MemberRankDrawer.vue │ │ ├── index.vue │ │ └── memberRank.data.ts │ ├── oauth │ │ ├── OauthDrawer.vue │ │ ├── callback.vue │ │ ├── index.vue │ │ └── oauth.data.ts │ └── token │ │ ├── index.vue │ │ └── token.data.ts │ └── sys │ ├── about │ └── index.vue │ ├── api │ ├── ApiDrawer.vue │ ├── api.data.ts │ └── index.vue │ ├── configuration │ ├── ConfigurationDrawer.vue │ ├── configuration.data.ts │ └── index.vue │ ├── department │ ├── DepartmentDrawer.vue │ ├── department.data.ts │ └── index.vue │ ├── dictionary │ ├── DictionaryDrawer.vue │ ├── dictionary.data.ts │ └── index.vue │ ├── dictionaryDetail │ ├── DictionaryDetailDrawer.vue │ ├── dictionaryDetail.data.ts │ └── index.vue │ ├── exception │ ├── Exception.vue │ └── index.ts │ ├── iframe │ ├── FrameBlank.vue │ └── index.vue │ ├── initialize │ └── index.vue │ ├── lock │ ├── LockPage.vue │ ├── index.vue │ └── useNow.ts │ ├── login │ ├── ForgetPasswordForm.vue │ ├── Login.vue │ ├── LoginForm.vue │ ├── LoginFormTitle.vue │ ├── QrCodeForm.vue │ ├── RegisterForm.vue │ ├── SessionTimeoutLogin.vue │ └── useLogin.ts │ ├── menu │ ├── MenuDrawer.vue │ ├── index.vue │ └── menu.data.ts │ ├── oauth │ ├── OauthDrawer.vue │ ├── callback.vue │ ├── index.vue │ └── oauth.data.ts │ ├── position │ ├── PositionDrawer.vue │ ├── index.vue │ └── position.data.ts │ ├── profile │ ├── data.ts │ └── index.vue │ ├── redirect │ └── index.vue │ ├── role │ ├── RoleDrawer.vue │ ├── index.vue │ └── role.data.ts │ ├── task │ ├── LogModal.vue │ ├── TaskDrawer.vue │ ├── index.vue │ ├── task.data.ts │ └── tasklog.data.ts │ ├── token │ ├── index.vue │ └── token.data.ts │ └── user │ ├── DeptTree.vue │ ├── UserDrawer.vue │ ├── index.vue │ └── user.data.ts ├── tsconfig.json ├── turbo.json ├── types ├── axios.d.ts ├── codemirror.d.ts ├── config.d.ts ├── global.d.ts ├── index.d.ts ├── module.d.ts ├── store.d.ts ├── tree.d.ts ├── utils.d.ts ├── vue-router.d.ts └── vueuseCore.d.ts ├── uno.config.ts └── vite.config.ts /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not ie 11 5 | -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/suyuan32/simple-admin-backend-ui 6 | options: 7 | commits: 8 | # filters: 9 | # Type: 10 | # - feat 11 | # - fix 12 | # - perf 13 | # - refactor 14 | commit_groups: 15 | # title_maps: 16 | # feat: Features 17 | # fix: Bug Fixes 18 | # perf: Performance Improvements 19 | # refactor: Code Refactoring 20 | header: 21 | pattern: "^(\\w*)\\:\\s(.*)$" 22 | pattern_maps: 23 | - Type 24 | - Subject 25 | notes: 26 | keywords: 27 | - BREAKING CHANGE 28 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .husky/ 3 | build/ 4 | public/ 5 | src/ 6 | tests/ 7 | types/ -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # spa-title 2 | VITE_GLOB_APP_TITLE = Simple Admin -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = false 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | VITE_BUILD_COMPRESS = 'none' 8 | 9 | # Delete console 10 | VITE_DROP_CONSOLE = false 11 | 12 | # Basic interface address SPA 13 | VITE_GLOB_API_URL= 14 | 15 | # File upload address, optional 16 | VITE_GLOB_UPLOAD_URL=/fms-api/cloud_file/upload 17 | 18 | # Interface prefix 19 | VITE_GLOB_API_URL_PREFIX= 20 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = false 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= 15 | 16 | # File upload address, optional 17 | VITE_GLOB_UPLOAD_URL=/fms-api/cloud_file/upload 18 | 19 | # Interface prefix 20 | VITE_GLOB_API_URL_PREFIX= 21 | -------------------------------------------------------------------------------- /.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=/sys-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=/fms-api/upload 19 | 20 | # Interface prefix 21 | VITE_GLOB_API_URL_PREFIX= 22 | -------------------------------------------------------------------------------- /.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 | package.json 17 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben'], 4 | rules: { 5 | 'no-undef': 'off', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.github/workflows/eslint.yml: -------------------------------------------------------------------------------- 1 | name: ESLint 2 | 3 | on: 4 | push: 5 | branches: ['master', 'dev'] 6 | pull_request: 7 | branches: ['master', 'dev'] 8 | schedule: 9 | - cron: '0 0 * * Mon' 10 | 11 | jobs: 12 | eslint: 13 | name: Run eslint scanning 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | security-events: write 18 | actions: read 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v3 23 | 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | 29 | - name: Install pnpm 30 | run: npm install -g pnpm 31 | 32 | - name: Install ESLint 33 | run: | 34 | npm install -g eslint 35 | npm install -g @microsoft/eslint-formatter-sarif 36 | 37 | - name: Install dependencies 38 | run: pnpm install 39 | 40 | - name: Run ESLint 41 | run: | 42 | pnpm lint 43 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master, dev] 6 | pull_request: 7 | branches: [master, dev] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x, 20.x] 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Install pnpm 27 | run: npm install -g pnpm 28 | 29 | - name: Install dependencies 30 | run: pnpm install 31 | 32 | - name: Build 33 | run: pnpm build 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node-jiti 3 | .DS_Store 4 | dist 5 | .cache 6 | .turbo 7 | 8 | tests/server/static 9 | tests/server/static/upload 10 | 11 | .local 12 | # local env files 13 | .env.local 14 | .env.*.local 15 | .eslintcache 16 | 17 | # Log files 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | pnpm-debug.log* 22 | 23 | # Editor directories and files 24 | .idea 25 | # .vscode 26 | *.suo 27 | *.ntvs* 28 | *.njsproj 29 | *.sln 30 | *.sw? 31 | 32 | package-lock.json 33 | pnpm-lock.yaml 34 | 35 | .history 36 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 3344 3 | onOpen: open-preview 4 | tasks: 5 | - init: pnpm install 6 | command: pnpm run dev 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PATH="/usr/local/bin:$PATH" 3 | 4 | pnpm run lint 5 | 6 | # Format and submit code according to lintstagedrc.js configuration 7 | # npm run lint 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.originallicense/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, Vben 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist 2 | public 3 | -------------------------------------------------------------------------------- /.stylelintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/stylelint-config'], 4 | }; 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vue.volar", 4 | "dbaeumer.vscode-eslint", 5 | "stylelint.vscode-stylelint", 6 | "esbenp.prettier-vscode", 7 | "mrmlnc.vscode-less", 8 | "lokalise.i18n-ally", 9 | "antfu.iconify", 10 | "antfu.unocss", 11 | "mikestead.dotenv" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Launch Chrome", 8 | "url": "http://localhost:5173", 9 | "webRoot": "${workspaceFolder}/src", 10 | "sourceMaps": true 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.27.0-alpine 2 | 3 | COPY dist/ /usr/share/nginx/html/ 4 | COPY deploy/default.conf /etc/nginx/conf.d/ 5 | 6 | LABEL MAINTAINER="yuansu.china.work@gmail.com" 7 | 8 | EXPOSE 80 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=$(shell git describe --tags --always) 2 | 3 | .PHONY: docker 4 | docker: # Compile and build the docker | 编译并构建 docker 镜像 5 | pnpm install 6 | pnpm build 7 | docker build -f Dockerfile -t ${DOCKER_USERNAME}/backend-ui:${VERSION} . 8 | 9 | .PHONY: docker-not-build 10 | docker-not-build: # Build the docker without compiling | 不编译直接构建镜像 11 | docker build -f Dockerfile -t ${DOCKER_USERNAME}/backend-ui:${VERSION} . 12 | 13 | .PHONY: publish-docker 14 | publish-docker: # Publish the docker | 发布镜像 15 | docker push ${DOCKER_USERNAME}/backend-ui:${VERSION} 16 | 17 | .PHONY: run-docker 18 | run-docker: # Run the docker image | 运行 docker 镜像 19 | docker volume create backendui 20 | docker run -d --name ${DOCKER_USERNAME}/backend-ui:${VERSION} -p 80:80 -v backendui:/etc/nginx --network docker-compose_simple-admin ${DOCKER_USERNAME}/backendui:${VERSION} 21 | 22 | .PHONY: help 23 | help: # Show help | 显示帮助 24 | @grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | sort | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done 25 | -------------------------------------------------------------------------------- /internal/eslint-config/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | *.md 5 | *.woff 6 | *.ttf 7 | .turbo 8 | dist 9 | package.json 10 | -------------------------------------------------------------------------------- /internal/eslint-config/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/stylelint-config/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | *.md 5 | *.woff 6 | *.ttf 7 | .turbo 8 | dist 9 | package.json 10 | -------------------------------------------------------------------------------- /internal/stylelint-config/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/ts-config/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /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": "bundler", 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "type": "module", 16 | "files": [ 17 | "base.json", 18 | "node.json", 19 | "vue-app.json", 20 | "node-server.json" 21 | ], 22 | "dependencies": { 23 | "@types/node": "^20.17.8", 24 | "vite": "^5.4.11" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/vite-config/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | *.sh 3 | node_modules 4 | *.md 5 | *.woff 6 | *.ttf 7 | .turbo 8 | dist 9 | package.json 10 | -------------------------------------------------------------------------------- /internal/vite-config/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /internal/vite-config/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config/application'; 2 | export * from './config/package'; 3 | -------------------------------------------------------------------------------- /internal/vite-config/src/plugins/compress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated 3 | * https://github.com/anncwb/vite-plugin-compression 4 | */ 5 | import type { PluginOption } from 'vite'; 6 | import compressPlugin from 'vite-plugin-compression'; 7 | 8 | export function configCompressPlugin({ 9 | compress, 10 | deleteOriginFile = false, 11 | }: { 12 | compress: string; 13 | deleteOriginFile?: boolean; 14 | }): PluginOption[] { 15 | const compressList = compress.split(','); 16 | 17 | const plugins: PluginOption[] = []; 18 | 19 | if (compressList.includes('gzip')) { 20 | plugins.push( 21 | compressPlugin({ 22 | ext: '.gz', 23 | deleteOriginFile, 24 | }), 25 | ); 26 | } 27 | 28 | if (compressList.includes('brotli')) { 29 | plugins.push( 30 | compressPlugin({ 31 | ext: '.br', 32 | algorithm: 'brotliCompress', 33 | deleteOriginFile, 34 | }), 35 | ); 36 | } 37 | return plugins; 38 | } 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/client'; 2 | 3 | const modules = import.meta.glob('./**/*.ts', { eager: true }); 4 | 5 | const mockModules: any[] = []; 6 | Object.keys(modules).forEach((key) => { 7 | if (key.includes('/_')) { 8 | return; 9 | } 10 | mockModules.push(...(modules as Recordable)[key].default); 11 | }); 12 | 13 | /** 14 | * Used in a production environment. Need to manually import all modules 15 | */ 16 | export function setupProdMockServer() { 17 | createProdMockServer(mockModules); 18 | } 19 | -------------------------------------------------------------------------------- /packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/a2fe82117b6980f75c7c30569dec2200a87e5331/packages/.gitkeep -------------------------------------------------------------------------------- /packages/hooks/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "type": "module", 16 | "exports": { 17 | ".": { 18 | "default": "./src/index.ts" 19 | } 20 | }, 21 | "main": "./src/index.ts", 22 | "module": "./src/index.ts", 23 | "files": [ 24 | "dist" 25 | ], 26 | "scripts": { 27 | "//build": "pnpm unbuild", 28 | "//stub": "pnpm unbuild --stub", 29 | "clean": "pnpm rimraf .turbo node_modules dist", 30 | "lint": "pnpm eslint ." 31 | }, 32 | "dependencies": { 33 | "@vueuse/core": "^10.11.1", 34 | "vue": "^3.5.13" 35 | }, 36 | "devDependencies": { 37 | "@vben/types": "workspace:*" 38 | } 39 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/hooks/src/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { type AnyFunction } from '@vben/types'; 2 | import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'; 3 | 4 | interface UseWindowSizeOptions { 5 | wait?: number; 6 | once?: boolean; 7 | immediate?: boolean; 8 | listenerOptions?: AddEventListenerOptions | boolean; 9 | } 10 | 11 | function useWindowSizeFn(fn: AnyFunction, options: UseWindowSizeOptions = {}) { 12 | const { wait = 150, immediate } = options; 13 | let handler = () => { 14 | fn(); 15 | }; 16 | const handleSize = useDebounceFn(handler, wait); 17 | handler = handleSize; 18 | 19 | const start = () => { 20 | if (immediate) { 21 | handler(); 22 | } 23 | window.addEventListener('resize', handler); 24 | }; 25 | 26 | const stop = () => { 27 | window.removeEventListener('resize', handler); 28 | }; 29 | 30 | tryOnMounted(() => { 31 | start(); 32 | }); 33 | 34 | tryOnUnmounted(() => { 35 | stop(); 36 | }); 37 | return { start, stop }; 38 | } 39 | 40 | export { useWindowSizeFn, type UseWindowSizeOptions }; 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /packages/types/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@vben/eslint-config/strict'], 4 | }; 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "type": "module", 16 | "exports": { 17 | ".": { 18 | "default": "./src/index.ts" 19 | } 20 | }, 21 | "main": "./src/index.ts", 22 | "module": "./src/index.ts", 23 | "files": [ 24 | "dist" 25 | ], 26 | "scripts": { 27 | "//build": "pnpm unbuild", 28 | "//stub": "pnpm unbuild --stub", 29 | "clean": "pnpm rimraf .turbo node_modules dist", 30 | "lint": "pnpm eslint ." 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /packages/types/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 任意类型的异步函数 3 | */ 4 | type AnyPromiseFunction = (...arg: any[]) => PromiseLike; 5 | 6 | /** 7 | * 任意类型的普通函数 8 | */ 9 | type AnyNormalFunction = (...arg: any[]) => any; 10 | 11 | /** 12 | * 任意类型的函数 13 | */ 14 | type AnyFunction = AnyNormalFunction | AnyPromiseFunction; 15 | 16 | /** 17 | * T | null 包装 18 | */ 19 | type Nullable = T | null; 20 | 21 | /** 22 | * T | Not null 包装 23 | */ 24 | type NonNullable = T extends null | undefined ? never : T; 25 | 26 | /** 27 | * 字符串类型对象 28 | */ 29 | type Recordable = Record; 30 | 31 | /** 32 | * 字符串类型对象(只读) 33 | */ 34 | interface ReadonlyRecordable { 35 | readonly [key: string]: T; 36 | } 37 | 38 | /** 39 | * setTimeout 返回值类型 40 | */ 41 | type TimeoutHandle = ReturnType; 42 | 43 | /** 44 | * setInterval 返回值类型 45 | */ 46 | type IntervalHandle = ReturnType; 47 | 48 | export { 49 | type AnyFunction, 50 | type AnyNormalFunction, 51 | type AnyPromiseFunction, 52 | type IntervalHandle, 53 | type NonNullable, 54 | type Nullable, 55 | type ReadonlyRecordable, 56 | type Recordable, 57 | type TimeoutHandle, 58 | }; 59 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'internal/*' 3 | - 'packages/*' 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/a2fe82117b6980f75c7c30569dec2200a87e5331/public/favicon.ico -------------------------------------------------------------------------------- /public/resource/img/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/a2fe82117b6980f75c7c30569dec2200a87e5331/public/resource/img/pwa-192x192.png -------------------------------------------------------------------------------- /public/resource/img/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/a2fe82117b6980f75c7c30569dec2200a87e5331/public/resource/img/pwa-512x512.png -------------------------------------------------------------------------------- /public/resource/tinymce/langs/README.md: -------------------------------------------------------------------------------- 1 | This is where language files should be placed. 2 | 3 | Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce 4 | -------------------------------------------------------------------------------- /public/resource/tinymce/license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /public/resource/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 6.8.3 (2024-02-08) 3 | */ 4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}(); -------------------------------------------------------------------------------- /public/resource/tinymce/skins/content/dark/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/content/default/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/content/tinymce-5/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/content/writer/content.min.css: -------------------------------------------------------------------------------- 1 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 2 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide-dark/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/dark/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}") 2 | //# sourceMappingURL=skin.shadowdom.js.map 3 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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} 2 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/default/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}") 2 | //# sourceMappingURL=skin.shadowdom.js.map 3 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/oxide/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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} 2 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/tinymce-5-dark/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}") 2 | //# sourceMappingURL=skin.shadowdom.js.map 3 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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} 2 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/tinymce-5/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/tinymce-5/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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}") 2 | //# sourceMappingURL=skin.shadowdom.js.map 3 | -------------------------------------------------------------------------------- /public/resource/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;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} 2 | -------------------------------------------------------------------------------- /src/api/fms/initialize.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | import { BaseResp } from '@/api/model/baseModel'; 3 | 4 | enum Api { 5 | InitializeDatabase = '/fms-api/init/database', 6 | } 7 | 8 | /** 9 | * @description: initialize the file manager database 10 | */ 11 | 12 | export const initializeFileDatabase = () => { 13 | return defHttp.get({ url: Api.InitializeDatabase }); 14 | }; 15 | -------------------------------------------------------------------------------- /src/api/fms/model/cloudFileModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: CloudFile info response 5 | */ 6 | export interface CloudFileInfo { 7 | id?: string; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | state?: boolean; 11 | name?: string; 12 | url?: string; 13 | size?: number; 14 | fileType?: number; 15 | userId?: string; 16 | providerId?: number; 17 | tagIds?: number[]; 18 | } 19 | 20 | /** 21 | * @description: CloudFile list response 22 | */ 23 | 24 | export type CloudFileListResp = BaseListResp; 25 | -------------------------------------------------------------------------------- /src/api/fms/model/cloudFileTagModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: CloudFileTag info response 5 | */ 6 | export interface CloudFileTagInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | name?: string; 12 | remark?: string; 13 | } 14 | 15 | /** 16 | * @description: CloudFileTag list response 17 | */ 18 | 19 | export type CloudFileTagListResp = BaseListResp; 20 | -------------------------------------------------------------------------------- /src/api/fms/model/fileModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '../../model/baseModel'; 2 | 3 | /** 4 | * author: Ryan Su 5 | * @description: file info response 6 | */ 7 | export interface fileInfo { 8 | id: string; 9 | createdAt?: number; 10 | name: string; 11 | fileType: string; 12 | size: number; 13 | path: string; 14 | publicPath: string; 15 | tagIds: number[]; 16 | } 17 | 18 | /** 19 | * author: Ryan Su 20 | * @description: file list response 21 | */ 22 | 23 | export type FileListResp = BaseListResp; 24 | 25 | /** 26 | * author: Ryan Su 27 | * @description: change status request 28 | */ 29 | export interface changeStatusReq { 30 | id: string; 31 | status: boolean; 32 | } 33 | 34 | /** 35 | * author: Ryan Su 36 | * @description: update file info request 37 | */ 38 | export interface updateFileInfoReq { 39 | id: string; 40 | name: string; 41 | tagIds: number[]; 42 | } 43 | -------------------------------------------------------------------------------- /src/api/fms/model/fileTagModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Tag info response 5 | */ 6 | export interface TagInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | name?: string; 12 | remark?: string; 13 | } 14 | 15 | /** 16 | * @description: Tag list response 17 | */ 18 | 19 | export type TagListResp = BaseListResp; 20 | -------------------------------------------------------------------------------- /src/api/fms/model/storageProviderModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: StorageProvider info response 5 | */ 6 | export interface StorageProviderInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | state?: boolean; 11 | name?: string; 12 | bucket?: string; 13 | endpoint?: string; 14 | secretId?: string; 15 | secretKey?: string; 16 | folder?: string; 17 | region?: string; 18 | isDefault?: boolean; 19 | useCdn?: boolean; 20 | cdnUrl?: string; 21 | } 22 | 23 | /** 24 | * @description: StorageProvider list response 25 | */ 26 | 27 | export type StorageProviderListResp = BaseListResp; 28 | -------------------------------------------------------------------------------- /src/api/mcms/messageSender.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | import { ErrorMessageMode } from '/#/axios'; 3 | import { BaseResp } from '@/api/model/baseModel'; 4 | import { SendEmailReq, SendSmsReq } from './model/messageModel'; 5 | 6 | enum Api { 7 | SendEmail = '/sys-api/email/send', 8 | SendSms = '/sys-api/sms/send', 9 | } 10 | 11 | /** 12 | * @description: Send Email 13 | */ 14 | 15 | export const sendEmail = (params: SendEmailReq, mode: ErrorMessageMode = 'notice') => { 16 | return defHttp.post( 17 | { url: Api.SendEmail, params }, 18 | { errorMessageMode: mode, successMessageMode: mode }, 19 | ); 20 | }; 21 | 22 | /** 23 | * @description: Send Sms 24 | */ 25 | 26 | export const sendSms = (params: SendSmsReq, mode: ErrorMessageMode = 'notice') => { 27 | return defHttp.post( 28 | { url: Api.SendSms, params }, 29 | { errorMessageMode: mode, successMessageMode: mode }, 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/api/mcms/model/emailLogModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: EmailLog info response 5 | */ 6 | export interface EmailLogInfo { 7 | id?: string; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | target?: string; 11 | subject?: string; 12 | content?: string; 13 | sendStatus?: number; 14 | provider?: string; 15 | } 16 | 17 | /** 18 | * @description: EmailLog list response 19 | */ 20 | 21 | export type EmailLogListResp = BaseListResp; 22 | -------------------------------------------------------------------------------- /src/api/mcms/model/emailProviderModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: EmailProvider info response 5 | */ 6 | export interface EmailProviderInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | name?: string; 11 | authType?: number; 12 | emailAddr?: string; 13 | password?: string; 14 | hostName?: string; 15 | identify?: string; 16 | secret?: string; 17 | port?: number; 18 | tls?: boolean; 19 | isDefault?: boolean; 20 | } 21 | 22 | /** 23 | * @description: EmailProvider list response 24 | */ 25 | 26 | export type EmailProviderListResp = BaseListResp; 27 | -------------------------------------------------------------------------------- /src/api/mcms/model/messageModel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Send email message request 3 | */ 4 | export interface SendEmailReq { 5 | target: string; 6 | subject: string; 7 | content: string; 8 | provider?: string; 9 | } 10 | 11 | /** 12 | * @description: Send sms message request 13 | */ 14 | export interface SendSmsReq { 15 | phoneNumber: string; 16 | params: string; 17 | templateId?: string; 18 | appId?: string; 19 | signName?: string; 20 | provider?: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/api/mcms/model/smsLogModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: SmsLog info response 5 | */ 6 | export interface SmsLogInfo { 7 | id?: string; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | phoneNumber?: string; 11 | content?: string; 12 | sendStatus?: number; 13 | provider?: string; 14 | } 15 | 16 | /** 17 | * @description: SmsLog list response 18 | */ 19 | 20 | export type SmsLogListResp = BaseListResp; 21 | -------------------------------------------------------------------------------- /src/api/mcms/model/smsProviderModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: SmsProvider info response 5 | */ 6 | export interface SmsProviderInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | name?: string; 11 | secretId?: string; 12 | secretKey?: string; 13 | region?: string; 14 | isDefault?: boolean; 15 | } 16 | 17 | /** 18 | * @description: SmsProvider list response 19 | */ 20 | 21 | export type SmsProviderListResp = BaseListResp; 22 | -------------------------------------------------------------------------------- /src/api/member/initialize.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | import { BaseResp } from '@/api/model/baseModel'; 3 | 4 | enum Api { 5 | InitializeMMSDatabase = '/mms-api/init/database', 6 | } 7 | 8 | /** 9 | * @description: initialize the member management service database 10 | */ 11 | 12 | export const initializeMMSDatabase = () => { 13 | return defHttp.get({ url: Api.InitializeMMSDatabase }); 14 | }; 15 | -------------------------------------------------------------------------------- /src/api/member/model/memberModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Member info response 5 | */ 6 | export interface MemberInfo { 7 | id?: string; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | username?: string; 12 | password?: string; 13 | nickname?: string; 14 | rankId?: number; 15 | mobile?: string; 16 | email?: string; 17 | avatar?: string; 18 | expiredAt?: number; 19 | } 20 | 21 | /** 22 | * @description: Member list response 23 | */ 24 | 25 | export type MemberListResp = BaseListResp; 26 | -------------------------------------------------------------------------------- /src/api/member/model/memberRankModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: MemberRank info response 5 | */ 6 | export interface MemberRankInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | name?: string; 11 | description?: string; 12 | remark?: string; 13 | } 14 | 15 | /** 16 | * @description: MemberRank list response 17 | */ 18 | 19 | export type MemberRankListResp = BaseListResp; 20 | -------------------------------------------------------------------------------- /src/api/model/baseModel.ts: -------------------------------------------------------------------------------- 1 | export interface BaseListReq { 2 | page: number; 3 | pageSize: number; 4 | } 5 | 6 | export interface BaseListResp { 7 | data: T[]; 8 | total: number; 9 | } 10 | 11 | export interface BaseDataResp { 12 | code: number; 13 | msg: string; 14 | data: T; 15 | } 16 | 17 | export interface BaseResp { 18 | code?: number; 19 | msg: string; 20 | } 21 | 22 | export interface BaseIDReq { 23 | id?: number; 24 | } 25 | 26 | export interface BaseIDsReq { 27 | ids: number[]; 28 | } 29 | 30 | export interface BaseUUIDReq { 31 | id: string; 32 | } 33 | 34 | export interface BaseUUIDsReq { 35 | ids: string[]; 36 | } 37 | -------------------------------------------------------------------------------- /src/api/sys/initialize.ts: -------------------------------------------------------------------------------- 1 | import { defHttp } from '@/utils/http/axios'; 2 | import { BaseResp } from '@/api/model/baseModel'; 3 | 4 | enum Api { 5 | InitializeDatabase = '/sys-api/core/init/database', 6 | InitializeJobDatabase = '/sys-api/core/init/job_database', 7 | InitializeMcmsDatabase = '/sys-api/core/init/mcms_database', 8 | } 9 | 10 | /** 11 | * @description: initialize the core database 12 | */ 13 | 14 | export const initialzeCoreDatabase = () => { 15 | return defHttp.get({ url: Api.InitializeDatabase }); 16 | }; 17 | 18 | /** 19 | * @description: initialize the job management service database 20 | */ 21 | 22 | export const initializeJobDatabase = () => { 23 | return defHttp.get({ url: Api.InitializeJobDatabase }); 24 | }; 25 | 26 | /** 27 | * @description: initialize the message center management service database 28 | */ 29 | 30 | export const initializeMcmsDatabase = () => { 31 | return defHttp.get({ url: Api.InitializeMcmsDatabase }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/api/sys/model/apiModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Api info response 5 | */ 6 | export interface ApiInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | trans?: string; 11 | path: string; 12 | description?: string; 13 | group: string; 14 | method: string; 15 | isRequired: boolean; 16 | serviceName: string; 17 | } 18 | 19 | /** 20 | * @description: Api list response 21 | */ 22 | 23 | export type ApiListResp = BaseListResp; 24 | -------------------------------------------------------------------------------- /src/api/sys/model/authorityModel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * author: Ryan Su 3 | * @description: 4 | */ 5 | 6 | import { BaseListResp } from '../../model/baseModel'; 7 | 8 | export interface MenuAuthorityInfo { 9 | roleId: number; 10 | menuIds: number[]; 11 | } 12 | 13 | /** 14 | * author: Ryan Su 15 | * @description: this interface is used to get the api list for authorization 16 | */ 17 | 18 | export interface ApiListReq { 19 | page: number; 20 | pageSize: number; 21 | path?: string; 22 | group?: string; 23 | description?: string; 24 | method?: string; 25 | } 26 | 27 | /** 28 | * author: Ryan Su 29 | * @description: 30 | */ 31 | 32 | export interface ApiAuthorityReq { 33 | roleId: number; 34 | data: ApiAuthorityInfo[]; 35 | } 36 | 37 | export interface ApiAuthorityInfo { 38 | path: string; 39 | method: string; 40 | } 41 | 42 | export type ApiAuthorityResp = BaseListResp; 43 | -------------------------------------------------------------------------------- /src/api/sys/model/captcha.ts: -------------------------------------------------------------------------------- 1 | export interface CaptchaResp { 2 | captchaId: string; 3 | imgPath: string; 4 | } 5 | 6 | export interface GetEmailCaptchaReq { 7 | email: string; 8 | } 9 | 10 | export interface GetSmsCaptchaReq { 11 | phoneNumber: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/api/sys/model/configurationModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Configuration info response 5 | */ 6 | export interface ConfigurationInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | sort?: number; 11 | state?: boolean; 12 | name?: string; 13 | key?: string; 14 | value?: string; 15 | category?: string; 16 | remark?: string; 17 | } 18 | 19 | /** 20 | * @description: Configuration list response 21 | */ 22 | 23 | export type ConfigurationListResp = BaseListResp; 24 | 25 | export interface ConfigurationListReq { 26 | page: number; 27 | pageSize: number; 28 | category?: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/api/sys/model/departmentModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Department info response 5 | */ 6 | export interface DepartmentInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | trans?: string; 11 | status?: number; 12 | sort?: number; 13 | name?: string; 14 | ancestors?: string; 15 | leader?: string; 16 | phone?: string; 17 | email?: string; 18 | remark?: string; 19 | parentId?: number; 20 | } 21 | 22 | /** 23 | * @description: Department list response 24 | */ 25 | 26 | export type DepartmentListResp = BaseListResp; 27 | -------------------------------------------------------------------------------- /src/api/sys/model/dictionaryDetailModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: DictionaryDetail info response 5 | */ 6 | export interface DictionaryDetailInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | title?: string; 12 | key?: string; 13 | value?: string; 14 | } 15 | 16 | /** 17 | * @description: DictionaryDetail list response 18 | */ 19 | 20 | export type DictionaryDetailListResp = BaseListResp; 21 | 22 | /** 23 | * @description: Dictionary name request 24 | */ 25 | export interface DictionaryNameReq { 26 | name: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/api/sys/model/dictionaryModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Dictionary info response 5 | */ 6 | export interface DictionaryInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | title?: string; 11 | name?: string; 12 | status?: number; 13 | desc?: string; 14 | } 15 | 16 | /** 17 | * @description: Dictionary list response 18 | */ 19 | 20 | export type DictionaryListResp = BaseListResp; 21 | -------------------------------------------------------------------------------- /src/api/sys/model/oauthProviderModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseDataResp, BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: OauthProvider info response 5 | */ 6 | export interface OauthProviderInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | name?: string; 11 | clientId?: string; 12 | clientSecret?: string; 13 | redirectUrl?: string; 14 | scopes?: string; 15 | authUrl?: string; 16 | tokenUrl?: string; 17 | authStyle?: number; 18 | infoUrl?: string; 19 | } 20 | 21 | /** 22 | * @description: OauthProvider list response 23 | */ 24 | 25 | export type OauthProviderListResp = BaseListResp; 26 | 27 | /** 28 | * author: Ryan Su 29 | * @description: Oauth log in request parameters 30 | */ 31 | export interface OauthLoginReq { 32 | state: string; 33 | provider: string; 34 | } 35 | 36 | /** 37 | * author: Ryan Su 38 | * @description: redirect response 39 | */ 40 | export type RedirectResp = BaseDataResp; 41 | 42 | /** 43 | * author: Ryan Su 44 | * @description: redirect information 45 | */ 46 | export interface RedirectInfo { 47 | URL: string; 48 | } 49 | -------------------------------------------------------------------------------- /src/api/sys/model/positionModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Position info response 5 | */ 6 | export interface PositionInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | sort?: number; 12 | name?: string; 13 | code?: string; 14 | remark?: string; 15 | } 16 | 17 | /** 18 | * @description: Position list response 19 | */ 20 | 21 | export type PositionListResp = BaseListResp; 22 | -------------------------------------------------------------------------------- /src/api/sys/model/roleModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Role info response 5 | */ 6 | export interface RoleInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | name?: string; 12 | code?: string; 13 | defaultRouter?: string; 14 | remark?: string; 15 | sort?: number; 16 | } 17 | 18 | /** 19 | * @description: Role list response 20 | */ 21 | 22 | export type RoleListResp = BaseListResp; 23 | -------------------------------------------------------------------------------- /src/api/sys/model/taskLogModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: TaskLog info response 5 | */ 6 | export interface TaskLogInfo { 7 | id?: number; 8 | startedAt?: number; 9 | finishedAt?: number; 10 | result?: number; 11 | } 12 | 13 | /** 14 | * @description: TaskLog list response 15 | */ 16 | 17 | export type TaskLogListResp = BaseListResp; 18 | -------------------------------------------------------------------------------- /src/api/sys/model/taskModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Task info response 5 | */ 6 | export interface TaskInfo { 7 | id?: number; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | name?: string; 12 | taskGroup?: string; 13 | cronExpression?: string; 14 | pattern?: string; 15 | payload?: string; 16 | } 17 | 18 | /** 19 | * @description: Task list response 20 | */ 21 | 22 | export type TaskListResp = BaseListResp; 23 | -------------------------------------------------------------------------------- /src/api/sys/model/tokenModel.ts: -------------------------------------------------------------------------------- 1 | import { BaseListResp } from '@/api/model/baseModel'; 2 | 3 | /** 4 | * @description: Token info response 5 | */ 6 | export interface TokenInfo { 7 | id?: string; 8 | createdAt?: number; 9 | updatedAt?: number; 10 | status?: number; 11 | uuid?: string; 12 | token?: string; 13 | source?: string; 14 | expiredAt?: number; 15 | username?: string; 16 | } 17 | 18 | /** 19 | * @description: Token list response 20 | */ 21 | 22 | export type TokenListResp = BaseListResp; 23 | -------------------------------------------------------------------------------- /src/api/sys/model/uploadModel.ts: -------------------------------------------------------------------------------- 1 | export interface UploadApiResp { 2 | msg: string; 3 | code: number; 4 | data: UploadInfo; 5 | } 6 | 7 | export interface UploadInfo { 8 | name: string; 9 | url: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/sys/upload.ts: -------------------------------------------------------------------------------- 1 | import { UploadApiResp } from '@/api/sys/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 | -------------------------------------------------------------------------------- /src/assets/images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/a2fe82117b6980f75c7c30569dec2200a87e5331/src/assets/images/header.jpg -------------------------------------------------------------------------------- /src/assets/images/login-page-bg-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/a2fe82117b6980f75c7c30569dec2200a87e5331/src/assets/images/login-page-bg-2.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suyuan32/simple-admin-backend-ui/a2fe82117b6980f75c7c30569dec2200a87e5331/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/svg/preview/resume.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/svg/preview/unscale.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Application/src/search/AppSearch.vue: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/components/Application/src/search/AppSearchKeyItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/ClickOutSide/src/ClickOutSide.vue: -------------------------------------------------------------------------------- 1 | 6 | 21 | -------------------------------------------------------------------------------- /src/components/CodeEditor/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | import codeEditor from './src/CodeEditor.vue'; 3 | import { MODE } from './src/typing'; 4 | 5 | export const CodeEditor = withInstall(codeEditor); 6 | export const LangMode = MODE; 7 | -------------------------------------------------------------------------------- /src/components/CodeEditor/src/typing.ts: -------------------------------------------------------------------------------- 1 | export enum MODE { 2 | JSON = 'json', 3 | YAML = 'yaml', 4 | } 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/ContextMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { createContextMenu, destroyContextMenu } from './src/createContextMenu'; 2 | 3 | export * from './src/typing'; 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/CountDown/src/useCountdown.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref } from 'vue'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | 4 | export function useCountdown(count: number) { 5 | const currentCount = ref(count); 6 | 7 | const isStart = ref(false); 8 | 9 | let timerId: ReturnType | null; 10 | 11 | function clear() { 12 | timerId && window.clearInterval(timerId); 13 | } 14 | 15 | function stop() { 16 | isStart.value = false; 17 | clear(); 18 | timerId = null; 19 | } 20 | 21 | function start() { 22 | if (unref(isStart) || !!timerId) { 23 | return; 24 | } 25 | isStart.value = true; 26 | timerId = setInterval(() => { 27 | if (unref(currentCount) === 1) { 28 | stop(); 29 | currentCount.value = count; 30 | } else { 31 | currentCount.value -= 1; 32 | } 33 | }, 1000); 34 | } 35 | 36 | function reset() { 37 | currentCount.value = count; 38 | stop(); 39 | } 40 | 41 | function restart() { 42 | reset(); 43 | start(); 44 | } 45 | 46 | tryOnUnmounted(() => { 47 | reset(); 48 | }); 49 | 50 | return { start, reset, restart, clear, stop, currentCount, isStart }; 51 | } 52 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Description/src/useDescription.ts: -------------------------------------------------------------------------------- 1 | import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing'; 2 | import { ref, getCurrentInstance, unref } from 'vue'; 3 | import { isProdMode } from '@/utils/env'; 4 | 5 | export function useDescription(props?: Partial): UseDescReturnType { 6 | if (!getCurrentInstance()) { 7 | throw new Error('useDescription() can only be used inside setup() or functional components!'); 8 | } 9 | const desc = ref>(null); 10 | const loaded = ref(false); 11 | 12 | function register(instance: DescInstance) { 13 | if (unref(loaded) && isProdMode()) { 14 | return; 15 | } 16 | desc.value = instance; 17 | props && instance.setDescProps(props); 18 | loaded.value = true; 19 | } 20 | 21 | const methods: DescInstance = { 22 | setDescProps: (descProps: Partial): void => { 23 | unref(desc)?.setDescProps(descProps); 24 | }, 25 | }; 26 | 27 | return [register, methods]; 28 | } 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Excel/src/typing.ts: -------------------------------------------------------------------------------- 1 | import type { JSON2SheetOpts, WritingOptions, BookType } from 'xlsx'; 2 | 3 | export interface ExcelData { 4 | header: string[]; 5 | results: T[]; 6 | meta: { sheetName: string }; 7 | } 8 | 9 | export interface JsonToSheet { 10 | data: T[]; 11 | header?: T; 12 | filename?: string; 13 | sheetName?: string; 14 | json2sheetOpts?: JSON2SheetOpts; 15 | write2excelOpts?: WritingOptions; 16 | } 17 | 18 | export interface AoAToSheet { 19 | data: T[][]; 20 | header?: T[]; 21 | filename?: string; 22 | sheetName?: string; 23 | write2excelOpts?: WritingOptions; 24 | } 25 | 26 | export interface ExportModalResult { 27 | filename: string; 28 | bookType: BookType; 29 | } 30 | 31 | export interface JsonToMultipleSheet { 32 | sheetList: JsonToSheet[]; 33 | filename?: string; 34 | write2excelOpts?: WritingOptions; 35 | } 36 | 37 | export interface AoaToMultipleSheet { 38 | sheetList: AoAToSheet[]; 39 | filename?: string; 40 | write2excelOpts?: WritingOptions; 41 | } 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | import BasicForm from './src/BasicForm.vue'; 2 | 3 | export * from './src/types/form'; 4 | export * from './src/types/formItem'; 5 | 6 | export { useComponentRegister } from './src/hooks/useComponentRegister'; 7 | export { useForm } from './src/hooks/useForm'; 8 | 9 | export { default as ApiSelect } from './src/components/ApiSelect.vue'; 10 | export { default as ApiMultipleSelect } from './src/components/ApiMultipleSelect.vue'; 11 | export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; 12 | export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; 13 | export { default as ApiTree } from './src/components/ApiTree.vue'; 14 | export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; 15 | export { default as ApiCascader } from './src/components/ApiCascader.vue'; 16 | export { default as ApiTransfer } from './src/components/ApiTransfer.vue'; 17 | export { default as DictionarySelect } from './src/components/DictionarySelect.vue'; 18 | 19 | export { BasicForm }; 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | import SvgIcon from './src/SvgIcon.vue'; 2 | import IconPicker from './src/IconPicker.vue'; 3 | 4 | export { IconPicker, SvgIcon }; 5 | -------------------------------------------------------------------------------- /src/components/JsonPreview/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | import jsonPreview from './src/JsonPreview.vue'; 3 | 4 | export const JsonPreview = withInstall(jsonPreview); 5 | 6 | export * from './src/typing'; 7 | -------------------------------------------------------------------------------- /src/components/JsonPreview/src/JsonPreview.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/components/JsonPreview/src/typing.ts: -------------------------------------------------------------------------------- 1 | export enum MODE { 2 | JSON = 'application/json', 3 | HTML = 'htmlmixed', 4 | JS = 'javascript', 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Markdown/src/typing.ts: -------------------------------------------------------------------------------- 1 | import Vditor from 'vditor'; 2 | 3 | export interface MarkDownActionType { 4 | getVditor: () => Vditor; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import BasicMenu from './src/BasicMenu.vue'; 2 | 3 | export { BasicMenu }; 4 | -------------------------------------------------------------------------------- /src/components/Menu/src/components/BasicMenuItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 16 | -------------------------------------------------------------------------------- /src/components/Menu/src/components/MenuItemContent.vue: -------------------------------------------------------------------------------- 1 | 8 | 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { Modal } from 'ant-design-vue'; 2 | import { defineComponent, toRefs, unref } from 'vue'; 3 | import { basicProps } from '../props'; 4 | import { useModalDragMove } from '../hooks/useModalDrag'; 5 | import { extendSlots } from '@/utils/helper/tsxHelper'; 6 | 7 | export default defineComponent({ 8 | name: 'Modal', 9 | inheritAttrs: false, 10 | props: basicProps as any, 11 | emits: ['cancel'], 12 | setup(props, { slots, emit, attrs }) { 13 | const { open, draggable, destroyOnClose } = toRefs(props); 14 | useModalDragMove({ 15 | open, 16 | destroyOnClose, 17 | draggable, 18 | }); 19 | 20 | const onCancel = (e: Event) => { 21 | emit('cancel', e); 22 | }; 23 | 24 | return () => { 25 | const propsData = { ...unref(attrs), ...props, onCancel } as Recordable; 26 | return {extendSlots(slots)}; 27 | }; 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalFooter.vue: -------------------------------------------------------------------------------- 1 | 20 | 34 | -------------------------------------------------------------------------------- /src/components/Modal/src/components/ModalHeader.vue: -------------------------------------------------------------------------------- 1 | 6 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Modal/src/hooks/useModalFullScreen.ts: -------------------------------------------------------------------------------- 1 | import { computed, Ref, ref, unref } from 'vue'; 2 | 3 | export interface UseFullScreenContext { 4 | wrapClassName: Ref; 5 | modalWrapperRef: Ref; 6 | extHeightRef: Ref; 7 | } 8 | 9 | export function useFullScreen(context: UseFullScreenContext) { 10 | const fullScreenRef = ref(false); 11 | 12 | const getWrapClassName = computed(() => { 13 | const clsName = unref(context.wrapClassName) || ''; 14 | return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName); 15 | }); 16 | 17 | function handleFullScreen(e: Event) { 18 | e && e.stopPropagation(); 19 | fullScreenRef.value = !unref(fullScreenRef); 20 | } 21 | return { getWrapClassName, handleFullScreen, fullScreenRef }; 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Preview/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImagePreview } from './src/Preview.vue'; 2 | export { createImgPreview } from './src/functional'; 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Prompt/index.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, VNode, defineComponent, h, render, reactive } from 'vue'; 2 | import { PromptProps, genFormSchemas } from './state'; 3 | import Dialog from './dialog.vue'; 4 | 5 | export function createPrompt(props: PromptProps) { 6 | let vm: Nullable = null; 7 | const data = reactive({ 8 | ...props, 9 | addFormSchemas: genFormSchemas({ 10 | label: props.label, 11 | required: props.required, 12 | inputType: props.inputType, 13 | defaultValue: props.defaultValue, 14 | }), 15 | }); 16 | const DialogWrap = defineComponent({ 17 | render() { 18 | return h(Dialog, { ...data } as any); 19 | }, 20 | }); 21 | 22 | vm = createVNode(DialogWrap); 23 | 24 | render(vm, document.createElement('div')); 25 | 26 | function close() { 27 | if (vm?.el && vm.el.parentNode) { 28 | vm.el.parentNode.removeChild(vm.el); 29 | } 30 | } 31 | 32 | return { 33 | vm, 34 | close, 35 | get $el() { 36 | return vm?.el as HTMLElement; 37 | }, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SimpleMenu } from './src/SimpleMenu.vue'; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/src/components/useSimpleMenuContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from '@/hooks/core/useContext'; 2 | import type { Emitter } from '@/utils/mitt'; 3 | import type { ComponentInternalInstance, InjectionKey, Ref } from 'vue'; 4 | 5 | export type MenuEmitterEvents = { 6 | 'on-update-opened': 7 | | (string | number)[] 8 | | { 9 | opened: boolean; 10 | parent?: ComponentInternalInstance | null; 11 | uidList: number[]; 12 | }; 13 | 'on-menu-item-select': string | number; 14 | 'open-name-change': { 15 | name: string | number; 16 | opened: boolean; 17 | }; 18 | 'on-update-active-name:submenu': number[]; 19 | }; 20 | 21 | export interface SimpleRootMenuContextProps { 22 | rootMenuEmitter: Emitter; 23 | activeName: Ref; 24 | } 25 | 26 | const key: InjectionKey = Symbol(); 27 | 28 | export function createSimpleRootMenuContext(context: SimpleRootMenuContextProps) { 29 | return createContext(context, key, { readonly: false, native: true }); 30 | } 31 | 32 | export function useSimpleRootMenuContext() { 33 | return useContext(key); 34 | } 35 | -------------------------------------------------------------------------------- /src/components/SimpleMenu/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface MenuState { 2 | activeName: string; 3 | openNames: string[]; 4 | activeSubMenuNames: string[]; 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Table/src/components/DictionaryTag.vue: -------------------------------------------------------------------------------- 1 | 4 | 32 | -------------------------------------------------------------------------------- /src/components/Table/src/components/EditTableHeaderIcon.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/CellComponent.ts: -------------------------------------------------------------------------------- 1 | import type { defineComponent } from 'vue'; 2 | import type { ComponentType } from '../../types/componentType'; 3 | import { componentMap } from '@/components/Table/src/componentMap'; 4 | 5 | import { Popover } from 'ant-design-vue'; 6 | import { h } from 'vue'; 7 | 8 | export interface ComponentProps { 9 | component: ComponentType; 10 | rule: boolean; 11 | popoverVisible: boolean; 12 | ruleMessage: string; 13 | getPopupContainer?: Fn; 14 | } 15 | 16 | export const CellComponent = ( 17 | { 18 | component = 'Input', 19 | rule = true, 20 | ruleMessage, 21 | popoverVisible, 22 | getPopupContainer, 23 | }: ComponentProps, 24 | { attrs }, 25 | ) => { 26 | const Comp = componentMap.get(component) as typeof defineComponent; 27 | 28 | const DefaultComp = h(Comp, attrs); 29 | if (!rule) { 30 | return DefaultComp; 31 | } 32 | return h( 33 | Popover, 34 | { 35 | overlayClassName: 'edit-cell-rule-popover', 36 | open: popoverVisible, 37 | ...(getPopupContainer ? { getPopupContainer } : {}), 38 | }, 39 | { 40 | default: () => DefaultComp, 41 | content: () => ruleMessage, 42 | }, 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/FullScreenSetting.vue: -------------------------------------------------------------------------------- 1 | 10 | 21 | -------------------------------------------------------------------------------- /src/components/Table/src/components/settings/RedoSetting.vue: -------------------------------------------------------------------------------- 1 | 9 | 22 | -------------------------------------------------------------------------------- /src/components/Table/src/const.ts: -------------------------------------------------------------------------------- 1 | import componentSetting from '@/settings/componentSetting'; 2 | 3 | const { table } = componentSetting; 4 | 5 | const { 6 | pageSizeOptions, 7 | defaultPageSize, 8 | fetchSetting, 9 | defaultSize, 10 | defaultSortFn, 11 | defaultFilterFn, 12 | } = table; 13 | 14 | export const ROW_KEY = 'key'; 15 | 16 | // Optional display number per page; 17 | export const PAGE_SIZE_OPTIONS = pageSizeOptions; 18 | 19 | // Number of items displayed per page 20 | export const PAGE_SIZE = defaultPageSize; 21 | 22 | // Common interface field settings 23 | export const FETCH_SETTING = fetchSetting; 24 | 25 | // Default Size 26 | export const DEFAULT_SIZE = defaultSize; 27 | 28 | // Configure general sort function 29 | export const DEFAULT_SORT_FN = defaultSortFn; 30 | 31 | export const DEFAULT_FILTER_FN = defaultFilterFn; 32 | 33 | // Default layout of table cells 34 | export const DEFAULT_ALIGN = 'center'; 35 | 36 | export const INDEX_COLUMN_FLAG = 'INDEX'; 37 | 38 | export const ACTION_COLUMN_FLAG = 'ACTION'; 39 | -------------------------------------------------------------------------------- /src/components/Table/src/helper.ts: -------------------------------------------------------------------------------- 1 | import { ROW_KEY } from './const'; 2 | import type { BasicTableProps } from './types/table'; 3 | 4 | export function parseRowKey( 5 | rowKey: BasicTableProps['rowKey'], 6 | record: RecordType, 7 | autoCreateKey?: boolean, 8 | ): number | string { 9 | if (autoCreateKey) { 10 | return ROW_KEY; 11 | } else { 12 | if (typeof rowKey === 'string') { 13 | return rowKey; 14 | } else if (rowKey) { 15 | return rowKey(record); 16 | } else { 17 | return ROW_KEY; 18 | } 19 | } 20 | } 21 | 22 | export function parseRowKeyValue( 23 | rowKey: BasicTableProps['rowKey'], 24 | record: RecordType, 25 | autoCreateKey?: boolean, 26 | ): number | string { 27 | return record[parseRowKey(rowKey, record, autoCreateKey)]; 28 | } 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 'remeda'; 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 | -------------------------------------------------------------------------------- /src/components/Table/src/types/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'Input' 3 | | 'InputNumber' 4 | | 'Select' 5 | | 'ApiSelect' 6 | | 'AutoComplete' 7 | | 'ApiTreeSelect' 8 | | 'Checkbox' 9 | | 'Switch' 10 | | 'DatePicker' 11 | | 'TimePicker' 12 | | 'RadioGroup' 13 | | 'RadioButtonGroup' 14 | | 'ApiRadioGroup'; 15 | -------------------------------------------------------------------------------- /src/components/Table/src/types/tableAction.ts: -------------------------------------------------------------------------------- 1 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; 2 | import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'; 3 | import { RoleEnum } from '@/enums/roleEnum'; 4 | 5 | export interface ActionItem extends ButtonProps { 6 | onClick?: Fn; 7 | label?: string; 8 | color?: 'success' | 'error' | 'warning'; 9 | icon?: string; 10 | popConfirm?: PopConfirm; 11 | disabled?: boolean; 12 | divider?: boolean; 13 | // 权限编码控制是否显示 14 | auth?: RoleEnum | RoleEnum[] | string | string[]; 15 | // 业务控制是否显示 16 | ifShow?: boolean | ((action: ActionItem) => boolean); 17 | tooltip?: string | TooltipProps; 18 | } 19 | 20 | export interface PopConfirm { 21 | title: string; 22 | okText?: string; 23 | cancelText?: string; 24 | confirm: Fn; 25 | cancel?: Fn; 26 | icon?: string; 27 | placement?: 28 | | 'top' 29 | | 'left' 30 | | 'right' 31 | | 'bottom' 32 | | 'topLeft' 33 | | 'topRight' 34 | | 'leftTop' 35 | | 'leftBottom' 36 | | 'rightTop' 37 | | 'rightBottom' 38 | | 'bottomLeft' 39 | | 'bottomRight'; 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Tinymce/src/tinymce.ts: -------------------------------------------------------------------------------- 1 | export const plugins = [ 2 | 'advlist', 3 | 'anchor', 4 | 'autolink', 5 | 'autosave', 6 | 'autoresize', 7 | 'code', 8 | 'codesample', 9 | 'directionality', 10 | 'fullscreen', 11 | 'insertdatetime', 12 | 'link', 13 | 'lists', 14 | 'media', 15 | 'nonbreaking', 16 | 'pagebreak', 17 | 'preview', 18 | 'save', 19 | 'searchreplace', 20 | 'visualblocks', 21 | 'visualchars', 22 | 'wordcount', 23 | 'image', 24 | ]; 25 | 26 | export const toolbar = [ 27 | 'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 28 | 'hr bullist numlist link preview anchor pagebreak insertdatetime media image forecolor backcolor fullscreen', 29 | ]; 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Tree/src/TreeIcon.ts: -------------------------------------------------------------------------------- 1 | import type { VNode } from 'vue'; 2 | import { h } from 'vue'; 3 | import Icon from '@/components/Icon/Icon.vue'; 4 | import { isString } from 'remeda'; 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 | -------------------------------------------------------------------------------- /src/components/Tree/style/index.less: -------------------------------------------------------------------------------- 1 | @tree-prefix-cls: ~'@{namespace}-tree'; 2 | 3 | .@{tree-prefix-cls} { 4 | background-color: @component-background; 5 | 6 | .ant-tree-node-content-wrapper { 7 | position: relative; 8 | 9 | .ant-tree-title { 10 | position: absolute; 11 | left: 0; 12 | width: 100%; 13 | overflow: hidden; 14 | text-overflow: ellipsis; 15 | white-space: nowrap; 16 | } 17 | } 18 | 19 | &__title { 20 | display: flex; 21 | position: relative; 22 | align-items: center; 23 | width: 100%; 24 | padding-right: 10px; 25 | 26 | &:hover { 27 | .@{tree-prefix-cls}__action { 28 | visibility: visible; 29 | } 30 | } 31 | } 32 | 33 | &__content { 34 | overflow: hidden; 35 | } 36 | 37 | &__actions { 38 | display: flex; 39 | position: absolute; 40 | //top: 2px; 41 | right: 3px; 42 | } 43 | 44 | &__action { 45 | visibility: hidden; 46 | margin-left: 4px; 47 | } 48 | 49 | &-header { 50 | border-bottom: 1px solid @border-color-base; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Tree/style/index.ts: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | -------------------------------------------------------------------------------- /src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | import basicUpload from './src/BasicUpload.vue'; 3 | import uploadImage from './src/components/ImageUpload.vue'; 4 | 5 | export const ImageUpload = withInstall(uploadImage); 6 | export const BasicUpload = withInstall(basicUpload); 7 | -------------------------------------------------------------------------------- /src/components/Upload/src/components/ThumbUrl.vue: -------------------------------------------------------------------------------- 1 | 6 | 19 | 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/registerGlobComp.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { Button } from './Button'; 3 | import { Input, Layout } from 'ant-design-vue'; 4 | 5 | export function registerGlobComp(app: App) { 6 | app.use(Input).use(Button).use(Layout); 7 | } 8 | -------------------------------------------------------------------------------- /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%; 8 | max-width: 100%; 9 | } 10 | } 11 | 12 | .ant-input-affix-wrapper .ant-input-suffix { 13 | right: 9px; 14 | } 15 | 16 | .ant-input-clear-icon { 17 | margin-right: 5px; 18 | } 19 | 20 | .ant-input-affix-wrapper-textarea-with-clear-btn { 21 | padding: 0 !important; 22 | 23 | textarea.ant-input { 24 | padding: 4px; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/design/ant/popconfirm.less: -------------------------------------------------------------------------------- 1 | // 修复气泡确认框内的按钮在内容宽度不够换行的情况 2 | // 初始问题发现在 2.10.1 版本 固定列页面 http://ip:port/#/comp/table/fixedColumn 3 | .ant-popconfirm { 4 | &-buttons { 5 | white-space: nowrap; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/design/config.less: -------------------------------------------------------------------------------- 1 | @import (reference) 'color.less'; 2 | @import (reference) 'var/index.less'; 3 | -------------------------------------------------------------------------------- /src/design/index.less: -------------------------------------------------------------------------------- 1 | @import 'transition/index.less'; 2 | @import 'var/index.less'; 3 | @import 'public.less'; 4 | @import 'ant/index.less'; 5 | @import './theme.less'; 6 | @import './entry.css'; 7 | 8 | input:-webkit-autofill { 9 | box-shadow: 0 0 0 1000px transparent inset; 10 | -webkit-text-fill-color: @text-color-base; 11 | } 12 | 13 | :-webkit-autofill { 14 | transition: background-color 5000s ease-in-out 0s !important; 15 | } 16 | 17 | html { 18 | overflow: hidden; 19 | text-size-adjust: 100%; 20 | } 21 | 22 | html, 23 | body { 24 | position: relative; 25 | width: 100%; 26 | height: 100%; 27 | overflow: visible; 28 | overflow-x: hidden; 29 | 30 | &.color-weak { 31 | filter: invert(80%); 32 | } 33 | 34 | &.gray-mode { 35 | filter: progid:dximagetransform.microsoft.basicimage(grayscale=1); 36 | } 37 | } 38 | 39 | a:focus, 40 | a:active, 41 | button, 42 | div, 43 | svg, 44 | span { 45 | outline: none; 46 | } 47 | 48 | // 保持 和 windi 一样的全局样式,减少升级带来的影响 49 | ul { 50 | margin: 0; 51 | padding: 0; 52 | list-style: none; 53 | } 54 | -------------------------------------------------------------------------------- /src/design/public.less: -------------------------------------------------------------------------------- 1 | #app { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | // ================================= 7 | // ==============scrollbar========== 8 | // ================================= 9 | 10 | ::-webkit-scrollbar { 11 | width: 7px; 12 | height: 8px; 13 | } 14 | 15 | // ::-webkit-scrollbar-track { 16 | // background: transparent; 17 | // } 18 | 19 | ::-webkit-scrollbar-track { 20 | background-color: rgb(0 0 0 / 5%); 21 | } 22 | 23 | ::-webkit-scrollbar-thumb { 24 | // background-color: rgba(144, 147, 153, 0.3); 25 | border-radius: 6px; 26 | // background: rgba(0, 0, 0, 0.6); 27 | background-color: rgb(144 147 153 / 30%); 28 | box-shadow: inset 0 0 6px rgb(0 0 0 / 20%); 29 | } 30 | 31 | ::-webkit-scrollbar-thumb:hover { 32 | background-color: @border-color-dark; 33 | } 34 | 35 | // ================================= 36 | // ==============nprogress========== 37 | // ================================= 38 | #nprogress { 39 | pointer-events: none; 40 | 41 | .bar { 42 | position: fixed; 43 | z-index: 99999; 44 | top: 0; 45 | left: 0; 46 | width: 100%; 47 | height: 2px; 48 | opacity: 0.75; 49 | background-color: @primary-color; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: 10 | 0.2s height ease-in-out, 11 | 0.2s padding-top ease-in-out, 12 | 0.2s padding-bottom ease-in-out; 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/design/transition/scroll.less: -------------------------------------------------------------------------------- 1 | .scroll-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | } 8 | 9 | &-enter-from { 10 | transform: translateY(-15px); 11 | } 12 | 13 | &-leave-to { 14 | transform: translateY(15px); 15 | } 16 | } 17 | 18 | .scroll-y-reverse-transition { 19 | .transition-default(); 20 | 21 | &-enter-from, 22 | &-leave-to { 23 | opacity: 0; 24 | } 25 | 26 | &-enter-from { 27 | transform: translateY(15px); 28 | } 29 | 30 | &-leave-to { 31 | transform: translateY(-15px); 32 | } 33 | } 34 | 35 | .scroll-x-transition { 36 | .transition-default(); 37 | 38 | &-enter-from, 39 | &-leave-to { 40 | opacity: 0; 41 | } 42 | 43 | &-enter-from { 44 | transform: translateX(-15px); 45 | } 46 | 47 | &-leave-to { 48 | transform: translateX(15px); 49 | } 50 | } 51 | 52 | .scroll-x-reverse-transition { 53 | .transition-default(); 54 | 55 | &-enter-from, 56 | &-leave-to { 57 | opacity: 0; 58 | } 59 | 60 | &-enter-from { 61 | transform: translateX(15px); 62 | } 63 | 64 | &-leave-to { 65 | transform: translateX(-15px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/design/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: 5 | opacity 0.1 ease-in-out, 6 | transform 0.15s ease-out; 7 | } 8 | 9 | .zoom-out-enter-from, 10 | .zoom-out-leave-to { 11 | transform: scale(0); 12 | opacity: 0; 13 | } 14 | 15 | // zoom-fade 16 | .zoom-fade-enter-active, 17 | .zoom-fade-leave-active { 18 | transition: 19 | transform 0.2s, 20 | opacity 0.3s ease-out; 21 | } 22 | 23 | .zoom-fade-enter-from { 24 | transform: scale(0.92); 25 | opacity: 0; 26 | } 27 | 28 | .zoom-fade-leave-to { 29 | transform: scale(1.06); 30 | opacity: 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/design/var/breakpoint.less: -------------------------------------------------------------------------------- 1 | // ================================= 2 | // ==============屏幕断点============ 3 | // ================================= 4 | 5 | // Extra small screen / phone 6 | @screen-xs: 320px; 7 | @screen-xs-min: @screen-xs; 8 | 9 | // Small screen / tablet 10 | @screen-sm: 640px; 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: 960px; 19 | @screen-lg-min: @screen-lg; 20 | 21 | // Extra large screen / full hd 22 | @screen-xl: 1280px; 23 | @screen-xl-min: @screen-xl; 24 | 25 | // Extra extra large screen / large desktop 26 | @screen-2xl: 1536px; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/directives/ellipsis.ts: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, DirectiveBinding, ObjectDirective, App } from 'vue'; 2 | 3 | interface IValue { 4 | width?: number; 5 | line?: number; 6 | } 7 | 8 | interface IOptions { 9 | [key: string]: CSSProperties; 10 | } 11 | 12 | const cssProperties: IOptions = { 13 | single: { 14 | overflow: 'hidden', 15 | textOverflow: 'ellipsis', 16 | whiteSpace: 'nowrap', 17 | }, 18 | multiple: { 19 | display: '-webkit-box', 20 | overflow: 'hidden', 21 | wordBreak: 'break-all', 22 | }, 23 | }; 24 | 25 | const Ellipsis: ObjectDirective = { 26 | mounted(el: HTMLElement, binding: DirectiveBinding>) { 27 | const { value = [100, 1], arg = 'single' } = binding; 28 | const [width, line] = value; 29 | Object.entries(cssProperties[arg]).forEach(([key, value]) => { 30 | el.style[key] = value; 31 | }); 32 | el.style.width = `${width}px`; 33 | if (arg === 'multiple') { 34 | el.style.webkitLineClamp = `${line}`; 35 | el.style.flexDirection = 'column'; 36 | } 37 | }, 38 | }; 39 | export function setupEllipsisDirective(app: App) { 40 | app.directive('ellipsis', Ellipsis); 41 | } 42 | export default Ellipsis; 43 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/directives/repeatClick.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prevent repeated clicks 3 | * @Example v-repeat-click="()=>{}" 4 | */ 5 | import { on, once } from '@/utils/domUtils'; 6 | import type { Directive, DirectiveBinding } from 'vue'; 7 | 8 | const repeatDirective: Directive = { 9 | beforeMount(el: Element, binding: DirectiveBinding) { 10 | let interval: Nullable = null; 11 | let startTime = 0; 12 | const handler = (): void => binding?.value(); 13 | const clear = (): void => { 14 | if (Date.now() - startTime < 100) { 15 | handler(); 16 | } 17 | interval && clearInterval(interval); 18 | interval = null; 19 | }; 20 | 21 | on(el, 'mousedown', (e: Event): void => { 22 | if ((e as MouseEvent).button !== 0) return; 23 | startTime = Date.now(); 24 | once(document as any, 'mouseup', clear); 25 | interval && clearInterval(interval); 26 | interval = setInterval(handler, 100); 27 | }); 28 | }, 29 | }; 30 | 31 | export default repeatDirective; 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/enums/appEnum.ts: -------------------------------------------------------------------------------- 1 | export const SIDE_BAR_MINI_WIDTH = 48; 2 | export const SIDE_BAR_SHOW_TIT_MINI_WIDTH = 80; 3 | 4 | export enum ContentEnum { 5 | // auto width 6 | FULL = 'full', 7 | // fixed width 8 | FIXED = 'fixed', 9 | } 10 | 11 | // menu theme enum 12 | export enum ThemeEnum { 13 | DARK = 'dark', 14 | LIGHT = 'light', 15 | } 16 | 17 | export enum SettingButtonPositionEnum { 18 | AUTO = 'auto', 19 | HEADER = 'header', 20 | FIXED = 'fixed', 21 | } 22 | 23 | export enum SessionTimeoutProcessingEnum { 24 | ROUTE_JUMP, 25 | PAGE_COVERAGE, 26 | } 27 | 28 | /** 29 | * 权限模式 30 | */ 31 | export enum PermissionModeEnum { 32 | // role 33 | // 角色权限 34 | ROLE = 'ROLE', 35 | // black 36 | // 后端 37 | BACK = 'BACK', 38 | // route mapping 39 | // 路由映射 40 | ROUTE_MAPPING = 'ROUTE_MAPPING', 41 | } 42 | 43 | // Route switching animation 44 | // 路由切换动画 45 | export enum RouterTransitionEnum { 46 | ZOOM_FADE = 'zoom-fade', 47 | ZOOM_OUT = 'zoom-out', 48 | FADE_SIDE = 'fade-slide', 49 | FADE = 'fade', 50 | FADE_BOTTOM = 'fade-bottom', 51 | FADE_SCALE = 'fade-scale', 52 | } 53 | 54 | export enum ParentIdEnum { 55 | DEFAULT = 1000000, 56 | } 57 | -------------------------------------------------------------------------------- /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 = 320, 12 | SM = 640, 13 | MD = 768, 14 | LG = 960, 15 | XL = 1280, 16 | XXL = 1536, 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 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | export const LOCALE_KEY = 'LOCALE__'; 2 | 3 | // user info key 4 | export const USER_INFO_KEY = 'USER__INFO__'; 5 | 6 | // dictionary info key 7 | export const DICT_INFO_KEY = 'DICT__INFO__'; 8 | 9 | // role info key 10 | export const ROLES_KEY = 'ROLES__KEY__'; 11 | 12 | // role name key 13 | export const ROLES_NAME_KEY = 'ROLES__NAME__KEY__'; 14 | 15 | // project config key 16 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 17 | export const API_ADDRESS = 'API_ADDRESS__'; 18 | 19 | // lock info 20 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 21 | 22 | export const MULTIPLE_TABS_KEY = 'MULTIPLE_TABS__KEY__'; 23 | 24 | export const APP_DARK_MODE_KEY = '__APP__DARK__MODE__'; 25 | 26 | // base global local key 27 | export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__'; 28 | 29 | // base global session key 30 | export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__'; 31 | 32 | export enum CacheTypeEnum { 33 | SESSION, 34 | LOCAL, 35 | } 36 | -------------------------------------------------------------------------------- /src/enums/commonEnum.ts: -------------------------------------------------------------------------------- 1 | export const ZERO_UUID = '00000000-0000-0000-0000-000000000000'; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 0, 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // initialize database page 7 | BASE_INITIAL_PAGE = '/init', 8 | // error page path 9 | ERROR_PAGE = '/exception', 10 | // error log page path 11 | ERROR_LOG_PAGE = '/error-log/list', 12 | // oauth callback page 13 | OAUTH_CALLBACK = '/oauth/login/callback', 14 | } 15 | 16 | export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight'; 17 | -------------------------------------------------------------------------------- /src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // super admin 3 | SUPER = '001', 4 | 5 | // tester 6 | STUFF = '002', 7 | } 8 | -------------------------------------------------------------------------------- /src/enums/sizeEnum.ts: -------------------------------------------------------------------------------- 1 | export enum SizeEnum { 2 | DEFAULT = 'default', 3 | SMALL = 'small', 4 | LARGE = 'large', 5 | } 6 | 7 | export enum SizeNumberEnum { 8 | DEFAULT = 48, 9 | SMALL = 16, 10 | LARGE = 64, 11 | } 12 | 13 | export const sizeMap: Map = (() => { 14 | const map = new Map(); 15 | map.set(SizeEnum.DEFAULT, SizeNumberEnum.DEFAULT); 16 | map.set(SizeEnum.SMALL, SizeNumberEnum.SMALL); 17 | map.set(SizeEnum.LARGE, SizeNumberEnum.LARGE); 18 | return map; 19 | })(); 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/hooks/core/useContext.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InjectionKey, 3 | provide, 4 | inject, 5 | reactive, 6 | readonly as defineReadonly, 7 | UnwrapRef, 8 | } from 'vue'; 9 | 10 | export interface CreateContextOptions { 11 | readonly?: boolean; 12 | createProvider?: boolean; 13 | native?: boolean; 14 | } 15 | 16 | type ShallowUnwrap = { 17 | [P in keyof T]: UnwrapRef; 18 | }; 19 | 20 | export function createContext( 21 | context: any, 22 | key: InjectionKey = Symbol(), 23 | options: CreateContextOptions = {}, 24 | ) { 25 | const { readonly = true, createProvider = true, native = false } = options; 26 | 27 | const state = reactive(context); 28 | const provideData = readonly ? defineReadonly(state) : state; 29 | createProvider && provide(key, native ? context : provideData); 30 | 31 | return { 32 | state, 33 | }; 34 | } 35 | 36 | export function useContext(key: InjectionKey, native?: boolean): T; 37 | 38 | export function useContext( 39 | key: InjectionKey = Symbol(), 40 | defaultValue?: any, 41 | ): ShallowUnwrap { 42 | return inject(key, defaultValue || {}); 43 | } 44 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | const getAutoCollapse = computed(() => appStore.getMultiTabsSetting.autoCollapse); 19 | 20 | function setMultipleTabSetting(multiTabsSetting: Partial) { 21 | appStore.setProjectConfig({ multiTabsSetting }); 22 | } 23 | return { 24 | setMultipleTabSetting, 25 | getShowMultipleTab, 26 | getShowQuick, 27 | getShowRedo, 28 | getShowFold, 29 | getAutoCollapse, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/hooks/setting/useTransitionSetting.ts: -------------------------------------------------------------------------------- 1 | import type { TransitionSetting } from '/#/config'; 2 | 3 | import { computed } from 'vue'; 4 | 5 | import { useAppStore } from '@/store/modules/app'; 6 | 7 | export function useTransitionSetting() { 8 | const appStore = useAppStore(); 9 | 10 | const getEnableTransition = computed(() => appStore.getTransitionSetting?.enable); 11 | 12 | const getOpenNProgress = computed(() => appStore.getTransitionSetting?.openNProgress); 13 | 14 | const getOpenPageLoading = computed((): boolean => { 15 | return !!appStore.getTransitionSetting?.openPageLoading; 16 | }); 17 | 18 | const getBasicTransition = computed(() => appStore.getTransitionSetting?.basicTransition); 19 | 20 | function setTransitionSetting(transitionSetting: Partial) { 21 | appStore.setProjectConfig({ transitionSetting }); 22 | } 23 | return { 24 | setTransitionSetting, 25 | 26 | getEnableTransition, 27 | getOpenNProgress, 28 | getOpenPageLoading, 29 | getBasicTransition, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/hooks/web/useDesign.ts: -------------------------------------------------------------------------------- 1 | import { useAppProviderContext } from '@/components/Application'; 2 | 3 | export function useDesign(scope: string) { 4 | const values = useAppProviderContext(); 5 | return { 6 | prefixCls: `${values.prefixCls}-${scope}`, 7 | prefixVar: values.prefixCls, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/hooks/web/usePagination.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { ref, unref, computed } from 'vue'; 3 | 4 | function pagination(list: T[], pageNo: number, pageSize: number): T[] { 5 | const offset = (pageNo - 1) * Number(pageSize); 6 | const ret = 7 | offset + Number(pageSize) >= list.length 8 | ? list.slice(offset, list.length) 9 | : list.slice(offset, offset + Number(pageSize)); 10 | return ret; 11 | } 12 | 13 | export function usePagination(list: Ref, pageSize: number) { 14 | const currentPage = ref(1); 15 | const pageSizeRef = ref(pageSize); 16 | 17 | const getPaginationList = computed(() => { 18 | return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); 19 | }); 20 | 21 | const getTotal = computed(() => { 22 | return unref(list).length; 23 | }); 24 | 25 | function setCurrentPage(page: number) { 26 | currentPage.value = page; 27 | } 28 | 29 | function setPageSize(pageSize: number) { 30 | pageSizeRef.value = pageSize; 31 | } 32 | 33 | return { setCurrentPage, getTotal, setPageSize, getPaginationList }; 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/web/useScript.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, ref } from 'vue'; 2 | 3 | interface ScriptOptions { 4 | src: string; 5 | } 6 | 7 | export function useScript(opts: ScriptOptions) { 8 | const isLoading = ref(false); 9 | const error = ref(false); 10 | const success = ref(false); 11 | let script: HTMLScriptElement; 12 | 13 | const promise = new Promise((resolve, reject) => { 14 | onMounted(() => { 15 | script = document.createElement('script'); 16 | script.type = 'text/javascript'; 17 | script.onload = function () { 18 | isLoading.value = false; 19 | success.value = true; 20 | error.value = false; 21 | resolve(''); 22 | }; 23 | 24 | script.onerror = function (err) { 25 | isLoading.value = false; 26 | success.value = false; 27 | error.value = true; 28 | reject(err); 29 | }; 30 | 31 | script.src = opts.src; 32 | document.head.appendChild(script); 33 | }); 34 | }); 35 | 36 | onUnmounted(() => { 37 | script && script.remove(); 38 | }); 39 | 40 | return { 41 | isLoading, 42 | error, 43 | success, 44 | toPromise: () => promise, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /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 | el = unref(el); 9 | 10 | if (!el) return; 11 | 12 | const Sortable = (await import('sortablejs')).default; 13 | Sortable.create(el, { 14 | animation: 100, 15 | delay: 400, 16 | delayOnTouchOnly: true, 17 | ...options, 18 | }); 19 | }); 20 | } 21 | 22 | return { initSortable }; 23 | } 24 | -------------------------------------------------------------------------------- /src/hooks/web/useTitle.ts: -------------------------------------------------------------------------------- 1 | import { watch, unref } from 'vue'; 2 | import { useI18n } from '@/hooks/web/useI18n'; 3 | import { useTitle as usePageTitle } from '@vueuse/core'; 4 | import { useGlobSetting } from '@/hooks/setting'; 5 | import { useRouter } from 'vue-router'; 6 | import { useLocaleStore } from '@/store/modules/locale'; 7 | import { REDIRECT_NAME } from '@/router/constant'; 8 | 9 | /** 10 | * Listening to page changes and dynamically changing site titles 11 | */ 12 | export function useTitle() { 13 | const { title } = useGlobSetting(); 14 | const { t } = useI18n(); 15 | const { currentRoute } = useRouter(); 16 | const localeStore = useLocaleStore(); 17 | 18 | const pageTitle = usePageTitle(); 19 | 20 | watch( 21 | [() => currentRoute.value.path, () => localeStore.getLocale], 22 | () => { 23 | const route = unref(currentRoute); 24 | 25 | if (route.name === REDIRECT_NAME) { 26 | return; 27 | } 28 | 29 | const tTitle = t(route?.meta?.title as string); 30 | pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`; 31 | }, 32 | { immediate: true }, 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/ErrorAction.vue: -------------------------------------------------------------------------------- 1 | 13 | 35 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/FullScreen.vue: -------------------------------------------------------------------------------- 1 | 9 | 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/layouts/default/header/components/user-dropdown/DropMenuItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/layouts/default/setting/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 15 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/components/SettingButton.vue: -------------------------------------------------------------------------------- 1 | 7 | 18 | -------------------------------------------------------------------------------- /src/layouts/default/tabs/components/TabRedo.vue: -------------------------------------------------------------------------------- 1 | 6 | 26 | 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/HeaderTrigger.vue: -------------------------------------------------------------------------------- 1 | 6 | 18 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/SiderTrigger.vue: -------------------------------------------------------------------------------- 1 | 7 | 13 | -------------------------------------------------------------------------------- /src/layouts/default/trigger/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | -------------------------------------------------------------------------------- /src/layouts/iframe/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.glob('./en/**/*.{json,ts,js}', { eager: true }); 5 | export default { 6 | message: { 7 | ...genMessage(modules as Recordable, 'en'), 8 | antdLocale, 9 | }, 10 | dateLocale: null, 11 | dateLocaleName: 'en', 12 | }; 13 | -------------------------------------------------------------------------------- /src/locales/lang/en/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: 'Login', 3 | errorLogList: 'Error Log', 4 | }; 5 | -------------------------------------------------------------------------------- /src/locales/lang/en/routes/system.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | systemManagementTitle: 'System', 3 | menuManagementTitle: 'Menu', 4 | roleManagementTitle: 'Role', 5 | apiManagementTitle: 'API', 6 | userManagementTitle: 'User', 7 | fileManagementTitle: 'File', 8 | userProfileTitle: 'Profile', 9 | dictionaryManagementTitle: 'Dictionary', 10 | dictionaryDetailManagementTitle: 'Key/Value', 11 | oauthManagement: 'Oauth', 12 | tokenManagement: 'Token', 13 | otherPages: 'Other Pages', 14 | positionManagement: 'Position', 15 | taskManagement: 'Scheduled Task', 16 | }; 17 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/antdLocale/DatePicker.ts: -------------------------------------------------------------------------------- 1 | export default { 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 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/routes/basic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: '登录', 3 | errorLogList: '错误日志列表', 4 | }; 5 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/routes/system.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | systemManagementTitle: '系统管理', 3 | menuManagementTitle: '菜单管理', 4 | roleManagementTitle: '角色管理', 5 | apiManagementTitle: 'API管理', 6 | userManagementTitle: '用户管理', 7 | fileManagementTitle: '文件管理', 8 | userProfileTitle: '用户个人信息', 9 | dictionaryManagementTitle: '字典管理', 10 | dictionaryDetailManagementTitle: '键值管理', 11 | oauthManagement: 'Oauth管理', 12 | tokenManagement: 'Token管理', 13 | otherPages: '其他页面', 14 | positionManagement: '职位管理', 15 | taskManagement: '定时任务管理', 16 | }; 17 | -------------------------------------------------------------------------------- /src/locales/lang/zh_CN.ts: -------------------------------------------------------------------------------- 1 | import { genMessage } from '../helper'; 2 | import antdLocale from 'ant-design-vue/es/locale/zh_CN'; 3 | import { deepMerge } from '/@/utils'; 4 | 5 | const modules = import.meta.glob('./zh-CN/**/*.{json,ts,js}', { eager: true }); 6 | 7 | export default { 8 | message: { 9 | ...genMessage(modules as Recordable, 'zh-CN'), 10 | antdLocale: { 11 | ...antdLocale, 12 | DatePicker: deepMerge( 13 | antdLocale.DatePicker, 14 | genMessage(modules as Recordable, 'zh-CN').antdLocale.DatePicker, 15 | ), 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/logics/mitt/routeChange.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to monitor routing changes to change the status of menus and tabs. There is no need to monitor the route, because the route status change is affected by the page rendering time, which will be slow 3 | */ 4 | 5 | import { mitt } from '@/utils/mitt'; 6 | import type { RouteLocationNormalized } from 'vue-router'; 7 | import { getRawRoute } from '@/utils'; 8 | 9 | const key = Symbol(); 10 | 11 | const emitter = mitt<{ 12 | [key]: RouteLocationNormalized; 13 | }>(); 14 | 15 | let lastChangeTab: RouteLocationNormalized; 16 | 17 | export function setRouteChange(lastChangeRoute: RouteLocationNormalized) { 18 | const r = getRawRoute(lastChangeRoute); 19 | emitter.emit(key, r); 20 | lastChangeTab = r; 21 | } 22 | 23 | export function listenerRouteChange( 24 | callback: (route: RouteLocationNormalized) => void, 25 | immediate = true, 26 | ) { 27 | emitter.on(key, callback); 28 | immediate && lastChangeTab && callback(lastChangeTab); 29 | } 30 | 31 | export function removeTabChangeListener() { 32 | emitter.clear(); 33 | } 34 | -------------------------------------------------------------------------------- /src/logics/theme/index.ts: -------------------------------------------------------------------------------- 1 | export async function changeTheme(_color: string) {} 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | export const PAGE_NOT_FOUND_NAME_CHILDREN = 'PageNotFoundChildren'; 7 | 8 | export const EXCEPTION_COMPONENT = () => import('@/views/sys/exception/Exception.vue'); 9 | 10 | /** 11 | * @description: default layout 12 | */ 13 | export const LAYOUT = () => import('@/layouts/default/index.vue'); 14 | 15 | /** 16 | * @description: parent-layout 17 | */ 18 | export const getParentLayout = (_name?: string) => { 19 | return () => 20 | new Promise((resolve) => { 21 | resolve({ 22 | name: _name || PARENT_LAYOUT_NAME, 23 | }); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /src/router/guard/stateGuard.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | import { useAppStore } from '@/store/modules/app'; 3 | import { useMultipleTabStore } from '@/store/modules/multipleTab'; 4 | import { useUserStore } from '@/store/modules/user'; 5 | import { usePermissionStore } from '@/store/modules/permission'; 6 | import { PageEnum } from '@/enums/pageEnum'; 7 | import { removeTabChangeListener } from '@/logics/mitt/routeChange'; 8 | 9 | export function createStateGuard(router: Router) { 10 | router.afterEach((to) => { 11 | // Just enter the login page and clear the authentication information 12 | if (to.path === PageEnum.BASE_LOGIN) { 13 | const tabStore = useMultipleTabStore(); 14 | const userStore = useUserStore(); 15 | const appStore = useAppStore(); 16 | const permissionStore = usePermissionStore(); 17 | appStore.resetAllState(); 18 | permissionStore.resetState(); 19 | tabStore.resetState(); 20 | userStore.resetState(); 21 | removeTabChangeListener(); 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createWebHistory, RouteRecordRaw, createRouter } from 'vue-router'; 2 | import type { App } from 'vue'; 3 | 4 | import { basicRoutes } from './routes'; 5 | 6 | // 白名单应该包含基本静态路由 7 | const WHITE_NAME_LIST: string[] = []; 8 | const getRouteNames = (array: any[]) => 9 | array.forEach((item) => { 10 | WHITE_NAME_LIST.push(item.name); 11 | getRouteNames(item.children || []); 12 | }); 13 | getRouteNames(basicRoutes); 14 | 15 | // app router 16 | // 创建一个可以被 Vue 应用程序使用的路由实例 17 | export const router = createRouter({ 18 | // 创建一个历史记录。 19 | history: createWebHistory(import.meta.env.VITE_PUBLIC_PATH), 20 | // 应该添加到路由的初始路由列表。 21 | routes: basicRoutes as unknown as RouteRecordRaw[], 22 | // 是否应该禁止尾部斜杠。默认为假 23 | strict: true, 24 | scrollBehavior: () => ({ left: 0, top: 0 }), 25 | }); 26 | 27 | // reset router 28 | export function resetRouter() { 29 | router.getRoutes().forEach((route) => { 30 | const { name } = route; 31 | if (name && !WHITE_NAME_LIST.includes(name as string)) { 32 | router.hasRoute(name) && router.removeRoute(name); 33 | } 34 | }); 35 | } 36 | 37 | // config router 38 | // 配置路由器 39 | export function setupRouter(app: App) { 40 | app.use(router); 41 | } 42 | -------------------------------------------------------------------------------- /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 SHOULD_ENABLE_STORAGE_ENCRYPTION = !isDevMode(); 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/settings/siteSetting.ts: -------------------------------------------------------------------------------- 1 | // github repo url 2 | export const GITHUB_URL = 'https://github.com/suyuan32/simple-admin-core'; 3 | 4 | // vue-vben-admin-next-doc 5 | export const DOC_URL = 'https://doc.ryansu.tech/'; 6 | 7 | // site url 8 | export const SITE_URL = 'https://doc.ryansu.tech/'; 9 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; 4 | 5 | const store = createPinia(); 6 | store.use(piniaPluginPersistedstate); 7 | 8 | export function setupStore(app: App) { 9 | app.use(store); 10 | } 11 | 12 | export { store }; 13 | -------------------------------------------------------------------------------- /src/store/modules/lock.ts: -------------------------------------------------------------------------------- 1 | import type { LockInfo } from '/#/store'; 2 | 3 | import { defineStore } from 'pinia'; 4 | import { LOCK_INFO_KEY } from '@/enums/cacheEnum'; 5 | 6 | interface LockState { 7 | lockInfo: Nullable; 8 | } 9 | 10 | export const useLockStore = defineStore({ 11 | id: 'app-lock', 12 | state: (): LockState => { 13 | return { 14 | lockInfo: null, 15 | }; 16 | }, 17 | getters: { 18 | getLockInfo(state): Nullable { 19 | return state.lockInfo; 20 | }, 21 | }, 22 | actions: { 23 | setLockInfo(info: LockInfo) { 24 | this.lockInfo = Object.assign({}, this.lockInfo, info); 25 | }, 26 | resetLockInfo() { 27 | this.lockInfo = null; 28 | }, 29 | // Unlock 30 | async unLock(password?: string) { 31 | if (this.lockInfo?.pwd === password) { 32 | this.resetLockInfo(); 33 | return true; 34 | } 35 | return false; 36 | }, 37 | }, 38 | persist: { 39 | key: LOCK_INFO_KEY, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /src/store/modules/role.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { RoleInfo } from '@/api/sys/model/roleModel'; 3 | import { getRoleList } from '@/api/sys/role'; 4 | 5 | interface RoleState { 6 | roleInfo: RoleInfo[]; 7 | } 8 | 9 | export const useRoleStore = defineStore('app-role', { 10 | state: (): RoleState => ({ 11 | roleInfo: [], 12 | }), 13 | getters: { 14 | getRoleInfo(): RoleInfo[] { 15 | return this.roleInfo; 16 | }, 17 | }, 18 | actions: { 19 | async getRoleInfoFromServer() { 20 | const roleInfo = await getRoleList({ 21 | page: 1, 22 | pageSize: 1000, 23 | }); 24 | this.roleInfo = roleInfo.data.data; 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/utils/helper/tsxHelper.tsx: -------------------------------------------------------------------------------- 1 | import { Slots } from 'vue'; 2 | import { RenderOpts } from '@/components/Form'; 3 | import { isFunction } from 'remeda'; 4 | 5 | /** 6 | * @description: Get slot to prevent empty error 7 | */ 8 | export function getSlot(slots: Slots, slot = 'default', data?: any, opts?: RenderOpts) { 9 | if (!slots || !Reflect.has(slots, slot)) { 10 | return null; 11 | } 12 | if (!isFunction(slots[slot])) { 13 | console.error(`${slot} is not a function!`); 14 | return null; 15 | } 16 | const slotFn = slots[slot]; 17 | if (!slotFn) return null; 18 | const params = { ...data, ...opts }; 19 | return slotFn(params); 20 | } 21 | 22 | /** 23 | * extends slots 24 | * @param slots 25 | * @param excludeKeys 26 | */ 27 | export function extendSlots(slots: Slots, excludeKeys: string[] = []) { 28 | const slotKeys = Object.keys(slots); 29 | const ret: any = {}; 30 | slotKeys.map((key) => { 31 | if (excludeKeys.includes(key)) { 32 | return null; 33 | } 34 | ret[key] = (data?: any) => getSlot(slots, key, data); 35 | }); 36 | return ret; 37 | } 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | const toString = Object.prototype.toString; 2 | 3 | export function is(val: unknown, type: string) { 4 | return toString.call(val) === `[object ${type}]`; 5 | } 6 | 7 | export function isDef(val?: T): val is T { 8 | return typeof val !== 'undefined'; 9 | } 10 | 11 | export function isWindow(val: any): val is Window { 12 | return typeof window !== 'undefined' && is(val, 'Window'); 13 | } 14 | 15 | export const isServer = typeof window === 'undefined'; 16 | 17 | export const isClient = !isServer; 18 | 19 | export function isHttpUrl(path: string): boolean { 20 | const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/; 21 | return reg.test(path); 22 | } 23 | 24 | export function upperFirst(str: string): string { 25 | return str.charAt(0).toUpperCase() + str.slice(1); 26 | } 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/utils/object.ts: -------------------------------------------------------------------------------- 1 | export function get(obj: object, path: string, defaultValue?: any) { 2 | const travel = (regexp: RegExp) => 3 | String.prototype.split 4 | .call(path, regexp) 5 | .filter(Boolean) 6 | .reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj); 7 | const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/); 8 | return result === undefined || result === obj ? defaultValue : result; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/propTypes.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, VNodeChild } from 'vue'; 2 | import { createTypes, VueTypeValidableDef, VueTypesInterface, toValidableType } from 'vue-types'; 3 | 4 | export type VueNode = VNodeChild | JSX.Element; 5 | 6 | type PropTypes = VueTypesInterface & { 7 | readonly style: VueTypeValidableDef; 8 | readonly VNodeChild: VueTypeValidableDef; 9 | }; 10 | const newPropTypes = createTypes({ 11 | func: undefined, 12 | bool: undefined, 13 | string: undefined, 14 | number: undefined, 15 | object: undefined, 16 | integer: undefined, 17 | }) as PropTypes; 18 | 19 | class propTypes extends newPropTypes { 20 | static override get style() { 21 | return toValidableType('style', { 22 | type: [String, Object], 23 | }); 24 | } 25 | 26 | static override get VNodeChild() { 27 | return toValidableType('VNodeChild', { 28 | type: undefined, 29 | }); 30 | } 31 | } 32 | export { propTypes }; 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/SiteAnalysis.vue: -------------------------------------------------------------------------------- 1 | 16 | 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 18 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/ProjectCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 24 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/QuickNav.vue: -------------------------------------------------------------------------------- 1 | 11 | 28 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 22 | -------------------------------------------------------------------------------- /src/views/mcms/emailProvider/email.data.ts: -------------------------------------------------------------------------------- 1 | import { getEmailProviderList } from '@/api/mcms/emailProvider'; 2 | import { FormSchema } from '@/components/Table'; 3 | import { useI18n } from '@/hooks/web/useI18n'; 4 | 5 | const { t } = useI18n(); 6 | 7 | export const formSchema: FormSchema[] = [ 8 | { 9 | field: 'target', 10 | label: t('mcms.email.targetAddress'), 11 | component: 'Input', 12 | required: true, 13 | }, 14 | { 15 | field: 'subject', 16 | label: t('mcms.email.subject'), 17 | component: 'Input', 18 | required: true, 19 | }, 20 | { 21 | field: 'content', 22 | label: t('mcms.email.content'), 23 | component: 'InputTextArea', 24 | required: true, 25 | }, 26 | { 27 | field: 'provider', 28 | label: t('mcms.emailLog.provider'), 29 | component: 'ApiSelect', 30 | required: true, 31 | defaultValue: 'tencent', 32 | componentProps: { 33 | api: getEmailProviderList, 34 | params: { 35 | page: 1, 36 | pageSize: 1000, 37 | }, 38 | resultField: 'data.data', 39 | labelField: 'name', 40 | valueField: 'name', 41 | }, 42 | }, 43 | ]; 44 | -------------------------------------------------------------------------------- /src/views/sys/exception/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Exception } from './Exception.vue'; 2 | -------------------------------------------------------------------------------- /src/views/sys/iframe/FrameBlank.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /src/views/sys/lock/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | -------------------------------------------------------------------------------- /src/views/sys/login/QrCodeForm.vue: -------------------------------------------------------------------------------- 1 | 17 | 31 | -------------------------------------------------------------------------------- /src/views/sys/profile/data.ts: -------------------------------------------------------------------------------- 1 | export interface FormData { 2 | avatar: string; 3 | nickname: string; 4 | email: string; 5 | mobile: string; 6 | } 7 | 8 | export interface ChangePasswordReq { 9 | oldPassword: string; 10 | newPassword: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/views/sys/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /types/codemirror.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@codemirror/lang-json'; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /types/tree.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@axolo/tree-array'; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /types/vueuseCore.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@vueuse/core'; 2 | -------------------------------------------------------------------------------- /uno.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, presetTypography, presetUno } from 'unocss'; 2 | 3 | export default defineConfig({ 4 | presets: [presetUno(), presetTypography()], 5 | }); 6 | --------------------------------------------------------------------------------